131 lines
3.9 KiB
PHP
131 lines
3.9 KiB
PHP
|
<?php
|
||
|
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
namespace OCA\RePod\Core\EpisodeAction;
|
||
|
|
||
|
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
||
|
use OCA\RePod\Service\UserService;
|
||
|
|
||
|
class EpisodeActionReader
|
||
|
{
|
||
|
public function __construct(
|
||
|
private EpisodeActionRepository $episodeActionRepository,
|
||
|
private UserService $userService
|
||
|
) {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* https://github.com/pbek/nextcloud-nextpod/blob/main/lib/Core/EpisodeAction/EpisodeActionExtraData.php#L119.
|
||
|
*
|
||
|
* @throws \Exception if the XML data could not be parsed
|
||
|
*/
|
||
|
public function parseRssXml(string $xmlString, string $episodeUrl, ?int $fetchedAtUnix = null): array
|
||
|
{
|
||
|
$episodes = [];
|
||
|
$xml = new \SimpleXMLElement($xmlString);
|
||
|
$channel = $xml->channel;
|
||
|
$episodeName = null;
|
||
|
$episodeLink = null;
|
||
|
$episodeImage = null;
|
||
|
$episodeDescription = null;
|
||
|
$episodeUrlPath = parse_url($episodeUrl, PHP_URL_PATH);
|
||
|
|
||
|
// Find episode by url and add data for it
|
||
|
/** @var \SimpleXMLElement $item */
|
||
|
foreach ($channel->item as $item) {
|
||
|
$url = (string) $item->enclosure['url'];
|
||
|
|
||
|
// First try to match the url directly
|
||
|
if (false === strpos($episodeUrl, $url)) {
|
||
|
// Then try to match the path only
|
||
|
// The podcast http://feeds.feedburner.com/abcradio/10percenthappier has a "rss_browser" query parameter
|
||
|
// for every item that changed all the time, so we can't match the full url
|
||
|
$path = parse_url($url, PHP_URL_PATH);
|
||
|
|
||
|
if ($episodeUrlPath !== $path) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get episode action
|
||
|
$episodeAction = $this->episodeActionRepository->findByEpisodeUrl($url, $this->userService->getUserUID());
|
||
|
|
||
|
// Get episode name
|
||
|
$episodeName = $this->stringOrNull($item->title);
|
||
|
|
||
|
// Get episode link
|
||
|
$episodeLink = $this->stringOrNull($item->link);
|
||
|
|
||
|
// Get episode image
|
||
|
$episodeImageChildren = $item->children('http://www.itunes.com/dtds/podcast-1.0.dtd');
|
||
|
if ($episodeImageChildren) {
|
||
|
$episodeImageAttributes = (array) $episodeImageChildren->image->attributes();
|
||
|
$episodeImage = $this->stringOrNull(array_key_exists('href', $episodeImageAttributes) ? (string) $episodeImageAttributes['href'] : '');
|
||
|
$iTunesChildren = $item->children('itunes', true);
|
||
|
|
||
|
if ($iTunesChildren && !$episodeImage) {
|
||
|
$episodeImage = $this->stringOrNull((string) $iTunesChildren->image['href']);
|
||
|
}
|
||
|
|
||
|
if (!$episodeImage) {
|
||
|
$episodeImage = $this->stringOrNull($channel->image->url);
|
||
|
}
|
||
|
|
||
|
if ($iTunesChildren && !$episodeImage) {
|
||
|
$episodeImage = $this->stringOrNull((string) $iTunesChildren->image['href']);
|
||
|
}
|
||
|
|
||
|
if (!$episodeImage) {
|
||
|
$channelImageChildren = $channel->children('http://www.itunes.com/dtds/podcast-1.0.dtd');
|
||
|
if ($channelImageChildren) {
|
||
|
$episodeImageAttributes = (array) $channelImageChildren->image->attributes();
|
||
|
$episodeImage = $this->stringOrNull(array_key_exists('href', $episodeImageAttributes) ? (string) $episodeImageAttributes['href'] : '');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!$episodeImage) {
|
||
|
preg_match('/<itunes:image\s+href="([^"]+)"/', $xmlString, $matches);
|
||
|
$episodeImage = $this->stringOrNull($matches[1]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get episode description
|
||
|
$episodeContentChildren = $item->children('content', true);
|
||
|
|
||
|
if ($episodeContentChildren) {
|
||
|
$episodeDescription = $this->stringOrNull($episodeContentChildren->encoded);
|
||
|
}
|
||
|
|
||
|
if (!$episodeDescription) {
|
||
|
$episodeDescription = $this->stringOrNull($item->description);
|
||
|
}
|
||
|
|
||
|
// Open links in new browser window/tab
|
||
|
$episodeDescription = str_replace('<a ', '<a class="description-link" target="_blank" ', $episodeDescription ?? '');
|
||
|
|
||
|
$episodes[] = new EpisodeActionExtraData(
|
||
|
$episodeUrl,
|
||
|
$this->stringOrNull($channel->title),
|
||
|
$episodeName,
|
||
|
$episodeLink,
|
||
|
$episodeImage,
|
||
|
$episodeDescription,
|
||
|
$fetchedAtUnix ?? (new \DateTime())->getTimestamp(),
|
||
|
$episodeAction
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $episodes;
|
||
|
}
|
||
|
|
||
|
private function stringOrNull(mixed $value): ?string
|
||
|
{
|
||
|
if ($value) {
|
||
|
return (string) $value;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
}
|