Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • adaures/castopod
  • mkljczk/castopod-host
  • spaetz/castopod-host
  • PatrykMis/castopod
  • jonas/castopod
  • ajeremias/castopod
  • misuzu/castopod
  • KrzysztofDomanczyk/castopod
  • Behel/castopod
  • nebulon/castopod
  • ewen/castopod
  • NeoluxConsulting/castopod
  • nateritter/castopod-og
  • prcutler/castopod
14 results
Select Git revision
Show changes
Showing
with 980 additions and 880 deletions
<?php
/**
* Class TestSeeder
* Inserts a superadmin user in the database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
class TestSeeder extends Seeder
{
public function run()
{
/** Inserts an active user with the following credentials:
* username: admin
* password: AGUehL3P
*/
$this->db->table('users')->insert([
'id' => 1,
'username' => 'admin',
'email' => 'admin@example.com',
'password_hash' =>
'$2y$10$TXJEHX/djW8jtzgpDVf7dOOCGo5rv1uqtAYWdwwwkttQcDkAeB2.6',
'active' => 1,
]);
$this->db
->table('auth_groups_users')
->insert(['group_id' => 1, 'user_id' => 1]);
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use App\Models\PodcastModel;
use Modules\Fediverse\Entities\Actor as FediverseActor;
use Override;
use RuntimeException;
/**
* @property Podcast|null $podcast
* @property boolean $is_podcast
*/
class Actor extends FediverseActor
{
protected ?Podcast $podcast = null;
protected bool $is_podcast = false;
public function getIsPodcast(): bool
{
return $this->getPodcast() instanceof Podcast;
}
public function getPodcast(): ?Podcast
{
if ($this->id === null) {
throw new RuntimeException('Podcast id must be set before getting associated podcast.');
}
if (! $this->podcast instanceof Podcast) {
$this->podcast = (new PodcastModel())->getPodcastByActorId($this->id);
}
return $this->podcast;
}
#[Override]
public function getAvatarImageUrl(): string
{
if ($this->podcast instanceof Podcast) {
return $this->podcast->cover->thumbnail_url;
}
return parent::getAvatarImageUrl();
}
#[Override]
public function getAvatarImageMimetype(): string
{
if ($this->podcast instanceof Podcast) {
return $this->podcast->cover->thumbnail_mimetype;
}
return parent::getAvatarImageMimetype();
}
}
<?php
/**
* Class AnalyticsPodcasts
* Entity for AnalyticsPodcasts
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsPodcasts extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'date' => 'datetime',
'duration' => 'integer',
'bandwidth' => 'integer',
'unique_listeners' => 'integer',
'hits' => 'integer',
];
}
<?php
/**
* Class AnalyticsPodcastsByCountry
* Entity for AnalyticsPodcastsByCountry
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsPodcastsByCountry extends Entity
{
/**
* @var string
*/
protected $labels;
protected $casts = [
'podcast_id' => 'integer',
'country_code' => 'string',
'date' => 'datetime',
'hits' => 'integer',
];
public function getLabels()
{
return lang('Countries.' . $this->attributes['labels']);
}
}
<?php
/**
* Class AnalyticsPodcastsByEpisode
* Entity for AnalyticsPodcastsByEpisode
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsPodcastsByEpisode extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'episode_id' => 'integer',
'date' => 'datetime',
'hits' => 'integer',
];
}
<?php
/**
* Class AnalyticsPodcastsByHour
* Entity for AnalyticsPodcastsByHour
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsPodcastsByHour extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'date' => 'datetime',
'hour' => 'integer',
'hits' => 'integer',
];
}
<?php
/**
* Class AnalyticsPodcastsByPlayer
* Entity for AnalyticsPodcastsByPlayer
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsPodcastsByPlayer extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'app' => '?string',
'device' => '?string',
'os' => '?string',
'is_bot' => 'boolean',
'date' => 'datetime',
'hits' => 'integer',
];
}
<?php
/**
* Class AnalyticsPodcastsByRegion
* Entity for AnalyticsPodcastsByRegion
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsPodcastsByRegion extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'country_code' => 'string',
'region_code' => '?string',
'latitude' => '?float',
'longitude' => '?float',
'date' => 'datetime',
'hits' => 'integer',
];
public function getCountryCode()
{
return lang('Countries.' . $this->attributes['country_code']);
}
}
<?php
/**
* Class AnalyticsPodcastsByService
* Entity for AnalyticsPodcastsByService
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsPodcastsByService extends Entity
{
/**
* @var string
*/
protected $labels;
protected $casts = [
'podcast_id' => 'integer',
'app' => '?string',
'device' => '?string',
'os' => '?string',
'is_bot' => 'boolean',
'date' => 'datetime',
'hits' => 'integer',
];
public function getLabels()
{
return \Opawg\UserAgentsPhp\UserAgentsRSS::getName(
$this->attributes['labels']
) ?? $this->attributes['labels'];
}
}
<?php
/**
* Class AnalyticsUnknownUseragents
* Entity for AnalyticsUnknownUseragents
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsUnknownUseragents extends Entity
{
protected $casts = [
'useragent' => 'integer',
'hits' => 'integer',
];
}
<?php
/**
* Class AnalyticsWebsiteByBrowser
* Entity for AnalyticsWebsiteByBrowser
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsWebsiteByBrowser extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'browser' => 'string',
'date' => 'datetime',
'hits' => 'integer',
];
}
<?php
/**
* Class AnalyticsWebsiteByEntryPage
* Entity for AnalyticsWebsiteByEntryPage
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsWebsiteByEntryPage extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'entry_page_url' => 'string',
'date' => 'datetime',
'hits' => 'integer',
];
}
<?php
/**
* Class class AnalyticsWebsiteByReferer
* Entity for AnalyticsWebsiteByReferer
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class AnalyticsWebsiteByReferer extends Entity
{
protected $casts = [
'podcast_id' => 'integer',
'referer_url' => 'string',
'date' => 'datetime',
'hits' => 'integer',
];
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Podlibre
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
......@@ -9,29 +11,37 @@
namespace App\Entities;
use App\Models\CategoryModel;
use CodeIgniter\Entity;
use CodeIgniter\Entity\Entity;
/**
* @property int $id
* @property int $parent_id
* @property Category|null $parent
* @property string $code
* @property string $apple_category
* @property string $google_category
*/
class Category extends Entity
{
protected ?Category $parent = null;
/**
* @var \App\Entity\Category|null
* @var array<string, string>
*/
protected $parent;
protected $casts = [
'id' => 'integer',
'parent_id' => 'integer',
'code' => 'string',
'apple_category' => 'string',
'id' => 'integer',
'parent_id' => '?integer',
'code' => 'string',
'apple_category' => 'string',
'google_category' => 'string',
];
public function getParent()
public function getParent(): ?self
{
$parentId = $this->attributes['parent_id'];
if ($this->parent_id === null) {
return null;
}
return $parentId != 0
? (new CategoryModel())->findParent($parentId)
: null;
return (new CategoryModel())->getCategoryById($this->parent_id);
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities\Clip;
use App\Entities\Episode;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\I18n\Time;
use CodeIgniter\Shield\Entities\User;
use Modules\Auth\Models\UserModel;
use Modules\Media\Entities\Audio;
use Modules\Media\Entities\Video;
use Modules\Media\Models\MediaModel;
/**
* @property int $id
* @property int $podcast_id
* @property Podcast $podcast
* @property int $episode_id
* @property Episode $episode
* @property string $title
* @property double $start_time
* @property double $end_time
* @property double $duration
* @property string $type
* @property int|null $media_id
* @property Video|Audio|null $media
* @property array<mixed>|null $metadata
* @property string $status
* @property string $logs
* @property User $user
* @property int $created_by
* @property int $updated_by
* @property Time|null $job_started_at
* @property Time|null $job_ended_at
*/
class BaseClip extends Entity
{
/**
* @var Video|Audio|null
*/
protected $media;
protected ?int $job_duration = null;
protected ?float $end_time = null;
/**
* @var array<int, string>
* @phpstan-var list<string>
*/
protected $dates = ['created_at', 'updated_at', 'job_started_at', 'job_ended_at'];
/**
* @var array<string, string>
*/
protected $casts = [
'id' => 'integer',
'podcast_id' => 'integer',
'episode_id' => 'integer',
'title' => 'string',
'start_time' => 'double',
'duration' => 'double',
'type' => 'string',
'media_id' => '?integer',
'metadata' => '?json-array',
'status' => 'string',
'logs' => 'string',
'created_by' => 'integer',
'updated_by' => 'integer',
];
/**
* @param array<string, mixed>|null $data
*/
public function __construct(?array $data = null)
{
parent::__construct($data);
}
public function getJobDuration(): ?int
{
if ($this->job_duration === null && $this->job_started_at && $this->job_ended_at) {
$this->job_duration = ($this->job_started_at->difference($this->job_ended_at))
->getSeconds();
}
return $this->job_duration;
}
public function getEndTime(): float
{
if ($this->end_time === null) {
$this->end_time = $this->start_time + $this->duration;
}
return $this->end_time;
}
public function getPodcast(): ?Podcast
{
return (new PodcastModel())->getPodcastById($this->podcast_id);
}
public function getEpisode(): ?Episode
{
return (new EpisodeModel())->getEpisodeById($this->episode_id);
}
public function getUser(): ?User
{
/** @var ?User */
return (new UserModel())->find($this->created_by);
}
public function setMedia(File $file, string $fileKey): static
{
if ($this->media_id !== null) {
$this->getMedia()
->setFile($file);
$this->getMedia()
->updated_by = $this->attributes['updated_by'];
(new MediaModel('audio'))->updateMedia($this->getMedia());
} else {
$media = new Audio([
'file_key' => $fileKey,
'language_code' => $this->getPodcast()
->language_code,
'uploaded_by' => $this->attributes['updated_by'],
'updated_by' => $this->attributes['updated_by'],
]);
$media->setFile($file);
$this->attributes['media_id'] = (new MediaModel())->saveMedia($media);
}
return $this;
}
public function getMedia(): Audio | Video | null
{
if ($this->media_id !== null && $this->media === null) {
$this->media = (new MediaModel($this->type))->getMediaById($this->media_id);
}
return $this->media;
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities\Clip;
class Soundbite extends BaseClip
{
protected string $type = 'audio';
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities\Clip;
use CodeIgniter\Files\File;
use Modules\Media\Entities\Video;
use Modules\Media\Models\MediaModel;
use Override;
/**
* @property array{name:string,preview:string} $theme
* @property string $format
*/
class VideoClip extends BaseClip
{
protected string $type = 'video';
/**
* @param array<string, mixed>|null $data
*/
public function __construct(?array $data = null)
{
parent::__construct($data);
if ($this->metadata !== null && $this->metadata !== []) {
$this->theme = $this->metadata['theme'];
$this->format = $this->metadata['format'];
}
}
/**
* @param array{name:string,preview:string} $theme
*/
public function setTheme(array $theme): self
{
// TODO: change?
$this->attributes['metadata'] = json_decode($this->attributes['metadata'] ?? '[]', true);
$this->attributes['theme'] = $theme;
$this->attributes['metadata']['theme'] = $theme;
$this->attributes['metadata'] = json_encode($this->attributes['metadata']);
return $this;
}
public function setFormat(string $format): self
{
$this->attributes['metadata'] = json_decode((string) $this->attributes['metadata'], true);
$this->attributes['format'] = $format;
$this->attributes['metadata']['format'] = $format;
$this->attributes['metadata'] = json_encode($this->attributes['metadata']);
return $this;
}
#[Override]
public function setMedia(File $file, string $fileKey): static
{
if ($this->attributes['media_id'] !== null) {
// media is already set, do nothing
return $this;
}
$video = new Video([
'file_key' => $fileKey,
'language_code' => $this->getPodcast()
->language_code,
'uploaded_by' => $this->attributes['created_by'],
'updated_by' => $this->attributes['created_by'],
]);
$video->setFile($file);
$this->attributes['media_id'] = (new MediaModel('video'))->saveMedia($video);
return $this;
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Podlibre
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PodcastModel;
use App\Models\EpisodeModel;
use CodeIgniter\Entity;
use CodeIgniter\Entity\Entity;
use RuntimeException;
/**
* @property int $podcast_id
* @property Podcast|null $podcast
* @property int|null $episode_id
* @property Episode|null $episode
* @property string $full_name
* @property string $person_group
* @property string $group_label
* @property string $person_role
* @property string $role_label
* @property int $person_id
* @property Person|null $person
*/
class Credit extends Entity
{
/**
* @var \App\Entities\Person
*/
protected $person;
protected ?Person $person = null;
/**
* @var \App\Entities\Podcast
*/
protected $podcast;
protected ?Podcast $podcast = null;
/**
* @var \App\Entities\Episode|null
*/
protected $episode;
protected ?Episode $episode = null;
/**
* @var string
*/
protected $group_label;
protected string $group_label;
protected string $role_label;
/**
* @var string
* @var array<string, string>
*/
protected $role_label;
public function getPodcast()
protected $casts = [
'podcast_id' => 'integer',
'episode_id' => '?integer',
'person_id' => 'integer',
'full_name' => 'string',
'person_group' => 'string',
'person_role' => 'string',
];
public function getPerson(): ?Person
{
return (new PodcastModel())->getPodcastById(
$this->attributes['podcast_id'],
);
if ($this->person_id === null) {
throw new RuntimeException('Credit must have person_id before getting person.');
}
if (! $this->person instanceof Person) {
$this->person = (new PersonModel())->getPersonById($this->person_id);
}
return $this->person;
}
public function getEpisode()
public function getPodcast(): ?Podcast
{
if (empty($this->episode_id)) {
throw new \RuntimeException(
'Credit must have episode_id before getting episode.',
);
if ($this->podcast_id === null) {
throw new RuntimeException('Credit must have podcast_id before getting podcast.');
}
if (empty($this->episode)) {
$this->episode = (new EpisodeModel())->getPublishedEpisodeById(
$this->episode_id,
$this->podcast_id,
);
if (! $this->podcast instanceof Podcast) {
$this->podcast = (new PodcastModel())->getPodcastById($this->podcast_id);
}
return $this->episode;
return $this->podcast;
}
public function getPerson()
public function getEpisode(): ?Episode
{
if (empty($this->person_id)) {
throw new \RuntimeException(
'Credit must have person_id before getting person.',
);
if ($this->episode_id === null) {
throw new RuntimeException('Credit must have episode_id before getting episode.');
}
if (empty($this->person)) {
$this->person = (new PersonModel())->getPersonById(
$this->person_id,
);
if (! $this->episode instanceof Episode) {
$this->episode = (new EpisodeModel())->getPublishedEpisodeById($this->podcast_id, $this->episode_id);
}
return $this->person;
return $this->episode;
}
public function getGroupLabel()
public function getGroupLabel(): string
{
if (empty($this->person_group)) {
return null;
} else {
return lang("PersonsTaxonomy.persons.{$this->person_group}.label");
if ($this->person_group === null) {
return '';
}
/** @var string */
return lang("PersonsTaxonomy.persons.{$this->person_group}.label");
}
public function getRoleLabel()
public function getRoleLabel(): string
{
if (empty($this->person_group) || empty($this->person_role)) {
return null;
} else {
return lang(
"PersonsTaxonomy.persons.{$this->person_group}.roles.{$this->person_role}.label",
);
if ($this->person_group === '') {
return '';
}
if ($this->person_role === '') {
return '';
}
/** @var string */
return lang("PersonsTaxonomy.persons.{$this->person_group}.roles.{$this->person_role}.label");
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Podlibre
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use App\Entities\Clip\Soundbite;
use App\Models\ClipModel;
use App\Models\EpisodeCommentModel;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PodcastModel;
use App\Models\SoundbiteModel;
use App\Models\EpisodePersonModel;
use App\Models\NoteModel;
use CodeIgniter\Entity;
use App\Models\PostModel;
use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter;
use Exception;
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;
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;
/**
* @property int $id
* @property int $podcast_id
* @property Podcast $podcast
* @property ?string $preview_id
* @property string $preview_link
* @property string $link
* @property string $guid
* @property string $slug
* @property string $title
* @property int $audio_id
* @property ?Audio $audio
* @property string $audio_url
* @property string $audio_web_url
* @property string $audio_opengraph_url
* @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 ?int $cover_id
* @property ?Image $cover
* @property int|null $transcript_id
* @property Transcript|null $transcript
* @property string|null $transcript_remote_url
* @property int|null $chapters_id
* @property Chapters|null $chapters
* @property string|null $chapters_remote_url
* @property string|null $parental_advisory
* @property int $number
* @property int $season_number
* @property string $type
* @property bool $is_blocked
* @property Location|null $location
* @property string|null $location_name
* @property string|null $location_geo
* @property string|null $location_osm
* @property bool $is_published_on_hubs
* @property int $downloads_count
* @property int $posts_count
* @property int $comments_count
* @property EpisodeComment[]|null $comments
* @property bool $is_premium
* @property int $created_by
* @property int $updated_by
* @property string $publication_status
* @property Time|null $published_at
* @property Time $created_at
* @property Time $updated_at
*
* @property Person[] $persons
* @property Soundbite[] $soundbites
* @property string $embed_url
*/
class Episode extends Entity
{
/**
* @var \App\Entities\Podcast
*/
protected $podcast;
public string $link = '';
/**
* @var string
*/
protected $link;
public string $audio_url = '';
/**
* @var \App\Libraries\Image
*/
protected $image;
public string $audio_web_url = '';
/**
* @var \CodeIgniter\Files\File
*/
protected $enclosure;
public string $audio_opengraph_url = '';
/**
* @var \CodeIgniter\Files\File
*/
protected $transcript;
protected Podcast $podcast;
/**
* @var \CodeIgniter\Files\File
*/
protected $chapters;
protected ?Audio $audio = null;
/**
* @var string
*/
protected $enclosure_media_path;
protected string $embed_url = '';
/**
* @var string
*/
protected $enclosure_url;
protected ?Image $cover = null;
/**
* @var string
*/
protected $enclosure_web_url;
protected ?string $description = null;
/**
* @var string
*/
protected $enclosure_opengraph_url;
protected ?Transcript $transcript = null;
/**
* @var string
*/
protected $transcript_url;
protected ?Chapters $chapters = null;
/**
* @var string
* @var Person[]|null
*/
protected $chapters_url;
protected ?array $persons = null;
/**
* @var \App\Entities\EpisodePerson[]
* @var Soundbite[]|null
*/
protected $persons;
protected ?array $soundbites = null;
/**
* @var \App\Entities\Soundbite[]
* @var Post[]|null
*/
protected $soundbites;
protected ?array $posts = null;
/**
* @var \App\Entities\Note[]
* @var EpisodeComment[]|null
*/
protected $notes;
protected ?array $comments = null;
/**
* Holds text only description, striped of any markdown or html special characters
*
* @var string
*/
protected $description;
protected ?Location $location = null;
/**
* The embeddable player URL
*
* @var string
*/
protected $embeddable_player;
protected ?string $publication_status = null;
/**
* @var string
* @var array<int, string>
* @phpstan-var list<string>
*/
protected $publication_status;
protected $dates = ['published_at', 'created_at', 'updated_at'];
/**
* Return custom rss as string
*
* @var string
* @var array<string, string>
*/
protected $custom_rss_string;
protected $dates = [
'published_at',
'created_at',
'updated_at',
'deleted_at',
];
protected $casts = [
'id' => 'integer',
'podcast_id' => 'integer',
'guid' => 'string',
'slug' => 'string',
'title' => 'string',
'enclosure_uri' => 'string',
'enclosure_duration' => 'integer',
'enclosure_mimetype' => 'string',
'enclosure_filesize' => 'integer',
'enclosure_headersize' => 'integer',
'description_markdown' => 'string',
'description_html' => 'string',
'image_uri' => '?string',
'image_mimetype' => '?string',
'transcript_uri' => '?string',
'chapters_uri' => '?string',
'parental_advisory' => '?string',
'number' => '?integer',
'season_number' => '?integer',
'type' => 'string',
'is_blocked' => 'boolean',
'location_name' => '?string',
'location_geo' => '?string',
'location_osmid' => '?string',
'custom_rss' => '?json-array',
'favourites_total' => 'integer',
'reblogs_total' => 'integer',
'notes_total' => 'integer',
'created_by' => 'integer',
'updated_by' => 'integer',
'id' => 'integer',
'podcast_id' => 'integer',
'preview_id' => '?string',
'guid' => 'string',
'slug' => 'string',
'title' => 'string',
'audio_id' => 'integer',
'description_markdown' => 'string',
'description_html' => 'string',
'cover_id' => '?integer',
'transcript_id' => '?integer',
'transcript_remote_url' => '?string',
'chapters_id' => '?integer',
'chapters_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',
'is_published_on_hubs' => 'boolean',
'downloads_count' => 'integer',
'posts_count' => 'integer',
'comments_count' => 'integer',
'is_premium' => 'boolean',
'created_by' => 'integer',
'updated_by' => 'integer',
];
/**
* Saves an episode image
*
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $image
*
* @param array<string, mixed> $data
*/
public function setImage($image)
#[Override]
public function injectRawData(array $data): static
{
if (
!empty($image) &&
(!($image instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
$image->isValid())
) {
helper('media');
// check whether the user has inputted an image and store
$this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_uri'] = save_media(
$image,
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->image = new \App\Libraries\Image(
$this->attributes['image_uri'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
}
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 getImage(): \App\Libraries\Image
public function setCover(UploadedFile | File|null $file = null): self
{
if ($image_uri = $this->attributes['image_uri']) {
return new \App\Libraries\Image(
$image_uri,
$this->attributes['image_mimetype'],
);
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
return $this;
}
return $this->getPodcast()->image;
}
/**
* Saves an enclosure
*
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $enclosure
*
*/
public function setEnclosure($enclosure = null)
{
if (
!empty($enclosure) &&
(!($enclosure instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
$enclosure->isValid())
) {
helper(['media', 'id3']);
$enclosure_metadata = get_file_tags($enclosure);
$this->attributes['enclosure_uri'] = save_media(
$enclosure,
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->attributes['enclosure_duration'] = round(
$enclosure_metadata['playtime_seconds'],
);
$this->attributes['enclosure_mimetype'] =
$enclosure_metadata['mime_type'];
$this->attributes['enclosure_filesize'] =
$enclosure_metadata['filesize'];
$this->attributes['enclosure_headersize'] =
$enclosure_metadata['avdataoffset'];
return $this;
if (array_key_exists('cover_id', $this->attributes) && $this->attributes['cover_id'] !== null) {
$this->getCover()
->setFile($file);
$this->getCover()
->updated_by = $this->attributes['updated_by'];
(new MediaModel('image'))->updateMedia($this->getCover());
} else {
$cover = new Image([
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '.' . $file->getExtension(),
'sizes' => config('Images')
->podcastCoverSizes,
'uploaded_by' => $this->attributes['updated_by'],
'updated_by' => $this->attributes['updated_by'],
]);
$cover->setFile($file);
$this->attributes['cover_id'] = (new MediaModel('image'))->saveMedia($cover);
}
return $this;
}
/**
* Saves an episode transcript
*
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $transcript
*
*/
public function setTranscript($transcript)
public function getCover(): Image
{
if (
!empty($transcript) &&
(!($transcript instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
$transcript->isValid())
) {
helper('media');
if ($this->cover instanceof Image) {
return $this->cover;
}
$this->attributes['transcript_uri'] = save_media(
$transcript,
$this->getPodcast()->name,
$this->attributes['slug'] . '-transcript',
);
if ($this->cover_id === null) {
$this->cover = $this->getPodcast()
->getCover();
return $this->cover;
}
return $this;
$this->cover = (new MediaModel('image'))->getMediaById($this->cover_id);
return $this->cover;
}
/**
* Saves an episode chapters
*
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $chapters
*
*/
public function setChapters($chapters)
public function setAudio(UploadedFile | File|null $file = null): self
{
if (
!empty($chapters) &&
(!($chapters instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
$chapters->isValid())
) {
helper('media');
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
return $this;
}
$this->attributes['chapters_uri'] = save_media(
$chapters,
$this->getPodcast()->name,
$this->attributes['slug'] . '-chapters',
);
if ($this->audio_id !== 0) {
$this->getAudio()
->setFile($file);
$this->getAudio()
->updated_by = $this->attributes['updated_by'];
(new MediaModel('audio'))->updateMedia($this->getAudio());
} else {
$audio = new Audio([
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $file->getRandomName(),
'language_code' => $this->getPodcast()
->language_code,
'uploaded_by' => $this->attributes['updated_by'],
'updated_by' => $this->attributes['updated_by'],
]);
$audio->setFile($file);
$this->attributes['audio_id'] = (new MediaModel())->saveMedia($audio);
}
return $this;
}
public function getEnclosure()
public function getAudio(): Audio
{
return new \CodeIgniter\Files\File($this->getEnclosureMediaPath());
}
if (! $this->audio instanceof Audio) {
$this->audio = (new MediaModel('audio'))->getMediaById($this->audio_id);
}
public function getTranscript()
{
return $this->attributes['transcript_uri']
? new \CodeIgniter\Files\File($this->getTranscriptMediaPath())
: null;
return $this->audio;
}
public function getChapters()
public function setTranscript(UploadedFile | File|null $file = null): self
{
return $this->attributes['chapters_uri']
? new \CodeIgniter\Files\File($this->getChaptersMediaPath())
: null;
}
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
return $this;
}
public function getEnclosureMediaPath()
{
helper('media');
if ($this->getTranscript() instanceof Transcript) {
$this->getTranscript()
->setFile($file);
$this->getTranscript()
->updated_by = $this->attributes['updated_by'];
(new MediaModel('transcript'))->updateMedia($this->getTranscript());
} else {
$transcript = new Transcript([
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-transcript.' . $file->getExtension(),
'language_code' => $this->getPodcast()
->language_code,
'uploaded_by' => $this->attributes['updated_by'],
'updated_by' => $this->attributes['updated_by'],
]);
$transcript->setFile($file);
$this->attributes['transcript_id'] = (new MediaModel('transcript'))->saveMedia($transcript);
}
return media_path($this->attributes['enclosure_uri']);
return $this;
}
public function getTranscriptMediaPath()
public function getTranscript(): ?Transcript
{
helper('media');
if ($this->transcript_id !== null && ! $this->transcript instanceof Transcript) {
$this->transcript = (new MediaModel('transcript'))->getMediaById($this->transcript_id);
}
return $this->attributes['transcript_uri']
? media_path($this->attributes['transcript_uri'])
: null;
return $this->transcript;
}
public function getChaptersMediaPath()
public function setChapters(UploadedFile | File|null $file = null): self
{
helper('media');
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
return $this;
}
return $this->attributes['chapters_uri']
? media_path($this->attributes['chapters_uri'])
: null;
}
if ($this->getChapters() instanceof Chapters) {
$this->getChapters()
->setFile($file);
$this->getChapters()
->updated_by = $this->attributes['updated_by'];
(new MediaModel('chapters'))->updateMedia($this->getChapters());
} else {
$chapters = new Chapters([
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-chapters' . '.' . $file->getExtension(),
'language_code' => $this->getPodcast()
->language_code,
'uploaded_by' => $this->attributes['updated_by'],
'updated_by' => $this->attributes['updated_by'],
]);
$chapters->setFile($file);
$this->attributes['chapters_id'] = (new MediaModel('chapters'))->saveMedia($chapters);
}
public function getEnclosureUrl()
{
helper('analytics');
return base_url(
route_to(
'analytics_hit',
base64_url_encode(
pack(
'I*',
$this->attributes['podcast_id'],
$this->attributes['id'],
// bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics
// - if file is shorter than 60sec, then it's enclosure_filesize
// - if file is longer than 60 seconds then it's enclosure_headersize + 60 seconds
$this->attributes['enclosure_duration'] <= 60
? $this->attributes['enclosure_filesize']
: $this->attributes['enclosure_headersize'] +
floor(
(($this->attributes['enclosure_filesize'] -
$this->attributes[
'enclosure_headersize'
]) /
$this->attributes[
'enclosure_duration'
]) *
60,
),
$this->attributes['enclosure_filesize'],
$this->attributes['enclosure_duration'],
strtotime($this->attributes['published_at']),
),
),
$this->attributes['enclosure_uri'],
),
);
return $this;
}
public function getEnclosureWebUrl()
public function getChapters(): ?Chapters
{
return $this->getEnclosureUrl() . '?_from=-+Website+-';
}
if ($this->chapters_id !== null && ! $this->chapters instanceof Chapters) {
$this->chapters = (new MediaModel('chapters'))->getMediaById($this->chapters_id);
}
public function getEnclosureOpengraphUrl()
{
return $this->getEnclosureUrl() . '?_from=-+Open+Graph+-';
return $this->chapters;
}
public function getTranscriptUrl()
/**
* Gets transcript url from transcript file uri if it exists or returns the transcript_remote_url which can be null.
*/
public function getTranscriptUrl(): ?string
{
return $this->attributes['transcript_uri']
? base_url($this->getTranscriptMediaPath())
: null;
if ($this->transcript instanceof Transcript) {
return $this->transcript->file_url;
}
return $this->transcript_remote_url;
}
public function getChaptersUrl()
/**
* Gets chapters file url from chapters file uri if it exists or returns the chapters_remote_url which can be null.
*/
public function getChaptersFileUrl(): ?string
{
return $this->attributes['chapters_uri']
? base_url($this->getChaptersMediaPath())
: null;
if ($this->chapters instanceof Chapters) {
return $this->chapters->file_url;
}
return $this->chapters_remote_url;
}
/**
* Returns the episode's persons
*
* @return \App\Entities\EpisodePerson[]
* @return Person[]
*/
public function getPersons()
public function getPersons(): array
{
if (empty($this->id)) {
throw new \RuntimeException(
'Episode must be created before getting persons.',
);
if ($this->id === null) {
throw new RuntimeException('Episode must be created before getting persons.');
}
if (empty($this->persons)) {
$this->persons = (new EpisodePersonModel())->getPersonsByEpisodeId(
$this->podcast_id,
$this->id,
);
if ($this->persons === null) {
$this->persons = (new PersonModel())->getEpisodePersons($this->podcast_id, $this->id);
}
return $this->persons;
}
/**
* Returns the episode’s soundbites
* Returns the episode’s clips
*
* @return \App\Entities\Episode[]
* @return Soundbite[]
*/
public function getSoundbites()
public function getSoundbites(): array
{
if (empty($this->id)) {
throw new \RuntimeException(
'Episode must be created before getting soundbites.',
);
if ($this->id === null) {
throw new RuntimeException('Episode must be created before getting soundbites.');
}
if (empty($this->soundbites)) {
$this->soundbites = (new SoundbiteModel())->getEpisodeSoundbites(
$this->getPodcast()->id,
$this->id,
);
if ($this->soundbites === null) {
$this->soundbites = (new ClipModel())->getEpisodeSoundbites($this->getPodcast()->id, $this->id);
}
return $this->soundbites;
}
public function getNotes()
/**
* @return Post[]
*/
public function getPosts(): array
{
if (empty($this->id)) {
throw new \RuntimeException(
'Episode must be created before getting soundbites.',
);
if ($this->id === null) {
throw new RuntimeException('Episode must be created before getting posts.');
}
if (empty($this->notes)) {
$this->notes = (new NoteModel())->getEpisodeNotes($this->id);
if ($this->posts === null) {
$this->posts = (new PostModel())->getEpisodePosts($this->id);
}
return $this->notes;
return $this->posts;
}
public function getLink()
/**
* @return EpisodeComment[]
*/
public function getComments(): array
{
return base_url(
route_to(
'episode',
$this->getPodcast()->name,
$this->attributes['slug'],
),
);
if ($this->id === null) {
throw new RuntimeException('Episode must be created before getting comments.');
}
if ($this->comments === null) {
$this->comments = (new EpisodeCommentModel())->getEpisodeComments($this->id);
}
return $this->comments;
}
public function getEmbeddablePlayer($theme = null)
public function getEmbedUrl(?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'],
),
);
return $theme
? url_to('embed-theme', esc($this->getPodcast()->handle), esc($this->attributes['slug']), $theme)
: url_to('embed', esc($this->getPodcast()->handle), esc($this->attributes['slug']));
}
public function setGuid(string $guid)
public function setGuid(?string $guid = null): static
{
return $this->attributes['guid'] = empty($guid)
? $this->getLink()
: $guid;
$this->attributes['guid'] = $guid ?? $this->link;
return $this;
}
public function getPodcast()
public function getPodcast(): ?Podcast
{
return (new PodcastModel())->getPodcastById(
$this->attributes['podcast_id'],
);
return (new PodcastModel())->getPodcastById($this->podcast_id);
}
public function setDescriptionMarkdown(string $descriptionMarkdown)
public function setDescriptionMarkdown(string $descriptionMarkdown): static
{
$converter = new CommonMarkConverter([
'html_input' => 'strip',
$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['description_markdown'] = $descriptionMarkdown;
$this->attributes['description_html'] = $converter->convertToHtml(
$descriptionMarkdown,
);
$this->attributes['description_html'] = $converter->convert($descriptionMarkdown);
return $this;
}
public function getDescriptionHtml($serviceSlug = null)
public function getDescription(): string
{
return (empty($this->getPodcast()->partner_id) ||
empty($this->getPodcast()->partner_link_url) ||
empty($this->getPodcast()->partner_image_url)
? ''
: "<div><a href=\"{$this->getPartnerLink(
$serviceSlug
)}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\"><img src=\"{$this->getPartnerImage(
$serviceSlug
)}\" alt=\"Partner image\" /></a></div>") .
$this->attributes['description_html'] .
(empty($this->getPodcast()->episode_description_footer_html)
? ''
: "<footer>{$this->getPodcast()->episode_description_footer_html}</footer>");
}
public function getDescription()
{
if ($this->description) {
return $this->description;
if ($this->description === null) {
$this->description = trim(
(string) preg_replace('~\s+~', ' ', strip_tags((string) $this->attributes['description_html'])),
);
}
return trim(
preg_replace(
'/\s+/',
' ',
strip_tags($this->attributes['description_html']),
),
);
return $this->description;
}
public function getPublicationStatus()
public function getPublicationStatus(): string
{
if ($this->publication_status) {
return $this->publication_status;
}
if (!$this->published_at) {
return 'not_published';
}
helper('date');
if ($this->published_at->isBefore(Time::now())) {
return 'published';
if ($this->publication_status === null) {
if (! $this->published_at instanceof Time) {
$this->publication_status = 'not_published';
} elseif ($this->getPodcast()->publication_status !== 'published') {
$this->publication_status = 'with_podcast';
} elseif ($this->published_at->isBefore(Time::now())) {
$this->publication_status = 'published';
} else {
$this->publication_status = 'scheduled';
}
}
return 'scheduled';
return $this->publication_status;
}
/**
* Saves the location name and fetches OpenStreetMap info
*
* @param string $locationName
*
*/
public function setLocation($locationName = null)
public function setLocation(?Location $location = null): static
{
helper('location');
if (! $location instanceof Location) {
$this->attributes['location_name'] = null;
$this->attributes['location_geo'] = null;
$this->attributes['location_osm'] = null;
return $this;
}
if (
$locationName &&
(empty($this->attributes['location_name']) ||
$this->attributes['location_name'] != $locationName)
! isset($this->attributes['location_name']) ||
$this->attributes['location_name'] !== $location->name
) {
$this->attributes['location_name'] = $locationName;
if ($location = fetch_osm_location($locationName)) {
$this->attributes['location_geo'] = $location['geo'];
$this->attributes['location_osmid'] = $location['osmid'];
}
} elseif (empty($locationName)) {
$this->attributes['location_name'] = null;
$this->attributes['location_geo'] = null;
$this->attributes['location_osmid'] = null;
$location->fetchOsmLocation();
$this->attributes['location_name'] = $location->name;
$this->attributes['location_geo'] = $location->geo;
$this->attributes['location_osm'] = $location->osm;
}
return $this;
}
/**
* Get custom rss tag as XML String
*
* @return string
*
*/
function getCustomRssString()
public function getLocation(): ?Location
{
helper('rss');
if (empty($this->attributes['custom_rss'])) {
return '';
} else {
$xmlNode = (new \App\Libraries\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')
->addChild('item');
array_to_rss(
[
'elements' => $this->custom_rss,
],
$xmlNode,
);
return str_replace(['<item>', '</item>'], '', $xmlNode->asXML());
if ($this->location_name === null) {
return null;
}
}
/**
* Saves custom rss tag into json
*
* @param string $customRssString
*
*/
function setCustomRssString($customRssString)
{
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><item>' .
$customRssString .
'</item></channel></rss>',
),
)['elements'][0]['elements'][0];
if (array_key_exists('elements', $customRssArray)) {
$this->attributes['custom_rss'] = json_encode(
$customRssArray['elements'],
);
} else {
$this->attributes['custom_rss'] = null;
if (! $this->location instanceof Location) {
$this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
}
return $this->location;
}
function getPartnerLink($serviceSlug = null)
public function getPreviewLink(): string
{
return rtrim($this->getPodcast()->partner_link_url, '/') .
'?pid=' .
$this->getPodcast()->partner_id .
'&guid=' .
urlencode($this->attributes['guid']) .
(empty($serviceSlug) ? '' : '&_from=' . $serviceSlug);
if ($this->preview_id === null) {
// generate preview id
if (! $previewUUID = (new EpisodeModel())->setEpisodePreviewId($this->id)) {
throw new Exception('Could not set episode preview id');
}
$this->preview_id = $previewUUID;
}
return url_to('episode-preview', (string) $this->preview_id);
}
function getPartnerImage($serviceSlug = null)
/**
* Returns the episode's clip count
*/
public function getClipCount(): int|string
{
return rtrim($this->getPodcast()->partner_image_url, '/') .
'?pid=' .
$this->getPodcast()->partner_id .
'&guid=' .
urlencode($this->attributes['guid']) .
(empty($serviceSlug) ? '' : '&_from=' . $serviceSlug);
if ($this->id === null) {
throw new RuntimeException('Episode must be created before getting number of video clips.');
}
return (new ClipModel())->getClipCount($this->podcast_id, $this->id);
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use App\Models\ActorModel;
use App\Models\EpisodeCommentModel;
use App\Models\EpisodeModel;
use CodeIgniter\I18n\Time;
use Michalsn\Uuid\UuidEntity;
use RuntimeException;
/**
* @property string $id
* @property string $uri
* @property int $episode_id
* @property Episode|null $episode
* @property int $actor_id
* @property Actor|null $actor
* @property string $in_reply_to_id
* @property EpisodeComment|null $reply_to_comment
* @property string $message
* @property string $message_html
* @property int $likes_count
* @property int $replies_count
* @property Time $created_at
* @property int $created_by
*
* @property EpisodeComment[] $replies
*/
class EpisodeComment extends UuidEntity
{
protected ?Episode $episode = null;
protected ?Actor $actor = null;
protected ?EpisodeComment $reply_to_comment = null;
/**
* @var EpisodeComment[]|null
*/
protected ?array $replies = null;
protected bool $has_replies = false;
/**
* @var array<int, string>
* @phpstan-var list<string>
*/
protected $dates = ['created_at'];
/**
* @var array<string, string>
*/
protected $casts = [
'id' => 'string',
'uri' => 'string',
'episode_id' => 'integer',
'actor_id' => 'integer',
'in_reply_to_id' => '?string',
'message' => 'string',
'message_html' => 'string',
'likes_count' => 'integer',
'replies_count' => 'integer',
'created_by' => 'integer',
'is_from_post' => 'boolean',
];
public function getEpisode(): ?Episode
{
if ($this->episode_id === null) {
throw new RuntimeException('Comment must have an episode_id before getting episode.');
}
if (! $this->episode instanceof Episode) {
$this->episode = (new EpisodeModel())->getEpisodeById($this->episode_id);
}
return $this->episode;
}
/**
* Returns the comment's actor
*/
public function getActor(): ?Actor
{
if ($this->actor_id === null) {
throw new RuntimeException('Comment must have an actor_id before getting actor.');
}
if (! $this->actor instanceof Actor) {
$this->actor = model(ActorModel::class, false)
->getActorById($this->actor_id);
}
return $this->actor;
}
/**
* @return EpisodeComment[]
*/
public function getReplies(): array
{
if ($this->id === null) {
throw new RuntimeException('Comment must be created before getting replies.');
}
if ($this->replies === null) {
$this->replies = (new EpisodeCommentModel())->getCommentReplies($this->id);
}
return $this->replies;
}
public function getHasReplies(): bool
{
return $this->getReplies() !== [];
}
public function getReplyToComment(): ?self
{
if ($this->in_reply_to_id === null) {
throw new RuntimeException('Comment is not a reply.');
}
if (! $this->reply_to_comment instanceof self) {
$this->reply_to_comment = model(EpisodeCommentModel::class, false)
->getCommentById($this->in_reply_to_id);
}
return $this->reply_to_comment;
}
public function setMessage(string $message): static
{
helper('fediverse');
$messageWithoutTags = strip_tags($message);
$this->attributes['message'] = $messageWithoutTags;
$this->attributes['message_html'] = str_replace("\n", '<br />', linkify($messageWithoutTags));
return $this;
}
}