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(),