3 namespace BookStack\Entities\Tools;
5 use Illuminate\Http\Request;
12 public $searches = [];
30 * Create a new instance from a search string.
32 public static function fromString(string $search): self
34 $decoded = static::decode($search);
35 $instance = new SearchOptions();
36 foreach ($decoded as $type => $value) {
37 $instance->$type = $value;
44 * Create a new instance from a request.
45 * Will look for a classic string term and use that
46 * Otherwise we'll use the details from an advanced search form.
48 public static function fromRequest(Request $request): self
50 if (!$request->has('search') && !$request->has('term')) {
51 return static::fromString('');
54 if ($request->has('term')) {
55 return static::fromString($request->get('term'));
58 $instance = new SearchOptions();
59 $inputs = $request->only(['search', 'types', 'filters', 'exact', 'tags']);
61 $parsedStandardTerms = static::parseStandardTermString($inputs['search'] ?? '');
62 $instance->searches = $parsedStandardTerms['terms'];
63 $instance->exacts = $parsedStandardTerms['exacts'];
65 array_push($instance->exacts, ...array_filter($inputs['exact'] ?? []));
67 $instance->tags = array_filter($inputs['tags'] ?? []);
69 foreach (($inputs['filters'] ?? []) as $filterKey => $filterVal) {
70 if (empty($filterVal)) {
73 $instance->filters[$filterKey] = $filterVal === 'true' ? '' : $filterVal;
76 if (isset($inputs['types']) && count($inputs['types']) < 4) {
77 $instance->filters['type'] = implode('|', $inputs['types']);
84 * Decode a search string into an array of terms.
86 protected static function decode(string $searchString): array
96 'exacts' => '/"(.*?)"/',
97 'tags' => '/\[(.*?)\]/',
98 'filters' => '/\{(.*?)\}/',
101 // Parse special terms
102 foreach ($patterns as $termType => $pattern) {
104 preg_match_all($pattern, $searchString, $matches);
105 if (count($matches) > 0) {
106 $terms[$termType] = $matches[1];
107 $searchString = preg_replace($pattern, '', $searchString);
111 // Parse standard terms
112 $parsedStandardTerms = static::parseStandardTermString($searchString);
113 array_push($terms['searches'], ...$parsedStandardTerms['terms']);
114 array_push($terms['exacts'], ...$parsedStandardTerms['exacts']);
116 // Split filter values out
118 foreach ($terms['filters'] as $filter) {
119 $explodedFilter = explode(':', $filter, 2);
120 $splitFilters[$explodedFilter[0]] = (count($explodedFilter) > 1) ? $explodedFilter[1] : '';
122 $terms['filters'] = $splitFilters;
128 * Parse a standard search term string into individual search terms and
129 * extract any exact terms searches to be made.
131 * @return array{terms: array<string>, exacts: array<string>}
133 protected static function parseStandardTermString(string $termString): array
135 $terms = explode(' ', $termString);
136 $indexDelimiters = SearchIndex::$delimiters;
142 foreach ($terms as $searchTerm) {
143 if ($searchTerm === '') {
147 $parsedList = (strpbrk($searchTerm, $indexDelimiters) === false) ? 'terms' : 'exacts';
148 $parsed[$parsedList][] = $searchTerm;
155 * Encode this instance to a search string.
157 public function toString(): string
159 $string = implode(' ', $this->searches ?? []);
161 foreach ($this->exacts as $term) {
162 $string .= ' "' . $term . '"';
165 foreach ($this->tags as $term) {
166 $string .= " [{$term}]";
169 foreach ($this->filters as $filterName => $filterVal) {
170 $string .= ' {' . $filterName . ($filterVal ? ':' . $filterVal : '') . '}';