Skip to content
Snippets Groups Projects
Commit 5525d81e authored by Oliver Goldschmidt's avatar Oliver Goldschmidt Committed by Demian Katz
Browse files

enables conditional filters for Solr

parent 41c6b845
No related merge requests found
...@@ -91,4 +91,12 @@ permission = access.StaffViewTab ...@@ -91,4 +91,12 @@ permission = access.StaffViewTab
; Only users with a staff affiliation can access the staff view tab ; Only users with a staff affiliation can access the staff view tab
;[shibboleth.StaffView] ;[shibboleth.StaffView]
;shibboleth = "affiliation staff@example.org" ;shibboleth = "affiliation staff@example.org"
;permission = access.StaffViewTab ;permission = access.StaffViewTab
\ No newline at end of file
; Example for conditional filters (see [ConditionalHiddenFilters] in
; searches.ini for details)
;[conditionalFilter.MyUniversity]
;require = ANY
;ipRange[] = 1.2.3.1-1.2.3.254
;role = loggedin
;permission = conditionalFilter.MyUniversity
...@@ -547,6 +547,27 @@ container_title = "Journal Title" ...@@ -547,6 +547,27 @@ container_title = "Journal Title"
;0 = "format:\"Book\" OR format:\"Journal\"" ;0 = "format:\"Book\" OR format:\"Journal\""
;1 = "language:\"English\" OR language:\"French\"" ;1 = "language:\"English\" OR language:\"French\""
; This section can get used to define conditional filters, i.e. filters
; that are applied under certain conditions.
; You can use a permission set as condition, which has to be defined in
; permissions.ini.
; Keys are ignored, but increasing numeric values (1, 2, 3...) are recommended.
; Values need to be formatted using this schema:
; [-]permission|filter-query
; Prefixing the condition with a minus (-) means that the filter is applied
; when the condition does not match (the permission is not granted).
; The filter may be any filter query valid for Solr.
; Examples:
; -conditionalFilter.MyUniversity|format:Book
; apply filter "format:Book" if permission conditionalFilter.MyUniversity
; (from permissions.ini) is not granted
; conditionalFilter.MyUniversity|format:Article
; apply filter "format:Article" if permission conditionalFilter.MyUniversity
; (from permissions.ini) is granted
[ConditionalHiddenFilters]
;0 = "-conditionalFilter.MyUniversity|format:Book"
;1 = "conditionalFilter.MyUniversity|format:Article"
; This section defines how records are handled when being fetched from Solr. ; This section defines how records are handled when being fetched from Solr.
[Records] [Records]
; Boolean value indicating if deduplication is enabled. Defaults to false. ; Boolean value indicating if deduplication is enabled. Defaults to false.
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
namespace VuFind\Search\Factory; namespace VuFind\Search\Factory;
use VuFind\Search\Solr\InjectHighlightingListener; use VuFind\Search\Solr\InjectHighlightingListener;
use VuFind\Search\Solr\InjectConditionalFilterListener;
use VuFind\Search\Solr\InjectSpellingListener; use VuFind\Search\Solr\InjectSpellingListener;
use VuFind\Search\Solr\MultiIndexListener; use VuFind\Search\Solr\MultiIndexListener;
use VuFind\Search\Solr\V3\ErrorListener as LegacyErrorListener; use VuFind\Search\Solr\V3\ErrorListener as LegacyErrorListener;
...@@ -177,6 +178,13 @@ abstract class AbstractSolrBackendFactory implements FactoryInterface ...@@ -177,6 +178,13 @@ abstract class AbstractSolrBackendFactory implements FactoryInterface
// Highlighting // Highlighting
$this->getInjectHighlightingListener($backend, $search)->attach($events); $this->getInjectHighlightingListener($backend, $search)->attach($events);
// Conditional Filters
if (isset($search->ConditionalHiddenFilters)
&& $search->ConditionalHiddenFilters->count() > 0
) {
$this->getInjectConditionalFilterListener($search)->attach($events);
}
// Spellcheck // Spellcheck
if (isset($config->Spelling->enabled) && $config->Spelling->enabled) { if (isset($config->Spelling->enabled) && $config->Spelling->enabled) {
if (isset($config->Spelling->simple) && $config->Spelling->simple) { if (isset($config->Spelling->simple) && $config->Spelling->simple) {
...@@ -395,4 +403,22 @@ abstract class AbstractSolrBackendFactory implements FactoryInterface ...@@ -395,4 +403,22 @@ abstract class AbstractSolrBackendFactory implements FactoryInterface
? $search->General->highlighting_fields : '*'; ? $search->General->highlighting_fields : '*';
return new InjectHighlightingListener($backend, $fl); return new InjectHighlightingListener($backend, $fl);
} }
/**
* Get a Conditional Filter Listener
*
* @param Config $search Search configuration
*
* @return InjectConditionalFilterListener
*/
protected function getInjectConditionalFilterListener(Config $search)
{
$listener = new InjectConditionalFilterListener(
$search->ConditionalHiddenFilters->toArray()
);
$listener->setAuthorizationService(
$this->serviceLocator->get('ZfcRbac\Service\AuthorizationService')
);
return $listener;
}
} }
\ No newline at end of file
<?php
/**
* Conditional Filter listener.
*
* PHP version 5
*
* Copyright (C) Villanova University 2013.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category VuFind2
* @package Search
* @author Oliver Goldschmidt <o.goldschmidt@tuhh.de>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link http://vufind.org Main Site
*/
namespace VuFind\Search\Solr;
use Zend\EventManager\SharedEventManagerInterface;
use Zend\EventManager\EventInterface;
use ZfcRbac\Service\AuthorizationServiceAwareInterface,
ZfcRbac\Service\AuthorizationServiceAwareTrait;
/**
* Conditional Filter listener.
*
* @category VuFind2
* @package Search
* @author Oliver Goldschmidt <o.goldschmidt@tuhh.de>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link http://vufind.org Main Site
*/
class InjectConditionalFilterListener
{
use AuthorizationServiceAwareTrait;
/**
* Filters to apply.
*
* @var array
*/
protected $filterList;
/**
* Filters from configuration.
*
* @var array
*/
protected $filters;
/**
* Constructor.
*
* @param array $searchConf Search configuration parameters
*
* @return void
*/
public function __construct($searchConf)
{
$this->filters = $searchConf;
$this->filterList = [];
}
/**
* Attach listener to shared event manager.
*
* @param SharedEventManagerInterface $manager Shared event manager
*
* @return void
*/
public function attach(SharedEventManagerInterface $manager)
{
$manager->attach('VuFind\Search', 'pre', [$this, 'onSearchPre']);
}
/**
* Add a conditional filter.
*
* @param String $configOption Conditional Filter
*
* @return void
*/
protected function addConditionalFilter($configOption)
{
$filterArr = explode('|', $configOption);
$filterCondition = $filterArr[0];
$filter = $filterArr[1];
$authService = $this->getAuthorizationService();
// if no authorization service is available, don't do anything
if (!$authService) {
return;
}
// if the filter condition starts with a minus (-), it should not match
// to get the filter applied
if (substr($filterCondition, 0, 1) == '-') {
if (!$authService->isGranted(substr($filterCondition, 1))) {
$this->filterList[] = $filter;
}
} else {
// otherwise the condition should match to apply the filter
if ($authService->isGranted($filterCondition)) {
$this->filterList[] = $filter;
}
}
}
/**
* Set up conditional hidden filters.
*
* @param EventInterface $event Event
*
* @return EventInterface
*/
public function onSearchPre(EventInterface $event)
{
// Add conditional filters
foreach ($this->filters as $fc) {
$this->addConditionalFilter($fc);
}
$params = $event->getParam('params');
$fq = $params->get('fq');
if (!is_array($fq)) {
$fq = [];
}
$new_fq = array_merge($fq, $this->filterList);
$params->set('fq', $new_fq);
return $event;
}
}
<?php
/**
* Unit tests for Conditional Filter listener.
*
* PHP version 5
*
* Copyright (C) Villanova University 2015.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category VuFind2
* @package Search
* @author Oliver Goldschmidt <o.goldschmidt@tuhh.de>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link http://vufind.org Main Site
*/
namespace VuFindTest\Search\Solr;
use VuFindSearch\ParamBag;
use VuFindSearch\Backend\Solr\Backend;
use VuFindSearch\Backend\Solr\Connector;
use VuFindSearch\Backend\Solr\HandlerMap;
use VuFind\Search\Solr\InjectConditionalFilterListener;
use VuFindTest\Unit\TestCase;
use Zend\EventManager\Event;
use ZfcRbac\Service\AuthorizationServiceAwareInterface,
ZfcRbac\Service\AuthorizationServiceAwareTrait;
/**
* Unit tests for Conditional Filter listener.
*
* @category VuFind2
* @package Search
* @author Oliver Goldschmidt <o.goldschmidt@tuhh.de>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link http://vufind.org Main Site
*/
class ConditionalFilterListenerTest extends TestCase
{
/**
* Sample configuration for ConditionalFilters.
*
* @var array
*/
protected static $searchConfig = [
'0' => '-conditionalFilter.sample|(NOT institution:"MyInst")',
'1' => 'conditionalFilter.sample|institution:"MyInst"'
];
/**
* Sample configuration for empty ConditionalFilters config.
*
* @var array
*/
protected static $emptySearchConfig = [ ];
/**
* Backend.
*
* @var BackendInterface
*/
protected $backend;
/**
* Setup.
*
* @return void
*/
protected function setup()
{
$handlermap = new HandlerMap(['select' => ['fallback' => true]]);
$connector = new Connector('http://example.org/', $handlermap);
$this->backend = new Backend($connector);
}
/**
* Test attaching listener.
*
* @return void
*/
public function testAttach()
{
$listener = new InjectConditionalFilterListener(self::$emptySearchConfig);
$mock = $this->getMock('Zend\EventManager\SharedEventManagerInterface');
$mock->expects($this->once())->method('attach')->with(
$this->equalTo('VuFind\Search'),
$this->equalTo('pre'),
$this->equalTo([$listener, 'onSearchPre'])
);
$listener->attach($mock);
}
/**
* Test the listener without setting an authorization service.
* This should return an empty array.
*
* @return void
*/
public function testConditionalFilterWithoutAuthorizationService()
{
$params = new ParamBag([ ]);
$listener = new InjectConditionalFilterListener(self::$searchConfig);
$event = new Event('pre', $this->backend, [ 'params' => $params]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals([ ], $fq);
}
/**
* Test the listener without setting an authorization service,
* but with fq-parameters.
* This should not touch the parameters.
*
* @return void
*/
public function testConditionalFilterWithoutAuthorizationServiceWithParams()
{
$params = new ParamBag(
[
'fq' => ['fulltext:Vufind', 'field2:novalue'],
]
);
$listener = new InjectConditionalFilterListener(self::$searchConfig);
$event = new Event('pre', $this->backend, [ 'params' => $params]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals(
[0 => 'fulltext:Vufind',
1 => 'field2:novalue'], $fq
);
}
/**
* Test the listener with an empty conditional filter config.
*
* @return void
*/
public function testConditionalFilterEmptyConfig()
{
$params = new ParamBag([ ]);
$listener = new InjectConditionalFilterListener(self::$emptySearchConfig);
$mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService')
->disableOriginalConstructor()
->getMock();
$listener->setAuthorizationService($mockAuth);
$event = new Event('pre', $this->backend, [ 'params' => $params]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals([ ], $fq);
}
/**
* Test the listener with an empty conditional filter config,
* but with given fq parameters
*
* @return void
*/
public function testConditionalFilterEmptyConfigWithFQ()
{
$params = new ParamBag(
[
'fq' => ['fulltext:Vufind', 'field2:novalue'],
]
);
$listener = new InjectConditionalFilterListener(self::$emptySearchConfig);
$mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService')
->disableOriginalConstructor()
->getMock();
$listener->setAuthorizationService($mockAuth);
$event = new Event('pre', $this->backend, [ 'params' => $params]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals(
[0 => 'fulltext:Vufind',
1 => 'field2:novalue'], $fq
);
}
/**
* Test the listener without preset fq parameters
* if the conditional filter is granted
*
* @return void
*/
public function testConditionalFilter()
{
$params = new ParamBag([ ]);
$listener = new InjectConditionalFilterListener(self::$searchConfig);
$mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService')
->disableOriginalConstructor()
->getMock();
$mockAuth->expects($this->any())->method('isGranted')
->with($this->equalTo('conditionalFilter.sample'))
->will($this->returnValue(true));
$listener->setAuthorizationService($mockAuth);
$event = new Event('pre', $this->backend, [ 'params' => $params]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals(
[0 => 'institution:"MyInst"'], $fq
);
}
/**
* Test the listener without preset fq parameters
* if the conditional filter is not granted
*
* @return void
*/
public function testNegativeConditionalFilter()
{
$params = new ParamBag([ ]);
$listener = new InjectConditionalFilterListener(self::$searchConfig);
$mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService')
->disableOriginalConstructor()
->getMock();
$mockAuth->expects($this->any())->method('isGranted')
->with($this->equalTo('conditionalFilter.sample'))
->will($this->returnValue(false));
$listener->setAuthorizationService($mockAuth);
$event = new Event('pre', $this->backend, [ 'params' => $params ]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals([0 => '(NOT institution:"MyInst")'], $fq);
}
/**
* Test the listener with preset fq-parameters
* if the conditional filter is not granted
*
* @return void
*/
public function testNegativeConditionalFilterWithFQ()
{
$params = new ParamBag(
[
'fq' => ['fulltext:Vufind', 'field2:novalue'],
]
);
$listener = new InjectConditionalFilterListener(self::$searchConfig);
$mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService')
->disableOriginalConstructor()
->getMock();
$mockAuth->expects($this->any())->method('isGranted')
->with($this->equalTo('conditionalFilter.sample'))
->will($this->returnValue(false));
$listener->setAuthorizationService($mockAuth);
$event = new Event('pre', $this->backend, ['params' => $params]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals(
[0 => 'fulltext:Vufind',
1 => 'field2:novalue',
2 => '(NOT institution:"MyInst")'
], $fq
);
}
/**
* Test the listener with preset fq-parameters
* if the conditional filter is granted
*
* @return void
*/
public function testConditionalFilterWithFQ()
{
$params = new ParamBag(
[
'fq' => ['fulltext:Vufind', 'field2:novalue'],
]
);
$listener = new InjectConditionalFilterListener(self::$searchConfig);
$mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService')
->disableOriginalConstructor()
->getMock();
$mockAuth->expects($this->any())->method('isGranted')
->with($this->equalTo('conditionalFilter.sample'))
->will($this->returnValue(true));
$listener->setAuthorizationService($mockAuth);
$event = new Event('pre', $this->backend, ['params' => $params]);
$listener->onSearchPre($event);
$fq = $params->get('fq');
$this->assertEquals(
[0 => 'fulltext:Vufind',
1 => 'field2:novalue',
2 => 'institution:"MyInst"'
], $fq
);
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment