Fixes #7009 -- Add no-op writer to RSS feed to register Podcast namespace for PSP-1 compliance.

This commit is contained in:
Buster Neece 2024-03-22 10:31:00 -05:00
parent b144860d63
commit c33fd9f30c
No known key found for this signature in database
2 changed files with 68 additions and 40 deletions

View File

@ -13,6 +13,7 @@ use App\Entity\PodcastEpisode;
use App\Exception\NotFoundException; use App\Exception\NotFoundException;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use App\Rss\PodcastNamespaceWriter;
use DateTime; use DateTime;
use MarcW\RssWriter\Extension\Atom\AtomLink; use MarcW\RssWriter\Extension\Atom\AtomLink;
use MarcW\RssWriter\Extension\Atom\AtomWriter; use MarcW\RssWriter\Extension\Atom\AtomWriter;
@ -54,14 +55,6 @@ final class PodcastFeedAction implements SingleActionInterface
$podcast = $request->getPodcast(); $podcast = $request->getPodcast();
// Create RSS Writer
$rssWriter = new RssWriter(null, [], true);
$rssWriter->registerWriter(new CoreWriter());
$rssWriter->registerWriter(new ItunesWriter());
$rssWriter->registerWriter(new SyWriter());
$rssWriter->registerWriter(new SlashWriter());
$rssWriter->registerWriter(new AtomWriter());
$channel = new RssChannel(); $channel = new RssChannel();
$channel->setTtl(5); $channel->setTtl(5);
$channel->setLastBuildDate(new DateTime()); $channel->setLastBuildDate(new DateTime());
@ -98,8 +91,6 @@ final class PodcastFeedAction implements SingleActionInterface
$channel->setImage($rssImage); $channel->setImage($rssImage);
// Iterate through episodes. // Iterate through episodes.
$rssItems = [];
$hasPublishedEpisode = false; $hasPublishedEpisode = false;
$hasExplicitEpisode = false; $hasExplicitEpisode = false;
@ -114,42 +105,13 @@ final class PodcastFeedAction implements SingleActionInterface
$hasExplicitEpisode = true; $hasExplicitEpisode = true;
} }
$episodeApi = $this->episodeApiGenerator->__invoke($episode, $request); $channel->addItem($this->buildItemForEpisode($episode, $request));
$rssItem = new RssItem();
$rssItem->setGuid((new RssGuid())->setGuid($episodeApi->id));
$rssItem->setTitle($episodeApi->title);
$rssItem->setDescription($episodeApi->description);
$rssItem->setLink($episodeApi->link ?? $episodeApi->links['self']);
$rssItem->setPubDate((new DateTime())->setTimestamp($episode->getPublishAt()));
$rssEnclosure = new RssEnclosure();
$rssEnclosure->setUrl($episodeApi->links['download']);
$podcastMedia = $episode->getMedia();
if (null !== $podcastMedia) {
$rssEnclosure->setType($podcastMedia->getMimeType());
$rssEnclosure->setLength($podcastMedia->getLength());
}
$rssItem->setEnclosure($rssEnclosure);
$rssItem->addExtension(
(new ItunesItem())
->setExplicit($episode->getExplicit())
->setImage($episodeApi->art)
);
$rssItems[] = $rssItem;
} }
if (!$hasPublishedEpisode) { if (!$hasPublishedEpisode) {
throw NotFoundException::podcast(); throw NotFoundException::podcast();
} }
$channel->setItems($rssItems);
$itunesChannel = new ItunesChannel(); $itunesChannel = new ItunesChannel();
$itunesChannel->setExplicit($hasExplicitEpisode); $itunesChannel->setExplicit($hasExplicitEpisode);
$itunesChannel->setImage($rssImage->getUrl()); $itunesChannel->setImage($rssImage->getUrl());
@ -179,6 +141,15 @@ final class PodcastFeedAction implements SingleActionInterface
->setType('application/rss+xml') ->setType('application/rss+xml')
); );
$rssWriter = new RssWriter(null, [
new CoreWriter(),
new ItunesWriter(),
new SyWriter(),
new SlashWriter(),
new AtomWriter(),
new PodcastNamespaceWriter(),
], true);
$response->getBody()->write( $response->getBody()->write(
$rssWriter->writeChannel($channel) $rssWriter->writeChannel($channel)
); );
@ -188,6 +159,38 @@ final class PodcastFeedAction implements SingleActionInterface
->withHeader('X-Robots-Tag', 'index, nofollow'); ->withHeader('X-Robots-Tag', 'index, nofollow');
} }
private function buildItemForEpisode(PodcastEpisode $episode, ServerRequest $request): RssItem
{
$episodeApi = $this->episodeApiGenerator->__invoke($episode, $request);
$rssItem = new RssItem();
$rssItem->setGuid((new RssGuid())->setGuid($episodeApi->id));
$rssItem->setTitle($episodeApi->title);
$rssItem->setDescription($episodeApi->description);
$rssItem->setLink($episodeApi->link ?? $episodeApi->links['self']);
$rssItem->setPubDate((new DateTime())->setTimestamp($episode->getPublishAt()));
$rssEnclosure = new RssEnclosure();
$rssEnclosure->setUrl($episodeApi->links['download']);
$podcastMedia = $episode->getMedia();
if (null !== $podcastMedia) {
$rssEnclosure->setType($podcastMedia->getMimeType());
$rssEnclosure->setLength($podcastMedia->getLength());
}
$rssItem->setEnclosure($rssEnclosure);
$rssItem->addExtension(
(new ItunesItem())
->setExplicit($episode->getExplicit())
->setImage($episodeApi->art)
);
return $rssItem;
}
private function buildItunesOwner(Podcast $podcast): ?ItunesOwner private function buildItunesOwner(Podcast $podcast): ?ItunesOwner
{ {
if (empty($podcast->getAuthor()) && empty($podcast->getEmail())) { if (empty($podcast->getAuthor()) && empty($podcast->getEmail())) {

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Rss;
use MarcW\RssWriter\WriterRegistererInterface;
/**
* Placeholder class to write the Podcast namespace for PSP-1 compliance.
*/
class PodcastNamespaceWriter implements WriterRegistererInterface
{
public function getRegisteredWriters(): array
{
return [];
}
public function getRegisteredNamespaces(): array
{
return [
'podcast' => 'https://podcastindex.org/namespace/1.0',
];
}
}