From bd7b4b84151c1dffab47d13f944074901c67b9c7 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Wed, 27 Jan 2016 14:56:07 -0500
Subject: [PATCH] Refactored all search persistence into VuFind\Search\Memory.

---
 .../src/VuFind/Controller/AbstractSearch.php  |  10 +-
 .../VuFind/src/VuFind/Search/Base/Options.php | 104 ++----------------
 .../VuFind/src/VuFind/Search/Base/Params.php  | 101 ++++++-----------
 module/VuFind/src/VuFind/Search/Memory.php    |  71 +++++++++++-
 .../VuFind/src/VuFind/Search/SearchRunner.php |   8 +-
 .../src/VuFind/View/Helper/Root/Factory.php   |  28 ++---
 ...GetLastSearchLink.php => SearchMemory.php} |  44 +++++++-
 .../src/VuFindTest/Search/MemoryTest.php      |  14 +--
 themes/bootstrap3/templates/cart/cart.phtml   |  10 +-
 themes/bootstrap3/templates/cart/email.phtml  |   2 +-
 themes/bootstrap3/templates/cart/export.phtml |   2 +-
 themes/bootstrap3/templates/cart/save.phtml   |   2 +-
 .../templates/collection/view.phtml           |   2 +-
 .../bootstrap3/templates/primo/advanced.phtml |   2 +-
 .../bootstrap3/templates/record/addtag.phtml  |   2 +-
 themes/bootstrap3/templates/record/cite.phtml |   2 +-
 .../bootstrap3/templates/record/email.phtml   |   2 +-
 .../templates/record/export-menu.phtml        |   2 +-
 themes/bootstrap3/templates/record/hold.phtml |   2 +-
 .../templates/record/illrequest.phtml         |   2 +-
 themes/bootstrap3/templates/record/save.phtml |   2 +-
 themes/bootstrap3/templates/record/sms.phtml  |   2 +-
 .../record/storageretrievalrequest.phtml      |   2 +-
 themes/bootstrap3/templates/record/view.phtml |   2 +-
 .../templates/search/advanced/layout.phtml    |   7 +-
 .../templates/search/advanced/limit.phtml     |   2 +-
 .../bootstrap3/templates/search/email.phtml   |   2 +-
 .../templates/search/searchbox.phtml          |   6 +-
 .../jquerymobile/templates/eds/advanced.phtml |   4 +-
 .../templates/primo/advanced.phtml            |   5 +-
 .../templates/search/advanced.phtml           |   4 +-
 .../templates/search/searchbox.phtml          |   4 +-
 themes/root/theme.config.php                  |   2 +-
 33 files changed, 227 insertions(+), 229 deletions(-)
 rename module/VuFind/src/VuFind/View/Helper/Root/{GetLastSearchLink.php => SearchMemory.php} (66%)

diff --git a/module/VuFind/src/VuFind/Controller/AbstractSearch.php b/module/VuFind/src/VuFind/Controller/AbstractSearch.php
index 2798a688ae7..893c4471cbd 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractSearch.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractSearch.php
@@ -185,12 +185,17 @@ class AbstractSearch extends AbstractBase
      */
     protected function rememberSearch($results)
     {
+        // Only save search URL if the property tells us to...
         if ($this->rememberSearch) {
             $searchUrl = $this->url()->fromRoute(
                 $results->getOptions()->getSearchAction()
             ) . $results->getUrlQuery()->getParams(false);
             $this->getSearchMemory()->rememberSearch($searchUrl);
         }
+
+        // Always save search parameters, since these are namespaced by search
+        // class ID.
+        $this->getSearchMemory()->rememberParams($results->getParams());
     }
 
     /**
@@ -279,8 +284,11 @@ class AbstractSearch extends AbstractBase
         $request = $this->getRequest()->getQuery()->toArray()
             + $this->getRequest()->getPost()->toArray();
 
+        $lastView = $this->getSearchMemory()
+            ->retrieveLastSetting($this->searchClassId, 'view');
         $view->results = $results = $runner->run(
-            $request, $this->searchClassId, $this->getSearchSetupCallback()
+            $request, $this->searchClassId, $this->getSearchSetupCallback(),
+            $lastView
         );
         $view->params = $results->getParams();
 
diff --git a/module/VuFind/src/VuFind/Search/Base/Options.php b/module/VuFind/src/VuFind/Search/Base/Options.php
index e2f57f162e8..d59e9181fe9 100644
--- a/module/VuFind/src/VuFind/Search/Base/Options.php
+++ b/module/VuFind/src/VuFind/Search/Base/Options.php
@@ -617,98 +617,6 @@ abstract class Options implements TranslatorAwareInterface
         return true;
     }
 
-    /**
-     * Get a session namespace specific to the current class.
-     *
-     * @return SessionContainer
-     */
-    public function getSession()
-    {
-        static $session = false;
-        if (!$session) {
-            $session = new SessionContainer(get_class($this));
-        }
-        return $session;
-    }
-
-    /**
-     * Remember the last sort option used.
-     *
-     * @param string $last Option to remember.
-     *
-     * @return void
-     */
-    public function rememberLastSort($last)
-    {
-        $session = $this->getSession();
-        if (!$session->getManager()->getStorage()->isImmutable()) {
-            $session->lastSort = $last;
-        }
-    }
-
-    /**
-     * Retrieve the last sort option used.
-     *
-     * @return string
-     */
-    public function getLastSort()
-    {
-        $session = $this->getSession();
-        return isset($session->lastSort) ? $session->lastSort : null;
-    }
-
-    /**
-     * Remember the last limit option used.
-     *
-     * @param string $last Option to remember.
-     *
-     * @return void
-     */
-    public function rememberLastLimit($last)
-    {
-        $session = $this->getSession();
-        if (!$session->getManager()->getStorage()->isImmutable()) {
-            $session->lastLimit = $last;
-        }
-    }
-
-    /**
-     * Retrieve the last limit option used.
-     *
-     * @return string
-     */
-    public function getLastLimit()
-    {
-        $session = $this->getSession();
-        return isset($session->lastLimit) ? $session->lastLimit : null;
-    }
-
-    /**
-     * Remember the last view option used.
-     *
-     * @param string $last Option to remember.
-     *
-     * @return void
-     */
-    public function rememberLastView($last)
-    {
-        $session = $this->getSession();
-        if (!$session->getManager()->getStorage()->isImmutable()) {
-            $session->lastView = $last;
-        }
-    }
-
-    /**
-     * Retrieve the last view option used.
-     *
-     * @return string
-     */
-    public function getLastView()
-    {
-        $session = $this->getSession();
-        return isset($session->lastView) ? $session->lastView : null;
-    }
-
     /**
      * Get default filters to apply to an empty search.
      *
@@ -836,6 +744,18 @@ abstract class Options implements TranslatorAwareInterface
         return $recommend;
     }
 
+    /**
+     * Get the identifier used for naming the various search classes in this family.
+     *
+     * @return string
+     */
+    public function getSearchClassId()
+    {
+        // Parse identifier out of class name of format VuFind\Search\[id]\Options:
+        $class = explode('\\', get_class($this));
+        return $class[2];
+    }
+
     /**
      * Sleep magic method -- the translator can't be serialized, so we need to
      * exclude it from serialization.  Since we can't obtain a new one in the
diff --git a/module/VuFind/src/VuFind/Search/Base/Params.php b/module/VuFind/src/VuFind/Search/Base/Params.php
index b354d0357cb..552ddc0881d 100644
--- a/module/VuFind/src/VuFind/Search/Base/Params.php
+++ b/module/VuFind/src/VuFind/Search/Base/Params.php
@@ -106,6 +106,13 @@ class Params implements ServiceLocatorAwareInterface
      */
     protected $view = null;
 
