*/ protected $casts = [ 'id' => 'integer', 'podcast_id' => 'integer', 'guid' => 'string', 'slug' => 'string', 'title' => 'string', 'audio_file_path' => 'string', 'audio_file_duration' => 'double', 'audio_file_mimetype' => 'string', 'audio_file_size' => 'integer', 'audio_file_header_size' => 'integer', 'description_markdown' => 'string', 'description_html' => 'string', 'image_path' => '?string', 'image_mimetype' => '?string', 'transcript_file_path' => '?string', 'transcript_file_remote_url' => '?string', 'chapters_file_path' => '?string', 'chapters_file_remote_url' => '?string', 'parental_advisory' => '?string', 'number' => '?integer', 'season_number' => '?integer', 'type' => 'string', 'is_blocked' => 'boolean', 'location_name' => '?string', 'location_geo' => '?string', 'location_osm' => '?string', 'custom_rss' => '?json-array', 'favourites_total' => 'integer', 'reblogs_total' => 'integer', 'notes_total' => 'integer', 'created_by' => 'integer', 'updated_by' => 'integer', ]; /** * Saves an episode image */ public function setImage(?Image $image = null): static { if ($image === null) { return $this; } // Save image $image->saveImage('podcasts/' . $this->getPodcast()->name, $this->attributes['slug'],); $this->attributes['image_mimetype'] = $image->mimetype; $this->attributes['image_path'] = $image->path; return $this; } public function getImage(): Image { if ($imagePath = $this->attributes['image_path']) { return new Image(null, $imagePath, $this->attributes['image_mimetype'],); } return $this->podcast->image; } /** * Saves an audio file */ public function setAudioFile(UploadedFile | File $audioFile): static { helper(['media', 'id3']); $audioMetadata = get_file_tags($audioFile); $this->attributes['audio_file_path'] = save_media( $audioFile, 'podcasts/' . $this->getPodcast()->name, $this->attributes['slug'], ); $this->attributes['audio_file_duration'] = $audioMetadata['playtime_seconds']; $this->attributes['audio_file_mimetype'] = $audioMetadata['mime_type']; $this->attributes['audio_file_size'] = $audioMetadata['filesize']; $this->attributes['audio_file_header_size'] = $audioMetadata['avdataoffset']; return $this; } /** * Saves an episode transcript file */ public function setTranscriptFile(UploadedFile | File $transcriptFile): static { helper('media'); $this->attributes['transcript_file_path'] = save_media( $transcriptFile, $this->getPodcast() ->name, $this->attributes['slug'] . '-transcript', ); return $this; } /** * Saves an episode chapters file */ public function setChaptersFile(UploadedFile | File $chaptersFile): static { helper('media'); $this->attributes['chapters_file_path'] = save_media( $chaptersFile, $this->getPodcast() ->name, $this->attributes['slug'] . '-chapters', ); return $this; } public function getAudioFile(): File { helper('media'); return new File(media_path($this->audio_file_path)); } public function getTranscriptFile(): ?File { if ($this->attributes['transcript_file_path']) { helper('media'); return new File(media_path($this->attributes['transcript_file_path']),); } return null; } public function getChaptersFile(): ?File { if ($this->attributes['chapters_file_path']) { helper('media'); return new File(media_path($this->attributes['chapters_file_path']),); } return null; } public function getAudioFileUrl(): string { helper('media'); return media_base_url($this->audio_file_path); } public function getAudioFileAnalyticsUrl(): string { helper('analytics'); return generate_episode_analytics_url( $this->podcast_id, $this->id, $this->audio_file_path, $this->audio_file_duration, $this->audio_file_size, $this->audio_file_header_size, $this->published_at, ); } public function getAudioFileWebUrl(): string { return $this->getAudioFileAnalyticsUrl() . '?_from=-+Website+-'; } public function getAudioFileOpengraphUrl(): string { return $this->getAudioFileAnalyticsUrl() . '?_from=-+Open+Graph+-'; } /** * Gets transcript url from transcript file uri if it exists or returns the transcript_file_remote_url which can be * null. */ public function getTranscriptFileUrl(): ?string { if ($this->attributes['transcript_file_path']) { return media_base_url($this->attributes['transcript_file_path']); } return $this->attributes['transcript_file_remote_url']; } /** * Gets chapters file url from chapters file uri if it exists or returns the chapters_file_remote_url which can be * null. */ public function getChaptersFileUrl(): ?string { if ($this->chapters_file_path) { return media_base_url($this->chapters_file_path); } return $this->chapters_file_remote_url; } /** * Returns the episode's persons * * @return Person[] */ public function getPersons(): array { if ($this->id === null) { throw new RuntimeException('Episode must be created before getting persons.',); } if ($this->persons === null) { $this->persons = (new PersonModel())->getEpisodePersons($this->podcast_id, $this->id,); } return $this->persons; } /** * Returns the episode’s soundbites * * @return Soundbite[] */ public function getSoundbites(): array { if ($this->id === null) { throw new RuntimeException('Episode must be created before getting soundbites.',); } if ($this->soundbites === null) { $this->soundbites = (new SoundbiteModel())->getEpisodeSoundbites($this->getPodcast() ->id, $this->id,); } return $this->soundbites; } /** * @return Note[] */ public function getNotes(): array { if ($this->id === null) { throw new RuntimeException('Episode must be created before getting soundbites.',); } if ($this->notes === null) { $this->notes = (new NoteModel())->getEpisodeNotes($this->id); } return $this->notes; } public function getLink(): string { return base_url(route_to('episode', $this->getPodcast() ->name, $this->attributes['slug'],),); } public function getEmbeddablePlayerUrl(string $theme = null): string { return base_url( $theme ? route_to( 'embeddable-player-theme', $this->getPodcast() ->name, $this->attributes['slug'], $theme, ) : route_to('embeddable-player', $this->getPodcast() ->name, $this->attributes['slug'],), ); } public function setGuid(?string $guid = null): static { $this->attributes['guid'] = $guid === null ? $this->getLink() : $guid; return $this; } public function getPodcast(): ?Podcast { return (new PodcastModel())->getPodcastById($this->attributes['podcast_id'],); } public function setDescriptionMarkdown(string $descriptionMarkdown): static { $converter = new CommonMarkConverter([ 'html_input' => 'strip', 'allow_unsafe_links' => false, ]); $this->attributes['description_markdown'] = $descriptionMarkdown; $this->attributes['description_html'] = $converter->convertToHtml($descriptionMarkdown,); 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 .= "
getPartnerLink( $serviceSlug, )}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\">getPartnerImageUrl( $serviceSlug, )}\" alt=\"Partner image\" />
"; } $descriptionHtml .= $this->attributes['description_html']; if ($this->getPodcast()->episode_description_footer_html) { $descriptionHtml .= ""; } return $descriptionHtml; } public function getDescription(): string { if ($this->description === null) { $this->description = trim( preg_replace('~\s+~', ' ', strip_tags($this->attributes['description_html']),), ); } return $this->description; } public function getPublicationStatus(): string { if ($this->publication_status === null) { if ($this->published_at === null) { $this->publication_status = 'not_published'; } elseif ($this->published_at->isBefore(Time::now())) { $this->publication_status = 'published'; } else { $this->publication_status = 'scheduled'; } } return $this->publication_status; } /** * Saves the location name and fetches OpenStreetMap info */ public function setLocation(?Location $location = null): static { if ($location === null) { $this->attributes['location_name'] = null; $this->attributes['location_geo'] = null; $this->attributes['location_osm'] = null; return $this; } if ( ! isset($this->attributes['location_name']) || $this->attributes['location_name'] !== $location->name ) { $location->fetchOsmLocation(); $this->attributes['location_name'] = $location->name; $this->attributes['location_geo'] = $location->geo; $this->attributes['location_osm'] = $location->osm; } return $this; } 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,); } 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( '', )) ->addChild('channel') ->addChild('item'); array_to_rss([ 'elements' => $this->custom_rss, ], $xmlNode,); return str_replace(['', ''], '', $xmlNode->asXML()); } /** * Saves custom rss tag into json */ public function setCustomRssString(?string $customRssString = null): static { if ($customRssString === null) { return $this; } helper('rss'); $customRssArray = rss_to_array( simplexml_load_string( '' . $customRssString . '', ), )['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($this->getPodcast()->partner_link_url, '/') . '?pid=' . $this->getPodcast() ->partner_id . '&guid=' . urlencode($this->attributes['guid']); if ($serviceSlug !== null) { $partnerLink .= '&_from=' . $serviceSlug; } return $partnerLink; } public function getPartnerImageUrl(string $serviceSlug = null): string { if ($serviceSlug !== null) { return '&_from=' . $serviceSlug; } return rtrim($this->getPodcast()->partner_image_url, '/') . '?pid=' . $this->getPodcast() ->partner_id . '&guid=' . urlencode($this->attributes['guid']); } }