*/ class PodcastEpisodesController extends AbstractApiCrudController { protected string $entityClass = Entity\PodcastEpisode::class; protected string $resourceRouteName = 'api:stations:podcast:episode'; public function __construct( ReloadableEntityManagerInterface $em, Serializer $serializer, ValidatorInterface $validator, protected Entity\Repository\StationRepository $stationRepository, protected Entity\Repository\PodcastRepository $podcastRepository, protected Entity\Repository\PodcastEpisodeRepository $episodeRepository ) { parent::__construct($em, $serializer, $validator); } public function listAction( ServerRequest $request, Response $response, string $podcast_id ): ResponseInterface { $station = $request->getStation(); $podcast = $this->podcastRepository->fetchPodcastForStation($station, $podcast_id); $queryBuilder = $this->em->createQueryBuilder() ->select('e, p, pm') ->from(Entity\PodcastEpisode::class, 'e') ->join('e.podcast', 'p') ->leftJoin('e.media', 'pm') ->where('e.podcast = :podcast') ->orderBy('e.title', 'ASC') ->setParameter('podcast', $podcast); $searchPhrase = trim($request->getParam('searchPhrase', '')); if (!empty($searchPhrase)) { $queryBuilder->andWhere('e.title LIKE :title') ->setParameter('title', '%' . $searchPhrase . '%'); } return $this->listPaginatedFromQuery($request, $response, $queryBuilder->getQuery()); } public function getAction( ServerRequest $request, Response $response, string $episode_id ): ResponseInterface { $station = $request->getStation(); $record = $this->getRecord($station, $episode_id); if (null === $record) { return $response->withStatus(404) ->withJson(Entity\Api\Error::notFound()); } $return = $this->viewRecord($record, $request); return $response->withJson($return); } public function createAction( ServerRequest $request, Response $response, string $podcast_id ): ResponseInterface { $station = $request->getStation(); $podcast = $this->podcastRepository->fetchPodcastForStation($station, $podcast_id); if (null === $podcast) { throw new RuntimeException('Podcast not found.'); } $parsedBody = (array)$request->getParsedBody(); $record = $this->editRecord( $parsedBody, new Entity\PodcastEpisode($podcast) ); if (!empty($parsedBody['artwork_file'])) { $artwork = UploadedFile::fromArray($parsedBody['artwork_file'], $station->getRadioTempDir()); $this->episodeRepository->writeEpisodeArt( $record, $artwork->readAndDeleteUploadedFile() ); $this->em->persist($record); $this->em->flush(); } if (!empty($parsedBody['media_file'])) { $media = UploadedFile::fromArray($parsedBody['media_file'], $station->getRadioTempDir()); $this->episodeRepository->uploadMedia( $record, $media->getClientFilename(), $media->getUploadedPath() ); } return $response->withJson($this->viewRecord($record, $request)); } public function editAction( ServerRequest $request, Response $response, string $episode_id ): ResponseInterface { $podcast = $this->getRecord($request->getStation(), $episode_id); if ($podcast === null) { return $response->withStatus(404) ->withJson(Entity\Api\Error::notFound()); } $this->editRecord((array)$request->getParsedBody(), $podcast); return $response->withJson(Entity\Api\Status::updated()); } public function deleteAction( ServerRequest $request, Response $response, string $episode_id ): ResponseInterface { $station = $request->getStation(); $record = $this->getRecord($station, $episode_id); if (null === $record) { return $response->withStatus(404) ->withJson(Entity\Api\Error::notFound()); } $fsStation = new StationFilesystems($station); $this->episodeRepository->delete($record, $fsStation->getPodcastsFilesystem()); return $response->withJson(Entity\Api\Status::deleted()); } /** * @param Entity\Station $station * @param string $id * * @return Entity\PodcastEpisode|null */ protected function getRecord(Entity\Station $station, string $id): ?object { return $this->episodeRepository->fetchEpisodeForStation($station, $id); } /** * @inheritDoc */ protected function viewRecord(object $record, ServerRequest $request): mixed { if (!($record instanceof Entity\PodcastEpisode)) { throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass)); } $isInternal = ('true' === $request->getParam('internal', 'false')); $router = $request->getRouter(); $return = new Entity\Api\PodcastEpisode(); $return->id = $record->getId(); $return->title = $record->getTitle(); $return->description = $record->getDescription(); $return->explicit = $record->getExplicit(); $return->publish_at = $record->getPublishAt(); $mediaRow = $record->getMedia(); $return->has_media = ($mediaRow instanceof Entity\PodcastMedia); if ($mediaRow instanceof Entity\PodcastMedia) { $media = new Entity\Api\PodcastMedia(); $media->id = $mediaRow->getId(); $media->original_name = $mediaRow->getOriginalName(); $media->length = $mediaRow->getLength(); $media->length_text = $mediaRow->getLengthText(); $media->path = $mediaRow->getPath(); $return->has_media = true; $return->media = $media; } else { $return->has_media = false; $return->media = new Entity\Api\PodcastMedia(); } $return->art_updated_at = $record->getArtUpdatedAt(); $return->has_custom_art = (0 !== $return->art_updated_at); $return->art = (string)$router->fromHere( route_name: 'api:stations:podcast:episode:art', route_params: ['episode_id' => $record->getId() . '|' . $record->getArtUpdatedAt()], absolute: true ); $return->links = [ 'self' => (string)$router->fromHere( route_name: $this->resourceRouteName, route_params: ['episode_id' => $record->getId()], absolute: !$isInternal ), 'public' => (string)$router->fromHere( route_name: 'public:podcast:episode', route_params: ['episode_id' => $record->getId()], absolute: !$isInternal ), 'download' => (string)$router->fromHere( route_name: 'api:stations:podcast:episode:download', route_params: ['episode_id' => $record->getId()], absolute: !$isInternal ), ]; $acl = $request->getAcl(); $station = $request->getStation(); if ($acl->isAllowed(Acl::STATION_PODCASTS, $station)) { $return->links['art'] = (string)$router->fromHere( route_name: 'api:stations:podcast:episode:art-internal', route_params: ['episode_id' => $record->getId()], absolute: !$isInternal ); $return->links['media'] = (string)$router->fromHere( route_name: 'api:stations:podcast:episode:media-internal', route_params: ['episode_id' => $record->getId()], absolute: !$isInternal ); } return $return; } }