From 61fd8e1221887e116d4f916e9be1ef7bb94805b4 Mon Sep 17 00:00:00 2001
From: Benjamin Mack <benni@typo3.org>
Date: Sun, 1 Feb 2015 13:09:31 +0100
Subject: [PATCH] [BUGFIX] Indexed search broken after moving
 "SearchResultContentObject"

Indexed search still uses the old cobject in both
plugins.

The problem arieses in just to call one method from
SearchResultContentObject->register_and_explode_search_string()

The necessary functions are moved to the common
IndexedSearchUtility.

Resolves: #64716
Releases: master
Change-Id: I386330f1f66342ee838e3616cac7f0924bd31522
Reviewed-on: http://review.typo3.org/36566
Reviewed-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
Tested-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../Classes/Controller/SearchController.php   |   9 +-
 .../Controller/SearchFormController.php       |  11 +-
 .../Classes/Utility/IndexedSearchUtility.php  | 115 +++++++++++++++++-
 3 files changed, 120 insertions(+), 15 deletions(-)

diff --git a/typo3/sysext/indexed_search/Classes/Controller/SearchController.php b/typo3/sysext/indexed_search/Classes/Controller/SearchController.php
index c77586ed4ad9..91c3cc5d2baf 100644
--- a/typo3/sysext/indexed_search/Classes/Controller/SearchController.php
+++ b/typo3/sysext/indexed_search/Classes/Controller/SearchController.php
@@ -729,12 +729,9 @@ class SearchController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControlle
 					array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $GLOBALS['TSFE']->csConvObj->utf8_encode(\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('localizedOperandOr', 'indexed_search'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'OR'),
 					array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $GLOBALS['TSFE']->csConvObj->utf8_encode(\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('localizedOperandNot', 'indexed_search'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'AND NOT')
 				);
-				$search = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\SearchResultContentObject::class);
-				$search->default_operator = $defaultOperator == 1 ? 'OR' : 'AND';
-				$search->operator_translate_table = $operatorTranslateTable;
-				$search->register_and_explode_search_string($searchWords);
-				if (is_array($search->sword_array)) {
-					$sWordArray = $this->procSearchWordsByLexer($search->sword_array);
+				$swordArray = \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::getExplodedSearchString($searchWords, $defaultOperator == 1 ? 'OR' : 'AND', $operatorTranslateTable);
+				if (is_array($swordArray)) {
+					$sWordArray = $this->procSearchWordsByLexer($swordArray);
 				}
 			}
 		}
