diff --git a/composer.json b/composer.json
index 8abdb27aebecd99926dc219ea225f8d261f6d06e..03610875516acbc8e6643b3dab18cff3c52a9391 100644
--- a/composer.json
+++ b/composer.json
@@ -28,7 +28,7 @@
         "pear/http_request2": "2.3.0",
         "pear/validate_ispn": "dev-master",
         "phing/phing": "2.16.1",
-        "serialssolutions/summon": "1.1.0",
+        "serialssolutions/summon": "1.2.0",
         "symfony/yaml": "3.4.10",
         "swagger-api/swagger-ui": "2.2.10",
         "vufind-org/vufindcode": "1.1.0",
diff --git a/composer.lock b/composer.lock
index a8c7b8b9809b06ad75480e841b393f853049a8f5..4975e8087c56664628283c75870e47936653f177 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "2de76da807eebd174adf10ac0713ffa5",
+    "content-hash": "82a765a499e6baa6261b9dbd74c9f849",
     "packages": [
         {
             "name": "ahand/mobileesp",
@@ -1628,16 +1628,16 @@
         },
         {
             "name": "serialssolutions/summon",
-            "version": "v1.1.0",
+            "version": "v1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/summon/Summon.php.git",
-                "reference": "170beb3b0505f2047613b101213379537e651ea2"
+                "reference": "d12150c53585e9b4275888754846da81c12acd71"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/summon/Summon.php/zipball/170beb3b0505f2047613b101213379537e651ea2",
-                "reference": "170beb3b0505f2047613b101213379537e651ea2",
+                "url": "https://api.github.com/repos/summon/Summon.php/zipball/d12150c53585e9b4275888754846da81c12acd71",
+                "reference": "d12150c53585e9b4275888754846da81c12acd71",
                 "shasum": ""
             },
             "type": "library",
@@ -1657,7 +1657,7 @@
                 }
             ],
             "description": "Library for interacting with Serials Solutions' Summon API.",
