Skip to content
Snippets Groups Projects
Podcast.php 18.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**
     * @copyright  2020 Podlibre
     * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
     * @link       https://castopod.org/
     */
    
    
    namespace App\Entities;
    
    
    use App\Entities\Media\Image;
    
    use App\Models\EpisodeModel;
    
    use CodeIgniter\HTTP\Files\UploadedFile;
    
    use CodeIgniter\I18n\Time;
    
    use League\CommonMark\Environment\Environment;
    use League\CommonMark\Extension\Autolink\AutolinkExtension;
    use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
    use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
    use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
    use League\CommonMark\MarkdownConverter;
    
     * @property string $guid
    
     * @property Actor|null $actor
    
     * @property string $link
     * @property string $feed_url
     * @property string $title
    
     * @property string|null $description Holds text only description, striped of any markdown or html special characters
    
     * @property string $description_markdown
     * @property  string $description_html
    
     * @property string $language_code
     * @property int $category_id
    
     * @property Category|null $category
    
     * @property int[] $other_categories_ids
     * @property Category[] $other_categories
     * @property string|null $parental_advisory
     * @property string|null $publisher
     * @property string $owner_name
     * @property string $owner_email
     * @property string $type
     * @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
     * @property string|null $imported_feed_url
     * @property string|null $new_feed_url
    
     * @property string|null $location_name
     * @property string|null $location_geo
    
     * @property string|null $payment_pointer
     * @property array|null $custom_rss
     * @property string $custom_rss_string
     * @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 Time $created_at;
     * @property Time $updated_at;
     * @property Time|null $deleted_at;
     *
     * @property Episode[] $episodes
    
     * @property Person[] $persons
    
     * @property User[] $contributors
     * @property Platform[] $podcasting_platforms
     * @property Platform[] $social_platforms
     * @property Platform[] $funding_platforms
     */
    
    class Podcast extends Entity
    {
    
        protected ?array $other_categories = null;
    
        protected ?array $other_categories_ids = null;
    
        protected ?array $contributors = null;
    
        protected ?array $podcasting_platforms = null;
    
        protected ?array $social_platforms = null;
    
        protected ?array $funding_platforms = null;
    
        protected string $custom_rss_string;
    
        protected $casts = [
    
            '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',
    
            'copyright' => '?string',
    
            'episode_description_footer_markdown' => '?string',
            'episode_description_footer_html' => '?string',
            'is_blocked' => 'boolean',
            'is_completed' => 'boolean',
            'is_locked' => 'boolean',
    
            'imported_feed_url' => '?string',
    
            'new_feed_url' => '?string',
    
            'location_name' => '?string',
            'location_geo' => '?string',
    
            'payment_pointer' => '?string',
    
            'partner_id' => '?string',
            'partner_link_url' => '?string',
            'partner_image_url' => '?string',
    
            'created_by' => 'integer',
            'updated_by' => 'integer',
    
        /**
         * @noRector ReturnTypeDeclarationRector
         */
        public function getActor(): ?Actor
    
            if ($this->actor_id === 0) {
    
                throw new RuntimeException('Podcast must have an actor_id before getting actor.');
    
            if ($this->actor === null) {
    
                // @phpstan-ignore-next-line
    
                $this->actor = model(ActorModel::class)
    
            // @phpstan-ignore-next-line
    
        public function setCover(UploadedFile | File $file = null): self
    
            if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
    
                return $this;
            }
    
            if (array_key_exists('cover_id', $this->attributes) && $this->attributes['cover_id'] !== null) {
                $this->getCover()
                    ->setFile($file);
                $this->getCover()
                    ->updated_by = (int) user_id();
                (new MediaModel('image'))->updateMedia($this->getCover());
            } else {
                $cover = new Image([
                    'file_name' => 'cover',
                    'file_directory' => 'podcasts/' . $this->attributes['handle'],
                    'sizes' => config('Images')
                        ->podcastCoverSizes,
                    'uploaded_by' => user_id(),
                    'updated_by' => user_id(),
                ]);
                $cover->setFile($file);
    
                $this->attributes['cover_id'] = (new MediaModel('image'))->saveMedia($cover);
            }
    
            return $this;
        }
    
    
            if (! $this->cover instanceof Image) {
                $this->cover = (new MediaModel('image'))->getMediaById($this->cover_id);
    
        public function setBanner(UploadedFile | File $file = null): self
    
            if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
    
                return $this;
            }
    
            if (array_key_exists('banner_id', $this->attributes) && $this->attributes['banner_id'] !== null) {
                $this->getBanner()
                    ->setFile($file);
                $this->getBanner()
                    ->updated_by = (int) user_id();
                (new MediaModel('image'))->updateMedia($this->getBanner());
            } else {
                $banner = new Image([
                    'file_name' => 'banner',
                    'file_directory' => 'podcasts/' . $this->attributes['handle'],
                    'sizes' => config('Images')
                        ->podcastBannerSizes,
                    'uploaded_by' => user_id(),
                    'updated_by' => user_id(),
                ]);
                $banner->setFile($file);
    
                $this->attributes['banner_id'] = (new MediaModel('image'))->saveMedia($banner);
            }
    
            return $this;
        }
    
    
                $defaultBanner = config('Images')
                    ->podcastBannerDefaultPaths[service('settings')->get('App.theme')] ?? config(
                        'Images'
                    )->podcastBannerDefaultPaths['default'];
    
                    'file_path' => $defaultBanner['path'],
                    'file_mimetype' => $defaultBanner['mimetype'],
    
                    'file_size' => 0,
                    'file_metadata' => [
                        'sizes' => config('Images')
                            ->podcastBannerSizes,
                    ],
                ]);
            }
    
            if (! $this->banner instanceof Image) {
                $this->banner = (new MediaModel('image'))->getMediaById($this->banner_id);
    
            return url_to('podcast-activity', $this->attributes['handle']);
    
            return url_to('podcast_feed', $this->attributes['handle']);
    
                throw new RuntimeException('Podcast must be created before getting episodes.');
    
                $this->episodes = (new EpisodeModel())->getPodcastEpisodes($this->id, $this->type);
    
        /**
         * Returns the podcast's persons
         *
    
                throw new RuntimeException('Podcast must be created before getting persons.');
    
                $this->persons = (new PersonModel())->getPodcastPersons($this->id);
    
        /**
         * Returns the podcast category entity
         */
    
        public function getCategory(): ?Category
    
                throw new RuntimeException('Podcast must be created before getting category.');
    
            if (! $this->category instanceof Category) {
    
                $this->category = (new CategoryModel())->getCategoryById($this->category_id);
    
                throw new RuntimeException('Podcasts must be created before getting contributors.');
    
                $this->contributors = (new UserModel())->getPodcastContributors($this->id);
    
        public function setDescriptionMarkdown(string $descriptionMarkdown): static
    
            ];
    
            $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['description_markdown'] = $descriptionMarkdown;
    
            $this->attributes['description_html'] = $converter->convert($descriptionMarkdown);
    
        public function setEpisodeDescriptionFooterMarkdown(?string $episodeDescriptionFooterMarkdown = null): static
        {
    
            if ($episodeDescriptionFooterMarkdown === null || $episodeDescriptionFooterMarkdown === '') {
    
                $this->attributes[
                    'episode_description_footer_markdown'
    
                $this->attributes[
                    'episode_description_footer_html'
    
                '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);
    
            if ($this->description === null) {
                $this->description = trim(
    
                    (string) preg_replace('~\s+~', ' ', strip_tags($this->attributes['description_html'])),
    
         * Returns the podcast's podcasting platform links
    
                throw new RuntimeException('Podcast must be created before getting podcasting platform links.');
    
            if ($this->podcasting_platforms === null) {
    
                $this->podcasting_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'podcasting');
    
            return $this->podcasting_platforms;
    
        }
    
        /**
         * Returns the podcast's social platform links
         *
    
                throw new RuntimeException('Podcast must be created before getting social platform links.');
    
            if ($this->social_platforms === null) {
    
                $this->social_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'social');
    
            return $this->social_platforms;
    
        }
    
        /**
         * Returns the podcast's funding platform links
         *
    
                throw new RuntimeException('Podcast must be created before getting funding platform links.');
    
            if ($this->funding_platforms === null) {
    
                $this->funding_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'funding');
    
            return $this->funding_platforms;
    
                throw new RuntimeException('Podcast must be created before getting other categories.');
    
            if ($this->other_categories === null) {
    
                $this->other_categories = (new CategoryModel())->getPodcastCategories($this->id);
    
            if ($this->other_categories_ids === null) {
    
                $this->other_categories_ids = array_column($this->getOtherCategories(), 'id');
    
    
        /**
         * Saves the location name and fetches OpenStreetMap info
         */
    
        public function setLocation(?Location $location = null): static
    
                $this->attributes['location_name'] = null;
                $this->attributes['location_geo'] = null;
    
                ! isset($this->attributes['location_name']) ||
    
                $this->attributes['location_name'] !== $location->name
    
                $this->attributes['location_name'] = $location->name;
                $this->attributes['location_geo'] = $location->geo;
                $this->attributes['location_osm'] = $location->osm;
    
        public function getLocation(): ?Location
        {
            if ($this->location_name === null) {
                return null;
            }
    
            if ($this->location === null) {
    
                $this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
    
        public function getCustomRssString(): string
    
            if ($this->attributes['custom_rss'] === null) {
    
    
            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://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>',
            ))->addChild('channel');
    
            array_to_rss([
                'elements' => $this->custom_rss,
    
    
            return str_replace(['<channel>', '</channel>'], '', $xmlNode->asXML());
    
        public function setCustomRssString(string $customRssString): static
    
                $this->attributes['custom_rss'] = null;
    
            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://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel>' .
                        $customRssString .
    
            if (array_key_exists('elements', $customRssArray)) {
    
                $this->attributes['custom_rss'] = json_encode($customRssArray['elements']);