Commit b869acb3 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

refactor: remove fields from podcast and episode entities to be replaced with plugins

parent 11ccd0eb
Loading
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
<?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);
    }
}
+37 −0
Original line number Diff line number Diff line
<?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);
    }
}
+34 −168
Original line number Diff line number Diff line
@@ -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 = '';

    protected ?Audio $audio = null;
    public string $audio_web_url = '';

    protected string $audio_url;
    public string $audio_opengraph_url = '';

    protected string $audio_web_url;
    protected Podcast $podcast;

    protected string $audio_opengraph_url;
    protected ?Audio $audio = null;

    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) {
+29 −148
Original line number Diff line number Diff line
@@ -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;

    /**
@@ -195,12 +180,8 @@ class Podcast extends Entity
        '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',
@@ -210,12 +191,7 @@ class Podcast extends Entity
        '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',
    ];
@@ -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;
    }
}
+47 −102

File changed.

Preview size limit exceeded, changes collapsed.

Loading