diff --git a/config/vufind/searches.ini b/config/vufind/searches.ini index 02f13a8e413e46c3ee5850451fa0c1e15d983352..51ef358741c335ace64fff40b46ea6ec09f19a32 100644 --- a/config/vufind/searches.ini +++ b/config/vufind/searches.ini @@ -606,3 +606,18 @@ view=full ; Priority order (descending) for record sources (record ID prefixes separated ; from the actual record by period, e.g. testsrc.12345) ;sources = alli,testsrc + +; This section defines settings used to fetch similar records. +[MoreLikeThis] +; Boolean value indicating whether the newer MoreLikeThis query handler should be +; used instead of the traditional MoreLikeThis component (default). Only the +; MoreLikeThis query handler supports sharded indexes, but as of this writing, the +; traditional component offers more nuanced relevance ranking. Results from these +; methods may differ. +;useMoreLikeThisHandler = true +; If the MoreLikeThis handler is used, this setting can be used to adjust its +; behavior. See https://cwiki.apache.org/confluence/display/solr/Other+Parsers#OtherParsers-MoreLikeThisQueryParser +; for more information regarding the possible parameters. +;params = "qf=title,title_short,callnumber-label,topic,language,author,publishDate mintf=1 mindf=1"; +; This setting can be used to limit the maximum number of suggestions. Default is 5. +;count = 5 diff --git a/module/VuFind/src/VuFind/Search/Factory/AbstractSolrBackendFactory.php b/module/VuFind/src/VuFind/Search/Factory/AbstractSolrBackendFactory.php index 54b99faa3ddc9d61e94082c4c6d7d28778d210e5..e0987fc71eb2dd63baea854232c53d647256628b 100644 --- a/module/VuFind/src/VuFind/Search/Factory/AbstractSolrBackendFactory.php +++ b/module/VuFind/src/VuFind/Search/Factory/AbstractSolrBackendFactory.php @@ -42,6 +42,7 @@ use VuFind\Search\Solr\HierarchicalFacetListener; use VuFindSearch\Backend\BackendInterface; use VuFindSearch\Backend\Solr\LuceneSyntaxHelper; use VuFindSearch\Backend\Solr\QueryBuilder; +use VuFindSearch\Backend\Solr\SimilarBuilder; use VuFindSearch\Backend\Solr\HandlerMap; use VuFindSearch\Backend\Solr\Connector; use VuFindSearch\Backend\Solr\Backend; @@ -156,6 +157,7 @@ abstract class AbstractSolrBackendFactory implements FactoryInterface { $backend = new Backend($connector); $backend->setQueryBuilder($this->createQueryBuilder()); + $backend->setSimilarBuilder($this->createSimilarBuilder()); if ($this->logger) { $backend->setLogger($this->logger); } @@ -372,6 +374,18 @@ abstract class AbstractSolrBackendFactory implements FactoryInterface return $builder; } + /** + * Create the similar records query builder. + * + * @return SimilarBuilder + */ + protected function createSimilarBuilder() + { + return new SimilarBuilder( + $this->config->get($this->searchConfig), $this->uniqueKey + ); + } + /** * Load the search specs. * diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Backend.php b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Backend.php index dd4a451cd9f7d089ff8f014ee99c364d8b172c46..75afebd866b106a32dd872830c705ac6e1620050 100644 --- a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Backend.php +++ b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Backend.php @@ -73,6 +73,13 @@ class Backend extends AbstractBackend */ protected $queryBuilder = null; + /** + * Similar records query builder. + * + * @var SimilarBuilder + */ + protected $similarBuilder = null; + /** * Constructor. * @@ -211,6 +218,7 @@ class Backend extends AbstractBackend $params = $params ?: new ParamBag(); $this->injectResponseWriter($params); + $params->mergeWith($this->getSimilarBuilder()->build($id, $params)); $response = $this->connector->similar($id, $params); $collection = $this->createRecordCollection($response); $this->injectSourceIdentifier($collection); @@ -304,6 +312,33 @@ class Backend extends AbstractBackend return $this->queryBuilder; } + /** + * Set the similar records query builder. + * + * @param SimilarBuilder $similarBuilder Similar builder + * + * @return void + */ + public function setSimilarBuilder(SimilarBuilder $similarBuilder) + { + $this->similarBuilder = $similarBuilder; + } + + /** + * Return similar records query builder. + * + * Lazy loads an empty default SimilarBuilder if none was set. + * + * @return SimilarBuilder + */ + public function getSimilarBuilder() + { + if (!$this->similarBuilder) { + $this->similarBuilder = new SimilarBuilder(); + } + return $this->similarBuilder; + } + /** * Return the record collection factory. * diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Connector.php b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Connector.php index 2aa7fca030a59dea9514f891c530f67692558e67..8471b6726b24a724d5ad4707cf025c168aa0cab7 100644 --- a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Connector.php +++ b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Connector.php @@ -185,23 +185,20 @@ class Connector implements \Zend\Log\LoggerAwareInterface /** * Return records similar to a given record specified by id. * - * Uses MoreLikeThis Request Handler + * Uses MoreLikeThis Request Component or MoreLikeThis Handler * - * @param string $id Id of given record + * @param string $id ID of given record (not currently used, but + * retained for backward compatibility / extensibility). * @param ParamBag $params Parameters * * @return string + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function similar($id, ParamBag $params = null) + public function similar($id, ParamBag $params) { - $params = $params ?: new ParamBag(); - $params - ->set('q', sprintf('%s:"%s"', $this->uniqueKey, addcslashes($id, '"'))); - $params->set('qt', 'morelikethis'); - $handler = $this->map->getHandler(__FUNCTION__); $this->map->prepare(__FUNCTION__, $params); - return $this->query($handler, $params); } diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/SimilarBuilder.php b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/SimilarBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..b9a47fcd2257f76efd22f7b9a04f43aab7a2b800 --- /dev/null +++ b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/SimilarBuilder.php @@ -0,0 +1,136 @@ +<?php + +/** + * SOLR SimilarBuilder. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * Copyright (C) The National Library of Finland 2016. + * + * 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 Search + * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> + * @author David Maus <maus@hab.de> + * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ +namespace VuFindSearch\Backend\Solr; + +use VuFindSearch\ParamBag; + +/** + * SOLR SimilarBuilder. + * + * @category VuFind + * @package Search + * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> + * @author David Maus <maus@hab.de> + * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ +class SimilarBuilder implements SimilarBuilderInterface +{ + /** + * Solr field used to store unique identifier + * + * @var string + */ + protected $uniqueKey; + + /** + * Whether to use MoreLikeThis Handler instead of the traditional MoreLikeThis + * component. + * + * @var bool + */ + protected $useHandler = false; + + /** + * MoreLikeThis Handler parameters + * + * @var string + */ + protected $handlerParams = ''; + + /** + * Number of similar records to retrieve + * + * @var int + */ + protected $count = 5; + + /** + * Constructor. + * + * @param \Zend\Config\Config $searchConfig Search config + * @param string $uniqueKey Solr field used to store unique + * identifier + * + * @return void + */ + public function __construct(\Zend\Config\Config $searchConfig = null, + $uniqueKey = 'id' + ) { + $this->uniqueKey = $uniqueKey; + if (isset($searchConfig->MoreLikeThis)) { + $mlt = $searchConfig->MoreLikeThis; + if (isset($mlt->useMoreLikeThisHandler) + && $mlt->useMoreLikeThisHandler + ) { + $this->useHandler = true; + $this->handlerParams = isset($mlt->params) ? $mlt->params : ''; + } + if (isset($mlt->count)) { + $this->count = $mlt->count; + } + } + } + + /// Public API + + /** + * Return SOLR search parameters based on a record Id and params. + * + * @param string $id Record Id + * + * @return ParamBag + */ + public function build($id) + { + $params = new ParamBag(); + if ($this->useHandler) { + $mltParams = $this->handlerParams + ? $this->handlerParams + : 'qf=title,title_short,callnumber-label,topic,language,author,' + . 'publishDate mintf=1 mindf=1'; + $params->set('q', sprintf('{!mlt %s}%s', $mltParams, $id)); + } else { + $params->set( + 'q', sprintf('%s:"%s"', $this->uniqueKey, addcslashes($id, '"')) + ); + $params->set('qt', 'morelikethis'); + } + if (null === $params->get('rows')) { + $params->set('rows', $this->count); + } + return $params; + } +} diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/SimilarBuilderInterface.php b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/SimilarBuilderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1397e45cd96b70f7146ddf40604cd43acc555d85 --- /dev/null +++ b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/SimilarBuilderInterface.php @@ -0,0 +1,59 @@ +<?php + +/** + * SOLR SimilarBuilder interface definition. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * Copyright (C) The National Library of Finland 2016. + * + * 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 Search + * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> + * @author David Maus <maus@hab.de> + * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ +namespace VuFindSearch\Backend\Solr; + +use VuFindSearch\ParamBag; + +/** + * SOLR SimilarBuilder interface definition. + * + * @category VuFind + * @package Search + * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> + * @author David Maus <maus@hab.de> + * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ +interface SimilarBuilderInterface +{ + /** + * Build SOLR query based on VuFind query object. + * + * @param string $id Record id + * + * @return ParamBag + */ + public function build($id); +} diff --git a/module/VuFindSearch/tests/unit-tests/src/VuFindTest/Backend/Solr/SimilarBuilderTest.php b/module/VuFindSearch/tests/unit-tests/src/VuFindTest/Backend/Solr/SimilarBuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1f07a0694f5b97986c8ce9ea065af23f29de5237 --- /dev/null +++ b/module/VuFindSearch/tests/unit-tests/src/VuFindTest/Backend/Solr/SimilarBuilderTest.php @@ -0,0 +1,114 @@ +<?php + +/** + * Unit tests for SOLR similar records query builder + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * Copyright (C) The National Library of Finland 2016. + * + * 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 Search + * @author David Maus <maus@hab.de> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ +namespace VuFindTest\Backend\Solr; + +use VuFindSearch\Backend\Solr\SimilarBuilder; + +/** + * Unit tests for SOLR similar records query builder + * + * @category VuFind + * @package Search + * @author David Maus <maus@hab.de> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ +class SimilarBuilderTest extends \VuFindTest\Unit\TestCase +{ + /** + * Test builder with default params. + * + * @return void + */ + public function testDefaultParams() + { + $sb = new SimilarBuilder(); + $response = $sb->build('testrecord'); + $rows = $response->get('rows'); + $this->assertEquals(5, $rows[0]); + $q = $response->get('q'); + $this->assertEquals('id:"testrecord"', $q[0]); + $qt = $response->get('qt'); + $this->assertEquals('morelikethis', $qt[0]); + } + + /** + * Test builder with alternative id field. + * + * @return void + */ + public function testAlternativeIdField() + { + $sb = new SimilarBuilder(null, 'key'); + $response = $sb->build('testrecord'); + $q = $response->get('q'); + $this->assertEquals('key:"testrecord"', $q[0]); + } + + /** + * Test builder with different configurations. + * + * @return void + */ + public function testMltConfig() + { + $config = [ + 'MoreLikeThis' => [ + 'count' => 10 + ] + ]; + $sb = new SimilarBuilder(new \Zend\Config\Config($config)); + $response = $sb->build('testrecord'); + $rows = $response->get('rows'); + $this->assertEquals(10, $rows[0]); + + $config['MoreLikeThis']['useMoreLikeThisHandler'] = true; + $sb = new SimilarBuilder(new \Zend\Config\Config($config)); + $response = $sb->build('testrecord'); + $rows = $response->get('rows'); + $this->assertEquals(10, $rows[0]); + $q = $response->get('q'); + $this->assertEquals( + '{!mlt qf=title,title_short,callnumber-label,topic,language,author,' + . 'publishDate mintf=1 mindf=1}testrecord', + $q[0] + ); + $qt = $response->get('qt'); + $this->assertEquals(null, $qt); + + $config['MoreLikeThis']['params'] = 'qf=title,topic'; + $sb = new SimilarBuilder(new \Zend\Config\Config($config)); + $response = $sb->build('testrecord'); + $q = $response->get('q'); + $this->assertEquals('{!mlt qf=title,topic}testrecord', $q[0]); + } +}