diff --git a/module/VuFind/src/VuFind/Connection/Manager.php b/module/VuFind/src/VuFind/Connection/Manager.php
new file mode 100644
index 0000000000000000000000000000000000000000..5fe83d41075953dd4a009af748ca690a86933b9d
--- /dev/null
+++ b/module/VuFind/src/VuFind/Connection/Manager.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Central class for connecting to resources used by VuFind.
+ *
+ * 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  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
+ */
+namespace VuFind\Connection;
+
+use VuFind\Config\Reader as ConfigReader, VuFind\ILS\Connection as ILSConnection;
+
+/**
+ * Central class for connecting to resources used by VuFind.
+ *
+ * @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 Manager
+{
+    /**
+     * Connect to the catalog.
+     *
+     * @return mixed CatalogConnection object on success, boolean false on error
+     */
+    public static function connectToCatalog()
+    {
+        // Use a static variable for the connection -- we never want more than one
+        // connection open at a time, so if we have previously connected, we will
+        // remember the old connection and return that instead of starting over.
+        static $catalog = false;
+        if ($catalog === false) {
+            $catalog = new ILSConnection();
+        }
+
+        return $catalog;
+    }
+
+    /**
+     * Connect to the index.
+     *
+     * @param string $type Index type to connect to (null for standard Solr).
+     * @param string $core Index core to use (null for default).
+     * @param string $url  Connection URL for index (null for config.ini default).
+     *
+     * @return object
+     */
+    public static function connectToIndex($type = null, $core = null, $url = null)
+    {
+        $configArray = ConfigReader::getConfig();
+
+        // Load config.ini settings for missing parameters:
+        if ($type == null) {
+            $type = 'Solr';
+        }
+        if ($url == null) {
+            // Load appropriate default server URL based on index type:
+            $url = ($type == 'SolrStats' && isset($configArray->Statistics->solr))
+                ? $configArray->Statistics->solr : $configArray->Index->url;
+        }
+
+        // Set appropriate default core if necessary:
+        if (empty($core) && $type == 'Solr') {
+            $core = isset($configArray->Index->default_core)
+                ? $configArray->Index->default_core : "biblio";
+        }
+
+        // Construct the object appropriately based on the $core setting:
+        $class = 'VuFind\\Connection\\' . $type;
+        if (empty($core)) {
+            $index = new $class($url);
+        } else {
+            $index = new $class($url, $core);
+        }
+
+        return $index;
+    }
+}
diff --git a/module/VuFind/src/VuFind/Connection/Solr.php b/module/VuFind/src/VuFind/Connection/Solr.php
new file mode 100644
index 0000000000000000000000000000000000000000..e83860d0511a1fd1e5c9f3135214ffabe42f5daf
--- /dev/null
+++ b/module/VuFind/src/VuFind/Connection/Solr.php
@@ -0,0 +1,1713 @@
+<?php
+/**
+ * Solr HTTP Interface
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2007.
+ *
+ * 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   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
+ * @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#index_interface Wiki
+ */
+namespace VuFind\Connection;
+use VuFind\Config\Reader as ConfigReader,
+    VuFind\Exception\Solr as SolrException,
+    VuFind\Http\Client as HttpClient,
+    VuFind\Log\Logger,
+    VuFind\Solr\Utils as SolrUtils;
+
+/**
+ * Solr HTTP Interface
+ *
+ * @category VuFind2
+ * @package  Support_Classes
+ * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/system_classes#index_interface Wiki
+ */
+class Solr
+{
+    /**
+     * A boolean value determining whether to print debug information
+     * @var boolean
+     */
+    protected $logger;
+    
+    /**
+     * The host to connect to
+     * @var string
+     */
+    protected $host;
+
+    /**
+     * The core being used on the host
+     * @var string
+     */
+    protected $core;
+
+    /**
+     * The HTTP request object used for REST transactions
+     * @var object \VuFind\Http\Client
+     */
+    protected $client;
+
+    /**
+     * An array of characters that are illegal in search strings
+     */
+    protected $illegal = array('!', ':', ';', '[', ']', '{', '}');
+
+    /**
+     * The path to the YAML file specifying available search types:
+     */
+    protected $searchSpecsFile = 'searchspecs.yaml';
+
+    /**
+     * An array of search specs pulled from $searchSpecsFile (above)
+     */
+    protected $searchSpecs = false;
+
+    /**
+     * Should boolean operators in the search string be treated as
+     * case-insensitive (false), or must they be ALL UPPERCASE (true)?
+     */
+    protected $caseSensitiveBooleans = true;
+
+    /**
+     * Should range operators (i.e. [a TO b]) in the search string be treated as
+     * case-insensitive (false), or must they be ALL UPPERCASE (true)?  Note that
+     * making this setting case insensitive not only changes the word "TO" to
+     * uppercase but also inserts OR clauses to check for case insensitive matches
+     * against the edges of the range...  i.e. ([a TO b] OR [A TO B]).
+     */
+    protected $caseSensitiveRanges = true;
+
+    /**
+     * Selected shard settings.
+     */
+    protected $solrShards = array();
+    protected $solrShardsFieldsToStrip = array();
+
+    /**
+     * Defaults used to populate the $userSearchParams array when the user omits
+     * keys from their options array.
+     */
+    protected $searchDefaults = array(
+        'query' => '*:*', 'handler' => null, 'filter' => null, 'start' => 0,
+        'limit' => 20, 'facet' => null, 'spell' => '', 'dictionary' => null,
+        'sort' => null, 'fields' => '*,score', 'method' => 'POST',
+        'highlight' => false, 'group' => array()
+    );
+
+    /**
+     * User preferences for the current search (reinitialized every time search()
+     * is called).
+     */
+    protected $userSearchParams;
+
+    /**
+     * Solr parameters derived from $userSearchParams (reinitialized every time
+     * search() is called).
+     */
+    protected $solrSearchParams;
+
+    /**
+     * Constructor
+     *
+     * @param string $base The base URL for the local Solr Server
+     * @param string $core The core to use on the specified server
+     */
+    public function __construct($base, $core = '')
+    {
+        $this->core = $core;
+        $this->host = $base . (empty($this->core) ? '' : ('/' . $this->core));
+        $this->client = new HttpClient(
+            null, array('timeout' => $this->getHttpTimeout())
+        );
+
+        // Don't waste time generating debug messages if nobody is listening:
+        $this->logger = Logger::debugNeeded() ? Logger::getInstance() : false;
+    }
+
+    /**
+     * Is this object configured with case-sensitive boolean operators?
+     *
+     * @return boolean
+     */
+    public function hasCaseSensitiveBooleans()
+    {
+        return $this->caseSensitiveBooleans;
+    }
+
+    /**
+     * Is this object configured with case-sensitive range operators?
+     *
+     * @return boolean
+     */
+    public function hasCaseSensitiveRanges()
+    {
+        return $this->caseSensitiveRanges;
+    }
+
+    /**
+     * Get the search specifications loaded from the specified YAML file.
+     *
+     * @param string $handler The named search to provide information about (set
+     * to null to get all search specifications)
+     *
+     * @return mixed Search specifications array if available, false if an invalid
+     * search is specified.
+     */
+    protected function getSearchSpecs($handler = null)
+    {
+        // Only load specs once:
+        if ($this->searchSpecs === false) {
+            $this->searchSpecs
+                = ConfigReader::getSearchSpecs($this->searchSpecsFile);
+        }
+
+        // Special case -- null $handler means we want all search specs.
+        if (is_null($handler)) {
+            return $this->searchSpecs;
+        }
+
+        // Return specs on the named search if found (easiest, most common case).
+        if (isset($this->searchSpecs[$handler])) {
+            return $this->searchSpecs[$handler];
+        }
+
+        // Check for a case-insensitive match -- this provides backward
+        // compatibility with different cases used in early VuFind versions
+        // and allows greater tolerance of minor typos in config files.
+        foreach ($this->searchSpecs as $name => $specs) {
+            if (strcasecmp($name, $handler) == 0) {
+                return $specs;
+            }
+        }
+
+        // If we made it this far, no search specs exist -- return false.
+        return false;
+    }
+
+    /**
+     * Retrieves a document specified by the ID.
+     *
+     * @param string $id     The document to retrieve from Solr
+     * @param array  $extras Extra parameters to pass to Solr (optional)
+     *
+     * @throws SolrException
+     * @return string    The requested resource (or null if bad ID)
+     */
+    public function getRecord($id, $extras = array())
+    {
+        if ($this->logger) {
+            $this->logger->debug('Get Record: '.$id);
+        }
+
+        // Query String Parameters
+        $options = $extras + array('q' => 'id:"' . addcslashes($id, '"') . '"');
+        $result = $this->select('GET', $options);
+
+        return isset($result['response']['docs'][0]) ?
+            $result['response']['docs'][0] : null;
+    }
+
+    /**
+     * Get records similiar to one record
+     * Uses MoreLikeThis Request Handler
+     *
+     * Uses SOLR MLT Query Handler
+     *
+     * @param string $id     A Solr document ID.
+     * @param array  $extras Extra parameters to pass to Solr (optional)
+     *
+     * @throws SolrException
+     * @return array     An array of query results similar to the specified record
+     */
+    public function getMoreLikeThis($id, $extras = array())
+    {
+        // Query String Parameters
+        $options = $extras + array(
+            'q' => 'id:"' . addcslashes($id, '"') . '"',
+            'qt' => 'morelikethis'
+        );
+        $result = $this->select('GET', $options);
+
+        return $result;
+    }
+
+    /**
+     * Get spelling suggestions based on input phrase.
+     *
+     * @param string $phrase The input phrase
+     *
+     * @return array         An array of spelling suggestions
+     */
+    public function checkSpelling($phrase)
+    {
+        if ($this->logger) {
+            $this->logger->debug('Spell Check: '.$phrase);
+        }
+
+        // Query String Parameters
+        $options = array(
+            'q'          => $phrase,
+            'rows'       => 0,
+            'start'      => 1,
+            'indent'     => 'yes',
+            'spellcheck' => 'true'
+        );
+
+        $result = $this->select('GET', $options);
+        return $result;
+    }
+
+     /**
+      * Internal method to build query string from search parameters
+      *
+      * @param array  $structure The SearchSpecs-derived structure or substructure
+      * defining the search, derived from the yaml file
+      * @param array  $values    The various values in an array with keys
+      * 'onephrase', 'and', 'or' (and perhaps others)
+      * @param string $joiner    The operator used to combine generated clauses
+      *
+      * @throws SolrException
+      * @return string           A search string suitable for adding to a query URL
+      */
+    protected function applySearchSpecs($structure, $values, $joiner = "OR")
+    {
+        $clauses = array();
+        foreach ($structure as $field => $clausearray) {
+            if (is_numeric($field)) {
+                // shift off the join string and weight
+                $sw = array_shift($clausearray);
+                $internalJoin = ' ' . $sw[0] . ' ';
+                // Build it up recursively
+                $sstring = '(' .
+                    $this->applySearchSpecs($clausearray, $values, $internalJoin) .
+                    ')';
+                // ...and add a weight if we have one
+                $weight = $sw[1];
+                if (!is_null($weight) && $weight && $weight > 0) {
+                    $sstring .= '^' . $weight;
+                }
+                // push it onto the stack of clauses
+                $clauses[] = $sstring;
+            } else if (!$this->isStripped($field)) {
+                // Otherwise, we've got a (list of) [munge, weight] pairs to deal
+                // with
+                foreach ($clausearray as $spec) {
+                    // build a string like title:("one two")
+                    $sstring = $field . ':(' . $values[$spec[0]] . ')';
+                    // Add the weight if we have one. Yes, I know, it's redundant
+                    // code.
+                    $weight = $spec[1];
+                    if (!is_null($weight) && $weight && $weight > 0) {
+                        $sstring .= '^' . $weight;
+                    }
+                    // ..and push it on the stack of clauses
+                    $clauses[] = $sstring;
+                }
+            }
+        }
+
+        // Join it all together
+        return implode(' ' . $joiner . ' ', $clauses);
+    }
+
+    /**
+     * _getStrippedFields -- internal method to read the fields that should get
+     * stripped for the used shards from config file
+     *
+     * @return array An array containing any field that should be stripped from query
+     */
+    protected function getStrippedFields()
+    {
+        // Store stripped fields as a static variable so that we only need to
+        // process the configuration settings once:
+        static $strippedFields = false;
+        if ($strippedFields === false) {
+            $strippedFields = array();
+            foreach ($this->solrShards as $index => $address) {
+                if (array_key_exists($index, $this->solrShardsFieldsToStrip)) {
+                    $parts = explode(',', $this->solrShardsFieldsToStrip[$index]);
+                    foreach ($parts as $part) {
+                        $strippedFields[] = trim($part);
+                    }
+                }
+            }
+            $strippedFields = array_unique($strippedFields);
+        }
+
+        return $strippedFields;
+    }
+
+    /**
+     * _isStripped -- internal method to check if a field is stripped from query
+     *
+     * @param string $field The name of the field that should be checked for
+     * stripping
+     *
+     * @return bool         A boolean value indicating whether the field should be
+     * stripped (true) or not (false)
+     */
+    protected function isStripped($field)
+    {
+        // Never strip fields if shards are disabled.
+        // Return true if the current field needs to be stripped.
+        if (isset($this->solrShards)
+            && in_array($field, $this->getStrippedFields())
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Given a field name and search string, return an array containing munged
+     * versions of the search string for use in _applySearchSpecs().
+     *
+     * @param string $field   The YAML search spec field name to search
+     * @param string $lookfor The string to search for in the field
+     * @param array  $custom  Custom munge settings from YAML search specs
+     * @param bool   $basic   Is $lookfor a basic (true) or advanced (false) query?
+     *
+     * @return  array         Array for use as _applySearchSpecs() values param
+     */
+    protected function buildMungeValues($field, $lookfor, $custom = null,
+        $basic = true
+    ) {
+        // Only tokenize basic queries:
+        if ($basic) {
+            // Tokenize Input
+            $tokenized = $this->tokenizeInput($lookfor);
+
+            // Create AND'd and OR'd queries
+            $andQuery = implode(' AND ', $tokenized);
+            $orQuery = implode(' OR ', $tokenized);
+
+            // Build possible inputs for searching:
+            $values = array();
+            $values['onephrase']
+                = '"' . str_replace('"', '', implode(' ', $tokenized)) . '"';
+            $values['and'] = $andQuery;
+            $values['or'] = $orQuery;
+        } else {
+            // If we're skipping tokenization, we just want to pass $lookfor through
+            // unmodified (it's probably an advanced search that won't benefit from
+            // tokenization).  We'll just set all possible values to the same thing,
+            // except that we'll try to do the "one phrase" in quotes if possible.
+            // IMPORTANT: If we detect a boolean NOT, we MUST omit the quotes.
+            $onephrase = (strstr($lookfor, '"') || strstr($lookfor, ' NOT '))
+                ? $lookfor : '"' . $lookfor . '"';
+            $values = array(
+                'onephrase' => $onephrase, 'and' => $lookfor, 'or' => $lookfor
+            );
+        }
+
+        // Apply custom munge operations if necessary:
+        if (is_array($custom)) {
+            foreach ($custom as $mungeName => $mungeOps) {
+                $values[$mungeName] = $lookfor;
+
+                // Skip munging of advanced queries:
+                if ($basic) {
+                    foreach ($mungeOps as $operation) {
+                        switch($operation[0]) {
+                        case 'append':
+                            $values[$mungeName] .= $operation[1];
+                            break;
+                        case 'lowercase':
+                            $values[$mungeName] = strtolower($values[$mungeName]);
+                            break;
+                        case 'preg_replace':
+                            $values[$mungeName] = preg_replace(
+                                $operation[1], $operation[2], $values[$mungeName]
+                            );
+                            break;
+                        case 'uppercase':
+                            $values[$mungeName] = strtoupper($values[$mungeName]);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        return $values;
+    }
+
+    /**
+     * Given a field name and search string, expand this into the necessary Lucene
+     * query to perform the specified search on the specified field(s).
+     *
+     * @param string $field   The YAML search spec field name to search
+     * @param string $lookfor The string to search for in the field
+     * @param bool   $basic   Is $lookfor a basic (true) or advanced (false) query?
+     *
+     * @return string         The query
+     */
+    protected function buildQueryComponent($field, $lookfor, $basic = true)
+    {
+        // Load the YAML search specifications:
+        $ss = $this->getSearchSpecs($field);
+
+        // If we received a field spec that wasn't defined in the YAML file,
+        // let's try simply passing it along to Solr.
+        if ($ss === false) {
+            return $field . ':(' . $lookfor . ')';
+        }
+
+        // If this is a basic query and we have Dismax settings, let's build
+        // a Dismax subquery to avoid some of the ugly side effects of our Lucene
+        // query generation logic.
+        if ($basic && isset($ss['DismaxFields'])) {
+            $qf = implode(' ', $ss['DismaxFields']);
+            $dmParams = '';
+            if (isset($ss['DismaxParams']) && is_array($ss['DismaxParams'])) {
+                foreach ($ss['DismaxParams'] as $current) {
+                    $dmParams .= ' ' . $current[0] . "='" .
+                        addcslashes($current[1], "'") . "'";
+                }
+            }
+            $dismaxQuery = '{!dismax qf="' . $qf . '"' . $dmParams . '}' . $lookfor;
+            $baseQuery = '_query_:"' . addslashes($dismaxQuery) . '"';
+        } else {
+            // Munge the user query in a few different ways:
+            $customMunge = isset($ss['CustomMunge']) ? $ss['CustomMunge'] : null;
+            $values
+                = $this->buildMungeValues($field, $lookfor, $customMunge, $basic);
+
+            // Apply the $searchSpecs property to the data:
+            $baseQuery = $this->applySearchSpecs($ss['QueryFields'], $values);
+        }
+
+        // Apply filter query if applicable:
+        if (isset($ss['FilterQuery'])) {
+            return "({$baseQuery}) AND ({$ss['FilterQuery']})";
+        }
+
+        return "($baseQuery)";
+    }
+
+    /**
+     * Given a field name and search string known to contain advanced features
+     * (as identified by isAdvanced()), expand this into the necessary Lucene
+     * query to perform the specified search on the specified field(s).
+     *
+     * @param string $handler The YAML search spec field name to search
+     * @param string $query   The string to search for in the field
+     *
+     * @return  string        The query
+     */
+    protected function buildAdvancedQuery($handler, $query)
+    {
+        $query = $this->buildAdvancedInnerQuery($handler, $query);
+
+        // Apply boost query/boost function, if any:
+        $ss = $this->getSearchSpecs($handler);
+        $bq = array();
+        if (isset($ss['DismaxParams']) && is_array($ss['DismaxParams'])) {
+            foreach ($ss['DismaxParams'] as $current) {
+                if ($current[0] == 'bq') {
+                    $bq[] = $current[1];
+                } else if ($current[0] == 'bf') {
+                    // BF parameter may contain multiple space-separated functions
+                    // with individual boosts.  We need to parse this into _val_
+                    // query components:
+                    $bfParts = explode(' ', $current[1]);
+                    foreach ($bfParts as $bf) {
+                        $bf = trim($bf);
+                        if (!empty($bf)) {
+                            $bfSubParts = explode('^', $bf, 2);
+                            $boost = '"' . addcslashes($bfSubParts[0], '"') . '"';
+                            if (isset($bfSubParts[1])) {
+                                $boost .= '^' . $bfSubParts[1];
+                            }
+                            $bq[] = '_val_:' . $boost;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!empty($bq)) {
+            $query = '(' . $query . ') AND (*:* OR ' . implode(' OR ', $bq) . ')';
+        }
+
+        return $query;
+    }
+
+    /**
+     * Support method for buildAdvancedQuery -- build the inner portion of the
+     * query; the calling method may then wrap this with additional settings.
+     *
+     * @param string $handler The YAML search spec field name to search
+     * @param string $query   The string to search for in the field
+     *
+     * @return  string        The query
+     */
+    protected function buildAdvancedInnerQuery($handler, $query)
+    {
+        // Special case -- if the user wants all records but the current handler
+        // has a filter query, apply the filter query:
+        if (trim($query) == '*:*') {
+            $ss = $this->getSearchSpecs($handler);
+            if (isset($ss['FilterQuery'])) {
+                return $ss['FilterQuery'];
+            }
+        }
+
+        // Strip out any colons that are NOT part of a field specification:
+        $query = preg_replace('/(\:\s+|\s+:)/', ' ', $query);
+
+        // If the query already includes field specifications, we can't easily
+        // apply it to other fields through our defined handlers, so we'll leave
+        // it as-is:
+        if (strstr($query, ':')) {
+            return $query;
+        }
+
+        // Convert empty queries to return all values in a field:
+        if (empty($query)) {
+            $query = '[* TO *]';
+        }
+
+        // If the query ends in a question mark, the user may not really intend to
+        // use the question mark as a wildcard -- let's account for that possibility
+        if (substr($query, -1) == '?') {
+            $query = "({$query}) OR (" . substr($query, 0, strlen($query) - 1) . ")";
+        }
+
+        // We're now ready to use the regular YAML query handler but with the
+        // $basic parameter set to false so that we leave the advanced query
+        // features unmolested.
+        return $this->buildQueryComponent($handler, $query, false);
+    }
+
+    /**
+     * Build Query string from search parameters
+     *
+     * @param array $search An array of search parameters
+     *
+     * @return string       The query
+     */
+    public function buildQuery($search)
+    {
+        $groups   = array();
+        $excludes = array();
+        if (is_array($search)) {
+            $query = '';
+
+            foreach ($search as $params) {
+
+                // Advanced Search
+                if (isset($params['group'])) {
+                    $thisGroup = array();
+                    // Process each search group
+                    foreach ($params['group'] as $group) {
+                        // Build this group individually as a basic search
+                        $thisGroup[] = $this->buildQuery(array($group));
+                    }
+                    // Is this an exclusion (NOT) group or a normal group?
+                    if ($params['group'][0]['bool'] == 'NOT') {
+                        $excludes[] = join(" OR ", $thisGroup);
+                    } else {
+                        $groups[] = join(
+                            " " . $params['group'][0]['bool'] . " ", $thisGroup
+                        );
+                    }
+                }
+
+                // Basic Search
+                if (isset($params['lookfor']) && $params['lookfor'] != '') {
+                    // Clean and validate input
+                    $lookfor = $this->validateInput($params['lookfor']);
+
+                    // Force boolean operators to uppercase if we are in a
+                    // case-insensitive mode:
+                    if (!$this->caseSensitiveBooleans) {
+                        $lookfor = SolrUtils::capitalizeBooleans($lookfor);
+                    }
+                    // Adjust range operators if we are in a case-insensitive mode:
+                    if (!$this->caseSensitiveRanges) {
+                        $lookfor = SolrUtils::capitalizeRanges($lookfor);
+                    }
+
+                    if (isset($params['field']) && ($params['field'] != '')) {
+                        if ($this->isAdvanced($lookfor)) {
+                            $query .= $this->buildAdvancedQuery(
+                                $params['field'], $lookfor
+                            );
+                        } else {
+                            $query .= $this->buildQueryComponent(
+                                $params['field'], $lookfor
+                            );
+                        }
+                    } else {
+                        $query .= $lookfor;
+                    }
+                }
+            }
+        }
+
+        // Put our advanced search together
+        if (count($groups) > 0) {
+            $query = "(" . join(") " . $search[0]['join'] . " (", $groups) . ")";
+        }
+        // and concatenate exclusion after that
+        if (count($excludes) > 0) {
+            $query .= " NOT ((" . join(") OR (", $excludes) . "))";
+        }
+
+        // Ensure we have a valid query to this point
+        if (!isset($query) || $query  == '') {
+            $query = '*:*';
+        }
+
+        return $query;
+    }
+
+    /**
+     * Normalize a sort option.
+     *
+     * @param string $sort The sort option.
+     *
+     * @return string      The normalized sort value.
+     */
+    protected function normalizeSort($sort)
+    {
+        // Break apart sort into field name and sort direction (note error
+        // suppression to prevent notice when direction is left blank):
+        @list($sortField, $sortDirection) = explode(' ', $sort);
+
+        // Default sort order (may be overridden by switch below):
+        $defaultSortDirection = 'asc';
+
+        // Translate special sort values into appropriate Solr fields:
+        switch ($sortField) {
+        case 'year':
+        case 'publishDate':
+            $sortField = 'publishDateSort';
+            $defaultSortDirection = 'desc';
+            break;
+        case 'author':
+            $sortField = 'authorStr';
+            break;
+        case 'title':
+            $sortField = 'title_sort';
+            break;
+        }
+
+        // Normalize sort direction to either "asc" or "desc":
+        $sortDirection = strtolower(trim($sortDirection));
+        if ($sortDirection != 'desc' && $sortDirection != 'asc') {
+            $sortDirection = $defaultSortDirection;
+        }
+
+        return $sortField . ' ' . $sortDirection;
+    }
+
+    /**
+     * Support method for initSearchParams() -- set up sort preferences.
+     *
+     * @return void
+     */
+    protected function initSearchSort()
+    {
+        // Add Sorting
+        $sort = $this->userSearchParams['sort'];
+        if (!empty($sort)) {
+            // There may be multiple sort options (ranked, with tie-breakers);
+            // process each individually, then assemble them back together again:
+            $sortParts = explode(',', $sort);
+            for ($x = 0; $x < count($sortParts); $x++) {
+                $sortParts[$x] = $this->normalizeSort($sortParts[$x]);
+            }
+            $this->solrSearchParams['sort'] = implode(',', $sortParts);
+        }
+    }
+
+    /**
+     * Support method for initSearchParams() -- set up field collapsing.
+     *
+     * @return void
+     */
+    protected function initFieldCollapse()
+    {
+        // Add Sorting
+        $fields = $this->userSearchParams['group'];
+        if (!empty($fields)) {
+            // There may be multiple sort options (ranked, with tie-breakers);
+            // process each individually, then assemble them back together again:
+            $this->solrSearchParams['group'] = 'true';
+            $this->solrSearchParams['group.field'] = $fields;
+        }
+    }
+
+    /**
+     * Add a value to the set of parameters to be sent to Solr.  This method
+     * handles multiple values properly -- if you set the same parameter repeatedly,
+     * ALL specified values will be retained.
+     *
+     * @param string $key   Name of parameter to set
+     * @param string $value Value to set
+     *
+     * @return void
+     */
+    protected function addSolrSearchParam($key, $value)
+    {
+        if (isset($this->solrSearchParams[$key])) {
+            if (!is_array($this->solrSearchParams[$key])) {
+                $this->solrSearchParams[$key]
+                    = array($this->solrSearchParams[$key]);
+            }
+            $this->solrSearchParams[$key][] = $value;
+        } else {
+            $this->solrSearchParams[$key] = $value;
+        }
+    }
+
+    /**
+     * Support method for initSearchParams() -- set up query, handler and filters.
+     *
+     * @return void
+     */
+    protected function initSearchQuery()
+    {
+        // Grab relevant user parameters:
+        $query = $this->userSearchParams['query'];
+        $filter = $this->userSearchParams['filter'];
+        $handler = $this->userSearchParams['handler'];
+
+        // Determine which handler to use
+        if (!$this->isAdvanced($query)) {
+            $ss = is_null($handler) ? null : $this->getSearchSpecs($handler);
+            // Is this a Dismax search?
+            if (isset($ss['DismaxFields'])) {
+                // Specify the fields to do a Dismax search on and use the default
+                // Dismax search handler so we can use appropriate user-specified
+                // solrconfig.xml settings:
+                $this->solrSearchParams['qf'] = implode(' ', $ss['DismaxFields']);
+                $this->solrSearchParams['qt'] = 'dismax';
+
+                // Load any custom Dismax parameters from the YAML search spec file:
+                if (isset($ss['DismaxParams']) && is_array($ss['DismaxParams'])) {
+                    foreach ($ss['DismaxParams'] as $current) {
+                        $this->addSolrSearchParam($current[0], $current[1]);
+                    }
+                }
+
+                // Apply search-specific filters if necessary:
+                if (isset($ss['FilterQuery'])) {
+                    if (is_array($filter)) {
+                        $filter[] = $ss['FilterQuery'];
+                    } else {
+                        $filter = array($ss['FilterQuery']);
+                    }
+                }
+            } else {
+                // Not DisMax... but if we have a handler set, we may still need
+                // to build a query using a setting in the YAML search specs or a
+                // simple field name:
+                if (!empty($handler)) {
+                    $query = $this->buildQueryComponent($handler, $query);
+                }
+            }
+        } else {
+            // Force boolean operators and ranges to uppercase if we are in a
+            // case-insensitive mode:
+            if (!$this->caseSensitiveBooleans) {
+                $query = SolrUtils::capitalizeBooleans($query);
+            }
+            if (!$this->caseSensitiveRanges) {
+                $query = SolrUtils::capitalizeRanges($query);
+            }
+
+            // Process advanced search -- if a handler was specified, let's see
+            // if we can adapt the search to work with the appropriate fields.
+            if (!empty($handler)) {
+                // If highlighting is enabled, we only want to use the inner query
+                // for highlighting; anything added outside of this is a boost and
+                // should be ignored for highlighting purposes!
+                if ($this->userSearchParams['highlight']) {
+                    $this->solrSearchParams['hl.q']
+                        = $this->buildAdvancedInnerQuery($handler, $query);
+                }
+                $query = $this->buildAdvancedQuery($handler, $query);
+            }
+        }
+
+        // Now that query and filters are fully processed, add them to the params:
+        $this->solrSearchParams['q'] = $query;
+        if (is_array($filter) && count($filter)) {
+            $this->solrSearchParams['fq'] = $filter;
+        }
+    }
+
+    /**
+     * Support method for initSearchParams() -- set up facets.
+     *
+     * @return void
+     */
+    protected function initSearchFacets()
+    {
+        // Build Facet Options
+        $facet = $this->userSearchParams['facet'];
+        if (isset($facet['field']) && !empty($facet['field'])) {
+            // Always use these values:
+            $this->solrSearchParams['facet'] = 'true';
+            $this->solrSearchParams['facet.mincount'] = 1;
+
+            // Process convenience parameters (short VuFind-specific names):
+            $this->solrSearchParams['facet.limit']
+                = (isset($facet['limit'])) ? $facet['limit'] : null;
+            unset($facet['limit']);
+            $this->solrSearchParams['facet.field']
+                = (isset($facet['field'])) ? $facet['field'] : null;
+            unset($facet['field']);
+            $this->solrSearchParams['facet.prefix']
+                = (isset($facet['prefix'])) ? $facet['prefix'] : null;
+            unset($facet['prefix']);
+            $this->solrSearchParams['facet.sort']
+                = (isset($facet['sort'])) ? $facet['sort'] : null;
+            unset($facet['sort']);
+            if (isset($facet['offset'])) {
+                $this->solrSearchParams['facet.offset'] = $facet['offset'];
+                unset($facet['offset']);
+            }
+
+            // Anything left at this point must be a native Solr parameter;
+            // pass it through unmodified:
+            foreach ($facet as $param => $value) {
+                $this->solrSearchParams[$param] = $value;
+            }
+        }
+    }
+
+    /**
+     * Support method for initSearchParams() -- set up spellcheck settings.
+     *
+     * @return void
+     */
+    protected function initSearchSpellCheck()
+    {
+        // Enable Spell Checking
+        if (!empty($this->userSearchParams['spell'])) {
+            $this->solrSearchParams['spellcheck'] = 'true';
+            $this->solrSearchParams['spellcheck.q']
+                = $this->userSearchParams['spell'];
+            if (!empty($this->userSearchParams['dictionary'])) {
+                $this->solrSearchParams['spellcheck.dictionary']
+                    = $this->userSearchParams['dictionary'];
+            }
+        }
+    }
+
+    /**
+     * Support method for initSearchParams() -- set up highlighting.
+     *
+     * @return void
+     */
+    protected function initSearchHighlight()
+    {
+        // Enable highlighting
+        if ($this->userSearchParams['highlight']) {
+            $this->solrSearchParams['hl'] = 'true';
+            $this->solrSearchParams['hl.fl'] = '*';
+            $this->solrSearchParams['hl.simple.pre'] = '{{{{START_HILITE}}}}';
+            $this->solrSearchParams['hl.simple.post'] = '{{{{END_HILITE}}}}';
+        }
+    }
+
+    /**
+     * Initialize the _userSearchParams and _solrSearchParams arrays by combining
+     * defaults with user-provided values.
+     *
+     * @param array $options Options from search() method.
+     *
+     * @return void
+     */
+    protected function initSearchParams($options)
+    {
+        // Combine user settings with defaults:
+        $this->userSearchParams = array();
+        foreach ($this->searchDefaults as $key => $default) {
+            $this->userSearchParams[$key] = isset($options[$key])
+                ? $options[$key] : $default;
+        }
+
+        // Prepare simple Solr parameters:
+        $this->solrSearchParams = array(
+            'rows' => $this->userSearchParams['limit'],
+            'start' => $this->userSearchParams['start'],
+            'fl' => $this->userSearchParams['fields']
+        );
+
+        // Update _solrSearchParams with various more complex settings:
+        $this->initSearchSort();
+        $this->initSearchQuery();
+        $this->initSearchFacets();
+        $this->initFieldCollapse();
+        $this->initSearchSpellCheck();
+        $this->initSearchHighlight();
+    }
+
+    /**
+     * Execute a search.
+     *
+     * @param array $options Array of search options with any number of the following
+     * keys:
+     *  <ul>
+     *    <li>query - The search query</li>
+     *    <li>handler - The Query Handler to use (null for default)</li>
+     *    <li>filter - The fields and values to filter results on</li>
+     *    <li>start - The record to start with</li>
+     *    <li>limit - The amount of records to return</li>
+     *    <li>facet - An associative array of faceting options with some or all of
+     *        these keys:
+     *      <ul>
+     *        <li>field - array of fields to show facet data for (REQUIRED)</li>
+     *        <li>limit - number of values to show for each facet</li>
+     *        <li>prefix - filter (only show facet values matching this prefix)</li>
+     *        <li>sort - either 'count' or 'lex'</li>
+     *        <li>offset - Offset into facet list (used for paging)</li>
+     *        <li>facet.* - Native Solr facet parameters may also be used here</li>
+     *      </ul>
+     *    </li>
+     *    <li>spell - Phrase to spell check</li>
+     *    <li>dictionary - Spell check dictionary to use</li>
+     *    <li>sort - Field name to use for sorting</li>
+     *    <li>fields - A list of fields to be returned</li>
+     *    <li>method - Method to use for sending request (GET/POST)</li>
+     *    <li>highlight - Boolean indicating whether or not to highlight results</li>
+     *    <li>group - Array of fields to use for field collapsing (optional)</li>
+     *  </ul>
+     *
+     * @throws SolrException
+     * @return array                  An array of query results
+     */
+    public function search($options = array())
+    {
+        $this->initSearchParams($options);
+
+        // debug
+        if ($this->logger) {
+            $debugMsg = 'Search options: ' . print_r($this->solrSearchParams, true);
+            $this->logger->debug($debugMsg);
+        }
+
+        return $this->select(
+            $this->userSearchParams['method'], $this->solrSearchParams
+        );
+    }
+
+    /**
+     * Convert an array of fields into XML for saving to Solr.
+     *
+     * @param array $fields Array of fields to save
+     *
+     * @return string       XML document ready for posting to Solr.
+     */
+    public function getSaveXML($fields)
+    {
+        // Create XML Document
+        $doc = new DOMDocument('1.0', 'UTF-8');
+
+        // Create add node
+        $node = $doc->createElement('add');
+        $addNode = $doc->appendChild($node);
+
+        // Create doc node
+        $node = $doc->createElement('doc');
+        $docNode = $addNode->appendChild($node);
+
+        // Add fields to XML docuemnt
+        foreach ($fields as $field => $value) {
+            // Normalize current value to an array for convenience:
+            if (!is_array($value)) {
+                $value = array($value);
+            }
+            // Add all non-empty values of the current field to the XML:
+            foreach ($value as $current) {
+                if ($current != '') {
+                    $node = $doc->createElement(
+                        'field', htmlspecialchars($current, ENT_COMPAT, 'UTF-8')
+                    );
+                    $node->setAttribute('name', $field);
+                    $docNode->appendChild($node);
+                }
+            }
+        }
+
+        return $doc->saveXML();
+    }
+
+    /**
+     * Save Record to Database
+     *
+     * @param string $xml XML document to post to Solr
+     *
+     * @throws SolrException
+     * @return bool
+     */
+    public function saveRecord($xml)
+    {
+        if ($this->logger) {
+            $this->logger->debug('Add Record');
+        }
+
+        return $this->update($xml);
+    }
+
+    /**
+     * Delete all records in the index.
+     *
+     * @return boolean
+     */
+    public function deleteAll()
+    {
+        if ($this->logger) {
+            $this->logger->debug('Delete ALL records from index');
+        }
+
+        // Build the delete XML
+        $body = '<delete><query>*:*</query></delete>';
+
+        // Attempt to post the XML:
+        return $this->update($body);
+    }
+
+    /**
+     * Delete Record from Database
+     *
+     * @param string $id ID for record to delete
+     *
+     * @return boolean
+     */
+    public function deleteRecord($id)
+    {
+        // Treat single-record deletion as a special case of multi-record deletion:
+        return $this->deleteRecords(array($id));
+    }
+
+    /**
+     * Delete Record from Database
+     *
+     * @param string $idList Array of IDs for record to delete
+     *
+     * @throws SolrException
+     * @return boolean
+     */
+    public function deleteRecords($idList)
+    {
+        if ($this->logger) {
+            $this->logger->debug('Delete Record List');
+        }
+
+        // Build the delete XML
+        $body = '<delete>';
+        foreach ($idList as $id) {
+            $body .= '<id>' . htmlspecialchars($id) . '</id>';
+        }
+        $body .= '</delete>';
+
+        // Attempt to post the XML:
+        $result = $this->update($body);
+
+        // Record the deletions in our change tracker database:
+        foreach ($idList as $id) {
+            $tracker = new VuFind_Model_Db_ChangeTracker();
+            $tracker->markDeleted($this->core, $id);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Commit
+     *
+     * @throws SolrException
+     * @return string
+     */
+    public function commit()
+    {
+        if ($this->logger) {
+            $this->logger->debug('Commit');
+        }
+
+        return $this->update('<commit/>', array('timeout' => 600000));
+    }
+
+    /**
+     * Optimize
+     *
+     * @throws SolrException
+     * @return string
+     */
+    public function optimize()
+    {
+        if ($this->logger) {
+            $this->logger->debug('Optimize');
+        }
+
+        return $this->update('<optimize/>', array('timeout' => 600000));
+    }
+
+    /**
+     * Set the shards for distributed search
+     *
+     * @param array $shards      Array of shards in associative Name => URL format
+     * @param array $stripFields Shard name => comma-separated list of fields to
+     * strip from that shard (optional)
+     *
+     * @return void
+     */
+    public function setShards($shards, $stripFields = array())
+    {
+        // if only one shard is used, take its URL as SOLR-Host-URL
+        if (count($shards) === 1) {
+            $shardsKeys = array_keys($shards);
+            $this->host = 'http://'.$shards[$shardsKeys[0]];
+        }
+        // always set the shards -- even if only one is selected, we may
+        // need to filter fields and facets:
+        $this->solrShards = $shards;
+        $this->solrShardsFieldsToStrip = $stripFields;
+    }
+
+    /**
+     * Strip facet settings that are illegal due to shard settings.
+     *
+     * @param array $value Current facet.field setting
+     *
+     * @return array       Filtered facet.field setting
+     */
+    protected function stripUnwantedFacets($value)
+    {
+        // Check the list of fields to strip and build an array of values that
+        // may apply to facets.
+        $badFacets = array();
+        if (!empty($this->solrShards) && !empty($this->solrShardsFieldsToStrip)) {
+            $shardNames = array_keys($this->solrShards);
+            foreach ($this->solrShardsFieldsToStrip as $indexName => $facets) {
+                if (in_array($indexName, $shardNames) === true) {
+                    $badFacets = array_merge($badFacets, explode(",", $facets));
+                }
+            }
+        }
+
+        // No bad facets means no filtering necessary:
+        if (empty($badFacets)) {
+            return $value;
+        }
+
+        // Ensure that $value is an array:
+        if (!is_array($value)) {
+            $value = array($value);
+        }
+
+        // Rebuild the $value array, excluding all unwanted facets:
+        $newValue = array();
+        foreach ($value as $current) {
+            if (!in_array($current, $badFacets)) {
+                $newValue[] = $current;
+            }
+        }
+
+        return $newValue;
+    }
+
+    /**
+     * Process the body of a Solr error response.
+     *
+     * @param string $detail The body of the HTTP error response.
+     *
+     * @throws SolrException
+     * @return void
+     */
+    protected function throwSolrError($detail)
+    {
+        // Attempt to extract the most useful error message from the response:
+        if (preg_match("/<title>(.*)<\/title>/msi", $detail, $matches)) {
+            $errorMsg = $matches[1];
+        } else {
+            $errorMsg = $detail;
+        }
+        throw new SolrException("Unexpected response -- " . $errorMsg);
+    }
+
+    /**
+     * Submit REST Request to read data
+     *
+     * @param string $method HTTP Method to use: GET, POST,
+     * @param array  $params Array of parameters for the request
+     *
+     * @throws SolrException
+     * @return array         The Solr response
+     */
+    protected function select($method = 'GET', $params = array())
+    {
+        $this->client->resetParameters();
+        $uri = $this->host . "/select/";
+
+        $params['wt'] = 'json';
+        $params['json.nl'] = 'arrarr';
+
+        // Build query string for use with GET or POST:
+        $query = array();
+        if ($params) {
+            foreach ($params as $function => $value) {
+                if ($function != '') {
+                    // Strip custom FacetFields when sharding makes it necessary:
+                    if ($function === 'facet.field') {
+                        $value = $this->stripUnwantedFacets($value);
+
+                        // If we stripped all values, skip the parameter:
+                        if (empty($value)) {
+                            continue;
+                        }
+                    }
+                    if (is_array($value)) {
+                        foreach ($value as $additional) {
+                            $additional = urlencode($additional);
+                            $query[] = "$function=$additional";
+                        }
+                    } else {
+                        $value = urlencode($value);
+                        $query[] = "$function=$value";
+                    }
+                }
+            }
+        }
+
+        // pass the shard parameter along to Solr if necessary:
+        if (is_array($this->solrShards) && count($this->solrShards) > 1) {
+            $query[] = 'shards=' . urlencode(implode(',', $this->solrShards));
+        }
+        $queryString = implode('&', $query);
+
+        // debug
+        if ($this->logger) {
+            $this->logger->debug(
+                $method . ' '
+                . print_r($this->host . "/select/?" . $queryString, true)
+            );
+        }
+
+        if ($method == 'GET') {
+            $uri .= '?' . $queryString;
+        } elseif ($method == 'POST') {
+            $this->client->setRawBody(
+                $queryString, 'application/x-www-form-urlencoded'
+            );
+        }
+
+        // Send Request
+        $this->client->setUri($uri);
+        $result = $this->client->setMethod($method)->send();
+        if (!$result->isSuccess()) {
+            $this->throwSolrError($result->getBody());
+        }
+        return $this->process($result->getBody());
+    }
+
+    /**
+     * Submit REST Request to write data
+     *
+     * @param string $xml     The command to execute
+     * @param array  $options Extra options to pass to the HTTP client
+     *
+     * @throws SolrException
+     * @return bool
+     */
+    protected function update($xml, $options = array())
+    {
+        $this->client->resetParameters();
+        $this->client->setConfig($options);
+        $this->client->setUri($this->host . "/update/");
+
+        // debug
+        if ($this->logger) {
+            $this->logger->debug(
+                'POST: ' . print_r($this->host . "/update/", true)
+                . 'XML' . print_r($xml, true)
+            );
+        }
+
+        // Set up XML
+        $this->client->setRawBody($xml, 'text/xml; charset=utf-8');
+
+        // Send Request
+        $result = $this->client->setMethod('POST')->send();
+
+        if (!$result->isSuccess()) {
+            $this->throwSolrError($result->getBody());
+        }
+
+        return true;
+    }
+
+    /**
+     * Perform normalization and analysis of Solr return value.
+     *
+     * @param array $result The raw response from Solr
+     *
+     * @throws SolrException
+     * @return array        The processed response from Solr
+     */
+    protected function process($result)
+    {
+        // Catch errors from SOLR
+        if (substr(trim($result), 0, 2) == '<h') {
+            $errorMsg = substr($result, strpos($result, '<pre>'));
+            $errorMsg = substr(
+                $errorMsg, strlen('<pre>'), strpos($result, "</pre>")
+            );
+            $msg = 'Unable to process query<br />Solr Returned: ' . $errorMsg;
+            throw new SolrException($msg);
+        }
+        $result = json_decode($result, true);
+
+        // Inject highlighting details into results if necessary:
+        if (isset($result['highlighting'])) {
+            foreach ($result['response']['docs'] as $key => $current) {
+                if (isset($result['highlighting'][$current['id']])) {
+                    $result['response']['docs'][$key]['_highlighting']
+                        = $result['highlighting'][$current['id']];
+                }
+            }
+            // Remove highlighting section now that we have copied its contents:
+            unset($result['highlighting']);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Input Tokenizer
+     *
+     * Tokenizes the user input based on spaces and quotes.  Then joins phrases
+     * together that have an AND, OR, NOT present.
+     *
+     * @param string $input User's input string
+     *
+     * @return array        Tokenized array
+     */
+    public function tokenizeInput($input)
+    {
+        // Tokenize on spaces and quotes
+        //preg_match_all('/"[^"]*"|[^ ]+/', $input, $words);
+        preg_match_all('/"[^"]*"[~[0-9]+]*|"[^"]*"|[^ ]+/', $input, $words);
+        $words = $words[0];
+
+        // Join words with AND, OR, NOT
+        $newWords = array();
+        for ($i=0; $i<count($words); $i++) {
+            if (($words[$i] == 'OR') || ($words[$i] == 'AND')
+                || ($words[$i] == 'NOT')
+            ) {
+                if (count($newWords)) {
+                    $newWords[count($newWords)-1] .= ' ' . $words[$i] . ' ' .
+                        $words[$i+1];
+                    $i = $i+1;
+                }
+            } else {
+                $newWords[] = $words[$i];
+            }
+        }
+
+        return $newWords;
+    }
+
+    /**
+     * Input Validater
+     *
+     * Cleans the input based on the Lucene Syntax rules.
+     *
+     * @param string $input User's input string
+     *
+     * @return bool         Fixed input
+     */
+    public function validateInput($input)
+    {
+        // Normalize fancy quotes:
+        $quotes = array(
+            "\xC2\xAB"     => '"', // « (U+00AB) in UTF-8
+            "\xC2\xBB"     => '"', // » (U+00BB) in UTF-8
+            "\xE2\x80\x98" => "'", // ‘ (U+2018) in UTF-8
+            "\xE2\x80\x99" => "'", // ’ (U+2019) in UTF-8
+            "\xE2\x80\x9A" => "'", // ‚ (U+201A) in UTF-8
+            "\xE2\x80\x9B" => "'", // ? (U+201B) in UTF-8
+            "\xE2\x80\x9C" => '"', // “ (U+201C) in UTF-8
+            "\xE2\x80\x9D" => '"', // ” (U+201D) in UTF-8
+            "\xE2\x80\x9E" => '"', // „ (U+201E) in UTF-8
+            "\xE2\x80\x9F" => '"', // ? (U+201F) in UTF-8
+            "\xE2\x80\xB9" => "'", // ‹ (U+2039) in UTF-8
+            "\xE2\x80\xBA" => "'", // › (U+203A) in UTF-8
+        );
+        $input = strtr($input, $quotes);
+
+        // If the user has entered a lone BOOLEAN operator, convert it to lowercase
+        // so it is treated as a word (otherwise it will trigger a fatal error):
+        switch(trim($input)) {
+        case 'OR':
+            return 'or';
+        case 'AND':
+            return 'and';
+        case 'NOT':
+            return 'not';
+        }
+
+        // If the string consists only of control characters and/or BOOLEANs with no
+        // other input, wipe it out entirely to prevent weird errors:
+        $operators = array('AND', 'OR', 'NOT', '+', '-', '"', '&', '|');
+        if (trim(str_replace($operators, '', $input)) == '') {
+            return '';
+        }
+
+        // Translate "all records" search into a blank string
+        if (trim($input) == '*:*') {
+            return '';
+        }
+
+        // Ensure wildcards are not at beginning of input
+        if ((substr($input, 0, 1) == '*') || (substr($input, 0, 1) == '?')) {
+            $input = substr($input, 1);
+        }
+
+        // Ensure all parens match
+        $start = preg_match_all('/\(/', $input, $tmp);
+        $end = preg_match_all('/\)/', $input, $tmp);
+        if ($start != $end) {
+            $input = str_replace(array('(', ')'), '', $input);
+        }
+
+        // Ensure ^ is used properly
+        $cnt = preg_match_all('/\^/', $input, $tmp);
+        $matches = preg_match_all('/.+\^[0-9]/', $input, $tmp);
+        if (($cnt) && ($cnt !== $matches)) {
+            $input = str_replace('^', '', $input);
+        }
+
+        // Remove unwanted brackets/braces that are not part of range queries.
+        // This is a bit of a shell game -- first we replace valid brackets and
+        // braces with tokens that cannot possibly already be in the query (due
+        // to ^ normalization in the step above).  Next, we remove all remaining
+        // invalid brackets/braces, and transform our tokens back into valid ones.
+        // Obviously, the order of the patterns/merges array is critically
+        // important to get this right!!
+        $patterns = array(
+            // STEP 1 -- escape valid brackets/braces
+            '/\[([^\[\]\s]+\s+TO\s+[^\[\]\s]+)\]/' .
+            ($this->caseSensitiveRanges ? '' : 'i'),
+            '/\{([^\{\}\s]+\s+TO\s+[^\{\}\s]+)\}/' .
+            ($this->caseSensitiveRanges ? '' : 'i'),
+            // STEP 2 -- destroy remaining brackets/braces
+            '/[\[\]\{\}]/',
+            // STEP 3 -- unescape valid brackets/braces
+            '/\^\^lbrack\^\^/', '/\^\^rbrack\^\^/',
+            '/\^\^lbrace\^\^/', '/\^\^rbrace\^\^/');
+        $matches = array(
+            // STEP 1 -- escape valid brackets/braces
+            '^^lbrack^^$1^^rbrack^^', '^^lbrace^^$1^^rbrace^^',
+            // STEP 2 -- destroy remaining brackets/braces
+            '',
+            // STEP 3 -- unescape valid brackets/braces
+            '[', ']', '{', '}');
+        $input = preg_replace($patterns, $matches, $input);
+        return $input;
+    }
+
+    /**
+     * Does the provided query use advanced Lucene syntax features?
+     *
+     * @param string $query Query to test.
+     *
+     * @return bool
+     */
+    public function isAdvanced($query)
+    {
+        // Check for various conditions that flag an advanced Lucene query:
+        if ($query == '*:*') {
+            return true;
+        }
+
+        // The following conditions do not apply to text inside quoted strings,
+        // so let's just strip all quoted strings out of the query to simplify
+        // detection.  We'll replace quoted phrases with a dummy keyword so quote
+        // removal doesn't interfere with the field specifier check below.
+        $query = preg_replace('/"[^"]*"/', 'quoted', $query);
+
+        // Check for field specifiers:
+        if (preg_match("/[^\s]\:[^\s]/", $query)) {
+            return true;
+        }
+
+        // Check for parentheses and range operators:
+        if (strstr($query, '(') && strstr($query, ')')) {
+            return true;
+        }
+        $rangeReg = '/(\[.+\s+TO\s+.+\])|(\{.+\s+TO\s+.+\})/';
+        if (!$this->caseSensitiveRanges) {
+            $rangeReg .= "i";
+        }
+        if (preg_match($rangeReg, $query)) {
+            return true;
+        }
+
+        // Build a regular expression to detect booleans -- AND/OR/NOT surrounded
+        // by whitespace, or NOT leading the query and followed by whitespace.
+        $boolReg = '/((\s+(AND|OR|NOT)\s+)|^NOT\s+)/';
+        if (!$this->caseSensitiveBooleans) {
+            $boolReg .= "i";
+        }
+        if (preg_match($boolReg, $query)) {
+            return true;
+        }
+
+        // Check for wildcards and fuzzy matches:
+        if (strstr($query, '*') || strstr($query, '?') || strstr($query, '~')) {
+            return true;
+        }
+
+        // Check for boosts:
+        if (preg_match('/[\^][0-9]+/', $query)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Remove illegal characters from the provided query.
+     *
+     * @param string $query Query to clean.
+     *
+     * @return string       Clean query.
+     */
+    public function cleanInput($query)
+    {
+        $query = trim(str_replace($this->illegal, '', $query));
+        $query = strtolower($query);
+
+        return $query;
+    }
+
+    /**
+     * Obtain information from an alphabetic browse index.
+     *
+     * @param string $source    Name of index to search
+     * @param string $from      Starting point for browse results
+     * @param int    $page      Result page to return (starts at 0)
+     * @param int    $page_size Number of results to return on each page
+     * @param string $method    Method to use for connecting to Solr (GET or
+     * POST)
+     *
+     * @return array
+     */
+    public function alphabeticBrowse($source, $from, $page, $page_size = 20,
+        $method = 'GET'
+    ) {
+        $this->client->resetParameters();
+        $uri = $this->host . "/browse";
+
+        $query = array(
+          'from='.urlencode($from),
+          'json.nl=arrarr' ,
+          'offset='.urlencode($page*$page_size),
+          'rows='.urlencode($page_size),
+          'source='.urlencode($source),
+          'wt=json'
+        );
+        
+        $queryString = implode('&', $query);
+
+        if ($method == 'GET') {
+            $uri .= '?' . $queryString;
+        } elseif ($method == 'POST') {
+            $this->client->setRawBody(
+                $queryString, 'application/x-www-form-urlencoded'
+            );
+        }
+
+        // Send Request
+        $this->client->setUri($uri);
+        $result = $this->client->setMethod($method)->send();
+        if (!$result->isSuccess()) {
+            $this->throwSolrError($result->getBody());
+        }
+        return $this->process($result->getBody());
+    }
+
+
+
+    /**
+     * Extract terms from the Solr index.
+     *
+     * @param string $field Field to extract terms from
+     * @param string $start Starting term to extract (blank for beginning of list)
+     * @param int    $limit Maximum number of terms to return (-1 for no limit)
+     *
+     * @return array Associative array parsed from Solr JSON
+     * response; meat of the response is in the ['terms'] element, which contains
+     * an index named for the requested term, which in turn contains an associative
+     * array of term => count in index.
+     * @access public
+     */
+    public function getTerms($field, $start, $limit)
+    {
+        $this->client->setUri($this->host . '/term');
+
+        $this->client->setParameterGet('terms', 'true');
+        $this->client->setParameterGet('terms.fl', $field);
+        $this->client->setParameterGet('terms.lower.incl', 'false');
+        $this->client->setParameterGet('terms.lower', $start);
+        $this->client->setParameterGet('terms.limit', $limit);
+        $this->client->setParameterGet('terms.sort', 'index');
+        $this->client->setParameterGet('wt', 'json');
+
+        $result = $this->client->setMethod('GET')->send();
+        $result = substr($result, strpos($result, '{'));
+        try {
+            // Process the JSON response:
+            $data = $this->process($result);
+
+            // Tidy the data into a more usable format:
+            $info = array();
+            for ($i=0;$i<count($data['terms']['id']);$i+=2) {
+                $info[$data['terms']['id'][$i]] = $data['terms']['id'][$i+1];
+            }
+            return $info;
+        } catch(Exception $e) {
+            return $result;
+        }
+    }
+
+    /**
+     * Get the HTTP timeout for communicating with the Solr server.
+     *
+     * @return int
+     */
+    public function getHttpTimeout()
+    {
+        $config = ConfigReader::getConfig();
+        return isset($config->Index->timeout) ? $config->Index->timeout : 30;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Controller/SearchController.php b/module/VuFind/src/VuFind/Controller/SearchController.php
index 701997263f6a1c44ac4f18584ae945ec87b78bdf..b10f0460ddadaaf0e1b44112b26a80ad43623e95 100644
--- a/module/VuFind/src/VuFind/Controller/SearchController.php
+++ b/module/VuFind/src/VuFind/Controller/SearchController.php
@@ -27,7 +27,8 @@
  */
 namespace VuFind\Controller;
 
-use Zend\Mvc\Controller\ActionController;
+use VuFind\Cache\Manager as CacheManager, VuFind\Search\Solr\Params,
+    VuFind\Search\Solr\Results, Zend\Mvc\Controller\ActionController;
 
 /**
  * Redirects the user to the appropriate default VuFind action.
@@ -47,8 +48,37 @@ class SearchController extends ActionController
      */
     public function homeAction()
     {
-        /* TODO:
-        $this->view->results = $this->getAdvancedFacets();
-         */
+        return array('results' => $this->getAdvancedFacets());
+    }
+
+    /**
+     * Return a Search Results object containing advanced facet information.  This
+     * data may come from the cache, and it is currently shared between the Home
+     * page and the Advanced search screen.
+     *
+     * @return VF_Search_Solr_Results
+     */
+    protected function getAdvancedFacets()
+    {
+        // Check if we have facet results cached, and build them if we don't.
+        $cache = CacheManager::getInstance()->getCache('object');
+        if (!($results = $cache->getItem('solrSearchHomeFacets'))) {
+            // Use advanced facet settings to get summary facets on the front page;
+            // we may want to make this more flexible later.  Also keep in mind that
+            // the template is currently looking for certain hard-coded fields; this
+            // should also be made smarter.
+            $params = new Params();
+            $params->initAdvancedFacets();
+
+            // We only care about facet lists, so don't get any results (this helps
+            // prevent problems with serialized File_MARC objects in the cache):
+            $params->setLimit(0);
+
+            $results = new Results($params);
+            /* TODO: fix caching:
+            $cache->setItem($results, 'solrSearchHomeFacets');
+             */
+        }
+        return $results;
     }
 }
diff --git a/module/VuFind/src/VuFind/Http/Client.php b/module/VuFind/src/VuFind/Http/Client.php
new file mode 100644
index 0000000000000000000000000000000000000000..21d53d18703ab8c491a8ec154981892452f70b83
--- /dev/null
+++ b/module/VuFind/src/VuFind/Http/Client.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Proxy Server Support for VuFind
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2009.
+ *
+ * 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
+ */
+namespace VuFind\Http;
+use VuFind\Config\Reader as ConfigReader, Zend\Http\Client as BaseClient;
+
+/**
+ * Proxy_Request Class
+ *
+ * This is a wrapper class around the Zend HTTP client which automatically
+ * initializes proxy server support when requested by the configuration file.
+ *
+ * @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 Client extends BaseClient
+{
+    protected $checkProxy = false;
+
+    /**
+     * Constructor
+     *
+     * @param string            $uri     Target URI
+     * @param array|Traversable $options options array (passed to \Zend\Http\Client)
+     */
+    public function __construct($uri = null, $options = array())
+    {
+        // If an adapter was not explicitly passed in, set a flag indicating that
+        // we need to recheck the proxy settings whenever the request URI is changed
+        // (since we need different behavior for localhost vs. proxy)
+        if (!isset($options['adapter'])) {
+            $this->checkProxy = true;
+        }
+
+        // Set up the configuration with the parent class; this will in turn call
+        // our overridden setUri() method and configure the adapter if $uri is set.
+        parent::__construct($uri, $options);
+    }
+
+    /**
+     * Set the URI for the next request
+     *
+     * @param \Zend\Uri\Http|string $uri Target URI
+     *
+     * @return Client
+     */
+    public function setUri($uri)
+    {
+        // If the "check proxy" flag is set, make sure the adapter is configured
+        // appropriately for the current URI.
+        if ($this->checkProxy) {
+            $this->configureAdapter($uri);
+        }
+        return parent::setUri($uri);
+    }
+
+    /**
+     * Configure the adapter based on VuFind settings.
+     *
+     * @param string $uri Target URI
+     *
+     * @return void
+     */
+    protected function configureAdapter($uri)
+    {
+        $config = ConfigReader::getConfig();
+
+        // Never proxy localhost traffic, even if configured to do so:
+        $skipProxy = (strstr($uri, '//localhost') !== false);
+
+        // Proxy server settings
+        if (isset($config->Proxy->host) && !$skipProxy) {
+            $options = array(
+                'adapter' => 'Zend\\Http\\Client\\Adapter\\Proxy',
+                'proxy_host' => $config->Proxy->host
+            );
+            if (isset($config->Proxy->port)) {
+                $options['proxy_port'] = $config->Proxy->port;
+            }
+            $this->setConfig($options);
+        } else {
+            // Default if no proxy settings found:
+            $this->setAdapter('Zend\\Http\\Client\\Adapter\\Socket');
+        }
+    }
+}
diff --git a/module/VuFind/src/VuFind/Log/Logger.php b/module/VuFind/src/VuFind/Log/Logger.php
new file mode 100644
index 0000000000000000000000000000000000000000..a40b3dca27f15e9e9430a58240c8058b6918d263
--- /dev/null
+++ b/module/VuFind/src/VuFind/Log/Logger.php
@@ -0,0 +1,226 @@
+<?php
+/**
+ * VF_Logger
+ *
+ * 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  Error_Logging
+ * @author   Chris Hallberg <challber@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\Log;
+use VuFind\Config\Reader as ConfigReader, Zend\Log\Logger as BaseLogger,
+    Zend\Log\Filter\Priority as PriorityFilter, Zend\Registry;
+
+/**
+ * This class wraps the BaseLogger class to allow for log verbosity
+ *
+ * @category VuFind2
+ * @package  Error_Logging
+ * @author   Chris Hallberg <challber@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+class Logger extends BaseLogger
+{
+    protected static $debugNeeded = false;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        parent::__construct();
+
+        $config = ConfigReader::getConfig();
+        /* TODO:
+        // DEBUGGER
+        if (!$config->System->debug == false) {
+            $writer = new VF_Log_Writer_Stream('php://output');
+            $formatter = new Zend_Log_Formatter_Simple(
+                '<pre>%timestamp% %priorityName%: %message%</pre>' . PHP_EOL
+            );
+            $writer->setFormatter($formatter);
+            $this->addWriters(
+                $writer,
+                'debug-'
+                . (is_int($config->System->debug) ? $config->System->debug : '5')
+            );
+        }
+        
+        // Activate database logging, if applicable:
+        if (isset($config->Logging->database)) {
+            $parts = explode(':', $config->Logging->database);
+            $table_name = $parts[0];
+            $error_types = isset($parts[1]) ? $parts[1] : '';
+
+            $columnMapping = array(
+                'priority' => 'priority',
+                'message' => 'message',
+                'logtime' => 'timestamp',
+                'ident' => 'ident'
+            );
+
+            // Make Writers
+            $filters = explode(',', $error_types);
+            $writer = new VF_Log_Writer_Db(
+                Zend_Db_Table::getDefaultAdapter(),
+                $table_name,
+                $columnMapping
+            );
+            $this->addWriters($writer, $filters);
+        }
+
+        // Activate file logging, if applicable:
+        if (isset($config->Logging->file)) {
+            $parts = explode(':', $config->Logging->file);
+            $file = $parts[0];
+            $error_types = isset($parts[1]) ? $parts[1] : '';
+
+            // Make Writers
+            $filters = explode(',', $error_types);
+            $writer = new VF_Log_Writer_Stream($file);
+            $this->addWriters($writer, $filters);
+        }
+
+        // Activate email logging, if applicable:
+        if (isset($config->Logging->email)) {
+            // Set up the logger's mailer to behave consistently with VuFind's
+            // general mailer:
+            $parts = explode(':', $config->Logging->email);
+            $email = $parts[0];
+            $error_types = isset($parts[1]) ? $parts[1] : '';
+
+            // use smtp
+            $mailer = new Zend_Mail('UTF-8');
+            $mailer->setFrom($config->Site->email);
+            $mailer->setSubject('VuFind Log Message');
+            $mailer->addTo($email);
+
+            // Make Writers
+            $filters = explode(',', $error_types);
+            $writer = new VF_Log_Writer_Mail($mailer);
+            $this->addWriters($writer, $filters);
+        }
+
+        // Null writer to avoid errors
+        if (count($this->_writers) == 0) {
+            $this->addWriter(new VF_Log_Writer_Null());
+        }
+         */
+    }
+
+    /**
+     * Applies an array of filters to a writer
+     *
+     * Filter keys: alert, error, notice, debug
+     *
+     * @param Zend_Log_Writer_Abstract $writer  The writer to apply the filters to
+     * @param string|array             $filters An array or comma-separated string of
+     * logging levels
+     *
+     * @return \Zend\Log\Writer $writer
+     */
+    protected function addWriters($writer, $filters)
+    {
+        if (!is_array($filters)) {
+            $filters = explode(',', $filters);
+        }
+
+        foreach ($filters as $filter) {
+            $parts = explode('-', $filter);
+            $priority = $parts[0];
+            $verbosity = isset($parts[1]) ? $parts[1] : false;
+
+            // VuFind's configuration provides four priority options, each
+            // combining two of the standard Zend levels.
+            switch(trim($priority)) {
+            case 'debug':
+                // Set static flag indicating that debug is turned on:
+                self::$debugNeeded = true;
+
+                $max = BaseLogger::INFO;  // Informational: informational messages
+                $min = BaseLogger::DEBUG; // Debug: debug messages
+                break;
+            case 'notice':
+                $max = BaseLogger::WARN;  // Warning: warning conditions
+                $min = BaseLogger::NOTICE;// Notice: normal but significant condition
+                break;
+            case 'error':
+                $max = BaseLogger::CRIT;  // Critical: critical conditions
+                $min = BaseLogger::ERR;   // Error: error conditions
+                break;
+            case 'alert':
+                $max = BaseLogger::EMERG; // Emergency: system is unusable
+                $min = BaseLogger::ALERT; // Alert: action must be taken immediately
+                break;
+            default:                    // INVALID FILTER
+                continue;
+            }
+
+            // Clone the submitted writer since we'll need a separate instance of the
+            // writer for each selected priority level.
+            $newWriter = clone($writer);
+
+            // verbosity
+            if ($verbosity) {
+                $newWriter->setVerbosity($verbosity);
+            }
+
+            // filtering -- only log messages between the min and max priority levels
+            $filter1 = new PriorityFilter($min, '<=');
+            $filter2 = new PriorityFilter($max, '>=');
+            $newWriter->addFilter($filter1);
+            $newWriter->addFilter($filter2);
+
+            // add the writer
+            $this->addWriter($newWriter);
+        }
+    }
+
+    /**
+     * Log a message at the debug priority level
+     *
+     * @param string $msg Debug message
+     *
+     * @return void
+     */
+    public static function getInstance($msg)
+    {
+        $registry = Registry::getInstance();
+        if (!$registry->isRegistered('Log')) {
+            $registry->set('Log', new Manager());
+        }
+        return $registry->get('Log');
+    }
+
+    /**
+     * Is one of the log writers listening for debug messages?  (This is useful to
+     * know, since some code can save time that would be otherwise wasted generating
+     * debug messages if we know that no one is listening).
+     *
+     * @return bool
+     */
+    public static function debugNeeded()
+    {
+        return self::$debugNeeded;
+    }
+}
diff --git a/module/VuFind/src/VuFind/Search/Base/Params.php b/module/VuFind/src/VuFind/Search/Base/Params.php
index 1ff62b0ee8d07b4bf8eaea762769c6bc57e43528..6c2f61a3ecd7383d8b32175e2a80caaecb1f41a6 100644
--- a/module/VuFind/src/VuFind/Search/Base/Params.php
+++ b/module/VuFind/src/VuFind/Search/Base/Params.php
@@ -25,6 +25,9 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://www.vufind.org  Main Page
  */
+namespace VuFind\Search\Base;
+use VuFind\Config\Reader as ConfigReader, VuFind\Search\Options as SearchOptions,
+    VuFind\Translator;
 
 /**
  * Abstract parameters search model.
@@ -37,7 +40,7 @@
  * @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
+class Params
 {
     // Search terms
     protected $searchTerms = array();
@@ -73,7 +76,7 @@ class VF_Search_Base_Params
         if (is_null($options)) {
             // Create a copy of the default configuration:
             $this->options = clone(
-                VF_Search_Options::getInstance($this->getSearchClassId())
+                SearchOptions::getInstance($this->getSearchClassId())
             );
         } else {
             $this->options = $options;
@@ -97,7 +100,7 @@ class VF_Search_Base_Params
      */
     public function getSearchClassId()
     {
-        return VF_Search_Options::extractSearchClassId(get_class($this));
+        return SearchOptions::extractSearchClassId(get_class($this));
     }
 
     /**
@@ -594,10 +597,10 @@ class VF_Search_Base_Params
                 && $search['group'][0]['bool'] == 'NOT'
             ) {
                 $excludes[]
-                    = join(' ' . VF_Translator::translate('OR') . ' ', $thisGroup);
+                    = join(' ' . Translator::translate('OR') . ' ', $thisGroup);
             } else if (isset($search['group'][0]['bool'])) {
                 $groups[] = join(
-                    " " . VF_Translator::translate($search['group'][0]['bool'])." ",
+                    " " . Translator::translate($search['group'][0]['bool'])." ",
                     $thisGroup
                 );
             }
@@ -609,7 +612,7 @@ class VF_Search_Base_Params
             $output .= "(" .
                 join(
                     ") " .
-                    VF_Translator::translate($this->searchTerms[0]['join']) . " (",
+                    Translator::translate($this->searchTerms[0]['join']) . " (",
                     $groups
                 ) .
                 ")";
@@ -618,8 +621,8 @@ class VF_Search_Base_Params
         // Concatenate exclusion after that
         if (count($excludes) > 0) {
             $output .= ' ' .
-                VF_Translator::translate('NOT') . ' ((' .
-                join(') ' . VF_Translator::translate('OR') . ' (', $excludes) . "))";
+                Translator::translate('NOT') . ' ((' .
+                join(') ' . Translator::translate('OR') . ' (', $excludes) . "))";
         }
 
         return $output;
@@ -696,7 +699,7 @@ class VF_Search_Base_Params
 
         // Load the necessary settings to determine the appropriate recommendations
         // module:
-        $searchSettings = VF_Config_Reader::getConfig($this->getSearchIni());
+        $searchSettings = ConfigReader::getConfig($this->getSearchIni());
 
         // If we have a search type set, save it so we can try to load a
         // type-specific recommendations module:
@@ -776,7 +779,7 @@ class VF_Search_Base_Params
                 // Break apart the setting into module name and extra parameters:
                 $current = explode(':', $current);
                 $module = array_shift($current);
-                $class = 'VF_Recommend_' . $module;
+                $class = 'VuFind\\Recommend\\' . $module;
                 $params = implode(':', $current);
 
                 // Build a recommendation module with the provided settings.
@@ -1028,7 +1031,7 @@ class VF_Search_Base_Params
                     $list[$facetLabel][] = array(
                         'value'       => $value,
                         'displayText' =>
-                            $translate ? VF_Translator::translate($value) : $value,
+                            $translate ? Translator::translate($value) : $value,
                         'field'       => $field
                     );
                 }
diff --git a/module/VuFind/src/VuFind/Search/Base/Results.php b/module/VuFind/src/VuFind/Search/Base/Results.php
index dfedd72a1849958e1a275b4769d48f1f554a9e34..3e842f76ce6367fa83c8011a27b44161a5b4eb37 100644
--- a/module/VuFind/src/VuFind/Search/Base/Results.php
+++ b/module/VuFind/src/VuFind/Search/Base/Results.php
@@ -25,6 +25,8 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://www.vufind.org  Main Page
  */
+namespace VuFind\Search\Base;
+use VuFind\Search\UrlHelper;
 
 /**
  * Abstract results search model.
@@ -37,7 +39,7 @@
  * @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
+abstract class Results
 {
     protected $params;
     // Total number of results available
@@ -65,7 +67,7 @@ abstract class VF_Search_Base_Results
      * @param VF_Search_Base_Params $params Object representing user search
      * parameters.
      */
-    public function __construct(VF_Search_Base_Params $params)
+    public function __construct(Params $params)
     {
         // Save the parameters, then perform the search:
         $this->params = $params;
@@ -90,7 +92,7 @@ abstract class VF_Search_Base_Results
     {
         // Set up URL helper:
         if (!isset($this->helpers['url'])) {
-            $this->helpers['url'] = new VF_Search_UrlHelper($this);
+            $this->helpers['url'] = new UrlHelper($this);
         }
         return $this->helpers['url'];
     }
diff --git a/module/VuFind/src/VuFind/Search/Solr/Params.php b/module/VuFind/src/VuFind/Search/Solr/Params.php
index 23b37b7d182357ae45ec9e1cadf9603b32d2e44c..1adfd6536773f0643a805f5f9b1ba004211076a2 100644
--- a/module/VuFind/src/VuFind/Search/Solr/Params.php
+++ b/module/VuFind/src/VuFind/Search/Solr/Params.php
@@ -25,6 +25,9 @@
  * @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\Params as BaseParams;
 
 /**
  * Solr Search Parameters
@@ -35,7 +38,7 @@
  * @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
+class Params extends BaseParams
 {
     // Facets
     protected $facetLimit = 30;
@@ -49,14 +52,15 @@ class VF_Search_Solr_Params extends VF_Search_Base_Params
     /**
      * Constructor
      *
-     * @param VF_Search_Base_Options $options Options to use (null to load defaults)
+     * @param \VuFind\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');
+        $config = ConfigReader::getConfig('facets');
         if (isset($config->Results_Settings->facet_limit)
             && is_numeric($config->Results_Settings->facet_limit)
         ) {
@@ -241,7 +245,7 @@ class VF_Search_Solr_Params extends VF_Search_Base_Params
      */
     public function initAdvancedFacets()
     {
-        $config = VF_Config_Reader::getConfig('facets');
+        $config = ConfigReader::getConfig('facets');
         if (isset($config->Advanced)) {
             foreach ($config->Advanced as $key => $value) {
                 $this->addFacet($key, $value);
@@ -261,7 +265,7 @@ class VF_Search_Solr_Params extends VF_Search_Base_Params
      */
     public function initBasicFacets()
     {
-        $config = VF_Config_Reader::getConfig('facets');
+        $config = ConfigReader::getConfig('facets');
         if (isset($config->ResultsTop)) {
             foreach ($config->ResultsTop as $key => $value) {
                 $this->addFacet($key, $value);
@@ -393,7 +397,7 @@ class VF_Search_Solr_Params extends VF_Search_Base_Params
      */
     public function getQueryIDLimit()
     {
-        $config = VF_Config_Reader::getConfig();
+        $config = ConfigReader::getConfig();
         return isset($config->Index->maxBooleanClauses)
             ? $config->Index->maxBooleanClauses : 1024;
     }
diff --git a/module/VuFind/src/VuFind/Search/Solr/Results.php b/module/VuFind/src/VuFind/Search/Solr/Results.php
index c621177ba95a3bd752368376401b74c9e70bd21a..9bcf708391ab4ab4b595dbf99928b9b43decffce 100644
--- a/module/VuFind/src/VuFind/Search/Solr/Results.php
+++ b/module/VuFind/src/VuFind/Search/Solr/Results.php
@@ -25,6 +25,13 @@
  * @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\Connection\Manager as ConnectionManager,
+    VuFind\Exception\RecordMissing as RecordMissingException,
+    VuFind\Search\Base\Results as BaseResults,
+    VuFind\Search\Options as SearchOptions,
+    VuFind\Translator\Translator;
 
 /**
  * Solr Search Parameters
@@ -35,7 +42,7 @@
  * @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
+class Results extends BaseResults
 {
     // Raw Solr search response:
     protected $rawResponse = null;
@@ -45,16 +52,17 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
      *
      * @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)
+     * that \VuFind\Search\$index\Options and \VuFind\Connection\$index are both
+     * valid classes)
      *
-     * @return VF_Connection_Solr
+     * @return \VuFind\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);
+        $options = SearchOptions::getInstance($index);
         $allShards = $options->getShards();
         if (is_null($shards)) {
             $shards = array_keys($allShards);
@@ -70,7 +78,7 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
         }
 
         // Connect to Solr and set up shards:
-        $solr = VF_Connection_Manager::connectToIndex($index);
+        $solr = ConnectionManager::connectToIndex($index);
         $solr->setShards($shards, $options->getSolrShardsFieldsToStrip());
         return $solr;
     }
@@ -327,7 +335,7 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
     protected function doSpellingReplace($term, $targetTerm, $inToken, $details,
         $returnArray
     ) {
-        $config = VF_Config_Reader::getConfig();
+        $config = ConfigReader::getConfig();
 
         $returnArray[$targetTerm]['freq'] = $details['freq'];
         foreach ($details['suggestions'] as $word => $freq) {
@@ -420,7 +428,7 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
                 $currentSettings = array();
                 $currentSettings['value'] = $facet[0];
                 $currentSettings['displayText']
-                    = $translate ? VF_Translator::translate($facet[0]) : $facet[0];
+                    = $translate ? Translator::translate($facet[0]) : $facet[0];
                 $currentSettings['count'] = $facet[1];
                 $currentSettings['isApplied']
                     = $this->params->hasFilter("$field:".$facet[0]);
@@ -437,23 +445,23 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
      *
      * @param string $id Unique identifier of record
      *
-     * @throws VF_Exception_RecordMissing
-     * @return VF_RecordDriver_Base
+     * @throws RecordMissingException
+     * @return \VuFind\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())
+        $options = SearchOptions::getInstance(
+            SearchOptions::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(
+            throw new RecordMissingException(
                 'Record ' . $id . ' does not exist.'
             );
         }
@@ -471,7 +479,7 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
     {
         // 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();
+        $params = new Params();
         $pageSize = $params->getQueryIDLimit();
         if ($pageSize < 1 || $pageSize > 100) {
             $pageSize = 100;
@@ -483,7 +491,7 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
             $currentPage = array_splice($ids, 0, $pageSize, array());
             $params->setQueryIDs($currentPage);
             $params->setLimit($pageSize);
-            $results = new VF_Search_Solr_Results($params);
+            $results = new Results($params);
             $retVal = array_merge($retVal, $results->getResults());
         }
 
@@ -519,7 +527,7 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
      *
      * @param array $data Solr data
      *
-     * @return VF_RecordDriver_Base
+     * @return \VuFind\RecordDriver\Base
      */
     protected static function initRecordDriver($data)
     {
@@ -527,12 +535,12 @@ class VF_Search_Solr_Results extends VF_Search_Base_Results
         static $badClasses = array();
 
         // Determine driver path based on record type:
-        $driver = 'VF_RecordDriver_Solr' . ucwords($data['recordtype']);
+        $driver = 'VuFind\\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';
+            $driver = 'VuFind\\RecordDriver\\SolrDefault';
         }
 
         // Build the object:
diff --git a/module/VuFind/src/VuFind/Search/UrlHelper.php b/module/VuFind/src/VuFind/Search/UrlHelper.php
index 0d43f52c42be95388f335f6e7b509c905b0a8a17..62244a6494ec522db7a7e652fe31878ee70ceb1a 100644
--- a/module/VuFind/src/VuFind/Search/UrlHelper.php
+++ b/module/VuFind/src/VuFind/Search/UrlHelper.php
@@ -25,7 +25,8 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org   Main Site
  */
- 
+namespace VuFind\Search;
+
 /**
  * Class to help build URLs and forms in the view based on search settings.
  *
@@ -35,7 +36,7 @@
  * @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
+class UrlHelper
 {
     protected $results;
     protected $basicSearchParam = 'lookfor';
diff --git a/module/VuFind/src/VuFind/Translator/Translator.php b/module/VuFind/src/VuFind/Translator/Translator.php
new file mode 100644
index 0000000000000000000000000000000000000000..3fae00e1a114fc60a96e68d333d8d6f2cb07d927
--- /dev/null
+++ b/module/VuFind/src/VuFind/Translator/Translator.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * VuFind Translator
+ *
+ * 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
+ */
+namespace VuFind\Translator;
+use Zend\View\Helper\Translator as TranslatorHelper;
+
+/**
+ * Wrapper class to handle text translation
+ *
+ * @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 Translator
+{
+    /**
+     * Translate a string using the Translate view helper.
+     *
+     * @param string $str String to translate
+     *
+     * @return string
+     */
+    public static function translate($str)
+    {
+        static $translator = false;
+        if (!$translator) {
+            $translator = new TranslatorHelper();
+        }
+        return $translator->translate($str);
+    }
+}
\ No newline at end of file