| 
									
										
										
										
											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
										 |  |  | } |