3 namespace BookStack\Search;
5 use Illuminate\Http\Request;
9 public array $searches = [];
10 public array $exacts = [];
11 public array $tags = [];
12 public array $filters = [];
15 * Create a new instance from a search string.
17 public static function fromString(string $search): self
19 $decoded = static::decode($search);
20 $instance = new SearchOptions();
21 foreach ($decoded as $type => $value) {
22 $instance->$type = $value;
29 * Create a new instance from a request.
30 * Will look for a classic string term and use that
31 * Otherwise we'll use the details from an advanced search form.
33 public static function fromRequest(Request $request): self
35 if (!$request->has('search') && !$request->has('term')) {
36 return static::fromString('');
39 if ($request->has('term')) {
40 return static::fromString($request->get('term'));
43 $instance = new SearchOptions();
44 $inputs = $request->only(['search', 'types', 'filters', 'exact', 'tags']);
46 $parsedStandardTerms = static::parseStandardTermString($inputs['search'] ?? '');
47 $instance->searches = $parsedStandardTerms['terms'];
48 $instance->exacts = $parsedStandardTerms['exacts'];
50 array_push($instance->exacts, ...array_filter($inputs['exact'] ?? []));
52 $instance->tags = array_filter($inputs['tags'] ?? []);
54 foreach (($inputs['filters'] ?? []) as $filterKey => $filterVal) {
55 if (empty($filterVal)) {
58 $instance->filters[$filterKey] = $filterVal === 'true' ? '' : $filterVal;
61 if (isset($inputs['types']) && count($inputs['types']) < 4) {
62 $instance->filters['type'] = implode('|', $inputs['types']);
69 * Decode a search string into an array of terms.
71 protected static function decode(string $searchString): array
81 'exacts' => '/"(.*?)"/',
82 'tags' => '/\[(.*?)\]/',
83 'filters' => '/\{(.*?)\}/',
86 // Parse special terms
87 foreach ($patterns as $termType => $pattern) {
89 preg_match_all($pattern, $searchString, $matches);
90 if (count($matches) > 0) {
91 $terms[$termType] = $matches[1];
92 $searchString = preg_replace($pattern, '', $searchString);
96 // Parse standard terms
97 $parsedStandardTerms = static::parseStandardTermString($searchString);
98 array_push($terms['searches'], ...$parsedStandardTerms['terms']);
99 array_push($terms['exacts'], ...$parsedStandardTerms['exacts']);
101 // Split filter values out
103 foreach ($terms['filters'] as $filter) {
104 $explodedFilter = explode(':', $filter, 2);
105 $splitFilters[$explodedFilter[0]] = (count($explodedFilter) > 1) ? $explodedFilter[1] : '';
107 $terms['filters'] = $splitFilters;
113 * Parse a standard search term string into individual search terms and
114 * extract any exact terms searches to be made.
116 * @return array{terms: array<string>, exacts: array<string>}
118 protected static function parseStandardTermString(string $termString): array
120 $terms = explode(' ', $termString);
121 $indexDelimiters = SearchIndex::$delimiters;
127 foreach ($terms as $searchTerm) {
128 if ($searchTerm === '') {
132 $parsedList = (strpbrk($searchTerm, $indexDelimiters) === false) ? 'terms' : 'exacts';
133 $parsed[$parsedList][] = $searchTerm;
140 * Encode this instance to a search string.
142 public function toString(): string
144 $string = implode(' ', $this->searches ?? []);
146 foreach ($this->exacts as $term) {
147 $string .= ' "' . $term . '"';
150 foreach ($this->tags as $term) {
151 $string .= " [{$term}]";
154 foreach ($this->filters as $filterName => $filterVal) {
155 $string .= ' {' . $filterName . ($filterVal ? ':' . $filterVal : '') . '}';