mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 04:51:17 +00:00
refactor: remove fields from podcast and episode entities to be replaced with plugins
This commit is contained in:
parent
11ccd0ebe7
commit
b869acb3a9
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastsMediumField adds medium field to podcast table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class DropDeprecatedPodcastsFields extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->dropColumn(
|
||||
'podcasts',
|
||||
'episode_description_footer_markdown,episode_description_footer_html,is_owner_email_removed_from_feed,medium,payment_pointer,verify_txt,custom_rss,partner_id,partner_link_url,partner_image_url'
|
||||
);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$fields = [
|
||||
'episode_description_footer_markdown' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'episode_description_footer_html' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'is_owner_email_removed_from_feed' => [
|
||||
'type' => 'BOOLEAN',
|
||||
'null' => false,
|
||||
'default' => 0,
|
||||
'after' => 'owner_email',
|
||||
],
|
||||
'medium' => [
|
||||
'type' => "ENUM('podcast','music','audiobook')",
|
||||
'null' => false,
|
||||
'default' => 'podcast',
|
||||
'after' => 'type',
|
||||
],
|
||||
'payment_pointer' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'comment' => 'Wallet address for Web Monetization payments',
|
||||
'null' => true,
|
||||
],
|
||||
'verify_txt' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
'after' => 'location_osm',
|
||||
],
|
||||
'custom_rss' => [
|
||||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
'partner_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
'null' => true,
|
||||
],
|
||||
'partner_link_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
],
|
||||
'partner_image_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('podcasts', $fields);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastsMediumField adds medium field to podcast table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class DropDeprecatedEpisodesFields extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->dropColumn('episodes', 'custom_rss');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$fields = [
|
||||
'custom_rss' => [
|
||||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('episodes', $fields);
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ declare(strict_types=1);
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Entities\Clip\Soundbite;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Models\ClipModel;
|
||||
use App\Models\EpisodeCommentModel;
|
||||
use App\Models\EpisodeModel;
|
||||
@ -29,14 +28,13 @@ use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
||||
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
|
||||
use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
|
||||
use League\CommonMark\MarkdownConverter;
|
||||
use Modules\Analytics\OP3;
|
||||
use Modules\Media\Entities\Audio;
|
||||
use Modules\Media\Entities\Chapters;
|
||||
use Modules\Media\Entities\Image;
|
||||
use Modules\Media\Entities\Transcript;
|
||||
use Modules\Media\Models\MediaModel;
|
||||
use Override;
|
||||
use RuntimeException;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@ -73,8 +71,6 @@ use SimpleXMLElement;
|
||||
* @property string|null $location_name
|
||||
* @property string|null $location_geo
|
||||
* @property string|null $location_osm
|
||||
* @property array|null $custom_rss
|
||||
* @property string $custom_rss_string
|
||||
* @property bool $is_published_on_hubs
|
||||
* @property int $posts_count
|
||||
* @property int $comments_count
|
||||
@ -94,19 +90,19 @@ use SimpleXMLElement;
|
||||
*/
|
||||
class Episode extends Entity
|
||||
{
|
||||
protected Podcast $podcast;
|
||||
public string $link = '';
|
||||
|
||||
protected string $link;
|
||||
public string $audio_url = '';
|
||||
|
||||
public string $audio_web_url = '';
|
||||
|
||||
public string $audio_opengraph_url = '';
|
||||
|
||||
protected Podcast $podcast;
|
||||
|
||||
protected ?Audio $audio = null;
|
||||
|
||||
protected string $audio_url;
|
||||
|
||||
protected string $audio_web_url;
|
||||
|
||||
protected string $audio_opengraph_url;
|
||||
|
||||
protected string $embed_url;
|
||||
protected string $embed_url = '';
|
||||
|
||||
protected ?Image $cover = null;
|
||||
|
||||
@ -140,8 +136,6 @@ class Episode extends Entity
|
||||
|
||||
protected ?Location $location = null;
|
||||
|
||||
protected string $custom_rss_string;
|
||||
|
||||
protected ?string $publication_status = null;
|
||||
|
||||
/**
|
||||
@ -176,7 +170,6 @@ class Episode extends Entity
|
||||
'location_name' => '?string',
|
||||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'custom_rss' => '?json-array',
|
||||
'is_published_on_hubs' => 'boolean',
|
||||
'posts_count' => 'integer',
|
||||
'comments_count' => 'integer',
|
||||
@ -185,6 +178,31 @@ class Episode extends Entity
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
#[Override]
|
||||
public function injectRawData(array $data): static
|
||||
{
|
||||
parent::injectRawData($data);
|
||||
|
||||
$this->link = url_to('episode', esc($this->getPodcast()->handle, 'url'), esc($this->attributes['slug'], 'url'));
|
||||
|
||||
$this->audio_url = url_to(
|
||||
'episode-audio',
|
||||
$this->getPodcast()
|
||||
->handle,
|
||||
$this->slug,
|
||||
$this->getAudio()
|
||||
->file_extension
|
||||
);
|
||||
|
||||
$this->audio_opengraph_url = $this->audio_url . '?_from=-+Open+Graph+-';
|
||||
$this->audio_web_url = $this->audio_url . '?_from=-+Website+-';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCover(UploadedFile | File $file = null): self
|
||||
{
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
@ -342,40 +360,6 @@ class Episode extends Entity
|
||||
return $this->chapters;
|
||||
}
|
||||
|
||||
public function getAudioUrl(): string
|
||||
{
|
||||
$audioURL = url_to(
|
||||
'episode-audio',
|
||||
$this->getPodcast()
|
||||
->handle,
|
||||
$this->slug,
|
||||
$this->getAudio()
|
||||
->file_extension
|
||||
);
|
||||
|
||||
// Wrap episode url with OP3 if episode is public and OP3 is enabled on this podcast
|
||||
if (! $this->is_premium && service('settings')->get(
|
||||
'Analytics.enableOP3',
|
||||
'podcast:' . $this->podcast_id
|
||||
)) {
|
||||
$op3 = new OP3(config('Analytics')->OP3);
|
||||
|
||||
return $op3->wrap($audioURL, $this);
|
||||
}
|
||||
|
||||
return $audioURL;
|
||||
}
|
||||
|
||||
public function getAudioWebUrl(): string
|
||||
{
|
||||
return $this->getAudioUrl() . '?_from=-+Website+-';
|
||||
}
|
||||
|
||||
public function getAudioOpengraphUrl(): string
|
||||
{
|
||||
return $this->getAudioUrl() . '?_from=-+Open+Graph+-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transcript url from transcript file uri if it exists or returns the transcript_remote_url which can be null.
|
||||
*/
|
||||
@ -468,11 +452,6 @@ class Episode extends Entity
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function getLink(): string
|
||||
{
|
||||
return url_to('episode', esc($this->getPodcast()->handle), esc($this->attributes['slug']));
|
||||
}
|
||||
|
||||
public function getEmbedUrl(string $theme = null): string
|
||||
{
|
||||
return $theme
|
||||
@ -482,7 +461,7 @@ class Episode extends Entity
|
||||
|
||||
public function setGuid(?string $guid = null): static
|
||||
{
|
||||
$this->attributes['guid'] = $guid ?? $this->getLink();
|
||||
$this->attributes['guid'] = $guid ?? $this->link;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -513,34 +492,6 @@ class Episode extends Entity
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescriptionHtml(?string $serviceSlug = null): string
|
||||
{
|
||||
$descriptionHtml = '';
|
||||
if (
|
||||
$this->getPodcast()
|
||||
->partner_id !== null &&
|
||||
$this->getPodcast()
|
||||
->partner_link_url !== null &&
|
||||
$this->getPodcast()
|
||||
->partner_image_url !== null
|
||||
) {
|
||||
$descriptionHtml .= "<div><a href=\"{$this->getPartnerLink(
|
||||
$serviceSlug,
|
||||
)}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\"><img src=\"{$this->getPartnerImageUrl(
|
||||
$serviceSlug,
|
||||
)}\" alt=\"Partner image\" /></a></div>";
|
||||
}
|
||||
|
||||
$descriptionHtml .= $this->attributes['description_html'];
|
||||
|
||||
if ($this->getPodcast()->episode_description_footer_html) {
|
||||
$descriptionHtml .= "<footer>{$this->getPodcast()
|
||||
->episode_description_footer_html}</footer>";
|
||||
}
|
||||
|
||||
return $descriptionHtml;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
if ($this->description === null) {
|
||||
@ -609,91 +560,6 @@ class Episode extends Entity
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom rss tag as XML String
|
||||
*/
|
||||
public function getCustomRssString(): string
|
||||
{
|
||||
if ($this->custom_rss === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
|
||||
$xmlNode = (new SimpleRSSElement(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://podcastindex.org/namespace/1.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>',
|
||||
))
|
||||
->addChild('channel')
|
||||
->addChild('item');
|
||||
array_to_rss([
|
||||
'elements' => $this->custom_rss,
|
||||
], $xmlNode);
|
||||
|
||||
return str_replace(['<item>', '</item>'], '', (string) $xmlNode->asXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves custom rss tag into json
|
||||
*/
|
||||
public function setCustomRssString(?string $customRssString = null): static
|
||||
{
|
||||
if ($customRssString === '') {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
|
||||
$customXML = simplexml_load_string(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://podcastindex.org/namespace/1.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><item>' .
|
||||
$customRssString .
|
||||
'</item></channel></rss>',
|
||||
);
|
||||
|
||||
if (! $customXML instanceof SimpleXMLElement) {
|
||||
// TODO: Failed to parse custom xml, should return error?
|
||||
return $this;
|
||||
}
|
||||
|
||||
$customRssArray = rss_to_array($customXML)['elements'][0]['elements'][0];
|
||||
|
||||
if (array_key_exists('elements', $customRssArray)) {
|
||||
$this->attributes['custom_rss'] = json_encode($customRssArray['elements']);
|
||||
} else {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPartnerLink(?string $serviceSlug = null): string
|
||||
{
|
||||
$partnerLink =
|
||||
rtrim((string) $this->getPodcast()->partner_link_url, '/') .
|
||||
'?pid=' .
|
||||
$this->getPodcast()
|
||||
->partner_id .
|
||||
'&guid=' .
|
||||
urlencode((string) $this->attributes['guid']);
|
||||
|
||||
if ($serviceSlug !== null) {
|
||||
$partnerLink .= '&_from=' . $serviceSlug;
|
||||
}
|
||||
|
||||
return $partnerLink;
|
||||
}
|
||||
|
||||
public function getPartnerImageUrl(string $serviceSlug = null): string
|
||||
{
|
||||
return rtrim((string) $this->getPodcast()->partner_image_url, '/') .
|
||||
'?pid=' .
|
||||
$this->getPodcast()
|
||||
->partner_id .
|
||||
'&guid=' .
|
||||
urlencode((string) $this->attributes['guid']) .
|
||||
($serviceSlug !== null ? '&_from=' . $serviceSlug : '');
|
||||
}
|
||||
|
||||
public function getPreviewLink(): string
|
||||
{
|
||||
if ($this->preview_id === null) {
|
||||
|
@ -10,7 +10,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Models\ActorModel;
|
||||
use App\Models\CategoryModel;
|
||||
use App\Models\EpisodeModel;
|
||||
@ -62,12 +61,8 @@ use RuntimeException;
|
||||
* @property string|null $publisher
|
||||
* @property string $owner_name
|
||||
* @property string $owner_email
|
||||
* @property bool $is_owner_email_removed_from_feed
|
||||
* @property string $type
|
||||
* @property string $medium
|
||||
* @property string|null $copyright
|
||||
* @property string|null $episode_description_footer_markdown
|
||||
* @property string|null $episode_description_footer_html
|
||||
* @property bool $is_blocked
|
||||
* @property bool $is_completed
|
||||
* @property bool $is_locked
|
||||
@ -77,15 +72,7 @@ use RuntimeException;
|
||||
* @property string|null $location_name
|
||||
* @property string|null $location_geo
|
||||
* @property string|null $location_osm
|
||||
* @property string|null $payment_pointer
|
||||
* @property array|null $custom_rss
|
||||
* @property bool $is_op3_enabled
|
||||
* @property string $op3_url
|
||||
* @property string $custom_rss_string
|
||||
* @property bool $is_published_on_hubs
|
||||
* @property string|null $partner_id
|
||||
* @property string|null $partner_link_url
|
||||
* @property string|null $partner_image_url
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property string $publication_status
|
||||
@ -166,8 +153,6 @@ class Podcast extends Entity
|
||||
|
||||
protected ?Location $location = null;
|
||||
|
||||
protected string $custom_rss_string;
|
||||
|
||||
protected ?string $publication_status = null;
|
||||
|
||||
/**
|
||||
@ -180,44 +165,35 @@ class Podcast extends Entity
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'guid' => 'string',
|
||||
'actor_id' => 'integer',
|
||||
'handle' => 'string',
|
||||
'title' => 'string',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'cover_id' => 'int',
|
||||
'banner_id' => '?int',
|
||||
'language_code' => 'string',
|
||||
'category_id' => 'integer',
|
||||
'parental_advisory' => '?string',
|
||||
'publisher' => '?string',
|
||||
'owner_name' => 'string',
|
||||
'owner_email' => 'string',
|
||||
'is_owner_email_removed_from_feed' => 'boolean',
|
||||
'type' => 'string',
|
||||
'medium' => 'string',
|
||||
'copyright' => '?string',
|
||||
'episode_description_footer_markdown' => '?string',
|
||||
'episode_description_footer_html' => '?string',
|
||||
'is_blocked' => 'boolean',
|
||||
'is_completed' => 'boolean',
|
||||
'is_locked' => 'boolean',
|
||||
'is_premium_by_default' => 'boolean',
|
||||
'imported_feed_url' => '?string',
|
||||
'new_feed_url' => '?string',
|
||||
'location_name' => '?string',
|
||||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'payment_pointer' => '?string',
|
||||
'custom_rss' => '?json-array',
|
||||
'is_published_on_hubs' => 'boolean',
|
||||
'partner_id' => '?string',
|
||||
'partner_link_url' => '?string',
|
||||
'partner_image_url' => '?string',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
'id' => 'integer',
|
||||
'guid' => 'string',
|
||||
'actor_id' => 'integer',
|
||||
'handle' => 'string',
|
||||
'title' => 'string',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'cover_id' => 'int',
|
||||
'banner_id' => '?int',
|
||||
'language_code' => 'string',
|
||||
'category_id' => 'integer',
|
||||
'parental_advisory' => '?string',
|
||||
'publisher' => '?string',
|
||||
'owner_name' => 'string',
|
||||
'owner_email' => 'string',
|
||||
'type' => 'string',
|
||||
'copyright' => '?string',
|
||||
'is_blocked' => 'boolean',
|
||||
'is_completed' => 'boolean',
|
||||
'is_locked' => 'boolean',
|
||||
'is_premium_by_default' => 'boolean',
|
||||
'imported_feed_url' => '?string',
|
||||
'new_feed_url' => '?string',
|
||||
'location_name' => '?string',
|
||||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'is_published_on_hubs' => 'boolean',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
public function getAtHandle(): string
|
||||
@ -454,42 +430,6 @@ class Podcast extends Entity
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEpisodeDescriptionFooterMarkdown(?string $episodeDescriptionFooterMarkdown = null): static
|
||||
{
|
||||
if ($episodeDescriptionFooterMarkdown === null || $episodeDescriptionFooterMarkdown === '') {
|
||||
$this->attributes[
|
||||
'episode_description_footer_markdown'
|
||||
] = null;
|
||||
$this->attributes[
|
||||
'episode_description_footer_html'
|
||||
] = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$config = [
|
||||
'html_input' => 'escape',
|
||||
'allow_unsafe_links' => false,
|
||||
];
|
||||
|
||||
$environment = new Environment($config);
|
||||
$environment->addExtension(new CommonMarkCoreExtension());
|
||||
$environment->addExtension(new AutolinkExtension());
|
||||
$environment->addExtension(new SmartPunctExtension());
|
||||
$environment->addExtension(new DisallowedRawHtmlExtension());
|
||||
|
||||
$converter = new MarkdownConverter($environment);
|
||||
|
||||
$this->attributes[
|
||||
'episode_description_footer_markdown'
|
||||
] = $episodeDescriptionFooterMarkdown;
|
||||
$this->attributes[
|
||||
'episode_description_footer_html'
|
||||
] = $converter->convert($episodeDescriptionFooterMarkdown);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
if ($this->description === null) {
|
||||
@ -638,68 +578,9 @@ class Podcast extends Entity
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom rss tag as XML String
|
||||
*/
|
||||
public function getCustomRssString(): string
|
||||
{
|
||||
if ($this->attributes['custom_rss'] === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
|
||||
$xmlNode = (new SimpleRSSElement(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://podcastindex.org/namespace/1.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>',
|
||||
))->addChild('channel');
|
||||
array_to_rss([
|
||||
'elements' => $this->custom_rss,
|
||||
], $xmlNode);
|
||||
|
||||
return str_replace(['<channel>', '</channel>'], '', (string) $xmlNode->asXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves custom rss tag into json
|
||||
*/
|
||||
public function setCustomRssString(string $customRssString): static
|
||||
{
|
||||
if ($customRssString === '') {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
$customRssArray = rss_to_array(
|
||||
simplexml_load_string(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://podcastindex.org/namespace/1.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel>' .
|
||||
$customRssString .
|
||||
'</channel></rss>',
|
||||
),
|
||||
)['elements'][0];
|
||||
|
||||
if (array_key_exists('elements', $customRssArray)) {
|
||||
$this->attributes['custom_rss'] = json_encode($customRssArray['elements']);
|
||||
} else {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsPremium(): bool
|
||||
{
|
||||
// podcast is premium if at least one of its episodes is set as premium
|
||||
return (new EpisodeModel())->doesPodcastHavePremiumEpisodes($this->id);
|
||||
}
|
||||
|
||||
public function getIsOp3Enabled(): bool
|
||||
{
|
||||
return service('settings')->get('Analytics.enableOP3', 'podcast:' . $this->id);
|
||||
}
|
||||
|
||||
public function getOp3Url(): string
|
||||
{
|
||||
return 'https://op3.dev/show/' . $this->guid;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
||||
use App\Entities\Category;
|
||||
use App\Entities\Location;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Mimes;
|
||||
@ -37,21 +37,13 @@ if (! function_exists('get_rss_feed')) {
|
||||
|
||||
$episodes = $podcast->episodes;
|
||||
|
||||
$itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
|
||||
|
||||
$podcastNamespace = 'https://podcastindex.org/namespace/1.0';
|
||||
|
||||
$atomNamespace = 'http://www.w3.org/2005/Atom';
|
||||
|
||||
$rss = new SimpleRSSElement(
|
||||
"<?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>"
|
||||
);
|
||||
$rss = new RssFeed();
|
||||
|
||||
$plugins->rssBeforeChannel($podcast);
|
||||
|
||||
$channel = $rss->addChild('channel');
|
||||
|
||||
$atomLink = $channel->addChild('link', null, $atomNamespace);
|
||||
$atomLink = $channel->addChild('link', null, RssFeed::ATOM_NAMESPACE);
|
||||
$atomLink->addAttribute('href', $podcast->feed_url);
|
||||
$atomLink->addAttribute('rel', 'self');
|
||||
$atomLink->addAttribute('type', 'application/rss+xml');
|
||||
@ -60,14 +52,14 @@ if (! function_exists('get_rss_feed')) {
|
||||
$websubHubs = config('WebSub')
|
||||
->hubs;
|
||||
foreach ($websubHubs as $websubHub) {
|
||||
$atomLinkHub = $channel->addChild('link', null, $atomNamespace);
|
||||
$atomLinkHub = $channel->addChild('link', null, RssFeed::ATOM_NAMESPACE);
|
||||
$atomLinkHub->addAttribute('href', $websubHub);
|
||||
$atomLinkHub->addAttribute('rel', 'hub');
|
||||
$atomLinkHub->addAttribute('type', 'application/rss+xml');
|
||||
}
|
||||
|
||||
if ($podcast->new_feed_url !== null) {
|
||||
$channel->addChild('new-feed-url', $podcast->new_feed_url, $itunesNamespace);
|
||||
$channel->addChild('new-feed-url', $podcast->new_feed_url, RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
// the last build date corresponds to the creation of the feed.xml cache
|
||||
@ -85,19 +77,17 @@ if (! function_exists('get_rss_feed')) {
|
||||
(new PodcastModel())->save($podcast);
|
||||
}
|
||||
|
||||
$channel->addChild('guid', $podcast->guid, $podcastNamespace);
|
||||
$channel->addChild('guid', $podcast->guid, RssFeed::PODCAST_NAMESPACE);
|
||||
$channel->addChild('title', $podcast->title, null, false);
|
||||
$channel->addChildWithCDATA('description', $podcast->description_html);
|
||||
|
||||
$channel->addChild('medium', $podcast->medium, $podcastNamespace);
|
||||
|
||||
$itunesImage = $channel->addChild('image', null, $itunesNamespace);
|
||||
$itunesImage = $channel->addChild('image', null, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
$itunesImage->addAttribute('href', $podcast->cover->feed_url);
|
||||
|
||||
$channel->addChild('language', $podcast->language_code);
|
||||
if ($podcast->location instanceof Location) {
|
||||
$locationElement = $channel->addChild('location', $podcast->location->name, $podcastNamespace);
|
||||
$locationElement = $channel->addChild('location', $podcast->location->name, RssFeed::PODCAST_NAMESPACE);
|
||||
if ($podcast->location->geo !== null) {
|
||||
$locationElement->addAttribute('geo', $podcast->location->geo);
|
||||
}
|
||||
@ -107,38 +97,16 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($podcast->payment_pointer !== null) {
|
||||
$valueElement = $channel->addChild('value', null, $podcastNamespace);
|
||||
$valueElement->addAttribute('type', 'webmonetization');
|
||||
$valueElement->addAttribute('method', 'ILP');
|
||||
$recipientElement = $valueElement->addChild('valueRecipient', null, $podcastNamespace);
|
||||
$recipientElement->addAttribute('name', $podcast->owner_name);
|
||||
$recipientElement->addAttribute('type', 'paymentpointer');
|
||||
$recipientElement->addAttribute('address', $podcast->payment_pointer);
|
||||
$recipientElement->addAttribute('split', '100');
|
||||
}
|
||||
|
||||
if ($podcast->is_owner_email_removed_from_feed) {
|
||||
$channel
|
||||
->addChild('locked', $podcast->is_locked ? 'yes' : 'no', $podcastNamespace);
|
||||
} else {
|
||||
$channel
|
||||
->addChild('locked', $podcast->is_locked ? 'yes' : 'no', $podcastNamespace)
|
||||
->addAttribute('owner', $podcast->owner_email);
|
||||
}
|
||||
|
||||
if ($podcast->verify_txt !== null) {
|
||||
$channel
|
||||
->addChild('txt', $podcast->verify_txt, $podcastNamespace)
|
||||
->addAttribute('purpose', 'verify');
|
||||
}
|
||||
$channel
|
||||
->addChild('locked', $podcast->is_locked ? 'yes' : 'no', RssFeed::PODCAST_NAMESPACE)
|
||||
->addAttribute('owner', $podcast->owner_email);
|
||||
|
||||
if ($podcast->imported_feed_url !== null) {
|
||||
$channel->addChild('previousUrl', $podcast->imported_feed_url, $podcastNamespace);
|
||||
$channel->addChild('previousUrl', $podcast->imported_feed_url, RssFeed::PODCAST_NAMESPACE);
|
||||
}
|
||||
|
||||
foreach ($podcast->podcasting_platforms as $podcastingPlatform) {
|
||||
$podcastingPlatformElement = $channel->addChild('id', null, $podcastNamespace);
|
||||
$podcastingPlatformElement = $channel->addChild('id', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$podcastingPlatformElement->addAttribute('platform', $podcastingPlatform->slug);
|
||||
if ($podcastingPlatform->account_id !== null) {
|
||||
$podcastingPlatformElement->addAttribute('id', $podcastingPlatform->account_id);
|
||||
@ -149,7 +117,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
}
|
||||
|
||||
$castopodSocialElement = $channel->addChild('social', null, $podcastNamespace);
|
||||
$castopodSocialElement = $channel->addChild('social', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$castopodSocialElement->addAttribute('priority', '1');
|
||||
$castopodSocialElement->addAttribute('platform', 'castopod');
|
||||
$castopodSocialElement->addAttribute('protocol', 'activitypub');
|
||||
@ -157,7 +125,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
$castopodSocialElement->addAttribute('accountUrl', $podcast->link);
|
||||
|
||||
foreach ($podcast->social_platforms as $socialPlatform) {
|
||||
$socialElement = $channel->addChild('social', null, $podcastNamespace);
|
||||
$socialElement = $channel->addChild('social', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$socialElement->addAttribute('priority', '2');
|
||||
$socialElement->addAttribute('platform', $socialPlatform->slug);
|
||||
|
||||
@ -181,7 +149,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
|
||||
if ($socialPlatform->slug === 'mastodon') {
|
||||
$socialSignUpelement = $socialElement->addChild('socialSignUp', null, $podcastNamespace);
|
||||
$socialSignUpelement = $socialElement->addChild('socialSignUp', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$socialSignUpelement->addAttribute('priority', '1');
|
||||
$socialSignUpelement->addAttribute(
|
||||
'homeUrl',
|
||||
@ -200,7 +168,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
$castopodSocialSignUpelement = $castopodSocialElement->addChild(
|
||||
'socialSignUp',
|
||||
null,
|
||||
$podcastNamespace
|
||||
RssFeed::PODCAST_NAMESPACE
|
||||
);
|
||||
$castopodSocialSignUpelement->addAttribute('priority', '1');
|
||||
$castopodSocialSignUpelement->addAttribute(
|
||||
@ -224,7 +192,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
$fundingPlatformElement = $channel->addChild(
|
||||
'funding',
|
||||
$fundingPlatform->account_id,
|
||||
$podcastNamespace,
|
||||
RssFeed::PODCAST_NAMESPACE,
|
||||
);
|
||||
$fundingPlatformElement->addAttribute('platform', $fundingPlatform->slug);
|
||||
if ($fundingPlatform->link_url !== null) {
|
||||
@ -234,7 +202,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
|
||||
foreach ($podcast->persons as $person) {
|
||||
foreach ($person->roles as $role) {
|
||||
$personElement = $channel->addChild('person', $person->full_name, $podcastNamespace);
|
||||
$personElement = $channel->addChild('person', $person->full_name, RssFeed::PODCAST_NAMESPACE);
|
||||
|
||||
$personElement->addAttribute('img', get_avatar_url($person, 'medium'));
|
||||
|
||||
@ -263,29 +231,26 @@ if (! function_exists('get_rss_feed')) {
|
||||
$channel->addChild(
|
||||
'explicit',
|
||||
$podcast->parental_advisory === 'explicit' ? 'true' : 'false',
|
||||
$itunesNamespace,
|
||||
RssFeed::ITUNES_NAMESPACE,
|
||||
);
|
||||
|
||||
$channel->addChild('author', $podcast->publisher ?: $podcast->owner_name, $itunesNamespace, false);
|
||||
$channel->addChild('author', $podcast->publisher ?: $podcast->owner_name, RssFeed::ITUNES_NAMESPACE, false);
|
||||
$channel->addChild('link', $podcast->link);
|
||||
|
||||
$owner = $channel->addChild('owner', null, $itunesNamespace);
|
||||
$owner = $channel->addChild('owner', null, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
$owner->addChild('name', $podcast->owner_name, $itunesNamespace, false);
|
||||
$owner->addChild('name', $podcast->owner_name, RssFeed::ITUNES_NAMESPACE, false);
|
||||
$owner->addChild('email', $podcast->owner_email, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
if (! $podcast->is_owner_email_removed_from_feed) {
|
||||
$owner->addChild('email', $podcast->owner_email, $itunesNamespace);
|
||||
}
|
||||
|
||||
$channel->addChild('type', $podcast->type, $itunesNamespace);
|
||||
$channel->addChild('type', $podcast->type, RssFeed::ITUNES_NAMESPACE);
|
||||
$podcast->copyright &&
|
||||
$channel->addChild('copyright', $podcast->copyright);
|
||||
if ($podcast->is_blocked || $subscription instanceof Subscription) {
|
||||
$channel->addChild('block', 'Yes', $itunesNamespace);
|
||||
$channel->addChild('block', 'Yes', RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
if ($podcast->is_completed) {
|
||||
$channel->addChild('complete', 'Yes', $itunesNamespace);
|
||||
$channel->addChild('complete', 'Yes', RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
$image = $channel->addChild('image');
|
||||
@ -293,12 +258,6 @@ if (! function_exists('get_rss_feed')) {
|
||||
$image->addChild('title', $podcast->title, null, false);
|
||||
$image->addChild('link', $podcast->link);
|
||||
|
||||
if ($podcast->custom_rss !== null) {
|
||||
array_to_rss([
|
||||
'elements' => $podcast->custom_rss,
|
||||
], $channel);
|
||||
}
|
||||
|
||||
// run plugins hook at the end
|
||||
$plugins->rssAfterChannel($podcast, $channel);
|
||||
|
||||
@ -328,7 +287,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
$item->addChild('guid', $episode->guid);
|
||||
$item->addChild('pubDate', $episode->published_at->format(DATE_RFC1123));
|
||||
if ($episode->location instanceof Location) {
|
||||
$locationElement = $item->addChild('location', $episode->location->name, $podcastNamespace);
|
||||
$locationElement = $item->addChild('location', $episode->location->name, RssFeed::PODCAST_NAMESPACE);
|
||||
if ($episode->location->geo !== null) {
|
||||
$locationElement->addAttribute('geo', $episode->location->geo);
|
||||
}
|
||||
@ -338,10 +297,10 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
}
|
||||
|
||||
$item->addChildWithCDATA('description', $episode->getDescriptionHtml($serviceSlug));
|
||||
$item->addChild('duration', (string) round($episode->audio->duration), $itunesNamespace);
|
||||
$item->addChildWithCDATA('description', $episode->description_html);
|
||||
$item->addChild('duration', (string) round($episode->audio->duration), RssFeed::ITUNES_NAMESPACE);
|
||||
$item->addChild('link', $episode->link);
|
||||
$episodeItunesImage = $item->addChild('image', null, $itunesNamespace);
|
||||
$episodeItunesImage = $item->addChild('image', null, RssFeed::ITUNES_NAMESPACE);
|
||||
$episodeItunesImage->addAttribute('href', $episode->cover->feed_url);
|
||||
|
||||
$episode->parental_advisory &&
|
||||
@ -350,18 +309,18 @@ if (! function_exists('get_rss_feed')) {
|
||||
$episode->parental_advisory === 'explicit'
|
||||
? 'true'
|
||||
: 'false',
|
||||
$itunesNamespace,
|
||||
RssFeed::ITUNES_NAMESPACE,
|
||||
);
|
||||
|
||||
$episode->number &&
|
||||
$item->addChild('episode', (string) $episode->number, $itunesNamespace);
|
||||
$item->addChild('episode', (string) $episode->number, RssFeed::ITUNES_NAMESPACE);
|
||||
$episode->season_number &&
|
||||
$item->addChild('season', (string) $episode->season_number, $itunesNamespace);
|
||||
$item->addChild('episodeType', $episode->type, $itunesNamespace);
|
||||
$item->addChild('season', (string) $episode->season_number, RssFeed::ITUNES_NAMESPACE);
|
||||
$item->addChild('episodeType', $episode->type, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
// If episode is of type trailer, add podcast:trailer tag on channel level
|
||||
if ($episode->type === 'trailer') {
|
||||
$trailer = $channel->addChild('trailer', $episode->title, $podcastNamespace);
|
||||
$trailer = $channel->addChild('trailer', $episode->title, RssFeed::PODCAST_NAMESPACE);
|
||||
$trailer->addAttribute('pubdate', $episode->published_at->format(DATE_RFC2822));
|
||||
$trailer->addAttribute(
|
||||
'url',
|
||||
@ -374,21 +333,15 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
}
|
||||
|
||||
// add podcast namespace tags for season and episode
|
||||
$episode->season_number &&
|
||||
$item->addChild('season', (string) $episode->season_number, $podcastNamespace);
|
||||
$episode->number &&
|
||||
$item->addChild('episode', (string) $episode->number, $podcastNamespace);
|
||||
|
||||
// add link to episode comments as podcast-activity format
|
||||
$comments = $item->addChild('comments', null, $podcastNamespace);
|
||||
$comments = $item->addChild('comments', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug));
|
||||
$comments->addAttribute('contentType', 'application/podcast-activity+json');
|
||||
|
||||
if ($episode->getPosts()) {
|
||||
$socialInteractUri = $episode->getPosts()[0]
|
||||
->uri;
|
||||
$socialInteractElement = $item->addChild('socialInteract', null, $podcastNamespace);
|
||||
$socialInteractElement = $item->addChild('socialInteract', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$socialInteractElement->addAttribute('uri', $socialInteractUri);
|
||||
$socialInteractElement->addAttribute('priority', '1');
|
||||
$socialInteractElement->addAttribute('platform', 'castopod');
|
||||
@ -405,7 +358,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
|
||||
if ($episode->transcript instanceof Transcript) {
|
||||
$transcriptElement = $item->addChild('transcript', null, $podcastNamespace);
|
||||
$transcriptElement = $item->addChild('transcript', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$transcriptElement->addAttribute('url', $episode->transcript->file_url);
|
||||
$transcriptElement->addAttribute(
|
||||
'type',
|
||||
@ -420,21 +373,21 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
|
||||
if ($episode->getChapters() instanceof Chapters) {
|
||||
$chaptersElement = $item->addChild('chapters', null, $podcastNamespace);
|
||||
$chaptersElement = $item->addChild('chapters', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$chaptersElement->addAttribute('url', $episode->chapters->file_url);
|
||||
$chaptersElement->addAttribute('type', 'application/json+chapters');
|
||||
}
|
||||
|
||||
foreach ($episode->soundbites as $soundbite) {
|
||||
// TODO: differentiate video from soundbites?
|
||||
$soundbiteElement = $item->addChild('soundbite', $soundbite->title, $podcastNamespace);
|
||||
$soundbiteElement = $item->addChild('soundbite', $soundbite->title, RssFeed::PODCAST_NAMESPACE);
|
||||
$soundbiteElement->addAttribute('startTime', (string) $soundbite->start_time);
|
||||
$soundbiteElement->addAttribute('duration', (string) round($soundbite->duration, 3));
|
||||
}
|
||||
|
||||
foreach ($episode->persons as $person) {
|
||||
foreach ($person->roles as $role) {
|
||||
$personElement = $item->addChild('person', esc($person->full_name), $podcastNamespace);
|
||||
$personElement = $item->addChild('person', esc($person->full_name), RssFeed::PODCAST_NAMESPACE);
|
||||
|
||||
$personElement->addAttribute(
|
||||
'role',
|
||||
@ -455,13 +408,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
}
|
||||
|
||||
if ($episode->is_blocked) {
|
||||
$item->addChild('block', 'Yes', $itunesNamespace);
|
||||
}
|
||||
|
||||
if ($episode->custom_rss !== null) {
|
||||
array_to_rss([
|
||||
'elements' => $episode->custom_rss,
|
||||
], $item);
|
||||
$item->addChild('block', 'Yes', RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
$plugins->rssAfterItem($episode, $item);
|
||||
@ -477,9 +424,7 @@ if (! function_exists('add_category_tag')) {
|
||||
*/
|
||||
function add_category_tag(SimpleXMLElement $node, Category $category): void
|
||||
{
|
||||
$itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
|
||||
|
||||
$itunesCategory = $node->addChild('category', null, $itunesNamespace);
|
||||
$itunesCategory = $node->addChild('category', null, RssFeed::ITUNES_NAMESPACE);
|
||||
$itunesCategory->addAttribute(
|
||||
'text',
|
||||
$category->parent instanceof Category
|
||||
@ -488,7 +433,7 @@ if (! function_exists('add_category_tag')) {
|
||||
);
|
||||
|
||||
if ($category->parent instanceof Category) {
|
||||
$itunesCategoryChild = $itunesCategory->addChild('category', null, $itunesNamespace);
|
||||
$itunesCategoryChild = $itunesCategory->addChild('category', null, RssFeed::ITUNES_NAMESPACE);
|
||||
$itunesCategoryChild->addAttribute('text', $category->apple_category);
|
||||
$node->addChild('category', $category->parent->apple_category);
|
||||
}
|
||||
@ -535,9 +480,9 @@ if (! function_exists('array_to_rss')) {
|
||||
* Inserts array (converted to XML node) in XML node
|
||||
*
|
||||
* @param array<string, mixed> $arrayNode
|
||||
* @param SimpleRSSElement $xmlNode The XML parent node where this arrayNode should be attached
|
||||
* @param RssFeed $xmlNode The XML parent node where this arrayNode should be attached
|
||||
*/
|
||||
function array_to_rss(array $arrayNode, SimpleRSSElement &$xmlNode): SimpleRSSElement
|
||||
function array_to_rss(array $arrayNode, RssFeed &$xmlNode): RssFeed
|
||||
{
|
||||
if (array_key_exists('elements', $arrayNode)) {
|
||||
foreach ($arrayNode['elements'] as $childArrayNode) {
|
||||
|
@ -65,10 +65,6 @@ if (! function_exists('get_podcast_metatags')) {
|
||||
'href' => url_to('podcast-activity', esc($podcast->handle)),
|
||||
]);
|
||||
|
||||
if ($podcast->payment_pointer) {
|
||||
$metatags->meta('monetization', $podcast->payment_pointer);
|
||||
}
|
||||
|
||||
return '<link type="application/rss+xml" rel="alternate" title="' . esc(
|
||||
$podcast->title
|
||||
) . '" href="' . $podcast->feed_url . '" />' . PHP_EOL . $metatags->__toString() . PHP_EOL . $schema->__toString();
|
||||
@ -123,10 +119,6 @@ if (! function_exists('get_episode_metatags')) {
|
||||
'href' => url_to('episode', $episode->podcast->handle, $episode->slug),
|
||||
]);
|
||||
|
||||
if ($episode->podcast->payment_pointer) {
|
||||
$metatags->meta('monetization', $episode->podcast->payment_pointer);
|
||||
}
|
||||
|
||||
return $metatags->__toString() . PHP_EOL . '<link rel="alternate" type="application/json+oembed" href="' . base_url(
|
||||
route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug)
|
||||
) . '" title="' . esc(
|
||||
|
@ -14,8 +14,31 @@ use DOMDocument;
|
||||
use Override;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class SimpleRSSElement extends SimpleXMLElement
|
||||
class RssFeed extends SimpleXMLElement
|
||||
{
|
||||
public const ATOM_NS = 'atom';
|
||||
|
||||
public const ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom';
|
||||
|
||||
public const ITUNES_NS = 'itunes';
|
||||
|
||||
public const ITUNES_NAMESPACE = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
|
||||
|
||||
public const PODCAST_NS = 'podcast';
|
||||
|
||||
public const PODCAST_NAMESPACE = 'https://podcastindex.org/namespace/1.0';
|
||||
|
||||
public function __construct(string $contents = '')
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
"<?xml version='1.0' encoding='utf-8'?><rss version='2.0' xmlns:atom='%s' xmlns:itunes='%s' xmlns:podcast='%s' xmlns:content='http://purl.org/rss/1.0/modules/content/'>%s</rss>",
|
||||
$this::ATOM_NAMESPACE,
|
||||
$this::ITUNES_NAMESPACE,
|
||||
$this::PODCAST_NAMESPACE,
|
||||
$contents
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child with $value inside CDATA
|
||||
*
|
||||
@ -67,4 +90,37 @@ class SimpleRSSElement extends SimpleXMLElement
|
||||
|
||||
return $newChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add RssFeed code into a RssFeed
|
||||
*
|
||||
* adapted from: https://stackoverflow.com/a/23527002
|
||||
*
|
||||
* @param self|array<self> $nodes
|
||||
*/
|
||||
public function appendNodes(self|array $nodes): void
|
||||
{
|
||||
if (! is_array($nodes)) {
|
||||
$nodes = [$nodes];
|
||||
}
|
||||
|
||||
foreach ($nodes as $element) {
|
||||
$namespaces = $element->getNamespaces();
|
||||
$namespace = $namespaces[array_key_first($namespaces)] ?? null;
|
||||
|
||||
if (trim((string) $element) === '') {
|
||||
$simpleRSS = $this->addChild($element->getName(), null, $namespace);
|
||||
} else {
|
||||
$simpleRSS = $this->addChild($element->getName(), (string) $element, $namespace);
|
||||
}
|
||||
|
||||
foreach ($element->children() as $child) {
|
||||
$simpleRSS->appendNodes($child);
|
||||
}
|
||||
|
||||
foreach ($element->attributes() as $name => $value) {
|
||||
$simpleRSS->addAttribute($name, (string) $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -87,7 +87,6 @@ class EpisodeModel extends UuidModel
|
||||
'location_name',
|
||||
'location_geo',
|
||||
'location_osm',
|
||||
'custom_rss',
|
||||
'is_published_on_hubs',
|
||||
'posts_count',
|
||||
'comments_count',
|
||||
|
@ -38,8 +38,6 @@ class PodcastModel extends Model
|
||||
'handle',
|
||||
'description_markdown',
|
||||
'description_html',
|
||||
'episode_description_footer_markdown',
|
||||
'episode_description_footer_html',
|
||||
'cover_id',
|
||||
'banner_id',
|
||||
'language_code',
|
||||
@ -47,10 +45,8 @@ class PodcastModel extends Model
|
||||
'parental_advisory',
|
||||
'owner_name',
|
||||
'owner_email',
|
||||
'is_owner_email_removed_from_feed',
|
||||
'publisher',
|
||||
'type',
|
||||
'medium',
|
||||
'copyright',
|
||||
'imported_feed_url',
|
||||
'new_feed_url',
|
||||
@ -60,13 +56,7 @@ class PodcastModel extends Model
|
||||
'location_name',
|
||||
'location_geo',
|
||||
'location_osm',
|
||||
'verify_txt',
|
||||
'payment_pointer',
|
||||
'custom_rss',
|
||||
'is_published_on_hubs',
|
||||
'partner_id',
|
||||
'partner_link_url',
|
||||
'partner_image_url',
|
||||
'is_premium_by_default',
|
||||
'published_at',
|
||||
'created_by',
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
syntaxHighlighting,
|
||||
} from "@codemirror/language";
|
||||
import { Compartment, EditorState } from "@codemirror/state";
|
||||
import { keymap } from "@codemirror/view";
|
||||
import { keymap, ViewUpdate } from "@codemirror/view";
|
||||
import { basicSetup, EditorView } from "codemirror";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, queryAssignedNodes, state } from "lit/decorators.js";
|
||||
@ -63,6 +63,12 @@ export class XMLEditor extends LitElement {
|
||||
language.of(xml()),
|
||||
minHeightEditor,
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
|
||||
if (viewUpdate.docChanged) {
|
||||
// Document changed, update textarea value
|
||||
this._textarea[0].value = viewUpdate.state.doc.toString();
|
||||
}
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@ -72,20 +78,11 @@ export class XMLEditor extends LitElement {
|
||||
parent: this.shadowRoot as ShadowRoot,
|
||||
});
|
||||
|
||||
this._textarea[0].hidden = true;
|
||||
if (this._textarea[0].form) {
|
||||
this._textarea[0].form.addEventListener("submit", () => {
|
||||
this._textarea[0].value = this.editorView.state.doc.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
if (this._textarea[0].form) {
|
||||
this._textarea[0].form.removeEventListener("submit", () => {
|
||||
this._textarea[0].value = this.editorView.state.doc.toString();
|
||||
});
|
||||
}
|
||||
// hide textarea
|
||||
this._textarea[0].style.position = "absolute";
|
||||
this._textarea[0].style.opacity = "0";
|
||||
this._textarea[0].style.zIndex = "-9999";
|
||||
this._textarea[0].style.pointerEvents = "none";
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
|
@ -9,7 +9,7 @@ use ViewComponents\Component;
|
||||
|
||||
class Alert extends Component
|
||||
{
|
||||
protected array $props = ['glyph', 'title'];
|
||||
protected array $props = ['glyph', 'title', 'variant'];
|
||||
|
||||
protected string $glyph = '';
|
||||
|
||||
|
@ -9,6 +9,8 @@ use ViewComponents\Component;
|
||||
|
||||
class ChartsComponent extends Component
|
||||
{
|
||||
protected array $props = ['title', 'subtitle', 'dataUrl', 'type'];
|
||||
|
||||
protected string $title;
|
||||
|
||||
protected string $subtitle = '';
|
||||
|
@ -9,7 +9,7 @@ use Override;
|
||||
|
||||
class Checkbox extends FormComponent
|
||||
{
|
||||
protected array $props = ['hint', 'isChecked'];
|
||||
protected array $props = ['hint', 'helper', 'isChecked'];
|
||||
|
||||
protected array $casts = [
|
||||
'isChecked' => 'boolean',
|
||||
|
@ -15,8 +15,8 @@ class Field extends Component
|
||||
'isRequired',
|
||||
'isReadonly',
|
||||
'as',
|
||||
'helper',
|
||||
'hint',
|
||||
'helper',
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
@ -34,10 +34,10 @@ class Field extends Component
|
||||
|
||||
protected string $as = 'Input';
|
||||
|
||||
protected string $helper = '';
|
||||
|
||||
protected string $hint = '';
|
||||
|
||||
protected string $helper = '';
|
||||
|
||||
#[Override]
|
||||
public function render(): string
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ use Override;
|
||||
|
||||
class RadioButton extends FormComponent
|
||||
{
|
||||
protected array $props = ['isChecked', 'hint'];
|
||||
protected array $props = ['isSelected', 'description'];
|
||||
|
||||
protected array $casts = [
|
||||
'isSelected' => 'boolean',
|
||||
|
@ -9,7 +9,7 @@ use Override;
|
||||
|
||||
class Toggler extends FormComponent
|
||||
{
|
||||
protected array $props = ['size', 'hint', 'isChecked'];
|
||||
protected array $props = ['size', 'hint', 'helper', 'isChecked'];
|
||||
|
||||
protected array $casts = [
|
||||
'isChecked' => 'boolean',
|
||||
|
@ -32,7 +32,7 @@ class XMLEditor extends FormComponent
|
||||
$textarea = form_textarea($this->attributes, $this->content);
|
||||
|
||||
return <<<HTML
|
||||
<xml-editor>{$textarea}</time-ago>
|
||||
<xml-editor>{$textarea}</xml-editor>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ namespace App\Views\Components;
|
||||
|
||||
class IconButton extends Button
|
||||
{
|
||||
public string $glyph;
|
||||
|
||||
protected array $props = ['glyph'];
|
||||
|
||||
protected string $glyph;
|
||||
|
||||
public function __construct(array $attributes)
|
||||
{
|
||||
$iconButtonAttributes = [
|
||||
|
@ -9,18 +9,18 @@ use ViewComponents\Component;
|
||||
|
||||
class Pill extends Component
|
||||
{
|
||||
protected array $props = ['size', 'variant', 'icon', 'iconClass', 'hint'];
|
||||
|
||||
/**
|
||||
* @var 'small'|'base'
|
||||
*/
|
||||
public string $size = 'base';
|
||||
protected string $size = 'base';
|
||||
|
||||
public string $variant = 'default';
|
||||
protected string $variant = 'default';
|
||||
|
||||
public string $icon = '';
|
||||
protected string $icon = '';
|
||||
|
||||
public string $iconClass = '';
|
||||
|
||||
protected array $props = ['size', 'variant', 'icon', 'iconClass', 'hint'];
|
||||
protected string $iconClass = '';
|
||||
|
||||
protected string $hint = '';
|
||||
|
||||
|
@ -180,14 +180,6 @@ $routes->group(
|
||||
],
|
||||
);
|
||||
});
|
||||
$routes->get('monetization-other', 'PodcastController::monetizationOther/$1', [
|
||||
'as' => 'podcast-monetization-other',
|
||||
'filter' => 'permission:podcast$1.edit',
|
||||
]);
|
||||
$routes->post('monetization-other', 'PodcastController::monetizationOtherAction/$1', [
|
||||
'as' => 'podcast-monetization-other',
|
||||
'filter' => 'permission:podcast$1.edit',
|
||||
]);
|
||||
$routes->group('analytics', static function ($routes): void {
|
||||
$routes->get('/', 'PodcastController::viewAnalytics/$1', [
|
||||
'as' => 'podcast-analytics',
|
||||
|
@ -190,9 +190,6 @@ class EpisodeController extends BaseController
|
||||
->with('error', lang('Episode.messages.sameSlugError'));
|
||||
}
|
||||
|
||||
$db = db_connect();
|
||||
$db->transStart();
|
||||
|
||||
$newEpisode = new Episode([
|
||||
'created_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
@ -217,11 +214,10 @@ class EpisodeController extends BaseController
|
||||
'season_number' => $this->request->getPost('season_number')
|
||||
? (int) $this->request->getPost('season_number')
|
||||
: null,
|
||||
'type' => $this->request->getPost('type'),
|
||||
'is_blocked' => $this->request->getPost('block') === 'yes',
|
||||
'custom_rss_string' => $this->request->getPost('custom_rss'),
|
||||
'is_premium' => $this->request->getPost('premium') === 'yes',
|
||||
'published_at' => null,
|
||||
'type' => $this->request->getPost('type'),
|
||||
'is_blocked' => $this->request->getPost('block') === 'yes',
|
||||
'is_premium' => $this->request->getPost('premium') === 'yes',
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
$transcriptChoice = $this->request->getPost('transcript-choice');
|
||||
@ -244,32 +240,12 @@ class EpisodeController extends BaseController
|
||||
|
||||
$episodeModel = new EpisodeModel();
|
||||
if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) {
|
||||
$db->transRollback();
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episodeModel->errors());
|
||||
}
|
||||
|
||||
// update podcast's episode_description_footer_markdown if changed
|
||||
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
|
||||
'description_footer'
|
||||
) === '' ? null : $this->request->getPost('description_footer');
|
||||
|
||||
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
|
||||
$podcastModel = new PodcastModel();
|
||||
|
||||
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||
$db->transRollback();
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $podcastModel->errors());
|
||||
}
|
||||
}
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('episode-view', [$this->podcast->id, $newEpisodeId])->with(
|
||||
'message',
|
||||
lang('Episode.messages.createSuccess')
|
||||
@ -330,7 +306,6 @@ class EpisodeController extends BaseController
|
||||
$this->episode->season_number = $this->request->getPost('season_number') ?: null;
|
||||
$this->episode->type = $this->request->getPost('type');
|
||||
$this->episode->is_blocked = $this->request->getPost('block') === 'yes';
|
||||
$this->episode->custom_rss_string = $this->request->getPost('custom_rss');
|
||||
$this->episode->is_premium = $this->request->getPost('premium') === 'yes';
|
||||
|
||||
$this->episode->updated_by = (int) user_id();
|
||||
@ -376,39 +351,14 @@ class EpisodeController extends BaseController
|
||||
$this->episode->chapters_remote_url = $chaptersRemoteUrl === '' ? null : $chaptersRemoteUrl;
|
||||
}
|
||||
|
||||
$db = db_connect();
|
||||
$db->transStart();
|
||||
|
||||
$episodeModel = new EpisodeModel();
|
||||
|
||||
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||
$db->transRollback();
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episodeModel->errors());
|
||||
}
|
||||
|
||||
// update podcast's episode_description_footer_markdown if changed
|
||||
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
|
||||
'description_footer'
|
||||
) === '' ? null : $this->request->getPost('description_footer');
|
||||
|
||||
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
|
||||
$podcastModel = new PodcastModel();
|
||||
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||
$db->transRollback();
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $podcastModel->errors());
|
||||
}
|
||||
}
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('episode-edit', [$this->podcast->id, $this->episode->id])->with(
|
||||
'message',
|
||||
lang('Episode.messages.editSuccess')
|
||||
|
@ -213,18 +213,14 @@ class PodcastController extends BaseController
|
||||
'parental_advisory' => $this->request->getPost('parental_advisory') !== 'undefined'
|
||||
? $this->request->getPost('parental_advisory')
|
||||
: null,
|
||||
'owner_name' => $this->request->getPost('owner_name'),
|
||||
'owner_email' => $this->request->getPost('owner_email'),
|
||||
'is_owner_email_removed_from_feed' => $this->request->getPost('is_owner_email_removed_from_feed') === 'yes',
|
||||
'publisher' => $this->request->getPost('publisher'),
|
||||
'type' => $this->request->getPost('type'),
|
||||
'medium' => $this->request->getPost('medium'),
|
||||
'copyright' => $this->request->getPost('copyright'),
|
||||
'location' => $this->request->getPost('location_name') === '' ? null : new Location(
|
||||
'owner_name' => $this->request->getPost('owner_name'),
|
||||
'owner_email' => $this->request->getPost('owner_email'),
|
||||
'publisher' => $this->request->getPost('publisher'),
|
||||
'type' => $this->request->getPost('type'),
|
||||
'copyright' => $this->request->getPost('copyright'),
|
||||
'location' => $this->request->getPost('location_name') === '' ? null : new Location(
|
||||
$this->request->getPost('location_name')
|
||||
),
|
||||
'verify_txt' => $this->request->getPost('verify_txt'),
|
||||
'custom_rss_string' => $this->request->getPost('custom_rss'),
|
||||
'is_blocked' => $this->request->getPost('block') === 'yes',
|
||||
'is_completed' => $this->request->getPost('complete') === 'yes',
|
||||
'is_locked' => $this->request->getPost('lock') === 'yes',
|
||||
@ -253,10 +249,6 @@ class PodcastController extends BaseController
|
||||
$this->request->getPost('other_categories') ?? [],
|
||||
);
|
||||
|
||||
// OP3
|
||||
service('settings')
|
||||
->set('Analytics.enableOP3', $this->request->getPost('enable_op3') === 'yes', 'podcast:' . $newPodcastId);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('podcast-view', [$newPodcastId])->with(
|
||||
@ -314,19 +306,11 @@ class PodcastController extends BaseController
|
||||
$this->podcast->publisher = $this->request->getPost('publisher');
|
||||
$this->podcast->owner_name = $this->request->getPost('owner_name');
|
||||
$this->podcast->owner_email = $this->request->getPost('owner_email');
|
||||
$this->podcast->is_owner_email_removed_from_feed = $this->request->getPost(
|
||||
'is_owner_email_removed_from_feed'
|
||||
) === 'yes';
|
||||
$this->podcast->type = $this->request->getPost('type');
|
||||
$this->podcast->medium = $this->request->getPost('medium');
|
||||
$this->podcast->copyright = $this->request->getPost('copyright');
|
||||
$this->podcast->location = $this->request->getPost('location_name') === '' ? null : new Location(
|
||||
$this->request->getPost('location_name')
|
||||
);
|
||||
$this->podcast->verify_txt = $this->request->getPost('verify_txt') === '' ? null : $this->request->getPost(
|
||||
'verify_txt'
|
||||
);
|
||||
$this->podcast->custom_rss_string = $this->request->getPost('custom_rss');
|
||||
$this->podcast->new_feed_url = $this->request->getPost('new_feed_url') === '' ? null : $this->request->getPost(
|
||||
'new_feed_url'
|
||||
);
|
||||
@ -359,14 +343,6 @@ class PodcastController extends BaseController
|
||||
$this->request->getPost('other_categories') ?? [],
|
||||
);
|
||||
|
||||
// enable/disable OP3?
|
||||
service('settings')
|
||||
->set(
|
||||
'Analytics.enableOP3',
|
||||
$this->request->getPost('enable_op3') === 'yes',
|
||||
'podcast:' . $this->podcast->id
|
||||
);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('podcast-edit', [$this->podcast->id])->with(
|
||||
@ -375,53 +351,6 @@ class PodcastController extends BaseController
|
||||
);
|
||||
}
|
||||
|
||||
public function monetizationOther(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/monetization_other', $data);
|
||||
}
|
||||
|
||||
public function monetizationOtherAction(): RedirectResponse
|
||||
{
|
||||
if (
|
||||
($partnerId = $this->request->getPost('partner_id')) === '' ||
|
||||
($partnerLinkUrl = $this->request->getPost('partner_link_url')) === '' ||
|
||||
($partnerImageUrl = $this->request->getPost('partner_image_url')) === '') {
|
||||
$partnerId = null;
|
||||
$partnerLinkUrl = null;
|
||||
$partnerImageUrl = null;
|
||||
}
|
||||
|
||||
$this->podcast->payment_pointer = $this->request->getPost(
|
||||
'payment_pointer'
|
||||
) === '' ? null : $this->request->getPost('payment_pointer');
|
||||
|
||||
$this->podcast->partner_id = $partnerId;
|
||||
$this->podcast->partner_link_url = $partnerLinkUrl;
|
||||
$this->podcast->partner_image_url = $partnerImageUrl;
|
||||
|
||||
$podcastModel = new PodcastModel();
|
||||
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $podcastModel->errors());
|
||||
}
|
||||
|
||||
return redirect()->route('podcast-monetization-other', [$this->podcast->id])->with(
|
||||
'message',
|
||||
lang('Podcast.messages.editSuccess')
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteBanner(): RedirectResponse
|
||||
{
|
||||
if (! $this->podcast->banner instanceof Image) {
|
||||
|
@ -37,17 +37,4 @@ class Analytics extends BaseConfig
|
||||
* Z&|qECKBrwgaaD>~;U/tXG1U%tSe_oi5Tzy)h>}5NC2npSrjvM0w_Q>cs=0o=H]*
|
||||
*/
|
||||
public string $salt = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* The Open Podcast Prefix Project Config
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public array $OP3 = [
|
||||
'host' => 'https://op3.dev/',
|
||||
];
|
||||
|
||||
public bool $enableOP3 = false;
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Analytics;
|
||||
|
||||
use App\Entities\Episode;
|
||||
|
||||
class OP3
|
||||
{
|
||||
protected string $host;
|
||||
|
||||
/**
|
||||
* @param array<string, string> $config
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->host = rtrim($config['host'], '/');
|
||||
}
|
||||
|
||||
public function wrap(string $audioURL, Episode $episode): string
|
||||
{
|
||||
// remove scheme from audioURI if https
|
||||
$audioURIWithoutHTTPS = preg_replace('(^https://)', '', $audioURL);
|
||||
|
||||
return $this->host . '/e,pg=' . $episode->podcast->guid . '/' . $audioURIWithoutHTTPS;
|
||||
}
|
||||
}
|
@ -72,7 +72,6 @@ class EpisodeController extends Controller
|
||||
{
|
||||
$episode->cover_url = $episode->getCover()
|
||||
->file_url;
|
||||
$episode->audio_url = $episode->getAudioUrl();
|
||||
$episode->duration = round($episode->audio->duration);
|
||||
|
||||
return $episode;
|
||||
|
@ -16,6 +16,7 @@ use Modules\Admin\Controllers\BaseController;
|
||||
use Modules\Plugins\Core\BasePlugin;
|
||||
use Modules\Plugins\Core\Markdown;
|
||||
use Modules\Plugins\Core\Plugins;
|
||||
use Modules\Plugins\Core\RSS;
|
||||
use Modules\Plugins\Manifest\Field;
|
||||
|
||||
class PluginController extends BaseController
|
||||
@ -330,6 +331,7 @@ class PluginController extends BaseController
|
||||
$this->request->getPost('client_timezone')
|
||||
)->setTimezone(app_timezone()),
|
||||
'markdown' => new Markdown($value),
|
||||
'rss' => new RSS($value),
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ namespace Modules\Plugins\Core;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use League\CommonMark\Environment\Environment;
|
||||
use League\CommonMark\Event\DocumentParsedEvent;
|
||||
@ -86,7 +86,7 @@ abstract class BasePlugin implements PluginInterface
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
|
||||
public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void
|
||||
{
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ abstract class BasePlugin implements PluginInterface
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void
|
||||
public function rssAfterItem(Episode $episode, RssFeed $item): void
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -6,17 +6,17 @@ namespace Modules\Plugins\Core;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
|
||||
interface PluginInterface
|
||||
{
|
||||
public function rssBeforeChannel(Podcast $podcast): void;
|
||||
|
||||
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void;
|
||||
public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void;
|
||||
|
||||
public function rssBeforeItem(Episode $episode): void;
|
||||
|
||||
public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void;
|
||||
public function rssAfterItem(Episode $episode, RssFeed $item): void;
|
||||
|
||||
public function siteHead(): void;
|
||||
}
|
||||
|
@ -6,15 +6,15 @@ namespace Modules\Plugins\Core;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
use Config\Database;
|
||||
use Modules\Plugins\Config\Plugins as PluginsConfig;
|
||||
|
||||
/**
|
||||
* @method void rssBeforeChannel(Podcast $podcast)
|
||||
* @method void rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel)
|
||||
* @method void rssAfterChannel(Podcast $podcast, RssFeed $channel)
|
||||
* @method void rssBeforeItem(Episode $episode)
|
||||
* @method void rssAfterItem(Episode $episode, SimpleRSSElement $item)
|
||||
* @method void rssAfterItem(Episode $episode, RssFeed $item)
|
||||
* @method void siteHead()
|
||||
*/
|
||||
class Plugins
|
||||
@ -28,25 +28,27 @@ class Plugins
|
||||
'checkbox' => ['permit_empty'],
|
||||
'datetime' => ['valid_date[Y-m-d H:i]'],
|
||||
'email' => ['valid_email'],
|
||||
'group' => ['permit_empty', 'is_list'],
|
||||
'markdown' => ['string'],
|
||||
'number' => ['integer'],
|
||||
'radio-group' => ['string'],
|
||||
'rss' => ['string'],
|
||||
'select' => ['string'],
|
||||
'select-multiple' => ['permit_empty', 'is_list'],
|
||||
'text' => ['string'],
|
||||
'textarea' => ['string'],
|
||||
'toggler' => ['permit_empty'],
|
||||
'url' => ['valid_url_strict'],
|
||||
'group' => ['permit_empty', 'is_list'],
|
||||
];
|
||||
|
||||
public const FIELDS_CASTS = [
|
||||
'checkbox' => 'bool',
|
||||
'datetime' => 'datetime',
|
||||
'markdown' => 'markdown',
|
||||
'number' => 'int',
|
||||
'rss' => 'rss',
|
||||
'toggler' => 'bool',
|
||||
'url' => 'uri',
|
||||
'markdown' => 'markdown',
|
||||
];
|
||||
|
||||
/**
|
||||
|
43
modules/Plugins/Core/RSS.php
Normal file
43
modules/Plugins/Core/RSS.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Plugins\Core;
|
||||
|
||||
use App\Libraries\RssFeed;
|
||||
use Exception;
|
||||
use Override;
|
||||
use Stringable;
|
||||
|
||||
class RSS implements Stringable
|
||||
{
|
||||
public function __construct(
|
||||
protected string $rss
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->rss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?RssFeed[]
|
||||
*/
|
||||
public function toSimpleRSS(): ?array
|
||||
{
|
||||
try {
|
||||
$rssFeed = new RssFeed("{$this->rss}");
|
||||
} catch (Exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
...$rssFeed->children(),
|
||||
...$rssFeed->children(RssFeed::ATOM_NS, true),
|
||||
...$rssFeed->children(RssFeed::ITUNES_NS, true),
|
||||
...$rssFeed->children(RssFeed::PODCAST_NS, true),
|
||||
];
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ namespace Modules\Plugins\Manifest;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* @property 'checkbox'|'datetime'|'email'|'markdown'|'number'|'radio-group'|'select-multiple'|'select'|'text'|'textarea'|'toggler'|'url'|'group' $type
|
||||
* @property 'checkbox'|'datetime'|'email'|'group'|'markdown'|'number'|'radio-group'|'rss'|'select-multiple'|'select'|'text'|'textarea'|'toggler'|'url' $type
|
||||
* @property string $key
|
||||
* @property string $label
|
||||
* @property string $hint
|
||||
@ -20,7 +20,7 @@ use Override;
|
||||
class Field extends ManifestObject
|
||||
{
|
||||
protected const VALIDATION_RULES = [
|
||||
'type' => 'permit_empty|in_list[checkbox,datetime,email,markdown,number,radio-group,select-multiple,select,text,textarea,toggler,url,group]',
|
||||
'type' => 'permit_empty|in_list[checkbox,datetime,email,group,markdown,number,radio-group,rss,select-multiple,select,text,textarea,toggler,url]',
|
||||
'key' => 'required|alpha_dash',
|
||||
'label' => 'required|string',
|
||||
'hint' => 'permit_empty|string',
|
||||
|
@ -14,7 +14,7 @@ use CodeIgniter\HTTP\URI;
|
||||
class Repository extends ManifestObject
|
||||
{
|
||||
protected const VALIDATION_RULES = [
|
||||
'type' => 'required|in_list[git]',
|
||||
'type' => 'permit_empty|in_list[git]',
|
||||
'url' => 'required|valid_url_strict',
|
||||
'directory' => 'permit_empty',
|
||||
];
|
||||
@ -26,7 +26,7 @@ class Repository extends ManifestObject
|
||||
'url' => URI::class,
|
||||
];
|
||||
|
||||
protected string $type;
|
||||
protected string $type = 'git';
|
||||
|
||||
protected URI $url;
|
||||
|
||||
|
@ -76,6 +76,7 @@
|
||||
"monetization",
|
||||
"podcasting2",
|
||||
"privacy",
|
||||
"productivity",
|
||||
"seo"
|
||||
]
|
||||
}
|
||||
@ -173,13 +174,14 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"group",
|
||||
"checkbox",
|
||||
"datetime",
|
||||
"email",
|
||||
"group",
|
||||
"markdown",
|
||||
"number",
|
||||
"radio-group",
|
||||
"rss",
|
||||
"select-multiple",
|
||||
"select",
|
||||
"text",
|
||||
|
@ -104,53 +104,46 @@ class FakeSinglePodcastApiSeeder extends Seeder
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: int, guid: string, actor_id: int, handle: string, title: string, description_markdown: string, description_html: string, cover_id: int, banner_id: int, language_code: string, category_id: int, parental_advisory: null, owner_name: string, owner_email: string, publisher: string, type: string, copyright: string, episode_description_footer_markdown: null, episode_description_footer_html: null, is_blocked: int, is_completed: int, is_locked: int, imported_feed_url: null, new_feed_url: null, payment_pointer: null, location_name: null, location_geo: null, location_osm: null, custom_rss: null, is_published_on_hubs: int, partner_id: null, partner_link_url: null, partner_image_url: null, created_by: int, updated_by: int, created_at: string, updated_at: string}
|
||||
* @return array{id: int, guid: string, actor_id: int, handle: string, title: string, description_markdown: string, description_html: string, cover_id: int, banner_id: int, language_code: string, category_id: int, parental_advisory: null, owner_name: string, owner_email: string, publisher: string, type: string, copyright: string, is_blocked: int, is_completed: int, is_locked: int, imported_feed_url: null, new_feed_url: null, location_name: null, location_geo: null, location_osm: null, is_published_on_hubs: int, created_by: int, updated_by: int, created_at: string, updated_at: string}
|
||||
*/
|
||||
public static function podcast(): array
|
||||
{
|
||||
return [
|
||||
'id' => 1,
|
||||
'guid' => '0d341200-0234-5de7-99a6-a7d02bea4ce2',
|
||||
'actor_id' => 1,
|
||||
'handle' => 'Handle',
|
||||
'title' => 'Title',
|
||||
'description_markdown' => 'description',
|
||||
'description_html' => '<p>description</p>',
|
||||
'cover_id' => 1,
|
||||
'banner_id' => 2,
|
||||
'language_code' => 'en',
|
||||
'category_id' => 1,
|
||||
'parental_advisory' => null,
|
||||
'owner_name' => 'Owner',
|
||||
'owner_email' => 'Owner@gmail.com',
|
||||
'publisher' => '',
|
||||
'type' => 'episodic',
|
||||
'copyright' => '',
|
||||
'episode_description_footer_markdown' => null,
|
||||
'episode_description_footer_html' => null,
|
||||
'is_blocked' => 0,
|
||||
'is_completed' => 0,
|
||||
'is_locked' => 1,
|
||||
'imported_feed_url' => null,
|
||||
'new_feed_url' => null,
|
||||
'payment_pointer' => null,
|
||||
'location_name' => null,
|
||||
'location_geo' => null,
|
||||
'location_osm' => null,
|
||||
'custom_rss' => null,
|
||||
'is_published_on_hubs' => 0,
|
||||
'partner_id' => null,
|
||||
'partner_link_url' => null,
|
||||
'partner_image_url' => null,
|
||||
'created_by' => 1,
|
||||
'updated_by' => 1,
|
||||
'created_at' => '2022-06-13 8:00:00',
|
||||
'updated_at' => '2022-06-13 8:00:00',
|
||||
'id' => 1,
|
||||
'guid' => '0d341200-0234-5de7-99a6-a7d02bea4ce2',
|
||||
'actor_id' => 1,
|
||||
'handle' => 'Handle',
|
||||
'title' => 'Title',
|
||||
'description_markdown' => 'description',
|
||||
'description_html' => '<p>description</p>',
|
||||
'cover_id' => 1,
|
||||
'banner_id' => 2,
|
||||
'language_code' => 'en',
|
||||
'category_id' => 1,
|
||||
'parental_advisory' => null,
|
||||
'owner_name' => 'Owner',
|
||||
'owner_email' => 'Owner@gmail.com',
|
||||
'publisher' => '',
|
||||
'type' => 'episodic',
|
||||
'copyright' => '',
|
||||
'is_blocked' => 0,
|
||||
'is_completed' => 0,
|
||||
'is_locked' => 1,
|
||||
'imported_feed_url' => null,
|
||||
'new_feed_url' => null,
|
||||
'location_name' => null,
|
||||
'location_geo' => null,
|
||||
'location_osm' => null,
|
||||
'is_published_on_hubs' => 0,
|
||||
'created_by' => 1,
|
||||
'updated_by' => 1,
|
||||
'created_at' => '2022-06-13 8:00:00',
|
||||
'updated_at' => '2022-06-13 8:00:00',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: int, podcast_id: int, guid: string, title: string, slug: string, audio_id: int, description_markdown: string, description_html: string, cover_id: int, transcript_id: null, transcript_remote_url: null, chapters_id: null, chapters_remote_url: null, parental_advisory: null, number: int, season_number: null, type: string, is_blocked: false, location_name: null, location_geo: null, location_osm: null, custom_rss: null, is_published_on_hubs: false, posts_count: int, comments_count: int, is_premium: false, created_by: int, updated_by: int, published_at: null, created_at: string, updated_at: string}
|
||||
* @return array{id:int,podcast_id:int,guid:string,title:string,slug:string,audio_id:int,description_markdown:string,description_html:string,cover_id:int,transcript_id:null,transcript_remote_url:null,chapters_id:null,chapters_remote_url:null,parental_advisory:null,number:int,season_number:null,type:string,is_blocked:false,location_name:null,location_geo:null,location_osm:null,is_published_on_hubs:false,posts_count:int,comments_count:int,is_premium:false,created_by:int,updated_by:int,published_at:null,created_at:string,updated_at:string}
|
||||
*/
|
||||
public static function episode(): array
|
||||
{
|
||||
@ -176,7 +169,6 @@ class FakeSinglePodcastApiSeeder extends Seeder
|
||||
'location_name' => null,
|
||||
'location_geo' => null,
|
||||
'location_osm' => null,
|
||||
'custom_rss' => null,
|
||||
'is_published_on_hubs' => false,
|
||||
'posts_count' => 0,
|
||||
'comments_count' => 0,
|
||||
|
@ -6,7 +6,7 @@ namespace Tests\Modules\Plugins;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\DatabaseTestTrait;
|
||||
use Modules\Plugins\Config\Plugins as PluginsConfig;
|
||||
@ -96,7 +96,7 @@ final class PluginsTest extends CIUnitTestCase
|
||||
self::$plugins->runHook('rssBeforeChannel', [$podcast]);
|
||||
$this->assertEquals('Podcast test', $podcast->title);
|
||||
|
||||
$channel = new SimpleRSSElement('<channel></channel>');
|
||||
$channel = new RssFeed('<channel></channel>');
|
||||
$this->assertTrue(empty($channel->foo));
|
||||
self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]);
|
||||
$this->assertFalse(empty($channel->foo));
|
||||
@ -106,7 +106,7 @@ final class PluginsTest extends CIUnitTestCase
|
||||
self::$plugins->runHook('rssBeforeItem', [$episode]);
|
||||
$this->assertEquals('Episode test', $episode->title);
|
||||
|
||||
$item = new SimpleRSSElement('<item></item>');
|
||||
$item = new RssFeed('<item></item>');
|
||||
$this->assertTrue(empty($item->efoo));
|
||||
self::$plugins->runHook('rssAfterItem', [$episode, $item]);
|
||||
$this->assertFalse(empty($item->efoo));
|
||||
@ -133,7 +133,7 @@ final class PluginsTest extends CIUnitTestCase
|
||||
self::$plugins->runHook('rssBeforeChannel', [$podcast]);
|
||||
$this->assertEquals('', $podcast->title);
|
||||
|
||||
$channel = new SimpleRSSElement('<channel></channel>');
|
||||
$channel = new RssFeed('<channel></channel>');
|
||||
$this->assertTrue(empty($channel->foo));
|
||||
self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]);
|
||||
$this->assertTrue(empty($channel->foo));
|
||||
@ -143,7 +143,7 @@ final class PluginsTest extends CIUnitTestCase
|
||||
self::$plugins->runHook('rssBeforeItem', [$episode]);
|
||||
$this->assertEquals('', $episode->title);
|
||||
|
||||
$item = new SimpleRSSElement('<item></item>');
|
||||
$item = new RssFeed('<item></item>');
|
||||
$this->assertTrue(empty($item->efoo));
|
||||
self::$plugins->runHook('rssAfterItem', [$episode, $item]);
|
||||
$this->assertTrue(empty($item->efoo));
|
||||
@ -167,7 +167,7 @@ final class PluginsTest extends CIUnitTestCase
|
||||
$this->assertEquals('Podcast test undeclared', $podcast->title);
|
||||
|
||||
// rssAfterChannel has not been declared in plugin manifest, should not be running
|
||||
$channel = new SimpleRSSElement('<channel></channel>');
|
||||
$channel = new RssFeed('<channel></channel>');
|
||||
$this->assertTrue(empty($channel->foo));
|
||||
self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]);
|
||||
$this->assertTrue(empty($channel->foo));
|
||||
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
use Modules\Plugins\Core\BasePlugin;
|
||||
|
||||
class AcmeAllHooksPlugin extends BasePlugin
|
||||
@ -16,7 +16,7 @@ class AcmeAllHooksPlugin extends BasePlugin
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
|
||||
public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void
|
||||
{
|
||||
$channel->addChild('foo', 'bar');
|
||||
}
|
||||
@ -28,7 +28,7 @@ class AcmeAllHooksPlugin extends BasePlugin
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void
|
||||
public function rssAfterItem(Episode $episode, RssFeed $item): void
|
||||
{
|
||||
$item->addChild('efoo', 'ebar');
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
use Modules\Plugins\Core\BasePlugin;
|
||||
|
||||
class AcmeUndeclaredHookPlugin extends BasePlugin
|
||||
@ -15,7 +15,7 @@ class AcmeUndeclaredHookPlugin extends BasePlugin
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
|
||||
public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void
|
||||
{
|
||||
$channel->addChild('foo', 'bar');
|
||||
}
|
||||
|
@ -11,6 +11,12 @@ $episodeNavigation = [
|
||||
'embed-add' => 'episodes.edit',
|
||||
],
|
||||
],
|
||||
'plugins' => [
|
||||
'icon' => 'puzzle-fill', // @icon('puzzle-fill')
|
||||
'items' => [],
|
||||
'items-labels' => [],
|
||||
'items-permissions' => [],
|
||||
],
|
||||
'clips' => [
|
||||
'icon' => 'clapperboard-fill', // @icon('clapperboard-fill')
|
||||
'items' => ['video-clips-list', 'video-clips-create', 'soundbites-list', 'soundbites-create'],
|
||||
@ -24,12 +30,6 @@ $episodeNavigation = [
|
||||
'count-route' => 'video-clips-list',
|
||||
'add-cta' => 'video-clips-create',
|
||||
],
|
||||
'plugins' => [
|
||||
'icon' => 'puzzle-fill', // @icon('puzzle-fill')
|
||||
'items' => [],
|
||||
'items-labels' => [],
|
||||
'items-permissions' => [],
|
||||
],
|
||||
];
|
||||
|
||||
foreach (plugins()->getPluginsWithEpisodeSettings() as $plugin) {
|
||||
|
@ -127,14 +127,6 @@
|
||||
isRequired="true"
|
||||
disallowList="header,quote" />
|
||||
|
||||
<x-Forms.Field
|
||||
as="MarkdownEditor"
|
||||
name="description_footer"
|
||||
label="<?= esc(lang('Episode.form.description_footer')) ?>"
|
||||
hint="<?= esc(lang('Episode.form.description_footer_hint')) ?>"
|
||||
value="<?= esc($podcast->episode_description_footer_markdown) ?? '' ?>"
|
||||
disallowList="header,quote" />
|
||||
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section title="<?= lang('Episode.form.premium_title') ?>">
|
||||
@ -211,12 +203,6 @@
|
||||
title="<?= lang('Episode.form.advanced_section_title') ?>"
|
||||
subtitle="<?= lang('Episode.form.advanced_section_subtitle') ?>"
|
||||
>
|
||||
<x-Forms.Field
|
||||
as="XMLEditor"
|
||||
name="custom_rss"
|
||||
label="<?= esc(lang('Episode.form.custom_rss')) ?>"
|
||||
hint="<?= esc(lang('Episode.form.custom_rss_hint')) ?>"
|
||||
/>
|
||||
|
||||
<x-Forms.Toggler name="block" isChecked="false" hint="<?= esc(lang('Episode.form.block_hint')) ?>"><?= lang('Episode.form.block') ?></x-Forms.Toggler>
|
||||
|
||||
|
@ -132,14 +132,6 @@
|
||||
isRequired="true"
|
||||
disallowList="header,quote" />
|
||||
|
||||
<x-Forms.Field
|
||||
as="MarkdownEditor"
|
||||
name="description_footer"
|
||||
label="<?= esc(lang('Episode.form.description_footer')) ?>"
|
||||
hint="<?= esc(lang('Episode.form.description_footer_hint')) ?>"
|
||||
value="<?= esc($podcast->episode_description_footer_markdown) ?? '' ?>"
|
||||
disallowList="header,quote" />
|
||||
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section title="<?= lang('Episode.form.premium_title') ?>" >
|
||||
@ -279,13 +271,6 @@
|
||||
title="<?= lang('Episode.form.advanced_section_title') ?>"
|
||||
subtitle="<?= lang('Episode.form.advanced_section_subtitle') ?>"
|
||||
>
|
||||
<x-Forms.Field
|
||||
as="XMLEditor"
|
||||
name="custom_rss"
|
||||
label="<?= esc(lang('Episode.form.custom_rss')) ?>"
|
||||
hint="<?= esc(lang('Episode.form.custom_rss_hint')) ?>"
|
||||
content="<?= esc($episode->custom_rss_string) ?>"
|
||||
/>
|
||||
|
||||
<x-Forms.Toggler id="block" name="block" isChecked="<?= $episode->is_blocked ? 'true' : 'false' ?>" hint="<?= esc(lang('Episode.form.block_hint')) ?>"><?= lang('Episode.form.block') ?></x-Forms.Toggler>
|
||||
|
||||
|
@ -118,6 +118,18 @@ case 'markdown': ?>
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'rss': ?>
|
||||
<x-Forms.Field
|
||||
as="XMLEditor"
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
content="<?= htmlspecialchars($value) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'datetime': ?>
|
||||
<x-Forms.Field
|
||||
as="DatetimePicker"
|
||||
|
@ -67,13 +67,11 @@ $podcastNavigation = [
|
||||
'subscription-list',
|
||||
'subscription-create',
|
||||
'platforms-funding',
|
||||
'podcast-monetization-other',
|
||||
],
|
||||
'items-permissions' => [
|
||||
'subscription-list' => 'manage-subscriptions',
|
||||
'subscription-create' => 'manage-subscriptions',
|
||||
'platforms-funding' => 'manage-platforms',
|
||||
'podcast-monetization-other' => 'edit',
|
||||
'subscription-list' => 'manage-subscriptions',
|
||||
'subscription-create' => 'manage-subscriptions',
|
||||
'platforms-funding' => 'manage-platforms',
|
||||
],
|
||||
],
|
||||
'contributors' => [
|
||||
@ -135,15 +133,6 @@ foreach (plugins()->getPluginsWithPodcastSettings() as $plugin) {
|
||||
'class' => 'text-sm opacity-60',
|
||||
]) ?>
|
||||
</a>
|
||||
<?php if ($podcast->is_op3_enabled): ?>
|
||||
<a href="<?= $podcast->op3_url ?>" class="inline-flex items-center text-xs gap-x-1 group hover:underline" data-tooltip="bottom" target="_blank" rel="noopener noreferrer" title="<?= lang('Podcast.form.op3_link') ?>">
|
||||
<?= icon('line-chart-fill', [
|
||||
'class' => 'text-xl text-white inline-flex items-center justify-center rounded',
|
||||
]) . 'OP3' . icon('external-link-fill', [
|
||||
'class' => 'text-sm opacity-60',
|
||||
]) ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -58,29 +58,6 @@
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
/>
|
||||
|
||||
<x-Forms.RadioGroup
|
||||
label="<?= lang('Podcast.form.medium.label') ?>"
|
||||
name="medium"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.podcast'),
|
||||
'value' => 'podcast',
|
||||
'description' => lang('Podcast.form.medium.podcast_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.music'),
|
||||
'value' => 'music',
|
||||
'description' => lang('Podcast.form.medium.music_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.audiobook'),
|
||||
'value' => 'audiobook',
|
||||
'description' => lang('Podcast.form.medium.audiobook_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
/>
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section
|
||||
@ -148,9 +125,6 @@
|
||||
hint="<?= esc(lang('Podcast.form.owner_email_hint')) ?>"
|
||||
isRequired="true" />
|
||||
|
||||
<x-Forms.Toggler class="mt-2" name="is_owner_email_removed_from_feed" isChecked="true" hint="<?= esc(lang('Podcast.form.is_owner_email_removed_from_feed_hint')) ?>">
|
||||
<?= lang('Podcast.form.is_owner_email_removed_from_feed') ?></x-Forms.Toggler>
|
||||
|
||||
<x-Forms.Field
|
||||
name="publisher"
|
||||
label="<?= esc(lang('Podcast.form.publisher')) ?>"
|
||||
@ -188,16 +162,6 @@
|
||||
<?= lang('Podcast.form.premium_by_default') ?></x-Forms.Toggler>
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section
|
||||
title="<?= lang('Podcast.form.op3') ?>"
|
||||
subtitle="<?= lang('Podcast.form.op3_hint') ?>">
|
||||
|
||||
<a href="https://op3.dev" target="_blank" rel="noopener noreferrer" class="inline-flex self-start text-xs font-semibold underline gap-x-1 text-skin-muted hover:no-underline"><?= icon('link', [
|
||||
'class' => 'text-sm',
|
||||
]) ?>op3.dev</a>
|
||||
<x-Forms.Toggler name="enable_op3" isChecked="false" hint="<?= esc(lang('Podcast.form.op3_enable_hint')) ?>"><?= lang('Podcast.form.op3_enable') ?></x-Forms.Toggler>
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section
|
||||
title="<?= lang('Podcast.form.location_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.location_section_subtitle') ?>" >
|
||||
@ -212,21 +176,6 @@
|
||||
<x-Forms.Section
|
||||
title="<?= lang('Podcast.form.advanced_section_title') ?>" >
|
||||
|
||||
<x-Forms.Field
|
||||
as="XMLEditor"
|
||||
name="custom_rss"
|
||||
label="<?= esc(lang('Podcast.form.custom_rss')) ?>"
|
||||
hint="<?= esc(lang('Podcast.form.custom_rss_hint')) ?>"
|
||||
rows="8" />
|
||||
|
||||
<x-Forms.Field
|
||||
as="Textarea"
|
||||
name="verify_txt"
|
||||
label="<?= esc(lang('Podcast.form.verify_txt')) ?>"
|
||||
hint="<?= esc(lang('Podcast.form.verify_txt_hint')) ?>"
|
||||
helper="<?= esc(lang('Podcast.form.verify_txt_helper')) ?>"
|
||||
rows="5" />
|
||||
|
||||
<x-Forms.Toggler class="mb-2" name="lock" isChecked="true" hint="<?= esc(lang('Podcast.form.lock_hint')) ?>">
|
||||
<?= lang('Podcast.form.lock') ?>
|
||||
</x-Forms.Toggler>
|
||||
|
@ -83,29 +83,6 @@
|
||||
isRequired="true"
|
||||
/>
|
||||
|
||||
<x-Forms.RadioGroup
|
||||
label="<?= lang('Podcast.form.medium.label') ?>"
|
||||
name="medium"
|
||||
value="<?= $podcast->medium ?>"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.podcast'),
|
||||
'value' => 'podcast',
|
||||
'description' => lang('Podcast.form.medium.podcast_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.music'),
|
||||
'value' => 'music',
|
||||
'description' => lang('Podcast.form.medium.music_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.audiobook'),
|
||||
'value' => 'audiobook',
|
||||
'description' => lang('Podcast.form.medium.audiobook_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
/>
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section
|
||||
@ -178,9 +155,6 @@
|
||||
hint="<?= esc(lang('Podcast.form.owner_email_hint')) ?>"
|
||||
isRequired="true" />
|
||||
|
||||
<x-Forms.Toggler class="mt-2" name="is_owner_email_removed_from_feed" isChecked="<?= $podcast->is_owner_email_removed_from_feed ? 'true' : 'false' ?>" hint="<?= esc(lang('Podcast.form.is_owner_email_removed_from_feed_hint')) ?>">
|
||||
<?= lang('Podcast.form.is_owner_email_removed_from_feed') ?></x-Forms.Toggler>
|
||||
|
||||
<x-Forms.Field
|
||||
name="publisher"
|
||||
label="<?= esc(lang('Podcast.form.publisher')) ?>"
|
||||
@ -221,17 +195,6 @@
|
||||
<?= lang('Podcast.form.premium_by_default') ?></x-Forms.Toggler>
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section
|
||||
title="<?= lang('Podcast.form.op3') ?>"
|
||||
subtitle="<?= lang('Podcast.form.op3_hint') ?>">
|
||||
|
||||
<a href="https://op3.dev" target="_blank" rel="noopener noreferrer" class="inline-flex self-start text-xs font-semibold underline gap-x-1 text-skin-muted hover:no-underline"><?= icon('link', [
|
||||
'class' => 'text-sm',
|
||||
]) ?>op3.dev</a>
|
||||
<x-Forms.Toggler name="enable_op3" isChecked="<?= service('settings')
|
||||
->get('Analytics.enableOP3', 'podcast:' . $podcast->id) ? 'true' : 'false' ?>" hint="<?= esc(lang('Podcast.form.op3_enable_hint')) ?>"><?= lang('Podcast.form.op3_enable') ?></x-Forms.Toggler>
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Forms.Section
|
||||
title="<?= lang('Podcast.form.location_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.location_section_subtitle') ?>" >
|
||||
@ -248,23 +211,6 @@
|
||||
title="<?= lang('Podcast.form.advanced_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.advanced_section_subtitle') ?>" >
|
||||
|
||||
<x-Forms.Field
|
||||
as="XMLEditor"
|
||||
name="custom_rss"
|
||||
label="<?= esc(lang('Podcast.form.custom_rss')) ?>"
|
||||
hint="<?= esc(lang('Podcast.form.custom_rss_hint')) ?>"
|
||||
content="<?= esc($podcast->custom_rss_string) ?>"
|
||||
rows="8" />
|
||||
|
||||
<x-Forms.Field
|
||||
as="Textarea"
|
||||
name="verify_txt"
|
||||
label="<?= esc(lang('Podcast.form.verify_txt')) ?>"
|
||||
hint="<?= esc(lang('Podcast.form.verify_txt_hint')) ?>"
|
||||
helper="<?= esc(lang('Podcast.form.verify_txt_helper')) ?>"
|
||||
value="<?= esc($podcast->verify_txt) ?>"
|
||||
rows="5" />
|
||||
|
||||
<x-Forms.Field
|
||||
name="new_feed_url"
|
||||
type="url"
|
||||
|
@ -1,47 +0,0 @@
|
||||
<?= $this->extend('_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Podcast.all_podcasts') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('pageTitle') ?>
|
||||
<?= lang('Podcast.monetization_other') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<form id="podcast-edit-form" action="<?= route_to('podcast-monetization-edit', $podcast->id) ?>" method="POST" class="flex flex-col w-full max-w-xl gap-y-6">
|
||||
<?= csrf_field() ?>
|
||||
<x-Forms.Section
|
||||
title="<?= lang('Podcast.form.monetization_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.monetization_section_subtitle') ?>" >
|
||||
|
||||
<x-Forms.Field
|
||||
name="payment_pointer"
|
||||
label="<?= esc(lang('Podcast.form.payment_pointer')) ?>"
|
||||
value="<?= esc($podcast->payment_pointer) ?>"
|
||||
hint="<?= esc(lang('Podcast.form.payment_pointer_hint')) ?>" />
|
||||
|
||||
<fieldset class="flex flex-col items-start p-4 rounded bg-base">
|
||||
<x-Heading tagName="legend" class="float-left" size="small"><?= lang('Podcast.form.partnership') ?></x-Heading>
|
||||
<div class="flex flex-col w-full clear-left gap-x-2 gap-y-4 md:flex-row">
|
||||
<div class="flex flex-col flex-shrink w-32">
|
||||
<x-Forms.Label for="partner_id" hint="<?= esc(lang('Podcast.form.partner_id_hint')) ?>" isOptional="true"><?= lang('Podcast.form.partner_id') ?></x-Forms.Label>
|
||||
<x-Forms.Input name="partner_id" value="<?= esc($podcast->partner_id) ?>" />
|
||||
</div>
|
||||
<div class="flex flex-col flex-1">
|
||||
<x-Forms.Label for="partner_link_url" hint="<?= esc(lang('Podcast.form.partner_link_url_hint')) ?>" isOptional="true"><?= lang('Podcast.form.partner_link_url') ?></x-Forms.Label>
|
||||
<x-Forms.Input name="partner_link_url" value="<?= esc($podcast->partner_link_url) ?>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<x-Forms.Label for="partner_image_url" hint="<?= esc(lang('Podcast.form.partner_image_url_hint')) ?>" isOptional="true"><?= lang('Podcast.form.partner_image_url') ?></x-Forms.Label>
|
||||
<x-Forms.Input name="partner_image_url" value="<?= esc($podcast->partner_image_url) ?>" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</x-Forms.Section>
|
||||
|
||||
<x-Button variant="primary" type="submit" class="self-end"><?= lang('Common.forms.save') ?></x-Button>
|
||||
|
||||
</form>
|
||||
<?= $this->endSection() ?>
|
@ -148,9 +148,9 @@
|
||||
<div class="col-start-2 px-8 py-4 text-white bg-header">
|
||||
<h2 class="text-xs font-bold tracking-wider uppercase whitespace-pre-line font-display"><?= lang('Episode.description') ?></h2>
|
||||
<?php if (substr_count($episode->description_markdown, "\n") > 6 || strlen($episode->description) > 500): ?>
|
||||
<x-SeeMore class="max-w-xl prose-sm text-white"><?= $episode->getDescriptionHtml('-+Website+-') ?></x-SeeMore>
|
||||
<x-SeeMore class="max-w-xl prose-sm text-white"><?= $episode->description_html ?></x-SeeMore>
|
||||
<?php else: ?>
|
||||
<div class="max-w-xl prose-sm text-white"><?= $episode->getDescriptionHtml('-+Website+-') ?></div>
|
||||
<div class="max-w-xl prose-sm text-white"><?= $episode->description_html ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?= $this->include('episode/_partials/navigation') ?>
|
||||
|
@ -141,9 +141,9 @@
|
||||
<div class="col-start-2 px-8 py-4 text-white bg-header">
|
||||
<h2 class="text-xs font-bold tracking-wider uppercase whitespace-pre-line font-display"><?= lang('Episode.description') ?></h2>
|
||||
<?php if (substr_count($episode->description_markdown, "\n") > 6 || strlen($episode->description) > 500): ?>
|
||||
<x-SeeMore class="max-w-xl prose-sm text-white"><?= $episode->getDescriptionHtml('-+Website+-') ?></x-SeeMore>
|
||||
<x-SeeMore class="max-w-xl prose-sm text-white"><?= $episode->description_html ?></x-SeeMore>
|
||||
<?php else: ?>
|
||||
<div class="max-w-xl prose-sm text-white"><?= $episode->getDescriptionHtml('-+Website+-') ?></div>
|
||||
<div class="max-w-xl prose-sm text-white"><?= $episode->description_html ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?= $this->include('episode/_partials/navigation') ?>
|
||||
|
Loading…
x
Reference in New Issue
Block a user