diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 1575d5d6933f1c35397da68c2487e35de48a245d..0d14ddca3a7c8b076c44633a32e53fb3623de43c 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -212,6 +212,9 @@ title_level_holds_mode = "disabled"
 ;holdings_text_fields[] = 'notes'
 ;holdings_text_fields[] = 'summary'
 
+; Whether support for multiple library cards is enabled. Default is false.
+;library_cards = true
+
 ; This section allows you to determine how the users will authenticate.
 ; You can use an LDAP directory, the local ILS (or multiple ILSes through
 ; the MultiILS option), the VuFind database (Database), Shibboleth, SIP2,
diff --git a/languages/en.ini b/languages/en.ini
index 96558d729abc31df239a5d351d029ebe67989c55..370cb8dbb62b12f2cb122b8f671c03807604d27b 100644
--- a/languages/en.ini
+++ b/languages/en.ini
@@ -4,6 +4,7 @@ Abstract = "Abstract"
 Access = "Access"
 Account = "Account"
 Add = "Add"
+Add a Library Card = "Add a Library Card"
 Add a Note = "Add a Note"
 Add Tag = "Add Tag"
 Add Tags = "Add Tags"
@@ -195,6 +196,8 @@ Company/Entity = "Company/Entity"
 Configuration = "Configuration"
 confirm_delete = "Are you sure you want to delete this?"
 confirm_delete_brief = "Delete Item?"
+confirm_delete_library_card_brief = "Delete Library Card?"
+confirm_delete_library_card_text = "Are you sure you want to delete this library card?"
 confirm_delete_list_brief = "Delete List?"
 confirm_delete_list_text = "Are you sure you want to delete this list?"
 confirm_delete_tags_brief = "Delete Tags"
@@ -255,6 +258,7 @@ Due Date = "Due Date"
 DVD = "DVD"
 eBook = "eBook"
 Edit = "Edit"
+Edit Library Card = "Edit Library Card"
 Edit this Advanced Search = "Edit this Advanced Search"
 edit_list = "Edit List"
 edit_list_fail = "Sorry, you are not permitted to edit this list"
@@ -511,6 +515,11 @@ Last Modified = "Last Modified"
 Last Name = "Last Name"
 less = "less"
 Library = "Library"
+Library Card = "Library Card"
+Library Card Deleted = "Library Card Deleted"
+Library Card Name = "Library Card Name"
+Library Cards = "Library Cards"
+Library Cards Disabled = "Library Cards Disabled"
 Library Catalog Password = "Library Catalog Password"
 Library Catalog Profile = "Library Catalog Profile"
 Library Catalog Record = "Library Catalog Record"
@@ -958,6 +967,7 @@ Use instead = "Use instead"
 User Account = "User Account"
 Username = "Username"
 Username cannot be blank = "Username cannot be blank"
+Username is already in use in another library card = "Username is already in use in another library card"
 VHS = "VHS"
 Video = "Video"
 Video Clips = "Video Clips"
@@ -995,6 +1005,7 @@ You do not have any fines = "You do not have any fines"
 You do not have any holds or recalls placed = "You do not have any holds or recalls placed"
 You do not have any interlibrary loan requests placed = "You do not have any interlibrary loan requests placed"
 You do not have any items checked out = "You do not have any items checked out"
+You do not have any library cards = "You do not have any library cards"
 You do not have any saved resources = "You do not have any saved resources. Perform a search and use the Add to Favorites button to save items."
 You do not have any storage retrieval requests placed = "You do not have any storage retrieval requests placed"
 You must be logged in first = "You must be logged in first"
diff --git a/languages/fi.ini b/languages/fi.ini
index 243e4f208e698de824ddd08b7bb08b505a0ba0ee..5a293e1c00f54e067adcaf2c22c7c351caf8649e 100644
--- a/languages/fi.ini
+++ b/languages/fi.ini
@@ -3,6 +3,7 @@ Abstract = "Abstrakti"
 Access = "Pääsy"
 Account = "Tili"
 Add = "Lisää"
+Add a Library Card = "Lisää kirjastokortti"
 Add a Note = "Lisää merkintä"
 Add Tag = "Lisää tagi"
 Add Tags = "Lisää tageja"
@@ -194,6 +195,8 @@ Company/Entity = "Yritys/yhteisö"
 Configuration = "Asetukset"
 confirm_delete = "Haluatko varmasti poistaa tämän?"
 confirm_delete_brief = "Poistetaanko?"
+confirm_delete_library_card_brief = "Poistetaanko kirjastokortti?"
+confirm_delete_library_card_text = "Haluatko varmasti poistaa tämän kirjastokortin?"
 confirm_delete_list_brief = "Poistetaanko lista?"
 confirm_delete_list_text = "Haluatko varmasti poistaa tämän listan?"
 confirm_delete_tags_brief = "Poista tagit"
@@ -254,6 +257,7 @@ Due Date = "Eräpäivä"
 DVD = "DVD"
 eBook = "E-kirja"
 Edit = "Muokkaa"
+Edit Library Card = "Muokkaa kirjastokorttia"
 Edit this Advanced Search = "Muokkaa tarkennettua hakua"
 edit_list = "Muokkaa listaa"
 edit_list_fail = "Listan muokkausoikeudet puuttuvat"
@@ -509,6 +513,11 @@ Last Modified = "Viimeksi muokattu"
 Last Name = "Sukunimi"
 less = "vähemmän"
 Library = "Kirjasto"
+Library Card = "Kirjastokortti"
+Library Card Deleted = "Kirjastokortti poistettu"
+Library Card Name = "Kirjastokortin nimi"
+Library Cards = "Kirjastokortit"
+Library Cards Disabled = "Kirjastokortit pois käytöstä"
 Library Catalog Password = "Kirjastokortin salasana"
 Library Catalog Profile = "Profiili kirjastojärjestelmässä"
 Library Catalog Record = "Lähetetty tietue"
@@ -956,6 +965,7 @@ Use instead = "Käytä"
 User Account = "Käyttäjätili"
 Username = "Käyttäjätunnus / viivakoodi"
 Username cannot be blank = "Käyttäjätunnus ei voi olla tyhjä"
+Username is already in use in another library card = "Käyttäjätunnus on jo käytössä toisessa kirjastokortissa"
 VHS = "VHS"
 Video = "Video"
 Video Clips = "Videoleikkeet"
@@ -993,6 +1003,7 @@ You do not have any fines = "Ei maksamattomia maksuja"
 You do not have any holds or recalls placed = "Ei voimassaolevia varauksia"
 You do not have any interlibrary loan requests placed = "Ei voimassaolevia kaukolainatilauksia"
 You do not have any items checked out = "Ei lainoja"
+You do not have any library cards = "Ei kirjastokortteja"
 You do not have any saved resources = "Ei tallennettuja tietueita"
 You do not have any storage retrieval requests placed = "Ei voimassaolevia varastotilauksia"
 You must be logged in first = "Kirjaudu sisään ensin"
diff --git a/languages/sv.ini b/languages/sv.ini
index 83f3fe55540fa2dea755952a576a5dd6b132f08c..9f2c188ce166481553fd3211a69a3806ffbe4d15 100644
--- a/languages/sv.ini
+++ b/languages/sv.ini
@@ -3,6 +3,7 @@ Abstract = "Abstrakt"
 Access = "Tillgång"
 Account = "Användarkonto"
 Add = "Lägg till"
+Add a Library Card = "Anslut bibliotekskort till mitt konto"
 Add a Note = "Lägg till en anteckning "
 Add Tag = "Lägg till en tagg"
 Add Tags = "Lägg till taggar"
@@ -194,6 +195,8 @@ Company/Entity = "Företag/organisation"
 Configuration = "Configuration"
 confirm_delete = "Är du säker att du vill ta bort denna?"
 confirm_delete_brief = "Radera?"
+confirm_delete_library_card_brief = "Radera bibliotekskortet?"
+confirm_delete_library_card_text = "Är du säker att du vill ta bort detta bibliotekskort?"
 confirm_delete_list_brief = "Radera listan??"
 confirm_delete_list_text = "Är du säker att du vill ta bort denna lista?"
 confirm_delete_tags_brief = "Radera taggar"
@@ -254,6 +257,7 @@ Due Date = "Förfallodag"
 DVD = "DVD"
 eBook = "E-bok"
 Edit = "Bearbeta"
+Edit Library Card = "Uppdatera bibliotekskortets uppgifter"
 Edit this Advanced Search = "Bearbeta utökade sökningen"
 edit_list = "Bearbeta listan"
 edit_list_fail = "Du har inte rättigheter för att bearbeta denna lista."
@@ -509,6 +513,11 @@ Last Modified = "Last Modified"
 Last Name = "Efternamn"
 less = "färre"
 Library = "Bibliotek"
+Library Card = "Bibliotekskort"
+Library Card Deleted = "Bibliotekskortet har raderats"
+Library Card Name = "Bibliotekskortets namn"
+Library Cards = "Bibliotekskort"
+Library Cards Disabled = "Bibliotekskort inte tillgängliga"
 Library Catalog Password = "Lösenord i bibliotekssystemet"
 Library Catalog Profile = "Profilen i bibliotekssystemet"
 Library Catalog Record = "Skickat post"
@@ -956,6 +965,7 @@ Use instead = "Använd i stället"
 User Account = "Användarkonto"
 Username = "Användarnamn / streckkod"
 Username cannot be blank = "Du måste välja ett användarnamn"
+Username is already in use in another library card = "Användarnamnet är redan i bruk för ett annat bibliotekskort"
 VHS = "VHS"
 Video = "Video"
 Video Clips = "Videoklipp"
@@ -993,6 +1003,7 @@ You do not have any fines = "Du har inga obetalda avgifter"
 You do not have any holds or recalls placed = "Du har inga reserveringar i kraft"
 You do not have any interlibrary loan requests placed = "Du har inga fjärrlånbeställningar i kraft"
 You do not have any items checked out = "Du har inga lån"