-            "time": "2017-05-17T20:36:35+00:00"
+            "time": "2018-07-18T14:28:58+00:00"
         },
         {
             "name": "swagger-api/swagger-ui",
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 40ea0d9483f61ea0041a48ab78809070e22cea97..2628622831f7311d2c9d07f7572aac71650ecdac 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -362,6 +362,7 @@ $config = [
             'VuFind\QRCode\Loader' => 'VuFind\QRCode\LoaderFactory',
             'VuFind\Recommend\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
             'VuFind\Record\Cache' => 'VuFind\Record\CacheFactory',
+            'VuFind\Record\FallbackLoader\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
             'VuFind\Record\Loader' => 'VuFind\Record\LoaderFactory',
             'VuFind\Record\Router' => 'VuFind\Record\RouterFactory',
             'VuFind\RecordDriver\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
@@ -520,6 +521,7 @@ $config = [
             'hierarchy_treerenderer' => [ /* see VuFind\Hierarchy\TreeRenderer\PluginManager for defaults */ ],
             'ils_driver' => [ /* See VuFind\ILS\Driver\PluginManager for defaults */ ],
             'recommend' => [ /* See VuFind\Recommend\PluginManager for defaults */ ],
+            'record_fallbackloader' => [ /* See VuFind\Record\FallbackLoader\PluginManager for defaults */ ],
             'recorddriver' => [ /* See VuFind\RecordDriver\PluginManager for defaults */ ],
             'recordtab' => [ /* See VuFind\RecordTab\PluginManager for defaults */ ],
             'related' => [ /* See VuFind\Related\PluginManager for defaults */ ],
diff --git a/module/VuFind/sql/migrations/pgsql/5.1/001-modify-resource-columns.sql b/module/VuFind/sql/migrations/pgsql/5.1/001-modify-resource-columns.sql
new file mode 100644
index 0000000000000000000000000000000000000000..8ef7bb71a5b4eb6dddb5ec6340af1aec4ae33c74
--- /dev/null
+++ b/module/VuFind/sql/migrations/pgsql/5.1/001-modify-resource-columns.sql
@@ -0,0 +1,6 @@
+--
+-- Modifications to table `resource`
+--
+
+ALTER TABLE "resource"
+  ADD COLUMN extra_metadata text DEFAULT NULL;
diff --git a/module/VuFind/sql/mysql.sql b/module/VuFind/sql/mysql.sql
index 57593a89c161a3c8797f8bb440aed1d856365ae8..58bff5275583acf7ae4accd0da1e31110372a181 100644
--- a/module/VuFind/sql/mysql.sql
+++ b/module/VuFind/sql/mysql.sql
@@ -74,6 +74,7 @@ CREATE TABLE `resource` (
   `author` varchar(255) DEFAULT NULL,
   `year` mediumint(6) DEFAULT NULL,
   `source` varchar(50) NOT NULL DEFAULT 'Solr',
+  `extra_metadata` mediumtext DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `record_id` (`record_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/module/VuFind/sql/pgsql.sql b/module/VuFind/sql/pgsql.sql
index 76d41610e8dafd9c6a00ab16532f479ded9f9de5..c92723756a2925261c632607db7af0f1fa4a332b 100644
--- a/module/VuFind/sql/pgsql.sql
+++ b/module/VuFind/sql/pgsql.sql
@@ -31,6 +31,7 @@ title varchar(255) NOT NULL DEFAULT '',
 author varchar(255) DEFAULT NULL,
 year int DEFAULT NULL,
 source varchar(50) NOT NULL DEFAULT 'Solr',
+extra_metadata text DEFAULT NULL,
 PRIMARY KEY (id)
 );
 CREATE INDEX resource_record_id_idx ON resource (record_id);
diff --git a/module/VuFind/src/VuFind/Db/Row/Resource.php b/module/VuFind/src/VuFind/Db/Row/Resource.php
index e2ecfd618b77628f4a73eb978fdb2d85a11b9d71..d0f14a80da830adf3173cade7840597598413bb4 100644
--- a/module/VuFind/src/VuFind/Db/Row/Resource.php
+++ b/module/VuFind/src/VuFind/Db/Row/Resource.php
@@ -196,6 +196,9 @@ class Resource extends RowGateway implements \VuFind\Db\Table\DbTableAwareInterf
             $this->year = intval($year);
         }
 
+        if ($extra = $driver->tryMethod('getExtraResourceMetadata')) {
+            $this->extra_metadata = json_encode($extra);
+        }
         return $this;
     }
 }
diff --git a/module/VuFind/src/VuFind/Db/Table/Resource.php b/module/VuFind/src/VuFind/Db/Table/Resource.php
index 4fe1921e2568360d68aa59368f696ae8c845bb6d..fd28573bd3344ee2eb64b20be40e67ab5a2b1896 100644
--- a/module/VuFind/src/VuFind/Db/Table/Resource.php
+++ b/module/VuFind/src/VuFind/Db/Table/Resource.php
@@ -222,6 +222,54 @@ class Resource extends Gateway
         return $this->select($callback);
     }
 
+    /**
+     * Update the database to reflect a changed record identifier.
+     *
+     * @param string $oldId  Original record ID
+     * @param string $newId  Revised record ID
+     * @param string $source Record source
+     *
+     * @return void
+     */
+    public function updateRecordId($oldId, $newId, $source = DEFAULT_SEARCH_BACKEND)
+    {
+        if ($oldId !== $newId
+            && $resource = $this->findResource($oldId, $source)
+        ) {
+            // Do this as a transaction to prevent odd behavior:
+            $connection = $this->getAdapter()->getDriver()->getConnection();
+            $connection->beginTransaction();
+            // Does the new ID already exist?
+            if ($newResource = $this->findResource($newId, $source)) {
+                // Special case: merge new ID and old ID:
+                $tableObjects = [];
+                foreach (['comments', 'userresource', 'resourcetags'] as $table) {
+                    $tableObjects[$table] = $this->getDbTable($table);
+                    $tableObjects[$table]->update(
+                        ['resource_id' => $newResource->id],
+                        ['resource_id' => $resource->id]
+                    );
+                }
+                $resource->delete();
+            } else {
+                // Default case: just update the record ID:
+                $resource->record_id = $newId();
+                $resource->save();
+            }
+            // Done -- commit the transaction:
+            $connection->commit();
+
+            // Deduplicate rows where necessary (this can be safely done outside
+            // of the transaction):
+            if (isset($tableObjects['resourcetags'])) {
+                $tableObjects['resourcetags']->deduplicate();
+            }
+            if (isset($tableObjects['userresource'])) {
+                $tableObjects['userresource']->deduplicate();
+            }
+        }
+    }
+
     /**
      * Apply a sort parameter to a query on the resource table.
      *
diff --git a/module/VuFind/src/VuFind/Db/Table/ResourceTags.php b/module/VuFind/src/VuFind/Db/Table/ResourceTags.php
index 6dfe17c2b1b17a9b08c198551de0ab1c0628f12f..21b8883402fe0c53047f50e5834495e8753b6dfc 100644
--- a/module/VuFind/src/VuFind/Db/Table/ResourceTags.php
+++ b/module/VuFind/src/VuFind/Db/Table/ResourceTags.php
@@ -591,4 +591,67 @@ class ResourceTags extends Gateway
         $this->delete($callback);
         return count($ids);
     }
+
+    /**
+     * Get a list of duplicate rows (this sometimes happens after merging IDs,
+     * for example after a Summon resource ID changes).
+     *
+     * @return mixed
+     */
+    public function getDuplicates()
+    {
+        $callback = function ($select) {
+            $select->columns(
+                [
+                    'resource_id' => new Expression(
+                        'MIN(?)', ['resource_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'tag_id' => new Expression(
+                        'MIN(?)', ['tag_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'list_id' => new Expression(
+                        'MIN(?)', ['list_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'user_id' => new Expression(
+                        'MIN(?)', ['user_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'cnt' => new Expression(
+                        'COUNT(?)', ['resource_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'id' => new Expression(
+                        'MIN(?)', ['id'], [Expression::TYPE_IDENTIFIER]
+                    )
+                ]
+            );
+            $select->group(['resource_id', 'tag_id', 'list_id', 'user_id']);
+            $select->having('COUNT(resource_id) > 1');
+        };
+        return $this->select($callback);
+    }
+
+    /**
+     * Deduplicate rows (sometimes necessary after merging foreign key IDs).
+     *
+     * @return void
+     */
+    public function deduplicate()
+    {
+        foreach ($this->getDuplicates() as $dupe) {
+            $callback = function ($select) use ($dupe) {
+                // match on all relevant IDs in duplicate group
+                $select->where(
+                    [
+                        'resource_id' => $dupe['resource_id'],
+                        'tag_id' => $dupe['tag_id'],
+                        'list_id' => $dupe['list_id'],
+                        'user_id' => $dupe['user_id'],
+                    ]
+                );
+                // getDuplicates returns the minimum id in the set, so we want to
+                // delete all of the duplicates with a higher id value.
+                $select->where->greaterThan('id', $dupe['id']);
+            };
+            $this->delete($callback);
+        }
+    }
 }
diff --git a/module/VuFind/src/VuFind/Db/Table/UserResource.php b/module/VuFind/src/VuFind/Db/Table/UserResource.php
index ce1ef8a8cb2d724b60677f509e47bd9a69d6c9f5..a59afaa4757d121985afa40a0bcca1f86c41d16e 100644
--- a/module/VuFind/src/VuFind/Db/Table/UserResource.php
+++ b/module/VuFind/src/VuFind/Db/Table/UserResource.php
@@ -210,4 +210,82 @@ class UserResource extends Gateway
         $result = $statement->execute();
         return (array)$result->current();
     }
+
+    /**
+     * Get a list of duplicate rows (this sometimes happens after merging IDs,
+     * for example after a Summon resource ID changes).
+     *
+     * @return mixed
+     */
+    public function getDuplicates()
+    {
+        $callback = function ($select) {
+            $select->columns(
+                [
+                    'resource_id' => new Expression(
+                        'MIN(?)', ['resource_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'list_id' => new Expression(
+                        'MIN(?)', ['list_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'user_id' => new Expression(
+                        'MIN(?)', ['user_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'cnt' => new Expression(
+                        'COUNT(?)', ['resource_id'], [Expression::TYPE_IDENTIFIER]
+                    ),
+                    'id' => new Expression(
+                        'MIN(?)', ['id'], [Expression::TYPE_IDENTIFIER]
+                    )
+                ]
+            );
+            $select->group(['resource_id', 'list_id', 'user_id']);
+            $select->having('COUNT(resource_id) > 1');
+        };
+        return $this->select($callback);
+    }
+
+    /**
+     * Deduplicate rows (sometimes necessary after merging foreign key IDs).
+     *
+     * @return void
+     */
+    public function deduplicate()
+    {
+        foreach ($this->getDuplicates() as $dupe) {
+            // Do this as a transaction to prevent odd behavior:
+            $connection = $this->getAdapter()->getDriver()->getConnection();
+            $connection->beginTransaction();
+
+            // Merge notes together...
+            $mainCriteria = [
+                'resource_id' => $dupe['resource_id'],
+                'list_id' => $dupe['list_id'],
+                'user_id' => $dupe['user_id'],
+            ];
+            $dupeRows = $this->select($mainCriteria);
+            $notes = [];
+            foreach ($dupeRows as $row) {
+                if (!empty($row['notes'])) {
+                    $notes[] = $row['notes'];
+                }
+            }
+            $this->update(
+                ['notes' => implode(' ', $notes)],
+                ['id' => $dupe['id']]
+            );
+            // Now delete extra rows...
+            $callback = function ($select) use ($dupe, $mainCriteria) {
+                // match on all relevant IDs in duplicate group
+                $select->where($mainCriteria);
+                // getDuplicates returns the minimum id in the set, so we want to
+                // delete all of the duplicates with a higher id value.
+                $select->where->greaterThan('id', $dupe['id']);
+            };
+            $this->delete($callback);
+
+            // Done -- commit the transaction:
+            $connection->commit();
+        }
+    }
 }
diff --git a/module/VuFind/src/VuFind/Record/FallbackLoader/FallbackLoaderInterface.php b/module/VuFind/src/VuFind/Record/FallbackLoader/FallbackLoaderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..707e1930d22024b64d1ecedbf6022fd0649ac8cf
--- /dev/null
+++ b/module/VuFind/src/VuFind/Record/FallbackLoader/FallbackLoaderInterface.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Record fallback loader interface
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2018.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Site
+ */
+namespace VuFind\Record\FallbackLoader;
+
+/**
+ * Record fallback loader interface
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Site
+ */
+interface FallbackLoaderInterface
+{
+    /**
+     * Given an array of IDs that failed to load, try to find them using a
+     * fallback mechanism.
+     *
+     * @param array $ids IDs to load
+     *
+     * @return array
+     */
+    public function load($ids);
+}
diff --git a/module/VuFind/src/VuFind/Record/FallbackLoader/PluginManager.php b/module/VuFind/src/VuFind/Record/FallbackLoader/PluginManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb9db19b3bb96506ef35aa943ef5f8ed5e94ee97
--- /dev/null
+++ b/module/VuFind/src/VuFind/Record/FallbackLoader/PluginManager.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Record fallback loader plugin manager
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2018.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:record_drivers Wiki
+ */
+namespace VuFind\Record\FallbackLoader;
+
+/**
+ * Record fallback loader plugin manager
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:record_drivers Wiki
+ */
+class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
+{
+    /**
+     * Default plugin aliases.
+     *
+     * @var array
+     */
+    protected $aliases = [
+        'summon' => 'VuFind\Record\FallbackLoader\Summon',
+    ];
+
+    /**
+     * Default plugin factories.
+     *
+     * @var array
+     */
+    protected $factories = [
+        'VuFind\Record\FallbackLoader\Summon' =>
+            'VuFind\Record\FallbackLoader\SummonFactory',
+    ];
+
+    /**
+     * Return the name of the base class or interface that plug-ins must conform
+     * to.
+     *
+     * @return string
+     */
+    protected function getExpectedInterface()
+    {
+        return 'VuFind\Record\FallbackLoader\FallbackLoaderInterface';
+    }
+}
diff --git a/module/VuFind/src/VuFind/Record/FallbackLoader/Summon.php b/module/VuFind/src/VuFind/Record/FallbackLoader/Summon.php
new file mode 100644
index 0000000000000000000000000000000000000000..599a541cc0fd3e8ffb52a1b615a3b5a69dfef750
--- /dev/null
+++ b/module/VuFind/src/VuFind/Record/FallbackLoader/Summon.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Summon record fallback loader
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2018.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Site
+ */
+namespace VuFind\Record\FallbackLoader;
+
+use SerialsSolutions\Summon\Zend2 as Connector;
+use VuFind\Db\Table\Resource;
+use VuFindSearch\Backend\Summon\Backend;
+use VuFindSearch\ParamBag;
+
+/**
+ * Summon record fallback loader
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Site
+ */
+class Summon implements FallbackLoaderInterface
+{
+    /**
+     * Resource table
+     *
+     * @var Resource
+     */
+    protected $table;
+
+    /**
+     * Summon backend
+     *
+     * @var Backend
+     */
+    protected $backend;
+
+    /**
+     * Constructor
+     *
+     * @param Resource $table   Resource database table object
+     * @param Backend  $backend Summon search backend
+     */
+    public function __construct(Resource $table, Backend $backend)
+    {
+        $this->table = $table;
+        $this->backend = $backend;
+    }
+
+    /**
+     * Given an array of IDs that failed to load, try to find them using a
+     * fallback mechanism.
+     *
+     * @param array $ids IDs to load
+     *
+     * @return array
+     */
+    public function load($ids)
+    {
+        $retVal = [];
+        foreach ($ids as $id) {
+            foreach ($this->fetchSingleRecord($id) as $record) {
+                $this->updateRecord($record, $id);
+                $retVal[] = $record;
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Fetch a single record (null if not found).
+     *
+     * @param string $id ID to load
+     *
+     * @return \VuFindSearch\Response\RecordCollectionInterface
+     */
+    protected function fetchSingleRecord($id)
+    {
+        $resource = $this->table->findResource($id, 'Summon');
+        if ($resource && ($extra = json_decode($resource->extra_metadata, true))) {
+            $bookmark = $extra['bookmark'] ?? '';
+            if (strlen($bookmark) > 0) {
+                $params = new ParamBag(
+                    ['summonIdType' => Connector::IDENTIFIER_BOOKMARK]
+                );
+                return $this->backend->retrieve($bookmark, $params);
+            }
+        }
+        return new \VuFindSearch\Backend\Summon\Response\RecordCollection([]);
+    }
+
+    /**
+     * When a record ID has changed, update the record driver and database to
+     * reflect the changes.
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $record     Record to update
+     * @param string                            $previousId Old ID of record
+     *
+     * @return void
+     */
+    protected function updateRecord($record, $previousId)
+    {
+        // Update the record driver with knowledge of the previous identifier...
+        $record->setPreviousUniqueId($previousId);
+
+        // Update the database to replace the obsolete identifier...
+        $this->table->updateRecordId($previousId, $record->getUniqueId(), 'Summon');
+    }
+}
diff --git a/module/VuFind/src/VuFind/Record/FallbackLoader/SummonFactory.php b/module/VuFind/src/VuFind/Record/FallbackLoader/SummonFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..c3d0d04930cd0577b56c03c0fc2722f4a12ef0dc
--- /dev/null
+++ b/module/VuFind/src/VuFind/Record/FallbackLoader/SummonFactory.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Summon record fallback loader factory
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2018.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Site
+ */
+namespace VuFind\Record\FallbackLoader;
+
+use Interop\Container\ContainerInterface;
+use Zend\ServiceManager\Factory\FactoryInterface;
+
+/**
+ * Summon record fallback loader factory
+ *
+ * @category VuFind
+ * @package  Record
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Site
+ */
+class SummonFactory implements FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        $backendManager = $container->get('VuFind\Search\BackendManager');
+        return new $requestedName(
+            $container->get('VuFind\Db\Table\PluginManager')->get('resource'),
+            $backendManager->get('Summon')
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/Record/Loader.php b/module/VuFind/src/VuFind/Record/Loader.php
index 32c4802b1e8893af4719434685d21221f818a7f7..106588a3fceee6a8ae303af5d1d5428459b5fc97 100644
--- a/module/VuFind/src/VuFind/Record/Loader.php
+++ b/module/VuFind/src/VuFind/Record/Loader.php
@@ -30,6 +30,7 @@
 namespace VuFind\Record;
 
 use VuFind\Exception\RecordMissing as RecordMissingException;
+use VuFind\Record\FallbackLoader\PluginManager as FallbackLoader;
 use VuFind\RecordDriver\PluginManager as RecordFactory;
 use VuFindSearch\Service as SearchService;
 
@@ -68,19 +69,29 @@ class Loader implements \Zend\Log\LoggerAwareInterface
      */
     protected $recordCache;
 
+    /**
+     * Fallback record loader
+     *
+     * @var FallbackLoader
+     */
+    protected $fallbackLoader;
+
     /**
      * Constructor
      *
-     * @param SearchService $searchService Search service
-     * @param RecordFactory $recordFactory Record loader
-     * @param Cache         $recordCache   Record Cache
+     * @param SearchService  $searchService  Search service
+     * @param RecordFactory  $recordFactory  Record loader
+     * @param Cache          $recordCache    Record Cache
+     * @param FallbackLoader $fallbackLoader Fallback record loader
      */
     public function __construct(SearchService $searchService,
-        RecordFactory $recordFactory, Cache $recordCache = null
+        RecordFactory $recordFactory, Cache $recordCache = null,
+        FallbackLoader $fallbackLoader = null
     ) {
         $this->searchService = $searchService;
         $this->recordFactory = $recordFactory;
         $this->recordCache = $recordCache;
+        $this->fallbackLoader = $fallbackLoader;
     }
 
     /**
@@ -177,6 +188,20 @@ class Loader implements \Zend\Log\LoggerAwareInterface
             }
         }
 
+        $retVal = $genuineRecords;
+        if ($list->hasUnchecked() && $this->fallbackLoader
+            && $this->fallbackLoader->has($source)
+        ) {
+            $fallbackRecords = $this->fallbackLoader->get($source)
+                ->load($list->getUnchecked());
+            foreach ($fallbackRecords as $record) {
+                $retVal[] = $record;
+                if (!$list->check($record->getUniqueId())) {
+                    $list->check($record->tryMethod('getPreviousUniqueId'));
+                }
+            }
+        }
+
         if ($list->hasUnchecked() && null !== $this->recordCache
             && $this->recordCache->isFallback($source)
         ) {
@@ -186,7 +211,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface
         }
 
         // Merge records found in cache and records loaded from original $source
-        $retVal = $genuineRecords;
         foreach ($cachedRecords as $cachedRecord) {
             $retVal[] = $cachedRecord;
         }
diff --git a/module/VuFind/src/VuFind/Record/LoaderFactory.php b/module/VuFind/src/VuFind/Record/LoaderFactory.php
index 7d508f7f72d6b225f885da11eaed61ba76bb2f35..8adb957982c8644f1e96cd00f564b0b2063e1088 100644
--- a/module/VuFind/src/VuFind/Record/LoaderFactory.php
+++ b/module/VuFind/src/VuFind/Record/LoaderFactory.php
@@ -64,7 +64,8 @@ class LoaderFactory implements FactoryInterface
         return new $requestedName(
             $container->get('VuFindSearch\Service'),
             $container->get('VuFind\RecordDriver\PluginManager'),
-            $container->get('VuFind\Record\Cache')
+            $container->get('VuFind\Record\Cache'),
+            $container->get('VuFind\Record\FallbackLoader\PluginManager')
         );
     }
 }
diff --git a/module/VuFind/src/VuFind/Record/SourceAndIdList.php b/module/VuFind/src/VuFind/Record/SourceAndIdList.php
index 893254c4549c0d2a28f58b41b6d57ae9242c5d59..361dcaf406dfe0608e7cf58ebccdeef657d3935c 100644
--- a/module/VuFind/src/VuFind/Record/SourceAndIdList.php
+++ b/module/VuFind/src/VuFind/Record/SourceAndIdList.php
@@ -115,15 +115,19 @@ class SourceAndIdList
         $id = $record->getUniqueId();
         $source = $record->getSourceIdentifier();
 
-        // First check if the primary ID is set; in some cases (e.g. Summon),
-        // the ID may have changed, so also check the prior ID if available.
-        if (isset($this->bySource[$source][$id])) {
-            return $this->bySource[$source][$id];
-        }
+        // In some cases (e.g. Summon), the ID may have changed, so also check the
+        // prior ID if available. We should do this BEFORE checking the primary ID
+        // to ensure that we match the correct record in the edge case where a list
+        // contains both an OLD record ID and the NEW record ID that it has been
+        // replaced with. Checking the old ID first ensures that we don't match the
+        // same position twice for two different records.
         $oldId = $record->tryMethod('getPreviousUniqueId');
         if ($oldId !== null && isset($this->bySource[$source][$oldId])) {
             return $this->bySource[$source][$oldId];
         }
+        if (isset($this->bySource[$source][$id])) {
+            return $this->bySource[$source][$id];
+        }
         return false;
     }
 }
diff --git a/module/VuFind/src/VuFind/RecordDriver/Summon.php b/module/VuFind/src/VuFind/RecordDriver/Summon.php
index df16c1fbc5ee191e1c337de752d0149ca7c3422b..6ce14877bf1614e2389f4b8ba2ba7ea05343256b 100644
--- a/module/VuFind/src/VuFind/RecordDriver/Summon.php
+++ b/module/VuFind/src/VuFind/RecordDriver/Summon.php
@@ -57,6 +57,35 @@ class Summon extends DefaultRecord
      */
     protected $dateConverter = null;
 
+    /**
+     * Previous unique ID (if applicable).
+     *
+     * @var string
+     */
+    protected $previousUniqueId = null;
+
+    /**
+     * Get previous unique ID (or null if not applicable).
+     *
+     * @return string
+     */
+    public function getPreviousUniqueId()
+    {
+        return $this->previousUniqueId;
+    }
+
+    /**
+     * Set previous unique ID
+     *
+     * @param string $id ID to set
+     *
+     * @return void
+     */
+    public function setPreviousUniqueId($id)
+    {
+        $this->previousUniqueId = $id;
+    }
+
     /**
      * Get all subject headings associated with this record.  Each heading is
      * returned as an array of chunks, increasing from least specific to most
@@ -137,6 +166,19 @@ class Summon extends DefaultRecord
             $this->fields['Edition'][0] : '';
     }
 
+    /**
+     * Get extra metadata to store in the resource table. In this instance,
+     * we use the BookMark value so that it can be used to recover expired
+     * records in favorite lists.
+     *
+     * @return string
+     */
+    public function getExtraResourceMetadata()
+    {
+        return isset($this->fields['BookMark'][0])
+            ? ['bookmark' => $this->fields['BookMark'][0]] : null;
+    }
+
     /**
      * Get an array of all the formats associated with the record.
      *
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Backend.php b/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Backend.php
index 91ff5c4c0b4797e9da9f89f817a78668c344ab2b..e4e4ac64b22b628517a70c1f6234a5765f8fc2ab 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Backend.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Backend.php
@@ -134,8 +134,11 @@ class Backend extends AbstractBackend implements RetrieveBatchInterface
      */
     public function retrieve($id, ParamBag $params = null)
     {
+        $finalParams = $params ?: new ParamBag();
+        // We normally look up by ID, but we occasionally need to use bookmarks:
+        $idType = $finalParams->get('summonIdType')[0] ?? Connector::IDENTIFIER_ID;
         try {
-            $response   = $this->connector->getRecord($id);
+            $response = $this->connector->getRecord($id, false, $idType);
         } catch (SummonException $e) {
             throw new BackendException(
                 $e->getMessage(),