diff --git a/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php b/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php
index 45d9f2e7b119..dbf5ccaf3371 100644
--- a/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php
+++ b/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php
@@ -117,7 +117,7 @@ class SearchFormController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin {
 		$this->initialize();
 		// Do search:
 		// If there were any search words entered...
-		if (is_array($this->sWArr)) {
+		if (is_array($this->sWArr) && !empty($this->sWArr)) {
 			$content = $this->doSearch($this->sWArr);
 		}
 		// Finally compile all the content, form, messages and results:
@@ -354,12 +354,9 @@ class SearchFormController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin {
 				// type = Sentence
 				$sWordArray = array(array('sword' => trim($inSW), 'oper' => 'AND'));
 			} else {
-				$search = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\SearchResultContentObject::class);
-				$search->default_operator = $defOp == 1 ? 'OR' : 'AND';
-				$search->operator_translate_table = $this->operator_translate_table;
-				$search->register_and_explode_search_string($inSW);
-				if (is_array($search->sword_array)) {
-					$sWordArray = $this->procSearchWordsByLexer($search->sword_array);
+				$searchWords = \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::getExplodedSearchString($inSW, $defOp == 1 ? 'OR' : 'AND', $this->operator_translate_table);
+				if (is_array($searchWords)) {
+					$sWordArray = $this->procSearchWordsByLexer($searchWords);
 				}
 			}
 		}
diff --git a/typo3/sysext/indexed_search/Classes/Utility/IndexedSearchUtility.php b/typo3/sysext/indexed_search/Classes/Utility/IndexedSearchUtility.php
index 35b1bedc6bb6..7bd29fdf382a 100644
--- a/typo3/sysext/indexed_search/Classes/Utility/IndexedSearchUtility.php
+++ b/typo3/sysext/indexed_search/Classes/Utility/IndexedSearchUtility.php
@@ -18,8 +18,6 @@ namespace TYPO3\CMS\IndexedSearch\Utility;
  * Class with common methods used across various classes in the indexed search.
  * Impementation is provided by various people from the TYPO3 community.
  *
- * This class is final because it contains only static methods.
- *
  * @author Dmitry Dulepov <dmitry@typo3.com>
  */
 class IndexedSearchUtility {
@@ -48,4 +46,117 @@ class IndexedSearchUtility {
 		return hexdec(substr(md5($stringToHash), 0, 7));
 	}
 
+
+	/**
+	 * Takes a search-string (WITHOUT SLASHES or else it'll be a little sppooky , NOW REMEMBER to unslash!!)
+	 * Sets up search words with operators.
+	 *
+	 * @param string $sword The input search-word string.
+	 * @param string $defaultOperator
+	 * @param array $operatorTranslateTable
+	 * @return array
+	 */
+	public static function getExplodedSearchString($sword, $defaultOperator, $operatorTranslateTable) {
+		$swordArray = array();
+		$sword = trim($sword);
+		if ($sword) {
+			$components = self::split($sword);
+			if (is_array($components)) {
+				$i = 0;
+				$lastoper = '';
+				foreach ($components as $key => $val) {
+					$operator = self::getOperator($val, $operatorTranslateTable);
+					if ($operator) {
+						$lastoper = $operator;
+					} elseif (strlen($val) > 1) {
+						// A searchword MUST be at least two characters long!
+						$swordArray[$i]['sword'] = $val;
+						$swordArray[$i]['oper'] = $lastoper ?: $defaultOperator;
+						$lastoper = '';
+						$i++;
+					}
+				}
+			}
+		}
+		return $swordArray;
+	}
+
+	/**
+	 * Used to split a search-word line up into elements to search for. This function will detect boolean words like AND and OR, + and -, and even find sentences encapsulated in ""
+	 * This function could be re-written to be more clean and effective - yet it's not that important.
+	 *
+	 * @param string $origSword The raw sword string from outside
+	 * @param string $specchars Special chars which are used as operators (+- is default)
+	 * @param string $delchars Special chars which are deleted if the append the searchword (+-., is default)
+	 * @return mixed Returns an ARRAY if there were search words, otherwise the return value may be unset.
+	 */
+	protected static function split($origSword, $specchars = '+-', $delchars = '+.,-') {
+		$value = NULL;
+		$sword = $origSword;
+		$specs = '[' . preg_quote($specchars, '/') . ']';
+		// As long as $sword is TRUE (that means $sword MUST be reduced little by little until its empty inside the loop!)
+		while ($sword) {
+			// There was a double-quote and we will then look for the ending quote.
+			if (preg_match('/^"/', $sword)) {
+				// Removes first double-quote
+				$sword = preg_replace('/^"/', '', $sword);
+				// Removes everything till next double-quote
+				preg_match('/^[^"]*/', $sword, $reg);
+				// reg[0] is the value, should not be trimmed
+				$value[] = $reg[0];
+				$sword = preg_replace('/^' . preg_quote($reg[0], '/') . '/', '', $sword);
+				// Removes last double-quote
+				$sword = trim(preg_replace('/^"/', '', $sword));
+			} elseif (preg_match('/^' . $specs . '/', $sword, $reg)) {
+				$value[] = $reg[0];
+				// Removes = sign
+				$sword = trim(preg_replace('/^' . $specs . '/', '', $sword));
+			} elseif (preg_match('/[\\+\\-]/', $sword)) {
+				// Check if $sword contains + or -
+				// + and - shall only be interpreted as $specchars when there's whitespace before it
+				// otherwise it's included in the searchword (e.g. "know-how")
+				// explode $sword to single words
+				$a_sword = explode(' ', $sword);
+				// get first word
+				$word = array_shift($a_sword);
+				// Delete $delchars at end of string
+				$word = rtrim($word, $delchars);
+				// add searchword to values
+				$value[] = $word;
+				// re-build $sword
+				$sword = implode(' ', $a_sword);
+			} else {
+				// There are no double-quotes around the value. Looking for next (space) or special char.
+				preg_match('/^[^ ' . preg_quote($specchars, '/') . ']*/', $sword, $reg);
+				// Delete $delchars at end of string
+				$word = rtrim(trim($reg[0]), $delchars);
+				$value[] = $word;
+				$sword = trim(preg_replace('/^' . preg_quote($reg[0], '/') . '/', '', $sword));
+			}
+		}
+		return $value;
+	}
+
+	/**
+	 * This returns an SQL search-operator (eg. AND, OR, NOT) translated from the current localized set of operators (eg. in danish OG, ELLER, IKKE).
+	 *
+	 * @param string $operator The possible operator to find in the internal operator array.
+	 * @param array $operatorTranslateTable an array of possible operators
+	 * @return string If found, the SQL operator for the localized input operator.
+	 * @access private
+	 */
+	protected static function getOperator($operator, $operatorTranslateTable) {
+		$operator = trim($operator);
+		// case-conversion is charset insensitive, but it doesn't spoil
+		// anything if input string AND operator table is already converted
+		$operator = strtolower($operator);
+		foreach ($operatorTranslateTable as $key => $val) {
+			$item = $operatorTranslateTable[$key][0];
+			// See note above.
+			$item = strtolower($item);
+			if ($operator == $item) {
+				return $operatorTranslateTable[$key][1];
+			}
+		}
+	}
 }
-- 
GitLab