ownedBy($user), 'The playlist folder does not belong to the user'); } throw_if($playables && $ruleGroups, new PlaylistBothSongsAndRulesProvidedException()); throw_if($ownSongsOnly && (!$ruleGroups || !License::isPlus()), new InvalidArgumentException( '"Own songs only" option only works with smart playlists and Plus license.' )); return DB::transaction( static function () use ($name, $user, $playables, $folder, $ruleGroups, $ownSongsOnly): Playlist { $playlist = $user->playlists()->create([ 'name' => $name, 'rules' => $ruleGroups, 'own_songs_only' => $ownSongsOnly, ]); $folder?->playlists()->attach($playlist); if (!$playlist->is_smart && $playables) { $playlist->addPlayables($playables, $user); } return $playlist; } ); } public function updatePlaylist( Playlist $playlist, string $name, ?Folder $folder = null, ?SmartPlaylistRuleGroupCollection $ruleGroups = null, bool $ownSongsOnly = false ): Playlist { if ($folder) { Assert::true($playlist->ownedBy($folder->user), 'The playlist folder does not belong to the user'); } throw_if($ownSongsOnly && (!$playlist->is_smart || !License::isPlus()), new InvalidArgumentException( '"Own songs only" option only works with smart playlists and Plus license.' )); $playlist->update([ 'name' => $name, 'rules' => $ruleGroups, 'own_songs_only' => $ownSongsOnly, ]); $folder?->playlists()->syncWithoutDetaching($playlist); return $playlist; } /** @return Collection */ public function addPlayablesToPlaylist( Playlist $playlist, Collection|Playable|array $playables, User $user ): Collection { return DB::transaction(function () use ($playlist, $playables, $user) { $playables = Collection::wrap($playables); $playlist->addPlayables( $playables->filter(static fn ($song): bool => !$playlist->playables->contains($song)), $user ); // if the playlist is collaborative, make the songs public if ($playlist->is_collaborative) { $this->makePlaylistContentPublic($playlist); } // we want a fresh copy of the songs with the possibly updated visibility return $this->songRepository->getManyInCollaborativeContext( ids: $playables->pluck('id')->all(), scopedUser: $user ); }); } public function removePlayablesFromPlaylist(Playlist $playlist, Collection|Playable|array $playables): void { $playlist->removePlayables($playables); } public function makePlaylistContentPublic(Playlist $playlist): void { $playlist->playables()->where('is_public', false)->update(['is_public' => true]); } public function movePlayablesInPlaylist(Playlist $playlist, array $movingIds, string $target, string $type): void { Assert::oneOf($type, ['before', 'after']); throw_if($playlist->is_smart, OperationNotApplicableForSmartPlaylistException::class); DB::transaction(static function () use ($playlist, $movingIds, $target, $type): void { $targetPosition = $playlist->playables()->wherePivot('song_id', $target)->value('position'); $insertPosition = $type === 'before' ? $targetPosition : $targetPosition + 1; // create a "gap" for the moving songs by incrementing the position of the songs after the target $playlist->playables() ->newPivotQuery() ->where('position', $type === 'before' ? '>=' : '>', $targetPosition) ->whereNotIn('song_id', $movingIds) ->increment('position', count($movingIds)); $values = []; foreach ($movingIds as $id) { $values[$id] = ['position' => $insertPosition++]; } $playlist->playables()->syncWithoutDetaching($values); }); } }