From 3779a7f4ced0ca4732f3816a23c39417dd331942 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Fri, 30 Oct 2015 10:59:10 -0400
Subject: [PATCH] Added filter mechanism to combined search. - Now you can
 provide multiple views into a single search backend.

---
 config/vufind/combined.ini                    | 10 +++++++--
 .../VuFind/Controller/CombinedController.php  | 22 ++++++++++++++-----
 .../templates/combined/results-ajax.phtml     |  6 ++---
 .../combined/stack-distributed.phtml          |  2 +-
 .../templates/combined/stack-left.phtml       |  2 +-
 .../templates/combined/stack-right.phtml      |  2 +-
 .../templates/combined/results-ajax.phtml     |  2 +-
 .../templates/combined/results.phtml          |  2 +-
 8 files changed, 33 insertions(+), 15 deletions(-)

diff --git a/config/vufind/combined.ini b/config/vufind/combined.ini
index 1e1f1245022..48625a997ba 100644
--- a/config/vufind/combined.ini
+++ b/config/vufind/combined.ini
@@ -1,6 +1,10 @@
 ; This file controls the "combined search" module. Each section is named for a
-; search backend (e.g. "Solr", "Summon", "WorldCat", etc.) and contains the
-; following settings:
+; search backend (e.g. "Solr", "Summon", "WorldCat", etc.). If you want to create
+; multiple columns using the same backend but different settings, you may add a
+; colon and a suffix (e.g. "Solr:filter1", "Solr:filter2") to differentiate the
+; sections.
+
+; Each section contains some or all of the following settings:
 ;
 ; label = The header on the column
 ; sublabel = Text to display below the header (optional)
@@ -22,6 +26,8 @@
 ;                           specified recommendations will be loaded into the top
 ;                           area of the column. If set to false, recommendations
 ;                           will be suppressed (default).
+; filter = One or more filters to apply to search results displayed in the column.
+;          Use multiple "filter[] = ..." lines if multiple filters are needed.
 ;
 ; All display text is subject to translation and may be added to the language
 ; .ini files.
diff --git a/module/VuFind/src/VuFind/Controller/CombinedController.php b/module/VuFind/src/VuFind/Controller/CombinedController.php
index 98ff2988a3c..7ff244878f7 100644
--- a/module/VuFind/src/VuFind/Controller/CombinedController.php
+++ b/module/VuFind/src/VuFind/Controller/CombinedController.php
@@ -70,13 +70,14 @@ class CombinedController extends AbstractSearch
         $this->getSearchMemory()->disable();
 
         // Validate configuration:
-        $searchClassId = $this->params()->fromQuery('id');
+        $sectionId = $this->params()->fromQuery('id');
         $config = $this->getServiceLocator()->get('VuFind\Config')->get('combined')
             ->toArray();
         $tabConfig = $this->getTabConfig($config);