+    /**
+     * Previously-used view (loaded in from session)
+     *
+     * @var string
+     */
+    protected $lastView = null;
+
     /**
      * Search options
      *
@@ -220,9 +227,7 @@ class Params implements ServiceLocatorAwareInterface
      */
     public function getSearchClassId()
     {
-        // Parse identifier out of class name of format VuFind\Search\[id]\Params:
-        $class = explode('\\', get_class($this));
-        return $class[2];
+        return $this->getOptions()->getSearchClassId();
     }
 
     /**
@@ -247,13 +252,6 @@ class Params implements ServiceLocatorAwareInterface
         $this->initSort($request);
         $this->initFilters($request);
         $this->initHiddenFilters($request);
-
-        // Remember the user's settings for future reference (we only want to do
-        // this in initFromRequest, since other code may call the set methods from
-        // other contexts!):
-        $this->getOptions()->rememberLastLimit($this->getLimit());
-        $this->getOptions()->rememberLastSort($this->getSort());
-        $this->rememberLastHiddenFilters($this->getHiddenFilters());
     }
 
     /**
@@ -475,6 +473,18 @@ class Params implements ServiceLocatorAwareInterface
         $this->setSort($request->get('sort'));
     }
 
+    /**
+     * Set the last value of the view parameter (if available in session).
+     *
+     * @param string $view Last valid view parameter value
+     *
+     * @return void
+     */
+    public function setLastView($view)
+    {
+        $this->lastView = $view;
+    }
+
     /**
      * Get the value for which results view to use
      *
@@ -487,25 +497,17 @@ class Params implements ServiceLocatorAwareInterface
     {
         // Check for a view parameter in the url.
         $view = $request->get('view');
-        $lastView = $this->getOptions()->getLastView();
-        if (!empty($view)) {
-            if ($view == 'rss') {
-                // we don't want to store rss in the Session
-                $this->setView('rss');
-            } else {
-                // store non-rss views in Session for persistence
-                $validViews = $this->getOptions()->getViewOptions();
-                // make sure the url parameter is a valid view
-                if (in_array($view, array_keys($validViews))) {
-                    $this->setView($view);
-                    $this->getOptions()->rememberLastView($view);
-                } else {
-                    $this->setView($this->getOptions()->getDefaultView());
-                }
-            }
-        } else if (!empty($lastView)) {
-            // if there is nothing in the URL, check the Session
-            $this->setView($lastView);
+        $validViews = $this->getOptions()->getViewOptions();
+        if ($view == 'rss') {
+            // RSS is a special case that does not require config validation
+            $this->setView('rss');
+        } else if (!empty($view) && in_array($view, array_keys($validViews))) {
+            // make sure the url parameter is a valid view
+            $this->setView($view);
+        } else if (!empty($this->lastView)) {
+            // if there is nothing in the URL, see if we had a previous value
+            // injected based on session information.
+            $this->setView($this->lastView);
         } else {
             // otherwise load the default
             $this->setView($this->getOptions()->getDefaultView());
@@ -1399,47 +1401,6 @@ class Params implements ServiceLocatorAwareInterface
         }
     }
 
-    /**
-     * Remember the last hidden filters used.
-     *
-     * @param string $last Option to remember.
-     *
-     * @return void
-     */
-    protected function rememberLastHiddenFilters($last)
-    {
-        $session = $this->getSession();
-        if (!$session->getManager()->getStorage()->isImmutable()) {
-            $session->lastHiddenFilters = $last;
-        }
-    }
-
-    /**
-     * Retrieve the last hidden filters used.
-     *
-     * @return array
-     */
-    public function getLastHiddenFilters()
-    {
-        $session = $this->getSession();
-        return isset($session->lastHiddenFilters)
-            ? $session->lastHiddenFilters : [];
-    }
-
-    /**
-     * Get a session namespace specific to the current class.
-     *
-     * @return SessionContainer
-     */
-    protected function getSession()
-    {
-        static $session = false;
-        if (!$session) {
-            $session = new SessionContainer(get_class($this));
-        }
-        return $session;
-    }
-
     /**
      * Return a query string for the current search with a search term replaced.
      *
diff --git a/module/VuFind/src/VuFind/Search/Memory.php b/module/VuFind/src/VuFind/Search/Memory.php
index 55f2769453c..480d11f8b5c 100644
--- a/module/VuFind/src/VuFind/Search/Memory.php
+++ b/module/VuFind/src/VuFind/Search/Memory.php
@@ -85,6 +85,47 @@ class Memory
         unset($this->session->last);
     }
 
+    /**
+     * Remember a user's last search parameters.
+     *
+     * @param string $context Context of search (usually search class ID).
+     * @param array  $params  Associative array of keys/values to store.
+     *
+     * @return void
+     */
+    public function rememberLastSettings($context, $params)
+    {
+        if (!$this->active) {
+            return;
+        }
+        foreach ($params as $setting => $value) {
+            $this->session->{"params|$context|$setting"} = $value;
+        }
+    }
+
+    /**
+     * Wrapper around rememberLastSettings() to extract key values from a
+     * search Params object.
+     *
+     * @param \VuFind\Search\Base\Params $params Parameter object
+     *
+     * @return void
+     */
+    public function rememberParams(\VuFind\Search\Base\Params $params)
+    {
+        $settings = [
+            'hiddenFilters' => $params->getHiddenFilters(),
+            'limit' => $params->getLimit(),
+            'sort' => $params->getSort(),
+            'view' => $params->getView(),
+        ];
+        // Special case: RSS view should not be persisted:
+        if (strtolower($settings['view']) == 'rss') {
+            unset($settings['view']);
+        }
+        $this->rememberLastSettings($params->getSearchClassId(), $settings);
+    }
+
     /**
      * Store the last accessed search URL in the session for future reference.
      *
@@ -107,13 +148,41 @@ class Memory
         }
     }
 
+    /**
+     * Deprecated alias for retrieveSearch, for legacy compatibility.
+     *
+     * @deprecated
+     *
+     * @return string|null
+     */
+    public function retrieve()
+    {
+        return $this->retrieveSearch();
+    }
+
+    /**
+     * Retrieve a previous user parameter, if available. Return $default if
+     * not found.
+     *
+     * @param string $context Context of search (usually search class ID).
+     * @param string $setting Name of setting to retrieve.
+     * @param mixed  $default Default value if setting is absent.
+     *
+     * @return mixed
+     */
+    public function retrieveLastSetting($context, $setting, $default = null)
+    {
+        return isset($this->session->{"params|$context|$setting"})
+            ? $this->session->{"params|$context|$setting"} : $default;
+    }
+
     /**
      * Retrieve last accessed search URL, if available.  Returns null if no URL
      * is available.
      *
      * @return string|null
      */
