diff --git a/common/framework/parsers/dbquery/variablebase.php b/common/framework/parsers/dbquery/variablebase.php index fb9d40b9d..a46ae408b 100644 --- a/common/framework/parsers/dbquery/variablebase.php +++ b/common/framework/parsers/dbquery/variablebase.php @@ -237,24 +237,9 @@ class VariableBase } break; case 'search': - $keywords = preg_split('/[\s,]+/', $value, 10, \PREG_SPLIT_NO_EMPTY); - $conditions = array(); - $placeholders = implode(', ', array_fill(0, count($keywords), '?')); - foreach ($keywords as $item) - { - if (substr($item, 0, 1) === '-') - { - $conditions[] = sprintf('%s NOT LIKE ?', $column); - $item = substr($item, 1); - } - else - { - $conditions[] = sprintf('%s LIKE ?', $column); - } - $params[] = '%' . str_replace(['\\', '_', '%'], ['\\\\', '\_', '\%'], $item) . '%'; - } - $conditions = implode(' AND ', $conditions); - $where = count($keywords) === 1 ? $conditions : "($conditions)"; + $parsed_keywords = $this->_parseSearchKeywords($column, $value); + $where = $parsed_keywords[0]; + $params = array_merge($params, $parsed_keywords[1]); break; case 'plus': $where = sprintf('%s = %s + %s', $column, $column, $is_expression ? $value : '?'); @@ -449,4 +434,90 @@ class VariableBase throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no more than ' . $this->minlength . ' characters'); } } + + /** + * Parse the search text. + * + * @param string $column + * @param string $value + * @return array + */ + protected function _parseSearchKeywords($column, $value) + { + // Initialze the return values. + $where = ''; + $params = array(); + + // parse the value (text); + $value = str_replace('"', '"', $value); + $keywords = preg_split('/(\([^\)]*?\))|(\-?\"[^\"]*?\")|[\s,]+/', trim($value), 10, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); + $conditions = array(); + $operators = array('AND', 'OR', '|'); + // loop the parsed keywords or operators + foreach ($keywords as $item) + { + // treat parenthesis + if (substr($item, 0, 1) === '(' && substr($item, -1) === ')') + { + $item = trim(substr($item, 1, -1)); + if ( $item !== "" ) + { + $parsed_keywords = $this->_parseSearchKeywords($column, substr($item, 1, -1)); + $conditions[] = $parsed_keywords[0]; + $conditions[] = 'AND'; + $params = array_merge($params, $parsed_keywords[1]); + } + continue; + } + + // process 'AND' or 'OR' operator + if (in_array($item, $operators)) + { + if ($item === '|') + { + $item = 'OR'; + } + // remove the last point (would be an operator) + array_pop($conditions); + $conditions[] = $item; + } + else + { + $item = str_replace('"', '"', $item); + + if (substr($item, 0, 1) === '-') + { + $conditions[] = sprintf('%s NOT LIKE ?', $column); + $item = substr($item, 1); + } + else + { + $conditions[] = sprintf('%s LIKE ?', $column); + } + + // trim quotation mark + if (substr($item, 0, 6) === substr($item, -6) && substr($item, -6) === '"') + { + $item = substr($item, 6, -6); + } + + // pass blank text + if (trim($item) === "") + { + array_pop($conditions); + continue; + } + + $params[] = '%' . str_replace(['\\', '_', '%'], ['\\\\', '\_', '\%'], $item) . '%'; + // if there is no operator, assume 'AND' + $conditions[] = 'AND'; + } + } + // remove the last point (would be an operator) + array_pop($conditions); + $conditions = implode(' ', $conditions); + $where = count($keywords) === 1 ? $conditions : "($conditions)"; + + return [$where, $params]; + } } diff --git a/modules/integration_search/integration_search.view.php b/modules/integration_search/integration_search.view.php index 080b79f2e..9e0c779f3 100644 --- a/modules/integration_search/integration_search.view.php +++ b/modules/integration_search/integration_search.view.php @@ -116,9 +116,9 @@ class integration_searchView extends integration_search $is_keyword = Context::get('is_keyword'); // As the variables from GET or POST will be escaped by setRequestArguments method at Context class, the double_escape variable should be "FALSE", and also the escape function might be useful when this method was called from the other way (for not escaped keyword). $is_keyword = escape(trim(utf8_normalize_spaces($is_keyword)), false); - if (mb_strlen($is_keyword, 'UTF-8') > 40) + if (mb_strlen($is_keyword, 'UTF-8') > 250) { - $is_keyword = mb_substr($is_keyword, 0, 40); + $is_keyword = mb_substr($is_keyword, 0, 250); } // Set page variables diff --git a/tests/_data/dbquery/selectTest3.xml b/tests/_data/dbquery/selectTest3.xml new file mode 100644 index 000000000..1f30ace49 --- /dev/null +++ b/tests/_data/dbquery/selectTest3.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/unit/framework/parsers/DBQueryParserTest.php b/tests/unit/framework/parsers/DBQueryParserTest.php index 736765375..560fcb140 100644 --- a/tests/unit/framework/parsers/DBQueryParserTest.php +++ b/tests/unit/framework/parsers/DBQueryParserTest.php @@ -70,6 +70,20 @@ class DBQueryParserTest extends \Codeception\TestCase\Test $this->assertEquals([20, '20201021'], $params); } + public function testSelectWithSearch() + { + $query = Rhymix\Framework\Parsers\DBQueryParser::loadXML(\RX_BASEDIR . 'tests/_data/dbquery/selectTest3.xml'); + $args = array('division' => 1234, 'last_division' => 4567, 's_title' => '"I love you" -"I hate you"', 's_content' => '"I love you" -"I hate you"', 'page' => 3); + $sql = $query->getQueryString('rx_', $args); + $params = $query->getQueryParams(); + + $this->assertEquals('SELECT * FROM `rx_documents` AS `documents` ' . + 'WHERE (`list_order` >= ? AND `list_order` < ?) AND ' . + '((`title` LIKE ? AND `title` NOT LIKE ?) OR (`content` LIKE ? AND `content` NOT LIKE ?)) ' . + 'ORDER BY `list_order` ASC LIMIT 40, 20', $sql); + $this->assertEquals(['1234', '4567', '%I love you%', '%I hate you%', '%I love you%', '%I hate you%'], $params); + } + public function testJoin1() { $query = Rhymix\Framework\Parsers\DBQueryParser::loadXML(\RX_BASEDIR . 'tests/_data/dbquery/selectJoinTest1.xml');