-        if (!isset($tabConfig[$searchClassId])) {
+        if (!isset($tabConfig[$sectionId])) {
             throw new \Exception('Illegal ID');
         }
+        list($searchClassId) = explode(':', $sectionId);
 
         // Retrieve results:
         $options = $this->getServiceLocator()
@@ -84,7 +85,7 @@ class CombinedController extends AbstractSearch
         $currentOptions = $options->get($searchClassId);
         list($controller, $action)
             = explode('-', $currentOptions->getSearchAction());
-        $settings = $tabConfig[$searchClassId];
+        $settings = $tabConfig[$sectionId];
 
         $this->adjustQueryForSettings($settings);
         $settings['view'] = $this->forwardTo($controller, $action);
@@ -151,8 +152,9 @@ class CombinedController extends AbstractSearch
         $supportsCart = false;
         $supportsCartOptions = [];
         foreach ($this->getTabConfig($config) as $current => $settings) {
+            list($searchClassId) = explode(':', $current);
             $this->adjustQueryForSettings($settings);
-            $currentOptions = $options->get($current);
+            $currentOptions = $options->get($searchClassId);
             $supportsCartOptions[] = $currentOptions->supportsCart();
             if ($currentOptions->supportsCart()) {
                 $supportsCart = true;
@@ -160,13 +162,19 @@ class CombinedController extends AbstractSearch
             list($controller, $action)
                 = explode('-', $currentOptions->getSearchAction());
             $combinedResults[$current] = $settings;
+
+            // Calculate a unique DOM id for this section of the search results;
+            // $searchClassId may contain colons, which must be converted.
+            $combinedResults[$current]['domId']
+                = 'combined_' . str_replace(':', '____', $searchClassId);
+
             $combinedResults[$current]['view']
                 = (!isset($settings['ajax']) || !$settings['ajax'])
                 ? $this->forwardTo($controller, $action)
                 : $this->createViewModel(['results' => $results]);
 
             // Special case: include appropriate "powered by" message:
-            if (strtolower($current) == 'summon') {
+            if (strtolower($searchClassId) == 'summon') {
                 $this->layout()->poweredBy = 'Powered by Summonâ„¢ from Serials '
                     . 'Solutions, a division of ProQuest.';
             }
@@ -253,6 +261,10 @@ class CombinedController extends AbstractSearch
         $query = $this->getRequest()->getQuery();
         $query->limit = isset($settings['limit']) ? $settings['limit'] : null;
 
+        // Apply filters, if any:
+        $query->filter = isset($settings['filter'])
+            ? (array)$settings['filter'] : null;
+
         // Reset override to avoid bleed-over from one section to the next!
         $query->recommendOverride = false;
 
diff --git a/themes/bootstrap3/templates/combined/results-ajax.phtml b/themes/bootstrap3/templates/combined/results-ajax.phtml
index d2c9eb3217d..6089b75de42 100644
--- a/themes/bootstrap3/templates/combined/results-ajax.phtml
+++ b/themes/bootstrap3/templates/combined/results-ajax.phtml
@@ -9,14 +9,14 @@
 
     // Set up Javascript for use below:
     $searchClassIdEncoded = urlencode($searchClassId);
-    $searchClassIdHtmlEscaped = $this->escapeHtml($searchClassId);
+    $targetIdHtmlEscaped = $this->escapeHtml('#' . $currentSearch['domId']);
     $lookforEncoded = urlencode($lookfor);
     $loadJs = <<<JS
 $(document).ready(function(){
     var url = path + '/Combined/Result?id=$searchClassIdEncoded&lookfor=$lookforEncoded';
-    $('#combined_$searchClassIdHtmlEscaped').load(url, '', function(responseText) {
+    $('$targetIdHtmlEscaped').load(url, '', function(responseText) {
         if (responseText.length == 0) {
-            $('#combined_$searchClassIdHtmlEscaped').hide();
+            $('$targetIdHtmlEscaped').hide();
         }
         $('a.openUrlEmbed').click(function() {
             embedOpenUrlLinks($(this));
diff --git a/themes/bootstrap3/templates/combined/stack-distributed.phtml b/themes/bootstrap3/templates/combined/stack-distributed.phtml
index d077ad4d748..bfd7281646e 100644
--- a/themes/bootstrap3/templates/combined/stack-distributed.phtml
+++ b/themes/bootstrap3/templates/combined/stack-distributed.phtml
@@ -21,7 +21,7 @@
             // Enable bulk options if appropriate:
             $viewParams['showBulkOptions'] = $this->supportsCartOptions[$columnIndex] && $this->showBulkOptions;
           ?>
-          <div id="combined_<?=$this->escapeHtmlAttr($searchClassId)?>">
+          <div id="<?=$this->escapeHtmlAttr($currentSearch['domId'])?>">
             <? $templateSuffix = (isset($currentSearch['ajax']) && $currentSearch['ajax']) ? 'ajax' : 'list'; ?>
             <?=$this->render('combined/results-' . $templateSuffix . '.phtml', $viewParams)?>
           </div>
diff --git a/themes/bootstrap3/templates/combined/stack-left.phtml b/themes/bootstrap3/templates/combined/stack-left.phtml
index c6228f0fe37..b75aa6bc471 100644
--- a/themes/bootstrap3/templates/combined/stack-left.phtml
+++ b/themes/bootstrap3/templates/combined/stack-left.phtml
@@ -14,7 +14,7 @@
       // Enable bulk options if appropriate:
       $viewParams['showBulkOptions'] = $this->supportsCartOptions[$columnIndex] && $this->showBulkOptions;
     ?>
-    <div id="combined_<?=$this->escapeHtmlAttr($searchClassId)?>">
+    <div id="<?=$this->escapeHtmlAttr($currentSearch['domId'])?>">
       <? $templateSuffix = (isset($currentSearch['ajax']) && $currentSearch['ajax']) ? 'ajax' : 'list'; ?>
       <?=$this->render('combined/results-' . $templateSuffix . '.phtml', $viewParams)?>
     </div>
diff --git a/themes/bootstrap3/templates/combined/stack-right.phtml b/themes/bootstrap3/templates/combined/stack-right.phtml
index 34340ab9bb3..bd6126dc369 100644
--- a/themes/bootstrap3/templates/combined/stack-right.phtml
+++ b/themes/bootstrap3/templates/combined/stack-right.phtml
@@ -14,7 +14,7 @@
       // Enable bulk options if appropriate:
       $viewParams['showBulkOptions'] = $this->supportsCartOptions[$columnIndex] && $this->showBulkOptions;
     ?>
-    <div id="combined_<?=$this->escapeHtmlAttr($searchClassId)?>">
+    <div id="<?=$this->escapeHtmlAttr($currentSearch['domId'])?>">
       <? $templateSuffix = (isset($currentSearch['ajax']) && $currentSearch['ajax']) ? 'ajax' : 'list'; ?>
       <?=$this->render('combined/results-' . $templateSuffix . '.phtml', $viewParams)?>
     </div>
diff --git a/themes/jquerymobile/templates/combined/results-ajax.phtml b/themes/jquerymobile/templates/combined/results-ajax.phtml
index eaf0b46e084..840e63c31c6 100644
--- a/themes/jquerymobile/templates/combined/results-ajax.phtml
+++ b/themes/jquerymobile/templates/combined/results-ajax.phtml
@@ -7,7 +7,7 @@
     // Set up Javascript for use below:
     $loadJs = 'var url = path + "/Combined/Result?id=' . urlencode($searchClassId)
         . '&lookfor=' . urlencode($lookfor) . '";'
-        . "\$('#combined_" . $this->escapeHtml($searchClassId) . "').load(url, '', function(responseText) {"
+        . "\$('#" . $this->escapeHtml($currentSearch['domId']) . "').load(url, '', function(responseText) {"
         . "if (responseText.length == 0) $('#combined_" . $this->escapeHtml($searchClassId) . "').hide();"
         . "$('.combinedButton').button(); });";
 ?>
diff --git a/themes/jquerymobile/templates/combined/results.phtml b/themes/jquerymobile/templates/combined/results.phtml
index 3bd8b746e26..2620b266742 100644
--- a/themes/jquerymobile/templates/combined/results.phtml
+++ b/themes/jquerymobile/templates/combined/results.phtml
@@ -13,7 +13,7 @@
     <?=$this->flashmessages()?>
     <? foreach ($this->combinedResults as $searchClassId => $currentSearch): ?>
       <? if ((!isset($currentSearch['ajax']) || !$currentSearch['ajax']) && isset($currentSearch['hide_if_empty']) && $currentSearch['hide_if_empty'] && $currentSearch['view']->results->getResultTotal() == 0) { continue; } ?>
-      <div class="combinedResult" id="combined_<?=$this->escapeHtml($searchClassId)?>">
+      <div class="combinedResult" id="<?=$this->escapeHtmlAttr($currentSearch['domId'])?>">
         <? if (isset($currentSearch['ajax']) && $currentSearch['ajax']): ?>
           <?=$this->render('combined/results-ajax.phtml', array('searchClassId' => $searchClassId, 'currentSearch' => $currentSearch))?>
         <? else: ?>
-- 
GitLab