diff --git a/module/VuFind/src/VuFind/Controller/Plugin/AbstractRequestBase.php b/module/VuFind/src/VuFind/Controller/Plugin/AbstractRequestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d3b289626933873457e85a81cd4cfc74ccfdeb4
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/Plugin/AbstractRequestBase.php
@@ -0,0 +1,299 @@
+<?php
+/**
+ * VuFind Action Helper - Requests Support Methods
+ *
+ * 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  Controller_Plugins
+ * @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\Controller\Plugin;
+use VuFind\ILS\Connection;
+use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
+
+/**
+ * Zend action helper base class to perform request-related actions
+ *
+ * @category VuFind2
+ * @package  Controller_Plugins
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+abstract class AbstractRequestBase extends AbstractPlugin
+{
+    /**
+     * Session data
+     *
+     * @var Container
+     */
+    protected $session;
+
+    /**
+     * HMAC generator
+     *
+     * @var \VuFind\Crypt\HMAC
+     */
+    protected $hmac;
+
+    /**
+     * Constructor
+     *
+     * @param \VuFind\Crypt\HMAC $hmac HMAC generator
+     */
+    public function __construct(\VuFind\Crypt\HMAC $hmac)
+    {
+        $this->hmac = $hmac;
+    }
+
+    /**
+     * Grab the Container object for storing helper-specific session
+     * data.
+     *
+     * @return Container
+     */
+    protected function getSession()
+    {
+        if (!isset($this->session)) {
+            $this->session = new Container(get_class($this) . '_Helper');
+        }
+        return $this->session;
+    }
+
+    /**
+     * Reset the array of valid IDs in the session (used for form submission
+     * validation)
+     *
+     * @return void
+     */
+    public function resetValidation()
+    {
+        $this->getSession()->validIds = array();
+    }
+
+    /**
+     * Add an ID to the validation array.
+     *
+     * @param string $id ID to remember
+     *
+     * @return void
+     */
+    public function rememberValidId($id)
+    {
+        // The session container doesn't allow modification of entries (as of
+        // ZF2beta5 anyway), so we have to do this in a roundabout way.
+        $existingArray = $this->getSession()->validIds;
+        $existingArray[] = $id;
+        $this->getSession()->validIds = $existingArray;
+    }
+
+    /**
+     * Method for validating contents of a request; returns an array of
+     * collected details if request is valid, otherwise returns false.
+     *
+     * @param array $linkData An array of keys to check
+     *
+     * @return boolean|array
+     */
+    public function validateRequest($linkData)
+    {
+        $controller = $this->getController();
+        $params = $controller->params();
+
+        $keyValueArray = array();
+        foreach ($linkData as $details) {
+            $keyValueArray[$details] = $params->fromQuery($details);
+        }
+        $hashKey = $this->hmac->generate($linkData, $keyValueArray);
+
+        if ($params->fromQuery('hashKey') != $hashKey) {
+            return false;
+        }
+
+        // Initialize gatheredDetails with any POST values we find; this will
+        // allow us to repopulate the form with user-entered values if there
+        // is an error.  However, it is important that we load the POST data
+        // FIRST and then override it with GET values in order to ensure that
+        // the user doesn't bypass the hashkey verification by manipulating POST
+        // values.
+        $gatheredDetails = $params->fromPost('gatheredDetails', array());
+
+        // Make sure the bib ID is included, even if it's not loaded as part of
+        // the validation loop below.
+        $gatheredDetails['id'] = $params->fromRoute('id', $params->fromQuery('id'));
+
+        // Get Values Passed from holdings.php
+        $gatheredDetails = array_merge($gatheredDetails, $keyValueArray);
+
+        return $gatheredDetails;
+    }
+
+    /**
+     * Check if the user-provided pickup location is valid.
+     *
+     * @param string $pickup          User-specified pickup location
+     * @param array  $extraHoldFields Hold form fields enabled by
+     * configuration/driver
+     * @param array  $pickUpLibs      Pickup library list from driver
+     *
+     * @return bool
+     */
+    public function validatePickUpInput($pickup, $extraHoldFields, $pickUpLibs)
+    {
+        // Not having to care for pickUpLocation is equivalent to having a valid one.
+        if (!in_array('pickUpLocation', $extraHoldFields)) {
+            return true;
+        }
+
+        // Check the valid pickup locations for a match against user input:
+        return $this->validatePickUpLocation($pickup, $pickUpLibs);
+    }
+
+    /**
+     * Check if the provided pickup location is valid.
+     *
+     * @param string $location   Location to check
+     * @param array  $pickUpLibs Pickup locations list from driver
+     *
+     * @return bool
+     */
+    public function validatePickUpLocation($location, $pickUpLibs)
+    {
+        foreach ($pickUpLibs as $lib) {
+            if ($location == $lib['locationID']) {
+                return true;
+            }
+        }
+
+        // If we got this far, something is wrong!
+         return false;
+    }
+
+    /**
+     * Check if the user-provided request group is valid.
+     *
+     * @param array $gatheredDetails User hold parameters
+     * @param array $extraHoldFields Form fields enabled by configuration/driver
+     * @param array $requestGroups   Request group list from driver
+     *
+     * @return bool
+     */
+    public function validateRequestGroupInput(
+        $gatheredDetails, $extraHoldFields, $requestGroups
+    ) {
+        // Not having to care for requestGroup is equivalent to having a valid one.
+        if (!in_array('requestGroup', $extraHoldFields)) {
+            return true;
+        }
+        if (!isset($gatheredDetails['level'])
+            || $gatheredDetails['level'] !== 'title'
+        ) {
+            return true;
+        }
+
+        // Check the valid pickup locations for a match against user input:
+        return $this->validateRequestGroup(
+            $gatheredDetails['requestGroupId'], $requestGroups
+        );
+    }
+
+    /**
+     * Check if the provided request group is valid.
+     *
+     * @param string $requestGroupId Id of the request group to check
+     * @param array  $requestGroups  Request group list from driver
+     *
+     * @return bool
+     */
+    public function validateRequestGroup($requestGroupId, $requestGroups)
+    {
+        foreach ($requestGroups as $group) {
+            if ($requestGroupId == $group['id']) {
+                return true;
+            }
+        }
+
+        // If we got this far, something is wrong!
+         return false;
+    }
+
+    /**
+     * Getting a default required date based on hold settings.
+     *
+     * @param array      $checkHolds Hold settings returned by the ILS driver's
+     * checkFunction method.
+     * @param Connection $catalog    ILS connection (optional)
+     * @param array      $patron     Patron details (optional)
+     * @param array      $holdInfo   Hold details (optional)
+     *
+     * @return int A timestamp representing the default required date
+     */
+    public function getDefaultRequiredDate($checkHolds, $catalog = null,
+        $patron = null, $holdInfo = null
+    ) {
+        // Load config:
+        $dateArray = isset($checkHolds['defaultRequiredDate'])
+             ? explode(":", $checkHolds['defaultRequiredDate'])
+             : array(0, 1, 0);
+
+        // Process special "driver" prefix and adjust default date
+        // settings accordingly:
+        if ($dateArray[0] == 'driver') {
+            $useDriver = true;
+            array_shift($dateArray);
+            if (count($dateArray) < 3) {
+                $dateArray = array(0, 1, 0);
+            }
+        } else {
+            $useDriver = false;
+        }
+
+        // If the driver setting is active, try it out:
+        if ($useDriver && $catalog
+            && $catalog->checkCapability('getHoldDefaultRequiredDate')
+        ) {
+            $result = $catalog->getHoldDefaultRequiredDate($patron, $holdInfo);
+            if (!empty($result)) {
+                return $result;
+            }
+        }
+
+        // If the driver setting is off or the driver didn't work, use the
+        // standard relative date mechanism:
+        return $this->getDateFromArray($dateArray);
+    }
+
+    /**
+     * Support method for getDefaultRequiredDate() -- generate a date based
+     * on a days/months/years offset array.
+     *
+     * @param array $dateArray 3-element array containing day/month/year offsets
+     *
+     * @return int A timestamp representing the default required date
+     */
+    protected function getDateFromArray($dateArray)
+    {
+        list($d, $m, $y) = $dateArray;
+        return mktime(
+            0, 0, 0, date('m')+$m, date('d')+$d, date('Y')+$y
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/Controller/Plugin/Holds.php b/module/VuFind/src/VuFind/Controller/Plugin/Holds.php
index fc9df415197d54055ff5c4e88402fb320aa83c29..ee7feb10c996c36fa33cbda83ace481b61f06cd3 100644
--- a/module/VuFind/src/VuFind/Controller/Plugin/Holds.php
+++ b/module/VuFind/src/VuFind/Controller/Plugin/Holds.php
@@ -26,8 +26,6 @@
  * @link     http://www.vufind.org  Main Page
  */
 namespace VuFind\Controller\Plugin;
-use VuFind\ILS\Connection;
-use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
 
 /**
  * Zend action helper to perform holds-related actions
@@ -38,73 +36,8 @@ use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://www.vufind.org  Main Page
  */
-class Holds extends AbstractPlugin
+class Holds extends AbstractRequestBase
 {
-    /**
-     * Session data
-     *
-     * @var Container
-     */
-    protected $session;
-
-    /**
-     * HMAC generator
-     *
-     * @var \VuFind\Crypt\HMAC
-     */
-    protected $hmac;
-
-    /**
-     * Constructor
-     *
-     * @param \VuFind\Crypt\HMAC $hmac HMAC generator
-     */
-    public function __construct(\VuFind\Crypt\HMAC $hmac)
-    {
-        $this->hmac = $hmac;
-    }
-
-    /**
-     * Grab the Container object for storing helper-specific session
-     * data.
-     *
-     * @return Container
-     */
-    protected function getSession()
-    {
-        if (!isset($this->session)) {
-            $this->session = new Container('Holds_Helper');
-        }
-        return $this->session;
-    }
-
-    /**
-     * Reset the array of valid IDs in the session (used for form submission
-     * validation)
-     *
-     * @return void
-     */
-    public function resetValidation()
-    {
-        $this->getSession()->validIds = array();
-    }
-
-    /**
-     * Add an ID to the validation array.
-     *
-     * @param string $id ID to remember
-     *
-     * @return void
-     */
-    public function rememberValidId($id)
-    {
-        // The session container doesn't allow modification of entries (as of
-        // ZF2beta5 anyway), so we have to do this in a roundabout way.
-        $existingArray = $this->getSession()->validIds;
-        $existingArray[] = $id;
-        $this->getSession()->validIds = $existingArray;
-    }
-
     /**
      * Update ILS details with cancellation-specific information, if appropriate.
      *
@@ -225,196 +158,4 @@ class Holds extends AbstractPlugin
         }
         return array();
     }
-
-    /**
-     * Method for validating contents of a request; returns an array of
-     * collected details if request is valid, otherwise returns false.
-     *
-     * @param array $linkData An array of keys to check
-     *
-     * @return boolean|array
-     */
-    public function validateRequest($linkData)
-    {
-        $controller = $this->getController();
-        $params = $controller->params();
-
-        $keyValueArray = array();
-        foreach ($linkData as $details) {
-            $keyValueArray[$details] = $params->fromQuery($details);
-        }
-        $hashKey = $this->hmac->generate($linkData, $keyValueArray);
-
-        if ($params->fromQuery('hashKey') != $hashKey) {
-            return false;
-        }
-
-        // Initialize gatheredDetails with any POST values we find; this will
-        // allow us to repopulate the form with user-entered values if there
-        // is an error.  However, it is important that we load the POST data
-        // FIRST and then override it with GET values in order to ensure that
-        // the user doesn't bypass the hashkey verification by manipulating POST
-        // values.
-        $gatheredDetails = $params->fromPost('gatheredDetails', array());
-
-        // Make sure the bib ID is included, even if it's not loaded as part of
-        // the validation loop below.
-        $gatheredDetails['id'] = $params->fromRoute('id', $params->fromQuery('id'));
-
-        // Get Values Passed from holdings.php
-        $gatheredDetails = array_merge($gatheredDetails, $keyValueArray);
-
-        return $gatheredDetails;
-    }
-
-    /**
-     * Check if the user-provided pickup location is valid.
-     *
-     * @param string $pickup          User-specified pickup location
-     * @param array  $extraHoldFields Hold form fields enabled by
-     * configuration/driver
-     * @param array  $pickUpLibs      Pickup library list from driver
-     *
-     * @return bool
-     */
-    public function validatePickUpInput($pickup, $extraHoldFields, $pickUpLibs)
-    {
-        // Not having to care for pickUpLocation is equivalent to having a valid one.
-        if (!in_array('pickUpLocation', $extraHoldFields)) {
-            return true;
-        }
-
-        // Check the valid pickup locations for a match against user input:
-        return $this->validatePickUpLocation($pickup, $pickUpLibs);
-    }
-
-    /**
-     * Check if the provided pickup location is valid.
-     *
-     * @param string $location   Location to check
-     * @param array  $pickUpLibs Pickup locations list from driver
-     *
-     * @return bool
-     */
-    public function validatePickUpLocation($location, $pickUpLibs)
-    {
-        foreach ($pickUpLibs as $lib) {
-            if ($location == $lib['locationID']) {
-                return true;
-            }
-        }
-
-        // If we got this far, something is wrong!
-         return false;
-    }
-
-    /**
-     * Check if the user-provided request group is valid.
-     *
-     * @param array $gatheredDetails User hold parameters
-     * @param array $extraHoldFields Form fields enabled by configuration/driver
-     * @param array $requestGroups   Request group list from driver
-     *
-     * @return bool
-     */
-    public function validateRequestGroupInput(
-        $gatheredDetails, $extraHoldFields, $requestGroups
-    ) {
-        // Not having to care for requestGroup is equivalent to having a valid one.
-        if (!in_array('requestGroup', $extraHoldFields)) {
-            return true;
-        }
-        if (!isset($gatheredDetails['level'])
-            || $gatheredDetails['level'] !== 'title'
-        ) {
-            return true;
-        }
-
-        // Check the valid pickup locations for a match against user input:
-        return $this->validateRequestGroup(
-            $gatheredDetails['requestGroupId'], $requestGroups
-        );
-    }
-
-    /**
-     * Check if the provided request group is valid.
-     *
-     * @param string $requestGroupId Id of the request group to check
-     * @param array  $requestGroups  Request group list from driver
-     *
-     * @return bool
-     */
-    public function validateRequestGroup($requestGroupId, $requestGroups)
-    {
-        foreach ($requestGroups as $group) {
-            if ($requestGroupId == $group['id']) {
-                return true;
-            }
-        }
-
-        // If we got this far, something is wrong!
-         return false;
-    }
-
-    /**
-     * Getting a default required date based on hold settings.
-     *
-     * @param array      $checkHolds Hold settings returned by the ILS driver's
-     * checkFunction method.
-     * @param Connection $catalog    ILS connection (optional)
-     * @param array      $patron     Patron details (optional)
-     * @param array      $holdInfo   Hold details (optional)
-     *
-     * @return int A timestamp representing the default required date
-     */
-    public function getDefaultRequiredDate($checkHolds, $catalog = null,
-        $patron = null, $holdInfo = null
-    ) {
-        // Load config:
-        $dateArray = isset($checkHolds['defaultRequiredDate'])
-             ? explode(":", $checkHolds['defaultRequiredDate'])
-             : array(0, 1, 0);
-
-        // Process special "driver" prefix and adjust default date
-        // settings accordingly:
-        if ($dateArray[0] == 'driver') {
-            $useDriver = true;
-            array_shift($dateArray);
-            if (count($dateArray) < 3) {
-                $dateArray = array(0, 1, 0);
-            }
-        } else {
-            $useDriver = false;
-        }
-
-        // If the driver setting is active, try it out:
-        if ($useDriver && $catalog
-            && $catalog->checkCapability('getHoldDefaultRequiredDate')
-        ) {
-            $result = $catalog->getHoldDefaultRequiredDate($patron, $holdInfo);
-            if (!empty($result)) {
-                return $result;
-            }
-        }
-
-        // If the driver setting is off or the driver didn't work, use the
-        // standard relative date mechanism:
-        return $this->getDateFromArray($dateArray);
-    }
-
-    /**
-     * Support method for getDefaultRequiredDate() -- generate a date based
-     * on a days/months/years offset array.
-     *
-     * @param array $dateArray 3-element array containing day/month/year offsets
-     *
-     * @return int A timestamp representing the default required date
-     */
-    protected function getDateFromArray($dateArray)
-    {
-        list($d, $m, $y) = $dateArray;
-        return mktime(
-            0, 0, 0, date('m')+$m, date('d')+$d, date('Y')+$y
-        );
-    }
 }
diff --git a/module/VuFind/src/VuFind/Controller/Plugin/ILLRequests.php b/module/VuFind/src/VuFind/Controller/Plugin/ILLRequests.php
index 2754027fb89357b108bcb29160f5ffb1dae0fb2b..4924cfbf655fdc780a1b26a3594a61d7eb56bdf5 100644
--- a/module/VuFind/src/VuFind/Controller/Plugin/ILLRequests.php
+++ b/module/VuFind/src/VuFind/Controller/Plugin/ILLRequests.php
@@ -28,7 +28,6 @@
  * @link     http://www.vufind.org  Main Page
  */
 namespace VuFind\Controller\Plugin;
-use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
 
 /**
  * Zend action helper to perform ILL request related actions
@@ -40,22 +39,8 @@ use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://www.vufind.org  Main Page
  */
-class ILLRequests extends Holds
+class ILLRequests extends AbstractRequestBase
 {
-    /**
-     * Grab the Container object for storing helper-specific session
-     * data.
-     *
-     * @return Container
-     */
-    protected function getSession()
-    {
-        if (!isset($this->session)) {
-            $this->session = new Container('ILLRequests_Helper');
-        }
-        return $this->session;
-    }
-
     /**
      * Update ILS details with cancellation-specific information, if appropriate.
      *
@@ -63,7 +48,7 @@ class ILLRequests extends Holds
      * @param array                  $ilsDetails   Details from ILS driver's
      * getMyILLRequests() method
      * @param array                  $cancelStatus Cancellation settings from ILS
-     * @param array                  $patron       ILS patron 
+     * @param array                  $patron       ILS patron
      * driver's checkFunction() method
      *
      * @return array $ilsDetails with cancellation info added
@@ -153,7 +138,7 @@ class ILLRequests extends Holds
                     );
                 }
             }
-            
+
             foreach ($details as $info) {
                 // If the user input contains a value not found in the session
                 // whitelist, something has been tampered with -- abort the process.
diff --git a/module/VuFind/src/VuFind/Controller/Plugin/StorageRetrievalRequests.php b/module/VuFind/src/VuFind/Controller/Plugin/StorageRetrievalRequests.php
index 0e9125362113d41820b45b1367df2fb506405e4d..36a409d6735ce74a8d7634873124314049c07fa9 100644
--- a/module/VuFind/src/VuFind/Controller/Plugin/StorageRetrievalRequests.php
+++ b/module/VuFind/src/VuFind/Controller/Plugin/StorageRetrievalRequests.php
@@ -28,7 +28,6 @@
  * @link     http://www.vufind.org  Main Page
  */
 namespace VuFind\Controller\Plugin;
-use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
 
 /**
  * Zend action helper to perform storage retrieval request related actions
@@ -40,22 +39,8 @@ use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://www.vufind.org  Main Page
  */
-class StorageRetrievalRequests extends Holds
+class StorageRetrievalRequests extends AbstractRequestBase
 {
-    /**
-     * Grab the Container object for storing helper-specific session
-     * data.
-     *
-     * @return Container
-     */
-    protected function getSession()
-    {
-        if (!isset($this->session)) {
-            $this->session = new Container('StorageRetrievalRequests_Helper');
-        }
-        return $this->session;
-    }
-
     /**
      * Update ILS details with cancellation-specific information, if appropriate.
      *
@@ -63,7 +48,7 @@ class StorageRetrievalRequests extends Holds
      * @param array                  $ilsDetails   Details from ILS driver's
      * getMyStorageRetrievalRequests() method
      * @param array                  $cancelStatus Cancellation settings from ILS
-     * @param array                  $patron       ILS patron 
+     * @param array                  $patron       ILS patron
      * driver's checkFunction() method
      *
      * @return array $ilsDetails with cancellation info added
@@ -153,7 +138,7 @@ class StorageRetrievalRequests extends Holds
                     );
                 }
             }
-            
+
             foreach ($details as $info) {
                 // If the user input contains a value not found in the session
                 // whitelist, something has been tampered with -- abort the process.