diff --git a/module/VuFind/src/VuFind/Search/Base/Options.php b/module/VuFind/src/VuFind/Search/Base/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..241695e06ab814c9d42de95020476d682613d3eb
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Base/Options.php
@@ -0,0 +1,501 @@
+<?php
+/**
+ * Abstract options search model.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFind\Search\Base;
+
+/**
+ * Abstract options search model.
+ *
+ * This abstract class defines the option methods for modeling a search in VuFind.
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+abstract class Options
+{
+    // Available sort options
+    protected $sortOptions = array();
+    protected $defaultSort = 'relevance';
+    protected $defaultSortByHandler = array();
+    protected $rssSort = null;
+
+    // Search options for the user
+    protected $defaultHandler = null;
+    protected $advancedHandlers = array();
+    protected $basicHandlers = array();
+    protected $specialAdvancedFacets = '';
+    protected $retainFiltersByDefault = true;
+
+    // Available limit options
+    protected $defaultLimit = 20;
+    protected $limitOptions = array();
+
+    // Available view options
+    protected $defaultView = 'list';
+    protected $viewOptions = array();
+
+    // Facet settings
+    protected $translatedFacets = array();
+
+    // Spelling
+    protected $spellcheck = true;
+
+    // Shard settings
+    protected $shards = array();
+    protected $defaultSelectedShards = array();
+    protected $visibleShardCheckboxes = false;
+
+    // Highlighting
+    protected $highlight = false;
+
+    // Autocomplete setting
+    protected $autocompleteEnabled = false;
+
+    // Configuration files to read search settings from
+    protected $searchIni = 'searches';
+    protected $facetsIni = 'facets';
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->limitOptions = array($this->defaultLimit);
+    }
+
+    /**
+     * Get string listing special advanced facet types.
+     *
+     * @return string
+     */
+    public function getSpecialAdvancedFacets()
+    {
+        return $this->specialAdvancedFacets;
+    }
+
+    /**
+     * Basic 'getter' for advanced search handlers.
+     *
+     * @return array
+     */
+    public function getAdvancedHandlers()
+    {
+        return $this->advancedHandlers;
+    }
+
+    /**
+     * Basic 'getter' for basic search handlers.
+     *
+     * @return array
+     */
+    public function getBasicHandlers()
+    {
+        return $this->basicHandlers;
+    }
+
+    /**
+     * Get default search handler.
+     *
+     * @return string
+     */
+    public function getDefaultHandler()
+    {
+        return $this->defaultHandler;
+    }
+
+    /**
+     * Get default limit setting.
+     *
+     * @return int
+     */
+    public function getDefaultLimit()
+    {
+        return $this->defaultLimit;
+    }
+
+    /**
+     * Get an array of limit options.
+     *
+     * @return array
+     */
+    public function getLimitOptions()
+    {
+        return $this->limitOptions;
+    }
+
+    /**
+     * Get the name of the ini file used for configuring facet parameters in this
+     * object.
+     *
+     * @return string
+     */
+    public function getFacetsIni()
+    {
+        return $this->facetsIni;
+    }
+
+    /**
+     * Get the name of the ini file used for configuring search parameters in this
+     * object.
+     *
+     * @return string
+     */
+    public function getSearchIni()
+    {
+        return $this->searchIni;
+    }
+
+    /**
+     * Override the limit options.
+     *
+     * @param array $options New options to set.
+     *
+     * @return void
+     */
+    public function setLimitOptions($options)
+    {
+        if (is_array($options) && !empty($options)) {
+            $this->limitOptions = $options;
+
+            // If the current default limit is no longer legal, pick the
+            // first option in the array as the new default:
+            if (!in_array($this->defaultLimit, $this->limitOptions)) {
+                $this->defaultLimit = $this->limitOptions[0];
+            }
+        }
+    }
+
+    /**
+     * Get an array of sort options.
+     *
+     * @return array
+     */
+    public function getSortOptions()
+    {
+        return $this->sortOptions;
+    }
+
+    /**
+     * Get the default sort option for the specified search handler.
+     *
+     * @param string $handler Search handler being used
+     *
+     * @return string
+     */
+    public function getDefaultSortByHandler($handler = null)
+    {
+        // Use default handler if none specified:
+        if (empty($handler)) {
+            $handler = $this->getDefaultHandler();
+        }
+        // Send back search-specific sort if available:
+        if (isset($this->defaultSortByHandler[$handler])) {
+            return $this->defaultSortByHandler[$handler];
+        }
+        // If no search-specific sort handler was found, use the overall default:
+        return $this->defaultSort;
+    }
+
+    /**
+     * Return the sorting value for RSS mode
+     *
+     * @param string $sort Sort setting to modify for RSS mode
+     *
+     * @return string
+     */
+    public function getRssSort($sort)
+    {
+        if (empty($this->rssSort)) {
+            return $sort;
+        }
+        if ($sort == 'relevance') {
+            return $this->rssSort;
+        }
+        return $this->rssSort . ',' . $sort;
+    }
+
+    /**
+     * Get default view setting.
+     *
+     * @return int
+     */
+    public function getDefaultView()
+    {
+        return $this->defaultView;
+    }
+
+    /**
+     * Get an array of view options.
+     *
+     * @return array
+     */
+    public function getViewOptions()
+    {
+        return $this->viewOptions;
+    }
+
+    /**
+     * Get a list of facets that are subject to translation.
+     *
+     * @return array
+     */
+    public function getTranslatedFacets()
+    {
+        return $this->translatedFacets;
+    }
+
+    /**
+     * Get current spellcheck setting and (optionally) change it.
+     *
+     * @param bool $bool True to enable, false to disable, null to leave alone
+     *
+     * @return bool
+     */
+    public function spellcheckEnabled($bool = null)
+    {
+        if (!is_null($bool)) {
+            $this->spellcheck = $bool;
+        }
+        return $this->spellcheck;
+    }
+
+    /**
+     * Is highlighting enabled?
+     *
+     * @return bool
+     */
+    public function highlightEnabled()
+    {
+        return $this->highlight;
+    }
+
+    /**
+     * Translate a field name to a displayable string for rendering a query in
+     * human-readable format:
+     *
+     * @param string $field Field name to display.
+     *
+     * @return string       Human-readable version of field name.
+     */
+    public function getHumanReadableFieldName($field)
+    {
+        if (isset($this->basicHandlers[$field])) {
+            return VF_Translator::translate($this->basicHandlers[$field]);
+        } else if (isset($this->advancedHandlers[$field])) {
+            return VF_Translator::translate($this->advancedHandlers[$field]);
+        } else {
+            return $field;
+        }
+    }
+
+    /**
+     * Turn off highlighting.
+     *
+     * @return void
+     */
+    public function disableHighlighting()
+    {
+        $this->highlight = false;
+    }
+
+    /**
+     * Is autocomplete enabled?
+     *
+     * @return bool
+     */
+    public function autocompleteEnabled()
+    {
+        return $this->autocompleteEnabled;
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    abstract public function getSearchAction();
+
+    /**
+     * Return an array describing the action used for performing advanced searches
+     * (same format as expected by the URL view helper).  Return false if the feature
+     * is not supported.
+     *
+     * @return array|bool
+     */
+    public function getAdvancedSearchAction()
+    {
+        // Assume unsupported by default:
+        return false;
+    }
+
+    /**
+     * Get a session namespace specific to the current class.
+     *
+     * @return Zend_Session_Namespace
+     */
+    public function getSession()
+    {
+        static $session = false;
+        if (!$session) {
+            $session = new Zend_Session_Namespace(get_class($this));
+        }
+        return $session;
+    }
+
+    /**
+     * Remember the last sort option used.
+     *
+     * @param string $last Option to remember.
+     *
+     * @return void
+     */
+    public function rememberLastSort($last)
+    {
+        $this->getSession()->lastSort = $last;
+    }
+
+    /**
+     * Retrieve the last sort option used.
+     *
+     * @return string
+     */
+    public function getLastSort()
+    {
+        $session = $this->getSession();
+        return isset($session->lastSort) ? $session->lastSort : null;
+    }
+
+    /**
+     * Remember the last limit option used.
+     *
+     * @param string $last Option to remember.
+     *
+     * @return void
+     */
+    public function rememberLastLimit($last)
+    {
+        $this->getSession()->lastLimit = $last;
+    }
+
+    /**
+     * Retrieve the last limit option used.
+     *
+     * @return string
+     */
+    public function getLastLimit()
+    {
+        $session = $this->getSession();
+        return isset($session->lastLimit) ? $session->lastLimit : null;
+    }
+
+    /**
+     * Remember the last view option used.
+     *
+     * @param string $last Option to remember.
+     *
+     * @return void
+     */
+    public function rememberLastView($last)
+    {
+        $this->getSession()->lastView = $last;
+    }
+
+    /**
+     * Retrieve the last view option used.
+     *
+     * @return string
+     */
+    public function getLastView()
+    {
+        $session = $this->getSession();
+        return isset($session->lastView) ? $session->lastView : null;
+    }
+
+    /**
+     * Should filter settings be retained across searches by default?
+     *
+     * @return bool
+     */
+    public function getRetainFilterSetting()
+    {
+        return $this->retainFiltersByDefault;
+    }
+
+    /**
+     * Get an associative array of available shards (key = internal VuFind ID for
+     * this shard; value = details needed to connect to shard; empty for non-sharded
+     * data sources).
+     *
+     * Although this mechanism was originally designed for Solr's sharding
+     * capabilities, it could also be useful for multi-database search situations
+     * (i.e. federated search, EBSCO's API, etc., etc.).
+     *
+     * @return array
+     */
+    public function getShards()
+    {
+        return $this->shards;
+    }
+
+    /**
+     * Get an array of default selected shards (values correspond with keys returned
+     * by getShards().
+     *
+     * @return array
+     */
+    public function getDefaultSelectedShards()
+    {
+        return $this->defaultSelectedShards;
+    }
+
+    /**
+     * Should we display shard checkboxes for this object?
+     *
+     * @return bool
+     */
+    public function showShardCheckboxes()
+    {
+        return $this->visibleShardCheckboxes;
+    }
+
+    /**
+     * If there is a limit to how many search results a user can access, this
+     * method will return that limit.  If there is no limit, this will return
+     * -1.
+     *
+     * @return int
+     */
+    public function getVisibleSearchResultLimit()
+    {
+        // No limit by default:
+        return -1;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Base/Params.php b/module/VuFind/src/VuFind/Search/Base/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..1ff62b0ee8d07b4bf8eaea762769c6bc57e43528
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Base/Params.php
@@ -0,0 +1,1459 @@
+<?php
+/**
+ * Abstract parameters search model.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Abstract parameters search model.
+ *
+ * This abstract class defines the parameters methods for modeling a search in VuFind
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Base_Params
+{
+    // Search terms
+    protected $searchTerms = array();
+    // Page number
+    protected $page = 1;
+    // Sort settings
+    protected $sort = null;
+    protected $skipRssSort = false;
+    // Result limit
+    protected $limit = 20;
+    protected $searchType  = 'basic';
+    // Shards
+    protected $selectedShards = array();
+    // View
+    protected $view = null;
+    // VF_Search_Base_Options subclass
+    protected $options;
+    // Recommendation settings
+    protected $recommend = array();
+    protected $recommendationEnabled = false;
+    // Facet settings
+    protected $facetConfig = array();
+    protected $checkboxFacets = array();
+    protected $filterList = array();
+
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Options $options Options to use (null to load defaults)
+     */
+    public function __construct($options = null)
+    {
+        if (is_null($options)) {
+            // Create a copy of the default configuration:
+            $this->options = clone(
+                VF_Search_Options::getInstance($this->getSearchClassId())
+            );
+        } else {
+            $this->options = $options;
+        }
+    }
+
+    /**
+     * Copy constructor
+     *
+     * @return void
+     */
+    public function __clone()
+    {
+        $this->options = clone($this->options);
+    }
+
+    /**
+     * Get the identifier used for naming the various search classes in this family.
+     *
+     * @return string
+     */
+    public function getSearchClassId()
+    {
+        return VF_Search_Options::extractSearchClassId(get_class($this));
+    }
+
+    /**
+     * External method caller (proxies unrecognized methods to options object)
+     *
+     * @param string $methodName Name of function trying to be called
+     * @param Array  $params     Parameters to be sent to the function
+     *
+     * @return mixed
+     */
+    public function __call($methodName, $params)
+    {
+        // Proxy undefined methods to the options object:
+        $method = array($this->options, $methodName);
+        if (!is_callable($method)) {
+            throw new Exception($methodName . ' cannot be called.');
+        }
+        return call_user_func_array($method, $params);
+    }
+
+
+    /**
+     * Pull the search parameters
+     *
+     * @param Zend_Controller_Request_Abstract $request the search object
+     *
+     * @return string
+     */
+    public function initFromRequest($request)
+    {
+        // We should init view first, since RSS view may cause certain variant
+        // behaviors:
+        $this->initView($request);
+        $this->initLimit($request);
+        $this->initPage($request);
+        $this->initShards($request);
+        // We have to initialize sort after search, since the search options may
+        // affect the default sort option.
+        $this->initSearch($request);
+        $this->initSort($request);
+        $this->initFilters($request);
+
+        // Always initialize recommendations last (since they rely on knowing
+        // other search settings that were set above).
+        $this->initRecommendations($request);
+
+        // Remember the user's settings for future reference (we only want to do
+        // this in initFromRequest, since other code may call the set methods from
+        // other contexts!):
+        $this->rememberLastLimit($this->getLimit());
+        $this->rememberLastSort($this->getSort());
+    }
+
+    /**
+     * Pull shard parameters from the request or set defaults
+     *
+     * @param Zend_Controller_Request_Abstract $request the search object
+     *
+     * @return void
+     */
+    protected function initShards($request)
+    {
+        $legalShards = array_keys($this->options->getShards());
+        $requestShards = $request->getParam('shard', array());
+        if (!is_array($requestShards)) {
+            $requestShards = array($requestShards);
+        }
+
+        // If a shard selection list is found as an incoming parameter,
+        // we should save valid values for future reference:
+        foreach ($requestShards as $current) {
+            if (in_array($current, $legalShards)) {
+                $this->selectedShards[] = $current;
+            }
+        }
+
+        // If we got this far and still have no selections established, revert to
+        // defaults:
+        if (empty($this->selectedShards)) {
+            $this->selectedShards = $this->options->getDefaultSelectedShards();
+        }
+    }
+
+    /**
+     * Pull the page size parameter or set to default
+     *
+     * @param Zend_Controller_Request_Abstract $request the search object
+     *
+     * @return void
+     */
+    protected function initLimit($request)
+    {
+        // Check for a limit parameter in the url.
+        $defaultLimit = $this->options->getDefaultLimit();
+        if (($limit = $request->getParam('limit')) != $defaultLimit) {
+            // make sure the url parameter is a valid limit
+            if (in_array($limit, $this->options->getLimitOptions())) {
+                $this->limit = $limit;
+                return;
+            }
+        }
+
+        // Increase default limit for RSS mode:
+        if ($this->getView() == 'rss' && $defaultLimit < 50) {
+            $defaultLimit = 50;
+        }
+
+        // If we got this far, setting was missing or invalid; load the default
+        $this->limit = $defaultLimit;
+    }
+
+    /**
+     * Pull the page parameter
+     *
+     * @param Zend_Controller_Request_Abstract $request the search object
+     *
+     * @return void
+     */
+    protected function initPage($request)
+    {
+        $this->page = intval($request->getParam('page'));
+        if ($this->page < 1) {
+            $this->page = 1;
+        }
+    }
+
+    /**
+     * Initialize the object's search settings from a request object.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initSearch($request)
+    {
+        // Try to initialize a basic search; if that fails, try for an advanced
+        // search next!
+        if (!$this->initBasicSearch($request)) {
+            $this->initAdvancedSearch($request);
+        }
+
+        // If no search parameters were found, default to an "all records" search:
+        if (count($this->searchTerms) == 0) {
+            // Treat it as an empty basic search
+            $this->setBasicSearch('');
+        }
+    }
+
+    /**
+     * Support method for initSearch() -- handle basic settings.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return boolean True if search settings were found, false if not.
+     */
+    protected function initBasicSearch($request)
+    {
+        // If no lookfor parameter was found, we have no search terms to
+        // add to our array!
+        if (is_null($lookfor = $request->getParam('lookfor'))) {
+            return false;
+        }
+
+        // If lookfor is an array, we may be dealing with a legacy Advanced
+        // Search URL.  If there's only one parameter, we can flatten it,
+        // but otherwise we should treat it as an error -- no point in going
+        // to great lengths for compatibility.
+        if (is_array($lookfor)) {
+            if (count($lookfor) > 1) {
+                throw new Exception("Unsupported search URL.");
+            }
+            $lookfor = $lookfor[0];
+        }
+
+        // Flatten type arrays for backward compatibility:
+        $handler = $request->getParam('type');
+        if (is_array($handler)) {
+            $handler = $handler[0];
+        }
+
+        // Set the search:
+        $this->setBasicSearch($lookfor, $handler);
+        return true;
+    }
+
+    /**
+     * Set a basic search query:
+     *
+     * @param string $lookfor The search query
+     * @param string $handler The search handler (null for default)
+     *
+     * @return void
+     */
+    public function setBasicSearch($lookfor, $handler = null)
+    {
+        $this->searchType = 'basic';
+
+        if (empty($handler)) {
+            $handler = $this->options->getDefaultHandler();
+        }
+
+        $this->searchTerms = array(
+            array(
+                'index'   => $handler,
+                'lookfor' => $lookfor
+            )
+        );
+    }
+
+    /**
+     * Support method for initSearch() -- handle advanced settings.  Advanced
+     * searches have numeric subscripts on the lookfor and type parameters --
+     * this is how they are distinguished from basic searches.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initAdvancedSearch($request)
+    {
+        $this->searchType = 'advanced';
+
+        $groupCount = 0;
+        // Loop through each search group
+        while (!is_null($lookfor = $request->getParam('lookfor' . $groupCount))) {
+            $group = array();
+            // Loop through each term inside the group
+            for ($i = 0; $i < count($lookfor); $i++) {
+                // Ignore advanced search fields with no lookup
+                if ($lookfor[$i] != '') {
+                    // Use default fields if not set
+                    $typeArr = $request->getParam('type' . $groupCount);
+                    if (isset($typeArr[$i]) && !empty($typeArr[$i])) {
+                        $handler = $typeArr[$i];
+                    } else {
+                        $handler = $this->options->getDefaultHandler();
+                    }
+
+                    // Add term to this group
+                    $boolArr = $request->getParam('bool' . $groupCount);
+                    $group[] = array(
+                        'field'   => $handler,
+                        'lookfor' => $lookfor[$i],
+                        'bool'    => isset($boolArr[0]) ? $boolArr[0] : null
+                    );
+                }
+            }
+
+            // Make sure we aren't adding groups that had no terms
+            if (count($group) > 0) {
+                // Add the completed group to the list
+                $this->searchTerms[] = array(
+                    'group' => $group,
+                    'join'  => $request->getParam('join')
+                );
+            }
+
+            // Increment
+            $groupCount++;
+        }
+    }
+
+    /**
+     * Get the value for which type of sorting to use
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return string
+     */
+    protected function initSort($request)
+    {
+        // Check for special parameter only relevant in RSS mode:
+        if ($request->getParam('skip_rss_sort', 'unset') != 'unset') {
+            $this->skipRssSort = true;
+        }
+        $this->setSort($request->getParam('sort'));
+    }
+
+    /**
+     * Get the value for which results view to use
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return string
+     */
+    protected function initView($request)
+    {
+        // Check for a view parameter in the url.
+        $view = $request->getParam('view');
+        $lastView = $this->getLastView();
+        if (!empty($view)) {
+            if ($view == 'rss') {
+                // we don't want to store rss in the Session
+                $this->setView('rss');
+            } else {
+                // store non-rss views in Session for persistence
+                $validViews = $this->getViewOptions();
+                // make sure the url parameter is a valid view
+                if (in_array($view, array_keys($validViews))) {
+                    $this->setView($view);
+                    $this->rememberLastView($view);
+                } else {
+                    $this->setView($this->getDefaultView());
+                }
+            }
+        } else if (!empty($lastView)) {
+            // if there is nothing in the URL, check the Session
+            $this->setView($lastView);
+        } else {
+            // otherwise load the default
+            $this->setView($this->getDefaultView());
+        }
+    }
+
+    /**
+     * Return the default sorting value
+     *
+     * @return string
+     */
+    public function getDefaultSort()
+    {
+        return $this->getDefaultSortByHandler($this->getSearchHandler());
+    }
+
+    /**
+     * Return the current limit value
+     *
+     * @return int
+     */
+    public function getLimit()
+    {
+        return $this->limit;
+    }
+
+    /**
+     * Change the value of the limit
+     *
+     * @param int $l New limit value.
+     *
+     * @return void
+     */
+    public function setLimit($l)
+    {
+        $this->limit = $l;
+    }
+
+    /**
+     * Change the page
+     *
+     * @param int $p New page value.
+     *
+     * @return void
+     */
+    public function setPage($p)
+    {
+        $this->page = $p;
+    }
+
+    /**
+     * Get the page value
+     *
+     * @return int
+     */
+    public function getPage()
+    {
+        return $this->page;
+    }
+
+    /**
+     * Return the sorting value
+     *
+     * @return int
+     */
+    public function getSort()
+    {
+        return $this->sort;
+    }
+
+    /**
+     * Set the sorting value (note: sort will be set to default if an illegal
+     * or empty value is passed in).
+     *
+     * @param string $sort  New sort value (null for default)
+     * @param bool   $force Set sort value without validating it?
+     *
+     * @return void
+     */
+    public function setSort($sort, $force = false)
+    {
+        // Skip validation if requested:
+        if ($force) {
+            $this->sort = $sort;
+            return;
+        }
+
+        // Validate and assign the sort value:
+        $valid = array_keys($this->getSortOptions());
+        if (!empty($sort) && in_array($sort, $valid)) {
+            $this->sort = $sort;
+        } else {
+            $this->sort = $this->getDefaultSort();
+        }
+
+        // In RSS mode, we may want to adjust sort settings:
+        if (!$this->skipRssSort && $this->getView() == 'rss') {
+            $this->sort = $this->getRssSort($this->sort);
+        }
+    }
+
+    /**
+     * Return the selected search handler (null for complex searches which have no
+     * single handler)
+     *
+     * @return string|null
+     */
+    public function getSearchHandler()
+    {
+        // We can only definitively name a handler if we have a basic search:
+        if (count($this->searchTerms) == 1
+            && isset($this->searchTerms[0]['index'])
+        ) {
+            return $this->searchTerms[0]['index'];
+        }
+        return null;
+    }
+
+    /**
+     * Return the search type (i.e. basic or advanced)
+     *
+     * @return string
+     */
+    public function getSearchType()
+    {
+        return $this->searchType;
+    }
+
+    /**
+     * Return the search terms
+     *
+     * @return string
+     */
+    public function getSearchTerms()
+    {
+        return $this->searchTerms;
+    }
+
+    /**
+     * Return the value for which search view we use
+     *
+     * @return string
+     */
+    public function getView()
+    {
+        return is_null($this->view) ? $this->getDefaultView() : $this->view;
+    }
+
+    /**
+     * Set the value for which search view we use
+     *
+     * @param String $v New view setting
+     *
+     * @return void
+     */
+    public function setView($v)
+    {
+        $this->view = $v;
+    }
+
+    /**
+     * Get a human-readable presentation version of the advanced search query
+     * stored in the object.  This will not work if $this->searchType is not
+     * 'advanced.'
+     *
+     * @return string
+     */
+    protected function buildAdvancedDisplayQuery()
+    {
+        // Groups and exclusions. This mirrors some logic in Solr.php
+        $groups   = array();
+        $excludes = array();
+
+        foreach ($this->searchTerms as $search) {
+            $thisGroup = array();
+            // Process each search group
+            if (isset($search['group'])) {
+                foreach ($search['group'] as $group) {
+                    // Build this group individually as a basic search
+                    $thisGroup[] = $this->getHumanReadableFieldName($group['field'])
+                        . ":{$group['lookfor']}";
+                }
+            }
+            // Is this an exclusion (NOT) group or a normal group?
+            if (isset($search['group'][0]['bool'])
+                && $search['group'][0]['bool'] == 'NOT'
+            ) {
+                $excludes[]
+                    = join(' ' . VF_Translator::translate('OR') . ' ', $thisGroup);
+            } else if (isset($search['group'][0]['bool'])) {
+                $groups[] = join(
+                    " " . VF_Translator::translate($search['group'][0]['bool'])." ",
+                    $thisGroup
+                );
+            }
+        }
+
+        // Base 'advanced' query
+        $output = '';
+        if (isset($this->searchTerms[0]['join'])) {
+            $output .= "(" .
+                join(
+                    ") " .
+                    VF_Translator::translate($this->searchTerms[0]['join']) . " (",
+                    $groups
+                ) .
+                ")";
+        }
+
+        // Concatenate exclusion after that
+        if (count($excludes) > 0) {
+            $output .= ' ' .
+                VF_Translator::translate('NOT') . ' ((' .
+                join(') ' . VF_Translator::translate('OR') . ' (', $excludes) . "))";
+        }
+
+        return $output;
+    }
+
+    /**
+     * Build a string for onscreen display showing the
+     *   query used in the search (not the filters).
+     *
+     * @return string user friendly version of 'query'
+     */
+    public function getDisplayQuery()
+    {
+        if ($this->searchType == 'advanced') {
+            return $this->buildAdvancedDisplayQuery();
+        }
+
+        // Default -- Basic search:
+        return isset($this->searchTerms[0]['lookfor'])
+            ? $this->searchTerms[0]['lookfor'] : '';
+    }
+
+    /**
+     * Get an array of recommendation objects for augmenting the results display.
+     *
+     * @param string $location Name of location to use as a filter (null to get
+     * associative array of all locations); legal non-null values: 'top', 'side'
+     *
+     * @return array
+     */
+    public function getRecommendations($location = 'top')
+    {
+        if (!$this->recommendationsEnabled()) {
+            return array();
+        }
+        if (is_null($location)) {
+            return $this->recommend;
+        }
+        return isset($this->recommend[$location])
+            ? $this->recommend[$location] : array();
+    }
+
+    /**
+     * Set the enabled status of recommendation modules -- it is often useful to turn
+     * off recommendations when retrieving results in a context other than standard
+     * display of results.
+     *
+     * @param bool $bool True to enable, false to disable (null to leave unchanged)
+     *
+     * @return bool      Current state of recommendations
+     */
+    public function recommendationsEnabled($bool = null)
+    {
+        if (!is_null($bool)) {
+            $this->recommendationEnabled = $bool;
+        }
+        return $this->recommendationEnabled;
+    }
+
+    /**
+     * Load all recommendation settings from the relevant ini file.  Returns an
+     * associative array where the key is the location of the recommendations (top
+     * or side) and the value is the settings found in the file (which may be either
+     * a single string or an array of strings).
+     *
+     * @return array associative: location (top/side) => search settings
+     */
+    protected function getRecommendationSettings()
+    {
+        // Bypass settings if recommendations are disabled.
+        if (!$this->recommendationsEnabled()) {
+            return array();
+        }
+
+        // Load the necessary settings to determine the appropriate recommendations
+        // module:
+        $searchSettings = VF_Config_Reader::getConfig($this->getSearchIni());
+
+        // If we have a search type set, save it so we can try to load a
+        // type-specific recommendations module:
+        $handler = $this->getSearchHandler();
+
+        // Load a type-specific recommendations setting if possible, or the default
+        // otherwise:
+        $recommend = array();
+        if (!is_null($handler)
+            && isset($searchSettings->TopRecommendations->$handler)
+        ) {
+            $recommend['top'] = $searchSettings->TopRecommendations
+                ->$handler->toArray();
+        } else {
+            $recommend['top']
+                = isset($searchSettings->General->default_top_recommend)
+                ? $searchSettings->General->default_top_recommend->toArray()
+                : false;
+        }
+        if (!is_null($handler)
+            && isset($searchSettings->SideRecommendations->$handler)
+        ) {
+            $recommend['side'] = $searchSettings->SideRecommendations
+                ->$handler->toArray();
+        } else {
+            $recommend['side']
+                = isset($searchSettings->General->default_side_recommend)
+                ? $searchSettings->General->default_side_recommend->toArray()
+                : false;
+        }
+        if (!is_null($handler)
+            && isset($searchSettings->NoResultsRecommendations->$handler)
+        ) {
+            $recommend['noresults'] = $searchSettings->NoResultsRecommendations
+                ->$handler->toArray();
+        } else {
+            $recommend['noresults']
+                = isset($searchSettings->General->default_noresults_recommend)
+                ? $searchSettings->General->default_noresults_recommend->toArray()
+                : false;
+        }
+
+        return $recommend;
+    }
+
+    /**
+     * Initialize the recommendations modules.
+     *
+     * @param Zend_Controller_Request_Abstract $request Zend request object
+     *
+     * @return void
+     */
+    protected function initRecommendations($request)
+    {
+        // If no settings were found, quit now:
+        $settings = $this->getRecommendationSettings();
+        if (empty($settings)) {
+            return;
+        }
+
+        // Process recommendations for each location:
+        $this->recommend = array(
+            'top' => array(), 'side' => array(), 'noresults' => array()
+        );
+        foreach ($settings as $location => $currentSet) {
+            // If the current location is disabled, skip processing!
+            if (empty($currentSet)) {
+                continue;
+            }
+            // Make sure the current location's set of recommendations is an array;
+            // if it's a single string, this normalization will simplify processing.
+            if (!is_array($currentSet)) {
+                $currentSet = array($currentSet);
+            }
+            // Now loop through all recommendation settings for the location.
+            foreach ($currentSet as $current) {
+                // Break apart the setting into module name and extra parameters:
+                $current = explode(':', $current);
+                $module = array_shift($current);
+                $class = 'VF_Recommend_' . $module;
+                $params = implode(':', $current);
+
+                // Build a recommendation module with the provided settings.
+                if (@class_exists($class)) {
+                    $obj = new $class($params);
+                    $obj->init($this, $request);
+                    $this->recommend[$location][] = $obj;
+                } else {
+                    throw new Exception(
+                        'Could not load recommendation module: ' . $class
+                    );
+                }
+            }
+        }
+    }
+
+    /**
+     * Parse apart the field and value from a URL filter string.
+     *
+     * @param string $filter A filter string from url : "field:value"
+     *
+     * @return array         Array with elements 0 = field, 1 = value.
+     */
+    public function parseFilter($filter)
+    {
+        // Split the string and assign the parts to $field and $value
+        $temp = explode(':', $filter, 2);
+        $field = array_shift($temp);
+        $value = count($temp) > 0 ? $temp[0] : '';
+
+        // Remove quotes from the value if there are any
+        if (substr($value, 0, 1)  == '"') {
+            $value = substr($value, 1);
+        }
+        if (substr($value, -1, 1) == '"') {
+            $value = substr($value, 0, -1);
+        }
+        // One last little clean on whitespace
+        $value = trim($value);
+
+        // Send back the results:
+        return array($field, $value);
+    }
+
+    /**
+     * Does the object already contain the specified filter?
+     *
+     * @param string $filter A filter string from url : "field:value"
+     *
+     * @return bool
+     */
+    public function hasFilter($filter)
+    {
+        // Extract field and value from URL string:
+        list($field, $value) = $this->parseFilter($filter);
+
+        if (isset($this->filterList[$field])
+            && in_array($value, $this->filterList[$field])
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Take a filter string and add it into the protected
+     *   array checking for duplicates.
+     *
+     * @param string $newFilter A filter string from url : "field:value"
+     *
+     * @return void
+     */
+    public function addFilter($newFilter)
+    {
+        // Extract field and value from URL string:
+        list($field, $value) = $this->parseFilter($newFilter);
+
+        // Check for duplicates -- if it's not in the array, we can add it
+        if (!$this->hasFilter($newFilter)) {
+            $this->filterList[$field][] = $value;
+        }
+    }
+
+    /**
+     * Remove a filter from the list.
+     *
+     * @param string $oldFilter A filter string from url : "field:value"
+     *
+     * @return void
+     */
+    public function removeFilter($oldFilter)
+    {
+        // Extract field and value from URL string:
+        list($field, $value) = $this->parseFilter($oldFilter);
+
+        // Make sure the field exists
+        if (isset($this->filterList[$field])) {
+            // Assume by default that we will not need to rebuild the array:
+            $rebuildArray = false;
+
+            // Loop through all filters on the field
+            for ($i = 0; $i < count($this->filterList[$field]); $i++) {
+                // Does it contain the value we don't want?
+                if ($this->filterList[$field][$i] == $value) {
+                    // If so remove it.
+                    unset($this->filterList[$field][$i]);
+
+                    // Flag that we now need to rebuild the array:
+                    $rebuildArray = true;
+                }
+            }
+
+            // If necessary, rebuild the array to remove gaps in the key sequence:
+            if ($rebuildArray) {
+                $this->filterList[$field] = array_values($this->filterList[$field]);
+            }
+        }
+    }
+
+    /**
+     * Remove all filters from the list.
+     *
+     * @param string $field Name of field to remove filters from (null to remove
+     * all filters from all fields)
+     *
+     * @return void
+     */
+    public function removeAllFilters($field = null)
+    {
+        if ($field == null) {
+            $this->filterList = array();
+        } else {
+            $this->filterList[$field] = array();
+        }
+    }
+
+    /**
+     * Add a field to facet on.
+     *
+     * @param string $newField Field name
+     * @param string $newAlias Optional on-screen display label
+     *
+     * @return void
+     */
+    public function addFacet($newField, $newAlias = null)
+    {
+        if ($newAlias == null) {
+            $newAlias = $newField;
+        }
+        $this->facetConfig[$newField] = $newAlias;
+    }
+
+    /**
+     * Add a checkbox facet.  When the checkbox is checked, the specified filter
+     * will be applied to the search.  When the checkbox is not checked, no filter
+     * will be applied.
+     *
+     * @param string $filter [field]:[value] pair to associate with checkbox
+     * @param string $desc   Description to associate with the checkbox
+     *
+     * @return void
+     */
+    public function addCheckboxFacet($filter, $desc)
+    {
+        // Extract the facet field name from the filter, then add the
+        // relevant information to the array.
+        list($fieldName) = explode(':', $filter);
+        $this->checkboxFacets[$fieldName]
+            = array('desc' => $desc, 'filter' => $filter);
+    }
+
+    /**
+     * Get a user-friendly string to describe the provided facet field.
+     *
+     * @param string $field Facet field name.
+     *
+     * @return string       Human-readable description of field.
+     */
+    public function getFacetLabel($field)
+    {
+        return isset($this->facetConfig[$field])
+            ? $this->facetConfig[$field] : "Other";
+    }
+
+    /**
+     * Get the current facet configuration.
+     *
+     * @return array
+     */
+    public function getFacetConfig()
+    {
+        return $this->facetConfig;
+    }
+
+    /**
+     * Reset the current facet configuration.
+     *
+     * @return void
+     */
+    public function resetFacetConfig()
+    {
+        $this->facetConfig = array();
+    }
+
+    /**
+     * Get the raw filter list.
+     *
+     * @return array
+     */
+    public function getFilters()
+    {
+        return $this->filterList;
+    }
+
+    /**
+     * Return an array structure containing all current filters
+     *    and urls to remove them.
+     *
+     * @param bool $excludeCheckboxFilters Should we exclude checkbox filters from
+     * the list (to be used as a complement to getCheckboxFacets()).
+     *
+     * @return array                       Field, values and translation status
+     */
+    public function getFilterList($excludeCheckboxFilters = false)
+    {
+        // Get a list of checkbox filters to skip if necessary:
+        $skipList = array();
+        if ($excludeCheckboxFilters) {
+            foreach ($this->checkboxFacets as $current) {
+                list($field, $value) = $this->parseFilter($current['filter']);
+                if (!isset($skipList[$field])) {
+                    $skipList[$field] = array();
+                }
+                $skipList[$field][] = $value;
+            }
+        }
+
+        $list = array();
+        // Loop through all the current filter fields
+        foreach ($this->filterList as $field => $values) {
+            // and each value currently used for that field
+            $translate = in_array($field, $this->getTranslatedFacets());
+            foreach ($values as $value) {
+                // Add to the list unless it's in the list of fields to skip:
+                if (!isset($skipList[$field])
+                    || !in_array($value, $skipList[$field])
+                ) {
+                    $facetLabel = $this->getFacetLabel($field);
+                    $list[$facetLabel][] = array(
+                        'value'       => $value,
+                        'displayText' =>
+                            $translate ? VF_Translator::translate($value) : $value,
+                        'field'       => $field
+                    );
+                }
+            }
+        }
+        return $list;
+    }
+
+    /**
+     * Get information on the current state of the boolean checkbox facets.
+     *
+     * @return array
+     */
+    public function getCheckboxFacets()
+    {
+        // Build up an array of checkbox facets with status booleans and
+        // toggle URLs.
+        $facets = array();
+        foreach ($this->checkboxFacets as $field => $details) {
+            $facets[$field] = $details;
+            if ($this->hasFilter($details['filter'])) {
+                $facets[$field]['selected'] = true;
+            } else {
+                $facets[$field]['selected'] = false;
+            }
+            // Is this checkbox always visible, even if non-selected on the
+            // "no results" screen?  By default, no (may be overridden by
+            // child classes).
+            $facets[$field]['alwaysVisible'] = false;
+        }
+        return $facets;
+    }
+
+    /**
+     * Support method for initDateFilters() -- normalize a year for use in a date
+     * range.
+     *
+     * @param string $year Value to check for valid year.
+     *
+     * @return string      Formatted year.
+     */
+    protected function formatYearForDateRange($year)
+    {
+        // Make sure parameter is set and numeric; default to wildcard otherwise:
+        $year = preg_match('/\d{2,4}/', $year) ? $year : '*';
+
+        // Pad to four digits:
+        if (strlen($year) == 2) {
+            $year = '19' . $year;
+        } else if (strlen($year) == 3) {
+            $year = '0' . $year;
+        }
+
+        return $year;
+    }
+
+    /**
+     * Support method for initDateFilters() -- build a filter query based on a range
+     * of dates.
+     *
+     * @param string $field field to use for filtering.
+     * @param string $from  year for start of range.
+     * @param string $to    year for end of range.
+     *
+     * @return string       filter query.
+     */
+    protected function buildDateRangeFilter($field, $from, $to)
+    {
+        // Make sure that $to is less than $from:
+        if ($to != '*' && $from!= '*' && $to < $from) {
+            $tmp = $to;
+            $to = $from;
+            $from = $tmp;
+        }
+
+        // Assume Solr syntax -- this should be overridden in child classes where
+        // other indexing methodologies are used.
+        return "{$field}:[{$from} TO {$to}]";
+    }
+
+    /**
+     * Support method for initFilters() -- initialize date-related filters.  Factored
+     * out as a separate method so that it can be more easily overridden by child
+     * classes.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initDateFilters($request)
+    {
+        $daterange = $request->getParam('daterange');
+        if (!empty($daterange)) {
+            $ranges = is_array($daterange) ? $daterange : array($daterange);
+            foreach ($ranges as $range) {
+                // Validate start and end of range:
+                $yearFrom = $this->formatYearForDateRange(
+                    $request->getParam($range . 'from')
+                );
+                $yearTo = $this->formatYearForDateRange(
+                    $request->getParam($range . 'to')
+                );
+
+                // Build filter only if necessary:
+                if (!empty($range) && ($yearFrom != '*' || $yearTo != '*')) {
+                    $dateFilter
+                        = $this->buildDateRangeFilter($range, $yearFrom, $yearTo);
+                    $this->addFilter($dateFilter);
+                }
+            }
+        }
+    }
+
+    /**
+     * Add filters to the object based on values found in the request object.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initFilters($request)
+    {
+        // Handle standard filters:
+        $filter = $request->getParam('filter');
+        if (!empty($filter)) {
+            if (is_array($filter)) {
+                foreach ($filter as $current) {
+                    $this->addFilter($current);
+                }
+            } else {
+                $this->addFilter($filter);
+            }
+        }
+
+        // Handle date range filters:
+        $this->initDateFilters($request);
+    }
+
+    /**
+     * Replace a search term in the query
+     *
+     * @param string $from Search term to find
+     * @param string $to   Search term to insert
+     *
+     * @return void
+     */
+    public function replaceSearchTerm($from, $to)
+    {
+        // Escape $from so it is regular expression safe (just in case it
+        // includes any weird punctuation -- unlikely but possible):
+        $from = addcslashes($from, '\^$.[]|()?*+{}/');
+
+        // If our "from" pattern contains non-word characters, we can't use word
+        // boundaries for matching.  We want to try to use word boundaries when
+        // possible, however, to avoid the replacement from affecting unexpected
+        // parts of the search query.
+        if (!preg_match('/.*[^\w].*/', $from)) {
+            $pattern = "/\b$from\b/i";
+        } else {
+            $pattern = "/$from/i";
+        }
+
+        // Advanced search
+        if (isset($this->searchTerms[0]['group'])) {
+            for ($i = 0; $i < count($this->searchTerms); $i++) {
+                for ($j = 0; $j < count($this->searchTerms[$i]['group']); $j++) {
+                    $this->searchTerms[$i]['group'][$j]['lookfor']
+                        = preg_replace(
+                            $pattern, $to,
+                            $this->searchTerms[$i]['group'][$j]['lookfor']
+                        );
+                }
+            }
+        } else {
+            // Basic search
+            for ($i = 0; $i < count($this->searchTerms); $i++) {
+                // Perform the replacement:
+                $this->searchTerms[$i]['lookfor'] = preg_replace(
+                    $pattern, $to, $this->searchTerms[$i]['lookfor']
+                );
+            }
+        }
+    }
+
+    /**
+     * Return a query string for the current search with a search term replaced.
+     *
+     * @param string $oldTerm The old term to replace
+     * @param string $newTerm The new term to search
+     *
+     * @return string         query string
+     */
+    public function getDisplayQueryWithReplacedTerm($oldTerm, $newTerm)
+    {
+        // Stash our old data for a minute
+        $oldTerms = $this->searchTerms;
+        // Replace the search term
+        $this->replaceSearchTerm($oldTerm, $newTerm);
+        // Get the new query string
+        $query = $this->getDisplayQuery();
+        // Restore the old data
+        $this->searchTerms = $oldTerms;
+        // Return the query string
+        return $query;
+    }
+
+    /**
+     * Find a word amongst the current search terms
+     *
+     * @param string $needle Search term to find
+     *
+     * @return bool          True/False if the word was found
+     */
+    public function findSearchTerm($needle)
+    {
+        // Escape slashes in $needle to avoid regular expression errors:
+        $needle = str_replace('/', '\/', $needle);
+
+        // Advanced search
+        if (isset($this->searchTerms[0]['group'])) {
+            foreach ($this->searchTerms as $group) {
+                foreach ($group['group'] as $search) {
+                    if (preg_match("/\b$needle\b/", $search['lookfor'])) {
+                        return true;
+                    }
+                }
+            }
+        } else {
+            // Basic search
+            foreach ($this->searchTerms as $haystack) {
+                if (preg_match("/\b$needle\b/", $haystack['lookfor'])) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Extract all the keywords from the advanced search as a string.
+     *
+     * @return string
+     */
+    public function extractAdvancedTerms()
+    {
+        $terms = array();
+        foreach ($this->searchTerms as $current) {
+            if (isset($current['lookfor'])) {
+                $terms[] = $current['lookfor'];
+            } else if (isset($current['group']) && is_array($current['group'])) {
+                foreach ($current['group'] as $subCurrent) {
+                    if (isset($subCurrent['lookfor'])) {
+                        $terms[] = $subCurrent['lookfor'];
+                    }
+                }
+            }
+        }
+        return implode(' ', $terms);
+    }
+
+    /**
+     * Basic 'getter' for list of available view options.
+     *
+     * @return array
+     */
+    public function getViewList()
+    {
+        $list = array();
+        foreach ($this->getViewOptions() as $key => $value) {
+            $list[$key] = array(
+                'desc' => $value,
+                'selected' => ($key == $this->getView())
+            );
+        }
+        return $list;
+    }
+
+    /**
+     * Return a list of urls for possible limits, along with which option
+     *    should be currently selected.
+     *
+     * @return array Limit urls, descriptions and selected flags
+     */
+    public function getLimitList()
+    {
+        // Loop through all the current limits
+        $valid = $this->getLimitOptions();
+        $list = array();
+        foreach ($valid as $limit) {
+            $list[$limit] = array(
+                'desc' => $limit,
+                'selected' => ($limit == $this->getLimit())
+            );
+        }
+        return $list;
+    }
+
+    /**
+     * Return a list of urls for sorting, along with which option
+     *    should be currently selected.
+     *
+     * @return array Sort urls, descriptions and selected flags
+     */
+    public function getSortList()
+    {
+        // Loop through all the current filter fields
+        $valid = $this->getSortOptions();
+        $list = array();
+        foreach ($valid as $sort => $desc) {
+            $list[$sort] = array(
+                'desc' => $desc,
+                'selected' => ($sort == $this->getSort())
+            );
+        }
+        return $list;
+    }
+
+    /**
+     * Restore settings from a minified object found in the database.
+     *
+     * @param VF_MS $minified Minified Search Object
+     *
+     * @return void
+     */
+    public function deminify($minified)
+    {
+        // Some values will transfer without changes
+        $this->filterList   = $minified->f;
+        $this->searchType   = $minified->ty;
+
+        // Search terms, we need to expand keys
+        $tempTerms = $minified->t;
+        foreach ($tempTerms as $term) {
+            $newTerm = array();
+            foreach ($term as $k => $v) {
+                switch ($k) {
+                case 'j':
+                    $newTerm['join']    = $v;
+                    break;
+                case 'i':
+                    $newTerm['index']   = $v;
+                    break;
+                case 'l':
+                    $newTerm['lookfor'] = $v;
+                    break;
+                case 'g':
+                    $newTerm['group'] = array();
+                    foreach ($v as $line) {
+                        $search = array();
+                        foreach ($line as $k2 => $v2) {
+                            switch ($k2) {
+                            case 'b':
+                                $search['bool']    = $v2;
+                                break;
+                            case 'f':
+                                $search['field']   = $v2;
+                                break;
+                            case 'l':
+                                $search['lookfor'] = $v2;
+                                break;
+                            }
+                        }
+                        $newTerm['group'][] = $search;
+                    }
+                    break;
+                }
+            }
+            $this->searchTerms[] = $newTerm;
+        }
+    }
+
+    /**
+     * Load all available facet settings.  This is mainly useful for showing
+     * appropriate labels when an existing search has multiple filters associated
+     * with it.
+     *
+     * @param string $preferredSection Section to favor when loading settings;
+     * if multiple sections contain the same facet, this section's description
+     * will be favored.
+     *
+     * @return void
+     */
+    public function activateAllFacets($preferredSection = false)
+    {
+        // By default, there is only set of facet settings, so this function isn't
+        // really necessary.  However, in the Search History screen, we need to
+        // use this for Solr-based Search Objects, so we need this dummy method to
+        // allow other types of Search Objects to co-exist with Solr-based ones.
+        // See the Solr Search Object for details of how this works if you need to
+        // implement context-sensitive facet settings in another module.
+    }
+
+    /**
+     * Override the normal search behavior with an explicit array of IDs that must
+     * be retrieved.
+     *
+     * @param array $ids Record IDs to load
+     *
+     * @return void
+     */
+    public function setQueryIDs($ids)
+    {
+        // This needs to be defined in child classes:
+        throw new Exception(get_class($this) . ' does not support setQueryIDs().');
+    }
+
+    /**
+     * Get the maximum number of IDs that may be sent to setQueryIDs (-1 for no
+     * limit).
+     *
+     * @return int
+     */
+    public function getQueryIDLimit()
+    {
+        return -1;
+    }
+
+    /**
+     * Get an array of the names of all selected shards.  These should correspond
+     * with keys in the array returned by the option class's getShards() method.
+     *
+     * @return array
+     */
+    public function getSelectedShards()
+    {
+        return $this->selectedShards;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Base/Results.php b/module/VuFind/src/VuFind/Search/Base/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..dfedd72a1849958e1a275b4769d48f1f554a9e34
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Base/Results.php
@@ -0,0 +1,480 @@
+<?php
+/**
+ * Abstract results search model.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Abstract results search model.
+ *
+ * This abstract class defines the results methods for modeling a search in VuFind.
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+abstract class VF_Search_Base_Results
+{
+    protected $params;
+    // Total number of results available
+    protected $resultTotal = null;
+    // Override (only for use in very rare cases)
+    protected $startRecordOverride = null;
+    // Array of results (represented as Record Driver objects) retrieved on latest
+    // search:
+    protected $results = null;
+    // An ID number for saving/retrieving search
+    protected $searchId = null;
+    protected $savedSearch = null;
+    // STATS
+    protected $queryStartTime = null;
+    protected $queryEndTime = null;
+    protected $queryTime = null;
+    // Helper objects
+    protected $helpers = array();
+    // Spelling
+    protected $suggestions = null;
+
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Params $params Object representing user search
+     * parameters.
+     */
+    public function __construct(VF_Search_Base_Params $params)
+    {
+        // Save the parameters, then perform the search:
+        $this->params = $params;
+    }
+
+    /**
+     * Copy constructor
+     *
+     * @return void
+     */
+    public function __clone()
+    {
+        $this->params = clone($this->params);
+    }
+
+    /**
+     * Get the URL helper for this object.
+     *
+     * @return VF_Search_UrlHelper
+     */
+    public function getUrl()
+    {
+        // Set up URL helper:
+        if (!isset($this->helpers['url'])) {
+            $this->helpers['url'] = new VF_Search_UrlHelper($this);
+        }
+        return $this->helpers['url'];
+    }
+
+    /**
+     * Actually execute the search.
+     *
+     * @return void
+     */
+    public function performAndProcessSearch()
+    {
+        // Initialize variables to defaults (to ensure they don't stay null
+        // and cause unnecessary repeat processing):
+        $this->resultTotal = 0;
+        $this->results = array();
+        $this->suggestions = array();
+
+        // Run the search:
+        $this->startQueryTimer();
+        $this->performSearch();
+        $this->stopQueryTimer();
+
+        // Process recommendations:
+        $recommendations = $this->params->getRecommendations(null);
+        if (is_array($recommendations)) {
+            foreach ($recommendations as $currentSet) {
+                foreach ($currentSet as $current) {
+                    $current->process($this);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    abstract public function getFacetList($filter = null);
+
+    /**
+     * Abstract support method for performAndProcessSearch -- perform a search based
+     * on the parameters passed to the object.  This method is responsible for
+     * filling in all of the key class properties: _results, _resultTotal, etc.
+     *
+     * @return void
+     */
+    abstract protected function performSearch();
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        // This needs to be defined in subclasses:
+        throw new Exception('getRecord needs to be defined.');
+    }
+
+    /**
+     * Static method to retrieve an array of records by ID.
+     *
+     * @param array $ids Array of unique record identifiers.
+     *
+     * @return array
+     */
+    public static function getRecords($ids)
+    {
+        // This is the default, dumb behavior for retrieving multiple records --
+        // just call getRecord() repeatedly.  For efficiency, this method should
+        // be overridden in child classes when possible to reduce API calls, etc.
+        $retVal = array();
+        foreach ($ids as $id) {
+            try {
+                $retVal[] = static::getRecord($id);
+            } catch (Exception $e) {
+                // Just omit missing records from the return array; calling code
+                // in the VF_Record::loadBatch() method will deal with this.
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Allow Results object to proxy methods of Params object.
+     *
+     * @param string $methodName Method to call
+     * @param array  $params     Method parameters
+     *
+     * @return mixed
+     */
+    public function __call($methodName, $params)
+    {
+        // Proxy undefined methods to the parameter object:
+        $method = array($this->params, $methodName);
+        if (!is_callable($method)) {
+            throw new Exception($methodName . ' cannot be called.');
+        }
+        return call_user_func_array($method, $params);
+    }
+
+    /**
+     * Get spelling suggestion information.
+     *
+     * @return array
+     */
+    public function getSpellingSuggestions()
+    {
+        // Not supported by default:
+        return array();
+    }
+
+    /**
+     * Get total count of records in the result set (not just current page).
+     *
+     * @return int
+     */
+    public function getResultTotal()
+    {
+        if (is_null($this->resultTotal)) {
+            $this->performAndProcessSearch();
+        }
+        return $this->resultTotal;
+    }
+
+    /**
+     * Manually override the start record number.
+     *
+     * @param int $rec Record number to use.
+     *
+     * @return void
+     */
+    public function overrideStartRecord($rec)
+    {
+        $this->startRecordOverride = $rec;
+    }
+
+    /**
+     * Get record number for start of range represented by current result set.
+     *
+     * @return int
+     */
+    public function getStartRecord()
+    {
+        if (!is_null($this->startRecordOverride)) {
+            return $this->startRecordOverride;
+        }
+        return (($this->getPage() - 1) * $this->getLimit()) + 1;
+    }
+
+    /**
+     * Get record number for end of range represented by current result set.
+     *
+     * @return int
+     */
+    public function getEndRecord()
+    {
+        $total = $this->getResultTotal();
+        $limit = $this->getLimit();
+        $page = $this->getPage();
+        if ($page * $limit > $total) {
+            // The end of the current page runs past the last record, use total
+            // results
+            return $total;
+        } else {
+            // Otherwise use the last record on this page
+            return $page * $limit;
+        }
+    }
+
+    /**
+     * Basic 'getter' for search results.
+     *
+     * @return array
+     */
+    public function getResults()
+    {
+        if (is_null($this->results)) {
+            $this->performAndProcessSearch();
+        }
+        return $this->results;
+    }
+
+    /**
+     * Basic 'getter' for ID of saved search.
+     *
+     * @return int
+     */
+    public function getSearchId()
+    {
+        return $this->searchId;
+    }
+
+    /**
+     * Is the current search saved in the database?
+     *
+     * @return bool
+     */
+    public function isSavedSearch()
+    {
+        // This data is not available until VuFind_Model_Db_Search::saveSearch()
+        // is called...  blow up if somebody tries to get data that is not yet
+        // available.
+        if (is_null($this->savedSearch)) {
+            throw new Exception(
+                'Cannot retrieve save status before updateSaveStatus is called.'
+            );
+        }
+        return $this->savedSearch;
+    }
+
+    /**
+     * Given a database row corresponding to the current search object,
+     * mark whether this search is saved and what its database ID is.
+     *
+     * @param Zend_Db_Table_Row $row Relevant database row.
+     *
+     * @return void
+     */
+    public function updateSaveStatus($row)
+    {
+        $this->searchId = $row->id;
+        $this->savedSearch = ($row->saved == true);
+    }
+
+    /**
+     * Start the timer to figure out how long a query takes.  Complements
+     * stopQueryTimer().
+     *
+     * @return void
+     */
+    protected function startQueryTimer()
+    {
+        // Get time before the query
+        $time = explode(" ", microtime());
+        $this->queryStartTime = $time[1] + $time[0];
+    }
+
+    /**
+     * End the timer to figure out how long a query takes.  Complements
+     * startQueryTimer().
+     *
+     * @return void
+     */
+    protected function stopQueryTimer()
+    {
+        $time = explode(" ", microtime());
+        $this->queryEndTime = $time[1] + $time[0];
+        $this->queryTime = $this->queryEndTime - $this->queryStartTime;
+    }
+
+    /**
+     * Basic 'getter' for query speed.
+     *
+     * @return float
+     */
+    public function getQuerySpeed()
+    {
+        if (is_null($this->queryTime)) {
+            $this->performAndProcessSearch();
+        }
+        return $this->queryTime;
+    }
+
+    /**
+     * Basic 'getter' for query start time.
+     *
+     * @return float
+     */
+    public function getStartTime()
+    {
+        if (is_null($this->queryStartTime)) {
+            $this->performAndProcessSearch();
+        }
+        return $this->queryStartTime;
+    }
+
+    /**
+     * Get a paginator for the result set.
+     *
+     * @return Zend_Paginator
+     */
+    public function getPaginator()
+    {
+        // If there is a limit on how many pages are accessible,
+        // apply that limit now:
+        $max = $this->getVisibleSearchResultLimit();
+        $total = $this->getResultTotal();
+        if ($max > 0 && $total > $max) {
+            $total = $max;
+        }
+
+        // Build the standard paginator control:
+        return Zend_Paginator::factory($total)
+            ->setCurrentPageNumber($this->getPage())
+            ->setItemCountPerPage($this->getLimit())
+            ->setPageRange(11);
+    }
+
+    /**
+     * Input Tokenizer - Specifically for spelling purposes
+     *
+     * Because of its focus on spelling, these tokens are unsuitable
+     * for actual searching. They are stripping important search data
+     * such as joins and groups, simply because they don't need to be
+     * spellchecked.
+     *
+     * @param string $input Query to tokenize
+     *
+     * @return array        Tokenized array
+     */
+    public function spellingTokens($input)
+    {
+        $joins = array("AND", "OR", "NOT");
+        $paren = array("(" => "", ")" => "");
+
+        // Base of this algorithm comes straight from
+        // PHP doco examples & benighted at gmail dot com
+        // http://php.net/manual/en/function.strtok.php
+        $tokens = array();
+        $token = strtok($input, ' ');
+        while ($token) {
+            // find bracketed tokens
+            if ($token{0}=='(') {
+                $token .= ' '.strtok(')').')';
+            }
+            // find double quoted tokens
+            if ($token{0}=='"') {
+                $token .= ' '.strtok('"').'"';
+            }
+            // find single quoted tokens
+            if ($token{0}=="'") {
+                $token .= ' '.strtok("'")."'";
+            }
+            $tokens[] = $token;
+            $token = strtok(' ');
+        }
+        // Some cleaning of tokens that are just boolean joins
+        //  and removal of brackets
+        $return = array();
+        foreach ($tokens as $token) {
+            // Ignore join
+            if (!in_array($token, $joins)) {
+                // And strip parentheses
+                $final = trim(strtr($token, $paren));
+                if ($final != "") {
+                    $return[] = $final;
+                }
+            }
+        }
+        return $return;
+    }
+
+    /**
+     * Basic 'getter' for suggestion list.
+     *
+     * @return array
+     */
+    public function getRawSuggestions()
+    {
+        if (is_null($this->suggestions)) {
+            $this->performAndProcessSearch();
+        }
+        return $this->suggestions;
+    }
+
+    /**
+     * Restore settings from a minified object found in the database.
+     *
+     * @param VF_MS $minified Minified Search Object
+     *
+     * @return void
+     */
+    public function deminify($minified)
+    {
+        $this->searchId = $minified->id;
+        $this->queryStartTime = $minified->i;
+        $this->queryTime = $minified->s;
+        $this->resultTotal = $minified->r;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Empty/Results.php b/module/VuFind/src/VuFind/Search/Empty/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..2dc46a2dba21a162dee6bdc74a780b079094fcb2
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Empty/Results.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Empty Search Object
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Simple search results object to represent an empty set (used when dealing with
+ * exceptions that prevent a "real" search object from being constructed).
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Empty_Results extends VF_Search_Base_Results
+{
+    /**
+     * Support method for constructor -- perform a search based on the parameters
+     * passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        // Do nothing
+    }
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing all
+     * of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array                Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        return array();
+    }
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @throws VF_Exception_RecordMissing
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        throw new Exception('Cannot get record from empty set.');
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Favorites/Options.php b/module/VuFind/src/VuFind/Search/Favorites/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..6dd725d48acf25c8606055ce1a04cb13681fe446
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Favorites/Options.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Favorites aspect of the Search Multi-class (Options)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Favorites Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_Favorites_Options extends VF_Search_Base_Options
+{
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->defaultSort = 'title';
+        $this->sortOptions = array(
+            'title' => 'sort_title', 'author' => 'sort_author',
+            'year DESC' => 'sort_year', 'year' => 'sort_year asc'
+        );
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'MyResearch', 'action' => 'Favorites');
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Favorites/Params.php b/module/VuFind/src/VuFind/Search/Favorites/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..15582743fb0298f72615ab74f827b33fa5707fd4
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Favorites/Params.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Favorites aspect of the Search Multi-class (Params)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Favorites Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_Favorites_Params extends VF_Search_Base_Params
+{
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Options $options Options to use (null to load defaults)
+     */
+    public function __construct($options = null)
+    {
+        parent::__construct($options);
+        $this->recommendationsEnabled(true);
+    }
+
+    /**
+     * Load all recommendation settings from the relevant ini file.  Returns an
+     * associative array where the key is the location of the recommendations (top
+     * or side) and the value is the settings found in the file (which may be either
+     * a single string or an array of strings).
+     *
+     * @return array associative: location (top/side) => search settings
+     */
+    protected function getRecommendationSettings()
+    {
+        return array('side' => 'FavoriteFacets');
+    }
+
+    /**
+     * Add filters to the object based on values found in the request object.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initFilters($request)
+    {
+        // Special filter -- if the "id" parameter is set, limit to a specific list:
+        $id = $request->getParam('id');
+        if (!empty($id)) {
+            $this->addFilter("lists:{$id}");
+        }
+
+        // Otherwise use standard parent behavior:
+        return parent::initFilters($request);
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Favorites/Results.php b/module/VuFind/src/VuFind/Search/Favorites/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..b687f924af02b9e7c53c1b9faa3cc2a434b2b14d
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Favorites/Results.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * Favorites aspect of the Search Multi-class (Results)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Favorites Results
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_Favorites_Results extends VF_Search_Base_Results
+{
+    protected $user = null;
+    protected $list = false;
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        // Make sure we have processed the search before proceeding:
+        if (is_null($this->user)) {
+            $this->performAndProcessSearch();
+        }
+
+        // If there is no filter, we'll use all facets as the filter:
+        if (is_null($filter)) {
+            $filter = $this->params->getFacetConfig();
+        }
+
+        // Start building the facet list:
+        $retVal = array();
+
+        // Loop through every requested field:
+        $validFields = array_keys($filter);
+        foreach ($validFields as $field) {
+            if (!isset($this->facets[$field])) {
+                $this->facets[$field] = array(
+                    'label' => $this->params->getFacetLabel($field),
+                    'list' => array()
+                );
+                switch ($field) {
+                case 'lists':
+                    $lists = $this->user ? $this->user->getLists() : array();
+                    foreach ($lists as $list) {
+                        $this->facets[$field]['list'][] = array(
+                            'value' => $list->id,
+                            'displayText' => $list->title,
+                            'count' => $list->cnt,
+                            'isApplied' =>
+                                $this->params->hasFilter("$field:".$list->id)
+                        );
+                    }
+                    break;
+                case 'tags':
+                    if ($this->list) {
+                        $tags = $this->list->getTags();
+                    } else {
+                        $tags = $this->user ? $this->user->getTags() : array();
+                    }
+                    foreach ($tags as $tag) {
+                        $this->facets[$field]['list'][] = array(
+                            'value' => $tag->tag,
+                            'displayText' => $tag->tag,
+                            'count' => $tag->cnt,
+                            'isApplied' =>
+                                $this->params->hasFilter("$field:".$tag->tag)
+                        );
+                    }
+                    break;
+                }
+            }
+            if (isset($this->facets[$field])) {
+                $retVal[$field] = $this->facets[$field];
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Support method for performAndProcessSearch -- perform a search based on the
+     * parameters passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        $list = $this->getListObject();
+        $account = VF_Account_Manager::getInstance();
+        $this->user = $account->isLoggedIn();
+
+        // Make sure the user and/or list objects make it possible to view
+        // the current result set -- we need to check logged in status and
+        // list permissions.
+        if (is_null($list) && !$this->user) {
+            throw new VF_Exception_ListPermission(
+                'Cannot retrieve favorites without logged in user.'
+            );
+        }
+        if (!is_null($list) && !$list->public
+            && (!$this->user || $list->user_id != $this->user->id)
+        ) {
+            throw new VF_Exception_ListPermission(
+                VF_Translator::translate('list_access_denied')
+            );
+        }
+
+        $resource = new VuFind_Model_Db_Resource();
+        $rawResults = $resource->getFavorites(
+            is_null($list) ? $this->user->id : $list->user_id,
+            isset($list->id) ? $list->id : null,
+            $this->getTagFilters(), $this->getSort()
+        );
+
+        // How many results were there?
+        $this->resultTotal = count($rawResults);
+
+        // Retrieve record drivers for the selected items.
+        $end = $this->getEndRecord();
+        $recordsToRequest = array();
+        for ($i = $this->getStartRecord() - 1; $i < $end; $i++) {
+            $row = $rawResults->getRow($i);
+            $recordsToRequest[] = array(
+                'id' => $row->record_id, 'source' => $row->source,
+                'extra_fields' => array(
+                    'title' => $row->title
+                )
+            );
+        }
+        $this->results = VF_Record::loadBatch($recordsToRequest);
+    }
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        throw new Exception(
+            'getRecord not supported by VF_Search_Favorites_Results'
+        );
+    }
+
+    /**
+     * Get an array of tags being applied as filters.
+     *
+     * @return array
+     */
+    protected function getTagFilters()
+    {
+        $filters = $this->params->getFilters();
+        return isset($filters['tags']) ? $filters['tags'] : array();
+    }
+
+    /**
+     * Get the list object associated with the current search (null if no list
+     * selected).
+     *
+     * @return VuFind_Model_Db_UserListRow|null
+     */
+    public function getListObject()
+    {
+        // If we haven't previously tried to load a list, do it now:
+        if ($this->list === false) {
+            // Check the filters for a list ID, and load the corresponding object
+            // if one is found:
+            $filters = $this->params->getFilters();
+            $listId = isset($filters['lists'][0]) ? $filters['lists'][0] : null;
+            $this->list = is_null($listId)
+                ? null : VuFind_Model_Db_UserList::getExisting($listId);
+        }
+        return $this->list;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Memory.php b/module/VuFind/src/VuFind/Search/Memory.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb992063162dc2b20dccf3a8f3c7581ff7b5ce61
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Memory.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * VuFind Search Memory
+ *
+ * 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  Support_Classes
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Wrapper class to handle search memory
+ *
+ * @category VuFind2
+ * @package  Support_Classes
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Memory
+{
+    /**
+     * Clear the last accessed search URL in the session.
+     *
+     * @return void
+     */
+    public static function forgetSearch()
+    {
+        $session = new Zend_Session_Namespace('Search');
+        unset($session->last);
+    }
+
+    /**
+     * Store the last accessed search URL in the session for future reference.
+     *
+     * @param string $url URL to remember
+     *
+     * @return void
+     */
+    public static function rememberSearch($url)
+    {
+        // Only remember URL if string is non-empty... otherwise clear the memory.
+        if (strlen(trim($url)) > 0) {
+            $session = new Zend_Session_Namespace('Search');
+            $session->last = $url;
+        } else {
+            self::forgetSearch();
+        }
+    }
+
+    /**
+     * Retrieve last accessed search URL, if available.  Returns null if no URL
+     * is available.
+     *
+     * @return string|null
+     */
+    public static function retrieve()
+    {
+        $session = new Zend_Session_Namespace('Search');
+        return isset($session->last) ? $session->last : null;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/MixedList/Options.php b/module/VuFind/src/VuFind/Search/MixedList/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..fe9291a56e612608201e8d54970a1f2595ee7ab2
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/MixedList/Options.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Mixed List aspect of the Search Multi-class (Options)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Mixed List Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_MixedList_Options extends VF_Search_Base_Options
+{
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Records', 'action' => 'Home');
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/MixedList/Params.php b/module/VuFind/src/VuFind/Search/MixedList/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..42661e7833e57b331b8d224b2bf551b070a59bb5
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/MixedList/Params.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Mixed List aspect of the Search Multi-class (Params)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Mixed List Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_MixedList_Params extends VF_Search_Base_Params
+{
+    protected $recordsToRequest;
+
+    /**
+     * Initialize the object's search settings from a request object.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initSearch($request)
+    {
+        $this->recordsToRequest = $request->getParam('id', array());
+
+        // We always want to display the entire list as one page:
+        $this->setLimit(count($this->recordsToRequest));
+    }
+
+    /**
+     * Get list of records to display.
+     *
+     * @return array
+     */
+    public function getRecordsToRequest()
+    {
+        return $this->recordsToRequest;
+    }
+
+    /**
+     * Load all recommendation settings from the relevant ini file.  Returns an
+     * associative array where the key is the location of the recommendations (top
+     * or side) and the value is the settings found in the file (which may be either
+     * a single string or an array of strings).
+     *
+     * @return array associative: location (top/side) => search settings
+     */
+    protected function getRecommendationSettings()
+    {
+        // No recommendation modules in mixed list view currently:
+        return array();
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/MixedList/Results.php b/module/VuFind/src/VuFind/Search/MixedList/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..74be45f2e2eab176f92044947fd90432e2371c98
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/MixedList/Results.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Mixed List aspect of the Search Multi-class (Results)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Mixed List Results
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_MixedList_Results extends VF_Search_Base_Results
+{
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        // Facets not supported here:
+        return array();
+    }
+
+    /**
+     * Support method for performAndProcessSearch -- perform a search based on the
+     * parameters passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        $recordsToRequest = $this->params->getRecordsToRequest();
+        $this->results = VF_Record::loadBatch($recordsToRequest);
+        $this->resultTotal = count($this->results);
+    }
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        throw new Exception(
+            'getRecord not supported by VF_Search_MixedList_Results'
+        );
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Options.php b/module/VuFind/src/VuFind/Search/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..a5e0b189372ce9e23d351cb99031262614344972
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Options.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Instance store for obtaining default search options objects.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\Search;
+
+/**
+ * Instance store for obtaining default search options objects.
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class Options
+{
+    /**
+     * Basic get
+     *
+     * @param string $type The search type of the object to retrieve
+     *
+     * @return VF_Search_Base_Options
+     */
+    public static function getInstance($type)
+    {
+        static $store = array();
+
+        if (!isset($store[$type])) {
+            $class = 'VuFind\\Search\\' . $type . '\\Options';
+            $store[$type] = new $class();
+        }
+        return $store[$type];
+    }
+
+    /**
+     * Extract the name of the search class family from a class name.
+     *
+     * @param string $className Class name to examine.
+     *
+     * @return string
+     */
+    public static function extractSearchClassId($className)
+    {
+        // Parse identifier out of class name of format VuFind\Search\[id]\Params:
+        $class = explode('\\', $className);
+        return $class[2];
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/ResultScroller.php b/module/VuFind/src/VuFind/Search/ResultScroller.php
new file mode 100644
index 0000000000000000000000000000000000000000..8dd8ecac659b5d24a8251545429fae6834800f86
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/ResultScroller.php
@@ -0,0 +1,347 @@
+<?php
+/**
+ * Class for managing "next" and "previous" navigation within result sets.
+ *
+ * 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  Support_Classes
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/system_classes Wiki
+ */
+
+/**
+ * Class for managing "next" and "previous" navigation within result sets.
+ *
+ * @category VuFind2
+ * @package  Support_Classes
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/system_classes Wiki
+ */
+class VF_Search_ResultScroller
+{
+    protected $enabled;
+    protected $data;
+
+    /**
+     * Constructor. Create a new search result scroller.
+     */
+    public function __construct()
+    {
+        // Is this functionality enabled in config.ini?
+        $config = VF_Config_Reader::getConfig();
+        $this->enabled = (isset($config->Record->next_prev_navigation)
+            && $config->Record->next_prev_navigation);
+
+        // Set up session namespace for the class.
+        $this->data = new Zend_Session_Namespace('ResultScroller');
+    }
+
+    /**
+     * Initialize this result set scroller. This should only be called
+     * prior to displaying the results of a new search.
+     *
+     * @param VF_Search_Base_Results $searchObject The search object that was
+     * used to execute the last search.
+     *
+     * @return bool
+     */
+    public function init($searchObject)
+    {
+        // Do nothing if disabled:
+        if (!$this->enabled) {
+            return;
+        }
+
+        // Save the details of this search in the session
+        $this->data->searchId = $searchObject->getSearchId();
+        $this->data->page = $searchObject->getPage();
+        $this->data->limit = $searchObject->getLimit();
+        $this->data->total = $searchObject->getResultTotal();
+
+        // save the IDs of records on the current page to the session
+        // so we can "slide" from one record to the next/previous records
+        // spanning 2 consecutive pages
+        $this->data->currIds = $this->fetchPage($searchObject);
+
+        // clear the previous/next page
+        unset($this->data->prevIds);
+        unset($this->data->nextIds);
+
+        return true;
+    }
+
+    /**
+     * Get the previous/next record in the last search
+     * result set relative to the current one, also return
+     * the position of the current record in the result set.
+     * Return array('previousRecord'=>previd, 'nextRecord'=>nextid,
+     * 'currentPosition'=>number, 'resultTotal'=>number).
+     *
+     * @param string $id The ID currently being displayed
+     *
+     * @return array
+     */
+    public function getScrollData($id)
+    {
+        $retVal = array(
+            'previousRecord'=>null,
+            'nextRecord'=>null,
+            'currentPosition'=>null,
+            'resultTotal'=>null);
+
+        // Do nothing if disabled:
+        if (!$this->enabled) {
+            return $retVal;
+        }
+
+        if (isset($this->data->currIds) && isset($this->data->searchId)) {
+            // we need to restore the last search object
+            // to fetch either the previous/next page of results
+            $lastSearch = $this->restoreLastSearch();
+
+            // give up if we can not restore the last search
+            if (!$lastSearch) {
+                return $retVal;
+            }
+
+            if (!isset($this->data->prevIds)) {
+                $this->data->prevIds = null;
+            }
+            if (!isset($this->data->nextIds)) {
+                $this->data->nextIds = null;
+            }
+            $retVal['resultTotal']
+                = isset($this->data->total) ? $this->data->total : 0;
+
+            // find where this record is in the current result page
+            $pos = array_search($id, $this->data->currIds);
+            if ($pos !== false) {
+                // OK, found this record in the current result page
+                // calculate it's position relative to the result set
+                $retVal['currentPosition']
+                    = ($this->data->page - 1) * $this->data->limit + $pos + 1;
+
+                // count how many records in the current result page
+                $count = count($this->data->currIds);
+
+                // if the current record is somewhere in the middle of the current
+                // page, ie: not first or last, then it is easy
+                if ($pos > 0 && $pos < $count - 1) {
+                    $retVal['previousRecord'] = $this->data->currIds[$pos - 1];
+                    $retVal['nextRecord'] = $this->data->currIds[$pos + 1];
+                    // and we're done
+                    return $retVal;
+                }
+
+                // if this record is first record on the current page
+                if ($pos == 0) {
+                    // if the current page is NOT the first page, and
+                    // the previous page has not been fetched before, then
+                    // fetch the previous page
+                    if ($this->data->page > 1
+                        && $this->data->prevIds == null
+                    ) {
+                        $this->data->prevIds = $this->fetchPage(
+                            $lastSearch, $this->data->page - 1
+                        );
+                    }
+
+                    // if there is something on the previous page, then the previous
+                    // record is the last record on the previous page
+                    if (!empty($this->data->prevIds)) {
+                        $retVal['previousRecord']
+                            = $this->data->prevIds[count($this->data->prevIds) - 1];
+                    }
+
+                    // if it is not the last record on the current page, then
+                    // we also have a next record on the current page
+                    if ($pos < $count - 1) {
+                        $retVal['nextRecord'] = $this->data->currIds[$pos + 1];
+                    }
+
+                    // and we're done
+                    return $retVal;
+                }
+
+                // if this record is last record on the current page
+                if ($pos == $count - 1) {
+                    // if the next page has not been fetched, then
+                    // fetch the next page
+                    if ($this->data->nextIds == null) {
+                        $this->data->nextIds = $this->fetchPage(
+                            $lastSearch, $this->data->page + 1
+                        );
+                    }
+
+                    // if there is something on the next page, then the next
+                    // record is the first record on the next page
+                    if (!empty($this->data->nextIds)) {
+                        $retVal['nextRecord'] = $this->data->nextIds[0];
+                    }
+
+                    // if it is not the first record on the current page, then
+                    // we also have a previous record on the current page
+                    if ($pos > 0) {
+                        $retVal['previousRecord'] = $this->data->currIds[$pos - 1];
+                    }
+
+                    // and we're done
+                    return $retVal;
+                }
+            } else {
+                // the current record is not on the current page
+
+                // if there is something on the previous page
+                if (!empty($this->data->prevIds)) {
+                    // check if current record is on the previous page
+                    $pos = array_search($id, $this->data->prevIds);
+                    if ($pos !== false) {
+                        // decrease the page in the session because
+                        // we're now sliding into the previous page
+                        $this->data->page--;
+
+                        // shift pages to the right
+                        $tmp = $this->data->currIds;
+                        $this->data->currIds = $this->data->prevIds;
+                        $this->data->nextIds = $tmp;
+                        $this->data->prevIds = null;
+
+                        // now we can set the previous/next record
+                        if ($pos > 0) {
+                            $retVal['previousRecord']
+                                = $this->data->currIds[$pos - 1];
+                        }
+                        $retVal['nextRecord'] = $this->data->nextIds[0];
+
+                        // recalculate the current position
+                        $retVal['currentPosition']
+                            = ($this->data->page - 1)
+                            * $this->data->limit + $pos + 1;
+
+                        // update the search URL in the session
+                        $lastSearch->setPage($this->data->page);
+                        $this->rememberSearch($lastSearch);
+
+                        // and we're done
+                        return $retVal;
+                    }
+                }
+
+                // if there is something on the next page
+                if (!empty($this->data->nextIds)) {
+                    // check if current record is on the next page
+                    $pos = array_search($id, $this->data->nextIds);
+                    if ($pos !== false) {
+                        // increase the page in the session because
+                        // we're now sliding into the next page
+                        $this->data->page++;
+
+                        // shift pages to the left
+                        $tmp = $this->data->currIds;
+                        $this->data->currIds = $this->data->nextIds;
+                        $this->data->prevIds = $tmp;
+                        $this->data->nextIds = null;
+
+                        // now we can set the previous/next record
+                        $retVal['previousRecord']
+                            = $this->data->prevIds[count($this->data->prevIds) - 1];
+                        if ($pos < count($this->data->currIds) - 1) {
+                            $retVal['nextRecord'] = $this->data->currIds[$pos + 1];
+                        }
+
+                        // recalculate the current position
+                        $retVal['currentPosition']
+                            = ($this->data->page - 1)
+                            * $this->data->limit + $pos + 1;
+
+                        // update the search URL in the session
+                        $lastSearch->setPage($this->data->page);
+                        $this->rememberSearch($lastSearch);
+
+                        // and we're done
+                        return $retVal;
+                    }
+                }
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Fetch the given page of results from the given search object and
+     * return the IDs of the records in an array.
+     *
+     * @param object $searchObject The search object to use to execute the search
+     * @param int    $page         The page number to fetch (null for current)
+     *
+     * @return array
+     */
+    protected function fetchPage($searchObject, $page = null)
+    {
+        if (!is_null($page)) {
+            $searchObject->setPage($page);
+            $searchObject->performAndProcessSearch();
+        }
+
+        $retVal = array();
+        foreach ($searchObject->getResults() as $record) {
+            $retVal[] = $record->getUniqueId();
+        }
+        return $retVal;
+    }
+
+    /**
+     * Restore the last saved search.
+     *
+     * @return VF_Search_Base_Results
+     */
+    protected function restoreLastSearch()
+    {
+        if (isset($this->data->searchId)) {
+            $searchTable = new VuFind_Model_Db_Search();
+            $rows = $searchTable->find($this->data->searchId);
+            if (count($rows) > 0) {
+                $search = $rows->getRow(0);
+                $minSO = unserialize($search->search_object);
+                return $minSO->deminify();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Update the remembered "last search" in the session.
+     *
+     * @param VF_Search_Base_Results $search Search object to remember.
+     *
+     * @return void
+     */
+    protected function rememberSearch($search)
+    {
+        $details = $search->getSearchAction();
+        $fc = Zend_Controller_Front::getInstance();
+        $baseUrl = $fc->getBaseUrl() . '/' .
+            $details['controller'] . '/' . $details['action'];
+        VF_Search_Memory::rememberSearch(
+            $baseUrl . $search->getUrl()->getParams(false)
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/Search/Solr/Options.php b/module/VuFind/src/VuFind/Search/Solr/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..7538ec50ec6c8b5a5f9ba78f4411f7ca30fb43db
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Solr/Options.php
@@ -0,0 +1,327 @@
+<?php
+/**
+ * Solr aspect of the Search Multi-class (Options)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFind\Search\Solr;
+use VuFind\Config\Reader as ConfigReader,
+    VuFind\Search\Base\Options as BaseOptions;
+
+/**
+ * Solr Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class Options extends BaseOptions
+{
+    // Spelling
+    protected $spellingLimit = 3;
+    protected $dictionary = 'default';
+    protected $spellSimple = false;
+    protected $spellSkipNumeric = true;
+
+    // Pre-assigned filters
+    protected $hiddenFilters = array();
+
+    // Shard fields to strip
+    protected $solrShardsFieldsToStrip = array();
+
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+
+        $searchSettings = ConfigReader::getConfig($this->searchIni);
+        if (isset($searchSettings->General->default_limit)) {
+            $this->defaultLimit = $searchSettings->General->default_limit;
+        }
+        if (isset($searchSettings->General->limit_options)) {
+            $this->limitOptions
+                = explode(",", $searchSettings->General->limit_options);
+        }
+        if (isset($searchSettings->General->default_sort)) {
+            $this->defaultSort = $searchSettings->General->default_sort;
+        }
+        if (isset($searchSettings->DefaultSortingByType)
+            && count($searchSettings->DefaultSortingByType) > 0
+        ) {
+            foreach ($searchSettings->DefaultSortingByType as $key => $val) {
+                $this->defaultSortByHandler[$key] = $val;
+            }
+        }
+        if (isset($searchSettings->RSS->sort)) {
+            $this->rssSort = $searchSettings->RSS->sort;
+        }
+        if (isset($searchSettings->General->default_view)) {
+            $this->defaultView = $searchSettings->General->default_view;
+        }
+        if (isset($searchSettings->General->default_handler)) {
+            $this->defaultHandler = $searchSettings->General->default_handler;
+        }
+        if (isset($searchSettings->General->retain_filters_by_default)) {
+            $this->retainFiltersByDefault
+                = $searchSettings->General->retain_filters_by_default;
+        }
+        if (isset($searchSettings->Basic_Searches)) {
+            foreach ($searchSettings->Basic_Searches as $key => $value) {
+                $this->basicHandlers[$key] = $value;
+            }
+        }
+        if (isset($searchSettings->Advanced_Searches)) {
+            foreach ($searchSettings->Advanced_Searches as $key => $value) {
+                $this->advancedHandlers[$key] = $value;
+            }
+        }
+
+        // Load sort preferences (or defaults if none in .ini file):
+        if (isset($searchSettings->Sorting)) {
+            foreach ($searchSettings->Sorting as $key => $value) {
+                $this->sortOptions[$key] = $value;
+            }
+        } else {
+            $this->sortOptions = array('relevance' => 'sort_relevance',
+                'year' => 'sort_year', 'year asc' => 'sort_year asc',
+                'callnumber' => 'sort_callnumber', 'author' => 'sort_author',
+                'title' => 'sort_title');
+        }
+        // Load view preferences (or defaults if none in .ini file):
+        if (isset($searchSettings->Views)) {
+            foreach ($searchSettings->Views as $key => $value) {
+                $this->viewOptions[$key] = $value;
+            }
+        } elseif (isset($searchSettings->General->default_view)) {
+            $this->viewOptions = array($this->defaultView => $this->defaultView);
+        } else {
+            $this->viewOptions = array('list' => 'List');
+        }
+
+        // Load facet preferences
+        $facetSettings = ConfigReader::getConfig($this->facetsIni);
+        if (isset($facetSettings->Advanced_Settings->translated_facets)
+            && count($facetSettings->Advanced_Settings->translated_facets) > 0
+        ) {
+            foreach ($facetSettings->Advanced_Settings->translated_facets as $c) {
+                $this->translatedFacets[] = $c;
+            }
+        }
+        if (isset($facetSettings->Advanced_Settings->special_facets)) {
+            $this->specialAdvancedFacets
+                = $facetSettings->Advanced_Settings->special_facets;
+        }
+
+        // Load Spelling preferences
+        $config = ConfigReader::getConfig();
+        if (isset($config->Spelling->enabled)) {
+            $this->spellcheck = $config->Spelling->enabled;
+        }
+        if (isset($config->Spelling->limit)) {
+            $this->spellingLimit = $config->Spelling->limit;
+        }
+        if (isset($config->Spelling->simple)) {
+            $this->spellSimple = $config->Spelling->simple;
+        }
+        if (isset($config->Spelling->skip_numeric)) {
+            $this->spellSkipNumeric = $config->Spelling->skip_numeric;
+        }
+
+        // Turn on highlighting if the user has requested highlighting or snippet
+        // functionality:
+        $highlight = !isset($searchSettings->General->highlighting)
+            ? false : $searchSettings->General->highlighting;
+        $snippet = !isset($searchSettings->General->snippets)
+            ? false : $searchSettings->General->snippets;
+        if ($highlight || $snippet) {
+            $this->highlight = true;
+        }
+
+        // Apply hidden filters:
+        if (isset($searchSettings->HiddenFilters)) {
+            foreach ($searchSettings->HiddenFilters as $field => $subfields) {
+                $rawFilter = $field.':'.'"'.addcslashes($subfields, '"').'"';
+                $this->addHiddenFilter($rawFilter);
+            }
+        }
+        if (isset($searchSettings->RawHiddenFilters)) {
+            foreach ($searchSettings->RawHiddenFilters as $rawFilter) {
+                $this->addHiddenFilter($rawFilter);
+            }
+        }
+
+        // Load autocomplete preference:
+        if (isset($searchSettings->Autocomplete->enabled)) {
+            $this->autocompleteEnabled = $searchSettings->Autocomplete->enabled;
+        }
+
+        // Load shard settings
+        if (isset($searchSettings->IndexShards)
+            && !empty($searchSettings->IndexShards)
+        ) {
+            foreach ($searchSettings->IndexShards as $k => $v) {
+                $this->shards[$k] = $v;
+            }
+            // If we have a default from the configuration, use that...
+            if (isset($searchSettings->ShardPreferences->defaultChecked)
+                && !empty($searchSettings->ShardPreferences->defaultChecked)
+            ) {
+                $defaultChecked
+                    = is_object($searchSettings->ShardPreferences->defaultChecked)
+                    ? $searchSettings->ShardPreferences->defaultChecked->toArray()
+                    : array($searchSettings->ShardPreferences->defaultChecked);
+                foreach ($defaultChecked as $current) {
+                    $this->defaultSelectedShards[] = $current;
+                }
+            } else {
+                // If no default is configured, use all shards...
+                $this->defaultSelectedShards = array_keys($this->shards);
+            }
+            // Apply checkbox visibility setting if applicable:
+            if (isset($searchSettings->ShardPreferences->showCheckboxes)) {
+                $this->visibleShardCheckboxes
+                    = $searchSettings->ShardPreferences->showCheckboxes;
+            }
+            // Apply field stripping if applicable:
+            if (isset($searchSettings->StripFields)) {
+                foreach ($searchSettings->StripFields as $k => $v) {
+                    $this->solrShardsFieldsToStrip[$k] = $v;
+                }
+            }
+        }
+    }
+
+    /**
+     * Add a hidden (i.e. not visible in facet controls) filter query to the object.
+     *
+     * @param string $fq Filter query for Solr.
+     *
+     * @return void
+     */
+    public function addHiddenFilter($fq)
+    {
+        $this->hiddenFilters[] = $fq;
+    }
+
+    /**
+     * Get an array of hidden filters.
+     *
+     * @return array
+     */
+    public function getHiddenFilters()
+    {
+        return $this->hiddenFilters;
+    }
+
+    /**
+     * Switch the spelling setting to simple
+     *
+     * @return void
+     */
+    public function usesSimpleSpelling()
+    {
+        return $this->spellSimple;
+    }
+
+    /**
+     * Switch the spelling dictionary to basic
+     *
+     * @return void
+     */
+    public function useBasicDictionary()
+    {
+        $this->dictionary = 'basicSpell';
+    }
+
+    /**
+     * Get the selected spelling dictionary
+     *
+     * @return string
+     */
+    public function getSpellingDictionary()
+    {
+        return $this->dictionary;
+    }
+
+
+    /**
+     * Are we skipping numeric words?
+     *
+     * @return bool
+     */
+    public function shouldSkipNumericSpelling()
+    {
+        return $this->spellSkipNumeric;
+    }
+
+
+    /**
+     * Get the selected spelling dictionary
+     *
+     * @return int
+     */
+    public function getSpellingLimit()
+    {
+        return $this->spellingLimit;
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Search', 'action' => 'Results');
+    }
+
+    /**
+     * Return an array describing the action used for performing advanced searches
+     * (same format as expected by the URL view helper).  Return false if the feature
+     * is not supported.
+     *
+     * @return array|bool
+     */
+    public function getAdvancedSearchAction()
+    {
+        return array('controller' => 'Search', 'action' => 'Advanced');
+    }
+
+    /**
+     * Get details on which Solr fields to strip when sharding is active.
+     *
+     * @return array
+     */
+    public function getSolrShardsFieldsToStrip()
+    {
+        return $this->solrShardsFieldsToStrip;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Solr/Params.php b/module/VuFind/src/VuFind/Search/Solr/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..23b37b7d182357ae45ec9e1cadf9603b32d2e44c
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Solr/Params.php
@@ -0,0 +1,400 @@
+<?php
+/**
+ * Solr aspect of the Search Multi-class (Params)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Solr_Params extends VF_Search_Base_Params
+{
+    // Facets
+    protected $facetLimit = 30;
+    protected $facetOffset = null;
+    protected $facetPrefix = null;
+    protected $facetSort = null;
+
+    // Override Query
+    protected $overrideQuery = false;
+
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Options $options Options to use (null to load defaults)
+     */
+    public function __construct($options = null)
+    {
+        parent::__construct($options);
+
+        // Use basic facet limit by default, if set:
+        $config = VF_Config_Reader::getConfig('facets');
+        if (isset($config->Results_Settings->facet_limit)
+            && is_numeric($config->Results_Settings->facet_limit)
+        ) {
+            $this->setFacetLimit($config->Results_Settings->facet_limit);
+        }
+    }
+
+    /**
+     * Set the override query
+     *
+     * @param string $q Override query
+     *
+     * @return void
+     */
+    public function setOverrideQuery($q)
+    {
+        $this->overrideQuery = $q;
+    }
+
+    /**
+     * Get the override query
+     *
+     * @return string
+     */
+    public function getOverrideQuery()
+    {
+        return $this->overrideQuery;
+    }
+
+    /**
+     * Return the current filters as an array of strings ['field:filter']
+     *
+     * @return array $filterQuery
+     */
+    public function getFilterSettings()
+    {
+        // Define Filter Query
+        $filterQuery = $this->getHiddenFilters();
+        foreach ($this->filterList as $field => $filter) {
+            foreach ($filter as $value) {
+                // Special case -- allow trailing wildcards and ranges:
+                if (substr($value, -1) == '*'
+                    || preg_match('/\[[^\]]+\s+TO\s+[^\]]+\]/', $value)
+                ) {
+                    $filterQuery[] = $field.':'.$value;
+                } else {
+                    $filterQuery[] = $field.':"'.$value.'"';
+                }
+            }
+        }
+        return $filterQuery;
+    }
+
+    /**
+     * Return current facet configurations
+     *
+     * @return array $facetSet
+     */
+    public function getFacetSettings()
+    {
+        // Build a list of facets we want from the index
+        $facetSet = array();
+        if (!empty($this->facetConfig)) {
+            $facetSet['limit'] = $this->facetLimit;
+            foreach ($this->facetConfig as $facetField => $facetName) {
+                $facetSet['field'][] = $facetField;
+            }
+            if ($this->facetOffset != null) {
+                $facetSet['offset'] = $this->facetOffset;
+            }
+            if ($this->facetPrefix != null) {
+                $facetSet['prefix'] = $this->facetPrefix;
+            }
+            if ($this->facetSort != null) {
+                $facetSet['sort'] = $this->facetSort;
+            }
+        }
+        return $facetSet;
+    }
+
+    /**
+     * Initialize the object's search settings from a request object.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initSearch($request)
+    {
+        // Special case -- did we get a list of IDs instead of a standard query?
+        $ids = $request->getParam('overrideIds', null);
+        if (is_array($ids) && !empty($ids)) {
+            $this->setQueryIDs($ids);
+        } else {
+            // Use standard initialization:
+            parent::initSearch($request);
+
+            // Another special case -- are we doing a tag search?
+            $tag = $request->getParam('tag', '');
+            if (!empty($tag)) {
+                $this->setBasicSearch($tag, 'tag');
+            }
+            if ($this->getSearchHandler() == 'tag') {
+                $this->initTagSearch();
+            }
+        }
+    }
+
+    /**
+     * Special case -- set up a tag-based search.
+     *
+     * @return void
+     */
+    public function initTagSearch()
+    {
+        $table = new VuFind_Model_Db_Tags();
+        $tag = $table->getByText($this->getDisplayQuery());
+        if (!empty($tag)) {
+            $rawResults = $tag->getResources('VuFind');
+        } else {
+            $rawResults = array();
+        }
+        $ids = array();
+        foreach ($rawResults as $current) {
+            $ids[] = $current->record_id;
+        }
+        $this->setQueryIDs($ids);
+    }
+
+    /**
+     * Set Facet Limit
+     *
+     * @param int $l the new limit value
+     *
+     * @return void
+     */
+    public function setFacetLimit($l)
+    {
+        $this->facetLimit = $l;
+    }
+
+    /**
+     * Set Facet Offset
+     *
+     * @param int $o the new offset value
+     *
+     * @return void
+     */
+    public function setFacetOffset($o)
+    {
+        $this->facetOffset = $o;
+    }
+
+    /**
+     * Set Facet Prefix
+     *
+     * @param string $p the new prefix value
+     *
+     * @return void
+     */
+    public function setFacetPrefix($p)
+    {
+        $this->facetPrefix = $p;
+    }
+
+    /**
+     * Set Facet Sorting
+     *
+     * @param string $s the new sorting action value
+     *
+     * @return void
+     */
+    public function setFacetSort($s)
+    {
+        $this->facetSort = $s;
+    }
+
+    /**
+     * Initialize facet settings for the advanced search screen.
+     *
+     * @return void
+     */
+    public function initAdvancedFacets()
+    {
+        $config = VF_Config_Reader::getConfig('facets');
+        if (isset($config->Advanced)) {
+            foreach ($config->Advanced as $key => $value) {
+                $this->addFacet($key, $value);
+            }
+        }
+        if (isset($config->Advanced_Settings->facet_limit)
+            && is_numeric($config->Advanced_Settings->facet_limit)
+        ) {
+            $this->setFacetLimit($config->Advanced_Settings->facet_limit);
+        }
+    }
+
+    /**
+     * Initialize facet settings for the standard search screen.
+     *
+     * @return void
+     */
+    public function initBasicFacets()
+    {
+        $config = VF_Config_Reader::getConfig('facets');
+        if (isset($config->ResultsTop)) {
+            foreach ($config->ResultsTop as $key => $value) {
+                $this->addFacet($key, $value);
+            }
+        }
+        if (isset($config->Results)) {
+            foreach ($config->Results as $key => $value) {
+                $this->addFacet($key, $value);
+            }
+        }
+    }
+
+    /**
+     * Adapt the search query to a spelling query
+     *
+     * @return string Spelling query
+     */
+    protected function buildSpellingQuery()
+    {
+        if ($this->searchType == 'advanced') {
+            return $this->extractAdvancedTerms();
+        }
+        return $this->getDisplayQuery();
+    }
+
+    /**
+     * Get Spelling Query
+     *
+     * @return string
+     */
+    public function getSpellingQuery()
+    {
+        // Build our spellcheck query
+        if ($this->options->spellcheckEnabled()) {
+            if ($this->options->usesSimpleSpelling()) {
+                $this->options->useBasicDictionary();
+            }
+            $spellcheck = $this->buildSpellingQuery();
+
+            // If the spellcheck query is purely numeric, skip it if
+            // the appropriate setting is turned on.
+            if ($this->options->shouldSkipNumericSpelling()
+                && is_numeric($spellcheck)
+            ) {
+                return '';
+            }
+            return $spellcheck;
+        }
+        return '';
+    }
+
+    /**
+     * Load all available facet settings.  This is mainly useful for showing
+     * appropriate labels when an existing search has multiple filters associated
+     * with it.
+     *
+     * @param string $preferredSection Section to favor when loading settings; if
+     * multiple sections contain the same facet, this section's description will
+     * be favored.
+     *
+     * @return void
+     */
+    public function activateAllFacets($preferredSection = false)
+    {
+        // Based on preference, change the order of initialization to make sure
+        // that preferred facet labels come in last.
+        if ($preferredSection == 'Advanced') {
+            $this->initBasicFacets();
+            $this->initAdvancedFacets();
+        } else {
+            $this->initAdvancedFacets();
+            $this->initBasicFacets();
+        }
+    }
+
+    /**
+     * Add filters to the object based on values found in the request object.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initFilters($request)
+    {
+        // Use the default behavior of the parent class, but add support for the
+        // special illustrations filter.
+        parent::initFilters($request);
+        switch ($request->getParam('illustration', -1)) {
+        case 1:
+            $this->addFilter('illustrated:Illustrated');
+            break;
+        case 0:
+            $this->addFilter('illustrated:"Not Illustrated"');
+            break;
+        }
+
+        // Check for hidden filters:
+        $hidden = $request->getParam('hiddenFilters');
+        if (!empty($hidden) && is_array($hidden)) {
+            foreach ($hidden as $current) {
+                $this->addHiddenFilter($current);
+            }
+        }
+    }
+
+    /**
+     * Override the normal search behavior with an explicit array of IDs that must
+     * be retrieved.
+     *
+     * @param array $ids Record IDs to load
+     *
+     * @return void
+     */
+    public function setQueryIDs($ids)
+    {
+        // No need for spell checking on an ID query!
+        $this->options->spellcheckEnabled(false);
+        for ($i = 0; $i < count($ids); $i++) {
+            $ids[$i] = '"' . addcslashes($ids[$i], '"') . '"';
+        }
+        $this->setOverrideQuery('id:(' . implode(' OR ', $ids) . ')');
+    }
+
+    /**
+     * Get the maximum number of IDs that may be sent to setQueryIDs (-1 for no
+     * limit).
+     *
+     * @return int
+     */
+    public function getQueryIDLimit()
+    {
+        $config = VF_Config_Reader::getConfig();
+        return isset($config->Index->maxBooleanClauses)
+            ? $config->Index->maxBooleanClauses : 1024;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Solr/Results.php b/module/VuFind/src/VuFind/Search/Solr/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..c621177ba95a3bd752368376401b74c9e70bd21a
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Solr/Results.php
@@ -0,0 +1,584 @@
+<?php
+/**
+ * Solr aspect of the Search Multi-class (Results)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Solr_Results extends VF_Search_Base_Results
+{
+    // Raw Solr search response:
+    protected $rawResponse = null;
+
+    /**
+     * Get a connection to the Solr index.
+     *
+     * @param null|array $shards Selected shards to use (null for defaults)
+     * @param string     $index  ID of index/search classes to use (this assumes
+     * that VF_Search_$index_Options and VF_Connection_$index are both valid classes)
+     *
+     * @return VF_Connection_Solr
+     */
+    public static function getSolrConnection($shards = null, $index = 'Solr')
+    {
+        // Turn on all shards by default if none are specified (since we may get
+        // called in a static context by getRecord(), we need to be sure that any
+        // given ID will yield results, even if not all shards are on by default).
+        $options = VF_Search_Options::getInstance($index);
+        $allShards = $options->getShards();
+        if (is_null($shards)) {
+            $shards = array_keys($allShards);
+        }
+
+        // If we have selected shards, we need to format them:
+        if (!empty($shards)) {
+            $selectedShards = array();
+            foreach ($shards as $current) {
+                $selectedShards[$current] = $allShards[$current];
+            }
+            $shards = $selectedShards;
+        }
+
+        // Connect to Solr and set up shards:
+        $solr = VF_Connection_Manager::connectToIndex($index);
+        $solr->setShards($shards, $options->getSolrShardsFieldsToStrip());
+        return $solr;
+    }
+
+    /**
+     * Support method for performAndProcessSearch -- perform a search based on the
+     * parameters passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        $solr = static::getSolrConnection($this->getSelectedShards());
+
+        // Collect the search parameters:
+        $overrideQuery = $this->getOverrideQuery();
+        $params = array(
+            'query' => !empty($overrideQuery)
+                ? $overrideQuery : $solr->buildQuery($this->getSearchTerms()),
+            'handler' => $this->getSearchHandler(),
+            // Account for reserved VuFind word 'relevance' (which really means
+            // "no sort parameter in Solr"):
+            'sort' => $this->getSort() == 'relevance' ? null : $this->getSort(),
+            'start' => $this->getStartRecord() - 1,
+            'limit' => $this->getLimit(),
+            'facet' => $this->params->getFacetSettings(),
+            'filter' => $this->params->getFilterSettings(),
+            'spell' => $this->params->getSpellingQuery(),
+            'dictionary' => $this->getSpellingDictionary(),
+            'highlight' => $this->highlightEnabled()
+        );
+
+        // Perform the search:
+        $this->rawResponse = $solr->search($params);
+
+        // How many results were there?
+        $this->resultTotal = isset($this->rawResponse['response']['numFound'])
+            ? $this->rawResponse['response']['numFound'] : 0;
+
+        // Process spelling suggestions if no index error resulted from the query
+        if ($this->spellcheckEnabled()) {
+            // Shingle dictionary
+            $this->processSpelling();
+            // Make sure we don't endlessly loop
+            if ($this->getSpellingDictionary() == 'default') {
+                // Expand against the basic dictionary
+                $this->basicSpelling();
+            }
+        }
+
+        // Construct record drivers for all the items in the response:
+        $this->results = array();
+        for ($x = 0; $x < count($this->rawResponse['response']['docs']); $x++) {
+            $this->results[] = static::initRecordDriver(
+                $this->rawResponse['response']['docs'][$x]
+            );
+        }
+    }
+
+    /**
+     * Process spelling suggestions from the results object
+     *
+     * @return void
+     */
+    protected function processSpelling()
+    {
+        // Do nothing if there are no suggestions
+        $suggestions = isset($this->rawResponse['spellcheck']['suggestions']) ?
+            $this->rawResponse['spellcheck']['suggestions'] : array();
+        if (count($suggestions) == 0) {
+            return;
+        }
+
+        // Loop through the array of search terms we have suggestions for
+        $suggestionList = array();
+        foreach ($suggestions as $suggestion) {
+            $ourTerm = $suggestion[0];
+
+            // Skip numeric terms if numeric suggestions are disabled
+            if ($this->shouldSkipNumericSpelling() && is_numeric($ourTerm)) {
+                continue;
+            }
+
+            $ourHit  = $suggestion[1]['origFreq'];
+            $count   = $suggestion[1]['numFound'];
+            $newList = $suggestion[1]['suggestion'];
+
+            $validTerm = true;
+
+            // Make sure the suggestion is for a valid search term.
+            // Sometimes shingling will have bridged two search fields (in
+            // an advanced search) or skipped over a stopword.
+            if (!$this->findSearchTerm($ourTerm)) {
+                $validTerm = false;
+            }
+
+            // Unless this term had no hits
+            if ($ourHit != 0) {
+                // Filter out suggestions we are already using
+                $newList = $this->filterSpellingTerms($newList);
+            }
+
+            // Make sure it has suggestions and is valid
+            if (count($newList) > 0 && $validTerm) {
+                // Did we get more suggestions then our limit?
+                if ($count > $this->getSpellingLimit()) {
+                    // Cut the list at the limit
+                    array_splice($newList, $this->getSpellingLimit());
+                }
+                $suggestionList[$ourTerm]['freq'] = $ourHit;
+                // Format the list nicely
+                foreach ($newList as $item) {
+                    if (is_array($item)) {
+                        $suggestionList[$ourTerm]['suggestions'][$item['word']]
+                            = $item['freq'];
+                    } else {
+                        $suggestionList[$ourTerm]['suggestions'][$item] = 0;
+                    }
+                }
+            }
+        }
+        $this->suggestions = $suggestionList;
+    }
+
+    /**
+     * Filter a list of spelling suggestions to remove suggestions
+     *   we are already searching for
+     *
+     * @param array $termList List of suggestions
+     *
+     * @return array          Filtered list
+     */
+    protected function filterSpellingTerms($termList)
+    {
+        $newList = array();
+        if (count($termList) == 0) {
+            return $newList;
+        }
+
+        foreach ($termList as $term) {
+            if (!$this->findSearchTerm($term['word'])) {
+                $newList[] = $term;
+            }
+        }
+        return $newList;
+    }
+
+    /**
+     * Try running spelling against the basic dictionary.
+     *   This function should ensure it doesn't return
+     *   single word suggestions that have been accounted
+     *   for in the shingle suggestions above.
+     *
+     * @return array Suggestions array
+     */
+    protected function basicSpelling()
+    {
+        // TODO: There might be a way to run the
+        //   search against both dictionaries from
+        //   inside solr. Investigate. Currently
+        //   submitting a second search for this.
+
+        // Create a new search object
+        $myClass = get_class($this);
+        $newParams = clone($this->params);
+        $newParams->useBasicDictionary();
+
+        // Don't waste time loading facets or highlighting/retrieving results:
+        $newParams->resetFacetConfig();
+        $newParams->disableHighlighting();
+        $newParams->setLimit(0);
+        $newParams->recommendationsEnabled(false);
+
+        $newSearch = new $myClass($newParams);
+
+        // Get the spelling results
+        $newList = $newSearch->getRawSuggestions();
+
+        // If there were no shingle suggestions
+        if (count($this->suggestions) == 0) {
+            // Just use the basic ones as provided
+            $this->suggestions = $newList;
+        } else {
+            // Otherwise...
+            // For all the new suggestions
+            foreach ($newList as $word => $data) {
+                // Check the old suggestions
+                $found = false;
+                foreach ($this->suggestions as $k => $v) {
+                    // Make sure it wasn't part of a shingle
+                    //   which has been suggested at a higher
+                    //   level.
+                    $found = preg_match("/\b$word\b/", $k) ? true : $found;
+                }
+                if (!$found) {
+                    $this->suggestions[$word] = $data;
+                }
+            }
+        }
+    }
+
+    /**
+     * Turn the list of spelling suggestions into an array of urls
+     *   for on-screen use to implement the suggestions.
+     *
+     * @return array Spelling suggestion data arrays
+     */
+    public function getSpellingSuggestions()
+    {
+        $returnArray = array();
+        $suggestions = $this->getRawSuggestions();
+        $tokens = $this->spellingTokens($this->params->getSpellingQuery());
+
+        foreach ($suggestions as $term => $details) {
+            // Find out if our suggestion is part of a token
+            $inToken = false;
+            $targetTerm = "";
+            foreach ($tokens as $token) {
+                // TODO - Do we need stricter matching here?
+                //   Similar to that in replaceSearchTerm()?
+                if (stripos($token, $term) !== false) {
+                    $inToken = true;
+                    // We need to replace the whole token
+                    $targetTerm = $token;
+                    // Go and replace this token
+                    $returnArray = $this->doSpellingReplace(
+                        $term, $targetTerm, $inToken, $details, $returnArray
+                    );
+                }
+            }
+            // If no tokens were found, just look for the suggestion 'as is'
+            if ($targetTerm == "") {
+                $targetTerm = $term;
+                $returnArray = $this->doSpellingReplace(
+                    $term, $targetTerm, $inToken, $details, $returnArray
+                );
+            }
+        }
+        return $returnArray;
+    }
+
+    /**
+     * Process one instance of a spelling replacement and modify the return
+     *   data structure with the details of what was done.
+     *
+     * @param string $term        The actually term we're replacing
+     * @param string $targetTerm  The term above, or the token it is inside
+     * @param bool   $inToken     Flag for whether the token or term is used
+     * @param array  $details     The spelling suggestions
+     * @param array  $returnArray Return data structure so far
+     *
+     * @return array              $returnArray modified
+     */
+    protected function doSpellingReplace($term, $targetTerm, $inToken, $details,
+        $returnArray
+    ) {
+        $config = VF_Config_Reader::getConfig();
+
+        $returnArray[$targetTerm]['freq'] = $details['freq'];
+        foreach ($details['suggestions'] as $word => $freq) {
+            // If the suggested word is part of a token
+            if ($inToken) {
+                // We need to make sure we replace the whole token
+                $replacement = str_replace($term, $word, $targetTerm);
+            } else {
+                $replacement = $word;
+            }
+            //  Do we need to show the whole, modified query?
+            if ($config->Spelling->phrase) {
+                $label = $this->getDisplayQueryWithReplacedTerm(
+                    $targetTerm, $replacement
+                );
+            } else {
+                $label = $replacement;
+            }
+            // Basic spelling suggestion data
+            $returnArray[$targetTerm]['suggestions'][$label] = array(
+                'freq' => $freq,
+                'new_term' => $replacement
+            );
+
+            // Only generate expansions if enabled in config
+            if ($config->Spelling->expand) {
+                // Parentheses differ for shingles
+                if (strstr($targetTerm, " ") !== false) {
+                    $replacement = "(($targetTerm) OR ($replacement))";
+                } else {
+                    $replacement = "($targetTerm OR $replacement)";
+                }
+                $returnArray[$targetTerm]['suggestions'][$label]['expand_term']
+                    = $replacement;
+            }
+        }
+
+        return $returnArray;
+    }
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        // Make sure we have processed the search before proceeding:
+        if (is_null($this->rawResponse)) {
+            $this->performAndProcessSearch();
+        }
+
+        // If there is no filter, we'll use all facets as the filter:
+        if (is_null($filter)) {
+            $filter = $this->params->getFacetConfig();
+        }
+
+        // Start building the facet list:
+        $list = array();
+
+        // If we have no facets to process, give up now
+        if (!isset($this->rawResponse['facet_counts']['facet_fields'])
+            || !is_array($this->rawResponse['facet_counts']['facet_fields'])
+        ) {
+            return $list;
+        }
+
+        // Loop through every field returned by the result set
+        $validFields = array_keys($filter);
+        foreach ($this->rawResponse['facet_counts']['facet_fields']
+                 as $field => $data) {
+            // Skip filtered fields and empty arrays:
+            if (!in_array($field, $validFields) || count($data) < 1) {
+                continue;
+            }
+            // Initialize the settings for the current field
+            $list[$field] = array();
+            // Add the on-screen label
+            $list[$field]['label'] = $filter[$field];
+            // Build our array of values for this field
+            $list[$field]['list']  = array();
+            // Should we translate values for the current facet?
+            $translate = in_array($field, $this->params->getTranslatedFacets());
+            // Loop through values:
+            foreach ($data as $facet) {
+                // Initialize the array of data about the current facet:
+                $currentSettings = array();
+                $currentSettings['value'] = $facet[0];
+                $currentSettings['displayText']
+                    = $translate ? VF_Translator::translate($facet[0]) : $facet[0];
+                $currentSettings['count'] = $facet[1];
+                $currentSettings['isApplied']
+                    = $this->params->hasFilter("$field:".$facet[0]);
+
+                // Store the collected values:
+                $list[$field]['list'][] = $currentSettings;
+            }
+        }
+        return $list;
+    }
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @throws VF_Exception_RecordMissing
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        $solr = static::getSolrConnection();
+
+        // Check if we need to apply hidden filters:
+        $options = VF_Search_Options::getInstance(
+            VF_Search_Options::extractSearchClassId(get_called_class())
+        );
+        $filters = $options->getHiddenFilters();
+        $extras = empty($filters) ? array() : array('fq' => $filters);
+
+        $record = $solr->getRecord($id, $extras);
+        if (empty($record)) {
+            throw new VF_Exception_RecordMissing(
+                'Record ' . $id . ' does not exist.'
+            );
+        }
+        return static::initRecordDriver($record);
+    }
+
+    /**
+     * Static method to retrieve an array of records by ID.
+     *
+     * @param array $ids Array of unique record identifiers.
+     *
+     * @return array
+     */
+    public static function getRecords($ids)
+    {
+        // Figure out how many records to retrieve at the same time --
+        // we'll use either 100 or the ID request limit, whichever is smaller.
+        $params = new VF_Search_Solr_Params();
+        $pageSize = $params->getQueryIDLimit();
+        if ($pageSize < 1 || $pageSize > 100) {
+            $pageSize = 100;
+        }
+
+        // Retrieve records a page at a time:
+        $retVal = array();
+        while (count($ids) > 0) {
+            $currentPage = array_splice($ids, 0, $pageSize, array());
+            $params->setQueryIDs($currentPage);
+            $params->setLimit($pageSize);
+            $results = new VF_Search_Solr_Results($params);
+            $retVal = array_merge($retVal, $results->getResults());
+        }
+
+        return $retVal;
+    }
+
+    /**
+     * Method to retrieve records similar to the provided ID.  Returns an
+     * array of record driver objects.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @return array
+     */
+    public function getSimilarRecords($id)
+    {
+        $solr = static::getSolrConnection($this->getSelectedShards());
+        $filters = $this->getHiddenFilters();
+        $extras = empty($filters) ? array() : array('fq' => $filters);
+        $rawResponse = $solr->getMoreLikeThis($id, $extras);
+        $results = array();
+        for ($x = 0; $x < count($rawResponse['response']['docs']); $x++) {
+            $results[] = static::initRecordDriver(
+                $rawResponse['response']['docs'][$x]
+            );
+        }
+        return $results;
+    }
+
+    /**
+     * Support method for _performSearch(): given an array of Solr response data,
+     * construct an appropriate record driver object.
+     *
+     * @param array $data Solr data
+     *
+     * @return VF_RecordDriver_Base
+     */
+    protected static function initRecordDriver($data)
+    {
+        // Remember bad classes to prevent unnecessary file accesses.
+        static $badClasses = array();
+
+        // Determine driver path based on record type:
+        $driver = 'VF_RecordDriver_Solr' . ucwords($data['recordtype']);
+
+        // If we can't load the driver, fall back to the default, index-based one:
+        if (isset($badClasses[$driver]) || !@class_exists($driver)) {
+            $badClasses[$driver] = 1;
+            $driver = 'VF_RecordDriver_SolrDefault';
+        }
+
+        // Build the object:
+        if (class_exists($driver)) {
+            return new $driver($data);
+        }
+
+        throw new Exception('Cannot find record driver -- ' . $driver);
+    }
+    
+    /**
+     * Get complete facet counts for several index fields
+     *
+     * @param array $facetfields  name of the Solr fields to return facets for
+     * @param bool  $removeFilter Clear existing filters from selected fields (true)
+     * or retain them (false)?
+     *
+     * @return array an array with the facet values for each index field
+     */
+    public function getFullFieldFacets($facetfields, $removeFilter = true)
+    {
+        $clone = clone($this);
+
+        // Manipulate facet settings temporarily:
+        $clone->resetFacetConfig();
+        $clone->setFacetLimit(-1);
+        foreach ($facetfields as $facetName) {
+            $clone->addFacet($facetName);
+
+            // Clear existing filters for the selected field if necessary:
+            if ($removeFilter) {
+                $clone->removeAllFilters($facetName);
+            }
+        }
+
+        // Do search
+        $result = $clone->getFacetList();
+
+        // Reformat into a hash:
+        //$returnFacets = $result['facet_counts']['facet_fields'];
+        foreach ($result as $key => $value) {
+            unset($result[$key]);
+            $result[$key]['data'] = $value;
+        }
+
+        // Send back data:
+        return $result;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuth/Options.php b/module/VuFind/src/VuFind/Search/SolrAuth/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..12656659a1ab528940e4ed190aab01b9a4b54340
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuth/Options.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Solr Authority aspect of the Search Multi-class (Options)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Authority Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_SolrAuth_Options extends VF_Search_Solr_Options
+{
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->facetsIni = $this->searchIni = 'authority';
+        parent::__construct();
+        $this->spellcheck = false;
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Authority', 'action' => 'Search');
+    }
+
+    /**
+     * Return an array describing the action used for performing advanced searches
+     * (same format as expected by the URL view helper).  Return false if the feature
+     * is not supported.
+     *
+     * @return array|bool
+     */
+    public function getAdvancedSearchAction()
+    {
+        // Not currently supported:
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuth/Params.php b/module/VuFind/src/VuFind/Search/SolrAuth/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..aea7563f3cd44332d44341559c872bf5ebc1ef4a
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuth/Params.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Solr Authority aspect of the Search Multi-class (Params)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Authority Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_SolrAuth_Params extends VF_Search_Solr_Params
+{
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuth/Results.php b/module/VuFind/src/VuFind/Search/SolrAuth/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..88ade212815fa5a1a281caccbdca93145031b359
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuth/Results.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Solr Authority aspect of the Search Multi-class (Results)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Authority Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_SolrAuth_Results extends VF_Search_Solr_Results
+{
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Params $params Object representing user search
+     * parameters.
+     */
+    public function __construct(VF_Search_Base_Params $params)
+    {
+        parent::__construct($params);
+    }
+
+    /**
+     * Get a connection to the Solr index.
+     *
+     * @param null|array $shards Selected shards to use (null for defaults)
+     * @param string     $index  ID of index/search classes to use (this assumes
+     * that VF_Search_$index_Options and VF_Connection_$index are both valid classes)
+     *
+     * @return VF_Connection_Solr
+     */
+    public static function getSolrConnection($shards = null, $index = 'SolrAuth')
+    {
+        return parent::getSolrConnection($shards, $index);
+    }
+
+    /**
+     * Support method for _performSearch(): given an array of Solr response data,
+     * construct an appropriate record driver object.
+     *
+     * @param array $data Solr data
+     *
+     * @return VF_RecordDriver_Base
+     */
+    protected static function initRecordDriver($data)
+    {
+        return new VF_RecordDriver_SolrAuth($data);
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuthor/Options.php b/module/VuFind/src/VuFind/Search/SolrAuthor/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..3813a822f8778f05164807fe2c5e05c7cf6ad060
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuthor/Options.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Author aspect of the Search Multi-class (Options)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Author Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_SolrAuthor_Options extends VF_Search_Solr_Options
+{
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        parent::__construct();
+
+        // No spell check needed in author module:
+        $this->spellcheck = false;
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Author', 'action' => 'Home');
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuthor/Params.php b/module/VuFind/src/VuFind/Search/SolrAuthor/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..ee30a716d432c54ca13b7c62e44ba60cbcc86a08
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuthor/Params.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Author aspect of the Search Multi-class (Params)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Author Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_SolrAuthor_Params extends VF_Search_Solr_Params
+{
+    /**
+     * Support method for _initSearch() -- handle basic settings.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return boolean True if search settings were found, false if not.
+     */
+    protected function initBasicSearch($request)
+    {
+        // If no lookfor parameter was found, we have no search terms to
+        // add to our array!
+        if (is_null($lookfor = $request->getParam('author'))) {
+            return false;
+        }
+
+        // Force the search to be a phrase:
+        $lookfor = '"' . str_replace('"', '\"', $lookfor) . '"';
+        
+        // Set the search (handler is always Author for this module):
+        $this->setBasicSearch($lookfor, 'Author');
+        return true;
+    }
+
+    /**
+     * Build a string for onscreen display showing the
+     *   query used in the search (not the filters).
+     *
+     * @return string user friendly version of 'query'
+     */
+    public function getDisplayQuery()
+    {
+        // For display purposes, undo the query manipulation performed above
+        // in initBasicSearch():
+        $q = parent::getDisplayQuery();
+        return str_replace('\"', '"', substr($q, 1, strlen($q) - 2));
+    }
+
+    /**
+     * Load all recommendation settings from the relevant ini file.  Returns an
+     * associative array where the key is the location of the recommendations (top
+     * or side) and the value is the settings found in the file (which may be either
+     * a single string or an array of strings).
+     *
+     * @return array associative: location (top/side) => search settings
+     */
+    protected function getRecommendationSettings()
+    {
+        // Load the necessary settings to determine the appropriate recommendations
+        // module:
+        $ss = VF_Config_Reader::getConfig($this->getSearchIni());
+
+        // Load the AuthorModuleRecommendations configuration if available, use
+        // standard defaults otherwise:
+        if (isset($ss->AuthorModuleRecommendations)) {
+            $recommend = array();
+            foreach ($ss->AuthorModuleRecommendations as $section => $content) {
+                $recommend[$section] = array();
+                foreach ($content as $current) {
+                    $recommend[$section][] = $current;
+                }
+            }
+        } else {
+            $recommend = array('side' => array('ExpandFacets:Author'));
+        }
+
+        return $recommend;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuthor/Results.php b/module/VuFind/src/VuFind/Search/SolrAuthor/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..7c919db68e1c89c00ef99fc5832f685ddb5f140a
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuthor/Results.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Author aspect of the Search Multi-class (Results)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Author Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+class VF_Search_SolrAuthor_Results extends VF_Search_Solr_Results
+{
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Params $params Object representing user search
+     * parameters.
+     */
+    public function __construct($params)
+    {
+        // Call parent constructor:
+        parent::__construct($params);
+
+        // Set up URL helper to use appropriate search parameter:
+        $this->getUrl()->setBasicSearchParam('author');
+    }
+
+    /**
+     * Is the current search saved in the database?
+     *
+     * @return bool
+     */
+    public function isSavedSearch()
+    {
+        // Author searches are never saved:
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Options.php b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1eb87883e593662f1f0e682152085e3883f4a65
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Options.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * AuthorFacets aspect of the Search Multi-class (Options)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * AuthorFacets Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_SolrAuthorFacets_Options extends VF_Search_Solr_Options
+{
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        parent::__construct();
+
+        // Special sort options...
+        // It's important to remember here we are talking about on-screen
+        //   sort values, not what is sent to Solr, since this screen
+        //   is really using facet sorting.
+        $this->sortOptions = array(
+            'relevance' => 'sort_author_relevance',
+            'author' => 'sort_author_author'
+        );
+
+        // No spell check needed in author module:
+        $this->spellcheck = false;
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Author', 'action' => 'Search');
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Params.php b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8b710b092e7b489417e1314ebe0458a872628f0
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Params.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * AuthorFacets aspect of the Search Multi-class (Params)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * AuthorFacets Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_SolrAuthorFacets_Params extends VF_Search_Solr_Params
+{
+    /**
+     * Set parameters based on a search object
+     *
+     * @param Zend_Controller_Request_Abstract $request Zend request object
+     *
+     * @return void
+     */
+    public function initFromRequest($request)
+    {
+        parent::initFromRequest($request);
+
+        // Force custom facet settings:
+        $this->facetConfig = array();
+        $this->addFacet('authorStr');
+        $this->setFacetOffset(($this->getPage() - 1) * $this->getLimit());
+        $this->setFacetLimit($this->getLimit() * 10);
+        // Sorting - defaults to off with unlimited facets, so let's
+        //           be explicit here for simplicity.
+        if ($this->getSort() == 'author') {
+            $this->setFacetSort('index');
+        } else {
+            $this->setFacetSort('count');
+        }
+    }
+
+    /**
+     * Support method for _initSearch() -- handle basic settings.
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return boolean True if search settings were found, false if not.
+     */
+    protected function initBasicSearch($request)
+    {
+        // If no lookfor parameter was found, we have no search terms to
+        // add to our array!
+        if (is_null($lookfor = $request->getParam('lookfor'))) {
+            return false;
+        }
+
+        // Set the search (handler is always Author for this module):
+        $this->setBasicSearch($lookfor, 'Author');
+        return true;
+    }
+
+    /**
+     * Load all recommendation settings from the relevant ini file.  Returns an
+     * associative array where the key is the location of the recommendations (top
+     * or side) and the value is the settings found in the file (which may be either
+     * a single string or an array of strings).
+     *
+     * @return array associative: location (top/side) => search settings
+     */
+    protected function getRecommendationSettings()
+    {
+        // No recommendations here:
+        return array();
+    }
+
+    /**
+     * Initialize view
+     *
+     * @param Zend_Controller_Request_Abstract $request A Zend request object.
+     *
+     * @return void
+     */
+    protected function initView($request)
+    {
+        $this->view = 'authorfacets';
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Results.php b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..44520312a7546ac093c1be22b51babedb6a12051
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Results.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * AuthorFacets aspect of the Search Multi-class (Results)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * AuthorFacets Search Results
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_SolrAuthorFacets_Results extends VF_Search_Solr_Results
+{
+    /**
+     * Support method for performAndProcessSearch -- perform a search based on the
+     * parameters passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        $solr = VF_Connection_Manager::connectToIndex();
+
+        // Collect the search parameters:
+        $params = array(
+            'query' => $solr->buildQuery($this->getSearchTerms()),
+            'handler' => $this->getSearchHandler(),
+            'limit' => 0,
+            'facet' => $this->params->getFacetSettings(),
+        );
+
+        // Perform the search:
+        $this->rawResponse = $solr->search($params);
+
+        // Get the facets from which we will build our results:
+        $facets = $this->getFacetList(array('authorStr' => null));
+        if (isset($facets['authorStr'])) {
+            $this->resultTotal
+                = (($this->getPage() - 1) * $this->getLimit())
+                + count($facets['authorStr']['list']);
+            $this->results = array_slice(
+                $facets['authorStr']['list'], 0, $this->getLimit()
+            );
+        }
+    }
+
+    /**
+     * Is the current search saved in the database?
+     *
+     * @return bool
+     */
+    public function isSavedSearch()
+    {
+        // Author searches are never saved:
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrReserves/Options.php b/module/VuFind/src/VuFind/Search/SolrReserves/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..b13bc66edd28143381a400a4957df4982333a1b1
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrReserves/Options.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Solr Reserves aspect of the Search Multi-class (Options)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Tuan Nguyen <tuan@yorku.ca>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Reserves Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Tuan Nguyen <tuan@yorku.ca>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_SolrReserves_Options extends VF_Search_Solr_Options
+{
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->facetsIni = $this->searchIni = 'reserves';
+        parent::__construct();
+        $this->spellcheck = false;
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Search', 'action' => 'Reserves');
+    }
+
+    /**
+     * Return an array describing the action used for performing advanced searches
+     * (same format as expected by the URL view helper).  Return false if the feature
+     * is not supported.
+     *
+     * @return array|bool
+     */
+    public function getAdvancedSearchAction()
+    {
+        // Not currently supported:
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrReserves/Params.php b/module/VuFind/src/VuFind/Search/SolrReserves/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..10eb95a963366008218bdf15713caafafbcc9048
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrReserves/Params.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Solr Reserves aspect of the Search Multi-class (Params)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Tuan Nguyen <tuan@yorku.ca>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Reserves Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Tuan Nguyen <tuan@yorku.ca>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_SolrReserves_Params extends VF_Search_Solr_Params
+{
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/SolrReserves/Results.php b/module/VuFind/src/VuFind/Search/SolrReserves/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..b5033d7286b3b6ed24a4f7d2278a2290e895caad
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/SolrReserves/Results.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Solr Reserves aspect of the Search Multi-class (Results)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Tuan Nguyen <tuan@yorku.ca>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Solr Reserves Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Tuan Nguyen <tuan@yorku.ca>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_SolrReserves_Results extends VF_Search_Solr_Results
+{
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Params $params Object representing user search
+     * parameters.
+     */
+    public function __construct(VF_Search_Base_Params $params)
+    {
+        parent::__construct($params);
+    }
+
+    /**
+     * Get a connection to the Solr index.
+     *
+     * @param null|array $shards Selected shards to use (null for defaults)
+     * @param string     $index  ID of index/search classes to use (this assumes
+     * that VF_Search_$index_Options and VF_Connection_$index are both valid classes)
+     *
+     * @return VF_Connection_Solr
+     */
+    public static function getSolrConnection($shards = null,
+        $index = 'SolrReserves'
+    ) {
+        return parent::getSolrConnection($shards, $index);
+    }
+
+    /**
+     * Support method for _performSearch(): given an array of Solr response data,
+     * construct an appropriate record driver object.
+     *
+     * @param array $data Solr data
+     *
+     * @return VF_RecordDriver_Base
+     */
+    protected static function initRecordDriver($data)
+    {
+        return new VF_RecordDriver_SolrReserves($data);
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Summon/Options.php b/module/VuFind/src/VuFind/Search/Summon/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..b2bc186c46ef62915f00f20f916c623d40358400
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Summon/Options.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Summon Search Options
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Summon Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Summon_Options extends VF_Search_Base_Options
+{
+    protected $resultLimit = 400;
+
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->searchIni = $this->facetsIni = 'Summon';
+        parent::__construct();
+
+        // Load facet preferences:
+        $facetSettings = VF_Config_Reader::getConfig($this->facetsIni);
+        if (isset($facetSettings->Advanced_Settings->translated_facets)
+            && count($facetSettings->Advanced_Facet_Settings->translated_facets) > 0
+        ) {
+            $list = $facetSettings->Advanced_Facet_Settings->translated_facets;
+            foreach ($list as $c) {
+                $this->translatedFacets[] = $c;
+            }
+        }
+        if (isset($facetSettings->Advanced_Facet_Settings->special_facets)) {
+            $this->specialAdvancedFacets
+                = $facetSettings->Advanced_Facet_Settings->special_facets;
+        }
+
+        // Load the search configuration file:
+        $searchSettings = VF_Config_Reader::getConfig($this->searchIni);
+
+        // Set up highlighting preference
+        if (isset($searchSettings->General->highlighting)) {
+            $this->highlight = $searchSettings->General->highlighting;
+        }
+
+        // Set up spelling preference
+        if (isset($searchSettings->Spelling->enabled)) {
+            $this->spellcheck = $searchSettings->Spelling->enabled;
+        }
+
+        // Load search preferences:
+        if (isset($searchSettings->General->retain_filters_by_default)) {
+            $this->retainFiltersByDefault
+                = $searchSettings->General->retain_filters_by_default;
+        }
+        if (isset($searchSettings->General->result_limit)) {
+            $this->resultLimit = $searchSettings->General->result_limit;
+        }
+
+        // Search handler setup:
+        if (isset($searchSettings->Basic_Searches)) {
+            foreach ($searchSettings->Basic_Searches as $key => $value) {
+                $this->basicHandlers[$key] = $value;
+            }
+        }
+        if (isset($searchSettings->Advanced_Searches)) {
+            foreach ($searchSettings->Advanced_Searches as $key => $value) {
+                $this->advancedHandlers[$key] = $value;
+            }
+        }
+
+        // Load sort preferences:
+        if (isset($searchSettings->Sorting)) {
+            foreach ($searchSettings->Sorting as $key => $value) {
+                $this->sortOptions[$key] = $value;
+            }
+        }
+        if (isset($searchSettings->General->default_sort)) {
+            $this->defaultSort = $searchSettings->General->default_sort;
+        }
+        if (isset($searchSettings->DefaultSortingByType)
+            && count($searchSettings->DefaultSortingByType) > 0
+        ) {
+            foreach ($searchSettings->DefaultSortingByType as $key => $val) {
+                $this->defaultSortByHandler[$key] = $val;
+            }
+        }
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Summon', 'action' => 'Search');
+    }
+
+    /**
+     * Return an array describing the action used for performing advanced searches
+     * (same format as expected by the URL view helper).  Return false if the feature
+     * is not supported.
+     *
+     * @return array|bool
+     */
+    public function getAdvancedSearchAction()
+    {
+        return array('controller' => 'Summon', 'action' => 'Advanced');
+    }
+
+    /**
+     * If there is a limit to how many search results a user can access, this
+     * method will return that limit.  If there is no limit, this will return
+     * -1.
+     *
+     * @return int
+     */
+    public function getVisibleSearchResultLimit()
+    {
+        return intval($this->resultLimit);
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Summon/Params.php b/module/VuFind/src/VuFind/Search/Summon/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..de56e03135a67683a2e0cbb519e0e5e614c5c85e
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Summon/Params.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Summon Search Parameters
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Summon Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Summon_Params extends VF_Search_Base_Params
+{
+    protected $fullFacetSettings = array();
+    protected $dateFacetSettings = array();
+
+    /**
+     * Add a field to facet on.
+     *
+     * @param string $newField Field name
+     * @param string $newAlias Optional on-screen display label
+     *
+     * @return void
+     */
+    public function addFacet($newField, $newAlias = null)
+    {
+        // Save the full field name (which may include extra parameters);
+        // we'll need these to do the proper search using the Summon class:
+        if (strstr($newField, 'PublicationDate')) {
+            // Special case -- we don't need to send this to the Summon API,
+            // but we do need to set a flag so VuFind knows to display the
+            // date facet control.
+            $this->dateFacetSettings[] = 'PublicationDate';
+        } else {
+            $this->fullFacetSettings[] = $newField;
+        }
+
+        // Field name may have parameters attached -- remove them:
+        $parts = explode(',', $newField);
+        return parent::addFacet($parts[0], $newAlias);
+    }
+
+    /**
+     * Get the full facet settings stored by addFacet -- these may include extra
+     * parameters needed by the search results class.
+     *
+     * @return array
+     */
+    public function getFullFacetSettings()
+    {
+        return $this->fullFacetSettings;
+    }
+
+    /**
+     * Get the date facet settings stored by addFacet.
+     *
+     * @return array
+     */
+    public function getDateFacetSettings()
+    {
+        return $this->dateFacetSettings;
+    }
+
+    /**
+     * Get a user-friendly string to describe the provided facet field.
+     *
+     * @param string $field Facet field name.
+     *
+     * @return string       Human-readable description of field.
+     */
+    public function getFacetLabel($field)
+    {
+        // The default use of "Other" for undefined facets doesn't work well with
+        // checkbox facets -- we'll use field names as the default within the Summon
+        // search object.
+        return isset($this->facetConfig[$field])
+            ? $this->facetConfig[$field] : $field;
+    }
+
+    /**
+     * Get information on the current state of the boolean checkbox facets.
+     *
+     * @return array
+     */
+    public function getCheckboxFacets()
+    {
+        // Grab checkbox facet details using the standard method:
+        $facets = parent::getCheckboxFacets();
+
+        // Special case -- if we have a "holdings only" facet, we want this to
+        // always appear, even on the "no results" screen, since setting this
+        // facet actually EXPANDS the result set, rather than reducing it:
+        if (isset($facets['holdingsOnly'])) {
+            $facets['holdingsOnly']['alwaysVisible'] = true;
+        }
+
+        // Return modified list:
+        return $facets;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Summon/Results.php b/module/VuFind/src/VuFind/Search/Summon/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..fbd19868b81e4ca04d1845ed96f8352e8a5d1dba
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Summon/Results.php
@@ -0,0 +1,316 @@
+<?php
+/**
+ * Summon Search Results
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * Summon Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_Summon_Results extends VF_Search_Base_Results
+{
+    // Raw search response:
+    protected $rawResponse = null;
+
+    /**
+     * Get a connection to the Summon API.
+     *
+     * @return VF_Connection_Summon
+     */
+    public static function getSummonConnection()
+    {
+        static $conn = false;
+        if (!$conn) {
+            $config = VF_Config_Reader::getConfig();
+            $id = isset($config->Summon->apiId) ? $config->Summon->apiId : null;
+            $key = isset($config->Summon->apiKey) ? $config->Summon->apiKey : null;
+            $conn = new VF_Connection_Summon($id, $key);
+        }
+        return $conn;
+    }
+
+    /**
+     * Support method for performAndProcessSearch -- perform a search based on the
+     * parameters passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        // The "relevance" sort option is a VuFind reserved word; we need to make
+        // this null in order to achieve the desired effect with Summon:
+        $sort = $this->params->getSort();
+        $finalSort = ($sort == 'relevance') ? null : $sort;
+
+        // Perform the actual search
+        $summon = self::getSummonConnection();
+        $query = new VF_Connection_Summon_Query(
+            $summon->buildQuery($this->getSearchTerms()),
+            array(
+                'sort' => $finalSort,
+                'pageNumber' => $this->params->getPage(),
+                'pageSize' => $this->params->getLimit(),
+                'didYouMean' => $this->spellcheckEnabled()
+            )
+        );
+        if ($this->highlightEnabled()) {
+            $query->setHighlight(true);
+            $query->setHighlightStart('{{{{START_HILITE}}}}');
+            $query->setHighlightEnd('{{{{END_HILITE}}}}');
+        }
+        $query->initFacets($this->params->getFullFacetSettings());
+        $query->initFilters($this->params->getFilterList());
+        $this->rawResponse = $summon->query($query);
+
+        // Add fake date facets if flagged earlier; this is necessary in order
+        // to display the date range facet control in the interface.
+        $dateFacets = $this->params->getDateFacetSettings();
+        if (!empty($dateFacets)) {
+            if (!isset($this->rawResponse['facetFields'])) {
+                $this->rawResponse['facetFields'] = array();
+            }
+            foreach ($dateFacets as $dateFacet) {
+                $this->rawResponse['facetFields'][] = array(
+                    'fieldName' => 'PublicationDate',
+                    'displayName' => 'PublicationDate',
+                    'counts' => array()
+                );
+            }
+        }
+
+        // Save spelling details if they exist.
+        if ($this->spellcheckEnabled()) {
+            $this->processSpelling();
+        }
+
+        // Store relevant details from the search results:
+        $this->resultTotal = $this->rawResponse['recordCount'];
+
+        // Construct record drivers for all the items in the response:
+        $this->results = array();
+        foreach ($this->rawResponse['documents'] as $current) {
+            $this->results[] = self::initRecordDriver($current);
+        }
+    }
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @throws VF_Exception_RecordMissing
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        $summon = static::getSummonConnection();
+        $record = $summon->getRecord($id);
+        if (empty($record) || !isset($record['documents'][0])) {
+            throw new VF_Exception_RecordMissing(
+                'Record ' . $id . ' does not exist.'
+            );
+        }
+        return static::initRecordDriver($record['documents'][0]);
+    }
+
+    /**
+     * Support method for _performSearch(): given an array of Solr response data,
+     * construct an appropriate record driver object.
+     *
+     * @param array $data Raw record data
+     *
+     * @return VF_RecordDriver_Base
+     */
+    protected static function initRecordDriver($data)
+    {
+        return new VF_RecordDriver_Summon($data);
+    }
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        // If there is no filter, we'll use all facets as the filter:
+        if (is_null($filter)) {
+            $filter = $this->params->getFacetConfig();
+        } else {
+            // If there is a filter, make sure the field names are properly
+            // stripped of extra parameters:
+            $oldFilter = $filter;
+            $filter = array();
+            foreach ($oldFilter as $key => $value) {
+                $key = explode(',', $key);
+                $key = trim($key[0]);
+                $filter[$key] = $value;
+            }
+        }
+
+        // We want to sort the facets to match the order in the .ini file.  Let's
+        // create a lookup array to determine order:
+        $i = 0;
+        $order = array();
+        foreach ($filter as $key => $value) {
+            $order[$key] = $i++;
+        }
+
+        // Loop through the facets returned by Summon.
+        $facetResult = array();
+        if (isset($this->rawResponse['facetFields'])
+            && is_array($this->rawResponse['facetFields'])
+        ) {
+            // Get the filter list -- we'll need to check it below:
+            $filterList = $this->params->getFilters();
+
+            foreach ($this->rawResponse['facetFields'] as $current) {
+                // The "displayName" value is actually the name of the field on
+                // Summon's side -- we'll probably need to translate this to a
+                // different value for actual display!
+                $field = $current['displayName'];
+
+                // Is this one of the fields we want to display?  If so, do work...
+                if (isset($filter[$field])) {
+                    // Should we translate values for the current facet?
+                    $translate
+                        = in_array($field, $this->params->getTranslatedFacets());
+
+                    // Loop through all the facet values to see if any are applied.
+                    foreach ($current['counts'] as $facetIndex => $facetDetails) {
+                        // Is the current field negated?  If so, we don't want to
+                        // show it -- this is currently used only for the special
+                        // "exclude newspapers" facet:
+                        if ($facetDetails['isNegated']) {
+                            unset($current['counts'][$facetIndex]);
+                            continue;
+                        }
+
+                        // We need to check two things to determine if the current
+                        // value is an applied filter.  First, is the current field
+                        // present in the filter list?  Second, is the current value
+                        // an active filter for the current field?
+                        $isApplied = in_array($field, array_keys($filterList))
+                            && in_array(
+                                $facetDetails['value'], $filterList[$field]
+                            );
+
+                        // Inject "applied" value into Summon results:
+                        $current['counts'][$facetIndex]['isApplied'] = $isApplied;
+
+                        // Create display value:
+                        $current['counts'][$facetIndex]['displayText'] = $translate
+                            ? VF_Translator::translate($facetDetails['value'])
+                            : $facetDetails['value'];
+                    }
+
+                    // Put the current facet cluster in order based on the .ini
+                    // settings, then override the display name again using .ini
+                    // settings.
+                    $i = $order[$field];
+                    $current['label'] = $filter[$field];
+
+                    // Create a reference to counts called list for consistency with
+                    // Solr output format -- this allows the facet recommendations
+                    // modules to be shared between the Search and Summon modules.
+                    $current['list'] = & $current['counts'];
+                    $facetResult[$i] = $current;
+                }
+            }
+        }
+        ksort($facetResult);
+
+        // Rewrite the sorted array with appropriate keys:
+        $finalResult = array();
+        foreach ($facetResult as $current) {
+            $finalResult[$current['displayName']] = $current;
+        }
+
+        return $finalResult;
+    }
+
+    /**
+     * Process spelling suggestions from the results object
+     *
+     * @return void
+     */
+    protected function processSpelling()
+    {
+        if (isset($this->rawResponse['didYouMeanSuggestions'])
+            && is_array($this->rawResponse['didYouMeanSuggestions'])
+        ) {
+            $this->suggestions = array();
+            foreach ($this->rawResponse['didYouMeanSuggestions'] as $current) {
+                if (!isset($this->suggestions[$current['originalQuery']])) {
+                    $this->suggestions[$current['originalQuery']] = array(
+                        'suggestions' => array()
+                    );
+                }
+                $this->suggestions[$current['originalQuery']]['suggestions'][]
+                    = $current['suggestedQuery'];
+            }
+        }
+    }
+
+    /**
+     * Turn the list of spelling suggestions into an array of urls
+     *   for on-screen use to implement the suggestions.
+     *
+     * @return array Spelling suggestion data arrays
+     */
+    public function getSpellingSuggestions()
+    {
+        $retVal = array();
+        foreach ($this->getRawSuggestions() as $term => $details) {
+            foreach ($details['suggestions'] as $word) {
+                // Strip escaped characters in the search term (for example, "\:")
+                $term = stripcslashes($term);
+                $word = stripcslashes($word);
+                $retVal[$term]['suggestions'][$word] = array('new_term' => $word);
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Get database recommendations from Summon, if any.
+     *
+     * @return array|bool false if no recommendations, detailed array otherwise.
+     */
+    public function getDatabaseRecommendations()
+    {
+        return isset($this->rawResponse['recommendationLists']['database']) ?
+            $this->rawResponse['recommendationLists']['database'] : false;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Tags/Options.php b/module/VuFind/src/VuFind/Search/Tags/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c61609075671b38559ac043f3811c5795371a20
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Tags/Options.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Tags aspect of the Search Multi-class (Options)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Tags Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_Tags_Options extends VF_Search_Base_Options
+{
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+        $this->basicHandlers = array('tags' => 'Tag');
+        $this->defaultSort = 'title';
+        $this->sortOptions = array(
+            'title' => 'sort_title', 'author' => 'sort_author',
+            'year DESC' => 'sort_year', 'year' => 'sort_year asc'
+        );
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'Tag', 'action' => 'Home');
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Tags/Params.php b/module/VuFind/src/VuFind/Search/Tags/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..10008e0534c5f261e5c34bc1d41baa65a1599057
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Tags/Params.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Tags aspect of the Search Multi-class (Params)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Tags Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_Tags_Params extends VF_Search_Base_Params
+{
+    /**
+     * Load all recommendation settings from the relevant ini file.  Returns an
+     * associative array where the key is the location of the recommendations (top
+     * or side) and the value is the settings found in the file (which may be either
+     * a single string or an array of strings).
+     *
+     * @return array associative: location (top/side) => search settings
+     */
+    protected function getRecommendationSettings()
+    {
+        // No recommendation modules in tag view currently:
+        return array();
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Tags/Results.php b/module/VuFind/src/VuFind/Search/Tags/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ccb7dac6618b7afed3a2449658197d826ba7b25
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Tags/Results.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Tags aspect of the Search Multi-class (Results)
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Search Tags Results
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_Tags_Results extends VF_Search_Base_Results
+{
+    /**
+     * Support method for performAndProcessSearch -- perform a search based on the
+     * parameters passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        $table = new VuFind_Model_Db_Tags();
+        $tag = $table->getByText($this->getDisplayQuery());
+        if (!empty($tag)) {
+            $rawResults = $tag->getResources(null, $this->getSort());
+        } else {
+            $rawResults = array();
+        }
+
+        // How many results were there?
+        $this->resultTotal = count($rawResults);
+
+        // Retrieve record drivers for the selected items.
+        $end = $this->getEndRecord();
+        $recordsToRequest = array();
+        for ($i = $this->getStartRecord() - 1; $i < $end; $i++) {
+            $row = $rawResults->getRow($i);
+            $recordsToRequest[]
+                = array('id' => $row->record_id, 'source' => $row->source);
+        }
+        $this->results = VF_Record::loadBatch($recordsToRequest);
+    }
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        throw new Exception(
+            'getRecord not supported by VF_Tags_Favorites_Results'
+        );
+    }
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        // Facets not supported:
+        return array();
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/UrlHelper.php b/module/VuFind/src/VuFind/Search/UrlHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..0d43f52c42be95388f335f6e7b509c905b0a8a17
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/UrlHelper.php
@@ -0,0 +1,450 @@
+<?php
+/**
+ * Class to help build URLs and forms in the view based on search settings.
+ *
+ * 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  View_Helpers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+ 
+/**
+ * Class to help build URLs and forms in the view based on search settings.
+ *
+ * @category VuFind2
+ * @package  View_Helpers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class VF_Search_UrlHelper extends Zend_View_Helper_Abstract
+{
+    protected $results;
+    protected $basicSearchParam = 'lookfor';
+    protected $defaultParams = array();
+
+    /**
+     * Constructor
+     *
+     * @param VF_Search_Base_Results $results VuFind search results object.
+     */
+    public function __construct($results)
+    {
+        $this->results = $results;
+    }
+    
+    /**
+     * Set the name of the parameter used for basic search terms.
+     *
+     * @param string $param Parameter name to set.
+     *
+     * @return void
+     */
+    public function setBasicSearchParam($param)
+    {
+        $this->basicSearchParam = $param;
+    }
+
+    /**
+     * Add a parameter to the object.
+     *
+     * @param string $name  Name of parameter
+     * @param string $value Value of parameter
+     *
+     * @return void
+     */
+    public function setDefaultParameter($name, $value)
+    {
+        $this->defaultParams[$name] = $value;
+    }
+
+    /**
+     * Get an array of URL parameters.
+     *
+     * @return array
+     */
+    protected function getParamArray()
+    {
+        $params = $this->defaultParams;
+
+        // Build all the URL parameters based on search object settings:
+        if ($this->results->getSearchType() == 'advanced') {
+            $terms = $this->results->getSearchTerms();
+            if (isset($terms[0]['join'])) {
+                $params['join'] = $terms[0]['join'];
+            }
+            for ($i = 0; $i < count($terms); $i++) {
+                if (isset($terms[$i]['group'])) {
+                    $params['bool' . $i] = array($terms[$i]['group'][0]['bool']);
+                    for ($j = 0; $j < count($terms[$i]['group']); $j++) {
+                        if (!isset($params['lookfor' . $i])) {
+                            $params['lookfor' . $i] = array();
+                        }
+                        if (!isset($params['type' . $i])) {
+                            $params['type' . $i] = array();
+                        }
+                        $params['lookfor'.$i][] = $terms[$i]['group'][$j]['lookfor'];
+                        $params['type' . $i][] = $terms[$i]['group'][$j]['field'];
+                    }
+                }
+            }
+        } else {
+            $search = $this->results->getDisplayQuery();
+            if (!empty($search)) {
+                $params[$this->basicSearchParam] = $search;
+            }
+            $type = $this->results->getSearchHandler();
+            if (!empty($type)) {
+                $params['type'] = $type;
+            }
+        }
+        $sort = $this->results->getSort();
+        if (!is_null($sort) && $sort != $this->results->getDefaultSort()) {
+            $params['sort'] = $sort;
+        }
+        $limit = $this->results->getLimit();
+        if (!is_null($limit) && $limit != $this->results->getDefaultLimit()) {
+            $params['limit'] = $limit;
+        }
+        $view = $this->results->getView();
+        if (!is_null($view) && $view != $this->results->getDefaultView()) {
+            $params['view'] = $view;
+        }
+        if ($this->results->getPage() != 1) {
+            $params['page'] = $this->results->getPage();
+        }
+        $filters = $this->results->getFilters();
+        if (!empty($filters)) {
+            $params['filter'] = array();
+            foreach ($filters as $field => $values) {
+                foreach ($values as $current) {
+                    $params['filter'][] = $field . ':"' . $current . '"';
+                }
+            }
+        }
+        $shards = $this->results->getSelectedShards();
+        if (!empty($shards)) {
+            sort($shards);
+            $key = implode(':::', $shards);
+            $defaultShards = $this->results->getDefaultSelectedShards();
+            sort($defaultShards);
+            if (implode(':::', $shards) != implode(':::', $defaultShards)) {
+                $params['shard'] = $shards;
+            }
+        }
+
+        return $params;
+    }
+
+    /**
+     * Replace a term in the search query (used for spelling replacement)
+     *
+     * @param string $from Search term to find
+     * @param string $to   Search term to insert
+     *
+     * @return string
+     */
+    public function replaceTerm($from, $to)
+    {
+        $newResults = clone($this->results);
+        $newResults->replaceSearchTerm($from, $to);
+        $myClass = get_class($this);
+        $helper = new $myClass($newResults);
+        return $helper->getParams();
+    }
+
+    /**
+     * Add a facet to the parameters.
+     *
+     * @param string $field Facet field
+     * @param string $value Facet value
+     *
+     * @return string
+     */
+    public function addFacet($field, $value)
+    {
+        // Facets are just a special case of filters:
+        return $this->addFilter($field . ':"' . $value . '"');
+    }
+
+    /**
+     * Add a filter to the parameters.
+     *
+     * @param string $filter Filter to add
+     *
+     * @return string
+     */
+    public function addFilter($filter)
+    {
+        $params = $this->getParamArray();
+
+        // Add the filter:
+        if (!isset($params['filter'])) {
+            $params['filter'] = array();
+        }
+        $params['filter'][] = $filter;
+
+        // Clear page:
+        unset($params['page']);
+
+        return '?' . $this->buildQueryString($params);
+    }
+
+    /**
+     * Get the current search parameters as a GET query.
+     *
+     * @param bool $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function getParams($escape = true)
+    {
+        return '?' . $this->buildQueryString($this->getParamArray(), $escape);
+    }
+
+    /**
+     * Remove a facet from the parameters.
+     *
+     * @param string $field  Facet field
+     * @param string $value  Facet value
+     * @param bool   $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function removeFacet($field, $value, $escape = true)
+    {
+        $params = $this->getParamArray();
+
+        // Remove the filter:
+        $newFilter = array();
+        if (isset($params['filter']) && is_array($params['filter'])) {
+            foreach ($params['filter'] as $current) {
+                list($currentField, $currentValue)
+                    = $this->results->parseFilter($current);
+                if ($currentField != $field || $currentValue != $value) {
+                    $newFilter[] = $current;
+                }
+            }
+        }
+        if (empty($newFilter)) {
+            unset($params['filter']);
+        } else {
+            $params['filter'] = $newFilter;
+        }
+
+        // Clear page:
+        unset($params['page']);
+
+        return '?' . $this->buildQueryString($params, $escape);
+    }
+
+    /**
+     * Remove a filter from the parameters.
+     *
+     * @param string $filter Filter to add
+     * @param bool   $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function removeFilter($filter, $escape = true)
+    {
+        // Treat this as a special case of removeFacet:
+        list($field, $value) = $this->results->parseFilter($filter);
+        return $this->removeFacet($field, $value, $escape);
+    }
+
+    /**
+     * Return HTTP parameters to render a different page of results.
+     *
+     * @param string $p      New page parameter (null for NO page parameter)
+     * @param bool   $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function setPage($p, $escape = true)
+    {
+        return $this->updateQueryString('page', $p, 1, $escape);
+    }
+
+    /**
+     * Return HTTP parameters to render the current page with a different sort
+     * parameter.
+     *
+     * @param string $s      New sort parameter (null for NO sort parameter)
+     * @param bool   $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function setSort($s, $escape = true)
+    {
+        return $this->updateQueryString(
+            'sort', $s, $this->results->getDefaultSort(), $escape
+        );
+    }
+
+    /**
+     * Return HTTP parameters to render the current page with a different search
+     * handler.
+     *
+     * @param string $handler new Handler.
+     * @param bool   $escape  Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function setHandler($handler, $escape = true)
+    {
+        return $this->updateQueryString(
+            'type', $handler, $this->results->getDefaultHandler(), $escape
+        );
+    }
+
+    /**
+     * Return HTTP parameters to render the current page with a different view
+     * parameter.
+     *
+     * Note: This is called setViewParam rather than setView to avoid a conflict
+     * with the Zend_View_Helper_Abstract interface.
+     *
+     * @param string $v      New sort parameter (null for NO view parameter)
+     * @param bool   $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function setViewParam($v, $escape = true)
+    {
+        // Because of the way view settings are stored in the session, we always
+        // want an explicit value here (hence null rather than default view in
+        // third parameter below):
+        return $this->updateQueryString('view', $v, null, $escape);
+    }
+
+    /**
+     * Return HTTP parameters to render the current page with a different limit
+     * parameter.
+     *
+     * @param string $l      New limit parameter (null for NO limit parameter)
+     * @param bool   $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    public function setLimit($l, $escape = true)
+    {
+        return $this->updateQueryString(
+            'limit', $l, $this->results->getDefaultLimit(), $escape
+        );
+    }
+
+    /**
+     * Turn the current GET parameters into a set of hidden form fields.
+     *
+     * @param array $filter Array of parameters to exclude -- key = field name,
+     * value = regular expression to exclude.
+     *
+     * @return string
+     */
+    public function asHiddenFields($filter = array())
+    {
+        $retVal = '';
+        foreach ($this->getParamArray() as $paramName => $paramValue) {
+            if (is_array($paramValue)) {
+                foreach ($paramValue as $paramValue2) {
+                    if (!$this->filtered($paramName, $paramValue2, $filter)) {
+                        $retVal .= '<input type="hidden" name="' .
+                            htmlspecialchars($paramName) . '[]" value="' .
+                            htmlspecialchars($paramValue2) . '" />';
+                    }
+                }
+            } else {
+                if (!$this->filtered($paramName, $paramValue, $filter)) {
+                    $retVal .= '<input type="hidden" name="' .
+                        htmlspecialchars($paramName) . '" value="' .
+                        htmlspecialchars($paramValue) . '" />';
+                }
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Support method for asHiddenFields -- are the provided field and value
+     * excluded by the provided filter?
+     *
+     * @param string $field  Field to check
+     * @param string $value  Regular expression to check
+     * @param array  $filter Filter provided to asHiddenFields() above
+     *
+     * @return bool
+     */
+    protected function filtered($field, $value, $filter)
+    {
+        return (isset($filter[$field]) && preg_match($filter[$field], $value));
+    }
+
+    /**
+     * Generic case of parameter rebuilding.
+     *
+     * @param string $field   Field to update
+     * @param string $value   Value to use (null to skip field entirely)
+     * @param string $default Default value (skip field if $value matches; null
+     *                        for no default).
+     * @param bool   $escape  Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    protected function updateQueryString($field, $value, $default = null,
+        $escape = true
+    ) {
+        $params = $this->getParamArray();
+        if (is_null($value) || $value == $default) {
+            unset($params[$field]);
+        } else {
+            $params[$field] = $value;
+        }
+        return '?' . $this->buildQueryString($params, $escape);
+    }
+
+    /**
+     * Turn an array into a properly URL-encoded query string.  This is
+     * equivalent to the built-in PHP http_build_query function, but it handles
+     * arrays in a more compact way and ensures that ampersands don't get
+     * messed up based on server-specific settings.
+     *
+     * @param array $a      Array of parameters to turn into a GET string
+     * @param bool  $escape Should we escape the string for use in the view?
+     *
+     * @return string
+     */
+    protected function buildQueryString($a, $escape = true)
+    {
+        $parts = array();
+        foreach ($a as $key => $value) {
+            if (is_array($value)) {
+                foreach ($value as $current) {
+                    $parts[] = urlencode($key . '[]') . '=' . urlencode($current);
+                }
+            } else {
+                $parts[] = urlencode($key) . '=' . urlencode($value);
+            }
+        }
+        $retVal = implode('&', $parts);
+        return $escape ? htmlspecialchars($retVal) : $retVal;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Options.php b/module/VuFind/src/VuFind/Search/WorldCat/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..508b8bdb4009a3cea972ef9cbd82bcc04ede25ab
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/WorldCat/Options.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * WorldCat Search Options
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * WorldCat Search Options
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_WorldCat_Options extends VF_Search_Base_Options
+{
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->searchIni = $this->facetsIni = 'WorldCat';
+        parent::__construct();
+
+        // Load the configuration file:
+        $searchSettings = VF_Config_Reader::getConfig($this->searchIni);
+
+        // Search handler setup:
+        $this->defaultHandler = 'srw.kw';
+        if (isset($searchSettings->Basic_Searches)) {
+            foreach ($searchSettings->Basic_Searches as $key => $value) {
+                $this->basicHandlers[$key] = $value;
+            }
+        }
+        if (isset($searchSettings->Advanced_Searches)) {
+            foreach ($searchSettings->Advanced_Searches as $key => $value) {
+                $this->advancedHandlers[$key] = $value;
+            }
+        }
+
+        // Load sort preferences:
+        if (isset($searchSettings->Sorting)) {
+            foreach ($searchSettings->Sorting as $key => $value) {
+                $this->sortOptions[$key] = $value;
+            }
+        }
+        if (isset($searchSettings->General->default_sort)) {
+            $this->defaultSort = $searchSettings->General->default_sort;
+        }
+        if (isset($searchSettings->DefaultSortingByType)
+            && count($searchSettings->DefaultSortingByType) > 0
+        ) {
+            foreach ($searchSettings->DefaultSortingByType as $key => $val) {
+                $this->defaultSortByHandler[$key] = $val;
+            }
+        }
+    }
+
+    /**
+     * Return an array describing the action used for rendering search results
+     * (same format as expected by the URL view helper).
+     *
+     * @return array
+     */
+    public function getSearchAction()
+    {
+        return array('controller' => 'WorldCat', 'action' => 'Search');
+    }
+
+    /**
+     * Return an array describing the action used for performing advanced searches
+     * (same format as expected by the URL view helper).  Return false if the feature
+     * is not supported.
+     *
+     * @return array|bool
+     */
+    public function getAdvancedSearchAction()
+    {
+        return array('controller' => 'WorldCat', 'action' => 'Advanced');
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Params.php b/module/VuFind/src/VuFind/Search/WorldCat/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..c11e2712d19639ae66517741ef9f46b76487122b
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/WorldCat/Params.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * WorldCat Search Parameters
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * WorldCat Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_WorldCat_Params extends VF_Search_Base_Params
+{
+    // Override Query
+    protected $overrideQuery = false;
+
+    /**
+     * Set the override query
+     *
+     * @param string $q Override query
+     *
+     * @return void
+     */
+    public function setOverrideQuery($q)
+    {
+        $this->overrideQuery = $q;
+    }
+
+    /**
+     * Get the override query
+     *
+     * @return string
+     */
+    public function getOverrideQuery()
+    {
+        return $this->overrideQuery;
+    }
+}
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..728b5d87224eebd6b1c51b4f81d4600fe38c3557
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/WorldCat/Results.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * WorldCat Search Results
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * 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  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+
+/**
+ * WorldCat Search Parameters
+ *
+ * @category VuFind2
+ * @package  SearchObject
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class VF_Search_WorldCat_Results extends VF_Search_Base_Results
+{
+    // Raw search response:
+    protected $rawResponse = null;
+
+    /**
+     * Get a connection to the WorldCat API.
+     *
+     * @return VF_Connection_WorldCat
+     */
+    public static function getWorldCatConnection()
+    {
+        static $wc = false;
+        if (!$wc) {
+            $wc = new VF_Connection_WorldCat();
+        }
+        return $wc;
+    }
+
+    /**
+     * Support method for performAndProcessSearch -- perform a search based on the
+     * parameters passed to the object.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        // Collect the search parameters:
+        $config = VF_Config_Reader::getConfig();
+        $wc = static::getWorldCatConnection();
+        $overrideQuery = $this->getOverrideQuery();
+        $query = empty($overrideQuery)
+            ? $wc->buildQuery($this->getSearchTerms()) : $overrideQuery;
+
+        // Perform the search:
+        $this->rawResponse  = $wc->search(
+            $query, $config->WorldCat->OCLCCode, $this->getPage(), $this->getLimit(),
+            $this->getSort()
+        );
+
+        // 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[] = static::initRecordDriver(
+                    $current->recordData->record->asXML()
+                );
+            }
+        }
+    }
+
+    /**
+     * Static method to retrieve a record by ID.  Returns a record driver object.
+     *
+     * @param string $id Unique identifier of record
+     *
+     * @throws VF_Exception_RecordMissing
+     * @return VF_RecordDriver_Base
+     */
+    public static function getRecord($id)
+    {
+        $wc = static::getWorldCatConnection();
+        $record = $wc->getRecord($id);
+        if (empty($record)) {
+            throw new VF_Exception_RecordMissing(
+                'Record ' . $id . ' does not exist.'
+            );
+        }
+        return static::initRecordDriver($record);
+    }
+
+    /**
+     * Support method for _performSearch(): given an array of Solr response data,
+     * construct an appropriate record driver object.
+     *
+     * @param array $data Raw record data
+     *
+     * @return VF_RecordDriver_Base
+     */
+    protected static function initRecordDriver($data)
+    {
+        return new VF_RecordDriver_WorldCat($data);
+    }
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        // No facets in WorldCat:
+        return array();
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php b/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php
index cc3f11effeb18e4d731e446e556aaeb4b0e41c85..e71341e525f75daf893c37c504800b63d462fe60 100644
--- a/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php
+++ b/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php
@@ -25,6 +25,8 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  */
+namespace VuFind\Theme\Root\Helper;
+use VuFind\Search\Options, Zend\View\Helper\AbstractHelper;
 
 /**
  * "Retrieve search options" view helper
@@ -35,17 +37,17 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  */
-class VuFind_Theme_Root_Helper_SearchOptions extends Zend_View_Helper_Abstract
+class SearchOptions extends AbstractHelper
 {
     /**
      * Wrapper function to the VF_Search_Options getInstance function
      *
      * @param string $type The search type of the object to retrieve
      *
-     * @return VF_Search_Base_Options
+     * @return SearchOptions
      */
-    public function searchOptions($type = 'Solr')
+    public function __invoke($type = 'Solr')
     {
-        return VF_Search_Options::getInstance($type);
+        return Options::getInstance($type);
     }
 }
\ No newline at end of file
diff --git a/themes/vufind/root/theme.ini b/themes/vufind/root/theme.ini
index e681ac4e3bf472d74ff5b3a3fc6f9b1bbaf5cd06..308b7e07c08bd79f48ab6d9ebfc5d92767b9edbb 100644
--- a/themes/vufind/root/theme.ini
+++ b/themes/vufind/root/theme.ini
@@ -7,5 +7,6 @@ helpers_to_register[] = "HeadScript"
 helpers_to_register[] = "HeadThemeResources"
 helpers_to_register[] = "ImageLink"
 helpers_to_register[] = "MobileUrl"
+helpers_to_register[] = "SearchOptions"
 helpers_to_register[] = "TransEsc"
 helpers_to_register[] = "Translate"