diff --git a/app/Services/PluginServices/QueryPluginsService.php b/app/Services/PluginServices/QueryPluginsService.php index 6d4c33b..41e18c0 100644 --- a/app/Services/PluginServices/QueryPluginsService.php +++ b/app/Services/PluginServices/QueryPluginsService.php @@ -12,16 +12,16 @@ class QueryPluginsService { public function queryPlugins(Plugins\QueryPluginsRequest $req): Plugins\QueryPluginsResponse { - $page = $req->page; + $page = $req->page; $perPage = $req->per_page; - $browse = $req->browse ?: 'popular'; - $search = $req->search ?? null; - $author = $req->author ?? null; + $browse = $req->browse ?: 'popular'; + $search = $req->search ?? null; + $author = $req->author ?? null; // Operators coming from the DTO - $tags = $req->tags ?? []; + $tags = $req->tags ?? []; $tagAnd = $req->tagAnd ?? []; - $tagOr = $req->tagOr ?? []; + $tagOr = $req->tagOr ?? []; $tagNot = $req->tagNot ?? []; // merge base tags with tagOr @@ -31,8 +31,8 @@ public function queryPlugins(Plugins\QueryPluginsRequest $req): Plugins\QueryPlu $callbacks = collect(); !empty($anyTags) && $callbacks->push(fn($q) => self::applyTagAny($q, $anyTags)); - !empty($tagAnd) && $callbacks->push(fn($q) => self::applyTagAll($q, $tagAnd)); - !empty($tagNot) && $callbacks->push(fn($q) => self::applyTagNot($q, $tagNot)); + !empty($tagAnd) && $callbacks->push(fn($q) => self::applyTagAll($q, $tagAnd)); + !empty($tagNot) && $callbacks->push(fn($q) => self::applyTagNot($q, $tagNot)); $search && $callbacks->push(fn($q) => self::applySearchWeighted($q, $search, $req)); $author && $callbacks->push(fn($q) => self::applyAuthor($q, $author)); @@ -56,7 +56,7 @@ public function queryPlugins(Plugins\QueryPluginsRequest $req): Plugins\QueryPlu return Plugins\QueryPluginsResponse::from([ 'plugins' => $plugins, - 'info' => ['page' => $page, 'pages' => $totalPages, 'results' => $total], + 'info' => ['page' => $page, 'pages' => $totalPages, 'results' => $total], ]); } @@ -69,9 +69,8 @@ public function queryPlugins(Plugins\QueryPluginsRequest $req): Plugins\QueryPlu public static function applySearchWeighted( Builder $query, string $search, - Plugins\QueryPluginsRequest $request - ): Builder - { + Plugins\QueryPluginsRequest $request, + ): Builder { $lcsearch = mb_strtolower($search); $slug = Regex::replace('/[^-\w]+/', '-', $lcsearch); $wordchars = Regex::replace('/\W+/', '', $lcsearch); @@ -84,7 +83,7 @@ public static function applySearchWeighted( ->orWhereRaw("slug %> ?", [$wordchars]) ->orWhereRaw("name %> ?", [$wordchars]) ->orWhereRaw("short_description %> ?", [$wordchars]) - ->orWhereFullText('description', $search) + ->orWhereFullText('description', $search), ) ->selectRaw("plugins.*, CASE @@ -97,29 +96,28 @@ public static function applySearchWeighted( WHEN short_description %> ? THEN 400000 WHEN to_tsvector('english', description) @@ plainto_tsquery(?) THEN 300000 ELSE 0 - END + log(GREATEST($sortColumn, 1)) AS score", [ - $search, - $search, - "$slug%", - "$search%", - $wordchars, - $wordchars, - $wordchars, - $search, - ]) + END + log(GREATEST($sortColumn, 1)) AS score", + [ + $search, + $search, + "$slug%", + "$search%", + $wordchars, + $wordchars, + $wordchars, + $search, + ]) ->orderByDesc('score'); } /** @param Builder $query */ public static function applyAuthor(Builder $query, string $author): Builder { - return $query->where(fn(Builder $q) - => $q + return $query->where(fn(Builder $q) => $q ->whereRaw("author %> '$author'") ->orWhereHas( 'contributors', - fn(Builder $q) - => $q + fn(Builder $q) => $q ->whereRaw("user_nicename %> '$author'") ->orWhereRaw("display_name %> '$author'"), )); @@ -127,7 +125,7 @@ public static function applyAuthor(Builder $query, string $author): Builder /** * @param Builder $query - * @param list $tags + * @param list $tags */ public static function applyTagAny(Builder $query, array $tags): Builder { @@ -136,7 +134,7 @@ public static function applyTagAny(Builder $query, array $tags): Builder /** * @param Builder $query - * @param list $tags + * @param list $tags */ public static function applyTagAll(Builder $query, array $tags): Builder { @@ -144,13 +142,13 @@ public static function applyTagAll(Builder $query, array $tags): Builder 'tags', fn(Builder $q) => $q->whereIn('slug', $tags), '>=', - count($tags) + count($tags), ); } /** * @param Builder $query - * @param list $tags + * @param list $tags */ public static function applyTagNot(Builder $query, array $tags): Builder { @@ -164,6 +162,13 @@ public static function applyTagNot(Builder $query, array $tags): Builder */ public static function applyBrowse(Builder $query, string $browse): Builder { + if ($browse === 'featured') { + $query->where(fn($q) => $q + ->where(fn($q) => $q->where('rating', '>=', 80)->where('num_ratings', '>', 100)) + ->orWhere('ac_origin', '!=', 'wp_org') + ); + } + return $query->reorder(self::browseToSortColumn($browse), 'desc'); } @@ -171,8 +176,8 @@ public static function browseToSortColumn(?string $browse): string { return match ($browse) { 'new' => 'added', - 'updated' => 'last_updated', - 'top-rated', 'featured' => 'rating', + 'top-rated' => 'rating', + 'updated', 'featured' => 'last_updated', default => 'active_installs', }; } diff --git a/tests/Feature/Services/Plugins/QueryPluginsServiceTest.php b/tests/Feature/Services/Plugins/QueryPluginsServiceTest.php index a0112c7..e4d5b37 100644 --- a/tests/Feature/Services/Plugins/QueryPluginsServiceTest.php +++ b/tests/Feature/Services/Plugins/QueryPluginsServiceTest.php @@ -208,29 +208,6 @@ expect($sql)->toContain('order by'); }); -test('browseToSortColumn returns correct column for each browse parameter', function () { - expect(QueryPluginsService::browseToSortColumn('new')) - ->toBe('added') - ->and(QueryPluginsService::browseToSortColumn('updated'))->toBe('last_updated') - ->and(QueryPluginsService::browseToSortColumn('top-rated'))->toBe('rating') - ->and(QueryPluginsService::browseToSortColumn('featured'))->toBe('rating') - ->and(QueryPluginsService::browseToSortColumn('popular'))->toBe('active_installs') - ->and(QueryPluginsService::browseToSortColumn(null))->toBe('active_installs'); -}); - -// [chuck 2025-09-13] These are no longer used, but keeping them commented for future reference. -// If they're still not used after 6 months, just delete them. -// -// test('normalizeSearchString handles various inputs correctly', function () { -// expect(QueryPluginsService::normalizeSearchString(null)) -// ->toBeNull() -// ->and(QueryPluginsService::normalizeSearchString(''))->toBe('') -// ->and(QueryPluginsService::normalizeSearchString(' test '))->toBe('test') -// ->and(QueryPluginsService::normalizeSearchString('test search'))->toBe('test search') -// ->and(QueryPluginsService::normalizeSearchString('test@example.com'))->toBe('test@example.com') -// ->and(QueryPluginsService::normalizeSearchString('test*search'))->toBe('test search'); -// }); - test('applySearchWeighted prioritizes relevance over install count', function () { // Create plugins with different install counts and names Plugin::factory()->create([