diff --git a/app/Console/Commands/NntmuxSearchDiag.php b/app/Console/Commands/NntmuxSearchDiag.php index 07238a797..e3e49a4d2 100644 --- a/app/Console/Commands/NntmuxSearchDiag.php +++ b/app/Console/Commands/NntmuxSearchDiag.php @@ -197,10 +197,8 @@ class NntmuxSearchDiag extends Command return 'MISSING_IN_INDEX'; } - $hidden = false; - if (! $showPasswords && (int) $release->passwordstatus !== 0) { - $hidden = true; - } + $maxVisiblePasswordStatus = $showPasswords ? 1 : 0; + $hidden = (int) $release->passwordstatus > $maxVisiblePasswordStatus; $expected = ReleaseSearchIndexDocument::normalize($release->toArray()); unset($expected['id']); diff --git a/app/Http/Controllers/Api/RSS.php b/app/Http/Controllers/Api/RSS.php index 26915e36c..b83a3ed94 100644 --- a/app/Http/Controllers/Api/RSS.php +++ b/app/Http/Controllers/Api/RSS.php @@ -76,7 +76,7 @@ class RSS extends ApiController %s %s %s %s ORDER BY postdate DESC %s", $cartSearch, - $this->releaseBrowseService->showPasswords(), + $this->releaseBrowseService->showPasswordsForRss(), $catSearch, ($videosId > 0 ? sprintf('AND r.videos_id = %d %s', $videosId, ($catSearch === '' ? $catLimit : '')) : ''), ($aniDbID > 0 ? sprintf('AND r.anidbid = %d %s', $aniDbID, ($catSearch === '' ? $catLimit : '')) : ''), @@ -141,7 +141,8 @@ class RSS extends ApiController 'min_size' => 0, 'max_age_days' => 0, 'groups_id' => null, - 'password_allow_rar' => str_contains($this->releaseBrowseService->showPasswords(), '<='), + 'password_allow_rar' => $this->releaseBrowseService->passwordAllowRar(), + 'password_status_min' => 0, 'sort_field' => 'postdate_ts', 'sort_dir' => 'desc', 'try_fuzzy' => false, @@ -205,7 +206,7 @@ class RSS extends ApiController ($airDate > -1 ? sprintf(' AND tve.firstaired >= DATE_SUB(CURDATE(), INTERVAL %d DAY)', $airDate) : ''), Category::TV_ROOT, Category::TV_OTHER, - $this->releaseBrowseService->showPasswords(), + $this->releaseBrowseService->showPasswordsForRss(), ! empty($limit) ? sprintf(' LIMIT %d OFFSET 0', min($limit, 100)) : '' ); @@ -235,7 +236,7 @@ class RSS extends ApiController (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''), Category::MOVIE_ROOT, Category::MOVIE_OTHER, - $this->releaseBrowseService->showPasswords(), + $this->releaseBrowseService->showPasswordsForRss(), ! empty($limit) ? sprintf(' LIMIT %d OFFSET 0', min($limit, 100)) : '' ); @@ -309,7 +310,7 @@ class RSS extends ApiController WHERE rn <= 5 ORDER BY FIELD(imdbid, '%s'), postdate DESC", implode("','", $topMovies->toArray()), - $this->releaseBrowseService->showPasswords(), + $this->releaseBrowseService->showPasswordsForRss(), implode("','", $topMovies->toArray()) ); @@ -376,7 +377,7 @@ class RSS extends ApiController WHERE rn <= 5 ORDER BY FIELD(videos_id, %s), postdate DESC", implode(',', $topShows->toArray()), - $this->releaseBrowseService->showPasswords(), + $this->releaseBrowseService->showPasswordsForRss(), implode(',', $topShows->toArray()) ); diff --git a/app/Services/Releases/ReleaseBrowseService.php b/app/Services/Releases/ReleaseBrowseService.php index 02ceb3d40..9f8e19a38 100644 --- a/app/Services/Releases/ReleaseBrowseService.php +++ b/app/Services/Releases/ReleaseBrowseService.php @@ -117,7 +117,7 @@ class ReleaseBrowseService 'min_size' => $minSize, 'max_age_days' => $maxAge, 'groups_id' => $groupId, - 'password_allow_rar' => str_contains($this->showPasswords(), '<='), + 'password_allow_rar' => $this->passwordAllowRar(), 'sort_field' => $indexSort, 'sort_dir' => $orderBy[1] ?? 'desc', // @phpstan-ignore offsetAccess.notFound 'try_fuzzy' => true, @@ -365,7 +365,7 @@ class ReleaseBrowseService 'min_size' => $minSize, 'max_age_days' => $maxAge, 'groups_id' => $groupId, - 'password_allow_rar' => str_contains($this->showPasswords(), '<='), + 'password_allow_rar' => $this->passwordAllowRar(), 'sort_field' => $indexSortField, 'sort_dir' => $orderBy[1] ?? 'desc', 'try_fuzzy' => false, @@ -432,10 +432,29 @@ class ReleaseBrowseService return match ($setting) { 1 => '<= '.self::PASSWD_RAR, - default => '= '.self::PASSWD_NONE, + default => '<= '.self::PASSWD_NONE, }; } + /** + * When true, search/browse includes RAR-passworded releases (passwordstatus <= 1). + */ + public function passwordAllowRar(): bool + { + return (int) Settings::settingValue('showpasswordedrelease') === 1; + } + + /** + * Password clause for RSS/feed SQL: excludes untested backlog (passwordstatus = -1). + * Site search and browse use {@see showPasswords()} which includes -1 until post-processing. + */ + public function showPasswordsForRss(): string + { + return $this->passwordAllowRar() + ? 'BETWEEN '.self::PASSWD_NONE.' AND '.self::PASSWD_RAR + : '= '.self::PASSWD_NONE; + } + /** * Use to order releases on site. * diff --git a/app/Services/Releases/ReleaseSearchService.php b/app/Services/Releases/ReleaseSearchService.php index 962a85832..fe85b8777 100644 --- a/app/Services/Releases/ReleaseSearchService.php +++ b/app/Services/Releases/ReleaseSearchService.php @@ -118,7 +118,7 @@ class ReleaseSearchService 'min_date' => $minDateCriteria, 'max_date' => $maxDateCriteria, 'groups_id' => $groupId, - 'password_allow_rar' => str_contains($this->showPasswords(), '<='), + 'password_allow_rar' => $this->passwordAllowRar(), 'sort_field' => $this->browseOrderToIndexSortField((string) $orderBy[0]), 'sort_dir' => $orderBy[1] ?? 'desc', 'try_fuzzy' => true, @@ -261,7 +261,7 @@ class ReleaseSearchService 'min_size' => $minSize, 'max_age_days' => $maxAge, 'groups_id' => $groupId, - 'password_allow_rar' => str_contains($this->showPasswords(), '<='), + 'password_allow_rar' => $this->passwordAllowRar(), 'sort_field' => $this->browseOrderToIndexSortField($orderField), 'sort_dir' => $orderDir, 'try_fuzzy' => true, @@ -1606,7 +1606,7 @@ class ReleaseSearchService 'excluded_category_ids' => array_map(static fn ($id): int => (int) $id, $excludedCategories), 'min_size' => $minSize, 'max_age_days' => $maxAge, - 'password_allow_rar' => str_contains($this->showPasswords(), '<='), + 'password_allow_rar' => $this->passwordAllowRar(), 'sort_field' => 'postdate_ts', 'sort_dir' => 'desc', 'try_fuzzy' => false, @@ -1940,10 +1940,18 @@ class ReleaseSearchService return match ($setting) { 1 => '<= '.self::PASSWD_RAR, - default => '= '.self::PASSWD_NONE, + default => '<= '.self::PASSWD_NONE, }; } + /** + * When true, search/browse includes RAR-passworded releases (passwordstatus <= 1). + */ + private function passwordAllowRar(): bool + { + return (int) Settings::settingValue('showpasswordedrelease') === 1; + } + /** * Use to order releases on site. * diff --git a/app/Services/Search/Contracts/SearchServiceInterface.php b/app/Services/Search/Contracts/SearchServiceInterface.php index 72e906717..6b5586a38 100644 --- a/app/Services/Search/Contracts/SearchServiceInterface.php +++ b/app/Services/Search/Contracts/SearchServiceInterface.php @@ -346,8 +346,8 @@ interface SearchServiceInterface * * @param array $criteria Keys: phrases (string|array|null), category_ids (list|null), * excluded_category_ids (list), min_size (int), max_age_days (int), - * groups_id (int|null), password_allow_rar (bool), sort_field (string), - * sort_dir (string), try_fuzzy (bool), release_ids (list|null) + * groups_id (int|null), password_allow_rar (bool), password_status_min (int|null), + * sort_field (string), sort_dir (string), try_fuzzy (bool), release_ids (list|null) * @return array{ids: list, total: int, fuzzy: bool} */ public function searchReleasesFiltered(array $criteria, int $limit, int $offset = 0): array; diff --git a/app/Services/Search/Drivers/ElasticSearchDriver.php b/app/Services/Search/Drivers/ElasticSearchDriver.php index 24ffdfd23..59ed0f54d 100644 --- a/app/Services/Search/Drivers/ElasticSearchDriver.php +++ b/app/Services/Search/Drivers/ElasticSearchDriver.php @@ -3107,7 +3107,11 @@ class ElasticSearchDriver implements SearchDriverInterface if ($allowRar) { $filter[] = ['range' => ['passwordstatus' => ['lte' => 1]]]; } else { - $filter[] = ['term' => ['passwordstatus' => 0]]; + $filter[] = ['range' => ['passwordstatus' => ['lte' => 0]]]; + } + + if (array_key_exists('password_status_min', $criteria) && $criteria['password_status_min'] !== null) { + $filter[] = ['range' => ['passwordstatus' => ['gte' => (int) $criteria['password_status_min']]]]; } return $filter; diff --git a/app/Services/Search/Drivers/ManticoreSearchDriver.php b/app/Services/Search/Drivers/ManticoreSearchDriver.php index af64d9ab3..e0387f4f6 100644 --- a/app/Services/Search/Drivers/ManticoreSearchDriver.php +++ b/app/Services/Search/Drivers/ManticoreSearchDriver.php @@ -2670,7 +2670,11 @@ class ManticoreSearchDriver implements SearchDriverInterface if ($allowRar) { $query->filter('passwordstatus', 'lte', 1); } else { - $query->filter('passwordstatus', '=', 0); + $query->filter('passwordstatus', 'lte', 0); + } + + if (array_key_exists('password_status_min', $criteria) && $criteria['password_status_min'] !== null) { + $query->filter('passwordstatus', 'gte', (int) $criteria['password_status_min']); } } diff --git a/tests/Unit/ReleaseSearchServiceOrderingTest.php b/tests/Unit/ReleaseSearchServiceOrderingTest.php index d60639afd..8d281d195 100644 --- a/tests/Unit/ReleaseSearchServiceOrderingTest.php +++ b/tests/Unit/ReleaseSearchServiceOrderingTest.php @@ -174,14 +174,14 @@ class ReleaseSearchServiceOrderingTest extends TestCase $sql = $method->invoke( $this->service, - 'WHERE r.passwordstatus = 0 AND r.id IN (1,2,3)', + 'WHERE r.passwordstatus <= 0 AND r.id IN (1,2,3)', ['postdate', 'desc'], 50, 0 ); $this->assertSame( - 'SELECT r.id FROM releases r WHERE r.passwordstatus = 0 AND r.id IN (1,2,3) ORDER BY r.postdate desc LIMIT 50 OFFSET 0', + 'SELECT r.id FROM releases r WHERE r.passwordstatus <= 0 AND r.id IN (1,2,3) ORDER BY r.postdate desc LIMIT 50 OFFSET 0', $sql ); $this->assertStringNotContainsString('JOIN', $sql); @@ -201,9 +201,9 @@ class ReleaseSearchServiceOrderingTest extends TestCase $service->expects($this->once()) ->method('getPagerCount') - ->with('SELECT COUNT(*) as count FROM releases r WHERE r.passwordstatus = 0 AND r.id IN (1,2,3)') + ->with('SELECT COUNT(*) as count FROM releases r WHERE r.passwordstatus <= 0 AND r.id IN (1,2,3)') ->willReturn(3); - $this->assertSame(3, $method->invoke($service, 'WHERE r.passwordstatus = 0 AND r.id IN (1,2,3)')); + $this->assertSame(3, $method->invoke($service, 'WHERE r.passwordstatus <= 0 AND r.id IN (1,2,3)')); } }