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