-    public function retrieve()
+    public function retrieveSearch()
     {
         return isset($this->session->last) ? $this->session->last : null;
     }
diff --git a/module/VuFind/src/VuFind/Search/SearchRunner.php b/module/VuFind/src/VuFind/Search/SearchRunner.php
index 26284d190d7..73a37f22a95 100644
--- a/module/VuFind/src/VuFind/Search/SearchRunner.php
+++ b/module/VuFind/src/VuFind/Search/SearchRunner.php
@@ -90,13 +90,16 @@ class SearchRunner
      * and attaching listeners; if provided, will be passed three parameters:
      * this object, the search parameters object, and a unique identifier for
      * the current running search.
+     * @param string           $lastView      Last valid view parameter loaded
+     * from a previous search (optional; used for view persistence).
      *
      * @return \VuFind\Search\Base\Results
      *
      * @throws \VuFindSearch\Backend\Exception\BackendException
      */
-    public function run($rawRequest, $searchClassId = 'Solr', $setupCallback = null)
-    {
+    public function run($rawRequest, $searchClassId = 'Solr', $setupCallback = null,
+        $lastView = null
+    ) {
         // Increment the ID counter, then save the current value to a variable;
         // since events within this run could theoretically trigger additional
         // runs of the SearchRunner, we can't rely on the property value past
@@ -112,6 +115,7 @@ class SearchRunner
         // Set up the search:
         $results = $this->resultsManager->get($searchClassId);
         $params = $results->getParams();
+        $params->setLastView($lastView);
         $params->initFromRequest($request);
 
         if (is_callable($setupCallback)) {
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
index dd02f9027b6..78be9b79621 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
@@ -237,20 +237,6 @@ class Factory
         return new Piwik($url, $siteId, $customVars);
     }
 
-    /**
-     * Construct the GetLastSearchLink helper.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return GetLastSearchLink
-     */
-    public static function getGetLastSearchLink(ServiceManager $sm)
-    {
-        return new GetLastSearchLink(
-            $sm->getServiceLocator()->get('VuFind\Search\Memory')
-        );
-    }
-
     /**
      * Construct the HelpText helper.
      *
@@ -443,6 +429,20 @@ class Factory
         );
     }
 
+    /**
+     * Construct the SearchMemory helper.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return SearchMemory
+     */
+    public static function getSearchMemory(ServiceManager $sm)
+    {
+        return new SearchMemory(
+            $sm->getServiceLocator()->get('VuFind\Search\Memory')
+        );
+    }
+
     /**
      * Construct the SearchOptions helper.
      *
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/GetLastSearchLink.php b/module/VuFind/src/VuFind/View/Helper/Root/SearchMemory.php
similarity index 66%
rename from module/VuFind/src/VuFind/View/Helper/Root/GetLastSearchLink.php
rename to module/VuFind/src/VuFind/View/Helper/Root/SearchMemory.php
index 8bc3eed1b82..3ee78f750cb 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/GetLastSearchLink.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/SearchMemory.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * "Last search link" view helper
+ * View helper for remembering recent user searches/parameters.
  *
  * PHP version 5
  *
@@ -29,7 +29,7 @@ namespace VuFind\View\Helper\Root;
 use VuFind\Search\Memory, Zend\View\Helper\AbstractHelper;
 
 /**
- * "Last search link" view helper
+ * View helper for remembering recent user searches/parameters.
  *
  * @category VuFind2
  * @package  View_Helpers
@@ -37,7 +37,7 @@ use VuFind\Search\Memory, Zend\View\Helper\AbstractHelper;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
  */
-class GetLastSearchLink extends AbstractHelper
+class SearchMemory extends AbstractHelper
 {
     /**
      * Search memory
@@ -66,9 +66,9 @@ class GetLastSearchLink extends AbstractHelper
      *
      * @return string
      */
-    public function __invoke($link, $prefix = '', $suffix = '')
+    public function getLastSearchLink($link, $prefix = '', $suffix = '')
     {
-        $last = $this->memory->retrieve();
+        $last = $this->memory->retrieveSearch();
         if (!empty($last)) {
             $escaper = $this->getView()->plugin('escapeHtml');
             return $prefix . '<a href="' . $escaper($last) . '">' . $link . '</a>'
@@ -76,4 +76,38 @@ class GetLastSearchLink extends AbstractHelper
         }
         return '';
     }
+
+    /**
+     * Retrieve the last hidden filters used.
+     *
+     * @return array
+     */
+    public function getLastHiddenFilters()
+    {
+        return $this->memory->retrieveLastSetting($context, 'hiddenFilters');
+    }
+
+    /**
+     * Retrieve the last limit option used.
+     *
+     * @param string $context Context of search (usually search class ID).
+     *
+     * @return string
+     */
+    public function getLastLimit($context)
+    {
+        return $this->memory->retrieveLastSetting($context, 'limit');
+    }
+
+    /**
+     * Retrieve the last sort option used.
+     *
+     * @param string $context Context of search (usually search class ID).
+     *
+     * @return string
+     */
+    public function getLastSort($context)
+    {
+        return $this->memory->retrieveLastSetting($context, 'sort');
+    }
 }
\ No newline at end of file
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Search/MemoryTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/MemoryTest.php
index 57610ef8cf2..53cb0a005b9 100644
--- a/module/VuFind/tests/unit-tests/src/VuFindTest/Search/MemoryTest.php
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/MemoryTest.php
@@ -50,10 +50,10 @@ class MemoryTest extends TestCase
     public function testBasicMemory()
     {
         $mem = new Memory();
-        $this->assertEquals(null, $mem->retrieve());
+        $this->assertEquals(null, $mem->retrieveSearch());
         $url = 'http://test';
         $mem->rememberSearch($url);
-        $this->assertEquals($url, $mem->retrieve());
+        $this->assertEquals($url, $mem->retrieveSearch());
     }
 
     /**
@@ -66,9 +66,9 @@ class MemoryTest extends TestCase
         $mem = new Memory();
         $url = 'http://test';
         $mem->rememberSearch($url);
-        $this->assertEquals($url, $mem->retrieve());
+        $this->assertEquals($url, $mem->retrieveSearch());
         $mem->forgetSearch();
-        $this->assertEquals(null, $mem->retrieve());
+        $this->assertEquals(null, $mem->retrieveSearch());
     }
 
     /**
@@ -80,7 +80,7 @@ class MemoryTest extends TestCase
     {
         $mem = new Memory();
         $mem->rememberSearch('');
-        $this->assertEquals(null, $mem->retrieve());
+        $this->assertEquals(null, $mem->retrieveSearch());
     }
 
     /**
@@ -93,9 +93,9 @@ class MemoryTest extends TestCase
         $mem = new Memory();
         $url = 'http://test';
         $mem->rememberSearch($url);
-        $this->assertEquals($url, $mem->retrieve());
+        $this->assertEquals($url, $mem->retrieveSearch());
         $mem->disable();
         $mem->rememberSearch('http://ignoreme');
-        $this->assertEquals($url, $mem->retrieve());
+        $this->assertEquals($url, $mem->retrieveSearch());
     }
 }
\ No newline at end of file
diff --git a/themes/bootstrap3/templates/cart/cart.phtml b/themes/bootstrap3/templates/cart/cart.phtml
index a6a1b0efc59..4efe7527d07 100644
--- a/themes/bootstrap3/templates/cart/cart.phtml
+++ b/themes/bootstrap3/templates/cart/cart.phtml
@@ -3,12 +3,12 @@
   $this->headTitle($this->translate('Book Bag'));
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li class="active">' . $this->transEsc('Book Bag') . '</li>';
 ?>
 <?=$this->flashmessages()?>
 <form class="form-inline" action="<?=$this->url('cart-home')?>" method="post"  name="cartForm">
-  <? if (!$this->cart()->isEmpty()): ?>
+  <? if (!$this->cart()->isEmpty()): ?>
     <div class="cart-controls clearfix">
       <div class="checkbox pull-left flip">
         <label>
@@ -55,7 +55,7 @@
           <li><a id="cart-confirm-empty" onClick="submitCartForm(this, {'empty':'empty'})" title="<?=$this->transEsc('bookbag_confirm_empty')?>"><?=$this->transEsc('confirm_dialog_yes')?></a></li>
           <li><a onClick="$('.fa.fa-spinner').remove()"><?=$this->transEsc('confirm_dialog_no')?></a></li>
         </ul>
-      </div>
+      </div>
     </div>
   <? endif; ?>
   <?=$this->render('cart/contents.phtml')?>
@@ -71,8 +71,8 @@
       return function() {location.reload()}
     }
   }
-  function submitCartForm(elem, data) {
-    var url = VuFind.getPath() + '/AJAX/JSON?method=getLightbox&submodule=Cart&subaction=Home';
+  function submitCartForm(elem, data) {
+    var url = VuFind.getPath() + '/AJAX/JSON?method=getLightbox&submodule=Cart&subaction=Home';
     $.post(url, data, determineCallback(elem));
   }
   function submitFormWithIds(elem, data) {
diff --git a/themes/bootstrap3/templates/cart/email.phtml b/themes/bootstrap3/templates/cart/email.phtml
index 181903dedb5..31b7b62a319 100644
--- a/themes/bootstrap3/templates/cart/email.phtml
+++ b/themes/bootstrap3/templates/cart/email.phtml
@@ -3,7 +3,7 @@
   $this->headTitle($this->translate('email_selected_favorites'));
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li><a href="' .$this->url('cart-home'). '">' .$this->transEsc('Cart'). '</a></li> '
     . '<li class="active">' . $this->transEsc('email_selected_favorites') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/cart/export.phtml b/themes/bootstrap3/templates/cart/export.phtml
index b1855b21891..fc66aa6a5c3 100644
--- a/themes/bootstrap3/templates/cart/export.phtml
+++ b/themes/bootstrap3/templates/cart/export.phtml
@@ -3,7 +3,7 @@
     $this->headTitle($this->translate('Export Favorites'));
 
     // Set up breadcrumbs:
-    $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+    $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li><a href="' .$this->url('cart-home'). '">' .$this->transEsc('Cart'). '</a></li> '
     . '<li class="active">' . $this->transEsc('Export Favorites') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/cart/save.phtml b/themes/bootstrap3/templates/cart/save.phtml
index a84570f30c8..6db7ef2e82d 100644
--- a/themes/bootstrap3/templates/cart/save.phtml
+++ b/themes/bootstrap3/templates/cart/save.phtml
@@ -3,7 +3,7 @@
     $this->headTitle($this->translate('bookbag_save_selected'));
 
     // Set up breadcrumbs:
-    $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ') .
+    $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ') .
         '<li class="active">' . $this->transEsc('bookbag_save_selected') . '</li>';
 ?>
 <h2><?=$this->transEsc('bookbag_save_selected')?></h2>
diff --git a/themes/bootstrap3/templates/collection/view.phtml b/themes/bootstrap3/templates/collection/view.phtml
index c18987cb591..33e58e99e73 100644
--- a/themes/bootstrap3/templates/collection/view.phtml
+++ b/themes/bootstrap3/templates/collection/view.phtml
@@ -16,7 +16,7 @@
   $tree = (strtolower($this->activeTab) == 'hierarchytree');
 
   // Set up breadcrumbs:
-  $lastSearch = $this->getLastSearchLink($this->transEsc('Search'));
+  $lastSearch = $this->searchMemory()->getLastSearchLink($this->transEsc('Search'));
   if (!empty($lastSearch)) {
     $this->layout()->breadcrumbs = '<li>' . $lastSearch . '</li> ';
   }
diff --git a/themes/bootstrap3/templates/primo/advanced.phtml b/themes/bootstrap3/templates/primo/advanced.phtml
index 379e72770ce..d2286aee4e6 100644
--- a/themes/bootstrap3/templates/primo/advanced.phtml
+++ b/themes/bootstrap3/templates/primo/advanced.phtml
@@ -77,7 +77,7 @@
           </div>
         </div>
       <? endfor; ?>
-      <? $lastSort = $this->options->getLastSort(); ?>
+      <? $lastSort = $this->searchMemory()->getLastSort($this->options->getSearchClassId()); ?>
       <? if (!empty($lastSort)): ?>
         <input type="hidden" name="sort" value="<?=$this->escapeHtmlAttr($lastSort)?>" />
       <? endif; ?>
diff --git a/themes/bootstrap3/templates/record/addtag.phtml b/themes/bootstrap3/templates/record/addtag.phtml
index 3dfefa5c71b..4b6bd084d9a 100644
--- a/themes/bootstrap3/templates/record/addtag.phtml
+++ b/themes/bootstrap3/templates/record/addtag.phtml
@@ -3,7 +3,7 @@
     $this->headTitle($this->translate('Add Tag'));
 
     // Set up breadcrumbs:
-    $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+    $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
       . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
       . '<li class="active">' . $this->transEsc('Add Tag') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/cite.phtml b/themes/bootstrap3/templates/record/cite.phtml
index e6deb92cbf0..a6db3edaea7 100644
--- a/themes/bootstrap3/templates/record/cite.phtml
+++ b/themes/bootstrap3/templates/record/cite.phtml
@@ -3,7 +3,7 @@
   $this->headTitle($this->translate('Record Citations'));
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
     . '<li class="active">' . $this->transEsc('Record Citations') . '</li>';
 
diff --git a/themes/bootstrap3/templates/record/email.phtml b/themes/bootstrap3/templates/record/email.phtml
index 3f512c4e30e..a6667493772 100644
--- a/themes/bootstrap3/templates/record/email.phtml
+++ b/themes/bootstrap3/templates/record/email.phtml
@@ -3,7 +3,7 @@
   $this->headTitle($this->translate('Email Record'));
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
     . '<li class="active">' . $this->transEsc('Email Record') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/export-menu.phtml b/themes/bootstrap3/templates/record/export-menu.phtml
index 376a99c270f..9b81be96143 100644
--- a/themes/bootstrap3/templates/record/export-menu.phtml
+++ b/themes/bootstrap3/templates/record/export-menu.phtml
@@ -3,7 +3,7 @@
   $this->headTitle($this->translate('Export Record'));
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
     . '<li class="active">' . $this->transEsc('Export Record') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/hold.phtml b/themes/bootstrap3/templates/record/hold.phtml
index 8706b196d42..48cbac68e4d 100644
--- a/themes/bootstrap3/templates/record/hold.phtml
+++ b/themes/bootstrap3/templates/record/hold.phtml
@@ -3,7 +3,7 @@
     $this->headTitle($this->translate('request_place_text') . ': ' . $this->driver->getBreadcrumb());
 
     // Set up breadcrumbs:
-    $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+    $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
         . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
         . '<li class="active">' . $this->transEsc('request_place_text') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/illrequest.phtml b/themes/bootstrap3/templates/record/illrequest.phtml
index 49e2074a521..5a695d8fdb5 100644
--- a/themes/bootstrap3/templates/record/illrequest.phtml
+++ b/themes/bootstrap3/templates/record/illrequest.phtml
@@ -3,7 +3,7 @@
     $this->headTitle($this->translate('ill_request_place_text') . ': ' . $this->driver->getBreadcrumb());
 
     // Set up breadcrumbs:
-    $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+    $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
         . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
         . '<li class="active">' . $this->transEsc('ill_request_place_text') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/save.phtml b/themes/bootstrap3/templates/record/save.phtml
index 41aeab736e4..219bb6a40d5 100644
--- a/themes/bootstrap3/templates/record/save.phtml
+++ b/themes/bootstrap3/templates/record/save.phtml
@@ -3,7 +3,7 @@
   $this->headTitle($this->translate('Save'));
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
     . '<li class="active">' . $this->transEsc('Save') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/sms.phtml b/themes/bootstrap3/templates/record/sms.phtml
index 00c2b9a2b90..dd75d556091 100644
--- a/themes/bootstrap3/templates/record/sms.phtml
+++ b/themes/bootstrap3/templates/record/sms.phtml
@@ -4,7 +4,7 @@
   echo $this->inlineScript(\Zend\View\Helper\HeadScript::FILE, 'vendor/libphonenumber.js', 'SET');
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ')
     . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> '
     . '<li class="active">' . $this->transEsc('Text this') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/storageretrievalrequest.phtml b/themes/bootstrap3/templates/record/storageretrievalrequest.phtml
index ee652a58e3a..de0866c5e66 100644
--- a/themes/bootstrap3/templates/record/storageretrievalrequest.phtml
+++ b/themes/bootstrap3/templates/record/storageretrievalrequest.phtml
@@ -3,7 +3,7 @@
     $this->headTitle($this->translate('storage_retrieval_request_place_text') . ': ' . $this->driver->getBreadcrumb());
 
     // Set up breadcrumbs:
-    $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li>')
+    $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li>')
         . '<li>' . $this->recordLink()->getBreadcrumb($this->driver) . '</li>'
         . '<li class="active">' . $this->transEsc('storage_retrieval_request_place_text') . '</li>';
 ?>
diff --git a/themes/bootstrap3/templates/record/view.phtml b/themes/bootstrap3/templates/record/view.phtml
index becd7fa5cdb..116f7300620 100644
--- a/themes/bootstrap3/templates/record/view.phtml
+++ b/themes/bootstrap3/templates/record/view.phtml
@@ -13,7 +13,7 @@
   }
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ') .
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ') .
     '<li class="active">' . $this->recordLink()->getBreadcrumb($this->driver) . '</li> ';
   $this->layout()->title = $this->driver->getShortTitle();
 ?>
diff --git a/themes/bootstrap3/templates/search/advanced/layout.phtml b/themes/bootstrap3/templates/search/advanced/layout.phtml
index 40e1c90c3ae..70e01f6bd1e 100644
--- a/themes/bootstrap3/templates/search/advanced/layout.phtml
+++ b/themes/bootstrap3/templates/search/advanced/layout.phtml
@@ -7,7 +7,7 @@
 
   // Set up breadcrumbs:
   $this->layout()->breadcrumbs = '<li>';
-  $lastSearchLink = $this->getLastSearchLink($this->transEsc('Search'));
+  $lastSearchLink = $this->searchMemory()->getLastSearchLink($this->transEsc('Search'));
   $this->layout()->breadcrumbs .= !empty($lastSearchLink)
     ? $lastSearchLink : $this->transEsc('Search');
   $this->layout()->breadcrumbs .= '</li> <li class="active">' . $this->transEsc('Advanced') . '</li>';
@@ -71,7 +71,10 @@
     <? endforeach; ?>
     <div class="row">
       <div class="<?=$this->layoutClass('mainbody')?>">
-        <input type="hidden" name="sort" value="relevance">
+        <? $lastSort = $this->searchMemory()->getLastSort($this->searchClassId); ?>
+        <? if (!empty($lastSort)): ?>
+          <input type="hidden" name="sort" value="<?=$this->escapeHtmlAttr($lastSort)?>" />
+        <? endif; ?>
         <div class="clearfix">
           <h2 class="pull-left flip"><?=$this->transEsc('Advanced Search')?></h2>
           <div id="groupJoin" class="form-inline pull-right flip">
diff --git a/themes/bootstrap3/templates/search/advanced/limit.phtml b/themes/bootstrap3/templates/search/advanced/limit.phtml
index fb9859a5af7..aaef6afe6e4 100644
--- a/themes/bootstrap3/templates/search/advanced/limit.phtml
+++ b/themes/bootstrap3/templates/search/advanced/limit.phtml
@@ -3,7 +3,7 @@
     $limitList = $this->options->getLimitOptions();
 
     // If a previous limit was used, make that the default; otherwise, use the "default default"
-    $lastLimit = $this->options->getLastLimit();
+    $lastLimit = $this->searchMemory()->getLastLimit($this->options->getSearchClassId());
     $defaultLimit = empty($lastLimit) ? $this->options->getDefaultLimit() : $lastLimit;
 ?>
 <? if (count($limitList) > 1): ?>
diff --git a/themes/bootstrap3/templates/search/email.phtml b/themes/bootstrap3/templates/search/email.phtml
index 5f4b5b19497..beabce01568 100644
--- a/themes/bootstrap3/templates/search/email.phtml
+++ b/themes/bootstrap3/templates/search/email.phtml
@@ -3,7 +3,7 @@
   $this->headTitle($this->translate('Email this Search'));
 
   // Set up breadcrumbs:
-  $this->layout()->breadcrumbs = '<li>' . $this->getLastSearchLink($this->transEsc('Search'), '', '</li> ') .
+  $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), '', '</li> ') .
     '<li class="active">' . $this->transEsc('Email this Search') . '</li>';
 ?>
 <?=$this->flashmessages()?>
diff --git a/themes/bootstrap3/templates/search/searchbox.phtml b/themes/bootstrap3/templates/search/searchbox.phtml
index 79e44829b77..47a53e3dae3 100644
--- a/themes/bootstrap3/templates/search/searchbox.phtml
+++ b/themes/bootstrap3/templates/search/searchbox.phtml
@@ -14,12 +14,12 @@
     $basicSearch = $this->searchbox()->combinedHandlersActive() ? 'combined-searchbox' : $options->getSearchAction();
     $searchHome = $options->getSearchHomeAction();
     $advSearch = $options->getAdvancedSearchAction();
-    $lastSort = $options->getLastSort();
-    $lastLimit = $options->getLastLimit();
+    $lastSort = $this->searchMemory()->getLastSort($this->searchClassId);
+    $lastLimit = $this->searchMemory()->getLastLimit($this->searchClassId);
     $ignoreHiddenFilterMemory = isset($this->ignoreHiddenFilterMemory) && $this->ignoreHiddenFilterMemory;
     $hiddenFilters = $this->searchtabs()->getHiddenFilters($this->searchClassId, $ignoreHiddenFilterMemory);
     if (empty($hiddenFilters) && !$ignoreHiddenFilterMemory) {
-        $hiddenFilters = $this->searchParams($this->searchClassId)->getLastHiddenFilters();
+        $hiddenFilters = $this->searchMemory()->getLastHiddenFilters($this->searchClassId);
     }
     $hiddenFilterParams = [];
     foreach ($hiddenFilters as $key => $filter) {
diff --git a/themes/jquerymobile/templates/eds/advanced.phtml b/themes/jquerymobile/templates/eds/advanced.phtml
index 2f16d759f57..68c3c4754ca 100644
--- a/themes/jquerymobile/templates/eds/advanced.phtml
+++ b/themes/jquerymobile/templates/eds/advanced.phtml
@@ -10,8 +10,8 @@
   $basicSearch = $options->getSearchAction();
   $searchHome = $basicSearch;
   $searchHome['action'] = 'Home';
-  $lastSort = $options->getLastSort();
-  $lastLimit = $options->getLastLimit();
+  $lastSort = $this->searchMemory()->getLastSort($options->getSearchClassId());
+  $lastLimit = $this->searchMemory()->getLastLimit($options->getSearchClassId());
 ?>
 <div data-role="page" id="Search-home">
   <?=$this->mobileMenu()->header(array('hideSearchLink' => true))?>
diff --git a/themes/jquerymobile/templates/primo/advanced.phtml b/themes/jquerymobile/templates/primo/advanced.phtml
index 9717bb4740a..006c6fd476d 100644
--- a/themes/jquerymobile/templates/primo/advanced.phtml
+++ b/themes/jquerymobile/templates/primo/advanced.phtml
@@ -8,8 +8,8 @@
   // Load search actions and settings (if any):
   $options = $this->searchOptions($this->searchClassId);
   $basicSearch = $options->getSearchAction();
-  $lastSort = $options->getLastSort();
-  $lastLimit = $options->getLastLimit();
+  $lastSort = $this->searchMemory()->getLastSort($options->getSearchClassId());
+  $lastLimit = $this->searchMemory()->getLastLimit($options->getSearchClassId());
 ?>
 <div data-role="page" id="Search-home">
   <?=$this->mobileMenu()->header(array('hideSearchLink' => true))?>
@@ -42,7 +42,6 @@
         <? endfor; ?>
         </fieldset>
       <? endfor; ?>
-      <? $lastSort = $this->options->getLastSort(); ?>
       <? if (!empty($lastSort)): ?>
         <input type="hidden" name="sort" value="<?=$this->escapeHtmlAttr($lastSort)?>" />
       <? endif; ?>
diff --git a/themes/jquerymobile/templates/search/advanced.phtml b/themes/jquerymobile/templates/search/advanced.phtml
index 30f80df5ece..cb394dec92d 100644
--- a/themes/jquerymobile/templates/search/advanced.phtml
+++ b/themes/jquerymobile/templates/search/advanced.phtml
@@ -10,8 +10,8 @@
   $basicSearch = $options->getSearchAction();
   $searchHome = $basicSearch;
   $searchHome['action'] = 'Home';
-  $lastSort = $options->getLastSort();
-  $lastLimit = $options->getLastLimit();
+  $lastSort = $this->searchMemory()->getLastSort($options->getSearchClassId());
+  $lastLimit = $this->searchMemory()->getLastLimit($options->getSearchClassId());
   if (isset($this->saved) && is_object($this->saved)) {
     $hiddenFilters = $this->saved->getParams()->getHiddenFilters();
   } else {
diff --git a/themes/jquerymobile/templates/search/searchbox.phtml b/themes/jquerymobile/templates/search/searchbox.phtml
index 000af3d404c..df31eca5c68 100644
--- a/themes/jquerymobile/templates/search/searchbox.phtml
+++ b/themes/jquerymobile/templates/search/searchbox.phtml
@@ -12,8 +12,8 @@
   );
   $handlerCount = count($handlers);
   $basicSearch = $this->searchbox()->combinedHandlersActive() ? 'combined-searchbox' : $options->getSearchAction();
-  $lastSort = $options->getLastSort();
-  $lastLimit = $options->getLastLimit();
+  $lastSort = $this->searchMemory()->getLastSort($options->getSearchClassId());
+  $lastLimit = $this->searchMemory()->getLastLimit($options->getSearchClassId());
 ?>
 <form method="get" action="<?=$this->url($basicSearch)?>" data-ajax="false">
   <label class="offscreen" for="searchForm_lookfor">
diff --git a/themes/root/theme.config.php b/themes/root/theme.config.php
index 41041b6f7f4..7822da54141 100644
--- a/themes/root/theme.config.php
+++ b/themes/root/theme.config.php
@@ -16,7 +16,6 @@ return array(
             'feedback' => 'VuFind\View\Helper\Root\Factory::getFeedback',
             'flashmessages' => 'VuFind\View\Helper\Root\Factory::getFlashmessages',
             'googleanalytics' => 'VuFind\View\Helper\Root\Factory::getGoogleAnalytics',
-            'getlastsearchlink' => 'VuFind\View\Helper\Root\Factory::getGetLastSearchLink',
             'helptext' => 'VuFind\View\Helper\Root\Factory::getHelpText',
             'historylabel' => 'VuFind\View\Helper\Root\Factory::getHistoryLabel',
             'ils' => 'VuFind\View\Helper\Root\Factory::getIls',
@@ -31,6 +30,7 @@ return array(
             'related' => 'VuFind\View\Helper\Root\Factory::getRelated',
             'safemoneyformat' => 'VuFind\View\Helper\Root\Factory::getSafeMoneyFormat',
             'searchbox' => 'VuFind\View\Helper\Root\Factory::getSearchBox',
+            'searchmemory' => 'VuFind\View\Helper\Root\Factory::getSearchMemory',
             'searchoptions' => 'VuFind\View\Helper\Root\Factory::getSearchOptions',
             'searchparams' => 'VuFind\View\Helper\Root\Factory::getSearchParams',
             'searchtabs' => 'VuFind\View\Helper\Root\Factory::getSearchTabs',
-- 
GitLab