castopod/app/Models/EpisodeModel.php

539 lines
16 KiB
PHP
Raw Normal View History

<?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\Models;
use App\Entities\Episode;
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\I18n\Time;
use Michalsn\Uuid\UuidModel;
use Ramsey\Uuid\Lazy\LazyUuidFromString;
class EpisodeModel extends UuidModel
{
/**
* TODO: remove, shouldn't be here
*
* @var array<string, array<string, string>>
*/
public static $themes = [
'light-transparent' => [
'style' => 'background-color: #fff; background-image: linear-gradient(45deg, #ccc 12.5%, transparent 12.5%, transparent 50%, #ccc 50%, #ccc 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;',
'background' => 'transparent',
'text' => '#000',
'inverted' => '#fff',
],
'light' => [
'style' => 'background-color: #fff;',
'background' => '#fff',
'text' => '#000',
'inverted' => '#fff',
],
'dark-transparent' => [
'style' => 'background-color: #001f1a; background-image: linear-gradient(45deg, #888 12.5%, transparent 12.5%, transparent 50%, #888 50%, #888 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;',
'background' => 'transparent',
'text' => '#fff',
'inverted' => '#000',
],
'dark' => [
'style' => 'background-color: #001f1a;',
'background' => '#313131',
'text' => '#fff',
'inverted' => '#000',
],
];
/**
* @var string[]
*/
protected $uuidFields = ['preview_id'];
/**
* @var string
*/
protected $table = 'episodes';
/**
* @var list<string>
*/
protected $allowedFields = [
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
'id',
'podcast_id',
'preview_id',
'guid',
'title',
'slug',
'audio_id',
'description_markdown',
'description_html',
'cover_id',
'transcript_id',
'transcript_remote_url',
'chapters_id',
'chapters_remote_url',
'parental_advisory',
'number',
'season_number',
'type',
'is_blocked',
2020-12-23 15:11:38 +01:00
'location_name',
'location_geo',
'location_osm',
'custom_rss',
'is_published_on_hubs',
'posts_count',
'comments_count',
'is_premium',
'published_at',
'created_by',
'updated_by',
];
/**
* @var string
*/
protected $returnType = Episode::class;
/**
* @var bool
*/
protected $useTimestamps = true;
/**
* @var array<string, string>
*/
protected $validationRules = [
'podcast_id' => 'required',
'title' => 'required',
'slug' => 'required|regex_match[/^[a-zA-Z0-9\-]{1,128}$/]',
'audio_id' => 'required',
'description_markdown' => 'required',
'number' => 'is_natural_no_zero|permit_empty',
'season_number' => 'is_natural_no_zero|permit_empty',
'type' => 'required',
'transcript_remote_url' => 'valid_url_strict|permit_empty',
'chapters_remote_url' => 'valid_url_strict|permit_empty',
'published_at' => 'valid_date|permit_empty',
'created_by' => 'required',
'updated_by' => 'required',
];
/**
* @var list<string>
*/
protected $afterInsert = ['writeEnclosureMetadata', 'clearCache'];
/**
* @var list<string>
*/
protected $afterUpdate = ['clearCache', 'writeEnclosureMetadata'];
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
/**
* @var list<string>
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
*/
protected $beforeDelete = ['clearCache'];
public function getEpisodeBySlug(string $podcastHandle, string $episodeSlug): ?Episode
{
$cacheName = "podcast-{$podcastHandle}_episode-{$episodeSlug}";
if (! ($found = cache($cacheName))) {
$found = $this->select('episodes.*')
->join('podcasts', 'podcasts.id = episodes.podcast_id')
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
->where('slug', $episodeSlug)
->where('podcasts.handle', $podcastHandle)
->where('`' . $this->db->getPrefix() . 'episodes`.`published_at` <= UTC_TIMESTAMP()', null, false)
->first();
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
public function getEpisodeById(int $episodeId): ?Episode
{
// TODO: episode id should be a composite key. The cache should include podcast_id.
$cacheName = "podcast_episode#{$episodeId}";
if (! ($found = cache($cacheName))) {
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
$builder = $this->where([
'id' => $episodeId,
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
]);
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
$found = $builder->first();
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
public function getPublishedEpisodeById(int $podcastId, int $episodeId): ?Episode
{
$cacheName = "podcast#{$podcastId}_episode#{$episodeId}_published";
if (! ($found = cache($cacheName))) {
$found = $this->where([
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
'id' => $episodeId,
])
->where('podcast_id', $podcastId)
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->first();
cache()
->save($cacheName, $found, DECADE);
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
}
return $found;
}
public function getEpisodeByPreviewId(string $previewId): ?Episode
{
$cacheName = "podcast_episode#preview-{$previewId}";
if (! ($found = cache($cacheName))) {
$builder = $this->where([
'preview_id' => $this->uuid->fromString($previewId)
->getBytes(),
]);
$found = $builder->first();
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
public function setEpisodePreviewId(int $episodeId): string|false
{
/** @var LazyUuidFromString $uuid */
$uuid = $this->uuid->{$this->uuidVersion}();
if (! $this->update($episodeId, [
'preview_id' => $uuid,
])) {
return false;
}
return (string) $uuid;
}
/**
* Gets all episodes for a podcast ordered according to podcast type Filtered depending on year or season
*
* @return Episode[]
*/
public function getPodcastEpisodes(
int $podcastId,
string $podcastType,
string $year = null,
string $season = null
): array {
$cacheName = implode(
'_',
array_filter(["podcast#{$podcastId}", $year, $season ? 'season' . $season : null, 'episodes']),
);
if (! ($found = cache($cacheName))) {
$where = [
'podcast_id' => $podcastId,
];
if ($year) {
$where['YEAR(published_at)'] = $year;
$where['season_number'] = null;
}
if ($season) {
$where['season_number'] = $season;
}
if ($podcastType === 'serial') {
// podcast is serial
$found = $this->where($where)
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->orderBy('season_number DESC, number ASC')
->findAll();
} else {
$found = $this->where($where)
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->orderBy('published_at', 'DESC')
->findAll();
}
$secondsToNextUnpublishedEpisode = $this->getSecondsToNextUnpublishedEpisode($podcastId);
cache()
->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
}
return $found;
}
/**
* Returns number of episodes of a podcast
*/
public function getPodcastEpisodesCount(int $podcastId): int|string
{
return $this
->where([
'podcast_id' => $podcastId,
])
->countAllResults();
}
/**
* Returns the timestamp difference in seconds between the next episode to publish and the current timestamp Returns
* false if there's no episode to publish
*
* @return int|false seconds
*/
public function getSecondsToNextUnpublishedEpisode(int $podcastId): int | false
{
$result = $this->builder()
->select('TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), `published_at`) as timestamp_diff')
->where([
'podcast_id' => $podcastId,
])
->where('`published_at` > UTC_TIMESTAMP()', null, false)
->orderBy('published_at', 'asc')
->get()
->getResultArray();
return $result !== []
? (int) $result[0]['timestamp_diff']
: false;
}
public function getCurrentSeasonNumber(int $podcastId): ?int
{
$result = $this->builder()
->selectMax('season_number', 'current_season_number')
->where('podcast_id', $podcastId)
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->get()
->getResultArray();
return $result[0]['current_season_number'] ? (int) $result[0]['current_season_number'] : null;
}
public function getNextEpisodeNumber(int $podcastId, ?int $seasonNumber): int
{
$result = $this->builder()
->selectMax('number', 'next_episode_number')
->where([
'podcast_id' => $podcastId,
'season_number' => $seasonNumber,
])
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->get()
->getResultArray();
return (int) $result[0]['next_episode_number'] + 1;
}
/**
* @return array{number_of_seasons: int, number_of_episodes: int, first_published_at?: Time}
*/
public function getPodcastStats(int $podcastId): array
{
$result = $this->builder()
->select(
'COUNT(DISTINCT season_number) as number_of_seasons, COUNT(*) as number_of_episodes, MIN(published_at) as first_published_at'
)
->where('podcast_id', $podcastId)
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->get()
->getResultArray();
$stats = [
'number_of_seasons' => (int) $result[0]['number_of_seasons'],
'number_of_episodes' => (int) $result[0]['number_of_episodes'],
];
if ($result[0]['first_published_at'] !== null) {
$stats['first_published_at'] = new Time($result[0]['first_published_at']);
}
return $stats;
}
public function resetCommentsCount(): int | false
{
$episodeCommentsCount = (new EpisodeCommentModel())->builder()
->select('episode_id, COUNT(*) as `comments_count`')
->where('in_reply_to_id', null)
->groupBy('episode_id')
->getCompiledSelect();
$episodePostsRepliesCount = (new PostModel())->builder()
->select('fediverse_posts.episode_id as episode_id, COUNT(*) as `comments_count`')
->join('fediverse_posts as fp', 'fediverse_posts.id = fp.in_reply_to_id')
->where('fediverse_posts.in_reply_to_id', null)
->where('fediverse_posts.episode_id IS NOT', null)
->groupBy('fediverse_posts.episode_id')
->getCompiledSelect();
/** @var BaseResult $query */
$query = $this->db->query(
'SELECT `episode_id` as `id`, SUM(`comments_count`) as `comments_count` FROM (' . $episodeCommentsCount . ' UNION ALL ' . $episodePostsRepliesCount . ') x GROUP BY `episode_id`'
);
$countsPerEpisodeId = $query->getResultArray();
if ($countsPerEpisodeId !== []) {
return (new self())->updateBatch($countsPerEpisodeId, 'id');
}
return 0;
}
public function resetPostsCount(): int | false
{
$episodePostsCount = $this->builder()
->select('episodes.id, COUNT(*) as `posts_count`')
->join('fediverse_posts', 'episodes.id = fediverse_posts.episode_id')
->where('in_reply_to_id', null)
->groupBy('episodes.id')
->get()
->getResultArray();
if ($episodePostsCount !== []) {
return $this->updateBatch($episodePostsCount, 'id');
}
return 0;
}
/**
* @param mixed[] $data
*
* @return mixed[]
*/
public function clearCache(array $data): array
{
/** @var int|null $episodeId */
$episodeId = is_array($data['id']) ? $data['id'][0] : $data['id'];
if ($episodeId === null) {
// Multiple episodes have been updated, do nothing
return $data;
}
/** @var ?Episode $episode */
$episode = (new self())->find($episodeId);
if (! $episode instanceof Episode) {
return $data;
}
// delete podcast cache
cache()
->deleteMatching("podcast#{$episode->podcast_id}*");
cache()
->deleteMatching("podcast-{$episode->podcast->handle}*");
cache()
->delete("podcast_episode#{$episode->id}");
cache()
->deleteMatching("page_podcast#{$episode->podcast_id}*");
cache()
->deleteMatching('page_credits_*');
cache()
->delete('episodes_markers');
return $data;
}
public function doesPodcastHavePremiumEpisodes(int $podcastId): bool
{
return $this->builder()
->where([
'podcast_id' => $podcastId,
'is_premium' => true,
])
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->countAllResults() > 0;
}
public function fullTextSearch(string $query): ?BaseBuilder
{
$prefix = $this->db->getPrefix();
$episodeTable = $prefix . $this->builder()->getTable();
$podcastModel = (new PodcastModel());
$podcastTable = $podcastModel->db->getPrefix() . $podcastModel->builder()->getTable();
$this->builder()
->select('' . $episodeTable . '.*')
->select('
' . $this->getFullTextMatchClauseForEpisodes($episodeTable, $query) . ' as episodes_score,
' . $podcastModel->getFullTextMatchClauseForPodcasts($podcastTable, $query) . ' as podcasts_score,
')
->select("{$podcastTable}.created_at AS podcast_created_at")
->select(
"{$podcastTable}.title as podcast_title, {$podcastTable}.handle as podcast_handle, {$podcastTable}.description_markdown as podcast_description_markdown"
)
->join($podcastTable, "{$podcastTable} on {$podcastTable}.id = {$episodeTable}.podcast_id")
->where('
(' .
$this->getFullTextMatchClauseForEpisodes($episodeTable, $query)
. 'OR' .
$podcastModel->getFullTextMatchClauseForPodcasts($podcastTable, $query)
. ')
');
return $this->builder;
}
public function getFullTextMatchClauseForEpisodes(string $table, string $value): string
{
return '
MATCH (
' . $table . '.title,
' . $table . '.description_markdown,
' . $table . '.slug,
' . $table . '.location_name
)
AGAINST(' . $this->db->escape($value) . ')
';
}
/**
* @param mixed[] $data
*
* @return mixed[]
*/
protected function writeEnclosureMetadata(array $data): array
{
/** @var int|null $episodeId */
$episodeId = is_array($data['id']) ? $data['id'][0] : $data['id'];
if ($episodeId === null) {
// Multiple episodes have been updated, do nothing
return $data;
}
/** @var ?Episode $episode */
$episode = (new self())->find($episodeId);
if (! $episode instanceof Episode) {
return $data;
}
helper('id3');
write_audio_file_tags($episode);
return $data;
}
}