From d09b5b6441fa2464fba24f84eff22348b879676c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Gro=C3=9Fe?= <grosse@cosmocode.de>
Date: Thu, 22 Mar 2018 14:22:41 +0100
Subject: [PATCH] feat(search): add config options to adjust default behavior

This adds two new config options:

`search_limit_to_first_ns`:
Limit the search to the current X namespaces. When a search is executed
from a page within a deeper namespace, the first X namespaces will be
added as filter.
Possible use case could be with language namespaces to ensure that the
default search is initially within the current language.

`search_default_fragment_behaviour`:
Option to specify the default fragment search behavior
---
 conf/dokuwiki.php                             |  2 +
 inc/Action/Search.php                         | 74 ++++++++++++++++++-
 inc/Ui/Search.php                             | 13 +++-
 lib/plugins/config/lang/en/lang.php           |  6 ++
 .../config/settings/config.metadata.php       |  2 +
 5 files changed, 90 insertions(+), 7 deletions(-)

diff --git a/conf/dokuwiki.php b/conf/dokuwiki.php
index c87a7cd0c..97f396ed6 100644
--- a/conf/dokuwiki.php
+++ b/conf/dokuwiki.php
@@ -156,6 +156,8 @@ $conf['broken_iua']  = 0;                //Platform with broken ignore_user_abor
 $conf['xsendfile']   = 0;                //Use X-Sendfile (1 = lighttpd, 2 = standard)
 $conf['renderer_xhtml'] = 'xhtml';       //renderer to use for main page generation
 $conf['readdircache'] = 0;               //time cache in second for the readdir operation, 0 to deactivate.
+$conf['search_limit_to_first_ns'] = 0;   //Option to limit the search to the current X namespaces
+$conf['search_default_fragment_behaviour'] = 'exact'; // Option to specify the default fragment search behavior
 
 /* Network Settings */
 $conf['dnslookups'] = 1;                 //disable to disallow IP to hostname lookups
diff --git a/inc/Action/Search.php b/inc/Action/Search.php
index 1fa19d889..f848d8752 100644
--- a/inc/Action/Search.php
+++ b/inc/Action/Search.php
@@ -30,11 +30,79 @@ class Search extends AbstractAction {
         if($s === '') throw new ActionAbort();
     }
 
+    public function preProcess()
+    {
+        $this->adjustGlobalQuery();
+    }
+
     /** @inheritdoc */
-    public function tplContent() {
-        global $QUERY;
-        $search = new \dokuwiki\Ui\Search($QUERY);
+    public function tplContent()
+    {
+        $search = new \dokuwiki\Ui\Search();
         $search->execute();
         $search->show();
     }
+
+    /**
+     * Adjust the global query accordingly to the config search_limit_to_first_ns and search_default_fragment_behaviour
+     *
+     * This will only do something if the search didn't originate from the form on the searchpage itself
+     */
+    protected function adjustGlobalQuery()
+    {
+        global $conf, $INPUT, $QUERY;
+
+        if ($INPUT->bool('searchPageForm')) {
+            return;
+        }
+
+        $Indexer = idx_get_indexer();
+        $parsedQuery = ft_queryParser($Indexer, $QUERY);
+
+        if (empty($parsedQuery['ns']) && empty($parsedQuery['notns'])) {
+            if ($conf['search_limit_to_first_ns'] > 0) {
+                $searchOriginPage = $INPUT->str('from');
+                if (getNS($searchOriginPage) !== false) {
+                    $nsParts = explode(':', getNS($searchOriginPage));
+                    $ns = implode(':', array_slice($nsParts, 0, $conf['search_limit_to_first_ns']));
+                    $QUERY .= " @$ns";
+                }
+            }
+        }
+
+        if ($conf['search_default_fragment_behaviour'] !== 'exact') {
+            if (empty(array_diff($parsedQuery['words'], $parsedQuery['and']))) {
+                if (strpos($QUERY, '*') === false) {
+                    $queryParts = explode(' ', $QUERY);
+                    $queryParts = array_map(function ($part) {
+                        if (strpos($part, '@') === 0) {
+                            return $part;
+                        }
+                        if (strpos($part, 'ns:') === 0) {
+                            return $part;
+                        }
+                        if (strpos($part, '^') === 0) {
+                            return $part;
+                        }
+                        if (strpos($part, '-ns:') === 0) {
+                            return $part;
+                        }
+
+                        global $conf;
+
+                        if ($conf['search_default_fragment_behaviour'] === 'starts_with') {
+                            return $part . '*';
+                        }
+                        if ($conf['search_default_fragment_behaviour'] === 'ends_with') {
+                            return '*' . $part;
+                        }
+
+                        return '*' . $part . '*';
+
+                    }, $queryParts);
+                    $QUERY = implode(' ', $queryParts);
+                }
+            }
+        }
+    }
 }
