diff --git a/app/Actions/Search/AlbumSearch.php b/app/Actions/Search/AlbumSearch.php index 411984776ba..fa264fe1647 100644 --- a/app/Actions/Search/AlbumSearch.php +++ b/app/Actions/Search/AlbumSearch.php @@ -53,17 +53,20 @@ public function queryTagAlbums(array $terms): Collection } /** - * @param string[] $terms + * @param string[] $terms + * @param Album|null $album the optional top album which is used as a search base * * @return Collection * * @throws InternalLycheeException */ - public function queryAlbums(array $terms): Collection + public function queryAlbums(array $terms, ?Album $album = null): Collection { $album_query = Album::query() ->select(['albums.*']) - ->join('base_albums', 'base_albums.id', '=', 'albums.id'); + ->join('base_albums', 'base_albums.id', '=', 'albums.id') + ->when($album !== null, fn ($q) => $q->where('albums._lft', '>=', $album->_lft) + ->where('albums._rgt', '<=', $album->_rgt)); $this->addSearchCondition($terms, $album_query); $this->albumQueryPolicy->applyBrowsabilityFilter($album_query); diff --git a/app/Http/Controllers/Gallery/SearchController.php b/app/Http/Controllers/Gallery/SearchController.php index f595a86536b..4c8e0d11c72 100644 --- a/app/Http/Controllers/Gallery/SearchController.php +++ b/app/Http/Controllers/Gallery/SearchController.php @@ -51,7 +51,7 @@ public function search(GetSearchRequest $request, AlbumSearch $album_search, Pho ->orderBy(ColumnSortingPhotoType::TAKEN_AT->value, OrderSortingType::ASC->value) ->paginate(Configs::getValueAsInt('search_pagination_limit')); - $album_results = $album_search->queryAlbums($terms); + $album_results = $album_search->queryAlbums($terms, $album); return ResultsResource::fromData($album_results, $photo_results); } diff --git a/app/Http/Requests/Search/GetSearchRequest.php b/app/Http/Requests/Search/GetSearchRequest.php index 9153044afde..eaf0f4a240c 100644 --- a/app/Http/Requests/Search/GetSearchRequest.php +++ b/app/Http/Requests/Search/GetSearchRequest.php @@ -21,6 +21,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use function Safe\base64_decode; +use function Safe\preg_match_all; class GetSearchRequest extends BaseApiRequest implements HasAbstractAlbum, HasTerms { @@ -59,10 +60,18 @@ protected function processValidatedValues(array $values, array $files): void $this->album = $this->album_factory->findNullalbleAbstractAlbumOrFail($album_id); // Escape special characters for a LIKE query - $this->terms = explode(' ', str_replace( + $terms = str_replace( ['\\', '%', '_'], ['\\\\', '\\%', '\\_'], base64_decode($values[RequestAttribute::TERM_ATTRIBUTE], true) - )); + ); + + // Explode the string by spaces but keep encapsulated strings as single terms + // This regex matches quoted strings and unquoted words + // Note: This regex is not perfect and may not handle all edge cases, but it works for most common cases. + // It captures quoted strings as a single match and unquoted words separately. + // Example: "hello world" foo bar -> ["hello world", "foo", "bar"] + preg_match_all('/"[^"]*"|\S+/', $terms, $matches); + $this->terms = array_map(fn ($term) => trim($term, '"'), $matches[0]); } } \ No newline at end of file diff --git a/resources/js/views/gallery-panels/Search.vue b/resources/js/views/gallery-panels/Search.vue index 20e2b44dea8..bbcf2c66c47 100644 --- a/resources/js/views/gallery-panels/Search.vue +++ b/resources/js/views/gallery-panels/Search.vue @@ -322,7 +322,7 @@ function goBack() { if (photoId.value !== undefined) { photoId.value = undefined; photo.value = undefined; - router.push({ name: "search-with-album", params: { albumId: albumId.value } }); + router.push({ name: "search", params: { albumId: albumId.value } }); return; } @@ -410,7 +410,7 @@ onKeyStroke("Escape", () => { onMounted(() => { if (photoId.value !== undefined) { - router.push({ name: "search-with-album", params: { albumId: albumId.value } }); + router.push({ name: "search", params: { albumId: albumId.value } }); } if (albumId.value !== "" && albumId.value !== ALL) {