2021-06-26 23:23:15 +08:00
|
|
|
<?php
|
|
|
|
|
2022-08-16 18:27:22 +08:00
|
|
|
namespace BookStack\Search;
|
2020-06-27 20:29:00 +08:00
|
|
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
|
|
class SearchOptions
|
|
|
|
{
|
2022-08-16 18:27:22 +08:00
|
|
|
public array $searches = [];
|
|
|
|
public array $exacts = [];
|
|
|
|
public array $tags = [];
|
|
|
|
public array $filters = [];
|
2020-06-27 20:29:00 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new instance from a search string.
|
|
|
|
*/
|
2021-10-27 05:04:18 +08:00
|
|
|
public static function fromString(string $search): self
|
2020-06-27 20:29:00 +08:00
|
|
|
{
|
|
|
|
$decoded = static::decode($search);
|
2021-11-06 00:27:59 +08:00
|
|
|
$instance = new SearchOptions();
|
2020-06-27 20:29:00 +08:00
|
|
|
foreach ($decoded as $type => $value) {
|
|
|
|
$instance->$type = $value;
|
|
|
|
}
|
2021-06-26 23:23:15 +08:00
|
|
|
|
2020-06-27 20:29:00 +08:00
|
|
|
return $instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new instance from a request.
|
|
|
|
* Will look for a classic string term and use that
|
|
|
|
* Otherwise we'll use the details from an advanced search form.
|
|
|
|
*/
|
2021-10-27 05:04:18 +08:00
|
|
|
public static function fromRequest(Request $request): self
|
2020-06-27 20:29:00 +08:00
|
|
|
{
|
2020-06-27 20:37:18 +08:00
|
|
|
if (!$request->has('search') && !$request->has('term')) {
|
|
|
|
return static::fromString('');
|
|
|
|
}
|
|
|
|
|
2020-06-27 20:29:00 +08:00
|
|
|
if ($request->has('term')) {
|
|
|
|
return static::fromString($request->get('term'));
|
|
|
|
}
|
|
|
|
|
2021-11-06 00:27:59 +08:00
|
|
|
$instance = new SearchOptions();
|
2020-06-27 20:29:00 +08:00
|
|
|
$inputs = $request->only(['search', 'types', 'filters', 'exact', 'tags']);
|
2021-11-13 02:03:44 +08:00
|
|
|
|
|
|
|
$parsedStandardTerms = static::parseStandardTermString($inputs['search'] ?? '');
|
|
|
|
$instance->searches = $parsedStandardTerms['terms'];
|
|
|
|
$instance->exacts = $parsedStandardTerms['exacts'];
|
|
|
|
|
|
|
|
array_push($instance->exacts, ...array_filter($inputs['exact'] ?? []));
|
|
|
|
|
2020-06-27 20:29:00 +08:00
|
|
|
$instance->tags = array_filter($inputs['tags'] ?? []);
|
2021-11-13 02:03:44 +08:00
|
|
|
|
2020-06-27 20:29:00 +08:00
|
|
|
foreach (($inputs['filters'] ?? []) as $filterKey => $filterVal) {
|
|
|
|
if (empty($filterVal)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$instance->filters[$filterKey] = $filterVal === 'true' ? '' : $filterVal;
|
|
|
|
}
|
2021-11-13 02:03:44 +08:00
|
|
|
|
2020-06-27 20:29:00 +08:00
|
|
|
if (isset($inputs['types']) && count($inputs['types']) < 4) {
|
|
|
|
$instance->filters['type'] = implode('|', $inputs['types']);
|
|
|
|
}
|
2021-06-26 23:23:15 +08:00
|
|
|
|
2020-06-27 20:29:00 +08:00
|
|
|
return $instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decode a search string into an array of terms.
|
|
|
|
*/
|
|
|
|
protected static function decode(string $searchString): array
|
|
|
|
{
|
|
|
|
$terms = [
|
|
|
|
'searches' => [],
|
2021-06-26 23:23:15 +08:00
|
|
|
'exacts' => [],
|
|
|
|
'tags' => [],
|
|
|
|
'filters' => [],
|
2020-06-27 20:29:00 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
$patterns = [
|
2021-06-26 23:23:15 +08:00
|
|
|
'exacts' => '/"(.*?)"/',
|
|
|
|
'tags' => '/\[(.*?)\]/',
|
|
|
|
'filters' => '/\{(.*?)\}/',
|
2020-06-27 20:29:00 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
// Parse special terms
|
|
|
|
foreach ($patterns as $termType => $pattern) {
|
|
|
|
$matches = [];
|
|
|
|
preg_match_all($pattern, $searchString, $matches);
|
|
|
|
if (count($matches) > 0) {
|
|
|
|
$terms[$termType] = $matches[1];
|
|
|
|
$searchString = preg_replace($pattern, '', $searchString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse standard terms
|
2021-11-13 02:03:44 +08:00
|
|
|
$parsedStandardTerms = static::parseStandardTermString($searchString);
|
|
|
|
array_push($terms['searches'], ...$parsedStandardTerms['terms']);
|
|
|
|
array_push($terms['exacts'], ...$parsedStandardTerms['exacts']);
|
2020-06-27 20:29:00 +08:00
|
|
|
|
|
|
|
// Split filter values out
|
|
|
|
$splitFilters = [];
|
|
|
|
foreach ($terms['filters'] as $filter) {
|
|
|
|
$explodedFilter = explode(':', $filter, 2);
|
|
|
|
$splitFilters[$explodedFilter[0]] = (count($explodedFilter) > 1) ? $explodedFilter[1] : '';
|
|
|
|
}
|
|
|
|
$terms['filters'] = $splitFilters;
|
|
|
|
|
|
|
|
return $terms;
|
|
|
|
}
|
|
|
|
|
2021-11-13 02:03:44 +08:00
|
|
|
/**
|
|
|
|
* Parse a standard search term string into individual search terms and
|
|
|
|
* extract any exact terms searches to be made.
|
|
|
|
*
|
|
|
|
* @return array{terms: array<string>, exacts: array<string>}
|
|
|
|
*/
|
|
|
|
protected static function parseStandardTermString(string $termString): array
|
|
|
|
{
|
|
|
|
$terms = explode(' ', $termString);
|
|
|
|
$indexDelimiters = SearchIndex::$delimiters;
|
|
|
|
$parsed = [
|
2021-11-13 21:28:17 +08:00
|
|
|
'terms' => [],
|
2021-11-13 02:03:44 +08:00
|
|
|
'exacts' => [],
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($terms as $searchTerm) {
|
|
|
|
if ($searchTerm === '') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parsedList = (strpbrk($searchTerm, $indexDelimiters) === false) ? 'terms' : 'exacts';
|
|
|
|
$parsed[$parsedList][] = $searchTerm;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $parsed;
|
|
|
|
}
|
|
|
|
|
2020-06-27 20:29:00 +08:00
|
|
|
/**
|
|
|
|
* Encode this instance to a search string.
|
|
|
|
*/
|
|
|
|
public function toString(): string
|
|
|
|
{
|
|
|
|
$string = implode(' ', $this->searches ?? []);
|
|
|
|
|
|
|
|
foreach ($this->exacts as $term) {
|
|
|
|
$string .= ' "' . $term . '"';
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->tags as $term) {
|
|
|
|
$string .= " [{$term}]";
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->filters as $filterName => $filterVal) {
|
|
|
|
$string .= ' {' . $filterName . ($filterVal ? ':' . $filterVal : '') . '}';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $string;
|
|
|
|
}
|
2021-03-08 06:24:05 +08:00
|
|
|
}
|