| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-18 00:56:55 +08:00
										 |  |  | namespace BookStack\Search; | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-29 22:05:57 +08:00
										 |  |  | use BookStack\Api\ApiEntityListFormatter; | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | use BookStack\Entities\Models\Entity; | 
					
						
							| 
									
										
										
										
											2023-05-19 03:53:39 +08:00
										 |  |  | use BookStack\Http\ApiController; | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | use Illuminate\Http\Request; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SearchApiController extends ApiController | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2022-09-29 22:05:57 +08:00
										 |  |  |     protected SearchRunner $searchRunner; | 
					
						
							|  |  |  |     protected SearchResultsFormatter $resultsFormatter; | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     protected $rules = [ | 
					
						
							|  |  |  |         'all' => [ | 
					
						
							| 
									
										
										
										
											2024-10-21 05:12:49 +08:00
										 |  |  |             'query' => ['required'], | 
					
						
							|  |  |  |             'page' => ['integer', 'min:1'], | 
					
						
							|  |  |  |             'count' => ['integer', 'min:1', 'max:100'], | 
					
						
							|  |  |  |             'include' => ['string', 'regex:/^[a-zA-Z,]*$/'], | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  |         ], | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-21 05:12:49 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Valid include parameters and their corresponding formatter methods. | 
					
						
							|  |  |  |      * These parameters allow for additional related data, like titles or tags, | 
					
						
							|  |  |  |      * to be included in the search results when requested via the API. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected const VALID_INCLUDES = [ | 
					
						
							|  |  |  |         'titles' => 'withRelatedTitles', | 
					
						
							|  |  |  |         'tags' => 'withTags', | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-07 04:42:04 +08:00
										 |  |  |     public function __construct(SearchRunner $searchRunner, SearchResultsFormatter $resultsFormatter) | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $this->searchRunner = $searchRunner; | 
					
						
							| 
									
										
										
										
											2021-12-07 04:42:04 +08:00
										 |  |  |         $this->resultsFormatter = $resultsFormatter; | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Run a search query against all main content types (shelves, books, chapters & pages) | 
					
						
							|  |  |  |      * in the system. Takes the same input as the main search bar within the BookStack | 
					
						
							|  |  |  |      * interface as a 'query' parameter. See https://www.bookstackapp.com/docs/user/searching/ | 
					
						
							|  |  |  |      * for a full list of search term options. Results contain a 'type' property to distinguish | 
					
						
							|  |  |  |      * between: bookshelf, book, chapter & page. | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2024-10-21 05:12:49 +08:00
										 |  |  |      * This method now supports the 'include' parameter, which allows API clients to specify related | 
					
						
							|  |  |  |      * fields (such as titles or tags) that should be included in the search results. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * The 'include' parameter is a comma-separated string. For example, adding `include=titles,tags` | 
					
						
							|  |  |  |      * will include both titles and tags in the API response. If the parameter is not provided, only | 
					
						
							|  |  |  |      * basic entity data will be returned. | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  |      * The paging parameters and response format emulates a standard listing endpoint | 
					
						
							|  |  |  |      * but standard sorting and filtering cannot be done on this endpoint. If a count value | 
					
						
							|  |  |  |      * is provided this will only be taken as a suggestion. The results in the response | 
					
						
							|  |  |  |      * may currently be up to 4x this value. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function all(Request $request) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->validate($request, $this->rules['all']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $options = SearchOptions::fromString($request->get('query') ?? ''); | 
					
						
							|  |  |  |         $page = intval($request->get('page', '0')) ?: 1; | 
					
						
							|  |  |  |         $count = min(intval($request->get('count', '0')) ?: 20, 100); | 
					
						
							| 
									
										
										
										
											2024-10-21 05:12:49 +08:00
										 |  |  |         $includes = $this->parseIncludes($request->get('include', '')); | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $results = $this->searchRunner->searchEntities($options, 'all', $page, $count); | 
					
						
							| 
									
										
										
										
											2021-12-07 04:42:04 +08:00
										 |  |  |         $this->resultsFormatter->format($results['results']->all(), $options); | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-21 05:12:49 +08:00
										 |  |  |         $formatter = new ApiEntityListFormatter($results['results']->all()); | 
					
						
							|  |  |  |         $formatter->withType(); // Always include type as it's essential for search results
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         foreach ($includes as $include) { | 
					
						
							|  |  |  |             if (isset(self::VALID_INCLUDES[$include])) { | 
					
						
							|  |  |  |                 $method = self::VALID_INCLUDES[$include]; | 
					
						
							|  |  |  |                 $formatter->$method(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $formatter->withField('preview_html', function (Entity $entity) { | 
					
						
							|  |  |  |             return [ | 
					
						
							|  |  |  |                 'name' => (string) $entity->getAttribute('preview_name'), | 
					
						
							|  |  |  |                 'content' => (string) $entity->getAttribute('preview_content'), | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return response()->json([ | 
					
						
							| 
									
										
										
										
											2024-10-21 05:12:49 +08:00
										 |  |  |             'data' => $formatter->format(), | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  |             'total' => $results['total'], | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-21 05:12:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Parse and validate the include parameter. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $includeString Comma-separated list of includes | 
					
						
							|  |  |  |      * @return array<string> | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function parseIncludes(string $includeString): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (empty($includeString)) { | 
					
						
							|  |  |  |             return []; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return array_filter( | 
					
						
							|  |  |  |             explode(',', strtolower($includeString)), | 
					
						
							|  |  |  |             fn($include) => isset (self::VALID_INCLUDES[$include]) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-11-15 00:28:01 +08:00
										 |  |  | } |