diff --git a/inc/Ui/Search.php b/inc/Ui/Search.php
index 2e09ee935..0fab58db4 100644
--- a/inc/Ui/Search.php
+++ b/inc/Ui/Search.php
@@ -17,11 +17,15 @@ class Search extends Ui
      *
      * @param string $query the search query
      */
-    public function __construct($query)
+    public function __construct()
     {
-        $this->query = $query;
+        global $QUERY;
+
         $Indexer = idx_get_indexer();
-        $this->parsedQuery = ft_queryParser($Indexer, $query);
+        $parsedQuery = ft_queryParser($Indexer, $QUERY);
+
+        $this->query = $QUERY;
+        $this->parsedQuery = $parsedQuery;
     }
 
     /**
@@ -68,8 +72,9 @@ class Search extends Ui
         $searchForm = (new Form())->attrs(['method' => 'get'])->addClass('search-results-form');
         $searchForm->setHiddenField('do', 'search');
         $searchForm->setHiddenField('from', $ID);
+        $searchForm->setHiddenField('searchPageForm', '1');
         $searchForm->addFieldsetOpen()->addClass('search-results-form__fieldset');
-        $searchForm->addTextInput('id')->val($query);
+        $searchForm->addTextInput('id')->val($query)->useInput(false);
         $searchForm->addButton('', $lang['btn_search'])->attr('type', 'submit');
 
         if ($this->isSearchAssistanceAvailable($this->parsedQuery)) {
diff --git a/lib/plugins/config/lang/en/lang.php b/lib/plugins/config/lang/en/lang.php
index 269d24f4c..cee84604c 100644
--- a/lib/plugins/config/lang/en/lang.php
+++ b/lib/plugins/config/lang/en/lang.php
@@ -178,6 +178,12 @@ $lang['xsendfile']   = 'Use the X-Sendfile header to let the webserver deliver s
 $lang['renderer_xhtml']   = 'Renderer to use for main (xhtml) wiki output';
 $lang['renderer__core']   = '%s (dokuwiki core)';
 $lang['renderer__plugin'] = '%s (plugin)';
+$lang['search_limit_to_first_ns'] = 'Limit the search to the current X namespaces. When a search is executed from a page within a deeper namespace, the first X namespaces will be added as filter';
+$lang['search_default_fragment_behaviour'] = 'Specify the default fragment search behavior';
+$lang['search_default_fragment_behaviour_o_exact'] = 'exact';
+$lang['search_default_fragment_behaviour_o_starts_with'] = 'starts with';
+$lang['search_default_fragment_behaviour_o_ends_with'] = 'ends with';
+$lang['search_default_fragment_behaviour_o_contains'] = 'contains';
 
 /* Network Options */
 $lang['dnslookups'] = 'DokuWiki will lookup hostnames for remote IP addresses of users editing pages. If you have a slow or non working DNS server or don\'t want this feature, disable this option';
diff --git a/lib/plugins/config/settings/config.metadata.php b/lib/plugins/config/settings/config.metadata.php
index 0527bb9c3..750245957 100644
--- a/lib/plugins/config/settings/config.metadata.php
+++ b/lib/plugins/config/settings/config.metadata.php
@@ -219,6 +219,8 @@ $meta['broken_iua']  = array('onoff');
 $meta['xsendfile']   = array('multichoice','_choices' => array(0,1,2,3),'_caution' => 'warning');
 $meta['renderer_xhtml'] = array('renderer','_format' => 'xhtml','_choices' => array('xhtml'),'_caution' => 'warning');
 $meta['readdircache'] = array('numeric');
+$meta['search_limit_to_first_ns'] = array('numeric', '_min' => 0);
+$meta['search_default_fragment_behaviour'] = array('multichoice','_choices' => array('exact', 'starts_with', 'ends_with', 'contains'),);
 
 $meta['_network']    = array('fieldset');
 $meta['dnslookups']  = array('onoff');
-- 
GitLab