+You do not have any library cards = "Du har inga bibliotekskort"
 You do not have any saved resources = "Du har inga sparade resurser"
 You do not have any storage retrieval requests placed = "Du har inga lagerbeställningar i kraft"
 You must be logged in first = "Du måste logga in först"
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 2e9a1598cd43598de060a2a3dd784aa6069aa83e..82c54e74b965492b9d14ff2a936a4f8b157f090a 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -88,6 +88,7 @@ $config = [
             'index' => 'VuFind\Controller\IndexController',
             'install' => 'VuFind\Controller\InstallController',
             'libguides' => 'VuFind\Controller\LibGuidesController',
+            'librarycards' => 'VuFind\Controller\LibraryCardsController',
             'missingrecord' => 'VuFind\Controller\MissingrecordController',
             'my-research' => 'VuFind\Controller\MyResearchController',
             'oai' => 'VuFind\Controller\OaiController',
@@ -704,6 +705,7 @@ $recordRoutes = [
 // Define dynamic routes -- controller => [route name => action]
 $dynamicRoutes = [
     'MyResearch' => ['userList' => 'MyList/[:id]', 'editList' => 'EditList/[:id]'],
+    'LibraryCards' => ['editLibraryCard' => 'editCard/[:id]'],
 ];
 
 // Define static routes -- Controller/Action strings
@@ -724,6 +726,8 @@ $staticRoutes = [
     'Install/FixSecurity', 'Install/FixSolr', 'Install/Home',
     'Install/PerformSecurityFix', 'Install/ShowSQL',
     'LibGuides/Home', 'LibGuides/Results',
+    'LibraryCards/Home', 'LibraryCards/SelectCard',
+    'LibraryCards/DeleteCard',
     'MyResearch/Account', 'MyResearch/ChangePassword', 'MyResearch/CheckedOut',
     'MyResearch/Delete', 'MyResearch/DeleteList', 'MyResearch/Edit',
     'MyResearch/Email', 'MyResearch/Favorites', 'MyResearch/Fines',
diff --git a/module/VuFind/sql/migrations/pgsql/2.5/001-add-user-card-table.sql b/module/VuFind/sql/migrations/pgsql/2.5/001-add-user-card-table.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3f381f2520f562c033e4764be064e1490255434c
--- /dev/null
+++ b/module/VuFind/sql/migrations/pgsql/2.5/001-add-user-card-table.sql
@@ -0,0 +1,21 @@
+--
+-- Table structure for table `user_card`
+--
+
+DROP TABLE IF EXISTS "user_card";
+
+CREATE TABLE `user_card` (
+id SERIAL,
+user_id int NOT NULL,
+card_name varchar(255) NOT NULL DEFAULT '',
+cat_username varchar(50) NOT NULL DEFAULT '',
+cat_password varchar(50) DEFAULT NULL,
+cat_pass_enc varchar(110) DEFAULT NULL,
+home_library varchar(100) NOT NULL DEFAULT '',
+created timestamp NOT NULL DEFAULT '1970-01-01 00:00:00',
+saved timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+PRIMARY KEY (id),
+CONSTRAINT user_card_ibfk_1 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE
+);
+CREATE INDEX user_card_cat_username_idx ON user_card (cat_username);
+CREATE INDEX user_card_user_id_idx ON user_card (user_id);
diff --git a/module/VuFind/sql/mysql.sql b/module/VuFind/sql/mysql.sql
index 6220d14246730ad73c46d8de7e869f1b9be0bd65..5cc8ef8863edfaa2fd2fa032c602d32f5a57fb42 100644
--- a/module/VuFind/sql/mysql.sql
+++ b/module/VuFind/sql/mysql.sql
@@ -258,6 +258,30 @@ CREATE TABLE `user_stats_fields` (
   PRIMARY KEY (`id`,`field`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `user_card`
+--
+
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_card` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL,
+  `card_name` varchar(255) NOT NULL DEFAULT '',
+  `cat_username` varchar(50) NOT NULL DEFAULT '',
+  `cat_password` varchar(50) DEFAULT NULL,
+  `cat_pass_enc` varchar(110) DEFAULT NULL,
+  `home_library` varchar(100) NOT NULL DEFAULT '',
+  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `saved` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `user_id` (`user_id`),
+  KEY `user_card_cat_username` (`cat_username`),
+  CONSTRAINT `user_card_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
diff --git a/module/VuFind/sql/pgsql.sql b/module/VuFind/sql/pgsql.sql
index 4300251d7a34d274049fd3b4473de27480a94d2e..bf6b6e536747e13df04ccb6f90afff69b194bf2e 100644
--- a/module/VuFind/sql/pgsql.sql
+++ b/module/VuFind/sql/pgsql.sql
@@ -1,6 +1,6 @@
--- 
+--
 -- Table structure for table comments
--- 
+--
 
 DROP TABLE IF EXISTS "comments";
 
@@ -18,9 +18,9 @@ CREATE INDEX comments_resource_id_idx ON comments (resource_id);
 
 -- --------------------------------------------------------
 
--- 
+--
 -- Table structure for table resource
--- 
+--
 
 CREATE TABLE resource (
 id SERIAL,
@@ -36,9 +36,9 @@ CREATE INDEX resource_record_id_idx ON resource (record_id);
 
 -- --------------------------------------------------------
 
--- 
+--
 -- Table structure for table resource_tags
--- 
+--
 
 CREATE TABLE resource_tags (
 id SERIAL,
@@ -57,9 +57,9 @@ CREATE INDEX resource_tags_list_id_idx ON resource_tags (list_id);
 
 -- --------------------------------------------------------
 
--- 
+--
 -- Table structure for table search. Than fixed created column default value. Old value is 0000-00-00.
--- 
+--
 
 CREATE TABLE search (
 id SERIAL,
@@ -71,7 +71,7 @@ title varchar(20) DEFAULT NULL,
 saved int NOT NULL DEFAULT '0',
 search_object bytea,
 PRIMARY KEY (id)
-); 
+);
 CREATE INDEX search_user_id_idx ON search (user_id);
 CREATE INDEX search_folder_id_idx ON search (folder_id);
 CREATE INDEX session_id_idx ON search (session_id);
@@ -79,9 +79,9 @@ CREATE INDEX session_id_idx ON search (session_id);
 
 -- --------------------------------------------------------
 
--- 
+--
 -- Table structure for table tags
--- 
+--
 
 CREATE TABLE tags (
 id SERIAL,
@@ -91,9 +91,9 @@ PRIMARY KEY (id)
 
 -- --------------------------------------------------------
 
--- 
+--
 -- Table structure for table user
--- 
+--
 
 CREATE TABLE "user"(
 id SERIAL,
@@ -113,14 +113,14 @@ created timestamp NOT NULL DEFAULT '1970-01-01 00:00:00',
 verify_hash varchar(42) NOT NULL DEFAULT '',
 PRIMARY KEY (id),
 UNIQUE (username)
-); 
+);
 
 
 -- --------------------------------------------------------
 
--- 
+--
 -- Table structure for table user_list
--- 
+--
 
 CREATE TABLE user_list (
 id SERIAL,
@@ -136,9 +136,9 @@ CREATE INDEX user_list_user_id_idx ON user_list (user_id);
 
 -- --------------------------------------------------------
 
--- 
+--
 -- Table structure for table user_resource
--- 
+--
 
 CREATE TABLE user_resource (
 id SERIAL,
@@ -156,9 +156,9 @@ CREATE INDEX user_resource_user_id_idx ON user_resource (user_id);
 CREATE INDEX user_resource_list_id_idx ON user_resource (list_id);
 
 
--- 
+--
 -- Table structure for table session
--- 
+--
 
 DROP TABLE IF EXISTS "session";
 
@@ -244,21 +244,45 @@ PRIMARY KEY (id)
 
 -- --------------------------------------------------------
 
--- 
+--
+-- Table structure for table `user_card`
+--
+
+DROP TABLE IF EXISTS "user_card";
+
+CREATE TABLE `user_card` (
+id SERIAL,
+user_id int NOT NULL,
+card_name varchar(255) NOT NULL DEFAULT '',
+cat_username varchar(50) NOT NULL DEFAULT '',
+cat_password varchar(50) DEFAULT NULL,
+cat_pass_enc varchar(110) DEFAULT NULL,
+home_library varchar(100) NOT NULL DEFAULT '',
+created timestamp NOT NULL DEFAULT '1970-01-01 00:00:00',
+saved timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+PRIMARY KEY (id),
+CONSTRAINT user_card_ibfk_1 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE
+);
+CREATE INDEX user_card_cat_username_idx ON user_card (cat_username);
+CREATE INDEX user_card_user_id_idx ON user_card (user_id);
+
+-- --------------------------------------------------------
+
+--
 -- Constraints for dumped tables
--- 
+--
 
--- 
+--
 -- Constraints for table comments
--- 
+--
 ALTER TABLE comments
 ADD CONSTRAINT comments_ibfk_1 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE,
 ADD CONSTRAINT comments_ibfk_2 FOREIGN KEY (resource_id) REFERENCES resource (id) ON DELETE CASCADE;
 
 
--- 
+--
 -- Constraints for table resource_tags
--- 
+--
 ALTER TABLE resource_tags
 ADD CONSTRAINT resource_tags_ibfk_14 FOREIGN KEY (resource_id) REFERENCES resource (id) ON DELETE CASCADE,
 ADD CONSTRAINT resource_tags_ibfk_15 FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE,
@@ -266,16 +290,16 @@ ADD CONSTRAINT resource_tags_ibfk_16 FOREIGN KEY (list_id) REFERENCES user_list
 ADD CONSTRAINT resource_tags_ibfk_17 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE SET NULL;
 
 
--- 
+--
 -- Constraints for table user_list
--- 
+--
 ALTER TABLE user_list
 ADD CONSTRAINT user_list_ibfk_1 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE;
 
 
--- 
+--
 -- Constraints for table user_resource
--- 
+--
 ALTER TABLE user_resource
 ADD CONSTRAINT user_resource_ibfk_3 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE,
 ADD CONSTRAINT user_resource_ibfk_4 FOREIGN KEY (resource_id) REFERENCES resource (id) ON DELETE CASCADE,
diff --git a/module/VuFind/src/VuFind/Controller/LibraryCardsController.php b/module/VuFind/src/VuFind/Controller/LibraryCardsController.php
new file mode 100644
index 0000000000000000000000000000000000000000..ad9020eaf67db5003be9c25a5361fa30cc190243
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/LibraryCardsController.php
@@ -0,0 +1,251 @@
+<?php
+/**
+ * LibraryCards Controller
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ * Copyright (C) The National Library of Finland 2015.
+ *
+ * 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>
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\Controller;
+
+/**
+ * Controller for the library card functionality.
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class LibraryCardsController extends AbstractBase
+{
+    /**
+     * Send user's library cards to the view
+     *
+     * @return mixed
+     */
+    public function homeAction()
+    {
+        if (!($user = $this->getUser())) {
+            return $this->forceLogin();
+        }
+
+        // Check for "delete card" request; parameter may be in GET or POST depending
+        // on calling context.
+        $deleteId = $this->params()->fromPost(
+            'delete', $this->params()->fromQuery('delete')
+        );
+        if ($deleteId) {
+            // If the user already confirmed the operation, perform the delete now;
+            // otherwise prompt for confirmation:
+            $confirm = $this->params()->fromPost(
+                'confirm', $this->params()->fromQuery('confirm')
+            );
+            if ($confirm) {
+                $success = $this->performDeleteLibraryCard($deleteId);
+                if ($success !== true) {
+                    return $success;
+                }
+            } else {
+                return $this->confirmDeleteLibraryCard($deleteId);
+            }
+        }
+
+        // Connect to the ILS for login drivers:
+        $catalog = $this->getILS();
+
+        return $this->createViewModel(
+            [
+                'libraryCards' => $user->getLibraryCards(),
+                'multipleTargets' => $catalog->checkCapability('getLoginDrivers')
+            ]
+        );
+    }
+
+    /**
+     * Send user's library card to the edit view
+     *
+     * @return mixed
+     */
+    public function editCardAction()
+    {
+        // User must be logged in to edit library cards:
+        $user = $this->getUser();
+        if ($user == false) {
+            return $this->forceLogin();
+        }
+
+        // Process form submission:
+        if ($this->formWasSubmitted('submit')) {
+            if ($redirect = $this->processEditLibraryCard($user)) {
+                return $redirect;
+            }
+        }
+
+        $id = $this->params()->fromRoute('id', $this->params()->fromQuery('id'));
+        $card = $user->getLibraryCard($id == 'NEW' ? null : $id);
+
+        $target = null;
+        $username = $card->cat_username;
+        $targets = null;
+        $defaultTarget = null;
+        // Connect to the ILS and check if multiple target support is available:
+        $catalog = $this->getILS();
+        if ($catalog->checkCapability('getLoginDrivers')) {
+            $targets = $catalog->getLoginDrivers();
+            $defaultTarget = $catalog->getDefaultLoginDriver();
+            if (strstr($username, '.')) {
+                list($target, $username) = explode('.', $username, 2);
+            }
+        }
+        $cardName = $this->params()->fromPost('card_name', $card->card_name);
+        $username = $this->params()->fromPost('username', $username);
+        $target = $this->params()->fromPost('target', $target);
+
+        // Send the card to the view:
+        return $this->createViewModel(
+            [
+                'card' => $card,
+                'cardName' => $cardName,
+                'target' => $target ? $target : $defaultTarget,
+                'username' => $username,
+                'password' => $card->cat_password,
+                'targets' => $targets,
+                'defaultTarget' => $defaultTarget
+            ]
+        );
+    }
+
+    /**
+     * Creates a confirmation box to delete or not delete the current list
+     *
+     * @return mixed
+     */
+    public function deleteCardAction()
+    {
+        // User must be logged in to edit library cards:
+        $user = $this->getUser();
+        if ($user == false) {
+            return $this->forceLogin();
+        }
+
+        // Get requested library card ID:
+        $cardID = $this->params()
+            ->fromPost('cardID', $this->params()->fromQuery('cardID'));
+
+        // Have we confirmed this?
+        $confirm = $this->params()->fromPost(
+            'confirm', $this->params()->fromQuery('confirm')
+        );
+        if ($confirm) {
+            $user->deleteLibraryCard($cardID);
+
+            // Success Message
+            $this->flashMessenger()->setNamespace('info')
+                ->addMessage('Library Card Deleted');
+            // Redirect to MyResearch library cards
+            return $this->redirect()->toRoute('librarycards-home');
+        }
+
+        // If we got this far, we must display a confirmation message:
+        return $this->confirm(
+            'confirm_delete_library_card_brief',
+            $this->url()->fromRoute('librarycards-deletecard'),
+            $this->url()->fromRoute('librarycards-home'),
+            'confirm_delete_library_card_text', ['cardID' => $cardID]
+        );
+    }
+
+    /**
+     * Activates a library card
+     *
+     * @return \Zend\Http\Response
+     */
+    public function selectCardAction()
+    {
+        $user = $this->getUser();
+        if ($user == false) {
+            return $this->forceLogin();
+        }
+
+        $cardID = $this->params()->fromQuery('cardID');
+        $user->activateLibraryCard($cardID);
+
+        $this->setFollowupUrlToReferer();
+        if ($url = $this->getFollowupUrl()) {
+            $this->clearFollowupUrl();
+            return $this->redirect()->toUrl($url);
+        }
+        return $this->redirect()->toRoute('myresearch-home');
+    }
+
+    /**
+     * Process the "edit library card" submission.
+     *
+     * @param \VuFind\Db\Row\User $user Logged in user
+     *
+     * @return object|bool        Response object if redirect is
+     * needed, false if form needs to be redisplayed.
+     */
+    protected function processEditLibraryCard($user)
+    {
+        $cardName = $this->params()->fromPost('card_name', '');
+        $target = $this->params()->fromPost('target', '');
+        $username = $this->params()->fromPost('username', '');
+        $password = $this->params()->fromPost('password', '');
+
+        if (!$username || !$password) {
+            $this->flashMessenger()->setNamespace('error')
+                ->addMessage('authentication_error_blank');
+            return false;
+        }
+
+        if ($target) {
+            $username = "$target.$username";
+        }
+
+        // Connect to the ILS and check that the credentials are correct:
+        $catalog = $this->getILS();
+        $patron = $catalog->patronLogin($username, $password);
+        if (!$patron) {
+            $this->flashMessenger()->setNamespace('error')
+                ->addMessage('authentication_error_invalid');
+            return false;
+        }
+
+        $id = $this->params()->fromRoute('id', $this->params()->fromQuery('id'));
+        try {
+            $user->saveLibraryCard(
+                $id == 'NEW' ? null : $id, $cardName, $username, $password
+            );
+        } catch(\VuFind\Exception\LibraryCard $e) {
+            $this->flashMessenger()->setNamespace('error')
+                ->addMessage($e->getMessage());
+            return false;
+        }
+
+        return $this->redirect()->toRoute('librarycards-home');
+    }
+}
diff --git a/module/VuFind/src/VuFind/Db/Row/User.php b/module/VuFind/src/VuFind/Db/Row/User.php
index 52d4f0588b6e34edfd821e4984030ff75bd89d86..6a82e935c6dfda3b2be1d75d8cfda545ee6b57c9 100644
--- a/module/VuFind/src/VuFind/Db/Row/User.php
+++ b/module/VuFind/src/VuFind/Db/Row/User.php
@@ -121,7 +121,14 @@ class User extends RowGateway implements \VuFind\Db\Table\DbTableAwareInterface,
             $this->cat_password = $password;
             $this->cat_pass_enc = null;
         }
-        return $this->save();
+
+        $result = $this->save();
+
+        // Update library card entry after saving the user so that we always have a
+        // user id:
+        $this->updateLibraryCardEntry();
+
+        return $result;
     }
 
     /**
@@ -199,6 +206,7 @@ class User extends RowGateway implements \VuFind\Db\Table\DbTableAwareInterface,
     public function changeHomeLibrary($homeLibrary)
     {
         $this->home_library = $homeLibrary;
+        $this->updateLibraryCardEntry();
         return $this->save();
     }
 
@@ -403,6 +411,214 @@ class User extends RowGateway implements \VuFind\Db\Table\DbTableAwareInterface,
         $userResourceTable->destroyLinks($resourceIDs, $this->id);
     }
 
+    /**
+     * Whether library cards are enabled
+     *
+     * @return boolean
+     */
+    public function libraryCardsEnabled()
+    {
+        return isset($this->config->Catalog->library_cards)
+            && $this->config->Catalog->library_cards;
+    }
+
+    /**
+     * Get all library cards associated with the user.
+     *
+     * @return \Zend\Db\ResultSet\AbstractResultSet
+     * @throws \VuFind\Exception\LibraryCard
+     */
+    public function getLibraryCards()
+    {
+        if (!$this->libraryCardsEnabled()) {
+            return new \Zend\Db\ResultSet\ResultSet();
+        }
+        $userCard = $this->getDbTable('UserCard');
+        return $userCard->select(['user_id' => $this->id]);
+    }
+
+    /**
+     * Get library card data
+     *
+     * @param int $id Library card ID
+     *
+     * @return UserCard|false Card data if found, false otherwise
+     * @throws \VuFind\Exception\LibraryCard
+     */
+    public function getLibraryCard($id = null)
+    {
+        if (!$this->libraryCardsEnabled()) {
+            throw new \VuFind\Exception\LibraryCard('Library Cards Disabled');
+        }
+
+        $userCard = $this->getDbTable('UserCard');
+        if ($id === null) {
+            $row = $userCard->createRow();
+            $row->card_name = '';
+            $row->user_id = $this->id;
+            $row->cat_username = '';
+            $row->cat_password = '';
+        } else {
+            $row = $userCard->select(['user_id' => $this->id, 'id' => $id])
+                ->current();
+            if ($row === false) {
+                throw new \VuFind\Exception\LibraryCard('Library Card Not Found');
+            }
+            if ($this->passwordEncryptionEnabled()) {
+                $row->cat_password = $this->encryptOrDecrypt(
+                    $row->cat_pass_enc, false
+                );
+            }
+        }
+
+        return $row;
+    }
+
+    /**
+     * Delete library card
+     *
+     * @param int $id Library card ID
+     *
+     * @return void
+     * @throws \VuFind\Exception\LibraryCard
+     */
+    public function deleteLibraryCard($id)
+    {
+        if (!$this->libraryCardsEnabled()) {
+            throw new \VuFind\Exception\LibraryCard('Library Cards Disabled');
+        }
+
+        $userCard = $this->getDbTable('UserCard');
+        $row = $userCard->select(['id' => $id, 'user_id' => $this->id])->current();
+
+        if (empty($row)) {
+            throw new \Exception('Library card not found');
+        }
+        $row->delete();
+
+        if ($row->cat_username == $this->cat_username) {
+            // Activate another card (if any) or remove cat_username and cat_password
+            $cards = $this->getLibraryCards();
+            if ($cards->count() > 0) {
+                $this->activateLibraryCard($cards->current()->id);
+            } else {
+                $this->cat_username = null;
+                $this->cat_password = null;
+                $this->cat_pass_enc = null;
+                $this->save();
+            }
+        }
+    }
+
+    /**
+     * Activate a library card for the given username
+     *
+     * @param int $id Library card ID
+     *
+     * @return void
+     * @throws \VuFind\Exception\LibraryCard
+     */
+    public function activateLibraryCard($id)
+    {
+        if (!$this->libraryCardsEnabled()) {
+            throw new \VuFind\Exception\LibraryCard('Library Cards Disabled');
+        }
+        $userCard = $this->getDbTable('UserCard');
+        $row = $userCard->select(['id' => $id, 'user_id' => $this->id])->current();
+
+        if (!empty($row)) {
+            $this->cat_username = $row->cat_username;
+            $this->cat_password = $row->cat_password;
+            $this->cat_pass_enc = $row->cat_pass_enc;
+            $this->home_library = $row->home_library;
+            $this->save();
+        }
+    }
+
+    /**
+     * Save library card with the given information
+     *
+     * @param int    $id       Card ID
+     * @param string $cardName Card name
+     * @param string $username Username
+     * @param string $password Password
+     *
+     * @return int Card ID
+     * @throws \VuFind\Exception\LibraryCard
+     */
+    public function saveLibraryCard($id, $cardName, $username, $password)
+    {
+        if (!$this->libraryCardsEnabled()) {
+            throw new \VuFind\Exception\LibraryCard('Library Cards Disabled');
+        }
+        $userCard = $this->getDbTable('UserCard');
+
+        // Check that the username is not already in use in another card
+        $row = $userCard->select(
+            ['user_id' => $this->id, 'cat_username' => $username]
+        )->current();
+        if (!empty($row) && ($id === null || $row->id != $id)) {
+            throw new \VuFind\Exception\LibraryCard(
+                'Username is already in use in another library card'
+            );
+        }
+
+        $row = null;
+        if ($id !== null) {
+            $row = $userCard->select(['user_id' => $this->id, 'id' => $id])
+                ->current();
+        }
+        if (empty($row)) {
+            $row = $userCard->createRow();
+            $row->user_id = $this->id;
+            $row->created = date('Y-m-d H:i:s');
+        }
+        $row->card_name = $cardName;
+        $row->cat_username = $username;
+        if ($this->passwordEncryptionEnabled()) {
+            $row->cat_password = null;
+            $row->cat_pass_enc = $this->encryptOrDecrypt($password, true);
+        } else {
+            $row->cat_password = $password;
+            $row->cat_pass_enc = null;
+        }
+
+        $row->save();
+        return $row->id;
+    }
+
+    /**
+     * Verify that the current card information exists in user's library cards
+     * (if enabled) and is up to date.
+     *
+     * @return void
+     * @throws \VuFind\Exception\PasswordSecurity
+     */
+    protected function updateLibraryCardEntry()
+    {
+        if (!$this->libraryCardsEnabled() || empty($this->cat_username)) {
+            return;
+        }
+
+        $userCard = $this->getDbTable('UserCard');
+        $row = $userCard->select(
+            ['user_id' => $this->id, 'cat_username' => $this->cat_username]
+        )->current();
+        if (empty($row)) {
+            $row = $userCard->createRow();
+            $row->user_id = $this->id;
+            $row->cat_username = $this->cat_username;
+            $row->card_name = $this->cat_username;
+            $row->created = date('Y-m-d H:i:s');
+        }
+        // Always update home library and password
+        $row->home_library = $this->home_library;
+        $row->cat_password = $this->cat_password;
+        $row->cat_pass_enc = $this->cat_pass_enc;
+
+        $row->save();
+    }
+
     /**
      * Destroy the user.
      *
diff --git a/module/VuFind/src/VuFind/Db/Row/UserCard.php b/module/VuFind/src/VuFind/Db/Row/UserCard.php
new file mode 100644
index 0000000000000000000000000000000000000000..2995794e426218c5e2f9219628432deb6b880ad3
--- /dev/null
+++ b/module/VuFind/src/VuFind/Db/Row/UserCard.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Row Definition for user_card
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The National Library of Finland 2015.
+ *
+ * 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  Db_Row
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\Db\Row;
+
+/**
+ * Row Definition for user_card
+ *
+ * @category VuFind2
+ * @package  Db_Row
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class UserCard extends RowGateway
+{
+    /**
+     * Constructor
+     *
+     * @param \Zend\Db\Adapter\Adapter $adapter Database adapter
+     */
+    public function __construct($adapter)
+    {
+        parent::__construct('id', 'user_card', $adapter);
+    }
+}
diff --git a/module/VuFind/src/VuFind/Db/Table/UserCard.php b/module/VuFind/src/VuFind/Db/Table/UserCard.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd78b358b8f304cf3f227053d9175b34f004de48
--- /dev/null
+++ b/module/VuFind/src/VuFind/Db/Table/UserCard.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Table Definition for user_card
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The National Library of Finland 2015.
+ *
+ * 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  Db_Table
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFind\Db\Table;
+
+/**
+ * Table Definition for user_card
+ *
+ * @category VuFind2
+ * @package  Db_Table
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class UserCard extends Gateway
+{
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        parent::__construct('user_card', 'VuFind\Db\Row\UserCard');
+    }
+}
diff --git a/module/VuFind/src/VuFind/Exception/LibraryCard.php b/module/VuFind/src/VuFind/Exception/LibraryCard.php
new file mode 100644
index 0000000000000000000000000000000000000000..069f3dcfe0e3fdcfb49793a947dc5fa10cc50857
--- /dev/null
+++ b/module/VuFind/src/VuFind/Exception/LibraryCard.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Library Card Exception
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The National Library of Finland 2015.
+ *
+ * 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  Exceptions
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+namespace VuFind\Exception;
+
+/**
+ * Library Card Exception
+ *
+ * @category VuFind2
+ * @package  Exceptions
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+class LibraryCard extends \Exception
+{
+}
diff --git a/themes/blueprint/templates/RecordTab/holdingsils.phtml b/themes/blueprint/templates/RecordTab/holdingsils.phtml
index 8b6ab43d8a99e844996dd610ec956f46c0d77dc7..678e1be691acf5eb3596489b6ed13b22f21c15bc 100644
--- a/themes/blueprint/templates/RecordTab/holdingsils.phtml
+++ b/themes/blueprint/templates/RecordTab/holdingsils.phtml
@@ -11,6 +11,9 @@
     // Set page title.
     $this->headTitle($this->translate('Holdings') . ': ' . $this->driver->getBreadcrumb());
 ?>
+
+<?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
 <? if ($offlineMode == "ils-offline"): ?>
   <div class="sysInfo">
     <h2><?=$this->transEsc('ils_offline_title')?></h2>
diff --git a/themes/blueprint/templates/librarycards/editcard.phtml b/themes/blueprint/templates/librarycards/editcard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..2c0112db6bf4e954300417722ab556d02356f1e7
--- /dev/null
+++ b/themes/blueprint/templates/librarycards/editcard.phtml
@@ -0,0 +1,39 @@
+<?
+    // Set up page title:
+    $pageTitle = empty($this->card->id) ? 'Add a Library Card' : "Edit Library Card";
+    $this->headTitle($this->translate($pageTitle));
+
+    // Set up breadcrumbs:
+    $this->layout()->breadcrumbs = '<a href="' . $this->url('myresearch-home') . '">'
+        . $this->transEsc('Your Account') . '</a>' . '<span>&gt;</span><em>'
+        . $this->transEsc($pageTitle) . '</em>';
+?>
+<h1><?=$this->transEsc($pageTitle); ?></h1>
+
+<?=$this->flashmessages()?>
+
+<form method="post" name="<?=empty($this->card->id) ? 'newCardForm' : 'editCardForm'?>" action="">
+  <label class="displayBlock" for="card_name"><?=$this->transEsc('Library Card Name'); ?>:</label>
+  <input id="card_name" type="text" name="card_name" value="<?=$this->escapeHtmlAttr($this->cardName)?>" size="50"
+    class="mainFocus <?=$this->jqueryValidation(array('required'=>'This field is required')) ?>"/>
+  <br class="clear"/>
+
+  <? if ($this->targets !== null): ?>
+  <label class="displayBlock" for="login_target"><?=$this->transEsc('login_target')?>:</label>
+  <select id="login_target" name="target">
+  <? foreach ($this->targets as $target): ?>
+    <option value="<?=$this->escapeHtmlAttr($target)?>"<?=($target == $this->target ? ' selected="selected"' : '')?>><?=$this->transEsc("source_$target", null, $target)?></option>
+  <? endforeach; ?>
+  </select>
+  <br class="clear"/>
+  <? endif; ?>
+
+  <label class="displayBlock" for="login_username"><?=$this->transEsc('Username')?>:</label>
+  <input id="login_username" type="text" name="username" value="<?=$this->escapeHtmlAttr($this->username)?>" size="15" class="<?=$this->jqueryValidation(array('required'=>'This field is required'))?>"/>
+  <br class="clear"/>
+  <label class="displayBlock" for="login_password"><?=$this->transEsc('Password')?>:</label>
+  <input id="login_password" type="password" name="password" value="<?=$this->escapeHtmlAttr($this->password)?>" size="15" class="<?=$this->jqueryValidation(array('required'=>'This field is required'))?>"/>
+  <br class="clear"/>
+
+  <input class="button" type="submit" name="submit" value="<?=$this->transEsc('Save') ?>"/>
+</form>
diff --git a/themes/blueprint/templates/librarycards/home.phtml b/themes/blueprint/templates/librarycards/home.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..1fa5b4f33cf18bba5874e17b432f8445d766de25
--- /dev/null
+++ b/themes/blueprint/templates/librarycards/home.phtml
@@ -0,0 +1,55 @@
+<?
+    // Set up page title:
+    $this->headTitle($this->translate('Library Cards'));
+
+    // Set up breadcrumbs:
+    $this->layout()->breadcrumbs = '<a href="' . $this->url('myresearch-home') . '">'
+        . $this->transEsc('Your Account') . '</a>' . '<span>&gt;</span><em>'
+        . $this->transEsc('Library Cards') . '</em>';
+?>
+<div class="<?=$this->layoutClass('mainbody')?>">
+  <?=$this->flashmessages()?>
+  <? if ($this->libraryCards->count() == 0): ?>
+    <?=$this->transEsc('You do not have any library cards')?>
+  <? else: ?>
+    <h3><?=$this->transEsc('Library Cards')?></h3>
+    <table class="datagrid fines" summary="<?=$this->transEsc('Library Cards')?>">
+      <tr>
+        <th><?=$this->transEsc('Library Card Name')?></th>
+        <? if ($this->multipleTargets): ?>
+        <th><?=$this->transEsc('login_target')?></th>
+        <? endif; ?>
+        <th><?=$this->transEsc('Username')?></th>
+        <th>&nbsp;</th>
+      </tr>
+      <? foreach ($this->libraryCards as $record): ?>
+        <tr>
+          <td><?=$this->escapeHtml($record['card_name'])?></td>
+          <? $username = $record['cat_username']; if ($this->multipleTargets): ?>
+            <? $target = ''; ?>
+            <? if (strstr($username, '.')): ?>
+              <? list($target, $username) = explode('.', $username, 2); ?>
+            <? endif; ?>
+            <td><?=$target ? $this->transEsc("source_$target", null, $target) : '&nbsp;' ?></td>
+          <? endif; ?>
+          <td><?=$this->escapeHtml($username)?></td>
+          <td>
+            <div class="libraryCardButtons">
+              <a class="edit smallButton" href="<?=$this->url('editLibraryCard') . $this->escapeHtmlAttr($record['id']) ?>" title="<?=$this->transEsc('Edit Library Card')?>"><i class="fa fa-edit"></i> <?=$this->transEsc('Edit')?></a>
+              <a class="delete smallButton" href="<?=$this->url('librarycards-deletecard') ?>?cardID=<?=urlencode($record['id'])?>"><?=$this->transEsc('Delete')?></a>
+            </div>
+          </td>
+        </tr>
+      <? endforeach; ?>
+    </table>
+
+    <div>
+      <a href="<?=$this->url('editLibraryCard') ?>NEW" class="add smallButton" title="<?=$this->transEsc('Add a Library Card')?>"><?=$this->transEsc('Add a Library Card')?></a>
+    </div>
+
+  <? endif; ?>
+</div>
+<div class="<?=$this->layoutClass('sidebar')?>">
+  <?=$this->context($this)->renderInContext("myresearch/menu.phtml", array('active' => 'fines'))?>
+</div>
+<div class="clear"></div>
diff --git a/themes/blueprint/templates/librarycards/selectcard.phtml b/themes/blueprint/templates/librarycards/selectcard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..8ebe45b2ac14c21209d6437641374433bed501ae
--- /dev/null
+++ b/themes/blueprint/templates/librarycards/selectcard.phtml
@@ -0,0 +1,24 @@
+<? if ($this->user): ?>
+  <?$cards = $this->user->getLibraryCards(); if ($cards->count() > 1): ?>
+    <form action="<?=$this->url('librarycards-selectcard')?>" method="get">
+      <label for="library_card"><?=$this->transEsc('Library Card')?></label>
+      <select id="library_card" name="cardID" class="jumpMenu">
+        <? foreach ($cards as $card): ?>
+          <?
+            $target = '';
+            $username = $card->cat_username;
+            if (strstr($username, '.')) {
+              list($target, $username) = explode('.', $username, 2);
+            }
+            $display = $this->transEsc($card->card_name ? $card->card_name : $card->cat_username);
+            if ($target) {
+              $display .= ' (' . $this->transEsc("source_$target", null, $target) . ')';
+            }
+          ?>
+          <option value="<?=$this->escapeHtmlAttr($card->id)?>"<?=$card->cat_username == $this->user->cat_username ? ' selected="selected"' : ''?>><?=$display ?></option>
+        <? endforeach; ?>
+      </select>
+      <noscript><input type="submit" class="btn btn-default" value="<?=$this->transEsc("Set")?>" /></noscript>
+    </form>
+  <? endif; ?>
+<? endif; ?>
diff --git a/themes/blueprint/templates/myresearch/checkedout.phtml b/themes/blueprint/templates/myresearch/checkedout.phtml
index 2728c44edb4d6d83a64d56919dbcc5ea53657b53..bda1cf0de3a3fba2783d5b72e82ef3b2047847de 100644
--- a/themes/blueprint/templates/myresearch/checkedout.phtml
+++ b/themes/blueprint/templates/myresearch/checkedout.phtml
@@ -11,6 +11,8 @@
   <h3><?=$this->transEsc('Your Checked Out Items')?></h3>
   <?=$this->flashmessages()?>
 
+  <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
   <? if (!empty($this->transactions)): ?>
     <? if ($this->renewForm): ?>
     <form name="renewals" action="" method="post" id="renewals">
diff --git a/themes/blueprint/templates/myresearch/fines.phtml b/themes/blueprint/templates/myresearch/fines.phtml
index 42d50c89c10776774dd3f719bed4623c51ae6a0b..eaa48a3627187eaed072b894a74dca203d7940d2 100644
--- a/themes/blueprint/templates/myresearch/fines.phtml
+++ b/themes/blueprint/templates/myresearch/fines.phtml
@@ -9,9 +9,14 @@
 ?>
 <div class="<?=$this->layoutClass('mainbody')?>">
   <? if (empty($this->fines)): ?>
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <?=$this->transEsc('You do not have any fines')?>
   <? else: ?>
     <h3><?=$this->transEsc('Your Fines')?></h3>
+
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <table class="datagrid fines" summary="<?=$this->transEsc('Your Fines')?>">
     <tr>
       <th><?=$this->transEsc('Title')?></th>
diff --git a/themes/blueprint/templates/myresearch/holds.phtml b/themes/blueprint/templates/myresearch/holds.phtml
index 315252f88d98869ddede0484d301044fadbc9655..d4f262129f757c835dbe54a0f46832e6a89ee3ab 100644
--- a/themes/blueprint/templates/myresearch/holds.phtml
+++ b/themes/blueprint/templates/myresearch/holds.phtml
@@ -12,6 +12,9 @@
 
   <?=$this->flashmessages()?>
 
+
+  <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
   <? if (!empty($this->recordList)): ?>
     <? if ($this->cancelForm): ?>
       <form name="cancelForm" action="" method="post" id="cancelHold">
diff --git a/themes/blueprint/templates/myresearch/illrequests.phtml b/themes/blueprint/templates/myresearch/illrequests.phtml
index 54bca16280f385ced66964365de5db34539fa82c..cea81d9ff78952a19235e2d8f2b8550d445edcdc 100644
--- a/themes/blueprint/templates/myresearch/illrequests.phtml
+++ b/themes/blueprint/templates/myresearch/illrequests.phtml
@@ -12,6 +12,8 @@
 
   <?=$this->flashmessages()?>
 
+  <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
   <? if (!empty($this->recordList)): ?>
     <? if ($this->cancelForm): ?>
       <form name="cancelForm" action="" method="post" id="cancelILLRequest">
@@ -83,8 +85,8 @@
             <? if (isset($ilsDetails['institution_name']) && !empty($ilsDetails['institution_name'])): ?>
               <strong><?=$this->transEsc('institution_' . $ilsDetails['institution_name'], array(), $ilsDetails['institution_name']) ?></strong>
               <br />
-            <? endif; ?> 
-                
+            <? endif; ?>
+
             <? /* Depending on the ILS driver, the "location" value may be a string or an ID; figure out the best
                value to display... */ ?>
             <? $pickupDisplay = ''; ?>
diff --git a/themes/blueprint/templates/myresearch/menu.phtml b/themes/blueprint/templates/myresearch/menu.phtml
index abe0d6d472ce545e292765cfb75185254593d784..779aa36fc9d1ab24a328feed6cd1c37dda4e18d8 100644
--- a/themes/blueprint/templates/myresearch/menu.phtml
+++ b/themes/blueprint/templates/myresearch/menu.phtml
@@ -23,6 +23,9 @@
       <? if ($this->ils()->checkCapability('getMyProfile')): ?>
         <li<?=$this->active == 'profile' ? ' class="active"' : ''?>><a href="<?=$this->url('myresearch-profile')?>"><?=$this->transEsc('Profile')?></a></li>
       <? endif; ?>
+      <? $user = $this->auth()->isLoggedIn(); if ($user && $user->libraryCardsEnabled()): ?>
+        <li<?=$this->active == 'librarycards' ? ' class="active"' : ''?>><a href="<?=$this->url('librarycards-home')?>"><?=$this->transEsc('Library Cards')?></a></li>
+      <? endif; ?>
     <? endif; ?>
     <li<?=$this->active == 'history' ? ' class="active"' : ''?>><a href="<?=$this->url('search-history')?>?require_login"><?=$this->transEsc('history_saved_searches')?></a></li>
   </ul>
@@ -47,7 +50,7 @@
       <? endforeach; ?>
       <li>
         <a href="<?=$this->url('editList', array('id'=>'NEW'))?>" title="<?=$this->transEsc('Create a List') ?>">
-          <?=$this->transEsc('Create a List') ?> 
+          <?=$this->transEsc('Create a List') ?>
         </a>
         <img src="<?=$this->imagelink('silk/add.png')?>" style="margin-left:2px;vertical-align:text-bottom"/>
       </li>
diff --git a/themes/blueprint/templates/myresearch/newpassword.phtml b/themes/blueprint/templates/myresearch/newpassword.phtml
index 0a2c1f639046f9930ffde5d1d4b81ee79a80eb2a..6271e39126e8152fc52f02968830534f0cbce06c 100644
--- a/themes/blueprint/templates/myresearch/newpassword.phtml
+++ b/themes/blueprint/templates/myresearch/newpassword.phtml
@@ -9,6 +9,7 @@
 <div class="<?=$this->layoutClass('mainbody')?>">
   <h2><?=$this->transEsc('Create New Password') ?></h2>
   <?=$this->flashmessages() ?>
+
   <? if (!$this->auth()->getManager()->supportsPasswordChange($this->auth_method)): ?>
     <div class="error"><?=$this->transEsc('recovery_new_disabled') ?></div>
   <? elseif (!isset($this->hash)): ?>
diff --git a/themes/blueprint/templates/myresearch/profile.phtml b/themes/blueprint/templates/myresearch/profile.phtml
index 63a413dcc3350b08f211aa261e7d7b630fdf1c4d..aaeba9f455bfa98226e7ce799c67153be9f1146f 100644
--- a/themes/blueprint/templates/myresearch/profile.phtml
+++ b/themes/blueprint/templates/myresearch/profile.phtml
@@ -16,6 +16,9 @@
 <div class="<?=$this->layoutClass('mainbody')?>">
   <h3><?=$this->transEsc('Your Profile')?></h3>
   <?=$this->flashmessages();?>
+
+  <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
   <? if ($showHomeLibForm): ?><form method="post" action="" id="profile_form"><? endif; ?>
   <?
       echo $this->renderArray(
diff --git a/themes/blueprint/templates/myresearch/storageretrievalrequests.phtml b/themes/blueprint/templates/myresearch/storageretrievalrequests.phtml
index dc092f7373ed3177cf1922c38175b65fbfeb28b8..5b67e7e35739eaaa11b789160eb84305f21be9d9 100644
--- a/themes/blueprint/templates/myresearch/storageretrievalrequests.phtml
+++ b/themes/blueprint/templates/myresearch/storageretrievalrequests.phtml
@@ -12,6 +12,8 @@
 
   <?=$this->flashmessages()?>
 
+  <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
   <? if (!empty($this->recordList)): ?>
     <? if ($this->cancelForm): ?>
       <form name="cancelForm" action="" method="post" id="cancelStorageRetrievalRequest">
diff --git a/themes/bootstrap3/templates/RecordTab/holdingsils.phtml b/themes/bootstrap3/templates/RecordTab/holdingsils.phtml
index dca7e05bee1591daf3a97b1de893c99b092b0bce..c18b8f1a0559f476e6dd52c8ea16faf6ad259ffd 100644
--- a/themes/bootstrap3/templates/RecordTab/holdingsils.phtml
+++ b/themes/bootstrap3/templates/RecordTab/holdingsils.phtml
@@ -11,6 +11,9 @@
     // Set page title.
     $this->headTitle($this->translate('Holdings') . ': ' . $this->driver->getBreadcrumb());
 ?>
+
+<?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
 <? if ($offlineMode == "ils-offline"): ?>
   <div class="alert alert-warning">
     <h2><?=$this->transEsc('ils_offline_title')?></h2>
diff --git a/themes/bootstrap3/templates/librarycards/editcard.phtml b/themes/bootstrap3/templates/librarycards/editcard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..beb61aa23e65bd30eaa2b92caf67a1cfcbac55d2
--- /dev/null
+++ b/themes/bootstrap3/templates/librarycards/editcard.phtml
@@ -0,0 +1,53 @@
+<?
+  // Set up page title:
+  $pageTitle = empty($this->card->id) ? 'Add a Library Card' : "Edit Library Card";
+  $this->headTitle($this->translate($pageTitle));
+
+  // Set up breadcrumbs:
+  $this->layout()->breadcrumbs = '<li><a href="' . $this->url('myresearch-home') . '">' . $this->transEsc('Your Account') . '</a></li>'
+    . '<li><a href="' . $this->url('librarycards-home') . '">' . $this->transEsc('Library Cards') . '</a></li>'
+    . '<li>' . $this->transEsc($pageTitle) . '</li>';
+?>
+
+<?=$this->flashmessages()?>
+
+<h2><?=$this->transEsc($pageTitle); ?></h2>
+
+<form class="form-horizontal edit-card-form" method="post" name="<?=empty($this->card->id) ? 'newCardForm' : 'editCardForm'?>">
+  <input type="hidden" name="id" value="<?=empty($this->card->id) ? 'NEW' : $this->card->id ?>"/>
+  <div class="form-group">
+    <label class="col-sm-3 control-label" for="card_name"><?=$this->transEsc('Library Card Name'); ?>:</label>
+    <div class="col-sm-9">
+      <input id="card_name" class="form-control" type="text" name="card_name" value="<?=$this->escapeHtmlAttr($this->cardName)?>"/>
+    </div>
+  </div>
+  <? if ($this->targets !== null): ?>
+  <div class="form-group">
+    <label class="col-sm-3 control-label" for="login_target"><?=$this->transEsc('login_target')?>:</label>
+    <div class="col-sm-9">
+      <select id="login_target" name="target" class="form-control">
+      <? foreach ($this->targets as $target): ?>
+        <option value="<?=$this->escapeHtmlAttr($target)?>"<?=($target == $this->target ? ' selected="selected"' : '')?>><?=$this->transEsc("source_$target", null, $target)?></option>
+      <? endforeach; ?>
+      </select>
+    </div>
+  </div>
+  <? endif; ?>
+  <div class="form-group">
+    <label class="col-sm-3 control-label" for="login_username"><?=$this->transEsc('Username')?>:</label>
+    <div class="col-sm-9">
+      <input id="login_username" type="text" name="username" value="<?=$this->escapeHtmlAttr($this->username)?>" class="form-control"/>
+    </div>
+  </div>
+  <div class="form-group">
+    <label class="col-sm-3 control-label" for="login_password"><?=$this->transEsc('Password')?>:</label>
+    <div class="col-sm-9">
+      <input id="login_password" type="password" name="password" value="<?=$this->escapeHtmlAttr($this->password)?>" class="form-control"/>
+    </div>
+  </div>
+  <div class="form-group">
+    <div class="col-sm-9 col-sm-offset-3">
+      <input class="btn btn-primary" type="submit" name="submit" value="<?=$this->transEsc('Save') ?>"/>
+    </div>
+  </div>
+</form>
diff --git a/themes/bootstrap3/templates/librarycards/home.phtml b/themes/bootstrap3/templates/librarycards/home.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..a8a4ab1094640488546c16efcf91c5bf7208990c
--- /dev/null
+++ b/themes/bootstrap3/templates/librarycards/home.phtml
@@ -0,0 +1,63 @@
+<?
+    // Set up page title:
+    $this->headTitle($this->translate('Library Cards'));
+
+    // Set up breadcrumbs:
+    $this->layout()->breadcrumbs = '<li><a href="' . $this->url('myresearch-home') . '">' . $this->transEsc('Your Account') . '</a></li> <li class="active">' . $this->transEsc('Library Cards') . '</li>';
+?>
+<div class="row">
+  <div class="<?=$this->layoutClass('mainbody')?>">
+
+    <?=$this->flashmessages()?>
+
+    <h2><?=$this->transEsc('Library Cards')?></h2>
+    <? if ($this->libraryCards->count() == 0): ?>
+      <div><?=$this->transEsc('You do not have any library cards')?></div>
+    <? else: ?>
+      <table class="table table-striped" summary="<?=$this->transEsc('Library Cards')?>">
+      <tr>
+        <th><?=$this->transEsc('Library Card Name')?></th>
+        <? if ($this->multipleTargets): ?>
+        <th><?=$this->transEsc('login_target')?></th>
+        <? endif; ?>
+        <th><?=$this->transEsc('Username')?></th>
+        <th>&nbsp;</th>
+      </tr>
+      <? foreach ($this->libraryCards as $record): ?>
+        <tr>
+          <td><?=$this->escapeHtml($record['card_name'])?></td>
+          <? $username = $record['cat_username']; if ($this->multipleTargets): ?>
+            <? $target = ''; ?>
+            <? if (strstr($username, '.')): ?>
+              <? list($target, $username) = explode('.', $username, 2); ?>
+            <? endif; ?>
+            <td><?=$target ? $this->transEsc("source_$target", null, $target) : '&nbsp;' ?></td>
+          <? endif; ?>
+          <td><?=$this->escapeHtml($username)?></td>
+          <td>
+            <div class="btn-group">
+              <a class="btn btn-link" href="<?=$this->url('editLibraryCard') . $this->escapeHtmlAttr($record['id']) ?>" title="<?=$this->transEsc('Edit Library Card')?>"><i class="fa fa-edit"></i> <?=$this->transEsc('Edit')?></a>
+              <a class="btn btn-link dropdown-toggle" data-toggle="dropdown" href="<?=$this->url('librarycards-deletecard') ?>?cardID=<?=urlencode($record['id'])?>">
+                <i class="fa fa-trash-o"></i> <?=$this->transEsc('Delete')?>
+              </a>
+              <ul class="dropdown-menu">
+                <li><a href="<?=$this->url('librarycards-deletecard') ?>?cardID=<?=urlencode($record['id'])?>&amp;confirm=1"><?=$this->transEsc('confirm_dialog_yes') ?></a></li>
+                <li><a href="#"><?=$this->transEsc('confirm_dialog_no')?></a></li>
+              </ul>
+            </div>
+          </td>
+        </tr>
+      <? endforeach; ?>
+      </table>
+    <? endif; ?>
+
+    <div class="btn-group">
+      <a href="<?=$this->url('editLibraryCard') ?>NEW" class="btn btn-link" title="<?=$this->transEsc('Add a Library Card')?>"><i class="fa fa-edit"></i> <?=$this->transEsc('Add a Library Card')?></a>
+    </div>
+  </div>
+
+  <div class="<?=$this->layoutClass('sidebar')?>">
+    <?=$this->context($this)->renderInContext("myresearch/menu.phtml", array('active' => 'librarycards'))?>
+  </div>
+
+</div>
diff --git a/themes/bootstrap3/templates/librarycards/selectcard.phtml b/themes/bootstrap3/templates/librarycards/selectcard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..1622d4410e40c4c78e869d1995f5d131ca4e83ad
--- /dev/null
+++ b/themes/bootstrap3/templates/librarycards/selectcard.phtml
@@ -0,0 +1,24 @@
+<? if ($this->user): ?>
+  <?$cards = $this->user->getLibraryCards(); if ($cards->count() > 1): ?>
+    <form class="form-inline" action="<?=$this->url('librarycards-selectcard')?>" method="get">
+      <label for="library_card"><?=$this->transEsc('Library Card')?></label>
+      <select id="library_card" name="cardID" class="jumpMenu form-control">
+        <? foreach ($cards as $card): ?>
+          <?
+            $target = '';
+            $username = $card->cat_username;
+            if (strstr($username, '.')) {
+              list($target, $username) = explode('.', $username, 2);
+            }
+            $display = $this->transEsc($card->card_name ? $card->card_name : $card->cat_username);
+            if ($target) {
+              $display .= ' (' . $this->transEsc("source_$target", null, $target) . ')';
+            }
+          ?>
+          <option value="<?=$this->escapeHtmlAttr($card->id)?>"<?=$card->cat_username == $this->user->cat_username ? ' selected="selected"' : ''?>><?=$display ?></option>
+        <? endforeach; ?>
+      </select>
+      <noscript><input type="submit" class="btn btn-default" value="<?=$this->transEsc("Set")?>" /></noscript>
+    </form>
+  <? endif; ?>
+<? endif; ?>
diff --git a/themes/bootstrap3/templates/myresearch/checkedout.phtml b/themes/bootstrap3/templates/myresearch/checkedout.phtml
index 95362507c4df64ede39e6466803d6f51df34b3fd..0b13cfd0b774b988afdc9b08336a7f6d7bfc9595 100644
--- a/themes/bootstrap3/templates/myresearch/checkedout.phtml
+++ b/themes/bootstrap3/templates/myresearch/checkedout.phtml
@@ -11,6 +11,8 @@
     <h2><?=$this->transEsc('Your Checked Out Items')?></h2>
     <?=$this->flashmessages()?>
 
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <? if (!empty($this->transactions)): ?>
       <? if ($this->renewForm): ?>
       <form name="renewals" action="" method="post" id="renewals">
diff --git a/themes/bootstrap3/templates/myresearch/fines.phtml b/themes/bootstrap3/templates/myresearch/fines.phtml
index 1499521e2a58be81453d15246bf0ef0a438639c5..a8d702f29546728fd68524369d4513a597a6c32a 100644
--- a/themes/bootstrap3/templates/myresearch/fines.phtml
+++ b/themes/bootstrap3/templates/myresearch/fines.phtml
@@ -7,6 +7,9 @@
 ?>
 <div class="<?=$this->layoutClass('mainbody')?>">
   <h2><?=$this->transEsc('Your Fines')?></h2>
+
+  <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
   <? if (empty($this->fines)): ?>
     <?=$this->transEsc('You do not have any fines')?>
   <? else: ?>
diff --git a/themes/bootstrap3/templates/myresearch/holds.phtml b/themes/bootstrap3/templates/myresearch/holds.phtml
index 5f5472fa33711b9c28abbad15cc077c85a82cccd..c98bd68d3ebf89973a315acd2bf6e733c61edd8a 100644
--- a/themes/bootstrap3/templates/myresearch/holds.phtml
+++ b/themes/bootstrap3/templates/myresearch/holds.phtml
@@ -12,6 +12,8 @@
 
     <?=$this->flashmessages()?>
 
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <? if (!empty($this->recordList)): ?>
       <? if ($this->cancelForm): ?>
         <form name="cancelForm" class="inline" action="" method="post" id="cancelHold">
diff --git a/themes/bootstrap3/templates/myresearch/illrequests.phtml b/themes/bootstrap3/templates/myresearch/illrequests.phtml
index d4e553991f025a008657f9b636b7022b91dd6e74..44710595317909db8a6f5ae7c71bc79de85807b6 100644
--- a/themes/bootstrap3/templates/myresearch/illrequests.phtml
+++ b/themes/bootstrap3/templates/myresearch/illrequests.phtml
@@ -13,6 +13,8 @@
 
     <?=$this->flashmessages()?>
 
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <? if (!empty($this->recordList)): ?>
       <? if ($this->cancelForm): ?>
         <form name="cancelForm" class="inline" action="" method="post" id="cancelILLRequest">
diff --git a/themes/bootstrap3/templates/myresearch/menu.phtml b/themes/bootstrap3/templates/myresearch/menu.phtml
index 089db75f3bfcf8256b64e32ec5731da15ea61b68..e6dc6c923809f6824806ed7c25d7627d0813941d 100644
--- a/themes/bootstrap3/templates/myresearch/menu.phtml
+++ b/themes/bootstrap3/templates/myresearch/menu.phtml
@@ -43,6 +43,12 @@
         <span class="pull-right"><i class="fa fa-user"></i></span>
       </a>
     <? endif; ?>
+    <? $user = $this->auth()->isLoggedIn(); if ($user && $user->libraryCardsEnabled()): ?>
+      <a href="<?=$this->url('librarycards-home')?>" class="list-group-item<?=$this->active == 'librarycards' ? ' active' : ''?>">
+        <?=$this->transEsc('Library Cards')?>
+        <span class="pull-right"><i class="fa fa-barcode"></i></span>
+      </a>
+    <? endif; ?>
   <? endif; ?>
   <a href="<?=$this->url('search-history')?>?require_login" class="list-group-item<?=$this->active == 'history' ? ' active' : ''?>">
     <?=$this->transEsc('history_saved_searches')?>
diff --git a/themes/bootstrap3/templates/myresearch/newpassword.phtml b/themes/bootstrap3/templates/myresearch/newpassword.phtml
index 224c2ea0c7fc18388c80ad6c2286b090e25356c6..d504382bf7dc7f9d575d1e693a0c65555a09642b 100644
--- a/themes/bootstrap3/templates/myresearch/newpassword.phtml
+++ b/themes/bootstrap3/templates/myresearch/newpassword.phtml
@@ -13,6 +13,7 @@
 
 <h2><?=$this->transEsc('Create New Password') ?></h2>
 <?=$this->flashmessages() ?>
+
 <? if (!$this->auth()->getManager()->supportsPasswordChange($this->auth_method)): ?>
   <div class="error"><?=$this->transEsc('recovery_new_disabled') ?></div>
 <? elseif (!isset($this->hash)): ?>
diff --git a/themes/bootstrap3/templates/myresearch/profile.phtml b/themes/bootstrap3/templates/myresearch/profile.phtml
index 9ff77a1d02ba2ad3431042d3775024c6ad6ed4f3..099c48bc2a0bb64bb5e9319bd48b9f6b91fb919b 100644
--- a/themes/bootstrap3/templates/myresearch/profile.phtml
+++ b/themes/bootstrap3/templates/myresearch/profile.phtml
@@ -16,6 +16,9 @@
   <div class="<?=$this->layoutClass('mainbody')?>">
     <h2><?=$this->transEsc('Your Profile')?></h2>
     <?=$this->flashmessages();?>
+
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <table class="table table-striped">
       <?
         echo $this->renderArray(
diff --git a/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml b/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml
index accbfa7066effc2b3c783c0e04c3c7ac034a91e0..ce6c0f70726438561ce4aefff1f7746a3a9d8b8d 100644
--- a/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml
+++ b/themes/bootstrap3/templates/myresearch/storageretrievalrequests.phtml
@@ -11,6 +11,8 @@
 
     <?=$this->flashmessages()?>
 
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <? if (!empty($this->recordList)): ?>
       <? if ($this->cancelForm): ?>
         <form name="cancelForm" class="inline" action="" method="post" id="cancelStorageRetrievalRequest">
diff --git a/themes/jquerymobile/templates/RecordTab/holdingsils.phtml b/themes/jquerymobile/templates/RecordTab/holdingsils.phtml
index aa06764893c5c9fcae9a9d8049d7eba706f3d358..8e10e9888c4615542ee20481079a2e1519b3a943 100644
--- a/themes/jquerymobile/templates/RecordTab/holdingsils.phtml
+++ b/themes/jquerymobile/templates/RecordTab/holdingsils.phtml
@@ -8,6 +8,9 @@
     // Set page title.
     $this->headTitle($this->translate('Holdings') . ': ' . $this->driver->getBreadcrumb());
 ?>
+
+<?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
 <? if ($offlineMode == "ils-offline"): ?>
   <div class="sysInfo">
     <h2><?=$this->transEsc('ils_offline_title')?></h2>
diff --git a/themes/jquerymobile/templates/librarycards/editcard.phtml b/themes/jquerymobile/templates/librarycards/editcard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..222360f807b7ca94007112732ab884632d47cdc6
--- /dev/null
+++ b/themes/jquerymobile/templates/librarycards/editcard.phtml
@@ -0,0 +1,50 @@
+<?
+    // Set up page title:
+    $pageTitle = empty($this->card->id) ? 'Add a Library Card' : "Edit Library Card";
+    $this->headTitle($this->translate($pageTitle));
+?>
+<div data-role="page" id="LibraryCards-editCard">
+  <?=$this->mobileMenu()->header()?>
+  <div data-role="content">
+    <h3><?=$this->transEsc($pageTitle); ?></h3>
+
+    <?=$this->flashmessages()?>
+
+    <form method="post" name="<?=empty($this->card->id) ? 'newCardForm' : 'editCardForm'?>" data-ajax="false">
+      <label class="displayBlock" for="card_name"><?=$this->transEsc('Library Card Name'); ?>:</label>
+      <input id="card_name" type="text" name="card_name" value="<?=$this->escapeHtmlAttr($this->cardName)?>" size="50"
+        class="mainFocus <?=$this->jqueryValidation(array('required'=>'This field is required')) ?>"/>
+      <br class="clear"/>
+
+      <? if ($this->targets !== null): ?>
+      <label class="displayBlock" for="login_target"><?=$this->transEsc('login_target')?>:</label>
+      <select id="login_target" name="target">
+      <? foreach ($this->targets as $target): ?>
+        <option value="<?=$this->escapeHtmlAttr($target)?>"<?=($target == $this->target ? ' selected="selected"' : '')?>><?=$this->transEsc("source_$target", null, $target)?></option>
+      <? endforeach; ?>
+      </select>
+      <br class="clear"/>
+      <? endif; ?>
+
+      <label class="displayBlock" for="login_username"><?=$this->transEsc('Username')?>:</label>
+      <input id="login_username" type="text" name="username" value="<?=$this->escapeHtmlAttr($this->username)?>" size="15" class="<?=$this->jqueryValidation(array('required'=>'This field is required'))?>"/>
+      <br class="clear"/>
+      <label class="displayBlock" for="login_password"><?=$this->transEsc('Password')?>:</label>
+      <input id="login_password" type="password" name="password" value="<?=$this->escapeHtmlAttr($this->password)?>" size="15" class="<?=$this->jqueryValidation(array('required'=>'This field is required'))?>"/>
+      <br class="clear"/>
+
+      <div class="ui-body ui-body-b">
+        <fieldset class="ui-grid-a">
+            <div class="ui-block-a">
+                <input class="button" data-role="button" data-theme="b" type="submit" name="submit" value="<?=$this->transEsc('Save') ?>"/>
+            </div>
+            <? if (!empty($this->card->id)): ?>
+              <div class="ui-block-b">
+                <a data-role="button" data-theme="c" data-mini="true" href="<?=$this->url('librarycards-deletecard') ?>?cardID=<?=urlencode($this->card->id)?>" id="deleteCard<?=$this->card->id ?>" title="<?=$this->transEsc("Delete")?>"><?=$this->transEsc("Delete")?></a>
+              </div>
+            <? endif; ?>
+        </fieldset>
+      </div>
+    </div>
+  <?=$this->mobileMenu()->footer()?>
+</div>
diff --git a/themes/jquerymobile/templates/librarycards/home.phtml b/themes/jquerymobile/templates/librarycards/home.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..dbf4e9d66b65c3447f60759b9cad888bf8676dbc
--- /dev/null
+++ b/themes/jquerymobile/templates/librarycards/home.phtml
@@ -0,0 +1,45 @@
+<?
+    // Set up page title:
+    $this->headTitle($this->translate('Library Cards'));
+
+    // Set up extra button for header:
+    $extraButton = '<a rel="external" href="'
+            . $this->url('editLibraryCard', array('id' => 'NEW'))
+            . '" data-icon="gear" class="ui-btn-left">'
+            . $this->transEsc("Add a Library Card")
+            . '</a>';
+?>
+
+<div data-role="page" id="LibraryCards-home" class="results-page">
+  <?=$this->mobileMenu()->header(array('extraButtons'=>array($extraButton)))?>
+  <div data-role="content">
+
+    <?=$this->flashmessages();?>
+
+    <? if ($this->libraryCards->count() == 0): ?>
+      <?=$this->transEsc('You do not have any library cards')?>
+    <? else: ?>
+      <h3><?=$this->transEsc('Library Cards')?></h3>
+
+      <ul class="results librarycards" data-role="listview" data-split-theme="d" data-inset="false">
+      <? foreach ($this->libraryCards as $record): ?>
+        <li>
+          <a rel="external" href="<?=$this->url('editLibraryCard') . $this->escapeHtmlAttr($record['id']) ?>">
+
+          <?=$this->escapeHtml($record['card_name'])?><br />
+          <? $username = $record['cat_username']; if ($this->multipleTargets): ?>
+            <? $target = ''; ?>
+            <? if (strstr($username, '.')): ?>
+              <? list($target, $username) = explode('.', $username, 2); ?>
+            <? endif; ?>
+            <?=$this->transEsc('login_target')?>: <?=$target ? $this->transEsc("source_$target", null, $target) : '&nbsp;' ?><br />
+          <? endif; ?>
+          <?=$this->transEsc('Username')?>: <?=$this->escapeHtml($username)?>
+          </a>
+        </li>
+        <? endforeach; ?>
+      </ul>
+    <? endif; ?>
+  </div>
+  <?=$this->mobileMenu()->footer()?>
+</div>
diff --git a/themes/jquerymobile/templates/librarycards/selectcard.phtml b/themes/jquerymobile/templates/librarycards/selectcard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..f58b5751bb6593381cb4412c9e8ba074523c620d
--- /dev/null
+++ b/themes/jquerymobile/templates/librarycards/selectcard.phtml
@@ -0,0 +1,30 @@
+<? if ($this->user): ?>
+  <?$cards = $this->user->getLibraryCards(); if ($cards->count() > 1): ?>
+    <form id="library_card_form" action="<?=$this->url('librarycards-selectcard')?>" method="get" data-ajax="false">
+      <label for="library_card"><?=$this->transEsc('Library Card')?></label>
+      <select id="library_card" name="cardID" class="jumpMenu">
+        <? foreach ($cards as $card): ?>
+          <?
+            $target = '';
+            $username = $card->cat_username;
+            if (strstr($username, '.')) {
+              list($target, $username) = explode('.', $username, 2);
+            }
+            $display = $this->transEsc($card->card_name ? $card->card_name : $card->cat_username);
+            if ($target) {
+              $display .= ' (' . $this->transEsc("source_$target", null, $target) . ')';
+            }
+          ?>
+          <option value="<?=$this->escapeHtmlAttr($card->id)?>"<?=$card->cat_username == $this->user->cat_username ? ' selected="selected"' : ''?>><?=$display ?></option>
+        <? endforeach; ?>
+      </select>
+      <noscript><input type="submit" class="btn btn-default" value="<?=$this->transEsc("Set")?>" /></noscript>
+    </form>
+    <script type="text/javascript">
+      $('#library_card').die('change');
+      $('#library_card').live('change', function() {
+        $('#library_card_form').submit();
+      });
+    </script>
+  <? endif; ?>
+<? endif; ?>
diff --git a/themes/jquerymobile/templates/myresearch/checkedout.phtml b/themes/jquerymobile/templates/myresearch/checkedout.phtml
index fa7ecdb1b9cfe81cdce29637dc67653cd1c05c0a..c570eb6cec17baa314b03fb088a3e6fbb232d330 100644
--- a/themes/jquerymobile/templates/myresearch/checkedout.phtml
+++ b/themes/jquerymobile/templates/myresearch/checkedout.phtml
@@ -8,6 +8,8 @@
     <h3><?=$this->transEsc('Your Checked Out Items')?></h3>
     <?=$this->flashmessages()?>
 
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <? if (!empty($this->transactions)): ?>
       <? if ($this->renewForm): ?>
       <form name="renewals" method="post" id="renewals">
diff --git a/themes/jquerymobile/templates/myresearch/fines.phtml b/themes/jquerymobile/templates/myresearch/fines.phtml
index fb4f7200a9f1e14b9c588d27fc07c1dc7ff04d89..ded56d0742a9ce13a09968e9eac1468371b60597 100644
--- a/themes/jquerymobile/templates/myresearch/fines.phtml
+++ b/themes/jquerymobile/templates/myresearch/fines.phtml
@@ -6,6 +6,9 @@
   <?=$this->mobileMenu()->header()?>
   <div data-role="content">
     <h3><?=$this->transEsc('Your Fines')?></h3>
+
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <? if (empty($this->fines)): ?>
       <p><?=$this->transEsc('You do not have any fines')?></p>
     <? else: ?>
diff --git a/themes/jquerymobile/templates/myresearch/footer-navbar.phtml b/themes/jquerymobile/templates/myresearch/footer-navbar.phtml
index 624e3b6788a5a3024eb42751c5c39749b073fe86..52bad29bdb93438df44019dcf6c66fe53bc841c3 100644
--- a/themes/jquerymobile/templates/myresearch/footer-navbar.phtml
+++ b/themes/jquerymobile/templates/myresearch/footer-navbar.phtml
@@ -1,14 +1,41 @@
-<? if ($this->auth()->isLoggedIn()): ?>
+<? $user = $this->auth()->isLoggedIn(); if ($user): ?>
+  <?
+    $rows = [];
+    if ($this->userlist()->getMode() !== 'disabled') {
+      $rows[] = '<li><a rel="external" '
+        . ($this->layout()->templateName=="mylist" ? ' class="ui-btn-active"' : '')
+        . ' href="' . $this->url('myresearch-favorites') . '">'
+        . $this->transEsc('Favorites') . '</a></li>';
+    }
+    $rows[] = '<li><a rel="external" '
+      . ($this->layout()->templateName=="history" ? ' class="ui-btn-active"' : '')
+      . ' href="' . $this->url('search-history') . '?require_login">'
+      . $this->transEsc('History') . '</a></li>';
+    if ($this->auth()->getManager()->supportsPasswordChange()) {
+      $rows[] = '<li><a rel="external" href="'
+        . $this->url('myresearch-changepassword') . '">'
+        . $this->transEsc("Change Password") . '</a></li>';
+    }
+    if ($user->libraryCardsEnabled()) {
+      $rows[] = '<li><a rel="external" href="'
+        . $this->url('librarycards-home') . '">'
+        . $this->transEsc('Library Cards') . '</a></li>';
+    }
+    $rows[] = '<li><a rel="external" href="'
+      . $this->url('myresearch-logout') . '">' . $this->transEsc("Log Out")
+      . '</a></li>';
+  ?>
   <div data-role="navbar">
     <ul>
-      <? if ($this->userlist()->getMode() !== 'disabled'): ?>
-        <li><a rel="external" <?=$this->layout()->templateName=="mylist" ? ' class="ui-btn-active"' : ''?> href="<?=$this->url('myresearch-favorites')?>"><?=$this->transEsc('Favorites')?></a></li>
-      <? endif; ?>
-      <li><a rel="external" <?=$this->layout()->templateName=="history" ? ' class="ui-btn-active"' : ''?> href="<?=$this->url('search-history')?>?require_login"><?=$this->transEsc('History')?></a></li>
-      <? if ($this->auth()->getManager()->supportsPasswordChange()): ?>
-        <li><a rel="external" href="<?=$this->url('myresearch-changepassword')?>"><?=$this->transEsc("Change Password")?></a></li>
-      <? endif; ?>
-      <li><a rel="external" href="<?=$this->url('myresearch-logout')?>"><?=$this->transEsc("Log Out")?></a></li>
+      <? foreach ($rows as $i => $row): ?>
+        <? if ($i > 0 && $i % 3 === 0): ?>
+    </ul>
+  </div>
+  <div data-role="navbar">
+    <ul>
+        <? endif; ?>
+        <?=$row?>
+      <? endforeach; ?>
     </ul>
   </div>
 <? else: ?>
diff --git a/themes/jquerymobile/templates/myresearch/holds.phtml b/themes/jquerymobile/templates/myresearch/holds.phtml
index 5d7051294005130bf9fb0a1e79e0c1093072e14b..ed6ae2e07418cf0ccf6821c5f13671c98529ea68 100644
--- a/themes/jquerymobile/templates/myresearch/holds.phtml
+++ b/themes/jquerymobile/templates/myresearch/holds.phtml
@@ -9,6 +9,8 @@
 
     <?=$this->flashmessages()?>
 
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <? if (!empty($this->recordList)): ?>
       <? if ($this->cancelForm): ?>
         <form name="cancelForm" method="post" id="cancelHold">
diff --git a/themes/jquerymobile/templates/myresearch/profile.phtml b/themes/jquerymobile/templates/myresearch/profile.phtml
index f8c403210fffd25e0a343ae53a60df106226da61..b5afd066898154158d1d3265fbcc124d4e20e73e 100644
--- a/themes/jquerymobile/templates/myresearch/profile.phtml
+++ b/themes/jquerymobile/templates/myresearch/profile.phtml
@@ -7,6 +7,9 @@
   <div data-role="content">
     <h3><?=$this->transEsc('Your Profile')?></h3>
     <?=$this->flashmessages();?>
+
+    <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', array('user' => $this->auth()->isLoggedIn())); ?>
+
     <dl class="biblio">
     <?
         echo $this->renderArray(