diff --git a/local/config/vufind/searches.ini b/local/config/vufind/searches.ini index ab20a8fc4da5f272a4bc75f93da2cbe615d80452..f6d6e5cdecfcffb5dea73ac3ea3062502d7bed2a 100644 --- a/local/config/vufind/searches.ini +++ b/local/config/vufind/searches.ini @@ -98,6 +98,11 @@ retain_filters_by_default = true ;default_filters[] = "institution:MyInstitution" ;default_filters[] = "(format:Book AND institution:MyInstitution)" +; the escaped_colon_searches is used by a listener on the search-pre event +; registered by the MungerInjectionFactory. This listener masks colons in the query string with a backslash +; whenever the search handler is one of the following +escaped_colon_searches[] = "Signatur" + [Cache] ; This controls whether the parsed searchspecs.yaml file will be stored to ; improve search performance; legal options are APC (use APC cache), File (store @@ -554,6 +559,11 @@ topic = "Subjects" ; specified preferences using checkboxes (default if commented out = all shards): ;defaultChecked[] = "Library Catalog" ;defaultChecked[] = "Website" +; The following line defines shards that shall NOT be queried by solr requests +; all other shards present in the [IndexShards] section will always be added to any solr query +; via a listener from the MungerInjectionFactory +;on_user_search_only = "finc-live" + ; Fields must be stripped if you have a field in your main index which is missing ; from any index includable by shards. This section can be ignored if you are diff --git a/module/finc/config/module.config.php b/module/finc/config/module.config.php index 9bbe7ad0c4af359d1e11728dd0227398aac161e7..7fa79289bc6d3aad84521b84fc3fef0935e32c4d 100644 --- a/module/finc/config/module.config.php +++ b/module/finc/config/module.config.php @@ -13,7 +13,15 @@ $config = [ 'VuFind\Export' => 'finc\Service\Factory::getExport', 'VuFind\SessionManager' => 'finc\Session\ManagerFactory', 'VuFind\CookieManager' => 'finc\Service\Factory::getCookieManager' - ] + ], + 'invokables' => [ + 'mungerinjectionfactory' => 'finc\Service\MungerInjectionFactory' + ], + 'delegators' => [ + 'VuFind\Search' => [ + 'mungerinjectionfactory' + ], + ], ], 'controllers' => [ 'factories' => [ diff --git a/module/finc/src/finc/Service/MungerInjectionFactory.php b/module/finc/src/finc/Service/MungerInjectionFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..5a950a026c8540ae29b37f41eae5b4bcccec775d --- /dev/null +++ b/module/finc/src/finc/Service/MungerInjectionFactory.php @@ -0,0 +1,156 @@ +<?php +/** + * Munger Injection Factory + * + * A Delegator Factory that registers several listeners at events triggered by the + * VuFind\Search service. + * + * PHP version 7 + * + * Copyright (C) Leipzig University Library 2019. + * + * 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 Finc/Service + * @author Dorian Merz <merz@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +namespace finc\Service; + +use VuFindSearch\Query\Query; +use VuFindSearch\Query\QueryGroup; +use Zend\Config\Config; +use Zend\ServiceManager\DelegatorFactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; +use Zend\EventManager\EventInterface; +use VuFindSearch\Service as SearchService; + +/** + * Munger Injection Factory + * + * + * @category VuFind + * @package Finc/Service + * @author Dorian Merz <merz@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class MungerInjectionFactory implements DelegatorFactoryInterface +{ + /** + * @var SearchService + */ + protected $instance; + + /** + * @var array names of search handlers for which colons should be escaped + */ + protected $searches_to_escape; + + /** + * @var array shard configuration to register in all queries + */ + protected $shards_to_register; + + /** + * Creates a delegator of VuFind/Search to register several listeners. + * + * @param ServiceLocatorInterface $serviceLocator + * @param string $name + * @param string $requestedName + * @param callable $callback + * + * @return mixed + */ + public function createDelegatorWithName( + ServiceLocatorInterface $serviceLocator, + $name, + $requestedName, + $callback + ) { + $instance = call_user_func($callback); + $searchConfig = $serviceLocator->get('VuFind\Config')->get('searches'); + $e = $instance->getEventManager()->getSharedManager(); + $handlers = $searchConfig->General->escaped_colon_searches; + if (!empty($handlers)) { + $this->searches_to_escape = $handlers->toArray(); + $e->attach( + 'VuFind\Search', + 'pre', + function (EventInterface $event) { + $params = $event->getParams(); + if (isset($params['query'])) { + $params['query'] = $this->escapeColons($params['query']); + } + } + ); + } + $shards = $searchConfig->IndexShards->toArray(); + if ($excludedShards = $searchConfig->ShardPreferences->on_user_search_only) { + $shards = array_diff_key($shards, array_flip(explode(',', $excludedShards))); + } + if (!empty($shards)) { + $this->shards_to_register = $shards; + $e->attach('VuFind\Search', 'pre', [$this, 'registerShards']); + } + return $instance; + } + + /** + * Escapes colons in Queries or recursively in QueryGroups. + * This prevents queries from being interpreted as advanced queries in Lucene syntax. + * cf. \VuFindSearch\Backend\Solr\LuceneSyntaxHelper::containsAdvancedLuceneSyntax + * + * @param Query|QueryGroup $queryOrGroup + * + * @return mixed + */ + private function escapeColons($queryOrGroup) + { + if ($queryOrGroup instanceof QueryGroup) { + $handler = $queryOrGroup->getReducedHandler(); + if (is_null($handler) || in_array($handler, $this->searches_to_escape)) { + foreach ($queryOrGroup->getQueries() as $query) { + $this->escapeColons($query); + } + } + } elseif (in_array($queryOrGroup->getHandler(), $this->searches_to_escape)) { + $queryOrGroup->setString( + // mask whitespaces that follow a colon + // that avoids the removal of that very colon via + // \VuFindSearch\Backend\Solr\LuceneSyntaxHelper::normalizeColons + preg_replace('/(?<=\:)\s/', '\ ', $queryOrGroup->getString()) + ); + } + return $queryOrGroup; + } + + /** + * Event Listener on Search/Pre that registers all configured shards for every + * search request + * + * @param EventInterface $event + * + * @return void + */ + public function registerShards(EventInterface $event) + { + $params = $event->getParam('params'); + if (empty($params->get('shards'))) { + $params->set('shards', implode(',', $this->shards_to_register)); + } + } +}