diff --git a/module/VuFind/src/VuFind/Controller/HoldsTrait.php b/module/VuFind/src/VuFind/Controller/HoldsTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ce2c62f7ec8d5309efbf3621238a6ebc6235507
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/HoldsTrait.php
@@ -0,0 +1,197 @@
+<?php
+/**
+ * Holds trait (for subclasses of AbstractRecord)
+ *
+ * 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
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\Controller;
+
+/**
+ * Holds trait (for subclasses of AbstractRecord)
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+trait HoldsTrait
+{
+    /**
+     * Action for dealing with blocked holds.
+     *
+     * @return mixed
+     */
+    public function blockedholdAction()
+    {
+        $this->flashMessenger()->setNamespace('error')
+            ->addMessage('hold_error_blocked');
+        return $this->redirectToRecord('#top');
+    }
+
+    /**
+     * Action for dealing with holds.
+     *
+     * @return mixed
+     */
+    public function holdAction()
+    {
+        $driver = $this->loadRecord();
+
+        // Stop now if the user does not have valid catalog credentials available:
+        if (!is_array($patron = $this->catalogLogin())) {
+            return $patron;
+        }
+
+        // If we're not supposed to be here, give up now!
+        $catalog = $this->getILS();
+        $checkHolds = $catalog->checkFunction(
+            'Holds',
+            [
+                'id' => $driver->getUniqueID(),
+                'patron' => $patron
+            ]
+        );
+        if (!$checkHolds) {
+            return $this->redirectToRecord();
+        }
+
+        // Do we have valid information?
+        // Sets $this->logonURL and $this->gatheredDetails
+        $gatheredDetails = $this->holds()->validateRequest($checkHolds['HMACKeys']);
+        if (!$gatheredDetails) {
+            return $this->redirectToRecord();
+        }
+
+        // Block invalid requests:
+        if (!$catalog->checkRequestIsValid(
+            $driver->getUniqueID(), $gatheredDetails, $patron
+        )) {
+            return $this->blockedholdAction();
+        }
+
+        // Send various values to the view so we can build the form:
+        $pickup = $catalog->getPickUpLocations($patron, $gatheredDetails);
+        $requestGroups = $catalog->checkCapability(
+            'getRequestGroups', [$driver->getUniqueID(), $patron]
+        ) ? $catalog->getRequestGroups($driver->getUniqueID(), $patron) : [];
+        $extraHoldFields = isset($checkHolds['extraHoldFields'])
+            ? explode(":", $checkHolds['extraHoldFields']) : [];
+
+        // Process form submissions if necessary:
+        if (!is_null($this->params()->fromPost('placeHold'))) {
+            // If the form contained a pickup location or request group, make sure
+            // they are valid:
+            $valid = $this->holds()->validateRequestGroupInput(
+                $gatheredDetails, $extraHoldFields, $requestGroups
+            );
+            if (!$valid) {
+                $this->flashMessenger()->setNamespace('error')
+                    ->addMessage('hold_invalid_request_group');
+            } elseif (!$this->holds()->validatePickUpInput(
+                $gatheredDetails['pickUpLocation'], $extraHoldFields, $pickup
+            )) {
+                $this->flashMessenger()->setNamespace('error')
+                    ->addMessage('hold_invalid_pickup');
+            } else {
+                // If we made it this far, we're ready to place the hold;
+                // if successful, we will redirect and can stop here.
+
+                // Add Patron Data to Submitted Data
+                $holdDetails = $gatheredDetails + ['patron' => $patron];
+
+                // Attempt to place the hold:
+                $function = (string)$checkHolds['function'];
+                $results = $catalog->$function($holdDetails);
+
+                // Success: Go to Display Holds
+                if (isset($results['success']) && $results['success'] == true) {
+                    $msg = [
+                        'html' => true,
+                        'msg' => 'hold_place_success_html',
+                        'tokens' => [
+                            '%%url%%' => $this->url()->fromRoute('myresearch-holds')
+                        ],
+                    ];
+                    $this->flashMessenger()->setNamespace('info')->addMessage($msg);
+                    return $this->redirectToRecord('#top');
+                } else {
+                    // Failure: use flash messenger to display messages, stay on
+                    // the current form.
+                    if (isset($results['status'])) {
+                        $this->flashMessenger()->setNamespace('error')
+                            ->addMessage($results['status']);
+                    }
+                    if (isset($results['sysMessage'])) {
+                        $this->flashMessenger()->setNamespace('error')
+                            ->addMessage($results['sysMessage']);
+                    }
+                }
+            }
+        }
+
+        // Find and format the default required date:
+        $defaultRequired = $this->holds()->getDefaultRequiredDate(
+            $checkHolds, $catalog, $patron, $gatheredDetails
+        );
+        $defaultRequired = $this->getServiceLocator()->get('VuFind\DateConverter')
+            ->convertToDisplayDate("U", $defaultRequired);
+        try {
+            $defaultPickup
+                = $catalog->getDefaultPickUpLocation($patron, $gatheredDetails);
+        } catch (\Exception $e) {
+            $defaultPickup = false;
+        }
+        try {
+            $defaultRequestGroup = empty($requestGroups)
+                ? false
+                : $catalog->getDefaultRequestGroup($patron, $gatheredDetails);
+        } catch (\Exception $e) {
+            $defaultRequestGroup = false;
+        }
+
+        $requestGroupNeeded = in_array('requestGroup', $extraHoldFields)
+            && !empty($requestGroups)
+            && (empty($gatheredDetails['level'])
+                || $gatheredDetails['level'] != 'copy');
+
+        $view = $this->createViewModel(
+            [
+                'gatheredDetails' => $gatheredDetails,
+                'pickup' => $pickup,
+                'defaultPickup' => $defaultPickup,
+                'homeLibrary' => $this->getUser()->home_library,
+                'extraHoldFields' => $extraHoldFields,
+                'defaultRequiredDate' => $defaultRequired,
+                'requestGroups' => $requestGroups,
+                'defaultRequestGroup' => $defaultRequestGroup,
+                'requestGroupNeeded' => $requestGroupNeeded,
+                'helpText' => isset($checkHolds['helpText'])
+                    ? $checkHolds['helpText'] : null
+            ]
+        );
+        $view->setTemplate('record/hold');
+        return $view;
+    }
+}
diff --git a/module/VuFind/src/VuFind/Controller/ILLRequestsTrait.php b/module/VuFind/src/VuFind/Controller/ILLRequestsTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..6dc78de9d9ba5a3e79d19f71fa1e55985134b302
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/ILLRequestsTrait.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ * ILL trait (for subclasses of AbstractRecord)
+ *
+ * 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
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\Controller;
+
+/**
+ * ILL trait (for subclasses of AbstractRecord)
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+trait ILLRequestsTrait
+{
+    /**
+     * Action for dealing with blocked ILL requests.
+     *
+     * @return mixed
+     */
+    public function blockedILLRequestAction()
+    {
+        $this->flashMessenger()->setNamespace('error')
+            ->addMessage('ill_request_error_blocked');
+        return $this->redirectToRecord('#top');
+    }
+
+    /**
+     * Action for dealing with ILL requests.
+     *
+     * @return mixed
+     */
+    public function illRequestAction()
+    {
+        $driver = $this->loadRecord();
+
+        // Stop now if the user does not have valid catalog credentials available:
+        if (!is_array($patron = $this->catalogLogin())) {
+            return $patron;
+        }
+
+        // If we're not supposed to be here, give up now!
+        $catalog = $this->getILS();
+        $checkRequests = $catalog->checkFunction(
+            'ILLRequests',
+            [
+                'id' => $driver->getUniqueID(),
+                'patron' => $patron
+            ]
+        );
+        if (!$checkRequests) {
+            return $this->redirectToRecord();
+        }
+
+        // Do we have valid information?
+        // Sets $this->logonURL and $this->gatheredDetails
+        $gatheredDetails = $this->ILLRequests()->validateRequest(
+            $checkRequests['HMACKeys']
+        );
+        if (!$gatheredDetails) {
+            return $this->redirectToRecord();
+        }
+
+        // Block invalid requests:
+        if (!$catalog->checkILLRequestIsValid(
+            $driver->getUniqueID(), $gatheredDetails, $patron
+        )) {
+            return $this->blockedILLRequestAction();
+        }
+
+        // Send various values to the view so we can build the form:
+
+        $extraFields = isset($checkRequests['extraFields'])
+            ? explode(":", $checkRequests['extraFields']) : [];
+
+        // Process form submissions if necessary:
+        if (!is_null($this->params()->fromPost('placeILLRequest'))) {
+            // If we made it this far, we're ready to place the hold;
+            // if successful, we will redirect and can stop here.
+
+            // Add Patron Data to Submitted Data
+            $details = $gatheredDetails + ['patron' => $patron];
+
+            // Attempt to place the hold:
+            $function = (string)$checkRequests['function'];
+            $results = $catalog->$function($details);
+
+            // Success: Go to Display ILL Requests
+            if (isset($results['success']) && $results['success'] == true) {
+                $this->flashMessenger()->setNamespace('info')
+                    ->addMessage('ill_request_place_success');
+                if ($this->inLightbox()) {
+                    return false;
+                }
+                return $this->redirect()->toRoute(
+                    'myresearch-illrequests'
+                );
+            } else {
+                // Failure: use flash messenger to display messages, stay on
+                // the current form.
+                if (isset($results['status'])) {
+                    $this->flashMessenger()->setNamespace('error')
+                        ->addMessage($results['status']);
+                }
+                if (isset($results['sysMessage'])) {
+                    $this->flashMessenger()->setNamespace('error')
+                        ->addMessage($results['sysMessage']);
+                }
+            }
+        }
+
+        // Find and format the default required date:
+        $defaultRequired = $this->ILLRequests()
+            ->getDefaultRequiredDate($checkRequests);
+        $defaultRequired = $this->getServiceLocator()->get('VuFind\DateConverter')
+            ->convertToDisplayDate("U", $defaultRequired);
+
+        // Get pickup libraries
+        $pickupLibraries = $catalog->getILLPickUpLibraries(
+            $driver->getUniqueID(), $patron, $gatheredDetails
+        );
+
+        // Get pickup locations. Note that these are independent of pickup library,
+        // and library specific locations must be retrieved when a library is
+        // selected.
+        $pickupLocations = $catalog->getPickUpLocations($patron, $gatheredDetails);
+
+        $view = $this->createViewModel(
+            [
+                'gatheredDetails' => $gatheredDetails,
+                'pickupLibraries' => $pickupLibraries,
+                'pickupLocations' => $pickupLocations,
+                'homeLibrary' => $this->getUser()->home_library,
+                'extraFields' => $extraFields,
+                'defaultRequiredDate' => $defaultRequired,
+                'helpText' => isset($checkRequests['helpText'])
+                    ? $checkRequests['helpText'] : null
+            ]
+        );
+        $view->setTemplate('record/illrequest');
+        return $view;
+    }
+}
diff --git a/module/VuFind/src/VuFind/Controller/RecordController.php b/module/VuFind/src/VuFind/Controller/RecordController.php
index 4af5c40ae2af5946b2803b7d63aea5ba79f6fc20..e94dca724724bee2cb2ec35ee3095c669e991597 100644
--- a/module/VuFind/src/VuFind/Controller/RecordController.php
+++ b/module/VuFind/src/VuFind/Controller/RecordController.php
@@ -38,6 +38,10 @@ namespace VuFind\Controller;
  */
 class RecordController extends AbstractRecord
 {
+    use HoldsTrait;
+    use ILLRequestsTrait;
+    use StorageRetrievalRequestsTrait;
+
     /**
      * Constructor
      *
@@ -53,409 +57,6 @@ class RecordController extends AbstractRecord
             ? $config->Site->defaultRecordTab : 'Holdings';
     }
 
-    /**
-     * Action for dealing with blocked holds.
-     *
-     * @return mixed
-     */
-    public function blockedholdAction()
-    {
-        $this->flashMessenger()->setNamespace('error')
-            ->addMessage('hold_error_blocked');
-        return $this->redirectToRecord('#top');
-    }
-
-    /**
-     * Action for dealing with blocked storage retrieval requests.
-     *
-     * @return mixed
-     */
-    public function blockedStorageRetrievalRequestAction()
-    {
-        $this->flashMessenger()->setNamespace('error')
-            ->addMessage('storage_retrieval_request_error_blocked');
-        return $this->redirectToRecord('#top');
-    }
-
-    /**
-     * Action for dealing with blocked ILL requests.
-     *
-     * @return mixed
-     */
-    public function blockedILLRequestAction()
-    {
-        $this->flashMessenger()->setNamespace('error')
-            ->addMessage('ill_request_error_blocked');
-        return $this->redirectToRecord('#top');
-    }
-
-    /**
-     * Action for dealing with holds.
-     *
-     * @return mixed
-     */
-    public function holdAction()
-    {
-        $driver = $this->loadRecord();
-
-        // Stop now if the user does not have valid catalog credentials available:
-        if (!is_array($patron = $this->catalogLogin())) {
-            return $patron;
-        }
-
-        // If we're not supposed to be here, give up now!
-        $catalog = $this->getILS();
-        $checkHolds = $catalog->checkFunction(
-            'Holds',
-            [
-                'id' => $driver->getUniqueID(),
-                'patron' => $patron
-            ]
-        );
-        if (!$checkHolds) {
-            return $this->forwardTo('Record', 'Home');
-        }
-
-        // Do we have valid information?
-        // Sets $this->logonURL and $this->gatheredDetails
-        $gatheredDetails = $this->holds()->validateRequest($checkHolds['HMACKeys']);
-        if (!$gatheredDetails) {
-            return $this->redirectToRecord();
-        }
-
-        // Block invalid requests:
-        if (!$catalog->checkRequestIsValid(
-            $driver->getUniqueID(), $gatheredDetails, $patron
-        )) {
-            return $this->blockedholdAction();
-        }
-
-        // Send various values to the view so we can build the form:
-        $pickup = $catalog->getPickUpLocations($patron, $gatheredDetails);
-        $requestGroups = $catalog->checkCapability(
-            'getRequestGroups', [$driver->getUniqueID(), $patron]
-        ) ? $catalog->getRequestGroups($driver->getUniqueID(), $patron) : [];
-        $extraHoldFields = isset($checkHolds['extraHoldFields'])
-            ? explode(":", $checkHolds['extraHoldFields']) : [];
-
-        // Process form submissions if necessary:
-        if (!is_null($this->params()->fromPost('placeHold'))) {
-            // If the form contained a pickup location or request group, make sure
-            // they are valid:
-            $valid = $this->holds()->validateRequestGroupInput(
-                $gatheredDetails, $extraHoldFields, $requestGroups
-            );
-            if (!$valid) {
-                $this->flashMessenger()->setNamespace('error')
-                    ->addMessage('hold_invalid_request_group');
-            } elseif (!$this->holds()->validatePickUpInput(
-                $gatheredDetails['pickUpLocation'], $extraHoldFields, $pickup
-            )) {
-                $this->flashMessenger()->setNamespace('error')
-                    ->addMessage('hold_invalid_pickup');
-            } else {
-                // If we made it this far, we're ready to place the hold;
-                // if successful, we will redirect and can stop here.
-
-                // Add Patron Data to Submitted Data
-                $holdDetails = $gatheredDetails + ['patron' => $patron];
-
-                // Attempt to place the hold:
-                $function = (string)$checkHolds['function'];
-                $results = $catalog->$function($holdDetails);
-
-                // Success: Go to Display Holds
-                if (isset($results['success']) && $results['success'] == true) {
-                    $msg = [
-                        'html' => true,
-                        'msg' => 'hold_place_success_html',
-                        'tokens' => [
-                            '%%url%%' => $this->url()->fromRoute('myresearch-holds')
-                        ],
-                    ];
-                    $this->flashMessenger()->setNamespace('info')->addMessage($msg);
-                    return $this->redirectToRecord('#top');
-                } else {
-                    // Failure: use flash messenger to display messages, stay on
-                    // the current form.
-                    if (isset($results['status'])) {
-                        $this->flashMessenger()->setNamespace('error')
-                            ->addMessage($results['status']);
-                    }
-                    if (isset($results['sysMessage'])) {
-                        $this->flashMessenger()->setNamespace('error')
-                            ->addMessage($results['sysMessage']);
-                    }
-                }
-            }
-        }
-
-        // Find and format the default required date:
-        $defaultRequired = $this->holds()->getDefaultRequiredDate(
-            $checkHolds, $catalog, $patron, $gatheredDetails
-        );
-        $defaultRequired = $this->getServiceLocator()->get('VuFind\DateConverter')
-            ->convertToDisplayDate("U", $defaultRequired);
-        try {
-            $defaultPickup
-                = $catalog->getDefaultPickUpLocation($patron, $gatheredDetails);
-        } catch (\Exception $e) {
-            $defaultPickup = false;
-        }
-        try {
-            $defaultRequestGroup = empty($requestGroups)
-                ? false
-                : $catalog->getDefaultRequestGroup($patron, $gatheredDetails);
-        } catch (\Exception $e) {
-            $defaultRequestGroup = false;
-        }
-
-        $requestGroupNeeded = in_array('requestGroup', $extraHoldFields)
-            && !empty($requestGroups)
-            && (empty($gatheredDetails['level'])
-                || $gatheredDetails['level'] != 'copy');
-
-        return $this->createViewModel(
-            [
-                'gatheredDetails' => $gatheredDetails,
-                'pickup' => $pickup,
-                'defaultPickup' => $defaultPickup,
-                'homeLibrary' => $this->getUser()->home_library,
-                'extraHoldFields' => $extraHoldFields,
-                'defaultRequiredDate' => $defaultRequired,
-                'requestGroups' => $requestGroups,
-                'defaultRequestGroup' => $defaultRequestGroup,
-                'requestGroupNeeded' => $requestGroupNeeded,
-                'helpText' => isset($checkHolds['helpText'])
-                    ? $checkHolds['helpText'] : null
-            ]
-        );
-    }
-
-    /**
-     * Action for dealing with storage retrieval requests.
-     *
-     * @return mixed
-     */
-    public function storageRetrievalRequestAction()
-    {
-        $driver = $this->loadRecord();
-
-        // Stop now if the user does not have valid catalog credentials available:
-        if (!is_array($patron = $this->catalogLogin())) {
-            return $patron;
-        }
-
-        // If we're not supposed to be here, give up now!
-        $catalog = $this->getILS();
-        $checkRequests = $catalog->checkFunction(
-            'StorageRetrievalRequests',
-            [
-                'id' => $driver->getUniqueID(),
-                'patron' => $patron
-            ]
-        );
-        if (!$checkRequests) {
-            return $this->forwardTo('Record', 'Home');
-        }
-
-        // Do we have valid information?
-        // Sets $this->logonURL and $this->gatheredDetails
-        $gatheredDetails = $this->storageRetrievalRequests()->validateRequest(
-            $checkRequests['HMACKeys']
-        );
-        if (!$gatheredDetails) {
-            return $this->redirectToRecord();
-        }
-
-        // Block invalid requests:
-        if (!$catalog->checkStorageRetrievalRequestIsValid(
-            $driver->getUniqueID(), $gatheredDetails, $patron
-        )) {
-            return $this->blockedStorageRetrievalRequestAction();
-        }
-
-        // Send various values to the view so we can build the form:
-        $pickup = $catalog->getPickUpLocations($patron, $gatheredDetails);
-        $extraFields = isset($checkRequests['extraFields'])
-            ? explode(":", $checkRequests['extraFields']) : [];
-
-        // Process form submissions if necessary:
-        if (!is_null($this->params()->fromPost('placeStorageRetrievalRequest'))) {
-            // If we made it this far, we're ready to place the hold;
-            // if successful, we will redirect and can stop here.
-
-            // Add Patron Data to Submitted Data
-            $details = $gatheredDetails + ['patron' => $patron];
-
-            // Attempt to place the hold:
-            $function = (string)$checkRequests['function'];
-            $results = $catalog->$function($details);
-
-            // Success: Go to Display Storage Retrieval Requests
-            if (isset($results['success']) && $results['success'] == true) {
-                $this->flashMessenger()->setNamespace('info')
-                    ->addMessage('storage_retrieval_request_place_success');
-                if ($this->inLightbox()) {
-                    return false;
-                }
-                return $this->redirect()->toRoute(
-                    'myresearch-storageretrievalrequests'
-                );
-            } else {
-                // Failure: use flash messenger to display messages, stay on
-                // the current form.
-                if (isset($results['status'])) {
-                    $this->flashMessenger()->setNamespace('error')
-                        ->addMessage($results['status']);
-                }
-                if (isset($results['sysMessage'])) {
-                    $this->flashMessenger()->setNamespace('error')
-                        ->addMessage($results['sysMessage']);
-                }
-            }
-        }
-
-        // Find and format the default required date:
-        $defaultRequired = $this->storageRetrievalRequests()
-            ->getDefaultRequiredDate($checkRequests);
-        $defaultRequired = $this->getServiceLocator()->get('VuFind\DateConverter')
-            ->convertToDisplayDate("U", $defaultRequired);
-        try {
-            $defaultPickup
-                = $catalog->getDefaultPickUpLocation($patron, $gatheredDetails);
-        } catch (\Exception $e) {
-            $defaultPickup = false;
-        }
-
-        return $this->createViewModel(
-            [
-                'gatheredDetails' => $gatheredDetails,
-                'pickup' => $pickup,
-                'defaultPickup' => $defaultPickup,
-                'homeLibrary' => $this->getUser()->home_library,
-                'extraFields' => $extraFields,
-                'defaultRequiredDate' => $defaultRequired,
-                'helpText' => isset($checkRequests['helpText'])
-                    ? $checkRequests['helpText'] : null
-            ]
-        );
-    }
-
-    /**
-     * Action for dealing with ILL requests.
-     *
-     * @return mixed
-     */
-    public function illRequestAction()
-    {
-        $driver = $this->loadRecord();
-
-        // Stop now if the user does not have valid catalog credentials available:
-        if (!is_array($patron = $this->catalogLogin())) {
-            return $patron;
-        }
-
-        // If we're not supposed to be here, give up now!
-        $catalog = $this->getILS();
-        $checkRequests = $catalog->checkFunction(
-            'ILLRequests',
-            [
-                'id' => $driver->getUniqueID(),
-                'patron' => $patron
-            ]
-        );
-        if (!$checkRequests) {
-            return $this->forwardTo('Record', 'Home');
-        }
-
-        // Do we have valid information?
-        // Sets $this->logonURL and $this->gatheredDetails
-        $gatheredDetails = $this->ILLRequests()->validateRequest(
-            $checkRequests['HMACKeys']
-        );
-        if (!$gatheredDetails) {
-            return $this->redirectToRecord();
-        }
-
-        // Block invalid requests:
-        if (!$catalog->checkILLRequestIsValid(
-            $driver->getUniqueID(), $gatheredDetails, $patron
-        )) {
-            return $this->blockedILLRequestAction();
-        }
-
-        // Send various values to the view so we can build the form:
-
-        $extraFields = isset($checkRequests['extraFields'])
-            ? explode(":", $checkRequests['extraFields']) : [];
-
-        // Process form submissions if necessary:
-        if (!is_null($this->params()->fromPost('placeILLRequest'))) {
-            // If we made it this far, we're ready to place the hold;
-            // if successful, we will redirect and can stop here.
-
-            // Add Patron Data to Submitted Data
-            $details = $gatheredDetails + ['patron' => $patron];
-
-            // Attempt to place the hold:
-            $function = (string)$checkRequests['function'];
-            $results = $catalog->$function($details);
-
-            // Success: Go to Display ILL Requests
-            if (isset($results['success']) && $results['success'] == true) {
-                $this->flashMessenger()->setNamespace('info')
-                    ->addMessage('ill_request_place_success');
-                if ($this->inLightbox()) {
-                    return false;
-                }
-                return $this->redirect()->toRoute(
-                    'myresearch-illrequests'
-                );
-            } else {
-                // Failure: use flash messenger to display messages, stay on
-                // the current form.
-                if (isset($results['status'])) {
-                    $this->flashMessenger()->setNamespace('error')
-                        ->addMessage($results['status']);
-                }
-                if (isset($results['sysMessage'])) {
-                    $this->flashMessenger()->setNamespace('error')
-                        ->addMessage($results['sysMessage']);
-                }
-            }
-        }
-
-        // Find and format the default required date:
-        $defaultRequired = $this->ILLRequests()
-            ->getDefaultRequiredDate($checkRequests);
-        $defaultRequired = $this->getServiceLocator()->get('VuFind\DateConverter')
-            ->convertToDisplayDate("U", $defaultRequired);
-
-        // Get pickup libraries
-        $pickupLibraries = $catalog->getILLPickUpLibraries(
-            $driver->getUniqueID(), $patron, $gatheredDetails
-        );
-
-        // Get pickup locations. Note that these are independent of pickup library,
-        // and library specific locations must be retrieved when a library is
-        // selected.
-        $pickupLocations = $catalog->getPickUpLocations($patron, $gatheredDetails);
-
-        return $this->createViewModel(
-            [
-                'gatheredDetails' => $gatheredDetails,
-                'pickupLibraries' => $pickupLibraries,
-                'pickupLocations' => $pickupLocations,
-                'homeLibrary' => $this->getUser()->home_library,
-                'extraFields' => $extraFields,
-                'defaultRequiredDate' => $defaultRequired,
-                'helpText' => isset($checkRequests['helpText'])
-                    ? $checkRequests['helpText'] : null
-            ]
-        );
-    }
-
     /**
      * Is the result scroller active?
      *
diff --git a/module/VuFind/src/VuFind/Controller/StorageRetrievalRequestsTrait.php b/module/VuFind/src/VuFind/Controller/StorageRetrievalRequestsTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..d31fac89ff199daf3f0acf0966da4bde226a7f27
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/StorageRetrievalRequestsTrait.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Storage retrieval requests trait (for subclasses of AbstractRecord)
+ *
+ * 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
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\Controller;
+
+/**
+ * Storage retrieval requests trait (for subclasses of AbstractRecord)
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+trait StorageRetrievalRequestsTrait
+{
+    /**
+     * Action for dealing with blocked storage retrieval requests.
+     *
+     * @return mixed
+     */
+    public function blockedStorageRetrievalRequestAction()
+    {
+        $this->flashMessenger()->setNamespace('error')
+            ->addMessage('storage_retrieval_request_error_blocked');
+        return $this->redirectToRecord('#top');
+    }
+
+    /**
+     * Action for dealing with storage retrieval requests.
+     *
+     * @return mixed
+     */
+    public function storageRetrievalRequestAction()
+    {
+        $driver = $this->loadRecord();
+
+        // Stop now if the user does not have valid catalog credentials available:
+        if (!is_array($patron = $this->catalogLogin())) {
+            return $patron;
+        }
+
+        // If we're not supposed to be here, give up now!
+        $catalog = $this->getILS();
+        $checkRequests = $catalog->checkFunction(
+            'StorageRetrievalRequests',
+            [
+                'id' => $driver->getUniqueID(),
+                'patron' => $patron
+            ]
+        );
+        if (!$checkRequests) {
+            return $this->redirectToRecord();
+        }
+
+        // Do we have valid information?
+        // Sets $this->logonURL and $this->gatheredDetails
+        $gatheredDetails = $this->storageRetrievalRequests()->validateRequest(
+            $checkRequests['HMACKeys']
+        );
+        if (!$gatheredDetails) {
+            return $this->redirectToRecord();
+        }
+
+        // Block invalid requests:
+        if (!$catalog->checkStorageRetrievalRequestIsValid(
+            $driver->getUniqueID(), $gatheredDetails, $patron
+        )) {
+            return $this->blockedStorageRetrievalRequestAction();
+        }
+
+        // Send various values to the view so we can build the form:
+        $pickup = $catalog->getPickUpLocations($patron, $gatheredDetails);
+        $extraFields = isset($checkRequests['extraFields'])
+            ? explode(":", $checkRequests['extraFields']) : [];
+
+        // Process form submissions if necessary:
+        if (!is_null($this->params()->fromPost('placeStorageRetrievalRequest'))) {
+            // If we made it this far, we're ready to place the hold;
+            // if successful, we will redirect and can stop here.
+
+            // Add Patron Data to Submitted Data
+            $details = $gatheredDetails + ['patron' => $patron];
+
+            // Attempt to place the hold:
+            $function = (string)$checkRequests['function'];
+            $results = $catalog->$function($details);
+
+            // Success: Go to Display Storage Retrieval Requests
+            if (isset($results['success']) && $results['success'] == true) {
+                $this->flashMessenger()->setNamespace('info')
+                    ->addMessage('storage_retrieval_request_place_success');
+                if ($this->inLightbox()) {
+                    return false;
+                }
+                return $this->redirect()->toRoute(
+                    'myresearch-storageretrievalrequests'
+                );
+            } else {
+                // Failure: use flash messenger to display messages, stay on
+                // the current form.
+                if (isset($results['status'])) {
+                    $this->flashMessenger()->setNamespace('error')
+                        ->addMessage($results['status']);
+                }
+                if (isset($results['sysMessage'])) {
+                    $this->flashMessenger()->setNamespace('error')
+                        ->addMessage($results['sysMessage']);
+                }
+            }
+        }
+
+        // Find and format the default required date:
+        $defaultRequired = $this->storageRetrievalRequests()
+            ->getDefaultRequiredDate($checkRequests);
+        $defaultRequired = $this->getServiceLocator()->get('VuFind\DateConverter')
+            ->convertToDisplayDate("U", $defaultRequired);
+        try {
+            $defaultPickup
+                = $catalog->getDefaultPickUpLocation($patron, $gatheredDetails);
+        } catch (\Exception $e) {
+            $defaultPickup = false;
+        }
+
+        $view = $this->createViewModel(
+            [
+                'gatheredDetails' => $gatheredDetails,
+                'pickup' => $pickup,
+                'defaultPickup' => $defaultPickup,
+                'homeLibrary' => $this->getUser()->home_library,
+                'extraFields' => $extraFields,
+                'defaultRequiredDate' => $defaultRequired,
+                'helpText' => isset($checkRequests['helpText'])
+                    ? $checkRequests['helpText'] : null
+            ]
+        );
+        $view->setTemplate('record/storageretrievalrequest');
+        return $view;
+    }
+}