diff --git a/local/config/finna/FeedbackForms.yaml.sample b/local/config/finna/FeedbackForms.yaml.sample index 43d6715fa3d..6d788e0ef8c 100644 --- a/local/config/finna/FeedbackForms.yaml.sample +++ b/local/config/finna/FeedbackForms.yaml.sample @@ -342,10 +342,9 @@ forms: settings: - [rows, 3] ReservationListRequest: - allowLocalOverride: true + allowLocalOverride: false title: ReservationList::form_title enabled: true - sendMethod: "email" onlyForLoggedUsers: true emailSubject: ReservationList::form_email_subject response: ReservationList::form_response diff --git a/local/config/finna/ReservationList.yaml.sample b/local/config/finna/ReservationList.yaml.sample index 80cbdc73afc..50bd4e7d077 100644 --- a/local/config/finna/ReservationList.yaml.sample +++ b/local/config/finna/ReservationList.yaml.sample @@ -9,8 +9,13 @@ # List can contain same information as under institution information to be more specific # about where the order is being delivered # LibraryCardSources is used to check for active connection to ILS to be able to use lists -# Connection holds information about type of lists, currently only value supported is type: Database -# and is the default value, if omitted +# Connection defines how the orders are handled with institutions: +# Type: +# feedbackform: Uses feedback forms for sending orders, see FeedbackForms.yaml.sample : ReservationListRequest for examples +# disec: Uses Disec api for sending orders +# base_url: Api base url +# secret: Api specific secret +# use_cat_id: [Optional] Use catalog id for user instead of firstName, lastName, email. Default false. # # Each list declared uses their own translation keys under translation domain ReservationList:: # @@ -46,4 +51,4 @@ Institutions: LibraryCardSources: - connection_established_to_use_lists Connection: - type: Database + type: feedbackform diff --git a/local/languages/finna/ReservationList/en-gb.ini b/local/languages/finna/ReservationList/en-gb.ini index 367d6a55b3e..309389f5db2 100644 --- a/local/languages/finna/ReservationList/en-gb.ini +++ b/local/languages/finna/ReservationList/en-gb.ini @@ -28,3 +28,13 @@ Reservation List Name = "Reservation lists name" Send Reservation Request = "Send reservation request" select_desired_contact_method = "Select desired contact method" Select = "Select" + +; List statuses: +status_unknown = "" +status_delivered = "Delivered" +status_in_process = "In process" +status_canceled = "Cancelled" +status_on_loan = "On Loan" +status_returned = "Returned" +status_renewed = "Renewed" +status_pending = "Pending" diff --git a/local/languages/finna/ReservationList/fi.ini b/local/languages/finna/ReservationList/fi.ini index 17335babf8f..8280e3da438 100644 --- a/local/languages/finna/ReservationList/fi.ini +++ b/local/languages/finna/ReservationList/fi.ini @@ -28,3 +28,13 @@ Reservation List Name = "Varauslistan nimi" Send Reservation Request = "Lähetä varauspyyntö" select_desired_contact_method = "Valitse haluamasi yhteydenottotapa" Select = "Valitse" + +; List statuses: +status_unknown = "" +status_delivered = "Toimitettu" +status_in_process = "Käsittelyssä" +status_canceled = "Peruutettu" +status_on_loan = "Lainassa" +status_returned = "Palautettu" +status_renewed = "Uusittu" +status_pending = "Odottaa" diff --git a/local/languages/finna/ReservationList/se.ini b/local/languages/finna/ReservationList/se.ini index 9a2a2d90098..914edf2496a 100644 --- a/local/languages/finna/ReservationList/se.ini +++ b/local/languages/finna/ReservationList/se.ini @@ -28,3 +28,13 @@ Reservation List Name = "" Send Reservation Request = "" select_desired_contact_method = "" Select = "" + +; List statuses: +status_unknown = "" +status_delivered = "" +status_in_process = "" +status_canceled = "" +status_on_loan = "" +status_returned = "" +status_renewed = "" +status_pending = "" diff --git a/local/languages/finna/ReservationList/sv.ini b/local/languages/finna/ReservationList/sv.ini index 70c81aa8a42..b8c86ad7914 100644 --- a/local/languages/finna/ReservationList/sv.ini +++ b/local/languages/finna/ReservationList/sv.ini @@ -28,3 +28,13 @@ Reservation List Name = "Namn för bokningslista" Send Reservation Request = "Skicka bokningsförfrågan" select_desired_contact_method = "Välj önskad kontaktmetod" Select = "Välj" + +; List statuses: +status_unknown = "" +status_delivered = "Levererad" +status_in_process = "Behandlas" +status_canceled = "Annullerad" +status_on_loan = "Utlånad" +status_returned = "Returnerad" +status_renewed = "Förnyad" +status_pending = "Väntar" diff --git a/module/Finna/config/module.config.php b/module/Finna/config/module.config.php index 78dec3aedcd..e59435cae06 100644 --- a/module/Finna/config/module.config.php +++ b/module/Finna/config/module.config.php @@ -380,7 +380,9 @@ 'Finna\Service\UserPreferenceService' => 'Finna\Service\UserPreferenceServiceFactory', 'Finna\Statistics\Driver\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', 'Finna\Statistics\EventHandler' => 'Finna\Statistics\EventHandlerFactory', + \Finna\ReservationList\Form\Form::class => \Finna\Form\FormFactory::class, \Finna\ReservationList\ReservationListService::class => \Finna\ReservationList\ReservationListServiceFactory::class, + \Finna\ReservationList\Connection\PluginManager::class => \VuFind\ServiceManager\AbstractPluginManagerFactory::class, 'Finna\Favorites\FavoritesService' => 'Finna\Favorites\FavoritesServiceFactory', 'Finna\View\CustomElement\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', 'Finna\Video\Handler\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', @@ -491,6 +493,8 @@ 'Finna\AjaxHandler\GetUserListFactory', 'Finna\AjaxHandler\GetUserLists' => 'Finna\AjaxHandler\GetUserListsFactory', + 'Finna\AjaxHandler\ReservationList' => + 'Finna\AjaxHandler\ReservationListFactory', 'Finna\AjaxHandler\ImportFavorites' => 'Finna\AjaxHandler\ImportFavoritesFactory', 'Finna\AjaxHandler\OnlinePaymentNotify' => @@ -531,6 +535,7 @@ 'getSearchTabsRecommendations' => 'Finna\AjaxHandler\GetSearchTabsRecommendations', 'getSimilarRecords' => 'Finna\AjaxHandler\GetSimilarRecords', 'getUserList' => 'Finna\AjaxHandler\GetUserList', + 'reservationList' => 'Finna\AjaxHandler\ReservationList', 'importFavorites' => 'Finna\AjaxHandler\ImportFavorites', 'onlinePaymentNotify' => 'Finna\AjaxHandler\OnlinePaymentNotify', 'registerOnlinePayment' => 'Finna\AjaxHandler\RegisterOnlinePayment', @@ -763,6 +768,7 @@ ], 'onlinepayment_handler' => [ /* see Finna\OnlinePayment\Handler\PluginManager for defaults */ ], 'video_handler' => [ /* see Finna\Video\Handler\PluginManager for defaults */ ], + 'reservationlist_connection' => [ /* see Finna\ReservationList\Connection\PluginManager for defaults */ ], 'recommend' => [ 'factories' => [ 'VuFind\Recommend\CollectionSideFacets' => 'Finna\Recommend\Factory::getCollectionSideFacets', diff --git a/module/Finna/sql/mysql.sql b/module/Finna/sql/mysql.sql index 544510fd9d3..7983930e6a6 100644 --- a/module/Finna/sql/mysql.sql +++ b/module/Finna/sql/mysql.sql @@ -340,7 +340,8 @@ CREATE TABLE `finna_resource_list` ( `list_type` varchar(200) NOT NULL DEFAULT 'resourcelist', `ordered` datetime DEFAULT NULL, `pickup_date` datetime DEFAULT NULL, - `connection` varchar(40) NOT NULL DEFAULT 'database', + `connection` varchar(40) NOT NULL DEFAULT 'feedbackform', + `external_id` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), CONSTRAINT `resource_list_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE diff --git a/module/Finna/src/Finna/AjaxHandler/ReservationList.php b/module/Finna/src/Finna/AjaxHandler/ReservationList.php new file mode 100644 index 00000000000..1a9319f39a0 --- /dev/null +++ b/module/Finna/src/Finna/AjaxHandler/ReservationList.php @@ -0,0 +1,112 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ + +namespace Finna\AjaxHandler; + +use Finna\ReservationList\Connection\PluginManager; +use Finna\ReservationList\ReservationListService; +use Laminas\Mvc\Controller\Plugin\Params; +use VuFind\Db\Entity\UserEntityInterface; +use VuFind\I18n\Translator\TranslatorAwareInterface; + +/** + * Reservation list ajax handler + * + * @category VuFind + * @package AjaxHandler + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ +class ReservationList extends \VuFind\AjaxHandler\AbstractBase implements TranslatorAwareInterface +{ + use \VuFind\I18n\Translator\TranslatorAwareTrait; + + /** + * Constructor + * + * @param ?UserEntityInterface $user Logged in user (or null) + * @param ReservationListService $reservationListService Reservation list service + * @param PluginManager $connectionHandler Reservation List connection handler + */ + public function __construct( + protected ?UserEntityInterface $user, + protected ReservationListService $reservationListService, + protected PluginManager $connectionHandler, + ) { + } + + /** + * Handle a request. + * + * @param Params $params Parameter helper from controller + * + * @return array [response data, HTTP status code] + */ + public function handleRequest(Params $params) + { + if ($this->user === null) { + return $this->formatResponse( + $this->translate('You must be logged in first'), + self::STATUS_HTTP_NEED_AUTH + ); + } + $listId = (int)$params->fromQuery('list_id'); + if (!$listId) { + return $this->formatResponse( + 'Bad request', + self::STATUS_HTTP_BAD_REQUEST + ); + } + $list = $this->reservationListService->getListById($listId, $this->user); + if (!$list) { + return $this->formatResponse( + 'Bad request', + self::STATUS_HTTP_BAD_REQUEST + ); + } + $result = []; + switch ($params->fromQuery('type')) { + case 'status': + $listProperties = $this->reservationListService->getListProperties( + $list->getInstitution(), + $list->getListConfigIdentifier() + ); + $connectionType = $list->getConnection(); + $handler = $this->connectionHandler->get($connectionType); + $handler->init($listProperties['properties']); + $response = $handler->getListStatus($list, $this->user); + $result['status'] = $this->translate($response); + break; + default: + break; + } + return $this->formatResponse($result); + } +} diff --git a/module/Finna/src/Finna/AjaxHandler/ReservationListFactory.php b/module/Finna/src/Finna/AjaxHandler/ReservationListFactory.php new file mode 100644 index 00000000000..ac8fe44228a --- /dev/null +++ b/module/Finna/src/Finna/AjaxHandler/ReservationListFactory.php @@ -0,0 +1,75 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ + +namespace Finna\AjaxHandler; + +use Psr\Container\ContainerInterface; + +/** + * Reservation list ajax handler + * + * @category VuFind + * @package AjaxHandler + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ +class ReservationListFactory implements \Laminas\ServiceManager\Factory\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 + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke( + ContainerInterface $container, + $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options passed to factory.'); + } + return new $requestedName( + $container->get(\VuFind\Auth\Manager::class)->getUserObject(), + $container->get(\Finna\ReservationList\ReservationListService::class), + $container->get(\Finna\ReservationList\Connection\PluginManager::class), + ); + } +} diff --git a/module/Finna/src/Finna/Controller/FeedbackController.php b/module/Finna/src/Finna/Controller/FeedbackController.php index 5ae8637ad3b..4bd8e39daf3 100644 --- a/module/Finna/src/Finna/Controller/FeedbackController.php +++ b/module/Finna/src/Finna/Controller/FeedbackController.php @@ -58,7 +58,7 @@ public function formAction() { // Always forward reservation list orders to reservation list controller $formId = $this->params()->fromRoute('id', $this->params()->fromQuery('id')); - if ($formId === \Finna\Form\Form::RESERVATION_LIST_REQUEST) { + if ($formId === \Finna\ReservationList\Form\Form::RESERVATION_LIST_REQUEST) { return $this->forwardTo('ReservationList', 'PlaceOrder'); } // Copy any record_id from query params to post params so that it's available diff --git a/module/Finna/src/Finna/Controller/ReservationListController.php b/module/Finna/src/Finna/Controller/ReservationListController.php index b5ce25123c1..bfea48c682e 100644 --- a/module/Finna/src/Finna/Controller/ReservationListController.php +++ b/module/Finna/src/Finna/Controller/ReservationListController.php @@ -35,7 +35,7 @@ namespace Finna\Controller; use Exception; -use Finna\Form\Form; +use Finna\ReservationList\Form\Form as ReservationListForm; use Finna\ReservationList\ReservationListService; use Finna\View\Helper\Root\ReservationList; use Laminas\ServiceManager\ServiceLocatorInterface; @@ -302,29 +302,25 @@ public function placeOrderAction() if (!$listProperties || !$listProperties['Enabled']) { throw new \VuFind\Exception\Forbidden('No list properties found.'); } - $formId = Form::RESERVATION_LIST_REQUEST; + $formId = ReservationListForm::RESERVATION_LIST_REQUEST; $resourcesText = ''; + $resourceIds = []; foreach ($this->reservationListService->getResourcesForList($list, $user) as $resource) { $resourcesText .= $resource->getRecordId() . '||' . $resource->getTitle() . PHP_EOL; + $resourceIds[] = $resource->getSource() . '|' . $resource->getRecordId(); } - // Set reservationlist specific form values - $request->getPost() + $postRequest = $request->getPost(); + // Set reservation list specific form values + $postRequest ->set('rl_list_id', $listId) ->set('rl_institution', $list->getInstitution()) ->set('rl_list_identifier', $list->getListConfigIdentifier()) - ->set('record_ids', $resourcesText); - - $form = $this->getService(\Finna\Form\Form::class); - $params = []; - if ($refererHeader = $this->getRequest()->getHeader('Referer')) { - $params['referrer'] = $refererHeader->getFieldValue(); - } - if ($userAgentHeader = $this->getRequest()->getHeader('User-Agent')) { - $params['userAgent'] = $userAgentHeader->getFieldValue(); - } - $form->setFormId($formId, $params, $request->getPost()->toArray()); + ->set('record_ids', $resourcesText) + ->set('resourceIDs', $resourceIds); + $form = $this->getService(\Finna\ReservationList\Form\Form::class); + $form->setFormId(formId: $formId, prefill: $postRequest->toArray()); if (!$form->isEnabled()) { throw new \VuFind\Exception\Forbidden("Form '$formId' is disabled"); } @@ -333,13 +329,13 @@ public function placeOrderAction() $view->setTemplate('feedback/form'); $view->useCaptcha = false; - $params = $this->params(); $form->setData($request->getPost()->toArray()); if (!$this->formWasSubmitted(useCaptcha: false)) { $form->setData( [ - 'name' => trim($user->getFirstname() . ' ' . $user->getLastname()), - 'email' => $user->getEmail(), + 'firstName' => $user->getFirstname(), + 'lastName' => $user->getLastname(), + 'email' => $user->getEmail(), ] ); return $view; @@ -348,13 +344,13 @@ public function placeOrderAction() if (!$form->isValid()) { return $view; } - - // Override recipients to match list's configured recipients: - $request->getPost()->set('recipient', $listProperties['Recipient']); - $primaryHandler = $form->getPrimaryHandler(); - $success = $primaryHandler->handle($form, $params, $user); - if ($success) { - $this->reservationListService->setListOrdered($user, $list, $request->getPost()); + $connectionType = $listProperties['Connection']['type']; + $handler = $this->getService(\Finna\ReservationList\Connection\PluginManager::class)->get($connectionType); + $handler->init($listProperties); + $result = $handler->placeOrder($this->params(), $user, $form); + if ($result['success']) { + $result['connection'] = $connectionType; + $this->reservationListService->setListOrdered($user, $list, $result); $this->flashMessenger()->addSuccessMessage($form->getSubmitResponse()); return $this->getRefreshResponse(); } else { diff --git a/module/Finna/src/Finna/Controller/ReservationListControllerFactory.php b/module/Finna/src/Finna/Controller/ReservationListControllerFactory.php index 668e8876c2c..d73e6019da4 100644 --- a/module/Finna/src/Finna/Controller/ReservationListControllerFactory.php +++ b/module/Finna/src/Finna/Controller/ReservationListControllerFactory.php @@ -3,7 +3,7 @@ /** * Reservation list controller factory. * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) The National Library of Finland 2024. * diff --git a/module/Finna/src/Finna/Db/Entity/FinnaResourceListEntityInterface.php b/module/Finna/src/Finna/Db/Entity/FinnaResourceListEntityInterface.php index 779626ae93e..23cf0213d74 100644 --- a/module/Finna/src/Finna/Db/Entity/FinnaResourceListEntityInterface.php +++ b/module/Finna/src/Finna/Db/Entity/FinnaResourceListEntityInterface.php @@ -3,7 +3,7 @@ /** * Finna resource list entity interface * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) The National Library of Finland 2024. * @@ -55,6 +55,7 @@ * @property string $ordered * @property string $pickup_date * @property string $connection + * @property string $external_id */ interface FinnaResourceListEntityInterface extends EntityInterface { @@ -222,4 +223,20 @@ public function getConnection(): string; * @return static */ public function setConnection(string $connection): static; + + /** + * Get external id + * + * @return ?string + */ + public function getExternalId(): ?string; + + /** + * Set external id + * + * @param ?string $id External id + * + * @return static + */ + public function setExternalId(?string $id): static; } diff --git a/module/Finna/src/Finna/Db/Entity/FinnaResourceListResourceEntityInterface.php b/module/Finna/src/Finna/Db/Entity/FinnaResourceListResourceEntityInterface.php index 18076f085f0..e74caf274cd 100644 --- a/module/Finna/src/Finna/Db/Entity/FinnaResourceListResourceEntityInterface.php +++ b/module/Finna/src/Finna/Db/Entity/FinnaResourceListResourceEntityInterface.php @@ -3,7 +3,7 @@ /** * Finna resource list resource entity interface. * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) The National Library of Finland 2024. * diff --git a/module/Finna/src/Finna/Db/Row/FinnaResourceList.php b/module/Finna/src/Finna/Db/Row/FinnaResourceList.php index ff6f7807ccc..ecbad659be4 100644 --- a/module/Finna/src/Finna/Db/Row/FinnaResourceList.php +++ b/module/Finna/src/Finna/Db/Row/FinnaResourceList.php @@ -3,7 +3,7 @@ /** * Row Definition for finna_resource_list * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) The National Library of Finland 2024. * @@ -314,4 +314,27 @@ public function setListConfigIdentifier(string $listConfigIdentifier): static $this->list_config_identifier = $listConfigIdentifier; return $this; } + + /** + * Get the external id + * + * @return ?string + */ + public function getExternalId(): ?string + { + return $this->external_id; + } + + /** + * Set the external id + * + * @param ?string $id External id + * + * @return static + */ + public function setExternalId(?string $id): static + { + $this->external_id = $id; + return $this; + } } diff --git a/module/Finna/src/Finna/Db/Row/FinnaResourceListResource.php b/module/Finna/src/Finna/Db/Row/FinnaResourceListResource.php index 2f0242adea7..80cabb80e07 100644 --- a/module/Finna/src/Finna/Db/Row/FinnaResourceListResource.php +++ b/module/Finna/src/Finna/Db/Row/FinnaResourceListResource.php @@ -3,7 +3,7 @@ /** * Table Definition for finna_resource_list_resource * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) The National Library of Finland 2024. * diff --git a/module/Finna/src/Finna/Db/Service/FinnaResourceListService.php b/module/Finna/src/Finna/Db/Service/FinnaResourceListService.php index df73ca19cb1..8d6962bf987 100644 --- a/module/Finna/src/Finna/Db/Service/FinnaResourceListService.php +++ b/module/Finna/src/Finna/Db/Service/FinnaResourceListService.php @@ -3,7 +3,7 @@ /** * Resource list service * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) The National Library of Finland 2024. * diff --git a/module/Finna/src/Finna/Db/Table/FinnaResourceList.php b/module/Finna/src/Finna/Db/Table/FinnaResourceList.php index f820d358d1a..6678c10238f 100644 --- a/module/Finna/src/Finna/Db/Table/FinnaResourceList.php +++ b/module/Finna/src/Finna/Db/Table/FinnaResourceList.php @@ -3,7 +3,7 @@ /** * Table Definition for finna_resource_list * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) Villanova University 2024. * Copyright (C) The National Library of Finland 2024. diff --git a/module/Finna/src/Finna/Db/Table/FinnaResourceListResource.php b/module/Finna/src/Finna/Db/Table/FinnaResourceListResource.php index 8d16a110a39..c073b589a4c 100644 --- a/module/Finna/src/Finna/Db/Table/FinnaResourceListResource.php +++ b/module/Finna/src/Finna/Db/Table/FinnaResourceListResource.php @@ -3,7 +3,7 @@ /** * Table Definition for finna_resource_list_resource * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) The National Library of Finland 2024. * diff --git a/module/Finna/src/Finna/Form/Form.php b/module/Finna/src/Finna/Form/Form.php index 6a31acf8a08..1c357bb61b4 100644 --- a/module/Finna/src/Finna/Form/Form.php +++ b/module/Finna/src/Finna/Form/Form.php @@ -69,13 +69,6 @@ class Form extends \VuFind\Form\Form */ public const ARCHIVE_MATERIAL_REQUEST = 'ArchiveRequest'; - /** - * Reservation request form id. - * - * @var string - */ - public const RESERVATION_LIST_REQUEST = 'ReservationListRequest'; - /** * Handlers that are considered safe for transmitting information about the user * @@ -343,10 +336,6 @@ public function reportPatronId(): bool */ public function getRecipient($postParams = null) { - // Always get recipients from postparams - if ($this->getFormId() === self::RESERVATION_LIST_REQUEST) { - return $postParams['recipient']; - } // Get recipient email address for feedback form from data source // configuration: if ($this->getFormId() === 'FeedbackRecord') { @@ -739,12 +728,7 @@ protected function getFormElements($config) $elements[$key] = ['type' => 'hidden', 'name' => $key, 'value' => null]; } } - // Add hidden fields for reservation list order form - if (self::RESERVATION_LIST_REQUEST === $this->getFormId()) { - $elements['rl_institution'] = ['type' => 'hidden', 'name' => 'rl_institution', 'value' => null]; - $elements['rl_list_identifier'] = ['type' => 'hidden', 'name' => 'rl_list_identifier', 'value' => null]; - $elements['rl_list_id'] = ['type' => 'hidden', 'name' => 'rl_list_id', 'value' => null]; - } + return $elements; } diff --git a/module/Finna/src/Finna/ReservationList/Connection/AbstractBase.php b/module/Finna/src/Finna/ReservationList/Connection/AbstractBase.php new file mode 100644 index 00000000000..e893641228f --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Connection/AbstractBase.php @@ -0,0 +1,96 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:controllers Wiki + */ + +namespace Finna\ReservationList\Connection; + +use Finna\Db\Entity\FinnaResourceListEntityInterface; +use Finna\ReservationList\Form\Form; +use Laminas\Mvc\Controller\Plugin\Params; +use Psr\Container\ContainerInterface; +use VuFind\Db\Entity\UserEntityInterface; +use VuFind\Service\GetServiceTrait; + +/** + * Connection abstract base + * + * @category VuFind + * @package ReservationList + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +abstract class AbstractBase implements ConnectionInterface, \Laminas\Log\LoggerAwareInterface +{ + use \VuFind\Log\LoggerAwareTrait; + use GetServiceTrait; + + /** + * Constructor + * + * @param ContainerInterface $serviceLocator Service locator used with GetServiceTrait + */ + public function __construct(ContainerInterface $serviceLocator) + { + $this->serviceLocator = $serviceLocator; + } + + /** + * Places an order + * + * @param Params $params Params plugin + * @param UserEntityInterface $user User entity + * @param Form $form Form posted when submitting the order + * + * @return array [ + * external_id: Id in external service or null, + * success: true or false, + * pickup_date: date for preferred pickup + * ] + */ + abstract public function placeOrder(Params $params, UserEntityInterface $user, Form $form = null): array; + + /** + * Check list status. Used for external services. + * + * @param FinnaResourceListEntityInterface $list List to check for status + * @param UserEntityInterface $user Current logged in user + * + * @return string + */ + abstract public function getListStatus(FinnaResourceListEntityInterface $list, UserEntityInterface $user): string; + + /** + * Initialize connection handler + * + * @param array $config List specific configuration from ReservationList.yaml + * + * @return static + */ + abstract public function init(array $config): static; +} diff --git a/module/Finna/src/Finna/ReservationList/Connection/ConnectionFactory.php b/module/Finna/src/Finna/ReservationList/Connection/ConnectionFactory.php new file mode 100644 index 00000000000..8f063b29961 --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Connection/ConnectionFactory.php @@ -0,0 +1,67 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ + +namespace Finna\ReservationList\Connection; + +use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerInterface; + +/** + * Connection factory + * + * @category VuFind + * @package ReservationList + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ +class ConnectionFactory 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 sent to factory.'); + } + return new $requestedName($container); + } +} diff --git a/module/Finna/src/Finna/ReservationList/Connection/ConnectionInterface.php b/module/Finna/src/Finna/ReservationList/Connection/ConnectionInterface.php new file mode 100644 index 00000000000..62fb1325205 --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Connection/ConnectionInterface.php @@ -0,0 +1,78 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ + +namespace Finna\ReservationList\Connection; + +use Finna\Db\Entity\FinnaResourceListEntityInterface; +use Finna\ReservationList\Form\Form; +use Laminas\Mvc\Controller\Plugin\Params; +use VuFind\Db\Entity\UserEntityInterface; + +/** + * Reservation list connection plugin interface + * + * @category VuFind + * @package ReservationList + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ +interface ConnectionInterface +{ + /** + * Places an order + * + * @param Params $params Params plugin + * @param UserEntityInterface $user User entity + * @param Form $form Form posted when submitting the order + * + * @return array [external_id: Id in external service or null, success: true or false] + */ + public function placeOrder(Params $params, UserEntityInterface $user, Form $form = null): array; + + /** + * Check list status. Used for external services. + * + * @param FinnaResourceListEntityInterface $list List to check for status + * @param UserEntityInterface $user Current logged in user + * + * @return string + */ + public function getListStatus(FinnaResourceListEntityInterface $list, UserEntityInterface $user): string; + + /** + * Initialize connection handler + * + * @param array $config List specific configuration from ReservationList.yaml + * + * @return static + * @throws \Exception If connection is not configured properly + */ + public function init(array $config): static; +} diff --git a/module/Finna/src/Finna/ReservationList/Connection/Disec.php b/module/Finna/src/Finna/ReservationList/Connection/Disec.php new file mode 100644 index 00000000000..4cab3ae14e4 --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Connection/Disec.php @@ -0,0 +1,193 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:controllers Wiki + */ + +namespace Finna\ReservationList\Connection; + +use Finna\Db\Entity\FinnaResourceListEntityInterface; +use Finna\ReservationList\Form\Form; +use Laminas\Mvc\Controller\Plugin\Params; +use VuFind\Db\Entity\UserEntityInterface; + +/** + * Disec connection handler + * + * @category VuFind + * @package ReservationList + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class Disec extends AbstractBase +{ + /** + * Disec orders url + * + * @param string + */ + protected $ordersUrl; + + /** + * API key used for disec authorization + * + * @param string + */ + protected $apiKey; + + /** + * Use catalog id to send user data instead of users first and last name + * + * @param bool + */ + protected bool $useCatId = false; + + /** + * Common headers in requests + * + * @var array + */ + protected array $requestHeaders = [ + 'Content-Type: application/json', + 'accept: */*', + ]; + + /** + * Places an order + * + * @param Params $params Params plugin + * @param UserEntityInterface $user User entity + * @param Form $form Form posted when submitting the order + * + * @return array [ + * external_id: Id in external service or null, + * success: true or false, + * pickup_date: date for preferred pickup + * ] + */ + public function placeOrder(Params $params, UserEntityInterface $user, Form $form = null): array + { + $data = []; + $client = $this->getService(\VuFindHttp\HttpService::class)->createClient($this->ordersUrl); + $client->setHeaders($this->requestHeaders); + $client->setMethod(\Laminas\Http\Request::METHOD_POST); + + $resources = []; + $recordLoader = $this->getService(\VuFind\Record\Loader::class); + foreach ($recordLoader->loadBatch($params->fromPost('resourceIDs', [])) as $record) { + if ($identifiers = $record->tryMethod('getIdentifier', [])) { + $resources[] = array_shift($identifiers); + } + } + $data = [ + 'resourceIds' => $resources, + 'contentInfo' => $params->fromPost('message', '') . PHP_EOL, + ]; + $data['contentInfo'] .= $params->fromPost('pickup_date') . PHP_EOL; + if ($catId = $user->getCatId()) { + [, $id] = explode('.', $catId); + if ($this->useCatId) { + $data['kohaId'] = (int)$id; + } + $data['contentInfo'] .= 'cat_id: ' . $id; + } + if (empty($data['kohaId'])) { + $data['customer'] = [ + 'firstName' => $params->fromPost('firstName') ?? $user->getFirstname(), + 'lastName' => $params->fromPost('lastName') ?? $user->getLastname(), + 'email' => $params->fromPost('email') ?? $user->getEmail(), + ]; + } + $client->setRawBody(json_encode($data)); + $response = $client->send(); + + if ($response->isSuccess()) { + $body = json_decode($response->getBody(), true); + return [ + 'success' => true, + 'external_id' => $body['id'], + 'pickup_date' => $params->fromPost('pickup_date', ''), + ]; + } + $this->debug('Disec: failed to place order: ' . $response->getBody()); + return [ + 'success' => false, + 'external_id' => null, + 'pickup_date' => null, + ]; + } + + /** + * Check list status. Used for external services. + * + * @param FinnaResourceListEntityInterface $list List to check for status + * @param UserEntityInterface $user Current logged in user + * + * @return string + */ + public function getListStatus(FinnaResourceListEntityInterface $list, UserEntityInterface $user): string + { + $externalId = $list->getExternalId(); + $formedUrl = implode('/', [$this->ordersUrl, $externalId]); + $client = $this->getService(\VuFindHttp\HttpService::class)->createClient($formedUrl); + $client->setHeaders($this->requestHeaders); + $client->setMethod(\Laminas\Http\Request::METHOD_GET); + $response = $client->send(); + $status = ReservationListStatus::UNKNOWN; + if ($response->isSuccess()) { + $body = json_decode($response->getBody(), true); + $status = ReservationListStatus::mapEnumFromString($body['status'] ?? ''); + } else { + $this->debug('Disec: failed to fetch status for list: ' . $response->getBody()); + } + return $status->getTranslationKey(); + } + + /** + * Initialize connection handler + * + * @param array $config List specific configuration from ReservationList.yaml + * + * @return static + * @throws \Exception If Disec connection is not configured properly + */ + public function init(array $config): static + { + try { + $baseUrl = $config['Connection']['base_url']; + if (!str_ends_with($baseUrl, '/')) { + $baseUrl .= '/'; + } + $this->ordersUrl = $baseUrl . 'orders'; + $this->requestHeaders[] = 'X-API-Key: ' . $config['Connection']['secret']; + $this->useCatId = $config['Connection']['use_cat_id'] ?? false; + } catch (\Exception $e) { + throw new \Exception('Disec: Invalid configuration'); + } + return $this; + } +} diff --git a/module/Finna/src/Finna/ReservationList/Connection/FeedbackForm.php b/module/Finna/src/Finna/ReservationList/Connection/FeedbackForm.php new file mode 100644 index 00000000000..7615856eca4 --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Connection/FeedbackForm.php @@ -0,0 +1,111 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:controllers Wiki + */ + +namespace Finna\ReservationList\Connection; + +use Finna\Db\Entity\FinnaResourceListEntityInterface; +use Finna\ReservationList\Form\Form; +use Laminas\Mvc\Controller\Plugin\Params; +use VuFind\Db\Entity\UserEntityInterface; + +/** + * FeedbackForm connection handler + * + * @category VuFind + * @package ReservationList + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class FeedbackForm extends AbstractBase +{ + /** + * Recipients for email handler defined in ReservationList.yaml + * + * @var array + */ + protected array $recipients; + + /** + * Places an order + * + * @param Params $params Params plugin + * @param UserEntityInterface $user User entity + * @param Form $form Form posted when submitting the order + * + * @return array [ + * external_id: Id in external service or null, + * success: true or false, + * pickup_date: date for preferred pickup + * ] + */ + public function placeOrder(Params $params, UserEntityInterface $user, Form $form = null): array + { + // Lists use list specific recipients for orders + $form->setRecipients($this->recipients); + $result = $form->getPrimaryHandler()->handle($form, $params, $user); + return [ + 'success' => $result, + 'external_id' => null, + 'pickup_date' => $params->fromPost('pickup_date', ''), + ]; + } + + /** + * Check list status. Used for external services. + * + * @param FinnaResourceListEntityInterface $list List to check for status + * @param UserEntityInterface $user Current logged in user + * + * @return string + */ + public function getListStatus(FinnaResourceListEntityInterface $list, UserEntityInterface $user): string + { + $status = ReservationListStatus::UNKNOWN; + return $status->getTranslationKey(); + } + + /** + * Initialize connection handler + * + * @param array $config List specific configuration from ReservationList.yaml + * + * @return static + * @throws \Exception If FeedbackForm connection is not configured properly + */ + public function init(array $config): static + { + try { + $this->recipients = $config['Recipient']; + } catch (\Exception $e) { + throw new \Exception('FeedbackForm: Invalid configuration'); + } + return $this; + } +} diff --git a/module/Finna/src/Finna/ReservationList/Connection/PluginManager.php b/module/Finna/src/Finna/ReservationList/Connection/PluginManager.php new file mode 100644 index 00000000000..ff89d176a50 --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Connection/PluginManager.php @@ -0,0 +1,95 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ + +namespace Finna\ReservationList\Connection; + +/** + * Reservation list plugin manager + * + * @category VuFind + * @package ReservationList + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki + */ +class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager +{ + /** + * Default plugin aliases. + * + * @var array + */ + protected $aliases = [ + 'Disec' => Disec::class, + 'disec' => Disec::class, + 'FeedbackForm' => FeedbackForm::class, + 'feedbackform' => FeedbackForm::class, + ]; + + /** + * Default plugin factories. + * + * @var array + */ + protected $factories = [ + Disec::class => ConnectionFactory::class, + FeedbackForm::class => ConnectionFactory::class, + ]; + + /** + * Constructor + * + * Make sure plugins are properly initialized. + * + * @param mixed $configOrContainerInstance Configuration or container instance + * @param array $v3config If $configOrContainerInstance is a + * container, this value will be passed to the parent constructor. + */ + public function __construct( + $configOrContainerInstance = null, + array $v3config = [] + ) { + // These objects are not meant to be shared -- every time we retrieve one, + // we are building a brand new object. + $this->sharedByDefault = false; + + parent::__construct($configOrContainerInstance, $v3config); + } + + /** + * Return the name of the base class or interface that plug-ins must conform + * to. + * + * @return string + */ + protected function getExpectedInterface() + { + return ConnectionInterface::class; + } +} diff --git a/module/Finna/src/Finna/ReservationList/Connection/ReservationListStatus.php b/module/Finna/src/Finna/ReservationList/Connection/ReservationListStatus.php new file mode 100644 index 00000000000..d974ab7ce06 --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Connection/ReservationListStatus.php @@ -0,0 +1,78 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/vufind2:developer_manual Wiki + */ + +namespace Finna\ReservationList\Connection; + +/** + * Reservation list status enum. + * + * @category VuFind + * @package ReservationList + * @author Juha Luoma + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/vufind2:developer_manual Wiki + */ +enum ReservationListStatus: string +{ + case UNKNOWN = 'unknown'; + case DELIVERED = 'delivered'; + case IN_PROCESS = 'in_process'; + case CANCELLED = 'cancelled'; + case ON_LOAN = 'on_loan'; + case RETURNED = 'returned'; + case RENEWED = 'renewed'; + case PENDING = 'pending'; + + /** + * Return a translation key representing the status. + * + * @return string + */ + public function getTranslationKey(): string + { + return 'ReservationList::status_' . $this->value; + } + + /** + * Return instance of ENUM by mapping a status key into a proper enum status + * + * @param string $text Value to map + * + * @return string Found mapped value or unknown if not found + */ + public static function mapEnumFromString(string $text): static + { + $statusKeyMappings = [ + 'loaned' => 'on_loan', + ]; + $text = mb_strtolower($text); + $text = $statusKeyMappings[$text] ?? $text; + return ReservationListStatus::tryFrom($text) ?? ReservationListStatus::UNKNOWN; + } +} diff --git a/module/Finna/src/Finna/ReservationList/Form/Form.php b/module/Finna/src/Finna/ReservationList/Form/Form.php new file mode 100644 index 00000000000..13515f6056a --- /dev/null +++ b/module/Finna/src/Finna/ReservationList/Form/Form.php @@ -0,0 +1,99 @@ + + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:controllers Wiki + */ + +namespace Finna\ReservationList\Form; + +/** + * Configurable form. + * + * @category VuFind + * @package ReservationList + * @author Samuli Sillanpää + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:controllers Wiki + */ +class Form extends \Finna\Form\Form +{ + /** + * Reservation request form id. + * + * @var string + */ + public const RESERVATION_LIST_REQUEST = 'ReservationListRequest'; + + /** + * Recipients for reservation lists + * + * @var array + */ + protected array $recipients = []; + + /** + * Set recipients + * + * @param array $recipients Array containing recipients [name, email] + * + * @return void + */ + public function setRecipients(array $recipients): void + { + $this->recipients = $recipients; + } + + /** + * Return form recipient. Name is in singular to override inherited methods + * + * @param array $postParams Posted form data + * + * @return array with name, email or null if not configured + */ + public function getRecipient($postParams = null) + { + return $this->recipients; + } + + /** + * Get form elements + * + * @param array $config Form configuration + * + * @return array + */ + protected function getFormElements($config) + { + $elements = parent::getFormElements($config); + // Add hidden fields for reservation list order form + $elements['rl_institution'] = ['type' => 'hidden', 'name' => 'rl_institution', 'value' => null]; + $elements['rl_list_identifier'] = ['type' => 'hidden', 'name' => 'rl_list_identifier', 'value' => null]; + $elements['rl_list_id'] = ['type' => 'hidden', 'name' => 'rl_list_id', 'value' => null]; + return $elements; + } +} diff --git a/module/Finna/src/Finna/ReservationList/ReservationListService.php b/module/Finna/src/Finna/ReservationList/ReservationListService.php index 99564d74e7e..11ff7ebfef5 100644 --- a/module/Finna/src/Finna/ReservationList/ReservationListService.php +++ b/module/Finna/src/Finna/ReservationList/ReservationListService.php @@ -53,6 +53,8 @@ use VuFind\RecordDriver\AbstractBase as RecordDriver; use VuFind\RecordDriver\DefaultRecord; +use function is_string; + /** * Reservation list service * @@ -75,6 +77,31 @@ class ReservationListService implements TranslatorAwareInterface, DbServiceAware */ public const RESOURCE_LIST_TYPE = 'reservationlist'; + /** + * Default connection handler used for list connections + * + * @var string + */ + public const DEFAULT_CONNECTION_HANDLER = 'feedbackform'; + + /** + * Default values for list config + * + * @var array + */ + protected const RESERVATION_LIST_DEFAULT_VALUES = [ + 'Enabled' => false, + 'Recipient' => [], + 'Datasources' => [], + 'Information' => [], + 'LibraryCardSources' => [], + 'CheckResourceStatus' => false, + 'Connection' => [ + 'type' => self::DEFAULT_CONNECTION_HANDLER, + ], + 'Identifier' => false, + ]; + /** * Constructor * @@ -88,6 +115,7 @@ class ReservationListService implements TranslatorAwareInterface, DbServiceAware * @param ?RecordCache $recordCache Record cache (optional) * @param ?Container $session Session container for remembering * state (optional) + * @param ?array $yamlConfig Reservation list yaml config */ public function __construct( protected FinnaResourceListServiceInterface $resourceListService, @@ -97,7 +125,8 @@ public function __construct( protected ResourcePopulator $resourcePopulator, protected RecordLoader $recordLoader, protected ?RecordCache $recordCache = null, - protected ?Container $session = null + protected ?Container $session = null, + protected ?array $yamlConfig = [] ) { } @@ -274,21 +303,23 @@ public function saveRecordToReservationList( /** * Set list ordered * - * @param UserEntityInterface $user User to check for rights to list - * @param FinnaResourceListEntityInterface $list List entity or id of the list - * @param Parameters $request Parameters to get values from + * @param UserEntityInterface $user User to check for rights to list + * @param FinnaResourceListEntityInterface $list List entity or id of the list + * @param array $newValues New values to update as key value pairs * * @return void */ public function setListOrdered( UserEntityInterface $user, FinnaResourceListEntityInterface $list, - Parameters $request + array $newValues ): void { if (!$this->userCanEditList($user, $list)) { throw new ListPermissionException('list_access_denied'); } - $list->setPickupDate(DateTime::createFromFormat('Y-m-d', $request->get('pickup_date')))->setOrdered(); + $list->setPickupDate(DateTime::createFromFormat('Y-m-d', $newValues['pickup_date']))->setOrdered(); + $list->setExternalId($newValues['external_id'] ?? null); + $list->setConnection($newValues['connection'] ?? self::DEFAULT_CONNECTION_HANDLER); $this->resourceListService->persistEntity($list); } @@ -495,4 +526,69 @@ public function getListsContainingRecord( self::RESOURCE_LIST_TYPE ); } + + /** + * Get list properties defined by institution and list identifier in ReservationList.yaml, + * institution specified information and + * formed translation_keys for the list. + * + * Institution information contains keys and values: + * - name => Example institution name + * - address => Example institution address + * - postal => Example institution postal + * - city => Example institution city + * - email => Example institution email + * + * Translation keys formed: + * - title => list_title_{$institution}_{$listIdentifier}, + * - description => list_description_{$institution}_{$listIdentifier}, + * + * @param string $institution Lists controlling institution + * @param string $listIdentifier List identifier + * + * @return array + */ + public function getListProperties( + string $institution, + string $listIdentifier + ): array { + foreach ($this->yamlConfig['Institutions'][$institution]['Lists'] ?? [] as $list) { + $list = $this->ensureListKeys($list); + if ($list['Identifier'] === $listIdentifier) { + return [ + 'properties' => $list, + 'institution_information' => $this->yamlConfig['Institutions'][$institution]['Information'] ?? [], + 'translation_keys' => [ + 'title' => "ReservationList::list_title_{$institution}_{$listIdentifier}", + 'description' => "ReservationList::list_description_{$institution}_{$listIdentifier}", + ], + ]; + } + } + return [ + 'properties' => self::RESERVATION_LIST_DEFAULT_VALUES, + 'institution_information' => [], + 'translation_keys' => [ + 'title' => '', + 'description' => '', + ], + ]; + } + + /** + * Ensure that lists have all the required keys defined needed in other places. + * Sets the list disabled if list identifier is not set or is not a string. + * + * @param array $list Properties of the list to ensure + * + * @return array + */ + public function ensureListKeys(array $list): array + { + $merged = array_merge(self::RESERVATION_LIST_DEFAULT_VALUES, $list); + if (!is_string($merged['Identifier'])) { + $merged['Enabled'] = false; + } + return $merged; + } } diff --git a/module/Finna/src/Finna/ReservationList/ReservationListServiceFactory.php b/module/Finna/src/Finna/ReservationList/ReservationListServiceFactory.php index 028a6b9caac..3dc6b5d6063 100644 --- a/module/Finna/src/Finna/ReservationList/ReservationListServiceFactory.php +++ b/module/Finna/src/Finna/ReservationList/ReservationListServiceFactory.php @@ -57,6 +57,8 @@ public function __invoke(ContainerInterface $container, $name, array $options = $serviceManager = $container->get(\VuFind\Db\Service\PluginManager::class); $sessionManager = $container->get(\Laminas\Session\SessionManager::class); $session = new \Laminas\Session\Container('ReservationList', $sessionManager); + $reservationListYaml = $container->get(\Finna\Config\YamlReader::class) + ->getFinna('ReservationList.yaml', 'config/finna'); return new ReservationListService( $serviceManager->get(\Finna\Db\Service\FinnaResourceListServiceInterface::class), $serviceManager->get(\Finna\Db\Service\FinnaResourceListResourceServiceInterface::class), @@ -65,7 +67,8 @@ public function __invoke(ContainerInterface $container, $name, array $options = $container->get(\VuFind\Record\ResourcePopulator::class), $container->get(\Finna\Record\Loader::class), $container->get(\VuFind\Record\Cache::class), - $session + $session, + $reservationListYaml ?: [] ); } } diff --git a/module/Finna/src/Finna/Search/ReservationList/Options.php b/module/Finna/src/Finna/Search/ReservationList/Options.php index 79e7748d894..708ae93bc11 100644 --- a/module/Finna/src/Finna/Search/ReservationList/Options.php +++ b/module/Finna/src/Finna/Search/ReservationList/Options.php @@ -3,7 +3,7 @@ /** * Reservation List Options * - * PHP version 8.1 + * PHP version 8 * * Copyright (C) National Library of Finland 2024. * diff --git a/module/Finna/src/Finna/View/Helper/Root/ReservationList.php b/module/Finna/src/Finna/View/Helper/Root/ReservationList.php index 857082f839b..b95bcb6b38a 100644 --- a/module/Finna/src/Finna/View/Helper/Root/ReservationList.php +++ b/module/Finna/src/Finna/View/Helper/Root/ReservationList.php @@ -36,7 +36,6 @@ use VuFind\RecordDriver\DefaultRecord; use function in_array; -use function is_string; /** * Reservation list view helper @@ -56,23 +55,6 @@ class ReservationList extends \Laminas\View\Helper\AbstractHelper */ protected ?UserEntityInterface $user; - /** - * Default values for list config - * - * @var array - */ - protected array $requiredFieldsAndDefaultValues = [ - 'Enabled' => false, - 'Recipient' => [], - 'Datasources' => [], - 'Information' => [], - 'LibraryCardSources' => [], - 'Connection' => [ - 'type' => 'Database', - ], - 'Identifier' => false, - ]; - /** * Constructor * @@ -103,20 +85,52 @@ public function __invoke(?UserEntityInterface $user = null): self } /** - * Ensure that lists have all the required keys defined needed in other places. - * Sets the list disabled if list identifier is not set or is not a string. + * Get list properties defined by institution and list identifier in ReservationList.yaml, + * institution specified information and + * formed translation_keys for the list. + * + * Institution information contains keys and values: + * - name => Example institution name + * - address => Example institution address + * - postal => Example institution postal + * - city => Example institution city + * - email => Example institution email + * + * Translation keys formed: + * - title => list_title_{$institution}_{$listIdentifier}, + * - description => list_description_{$institution}_{$listIdentifier}, * - * @param array $list Properties of the list to ensure + * @param string $institution Lists controlling institution + * @param string $listIdentifier List identifier * * @return array */ - protected function ensureListKeys(array $list): array + public function getListProperties( + string $institution, + string $listIdentifier + ): array { + return $this->reservationListService->getListProperties($institution, $listIdentifier); + } + + /** + * Display buttons which routes the request to proper list procedures + * Checks if the list should be displayed for logged-in only users. + * + * @param DefaultRecord $driver Driver to use for checking available lists + * + * @return string + */ + public function renderReserveTemplate(DefaultRecord $driver): string { - $merged = array_merge($this->requiredFieldsAndDefaultValues, $list); - if (!is_string($merged['Identifier'])) { - $merged['Enabled'] = false; + if (!$this->isFunctionalityEnabled()) { + return ''; } - return $merged; + // Collect lists where we could potentially save this: + $lists = $this->getAvailableListsForRecord($driver); + + // Set up the needed context in the view: + $view = $this->getView(); + return $view->render('Helpers/reservationlist-reserve.phtml', compact('lists', 'driver')); } /** @@ -126,7 +140,7 @@ protected function ensureListKeys(array $list): array * * @return array */ - protected function getAvailableListsForRecord(DefaultRecord $driver): array + public function getAvailableListsForRecord(DefaultRecord $driver): array { $datasource = $driver->tryMethod('getDatasource'); if (!$datasource) { @@ -136,7 +150,7 @@ protected function getAvailableListsForRecord(DefaultRecord $driver): array foreach ($this->yamlConfig['Institutions'] ?? [] as $institution => $settings) { $current = [$institution => []]; foreach ($settings['Lists'] ?? [] as $list) { - $list = $this->ensureListKeys($list); + $list = $this->reservationListService->ensureListKeys($list); if ( $list['Enabled'] && in_array($datasource, $list['Datasources']) @@ -153,54 +167,6 @@ protected function getAvailableListsForRecord(DefaultRecord $driver): array return $result; } - /** - * Get list properties defined by institution and list identifier in ReservationList.yaml, - * institution specified information and - * formed translation_keys for the list. - * - * Institution information contains keys and values: - * - name => Example institution name - * - address => Example institution address - * - postal => Example institution postal - * - city => Example institution city - * - email => Example institution email - * - * Translation keys formed: - * - title => list_title_{$institution}_{$listIdentifier}, - * - description => list_description_{$institution}_{$listIdentifier}, - * - * @param string $institution Lists controlling institution - * @param string $listIdentifier List identifier - * - * @return array - */ - public function getListProperties( - string $institution, - string $listIdentifier - ): array { - foreach ($this->yamlConfig['Institutions'][$institution]['Lists'] ?? [] as $list) { - $list = $this->ensureListKeys($list); - if ($list['Identifier'] === $listIdentifier) { - return [ - 'properties' => $list, - 'institution_information' => $this->yamlConfig['Institutions'][$institution]['Information'] ?? [], - 'translation_keys' => [ - 'title' => "ReservationList::list_title_{$institution}_{$listIdentifier}", - 'description' => "ReservationList::list_description_{$institution}_{$listIdentifier}", - ], - ]; - } - } - return [ - 'properties' => $this->requiredFieldsAndDefaultValues, - 'institution_information' => [], - 'translation_keys' => [ - 'title' => '', - 'description' => '', - ], - ]; - } - /** * Check if the user has proper requirements to order records. * Function checks if there is required LibraryCardSources @@ -223,27 +189,6 @@ public function checkUserRightsForList(array $list): bool return in_array($patron['source'], $list['LibraryCardSources']); } - /** - * Display buttons which routes the request to proper list procedures - * Checks if the list should be displayed for logged-in only users. - * - * @param DefaultRecord $driver Driver to use for checking available lists - * - * @return string - */ - public function renderReserveTemplate(DefaultRecord $driver): string - { - if (!$this->isFunctionalityEnabled()) { - return ''; - } - // Collect lists where we could potentially save this: - $lists = $this->getAvailableListsForRecord($driver); - - // Set up the needed context in the view: - $view = $this->getView(); - return $view->render('Helpers/reservationlist-reserve.phtml', compact('lists', 'driver')); - } - /** * Get available reservation lists for user, user must be invoked * diff --git a/themes/finna2/js/finna-reservation-list.js b/themes/finna2/js/finna-reservation-list.js new file mode 100644 index 00000000000..966b8be112e --- /dev/null +++ b/themes/finna2/js/finna-reservation-list.js @@ -0,0 +1,33 @@ +/*global VuFind, finna */ +finna.reservationList = (function finnaReservationList() { + /** + * Check status of given list + * @param {HTMLElement} element Element where status is inserted + */ + function checkStatus(element) { + const queryParams = new URLSearchParams({method: 'reservationList', list_id: element.dataset.listId, type: 'status'}); + fetch (`${VuFind.path}/AJAX/JSON?${queryParams.toString()}`) + .then(response => { + if (!response.ok) { + throw new Error(VuFind.translate('error_occurred')); + } + return response.json(); + }) + .then(responseJSON => { + if (responseJSON.data && responseJSON.data.status) { + element.innerText = responseJSON.data.status; + element.classList.remove('pending'); + } + }); + } + + var my = { + init: () => { + document.querySelectorAll('.js-reservation-list-status').forEach(el => { + checkStatus(el); + }); + } + }; + + return my; +})(); diff --git a/themes/finna2/js/finna.js b/themes/finna2/js/finna.js index 380dd3b7972..5ab6a4c2dba 100644 --- a/themes/finna2/js/finna.js +++ b/themes/finna2/js/finna.js @@ -44,6 +44,7 @@ var finna = (function finnaModule() { 'mdEditable', 'a11y', 'finnaDatepicker', + 'reservationList', ]; $.each(modules, function initModule(ind, module) { diff --git a/themes/finna2/scss/finna/reservationlist.scss b/themes/finna2/scss/finna/reservationlist.scss index 54359f89131..225c88feaeb 100644 --- a/themes/finna2/scss/finna/reservationlist.scss +++ b/themes/finna2/scss/finna/reservationlist.scss @@ -12,6 +12,9 @@ p { margin: 0; } + &.js-reservation-list-status.pending { + display: none; + } } .list-controls { margin-top: 10px; diff --git a/themes/finna2/templates/reservationlist/displaylist.phtml b/themes/finna2/templates/reservationlist/displaylist.phtml index 2362ac86594..66d51974e4e 100644 --- a/themes/finna2/templates/reservationlist/displaylist.phtml +++ b/themes/finna2/templates/reservationlist/displaylist.phtml @@ -57,6 +57,10 @@

transEsc('ReservationList::list_pickup_date', ['%%date%%' => $this->list->getPickupDate()->format('d.m.Y')]) ?>

+ list->getOrdered() && $this->list->getExternalId()): ?> +
+
+
list->getOrdered() && $this->results->getResults() && $listProperties['properties']['Enabled']): ?> diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php index f10d79e2f33..4049c92d6dd 100644 --- a/themes/finna2/theme.config.php +++ b/themes/finna2/theme.config.php @@ -265,6 +265,7 @@ 'finna-select-a11y.js', 'finna-a11y.js', 'finna-datepicker.js', + 'finna-reservation-list.js', 'components/finna-bazaar-browse-bar.js', 'components/finna-md-editable.js', 'components/finna-tabs-nav.js',