2020-06-26 14:34:52 +00:00
|
|
|
<?php
|
2020-08-04 11:25:22 +00:00
|
|
|
|
2021-06-08 09:52:11 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-06-26 14:34:52 +00:00
|
|
|
/**
|
2022-02-19 16:06:11 +00:00
|
|
|
* @copyright 2020 Ad Aures
|
2020-06-26 14:34:52 +00:00
|
|
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
|
|
|
* @link https://castopod.org/
|
|
|
|
*/
|
2021-05-19 16:35:13 +00:00
|
|
|
use App\Entities\Category;
|
2023-04-14 11:11:53 +00:00
|
|
|
use App\Entities\Location;
|
2021-05-19 16:35:13 +00:00
|
|
|
use App\Entities\Podcast;
|
2023-04-14 11:11:53 +00:00
|
|
|
|
2020-08-04 11:25:22 +00:00
|
|
|
use App\Libraries\SimpleRSSElement;
|
2020-06-26 14:34:52 +00:00
|
|
|
use CodeIgniter\I18n\Time;
|
2020-11-24 20:18:08 +00:00
|
|
|
use Config\Mimes;
|
2023-04-14 11:11:53 +00:00
|
|
|
use Modules\Media\Entities\Chapters;
|
|
|
|
use Modules\Media\Entities\Transcript;
|
2022-09-28 15:02:09 +00:00
|
|
|
use Modules\PremiumPodcasts\Entities\Subscription;
|
2023-08-27 13:26:06 +00:00
|
|
|
use Modules\WebSub\Config\WebSub;
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2021-05-19 16:35:13 +00:00
|
|
|
if (! function_exists('get_rss_feed')) {
|
2021-05-06 14:00:48 +00:00
|
|
|
/**
|
|
|
|
* Generates the rss feed for a given podcast entity
|
|
|
|
*
|
2021-05-12 14:00:25 +00:00
|
|
|
* @param string $serviceSlug The name of the service that fetches the RSS feed for future reference when the audio file is eventually downloaded
|
2021-05-06 14:00:48 +00:00
|
|
|
* @return string rss feed as xml
|
|
|
|
*/
|
2022-09-28 15:02:09 +00:00
|
|
|
function get_rss_feed(
|
|
|
|
Podcast $podcast,
|
|
|
|
string $serviceSlug = '',
|
|
|
|
Subscription $subscription = null,
|
|
|
|
string $token = null
|
|
|
|
): string {
|
2021-05-06 14:00:48 +00:00
|
|
|
$episodes = $podcast->episodes;
|
|
|
|
|
2021-05-17 17:11:23 +00:00
|
|
|
$itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2023-07-29 08:04:19 +00:00
|
|
|
$podcastNamespace = 'https://podcastindex.org/namespace/1.0';
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2022-03-23 09:48:22 +00:00
|
|
|
$atomNamespace = 'http://www.w3.org/2005/Atom';
|
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
$rss = new SimpleRSSElement(
|
2022-03-23 09:48:22 +00:00
|
|
|
"<?xml version='1.0' encoding='utf-8'?><rss version='2.0' xmlns:itunes='{$itunesNamespace}' xmlns:podcast='{$podcastNamespace}' xmlns:atom='{$atomNamespace}' xmlns:content='http://purl.org/rss/1.0/modules/content/'></rss>"
|
2020-10-06 15:39:27 +00:00
|
|
|
);
|
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
$channel = $rss->addChild('channel');
|
2020-11-18 17:17:56 +00:00
|
|
|
|
2022-03-23 09:48:22 +00:00
|
|
|
$atomLink = $channel->addChild('link', null, $atomNamespace);
|
2021-05-17 17:11:23 +00:00
|
|
|
$atomLink->addAttribute('href', $podcast->feed_url);
|
|
|
|
$atomLink->addAttribute('rel', 'self');
|
|
|
|
$atomLink->addAttribute('type', 'application/rss+xml');
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2022-03-15 16:47:35 +00:00
|
|
|
// websub: add links to hubs defined in config
|
2023-11-15 16:17:43 +00:00
|
|
|
$websubHubs = config('WebSub')
|
2022-03-15 16:47:35 +00:00
|
|
|
->hubs;
|
|
|
|
foreach ($websubHubs as $websubHub) {
|
2022-03-23 09:48:22 +00:00
|
|
|
$atomLinkHub = $channel->addChild('link', null, $atomNamespace);
|
2022-03-15 16:47:35 +00:00
|
|
|
$atomLinkHub->addAttribute('href', $websubHub);
|
|
|
|
$atomLinkHub->addAttribute('rel', 'hub');
|
|
|
|
$atomLinkHub->addAttribute('type', 'application/rss+xml');
|
|
|
|
}
|
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($podcast->new_feed_url !== null) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$channel->addChild('new-feed-url', $podcast->new_feed_url, $itunesNamespace);
|
2020-11-18 17:17:56 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
// the last build date corresponds to the creation of the feed.xml cache
|
2021-06-08 09:52:11 +00:00
|
|
|
$channel->addChild('lastBuildDate', (new Time('now'))->format(DATE_RFC1123));
|
2022-02-19 16:06:11 +00:00
|
|
|
$channel->addChild('generator', 'Castopod - https://castopod.org/');
|
2021-05-06 14:00:48 +00:00
|
|
|
$channel->addChild('docs', 'https://cyber.harvard.edu/rss/rss.html');
|
2021-02-10 16:20:01 +00:00
|
|
|
|
2021-06-21 11:58:43 +00:00
|
|
|
$channel->addChild('guid', $podcast->guid, $podcastNamespace);
|
2021-07-29 10:46:51 +00:00
|
|
|
$channel->addChild('title', $podcast->title, null, false);
|
2021-05-06 14:00:48 +00:00
|
|
|
$channel->addChildWithCDATA('description', $podcast->description_html);
|
2020-06-26 14:34:52 +00:00
|
|
|
|
2021-05-17 17:11:23 +00:00
|
|
|
$itunesImage = $channel->addChild('image', null, $itunesNamespace);
|
2021-03-19 16:12:36 +00:00
|
|
|
|
2021-11-01 17:12:03 +00:00
|
|
|
$itunesImage->addAttribute('href', $podcast->cover->feed_url);
|
2020-06-26 14:34:52 +00:00
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
$channel->addChild('language', $podcast->language_code);
|
2023-04-14 11:11:53 +00:00
|
|
|
if ($podcast->location instanceof Location) {
|
2022-03-04 14:33:48 +00:00
|
|
|
$locationElement = $channel->addChild('location', $podcast->location->name, $podcastNamespace);
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($podcast->location->geo !== null) {
|
|
|
|
$locationElement->addAttribute('geo', $podcast->location->geo);
|
2020-12-23 14:11:38 +00:00
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-17 17:11:23 +00:00
|
|
|
if ($podcast->location->osm !== null) {
|
|
|
|
$locationElement->addAttribute('osm', $podcast->location->osm);
|
2020-12-23 14:11:38 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($podcast->payment_pointer !== null) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$valueElement = $channel->addChild('value', null, $podcastNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
$valueElement->addAttribute('type', 'webmonetization');
|
2022-11-07 16:00:40 +00:00
|
|
|
$valueElement->addAttribute('method', 'ILP');
|
2021-06-08 09:52:11 +00:00
|
|
|
$recipientElement = $valueElement->addChild('valueRecipient', null, $podcastNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
$recipientElement->addAttribute('name', $podcast->owner_name);
|
2022-11-07 16:00:40 +00:00
|
|
|
$recipientElement->addAttribute('type', 'paymentpointer');
|
2021-06-08 09:52:11 +00:00
|
|
|
$recipientElement->addAttribute('address', $podcast->payment_pointer);
|
2021-05-12 14:00:25 +00:00
|
|
|
$recipientElement->addAttribute('split', '100');
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
$channel
|
2021-06-09 12:40:22 +00:00
|
|
|
->addChild('locked', $podcast->is_locked ? 'yes' : 'no', $podcastNamespace)
|
2021-05-06 14:00:48 +00:00
|
|
|
->addAttribute('owner', $podcast->owner_email);
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($podcast->imported_feed_url !== null) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$channel->addChild('previousUrl', $podcast->imported_feed_url, $podcastNamespace);
|
2020-11-24 20:18:08 +00:00
|
|
|
}
|
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
foreach ($podcast->podcasting_platforms as $podcastingPlatform) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$podcastingPlatformElement = $channel->addChild('id', null, $podcastNamespace);
|
|
|
|
$podcastingPlatformElement->addAttribute('platform', $podcastingPlatform->slug);
|
2022-01-04 16:37:59 +00:00
|
|
|
if ($podcastingPlatform->account_id !== null) {
|
|
|
|
$podcastingPlatformElement->addAttribute('id', $podcastingPlatform->account_id);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($podcastingPlatform->link_url !== null) {
|
2022-03-04 14:33:48 +00:00
|
|
|
$podcastingPlatformElement->addAttribute('url', $podcastingPlatform->link_url);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 16:15:42 +00:00
|
|
|
$castopodSocialElement = $channel->addChild('social', null, $podcastNamespace);
|
|
|
|
$castopodSocialElement->addAttribute('priority', '1');
|
|
|
|
$castopodSocialElement->addAttribute('platform', 'castopod');
|
|
|
|
$castopodSocialElement->addAttribute('protocol', 'activitypub');
|
|
|
|
$castopodSocialElement->addAttribute('accountId', "@{$podcast->actor->username}@{$podcast->actor->domain}");
|
|
|
|
$castopodSocialElement->addAttribute('accountUrl', $podcast->link);
|
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
foreach ($podcast->social_platforms as $socialPlatform) {
|
2023-02-22 16:29:45 +00:00
|
|
|
$socialElement = $channel->addChild('social', null, $podcastNamespace);
|
2022-03-03 16:15:42 +00:00
|
|
|
$socialElement->addAttribute('priority', '2');
|
|
|
|
$socialElement->addAttribute('platform', $socialPlatform->slug);
|
|
|
|
|
|
|
|
// TODO: get activitypub info somewhere else
|
|
|
|
if (in_array(
|
|
|
|
$socialPlatform->slug,
|
|
|
|
['mastodon', 'peertube', 'funkwhale', 'misskey', 'mobilizon', 'pixelfed', 'plume', 'writefreely'],
|
|
|
|
true
|
|
|
|
)) {
|
|
|
|
$socialElement->addAttribute('protocol', 'activitypub');
|
|
|
|
} else {
|
|
|
|
$socialElement->addAttribute('protocol', $socialPlatform->slug);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($socialPlatform->account_id !== null) {
|
|
|
|
$socialElement->addAttribute('accountId', esc($socialPlatform->account_id));
|
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($socialPlatform->link_url !== null) {
|
2022-03-03 16:15:42 +00:00
|
|
|
$socialElement->addAttribute('accountUrl', esc($socialPlatform->link_url));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($socialPlatform->slug === 'mastodon') {
|
|
|
|
$socialSignUpelement = $socialElement->addChild('socialSignUp', null, $podcastNamespace);
|
|
|
|
$socialSignUpelement->addAttribute('priority', '1');
|
|
|
|
$socialSignUpelement->addAttribute(
|
|
|
|
'homeUrl',
|
|
|
|
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
|
|
|
$socialPlatform->link_url,
|
|
|
|
PHP_URL_HOST
|
|
|
|
) . '/public'
|
|
|
|
);
|
|
|
|
$socialSignUpelement->addAttribute(
|
|
|
|
'signUpUrl',
|
|
|
|
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
|
|
|
$socialPlatform->link_url,
|
|
|
|
PHP_URL_HOST
|
|
|
|
) . '/auth/sign_up'
|
|
|
|
);
|
|
|
|
$castopodSocialSignUpelement = $castopodSocialElement->addChild(
|
|
|
|
'socialSignUp',
|
|
|
|
null,
|
|
|
|
$podcastNamespace
|
|
|
|
);
|
|
|
|
$castopodSocialSignUpelement->addAttribute('priority', '1');
|
|
|
|
$castopodSocialSignUpelement->addAttribute(
|
|
|
|
'homeUrl',
|
|
|
|
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
|
|
|
$socialPlatform->link_url,
|
|
|
|
PHP_URL_HOST
|
|
|
|
) . '/public'
|
|
|
|
);
|
|
|
|
$castopodSocialSignUpelement->addAttribute(
|
|
|
|
'signUpUrl',
|
|
|
|
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
|
|
|
$socialPlatform->link_url,
|
|
|
|
PHP_URL_HOST
|
|
|
|
) . '/auth/sign_up'
|
|
|
|
);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
2020-11-24 20:18:08 +00:00
|
|
|
}
|
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
foreach ($podcast->funding_platforms as $fundingPlatform) {
|
2021-05-06 14:00:48 +00:00
|
|
|
$fundingPlatformElement = $channel->addChild(
|
|
|
|
'funding',
|
2022-01-04 16:37:59 +00:00
|
|
|
$fundingPlatform->account_id,
|
2021-05-17 17:11:23 +00:00
|
|
|
$podcastNamespace,
|
2020-12-07 20:13:46 +00:00
|
|
|
);
|
2021-06-08 09:52:11 +00:00
|
|
|
$fundingPlatformElement->addAttribute('platform', $fundingPlatform->slug);
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($fundingPlatform->link_url !== null) {
|
2022-03-04 14:33:48 +00:00
|
|
|
$fundingPlatformElement->addAttribute('url', $fundingPlatform->link_url);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
2020-12-07 20:13:46 +00:00
|
|
|
}
|
|
|
|
|
2021-05-18 17:16:36 +00:00
|
|
|
foreach ($podcast->persons as $person) {
|
|
|
|
foreach ($person->roles as $role) {
|
2023-02-22 16:29:45 +00:00
|
|
|
$personElement = $channel->addChild('person', $person->full_name, $podcastNamespace);
|
2021-05-18 17:16:36 +00:00
|
|
|
|
2023-04-13 11:45:03 +00:00
|
|
|
$personElement->addAttribute('img', get_avatar_url($person, 'medium'));
|
2021-05-12 14:00:25 +00:00
|
|
|
|
2021-05-18 17:16:36 +00:00
|
|
|
if ($person->information_url !== null) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$personElement->addAttribute('href', $person->information_url);
|
2021-05-18 17:16:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$personElement->addAttribute(
|
2021-02-10 16:20:01 +00:00
|
|
|
'role',
|
2022-03-04 14:33:48 +00:00
|
|
|
lang("PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", [], 'en'),
|
2021-02-10 16:20:01 +00:00
|
|
|
);
|
2021-05-12 14:00:25 +00:00
|
|
|
|
2021-05-18 17:16:36 +00:00
|
|
|
$personElement->addAttribute(
|
2021-02-10 16:20:01 +00:00
|
|
|
'group',
|
2022-03-04 14:33:48 +00:00
|
|
|
lang("PersonsTaxonomy.persons.{$role->group}.label", [], 'en'),
|
2021-02-10 16:20:01 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
// set main category first, then other categories as apple
|
|
|
|
add_category_tag($channel, $podcast->category);
|
|
|
|
foreach ($podcast->other_categories as $other_category) {
|
|
|
|
add_category_tag($channel, $other_category);
|
|
|
|
}
|
|
|
|
|
|
|
|
$channel->addChild(
|
|
|
|
'explicit',
|
|
|
|
$podcast->parental_advisory === 'explicit' ? 'true' : 'false',
|
2021-05-17 17:11:23 +00:00
|
|
|
$itunesNamespace,
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
$channel->addChild(
|
|
|
|
'author',
|
|
|
|
$podcast->publisher ? $podcast->publisher : $podcast->owner_name,
|
2021-05-17 17:11:23 +00:00
|
|
|
$itunesNamespace,
|
2022-04-06 08:42:06 +00:00
|
|
|
false
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
|
|
|
$channel->addChild('link', $podcast->link);
|
|
|
|
|
2021-05-17 17:11:23 +00:00
|
|
|
$owner = $channel->addChild('owner', null, $itunesNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2022-04-06 08:42:06 +00:00
|
|
|
$owner->addChild('name', $podcast->owner_name, $itunesNamespace, false);
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2021-05-17 17:11:23 +00:00
|
|
|
$owner->addChild('email', $podcast->owner_email, $itunesNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2021-05-17 17:11:23 +00:00
|
|
|
$channel->addChild('type', $podcast->type, $itunesNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
$podcast->copyright &&
|
|
|
|
$channel->addChild('copyright', $podcast->copyright);
|
2021-08-11 15:47:23 +00:00
|
|
|
if ($podcast->is_blocked) {
|
2021-05-17 17:11:23 +00:00
|
|
|
$channel->addChild('block', 'Yes', $itunesNamespace);
|
2021-08-11 15:47:23 +00:00
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-08-11 15:47:23 +00:00
|
|
|
if ($podcast->is_completed) {
|
2021-05-17 17:11:23 +00:00
|
|
|
$channel->addChild('complete', 'Yes', $itunesNamespace);
|
2021-08-11 15:47:23 +00:00
|
|
|
}
|
2021-05-06 14:00:48 +00:00
|
|
|
|
|
|
|
$image = $channel->addChild('image');
|
2021-11-01 17:12:03 +00:00
|
|
|
$image->addChild('url', $podcast->cover->feed_url);
|
2021-07-29 10:46:51 +00:00
|
|
|
$image->addChild('title', $podcast->title, null, false);
|
2021-05-06 14:00:48 +00:00
|
|
|
$image->addChild('link', $podcast->link);
|
2021-03-19 16:12:36 +00:00
|
|
|
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($podcast->custom_rss !== null) {
|
2021-05-19 16:35:13 +00:00
|
|
|
array_to_rss([
|
|
|
|
'elements' => $podcast->custom_rss,
|
2021-06-08 09:52:11 +00:00
|
|
|
], $channel);
|
2021-03-19 16:12:36 +00:00
|
|
|
}
|
2020-06-26 14:34:52 +00:00
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
foreach ($episodes as $episode) {
|
2023-04-14 11:11:53 +00:00
|
|
|
if ($episode->is_premium && ! $subscription instanceof Subscription) {
|
2022-09-28 15:02:09 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
$item = $channel->addChild('item');
|
2021-07-29 10:46:51 +00:00
|
|
|
$item->addChild('title', $episode->title, null, false);
|
2021-05-06 14:00:48 +00:00
|
|
|
$enclosure = $item->addChild('enclosure');
|
|
|
|
|
2022-09-28 15:02:09 +00:00
|
|
|
$enclosureParams = implode('&', array_filter([
|
|
|
|
$episode->is_premium ? 'token=' . $token : null,
|
|
|
|
$serviceSlug !== '' ? '_from=' . urlencode($serviceSlug) : null,
|
|
|
|
]));
|
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
$enclosure->addAttribute(
|
|
|
|
'url',
|
2022-12-09 15:04:42 +00:00
|
|
|
$episode->audio_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams),
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
2021-12-14 16:41:10 +00:00
|
|
|
$enclosure->addAttribute('length', (string) $episode->audio->file_size);
|
2021-12-17 17:14:37 +00:00
|
|
|
$enclosure->addAttribute('type', $episode->audio->file_mimetype);
|
2021-05-06 14:00:48 +00:00
|
|
|
|
|
|
|
$item->addChild('guid', $episode->guid);
|
2021-06-08 09:52:11 +00:00
|
|
|
$item->addChild('pubDate', $episode->published_at->format(DATE_RFC1123));
|
2023-04-14 11:11:53 +00:00
|
|
|
if ($episode->location instanceof Location) {
|
2023-02-22 16:29:45 +00:00
|
|
|
$locationElement = $item->addChild('location', $episode->location->name, $podcastNamespace);
|
2021-05-12 14:00:25 +00:00
|
|
|
if ($episode->location->geo !== null) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$locationElement->addAttribute('geo', $episode->location->geo);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-17 17:11:23 +00:00
|
|
|
if ($episode->location->osm !== null) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$locationElement->addAttribute('osm', $episode->location->osm);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-06-08 09:52:11 +00:00
|
|
|
$item->addChildWithCDATA('description', $episode->getDescriptionHtml($serviceSlug));
|
2022-07-02 11:03:38 +00:00
|
|
|
$item->addChild('duration', (string) round($episode->audio->duration), $itunesNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
$item->addChild('link', $episode->link);
|
2021-06-08 09:52:11 +00:00
|
|
|
$episodeItunesImage = $item->addChild('image', null, $itunesNamespace);
|
2021-11-01 17:12:03 +00:00
|
|
|
$episodeItunesImage->addAttribute('href', $episode->cover->feed_url);
|
2021-05-06 14:00:48 +00:00
|
|
|
|
|
|
|
$episode->parental_advisory &&
|
|
|
|
$item->addChild(
|
|
|
|
'explicit',
|
|
|
|
$episode->parental_advisory === 'explicit'
|
|
|
|
? 'true'
|
|
|
|
: 'false',
|
2021-05-17 17:11:23 +00:00
|
|
|
$itunesNamespace,
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
$episode->number &&
|
2021-10-05 13:23:23 +00:00
|
|
|
$item->addChild('episode', (string) $episode->number, $itunesNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
$episode->season_number &&
|
2021-10-05 13:23:23 +00:00
|
|
|
$item->addChild('season', (string) $episode->season_number, $itunesNamespace);
|
2021-05-17 17:11:23 +00:00
|
|
|
$item->addChild('episodeType', $episode->type, $itunesNamespace);
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2021-07-24 08:49:16 +00:00
|
|
|
// add link to episode comments as podcast-activity format
|
|
|
|
$comments = $item->addChild('comments', null, $podcastNamespace);
|
2021-07-26 13:10:46 +00:00
|
|
|
$comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug));
|
2021-07-24 08:49:16 +00:00
|
|
|
$comments->addAttribute('contentType', 'application/podcast-activity+json');
|
|
|
|
|
2022-03-03 16:15:42 +00:00
|
|
|
if ($episode->getPosts()) {
|
2022-03-30 12:51:18 +00:00
|
|
|
$socialInteractUri = $episode->getPosts()[0]
|
2022-03-03 16:15:42 +00:00
|
|
|
->uri;
|
2022-03-30 12:51:18 +00:00
|
|
|
$socialInteractElement = $item->addChild('socialInteract', null, $podcastNamespace);
|
|
|
|
$socialInteractElement->addAttribute('uri', $socialInteractUri);
|
2022-03-03 16:15:42 +00:00
|
|
|
$socialInteractElement->addAttribute('priority', '1');
|
|
|
|
$socialInteractElement->addAttribute('platform', 'castopod');
|
|
|
|
$socialInteractElement->addAttribute('protocol', 'activitypub');
|
|
|
|
$socialInteractElement->addAttribute(
|
|
|
|
'accountId',
|
|
|
|
"@{$podcast->actor->username}@{$podcast->actor->domain}"
|
|
|
|
);
|
|
|
|
$socialInteractElement->addAttribute(
|
|
|
|
'pubDate',
|
|
|
|
$episode->getPosts()[0]
|
|
|
|
->published_at->format(DateTime::ISO8601)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-04-14 11:11:53 +00:00
|
|
|
if ($episode->transcript instanceof Transcript) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$transcriptElement = $item->addChild('transcript', null, $podcastNamespace);
|
2021-12-17 17:14:37 +00:00
|
|
|
$transcriptElement->addAttribute('url', $episode->transcript->file_url);
|
2021-05-06 14:00:48 +00:00
|
|
|
$transcriptElement->addAttribute(
|
|
|
|
'type',
|
2021-10-26 10:27:57 +00:00
|
|
|
Mimes::guessTypeFromExtension(
|
2021-12-17 17:14:37 +00:00
|
|
|
pathinfo($episode->transcript->file_url, PATHINFO_EXTENSION)
|
2021-10-26 10:27:57 +00:00
|
|
|
) ?? 'text/html',
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
2023-07-29 08:27:06 +00:00
|
|
|
// Castopod only allows for captions (SubRip files)
|
|
|
|
$transcriptElement->addAttribute('rel', 'captions');
|
|
|
|
// TODO: allow for multiple languages
|
2021-06-08 09:52:11 +00:00
|
|
|
$transcriptElement->addAttribute('language', $podcast->language_code);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
|
|
|
|
2023-04-14 11:11:53 +00:00
|
|
|
if ($episode->getChapters() instanceof Chapters) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$chaptersElement = $item->addChild('chapters', null, $podcastNamespace);
|
2021-12-17 17:14:37 +00:00
|
|
|
$chaptersElement->addAttribute('url', $episode->chapters->file_url);
|
2021-06-08 09:52:11 +00:00
|
|
|
$chaptersElement->addAttribute('type', 'application/json+chapters');
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 16:25:03 +00:00
|
|
|
foreach ($episode->soundbites as $soundbite) {
|
2021-12-14 16:41:10 +00:00
|
|
|
// TODO: differentiate video from soundbites?
|
2022-01-03 13:52:07 +00:00
|
|
|
$soundbiteElement = $item->addChild('soundbite', $soundbite->title, $podcastNamespace);
|
2022-12-02 15:32:27 +00:00
|
|
|
$soundbiteElement->addAttribute('startTime', (string) $soundbite->start_time);
|
2022-07-02 11:03:38 +00:00
|
|
|
$soundbiteElement->addAttribute('duration', (string) round($soundbite->duration, 3));
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
|
|
|
|
2021-05-18 17:16:36 +00:00
|
|
|
foreach ($episode->persons as $person) {
|
|
|
|
foreach ($person->roles as $role) {
|
2023-02-22 16:29:45 +00:00
|
|
|
$personElement = $item->addChild('person', esc($person->full_name), $podcastNamespace);
|
2021-05-18 17:16:36 +00:00
|
|
|
|
|
|
|
$personElement->addAttribute(
|
2021-05-06 14:00:48 +00:00
|
|
|
'role',
|
2023-02-22 16:29:45 +00:00
|
|
|
esc(lang("PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", [], 'en')),
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
2021-05-18 17:16:36 +00:00
|
|
|
|
|
|
|
$personElement->addAttribute(
|
2021-05-06 14:00:48 +00:00
|
|
|
'group',
|
2022-03-25 14:37:14 +00:00
|
|
|
esc(lang("PersonsTaxonomy.persons.{$role->group}.label", [], 'en')),
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
2021-05-18 17:16:36 +00:00
|
|
|
|
2023-04-13 11:45:03 +00:00
|
|
|
$personElement->addAttribute('img', get_avatar_url($person, 'medium'));
|
2021-05-18 17:16:36 +00:00
|
|
|
|
|
|
|
if ($person->information_url !== null) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$personElement->addAttribute('href', $person->information_url);
|
2021-05-18 17:16:36 +00:00
|
|
|
}
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-11 15:47:23 +00:00
|
|
|
if ($episode->is_blocked) {
|
2021-05-17 17:11:23 +00:00
|
|
|
$item->addChild('block', 'Yes', $itunesNamespace);
|
2021-08-11 15:47:23 +00:00
|
|
|
}
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2021-05-18 17:16:36 +00:00
|
|
|
if ($episode->custom_rss !== null) {
|
2021-05-19 16:35:13 +00:00
|
|
|
array_to_rss([
|
|
|
|
'elements' => $episode->custom_rss,
|
2021-06-08 09:52:11 +00:00
|
|
|
], $item);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rss->asXML();
|
|
|
|
}
|
2020-06-26 14:34:52 +00:00
|
|
|
}
|
2020-10-02 15:38:16 +00:00
|
|
|
|
2021-05-19 16:35:13 +00:00
|
|
|
if (! function_exists('add_category_tag')) {
|
2021-05-06 14:00:48 +00:00
|
|
|
/**
|
|
|
|
* Adds <itunes:category> and <category> tags to node for a given category
|
|
|
|
*/
|
|
|
|
function add_category_tag(SimpleXMLElement $node, Category $category): void
|
|
|
|
{
|
2021-05-17 17:11:23 +00:00
|
|
|
$itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2022-03-23 09:48:22 +00:00
|
|
|
$itunesCategory = $node->addChild('category', null, $itunesNamespace);
|
2021-05-17 17:11:23 +00:00
|
|
|
$itunesCategory->addAttribute(
|
2021-05-06 14:00:48 +00:00
|
|
|
'text',
|
2023-04-14 11:11:53 +00:00
|
|
|
$category->parent instanceof Category
|
2021-05-06 14:00:48 +00:00
|
|
|
? $category->parent->apple_category
|
|
|
|
: $category->apple_category,
|
2020-10-02 15:38:16 +00:00
|
|
|
);
|
2021-05-06 14:00:48 +00:00
|
|
|
|
2023-04-14 11:11:53 +00:00
|
|
|
if ($category->parent instanceof Category) {
|
2022-03-23 09:48:22 +00:00
|
|
|
$itunesCategoryChild = $itunesCategory->addChild('category', null, $itunesNamespace);
|
2021-06-08 09:52:11 +00:00
|
|
|
$itunesCategoryChild->addAttribute('text', $category->apple_category);
|
2021-05-06 14:00:48 +00:00
|
|
|
$node->addChild('category', $category->parent->apple_category);
|
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
$node->addChild('category', $category->apple_category);
|
2020-10-02 15:38:16 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-19 16:12:36 +00:00
|
|
|
|
2021-05-19 16:35:13 +00:00
|
|
|
if (! function_exists('rss_to_array')) {
|
2021-05-06 14:00:48 +00:00
|
|
|
/**
|
|
|
|
* Converts XML to array
|
|
|
|
*
|
2021-05-14 17:59:35 +00:00
|
|
|
* FIXME: param should be SimpleRSSElement
|
|
|
|
*
|
|
|
|
* @return array<string, mixed>
|
2021-05-06 14:00:48 +00:00
|
|
|
*/
|
2021-05-14 17:59:35 +00:00
|
|
|
function rss_to_array(SimpleXMLElement $rssNode): array
|
2021-05-06 14:00:48 +00:00
|
|
|
{
|
2023-07-29 08:04:19 +00:00
|
|
|
$nameSpaces = ['', 'http://www.itunes.com/dtds/podcast-1.0.dtd', 'https://podcastindex.org/namespace/1.0'];
|
2021-05-06 14:00:48 +00:00
|
|
|
$arrayNode = [];
|
2021-05-14 17:59:35 +00:00
|
|
|
$arrayNode['name'] = $rssNode->getName();
|
|
|
|
$arrayNode['namespace'] = $rssNode->getNamespaces(false);
|
|
|
|
foreach ($rssNode->attributes() as $key => $value) {
|
2021-03-19 16:12:36 +00:00
|
|
|
$arrayNode['attributes'][$key] = (string) $value;
|
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-14 17:59:35 +00:00
|
|
|
$textcontent = trim((string) $rssNode);
|
2021-05-06 14:00:48 +00:00
|
|
|
if (strlen($textcontent) > 0) {
|
|
|
|
$arrayNode['content'] = $textcontent;
|
2021-03-19 16:12:36 +00:00
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
foreach ($nameSpaces as $currentNameSpace) {
|
2021-05-14 17:59:35 +00:00
|
|
|
foreach ($rssNode->children($currentNameSpace) as $childXmlNode) {
|
2021-05-06 14:00:48 +00:00
|
|
|
$arrayNode['elements'][] = rss_to_array($childXmlNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $arrayNode;
|
2021-03-19 16:12:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 16:35:13 +00:00
|
|
|
if (! function_exists('array_to_rss')) {
|
2021-05-06 14:00:48 +00:00
|
|
|
/**
|
|
|
|
* Inserts array (converted to XML node) in XML node
|
|
|
|
*
|
2021-05-14 17:59:35 +00:00
|
|
|
* @param array<string, mixed> $arrayNode
|
2021-05-06 14:00:48 +00:00
|
|
|
* @param SimpleRSSElement $xmlNode The XML parent node where this arrayNode should be attached
|
|
|
|
*/
|
2021-05-19 16:35:13 +00:00
|
|
|
function array_to_rss(array $arrayNode, SimpleRSSElement &$xmlNode): SimpleRSSElement
|
|
|
|
{
|
2021-05-06 14:00:48 +00:00
|
|
|
if (array_key_exists('elements', $arrayNode)) {
|
|
|
|
foreach ($arrayNode['elements'] as $childArrayNode) {
|
|
|
|
$childXmlNode = $xmlNode->addChild(
|
|
|
|
$childArrayNode['name'],
|
|
|
|
$childArrayNode['content'] ?? null,
|
2021-06-11 08:03:54 +00:00
|
|
|
$childArrayNode['namespace'] === []
|
|
|
|
? null
|
|
|
|
: current($childArrayNode['namespace'])
|
2021-05-06 14:00:48 +00:00
|
|
|
);
|
|
|
|
if (array_key_exists('attributes', $childArrayNode)) {
|
|
|
|
foreach (
|
|
|
|
$childArrayNode['attributes']
|
|
|
|
as $attributeKey => $attributeValue
|
|
|
|
) {
|
2021-06-08 09:52:11 +00:00
|
|
|
$childXmlNode->addAttribute($attributeKey, $attributeValue);
|
2021-05-06 14:00:48 +00:00
|
|
|
}
|
2021-03-19 16:12:36 +00:00
|
|
|
}
|
2022-03-04 14:33:48 +00:00
|
|
|
|
2021-05-06 14:00:48 +00:00
|
|
|
array_to_rss($childArrayNode, $childXmlNode);
|
2021-03-19 16:12:36 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-06 14:00:48 +00:00
|
|
|
|
|
|
|
return $xmlNode;
|
2021-03-19 16:12:36 +00:00
|
|
|
}
|
|
|
|
}
|