diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c1d8955..ef153d024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Additional tabs "Albums" and "Tracks" to the artist details pane - Additional tabs "Tracks" and "Artists" to the album details pane - Favorite toggle to the details pane of the tracks, albums, artists, playlists, and podcasts +- New filter "Favorite" for the smart list ### Changed - Drop support for PHP versions older 7.4 (i.e. PHP 7.1 - 7.3) diff --git a/js/app/controllers/sidebar/smartlistfilterscontroller.js b/js/app/controllers/sidebar/smartlistfilterscontroller.js index 6502eaec4..2a7c7bc4f 100644 --- a/js/app/controllers/sidebar/smartlistfilterscontroller.js +++ b/js/app/controllers/sidebar/smartlistfilterscontroller.js @@ -34,6 +34,7 @@ angular.module('Music').controller('SmartListFiltersController', [ $('#filter-genres').chosen(); $('#filter-artists').chosen(); $('#filter-history').chosen({allow_single_deselect: true, disable_search: true, placeholder_text_single: ' '}); + $('#filter-favorite').chosen({allow_single_deselect: true, disable_search: true, placeholder_text_single: ' '}); const $chosenInputs = $('#smartlist-filters .chosen-container'); const $filterGenres = $('#filter-genres'); const $filterSize = $('#filter-size'); diff --git a/lib/BusinessLayer/PlaylistBusinessLayer.php b/lib/BusinessLayer/PlaylistBusinessLayer.php index 3b02817dd..3cc11aae8 100644 --- a/lib/BusinessLayer/PlaylistBusinessLayer.php +++ b/lib/BusinessLayer/PlaylistBusinessLayer.php @@ -193,12 +193,13 @@ public function getDuration(int $playlistId, string $userId) : int { * @param int[] $artists Array of artist IDs * @param int|null $fromYear Earliest release year to include * @param int|null $toYear Latest release year to include + * @param string|null $favorite One of: 'track', 'album', 'artists', 'track_album_artist', null * @param int $size Size of the playlist to generate, provided that there are enough matching tracks * @param string $userId the name of the user */ public function generate( ?string $history, bool $historyStrict, array $genres, array $artists, - ?int $fromYear, ?int $toYear, int $size, string $userId) : Playlist { + ?int $fromYear, ?int $toYear, ?string $favorite, int $size, string $userId) : Playlist { $now = new \DateTime(); $nowStr = $now->format(PlaylistMapper::SQL_DATE_FORMAT); @@ -212,7 +213,9 @@ public function generate( list('sortBy' => $sortBy, 'invert' => $invertSort) = self::sortRulesForHistory($history); $limit = ($sortBy === SortBy::None) ? null : ($historyStrict ? $size : $size * 4); - $tracks = $this->trackMapper->findAllByCriteria($genres, $artists, $fromYear, $toYear, $sortBy, $invertSort, $userId, $limit); + $favoriteMask = self::favoriteMask($favorite); + + $tracks = $this->trackMapper->findAllByCriteria($genres, $artists, $fromYear, $toYear, $favoriteMask, $sortBy, $invertSort, $userId, $limit); if ($sortBy !== SortBy::None && !$historyStrict) { // When generating by non-strict history, use a pool of tracks at maximum twice the size of final list. @@ -247,4 +250,14 @@ private static function sortRulesForHistory(?string $history) : array { return ['sortBy' => SortBy::None, 'invert' => false]; } } + + private static function favoriteMask(?string $mode) : ?int { + switch ($mode) { + case 'track': return TrackMapper::FAVORITE_TRACK; + case 'album': return TrackMapper::FAVORITE_ALBUM; + case 'artist': return TrackMapper::FAVORITE_ARTIST; + case 'track_album_artist': return TrackMapper::FAVORITE_TRACK | TrackMapper::FAVORITE_ALBUM | TrackMapper::FAVORITE_ARTIST; + default: return null; + } + } } diff --git a/lib/Controller/PlaylistApiController.php b/lib/Controller/PlaylistApiController.php index 6c2e1f0b6..43545f3c7 100644 --- a/lib/Controller/PlaylistApiController.php +++ b/lib/Controller/PlaylistApiController.php @@ -165,7 +165,7 @@ private function toFullTree($playlist) { */ public function generate( ?bool $useLatestParams, ?string $history, ?string $genres, ?string $artists, - ?int $fromYear, ?int $toYear, int $size=100, string $historyStrict='false') { + ?int $fromYear, ?int $toYear, ?string $favorite=null, int $size=100, string $historyStrict='false') { if ($useLatestParams) { $history = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_history') ?: null; @@ -173,6 +173,7 @@ public function generate( $artists = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_artists') ?: null; $fromYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_from_year') ?: null; $toYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_to_year') ?: null; + $favorite = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_favorite') ?: null; $size = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_size', 100); $historyStrict = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_history_strict', 'false'); } else { @@ -181,6 +182,7 @@ public function generate( $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_artists', $artists ?? ''); $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_from_year', (string)$fromYear); $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_to_year', (string)$toYear); + $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_favorite', $favorite ?? ''); $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_size', (string)$size); $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_history_strict', $historyStrict); } @@ -191,7 +193,7 @@ public function generate( $artists = $this->artistBusinessLayer->findAllIds($this->userId, self::toIntArray($artists)); $playlist = $this->playlistBusinessLayer->generate( - $history, $historyStrict, $genres, $artists, $fromYear, $toYear, $size, $this->userId); + $history, $historyStrict, $genres, $artists, $fromYear, $toYear, $favorite, $size, $this->userId); $result = $playlist->toAPI($this->urlGenerator); $result['params'] = [ @@ -201,6 +203,7 @@ public function generate( 'artists' => \implode(',', $artists) ?: null, 'fromYear' => $fromYear ?: null, 'toYear' => $toYear ?: null, + 'favorite' => $favorite ?: null, 'size' => $size ]; diff --git a/lib/Db/TrackMapper.php b/lib/Db/TrackMapper.php index 69fd4e2e1..13f760391 100644 --- a/lib/Db/TrackMapper.php +++ b/lib/Db/TrackMapper.php @@ -268,18 +268,23 @@ public function findAllByNameArtistOrAlbum(?string $name, ?string $artistName, ? } } + const FAVORITE_TRACK = 0x1; + const FAVORITE_ALBUM = 0x2; + const FAVORITE_ARTIST = 0x4; + /** * Returns all tracks specified by various criteria, all of which are optional * @param int[] $genres Array of genre IDs * @param int[] $artists Array of artist IDs * @param int|null $fromYear Earliest release year to include * @param int|null $toYear Latest release year to include + * @param int|null $favorite Bit mask of FAVORITE_TRACK, FAVORITE_ALBUM, FAVORITE_ARTIST (given favorite types are ORed in the query) * @param int $sortBy Sorting rule as defined in the class SortBy * @param string $userId the name of the user * @return Track[] Tracks matching the criteria */ public function findAllByCriteria( - array $genres, array $artists, ?int $fromYear, ?int $toYear, + array $genres, array $artists, ?int $fromYear, ?int $toYear, ?int $favorite, int $sortBy, bool $invertSort, string $userId, ?int $limit=null, ?int $offset=null) : array { $sqlConditions = []; @@ -305,6 +310,20 @@ public function findAllByCriteria( $params[] = $toYear; } + if (!empty($favorite)) { + $favConds = []; + if ($favorite & self::FAVORITE_TRACK) { + $favConds[] = '`*PREFIX*music_tracks`.`starred` IS NOT NULL'; + } + if ($favorite & self::FAVORITE_ALBUM) { + $favConds[] = '`album`.`starred` IS NOT NULL'; + } + if ($favorite & self::FAVORITE_ARTIST) { + $favConds[] = '`artist`.`starred` IS NOT NULL'; + } + $sqlConditions[] = '(' . \implode(' OR ', $favConds) . ')'; + } + $sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions), $this->formatSortingClause($sortBy, $invertSort)); return $this->findEntities($sql, $params, $limit, $offset); } diff --git a/templates/partials/sidebar/smartlistfilters.php b/templates/partials/sidebar/smartlistfilters.php index d29344528..e6185c361 100644 --- a/templates/partials/sidebar/smartlistfilters.php +++ b/templates/partials/sidebar/smartlistfilters.php @@ -43,5 +43,16 @@ +
+ + +
+