diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetFacetData.php b/module/VuFind/src/VuFind/AjaxHandler/GetFacetData.php index 1bad9033de9010f87454295f9f9fd345cfc6bd87..8af94aab924ff076516644ea9ff591ae3108cb4f 100644 --- a/module/VuFind/src/VuFind/AjaxHandler/GetFacetData.php +++ b/module/VuFind/src/VuFind/AjaxHandler/GetFacetData.php @@ -93,25 +93,39 @@ class GetFacetData extends AbstractBase { $this->disableSessionWrites(); // avoid session write timing bug - $facet = $params->fromQuery('facetName'); - $sort = $params->fromQuery('facetSort'); - $operator = $params->fromQuery('facetOperator'); - $backend = $params->fromQuery('source', DEFAULT_SEARCH_BACKEND); + // Allow both GET and POST variables: + $request = $params->fromQuery() + $params->fromPost(); + + $facet = $request['facetName'] ?? null; + $sort = $request['facetSort'] ?? null; + $operator = $request['facetOperator'] ?? null; + $backend = $request['source'] ?? DEFAULT_SEARCH_BACKEND; $results = $this->resultsManager->get($backend); $paramsObj = $results->getParams(); $paramsObj->addFacet($facet, null, $operator === 'OR'); - $paramsObj->initFromRequest(new Parameters($params->fromQuery())); + $paramsObj->initFromRequest(new Parameters($request)); $facets = $results->getFullFieldFacets([$facet], false, -1, 'count'); if (empty($facets[$facet]['data']['list'])) { $facets = []; } else { + // Set appropriate query suppression / extra field behavior: + $queryHelper = $results->getUrlQuery(); + $queryHelper->setSuppressQuery( + (bool)($request['querySuppressed'] ?? false) + ); + $extraFields = array_filter(explode(',', $request['extraFields'] ?? '')); + foreach ($extraFields as $field) { + if (isset($request[$field])) { + $queryHelper->setDefaultParameter($field, $request[$field]); + } + } + $facetList = $facets[$facet]['data']['list']; $this->facetHelper->sortFacetList($facetList, $sort); - $facets = $this->facetHelper->buildFacetArray( - $facet, $facetList, $results->getUrlQuery(), false - ); + $facets = $this->facetHelper + ->buildFacetArray($facet, $facetList, $queryHelper, false); } return $this->formatResponse(compact('facets')); } diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php b/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php index eee1cf677c468128cb822d8431756ba3666b65a1..cdce8dcc38f5dafe9e3738a9537ceb34cf867d7b 100644 --- a/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php +++ b/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php @@ -132,6 +132,16 @@ class GetSideFacets extends \VuFind\AjaxHandler\AbstractBase return $this->formatResponse('', self::STATUS_HTTP_ERROR); } + // Set appropriate query suppression / extra field behavior: + $queryHelper = $results->getUrlQuery(); + $queryHelper->setSuppressQuery((bool)($request['querySuppressed'] ?? false)); + $extraFields = array_filter(explode(',', $request['extraFields'] ?? '')); + foreach ($extraFields as $field) { + if (isset($request[$field])) { + $queryHelper->setDefaultParameter($field, $request[$field]); + } + } + $recommend = $results->getRecommendations($configLocation)[0] ?? null; if (null === $recommend) { return $this->formatResponse( diff --git a/module/VuFind/src/VuFind/Search/UrlQueryHelper.php b/module/VuFind/src/VuFind/Search/UrlQueryHelper.php index cff9f71510ee9d2146c48e96e64acff4bdc9a6d8..507bb2355f1a19c443b871f5cde5c4545f3f8e6c 100644 --- a/module/VuFind/src/VuFind/Search/UrlQueryHelper.php +++ b/module/VuFind/src/VuFind/Search/UrlQueryHelper.php @@ -186,19 +186,40 @@ class UrlQueryHelper } /** - * Add a parameter to the object. + * Set the default value of a parameter, and add that parameter to the object + * if it is not already defined. * - * @param string $name Name of parameter - * @param string $value Value of parameter + * @param string $name Name of parameter + * @param string $value Value of parameter + * @param bool $forceOverride Force an override of the existing value, even if + * it was set in the incoming $urlParams in the constructor (defaults to true for + * backward compatibility) * * @return UrlQueryHelper */ - public function setDefaultParameter($name, $value) + public function setDefaultParameter($name, $value, $forceOverride = true) { - $this->urlParams[$name] = $value; + // Add the new default to the configuration, and apply it to the query + // if no existing value has already been set in this position (or if an + // override has been forced). + $this->config['defaults'][$name] = $value; + if (!isset($this->urlParams[$name]) || $forceOverride) { + $this->urlParams[$name] = $value; + } return $this; } + /** + * Get an array of field names with configured defaults; this is a useful way + * to identify custom query parameters added through setDefaultParameter(). + * + * @return array + */ + public function getParamsWithConfiguredDefaults() + { + return array_keys($this->config['defaults'] ?? []); + } + /** * Control query suppression * diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Search/UrlQueryHelperTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/UrlQueryHelperTest.php index b0eadd2804f94d3608635c4d2685b344b9f48393..b3f59b0fa3e2b57d16738419d34b8938798eff30 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Search/UrlQueryHelperTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/UrlQueryHelperTest.php @@ -44,16 +44,28 @@ use VuFindTest\Unit\TestCase as TestCase; */ class UrlQueryHelperTest extends TestCase { + /** + * Get a preconfigured helper. + * + * @param array $request Request parameters + * @param Query $query Query object + * + * @return UrlQueryHelper + */ + protected function getHelper($request = ['foo' => 'bar'], $query = null) + { + return new UrlQueryHelper($request, $query ?? new Query('search')); + } + /** * Test the basic functionality of the helper. * * @return void */ - public function testBasicFunctionality() + public function testBasicGetters() { // Test basic getters - $query = new Query('search'); - $helper = new UrlQueryHelper(['foo' => 'bar'], $query); + $helper = $this->getHelper(); $this->assertEquals('?foo=bar&lookfor=search', $helper->getParams()); $this->assertEquals('?foo=bar&lookfor=search', (string)$helper); $this->assertEquals( @@ -63,54 +75,114 @@ class UrlQueryHelperTest extends TestCase '<input type="hidden" name="foo" value="bar" />', $helper->asHiddenFields(['lookfor' => '/.*/']) ); + } - // Test setDefaultParameters and disabling escaping + /** + * Test the behavior of setDefaultParameters + * + * @return void + */ + public function testSetDefaultParameters() + { + $helper = $this->getHelper(); + + // Test setDefaultParameters and disabling escaping. Note that, because + // the "foo" parameter is already set in the original request, adding a + // default does NOT override the existing value. + $this->assertEquals( + '?foo=bar&lookfor=search', + $helper->setDefaultParameter('foo', 'baz', false)->getParams(false) + ); + // Now let's add a default parameter that was NOT part of the incoming + // request... we DO want this to get added to the query: + $this->assertEquals( + '?foo=bar&lookfor=search&xyzzy=true', + $helper->setDefaultParameter('xyzzy', 'true', false)->getParams(false) + ); + // Finally, let's force an override of an existing parameter: $this->assertEquals( - '?foo=baz&lookfor=search', - $helper->setDefaultParameter('foo', 'baz')->getParams(false) + '?foo=baz&lookfor=search&xyzzy=true', + $helper->setDefaultParameter('foo', 'baz', true)->getParams(false) ); - // Test query suppression + // Confirm that we can look up a list of configured parameters: + $this->assertEquals( + ['foo', 'xyzzy'], $helper->getParamsWithConfiguredDefaults() + ); + } + + /** + * Test query suppression. + * + * @return void + */ + public function testQuerySuppression() + { + $helper = $this->getHelper(); $this->assertEquals(false, $helper->isQuerySuppressed()); $helper->setSuppressQuery(true); $this->assertEquals(true, $helper->isQuerySuppressed()); - $this->assertEquals('?foo=baz', $helper->getParams()); + $this->assertEquals('?foo=bar', $helper->getParams()); $helper->setSuppressQuery(false); $this->assertEquals(false, $helper->isQuerySuppressed()); - $this->assertEquals('?foo=baz&lookfor=search', $helper->getParams(false)); + $this->assertEquals('?foo=bar&lookfor=search', $helper->getParams(false)); + } - // Test replacing query terms + /** + * Test replacing query terms + * + * @return void + */ + public function testReplacingQueryTerms() + { + $helper = $this->getHelper(); $this->assertEquals( - '?foo=baz&lookfor=srch', + '?foo=bar&lookfor=srch', $helper->replaceTerm('search', 'srch')->getParams() ); $this->assertEquals( - '?foo=baz&lookfor=srch', + '?foo=bar&lookfor=srch', $helper->setSearchTerms('srch')->getParams() ); + } - // Test adding/removing facets and filters + /** + * Test adding/removing facets and filters + * + * @return void + */ + public function testFacetsAndFilters() + { + $helper = $this->getHelper(); $faceted = $helper->addFacet('f', '1')->addFilter('f:2'); $this->assertEquals( - '?foo=baz&lookfor=search&filter%5B%5D=f%3A%221%22&filter%5B%5D=f%3A2', + '?foo=bar&lookfor=search&filter%5B%5D=f%3A%221%22&filter%5B%5D=f%3A2', $faceted->getParams(false) ); $this->assertEquals( - '?foo=baz&lookfor=search&filter%5B%5D=f%3A%221%22', + '?foo=bar&lookfor=search&filter%5B%5D=f%3A%221%22', $faceted->removeFacet('f', '2')->getParams(false) ); $this->assertEquals( - '?foo=baz&lookfor=search&filter%5B%5D=f%3A2', + '?foo=bar&lookfor=search&filter%5B%5D=f%3A2', $faceted->removeFilter('f:1')->getParams(false) ); $this->assertEquals( - '?foo=baz&lookfor=search', + '?foo=bar&lookfor=search', $faceted->removeAllFilters()->getParams(false) ); + } - // Test stacking setters + /** + * Test stacking setters + * + * @return void + */ + public function testStackingSetters() + { + $helper = $this->getHelper(); $this->assertEquals( - '?foo=baz&sort=title&view=grid&lookfor=search&type=x&limit=50&page=3', + '?foo=bar&sort=title&view=grid&lookfor=search&type=x&limit=50&page=3', $helper->setSort('title')->setViewParam('grid')->setHandler('x') ->setLimit(50)->setPage(3)->getParams(false) ); diff --git a/themes/bootstrap3/js/facets.js b/themes/bootstrap3/js/facets.js index 5700d0c927d33febf15e10d783e1217f29853188..cb51fc1ef98a1cba121826f96b733b7ac53328cb 100644 --- a/themes/bootstrap3/js/facets.js +++ b/themes/bootstrap3/js/facets.js @@ -106,25 +106,23 @@ function initFacetTree(treeNode, inSidebar) } treeNode.data('loaded', true); - var source = treeNode.data('source'); - var facet = treeNode.data('facet'); - var operator = treeNode.data('operator'); - var sort = treeNode.data('sort'); - var query = window.location.href.split('?')[1]; - if (inSidebar) { treeNode.prepend('<li class="list-group-item"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i></li>'); } else { treeNode.prepend('<div><i class="fa fa-spinner fa-spin" aria-hidden="true"></i><div>'); } - $.getJSON(VuFind.path + '/AJAX/JSON?' + query, - { - method: "getFacetData", - source: source, - facetName: facet, - facetSort: sort, - facetOperator: operator - }, + var request = { + method: "getFacetData", + source: treeNode.data('source'), + facetName: treeNode.data('facet'), + facetSort: treeNode.data('sort'), + facetOperator: treeNode.data('operator'), + query: treeNode.data('query'), + querySuppressed: treeNode.data('querySuppressed'), + extraFields: treeNode.data('extraFields') + }; + $.getJSON(VuFind.path + '/AJAX/JSON?' + request.query, + request, function getFacetData(response/*, textStatus*/) { buildFacetTree(treeNode, response.data.facets, inSidebar); } @@ -178,18 +176,18 @@ VuFind.register('sideFacets', function SideFacets() { if (facetList.length === 0) { return; } - var urlParts = window.location.href.split('?'); - var query = urlParts.length > 1 ? urlParts[1] : ''; var request = { method: 'getSideFacets', searchClassId: $container.data('searchClassId'), location: $container.data('location'), configIndex: $container.data('configIndex'), - query: query, + query: $container.data('query'), + querySuppressed: $container.data('querySuppressed'), + extraFields: $container.data('extraFields'), enabledFacets: facetList }; $container.find('.facet-load-indicator').removeClass('hidden'); - $.getJSON(VuFind.path + '/AJAX/JSON?' + query, request) + $.getJSON(VuFind.path + '/AJAX/JSON?' + request.query, request) .done(function onGetSideFacetsDone(response) { $.each(response.data.facets, function initFacet(facet, facetData) { var containerSelector = typeof facetData.checkboxCount !== 'undefined' diff --git a/themes/bootstrap3/templates/Recommend/SideFacets/facet.phtml b/themes/bootstrap3/templates/Recommend/SideFacets/facet.phtml index 502b89ff9d89a395808d0b81ac1713a67d01a03e..e8281567430f7d66a60abec4015ed6bd261fb324 100644 --- a/themes/bootstrap3/templates/Recommend/SideFacets/facet.phtml +++ b/themes/bootstrap3/templates/Recommend/SideFacets/facet.phtml @@ -29,7 +29,8 @@ 'allowExclude' => $this->recommend->excludeAllowed($facet), 'title' => $facet, 'sortOptions' => $hierarchicalFacetSortOptions[$facet] ?? $hierarchicalFacetSortOptions['*'] ?? null, - 'collapsedFacets' => $this->collapsedFacets + 'collapsedFacets' => $this->collapsedFacets, + 'results' => $this->results ] ); ?> <noscript> diff --git a/themes/bootstrap3/templates/Recommend/SideFacets/hierarchical-facet.phtml b/themes/bootstrap3/templates/Recommend/SideFacets/hierarchical-facet.phtml index ddfb7bd09f820d46f3d63eb174af13897e84aa77..6679b09e39ac1b351e405e98158ff97ed191ad06 100644 --- a/themes/bootstrap3/templates/Recommend/SideFacets/hierarchical-facet.phtml +++ b/themes/bootstrap3/templates/Recommend/SideFacets/hierarchical-facet.phtml @@ -1,4 +1,18 @@ -<?php $this->headScript()->appendFile('vendor/jsTree/jstree.min.js'); ?> +<?php + $this->headScript()->appendFile('vendor/jsTree/jstree.min.js'); + + // We need to pass the current URL query to the Javascript; we use substr() to + // strip off the leading ? character. The "suppress query" option is used for + // special search types like course reserves / new items. The AJAX handler needs + // the real Solr query in order to process facets correctly, so we need to + // unsuppress it here. + $querySuppressed = $this->results->getUrlQuery()->isQuerySuppressed(); + $urlQuery = substr($this->results->getUrlQuery()->setSuppressQuery(false)->getParams(false), 1); + $this->results->getUrlQuery()->setSuppressQuery($querySuppressed); // restore original config + // We also need to inform the helper about any special parameters used in place + // of the suppressed query: + $extraUrlFields = $this->results->getUrlQuery()->getParamsWithConfiguredDefaults(); +?> <?php if (!in_array($this->title, $this->collapsedFacets)): ?> <?php $script = <<<JS @@ -25,5 +39,8 @@ JS; data-exclude="<?=$this->allowExclude?>" data-operator="<?=$this->recommend->getFacetOperator($this->title)?>" data-exclude-title="<?=$this->transEsc('exclude_facet')?>" - data-sort="<?=isset($this->sortOptions) ? $this->sortOptions : ''?>"> + data-sort="<?=isset($this->sortOptions) ? $this->sortOptions : ''?>" + data-query="<?=$this->escapeHtmlAttr($urlQuery)?>" + data-query-suppressed="<?=$querySuppressed ? '1' : '0' ?>" + data-extra-fields="<?=$this->escapeHtml(implode(',', $extraUrlFields))?>"> </li> diff --git a/themes/bootstrap3/templates/Recommend/SideFacetsDeferred.phtml b/themes/bootstrap3/templates/Recommend/SideFacetsDeferred.phtml index 4e82bd5a93c28686230b5d556e4b19dcdac88335..11a0e7acbc0decae2d71e7e6db19ce824dced224 100644 --- a/themes/bootstrap3/templates/Recommend/SideFacetsDeferred.phtml +++ b/themes/bootstrap3/templates/Recommend/SideFacetsDeferred.phtml @@ -1,81 +1,98 @@ <?php - $this->headScript()->appendFile('vendor/jsTree/jstree.min.js'); - $this->headScript()->appendFile('facets.js'); - - $results = $this->recommend->getResults(); - $activeFacets = $this->recommend->getActiveFacets(); - $rangeFacets = $this->recommend->getAllRangeFacets(); - - foreach ($activeFacets as $field => $facetName) { - if (isset($rangeFacets[$field]) && 'date' === $rangeFacets[$field]['type']) { - $this->headScript()->appendFile('vendor/bootstrap-slider.min.js'); - $this->headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); - break; + $this->headScript()->appendFile('vendor/jsTree/jstree.min.js'); + $this->headScript()->appendFile('facets.js'); + + $results = $this->recommend->getResults(); + $activeFacets = $this->recommend->getActiveFacets(); + $rangeFacets = $this->recommend->getAllRangeFacets(); + $collapsedFacets = $this->recommend->getCollapsedFacets(); + $forceUncollapsedFacets = []; + + // Make sure facets with active selections are not collapsed: + $filterList = $results->getParams()->getFilterList(true); + foreach ($filterList as $field => $filters) { + foreach ($filters as $filter) { + $index = isset($filter['field']) ? array_search($filter['field'], $collapsedFacets) : false; + if ($index !== false) { + unset($collapsedFacets[$index]); // Open if we have a match + $forceUncollapsedFacets[] = $filter['field']; + } + } + } + + foreach ($activeFacets as $field => $facetName) { + if (isset($rangeFacets[$field]) && 'date' === $rangeFacets[$field]['type']) { + $this->headScript()->appendFile('vendor/bootstrap-slider.min.js'); + $this->headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); + break; + } } - } ?> <?php if ($results->getResultTotal() > 0): ?> - <h2><?=$this->transEsc(isset($this->overrideSideFacetCaption) ? $this->overrideSideFacetCaption : 'Narrow Search')?></h2> - <div class="side-facets-container-ajax" data-search-class-id="<?=$this->escapeHtmlAttr($this->searchClassId) ?>" data-location="<?=$this->escapeHtmlAttr($this->location) ?>" data-config-index="<?=$this->escapeHtmlAttr($this->configIndex) ?>"> -<?php endif; ?> -<?php $checkboxFilters = $results->getParams()->getCheckboxFacets(); ?> -<?php $checkboxesShown = false; ?> -<?php if (count($checkboxFilters) > 0): - foreach ($checkboxFilters as $current) { - if ($results->getResultTotal() > 0 || $current['selected'] || $current['alwaysVisible']) { - $checkboxesShown = true; - break; - } - } - ?> - <?php if ($checkboxesShown): ?> - <div class="checkboxFilter"> - <?=$this->context($this)->renderInContext('Recommend/SideFacets/checkbox-filters.phtml', ['checkboxFilters' => $checkboxFilters, 'results' => $results]); ?> - </div> - <?php endif; ?> -<?php endif; ?> -<?php $extraFilters = isset($this->extraSideFacetFilters) ? $this->extraSideFacetFilters : []; ?> -<?php $collapsedFacets = $this->recommend->getCollapsedFacets() ?> -<?php $filterList = array_merge($results->getParams()->getFilterList(true), $extraFilters); ?> -<?php if (!empty($filterList)): ?> - <?=$this->context($this)->renderInContext('Recommend/SideFacets/filter-list.phtml', [ - 'collapsedFacets' => $collapsedFacets, - 'extraFilters' => $extraFilters, - 'filterList' => $filterList, - ]); ?> -<?php endif; ?> -<?= isset($this->sideFacetExtraControls) ? $this->sideFacetExtraControls : '' ?> -<?php $sideFacetSet = $this->recommend->getFacetSet(); ?> -<?php $hierarchicalFacets = $this->recommend->getHierarchicalFacets() ?> -<?php $hierarchicalFacetSortOptions = $this->recommend->getHierarchicalFacetSortOptions() ?> -<?php if (!empty($activeFacets) && $results->getResultTotal() > 0): ?> - <?php foreach ($activeFacets as $field => $facetName): ?> - <?php $allowExclude = $this->recommend->excludeAllowed($field); ?> - <div class="facet-group" id="side-panel-<?=$this->escapeHtmlAttr($field) ?>"> - <button class="title<?php if (in_array($field, $collapsedFacets)): ?> collapsed<?php endif ?>" data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($field) ?>" > - <?=$this->transEsc($facetName)?> - </button> - <div id="side-collapse-<?=$this->escapeHtmlAttr($field) ?>" class="collapse<?php if (!in_array($field, $collapsedFacets)): ?> in<?php endif ?>" data-facet="<?=$this->escapeHtmlAttr($field) ?>"> + <h2><?=$this->transEsc($this->slot('side-facet-caption')->get('Narrow Search')) ?></h2> +<?php + // We need to pass the current URL query to the Javascript; we use substr() to + // strip off the leading ? character. The "suppress query" option is used for + // special search types like course reserves / new items. The AJAX handler needs + // the real Solr query in order to process facets correctly, so we need to + // unsuppress it here. + $querySuppressed = $results->getUrlQuery()->isQuerySuppressed(); + $urlQuery = substr($results->getUrlQuery()->setSuppressQuery(false)->getParams(false), 1); + $results->getUrlQuery()->setSuppressQuery($querySuppressed); // restore original config + // We also need to inform the helper about any special parameters used in place + // of the suppressed query: + $extraUrlFields = $results->getUrlQuery()->getParamsWithConfiguredDefaults(); +?> +<div class="side-facets-container-ajax" data-search-class-id="<?=$this->escapeHtmlAttr($this->searchClassId) ?>" data-location="<?=$this->escapeHtmlAttr($this->location) ?>" data-config-index="<?=$this->escapeHtmlAttr($this->configIndex)?>" data-query="<?=$this->escapeHtmlAttr($urlQuery)?>" data-query-suppressed="<?=$querySuppressed ? '1' : '0' ?>" data-extra-fields="<?=$this->escapeHtml(implode(',', $extraUrlFields))?>"> + <?php endif; ?> + <?php $checkboxFilters = $this->recommend->getCheckboxFacetSet(); ?> + <?php $checkboxesShown = false; ?> + <?php if (count($checkboxFilters) > 0): + foreach ($checkboxFilters as $current) { + if ($results->getResultTotal() > 0 || $current['alwaysVisible']) { + $checkboxesShown = true; + break; + } + } + ?> + <?php if ($checkboxesShown): ?> + <div class="checkboxFilter"> + <?=$this->context($this)->renderInContext('Recommend/SideFacets/checkbox-filters.phtml', ['checkboxFilters' => $checkboxFilters, 'results' => $results]); ?> + </div> + <?php endif; ?> + <?php endif; ?> + <?= $this->sideFacetExtraControls ?? '' ?> + <?php $sideFacetSet = $this->recommend->getFacetSet(); ?> + <?php $hierarchicalFacets = $this->recommend->getHierarchicalFacets() ?> + <?php $hierarchicalFacetSortOptions = $this->recommend->getHierarchicalFacetSortOptions() ?> + <?php if (!empty($activeFacets) && $results->getResultTotal() > 0): ?> + <?php foreach ($activeFacets as $field => $facetName): ?> + <?php $allowExclude = $this->recommend->excludeAllowed($field); ?> + <div class="facet-group" id="side-panel-<?=$this->escapeHtmlAttr($field) ?>"> + <button class="title<?php if (in_array($field, $collapsedFacets)): ?> collapsed<?php endif ?>" data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($field) ?>" > + <?=$this->transEsc($facetName)?> + </button> + <div id="side-collapse-<?=$this->escapeHtmlAttr($field) ?>" class="collapse<?php if (!in_array($field, $collapsedFacets)): ?> in<?php endif ?>" data-facet="<?=$this->escapeHtmlAttr($field) ?>"<?php if (in_array($field, $forceUncollapsedFacets)): ?> data-force-in="1"<?php endif ?>> <span class="facet-load-indicator hidden"> <span class="text"> <i class="fa fa-spinner fa-spin"></i> <?=$this->transEsc('Loading')?>... </span> </span> - <span class="facet-load-failed hidden"> <?=$this->transEsc('ajax_load_interrupted')?></span> - <?php if (in_array($field, $hierarchicalFacets)): ?> - <div id="facet_<?=$this->escapeHtml($field)?>" class="jstree-facet" - data-facet="<?=$this->escapeHtmlAttr($field)?>" - data-path="" - data-exclude="<?=$allowExclude?>" - data-operator="<?=$this->recommend->getFacetOperator($field)?>" - data-exclude-title="<?=$this->transEsc('exclude_facet')?>" - data-sort="<?=$hierarchicalFacetSortOptions[$field] ?? ''?>"> + <span class="facet-load-failed hidden"> <?=$this->transEsc('ajax_load_interrupted')?></span> + <?php if (in_array($field, $hierarchicalFacets)): ?> + <div id="facet_<?=$this->escapeHtml($field)?>" class="jstree-facet" + data-facet="<?=$this->escapeHtmlAttr($field)?>" + data-path="" + data-exclude="<?=$allowExclude?>" + data-operator="<?=$this->recommend->getFacetOperator($field)?>" + data-exclude-title="<?=$this->transEscAttr('exclude_facet')?>" + data-sort="<?=$hierarchicalFacetSortOptions[$field] ?? ''?>"> + </div> + <?php endif; ?> </div> - <?php endif; ?> - </div> - </div> - <?php endforeach; ?> -<?php endif; ?> -<?php if ($results->getResultTotal() > 0): ?> - </div> + </div> + <?php endforeach; ?> + <?php endif; ?> + <?php if ($results->getResultTotal() > 0): ?> +</div> <?php endif; ?>