mirror of
https://code.castopod.org/adaures/castopod
synced 2025-06-06 09:22:01 +00:00
feat(plugins): add html field type + CodeEditor component + rework html head generation
update php and js packages to latest
This commit is contained in:
parent
b869acb3a9
commit
8cf9c6dc83
@ -67,7 +67,7 @@ class Filters extends BaseConfig
|
|||||||
/**
|
/**
|
||||||
* List of filter aliases that are always applied before and after every request.
|
* List of filter aliases that are always applied before and after every request.
|
||||||
*
|
*
|
||||||
* @var array<string, array<string, array<string, string|array<string>>>>>|array<string, list<string>>
|
* @var array<string, array<string, array<string, string|array<string>>>>|array<string, list<string>>
|
||||||
*/
|
*/
|
||||||
public array $globals = [
|
public array $globals = [
|
||||||
'before' => [
|
'before' => [
|
||||||
|
@ -25,7 +25,7 @@ class Generators extends BaseConfig
|
|||||||
*
|
*
|
||||||
* YOU HAVE BEEN WARNED!
|
* YOU HAVE BEEN WARNED!
|
||||||
*
|
*
|
||||||
* @var array<string, string>
|
* @var array<string, string|array<string,string>>
|
||||||
*/
|
*/
|
||||||
public array $views = [
|
public array $views = [
|
||||||
'make:cell' => [
|
'make:cell' => [
|
||||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use App\Libraries\Breadcrumb;
|
use App\Libraries\Breadcrumb;
|
||||||
|
use App\Libraries\HtmlHead;
|
||||||
use App\Libraries\Negotiate;
|
use App\Libraries\Negotiate;
|
||||||
use App\Libraries\Router;
|
use App\Libraries\Router;
|
||||||
use CodeIgniter\Config\BaseService;
|
use CodeIgniter\Config\BaseService;
|
||||||
@ -66,4 +67,13 @@ class Services extends BaseService
|
|||||||
|
|
||||||
return new Breadcrumb();
|
return new Breadcrumb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function html_head(bool $getShared = true): HtmlHead
|
||||||
|
{
|
||||||
|
if ($getShared) {
|
||||||
|
return self::getSharedInstance('html_head');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HtmlHead();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use App\Views\Decorators\SiteHead;
|
|
||||||
use CodeIgniter\Config\View as BaseView;
|
use CodeIgniter\Config\View as BaseView;
|
||||||
use CodeIgniter\View\ViewDecoratorInterface;
|
use CodeIgniter\View\ViewDecoratorInterface;
|
||||||
use ViewComponents\Decorator;
|
use ViewComponents\Decorator;
|
||||||
@ -54,5 +53,5 @@ class View extends BaseView
|
|||||||
*
|
*
|
||||||
* @var list<class-string<ViewDecoratorInterface>>
|
* @var list<class-string<ViewDecoratorInterface>>
|
||||||
*/
|
*/
|
||||||
public array $decorators = [Decorator::class, SiteHead::class];
|
public array $decorators = [Decorator::class];
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,9 @@ class ActorController extends FediverseActorController
|
|||||||
}
|
}
|
||||||
|
|
||||||
helper(['form', 'components', 'svg']);
|
helper(['form', 'components', 'svg']);
|
||||||
$data = [
|
|
||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
'metatags' => get_follow_metatags($this->actor),
|
set_follow_metatags($this->actor);
|
||||||
|
$data = [
|
||||||
'actor' => $this->actor,
|
'actor' => $this->actor,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -164,8 +164,8 @@ class CreditsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_page_metatags($page);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_page_metatags($page),
|
|
||||||
'page' => $page,
|
'page' => $page,
|
||||||
'credits' => $credits,
|
'credits' => $credits,
|
||||||
];
|
];
|
||||||
|
@ -96,8 +96,8 @@ class EpisodeCommentController extends BaseController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
|
set_episode_comment_metatags($this->comment);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_episode_comment_metatags($this->comment),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'actor' => $this->actor,
|
'actor' => $this->actor,
|
||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
|
@ -86,8 +86,8 @@ class EpisodeController extends BaseController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
|
set_episode_metatags($this->episode);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_episode_metatags($this->episode),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
@ -135,8 +135,8 @@ class EpisodeController extends BaseController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
|
set_episode_metatags($this->episode);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_episode_metatags($this->episode),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
@ -184,13 +184,13 @@ class EpisodeController extends BaseController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
// get chapters from json file
|
set_episode_metatags($this->episode);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_episode_metatags($this->episode),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// get chapters from json file
|
||||||
if (isset($this->episode->chapters->file_key)) {
|
if (isset($this->episode->chapters->file_key)) {
|
||||||
/** @var FileManagerInterface $fileManager */
|
/** @var FileManagerInterface $fileManager */
|
||||||
$fileManager = service('file_manager');
|
$fileManager = service('file_manager');
|
||||||
@ -243,13 +243,13 @@ class EpisodeController extends BaseController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
// get transcript from json file
|
set_episode_metatags($this->episode);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_episode_metatags($this->episode),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// get transcript from json file
|
||||||
if ($this->episode->transcript !== null) {
|
if ($this->episode->transcript !== null) {
|
||||||
$data['transcript'] = $this->episode->transcript;
|
$data['transcript'] = $this->episode->transcript;
|
||||||
|
|
||||||
|
@ -32,9 +32,9 @@ class HomeController extends BaseController
|
|||||||
return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
|
return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_home_metatags();
|
||||||
// default behavior: list all podcasts on home page
|
// default behavior: list all podcasts on home page
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_home_metatags(),
|
|
||||||
'podcasts' => $allPodcasts,
|
'podcasts' => $allPodcasts,
|
||||||
'sortBy' => $sortBy,
|
'sortBy' => $sortBy,
|
||||||
];
|
];
|
||||||
|
@ -49,8 +49,8 @@ class PageController extends BaseController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($found = cache($cacheName))) {
|
if (! ($found = cache($cacheName))) {
|
||||||
|
set_page_metatags($this->page);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_page_metatags($this->page),
|
|
||||||
'page' => $this->page,
|
'page' => $this->page,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -78,8 +78,8 @@ class PodcastController extends BaseController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
|
set_podcast_metatags($this->podcast, 'activity');
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_podcast_metatags($this->podcast, 'activity'),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id),
|
'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id),
|
||||||
];
|
];
|
||||||
@ -128,8 +128,8 @@ class PodcastController extends BaseController
|
|||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
$stats = (new EpisodeModel())->getPodcastStats($this->podcast->id);
|
$stats = (new EpisodeModel())->getPodcastStats($this->podcast->id);
|
||||||
|
|
||||||
|
set_podcast_metatags($this->podcast, 'about');
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_podcast_metatags($this->podcast, 'about'),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
];
|
];
|
||||||
@ -245,8 +245,8 @@ class PodcastController extends BaseController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_podcast_metatags($this->podcast, 'episodes');
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_podcast_metatags($this->podcast, 'episodes'),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episodesNav' => $episodesNavigation,
|
'episodesNav' => $episodesNavigation,
|
||||||
'activeQuery' => $activeQuery,
|
'activeQuery' => $activeQuery,
|
||||||
@ -315,8 +315,8 @@ class PodcastController extends BaseController
|
|||||||
|
|
||||||
public function links(): string
|
public function links(): string
|
||||||
{
|
{
|
||||||
|
set_podcast_metatags($this->podcast, 'links');
|
||||||
return view('podcast/links', [
|
return view('podcast/links', [
|
||||||
'metatags' => get_podcast_metatags($this->podcast, 'links'),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ class PostController extends FediversePostController
|
|||||||
#[Override]
|
#[Override]
|
||||||
public function _remap(string $method, string ...$params): mixed
|
public function _remap(string $method, string ...$params): mixed
|
||||||
{
|
{
|
||||||
|
|
||||||
if (
|
if (
|
||||||
! ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) instanceof Podcast
|
! ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) instanceof Podcast
|
||||||
) {
|
) {
|
||||||
@ -54,15 +55,20 @@ class PostController extends FediversePostController
|
|||||||
$this->podcast = $podcast;
|
$this->podcast = $podcast;
|
||||||
$this->actor = $this->podcast->actor;
|
$this->actor = $this->podcast->actor;
|
||||||
|
|
||||||
|
if (count($params) <= 1) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
count($params) > 1 &&
|
! ($post = (new PostModel())->getPostById($params[1])) instanceof CastopodPost
|
||||||
($post = (new PostModel())->getPostById($params[1])) instanceof CastopodPost
|
|
||||||
) {
|
) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
$this->post = $post;
|
$this->post = $post;
|
||||||
|
|
||||||
unset($params[0]);
|
unset($params[0]);
|
||||||
unset($params[1]);
|
unset($params[1]);
|
||||||
}
|
|
||||||
|
|
||||||
return $this->{$method}(...$params);
|
return $this->{$method}(...$params);
|
||||||
}
|
}
|
||||||
@ -74,10 +80,6 @@ class PostController extends FediversePostController
|
|||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->post instanceof CastopodPost) {
|
|
||||||
throw PageNotFoundException::forPageNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
$cacheName = implode(
|
$cacheName = implode(
|
||||||
'_',
|
'_',
|
||||||
array_filter([
|
array_filter([
|
||||||
@ -91,8 +93,8 @@ class PostController extends FediversePostController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
|
set_post_metatags($this->post);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_post_metatags($this->post),
|
|
||||||
'post' => $this->post,
|
'post' => $this->post,
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
@ -254,8 +256,8 @@ class PostController extends FediversePostController
|
|||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_remote_actions_metatags($this->post, $action);
|
||||||
$data = [
|
$data = [
|
||||||
'metatags' => get_remote_actions_metatags($this->post, $action),
|
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'actor' => $this->actor,
|
'actor' => $this->actor,
|
||||||
'post' => $this->post,
|
'post' => $this->post,
|
||||||
|
@ -36,7 +36,7 @@ use Modules\Media\Models\MediaModel;
|
|||||||
* @property string $type
|
* @property string $type
|
||||||
* @property int|null $media_id
|
* @property int|null $media_id
|
||||||
* @property Video|Audio|null $media
|
* @property Video|Audio|null $media
|
||||||
* @property array|null $metadata
|
* @property array<mixed>|null $metadata
|
||||||
* @property string $status
|
* @property string $status
|
||||||
* @property string $logs
|
* @property string $logs
|
||||||
* @property User $user
|
* @property User $user
|
||||||
|
@ -16,7 +16,7 @@ use Modules\Media\Models\MediaModel;
|
|||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property array $theme
|
* @property array{name:string,preview:string} $theme
|
||||||
* @property string $format
|
* @property string $format
|
||||||
*/
|
*/
|
||||||
class VideoClip extends BaseClip
|
class VideoClip extends BaseClip
|
||||||
@ -37,7 +37,7 @@ class VideoClip extends BaseClip
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, string> $theme
|
* @param array{name:string,preview:string} $theme
|
||||||
*/
|
*/
|
||||||
public function setTheme(array $theme): self
|
public function setTheme(array $theme): self
|
||||||
{
|
{
|
||||||
|
@ -105,7 +105,7 @@ if (! function_exists('publication_pill')) {
|
|||||||
|
|
||||||
$label = lang('Episode.publication_status.' . $publicationStatus);
|
$label = lang('Episode.publication_status.' . $publicationStatus);
|
||||||
|
|
||||||
// @icon('error-warning-fill')
|
// @icon("error-warning-fill")
|
||||||
return '<x-Pill ' . ($title === '' ? '' : 'title="' . $title . '"') . ' variant="' . $variant . '" class="' . $customClass .
|
return '<x-Pill ' . ($title === '' ? '' : 'title="' . $title . '"') . ' variant="' . $variant . '" class="' . $customClass .
|
||||||
'">' . $label . ($publicationStatus === 'with_podcast' ? icon('error-warning-fill', [
|
'">' . $label . ($publicationStatus === 'with_podcast' ? icon('error-warning-fill', [
|
||||||
'class' => 'flex-shrink-0 ml-1 text-lg',
|
'class' => 'flex-shrink-0 ml-1 text-lg',
|
||||||
@ -129,20 +129,20 @@ if (! function_exists('publication_button')) {
|
|||||||
$label = lang('Episode.publish');
|
$label = lang('Episode.publish');
|
||||||
$route = route_to('episode-publish', $podcastId, $episodeId);
|
$route = route_to('episode-publish', $podcastId, $episodeId);
|
||||||
$variant = 'primary';
|
$variant = 'primary';
|
||||||
$iconLeft = 'upload-cloud-fill'; // @icon('upload-cloud-fill')
|
$iconLeft = 'upload-cloud-fill'; // @icon("upload-cloud-fill")
|
||||||
break;
|
break;
|
||||||
case 'with_podcast':
|
case 'with_podcast':
|
||||||
case 'scheduled':
|
case 'scheduled':
|
||||||
$label = lang('Episode.publish_edit');
|
$label = lang('Episode.publish_edit');
|
||||||
$route = route_to('episode-publish_edit', $podcastId, $episodeId);
|
$route = route_to('episode-publish_edit', $podcastId, $episodeId);
|
||||||
$variant = 'warning';
|
$variant = 'warning';
|
||||||
$iconLeft = 'upload-cloud-fill'; // @icon('upload-cloud-fill')
|
$iconLeft = 'upload-cloud-fill'; // @icon("upload-cloud-fill")
|
||||||
break;
|
break;
|
||||||
case 'published':
|
case 'published':
|
||||||
$label = lang('Episode.unpublish');
|
$label = lang('Episode.unpublish');
|
||||||
$route = route_to('episode-unpublish', $podcastId, $episodeId);
|
$route = route_to('episode-unpublish', $podcastId, $episodeId);
|
||||||
$variant = 'danger';
|
$variant = 'danger';
|
||||||
$iconLeft = 'cloud-off-fill'; // @icon('cloud-off-fill')
|
$iconLeft = 'cloud-off-fill'; // @icon("cloud-off-fill")
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$label = '';
|
$label = '';
|
||||||
|
@ -422,7 +422,7 @@ if (! function_exists('add_category_tag')) {
|
|||||||
/**
|
/**
|
||||||
* Adds <itunes:category> and <category> tags to node for a given category
|
* Adds <itunes:category> and <category> tags to node for a given category
|
||||||
*/
|
*/
|
||||||
function add_category_tag(SimpleXMLElement $node, Category $category): void
|
function add_category_tag(RssFeed $node, Category $category): void
|
||||||
{
|
{
|
||||||
$itunesCategory = $node->addChild('category', null, RssFeed::ITUNES_NAMESPACE);
|
$itunesCategory = $node->addChild('category', null, RssFeed::ITUNES_NAMESPACE);
|
||||||
$itunesCategory->addAttribute(
|
$itunesCategory->addAttribute(
|
||||||
@ -441,70 +441,3 @@ if (! function_exists('add_category_tag')) {
|
|||||||
$node->addChild('category', $category->apple_category);
|
$node->addChild('category', $category->apple_category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('rss_to_array')) {
|
|
||||||
/**
|
|
||||||
* Converts XML to array
|
|
||||||
*
|
|
||||||
* FIXME: param should be SimpleRSSElement
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
function rss_to_array(SimpleXMLElement $rssNode): array
|
|
||||||
{
|
|
||||||
$nameSpaces = ['', 'http://www.itunes.com/dtds/podcast-1.0.dtd', 'https://podcastindex.org/namespace/1.0'];
|
|
||||||
$arrayNode = [];
|
|
||||||
$arrayNode['name'] = $rssNode->getName();
|
|
||||||
$arrayNode['namespace'] = $rssNode->getNamespaces(false);
|
|
||||||
foreach ($rssNode->attributes() as $key => $value) {
|
|
||||||
$arrayNode['attributes'][$key] = (string) $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$textcontent = trim((string) $rssNode);
|
|
||||||
if (strlen($textcontent) > 0) {
|
|
||||||
$arrayNode['content'] = $textcontent;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($nameSpaces as $currentNameSpace) {
|
|
||||||
foreach ($rssNode->children($currentNameSpace) as $childXmlNode) {
|
|
||||||
$arrayNode['elements'][] = rss_to_array($childXmlNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $arrayNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! function_exists('array_to_rss')) {
|
|
||||||
/**
|
|
||||||
* Inserts array (converted to XML node) in XML node
|
|
||||||
*
|
|
||||||
* @param array<string, mixed> $arrayNode
|
|
||||||
* @param RssFeed $xmlNode The XML parent node where this arrayNode should be attached
|
|
||||||
*/
|
|
||||||
function array_to_rss(array $arrayNode, RssFeed &$xmlNode): RssFeed
|
|
||||||
{
|
|
||||||
if (array_key_exists('elements', $arrayNode)) {
|
|
||||||
foreach ($arrayNode['elements'] as $childArrayNode) {
|
|
||||||
$childXmlNode = $xmlNode->addChild(
|
|
||||||
$childArrayNode['name'],
|
|
||||||
$childArrayNode['content'] ?? null,
|
|
||||||
$childArrayNode['namespace'] === []
|
|
||||||
? null
|
|
||||||
: current($childArrayNode['namespace'])
|
|
||||||
);
|
|
||||||
if (array_key_exists('attributes', $childArrayNode)) {
|
|
||||||
foreach (
|
|
||||||
$childArrayNode['attributes'] as $attributeKey => $attributeValue
|
|
||||||
) {
|
|
||||||
$childXmlNode->addAttribute($attributeKey, $attributeValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
array_to_rss($childArrayNode, $childXmlNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $xmlNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -8,19 +8,19 @@ use App\Entities\EpisodeComment;
|
|||||||
use App\Entities\Page;
|
use App\Entities\Page;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
use App\Entities\Post;
|
use App\Entities\Post;
|
||||||
use Melbahja\Seo\MetaTags;
|
use App\Libraries\HtmlHead;
|
||||||
use Melbahja\Seo\Schema;
|
use Melbahja\Seo\Schema;
|
||||||
use Melbahja\Seo\Schema\Thing;
|
use Melbahja\Seo\Schema\Thing;
|
||||||
use Modules\Fediverse\Entities\PreviewCard;
|
use Modules\Fediverse\Entities\PreviewCard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2021 Ad Aures
|
* @copyright 2024 Ad Aures
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (! function_exists('get_podcast_metatags')) {
|
if (! function_exists('set_podcast_metatags')) {
|
||||||
function get_podcast_metatags(Podcast $podcast, string $page): string
|
function set_podcast_metatags(Podcast $podcast, string $page): void
|
||||||
{
|
{
|
||||||
$category = '';
|
$category = '';
|
||||||
if ($podcast->category->parent_id !== null) {
|
if ($podcast->category->parent_id !== null) {
|
||||||
@ -48,10 +48,11 @@ if (! function_exists('get_podcast_metatags')) {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
|
$head = service('html_head');
|
||||||
|
|
||||||
$metatags
|
$head
|
||||||
->title($podcast->title . ' (@' . $podcast->handle . ') • ' . lang('Podcast.' . $page))
|
->title(sprintf('%s (@%s) • %s', $podcast->title, $podcast->handle, lang('Podcast.' . $page)))
|
||||||
->description(esc($podcast->description))
|
->description(esc($podcast->description))
|
||||||
->image((string) $podcast->cover->og_url)
|
->image((string) $podcast->cover->og_url)
|
||||||
->canonical((string) current_url())
|
->canonical((string) current_url())
|
||||||
@ -59,20 +60,18 @@ if (! function_exists('get_podcast_metatags')) {
|
|||||||
->og('image:height', (string) config('Images')->podcastCoverSizes['og']['height'])
|
->og('image:height', (string) config('Images')->podcastCoverSizes['og']['height'])
|
||||||
->og('locale', $podcast->language_code)
|
->og('locale', $podcast->language_code)
|
||||||
->og('site_name', esc(service('settings')->get('App.siteName')))
|
->og('site_name', esc(service('settings')->get('App.siteName')))
|
||||||
->push('link', [
|
->tag('link', null, [
|
||||||
'rel' => 'alternate',
|
'rel' => 'alternate',
|
||||||
'type' => 'application/activity+json',
|
'type' => 'application/activity+json',
|
||||||
'href' => url_to('podcast-activity', esc($podcast->handle)),
|
'href' => url_to('podcast-activity', esc($podcast->handle)),
|
||||||
]);
|
])->appendRawContent('<link type="application/rss+xml" rel="alternate" title="' . esc(
|
||||||
|
|
||||||
return '<link type="application/rss+xml" rel="alternate" title="' . esc(
|
|
||||||
$podcast->title
|
$podcast->title
|
||||||
) . '" href="' . $podcast->feed_url . '" />' . PHP_EOL . $metatags->__toString() . PHP_EOL . $schema->__toString();
|
) . '" href="' . $podcast->feed_url . '" />' . $schema);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('get_episode_metatags')) {
|
if (! function_exists('set_episode_metatags')) {
|
||||||
function get_episode_metatags(Episode $episode): string
|
function set_episode_metatags(Episode $episode): void
|
||||||
{
|
{
|
||||||
$schema = new Schema(
|
$schema = new Schema(
|
||||||
new Thing('PodcastEpisode', [
|
new Thing('PodcastEpisode', [
|
||||||
@ -80,7 +79,7 @@ if (! function_exists('get_episode_metatags')) {
|
|||||||
'name' => $episode->title,
|
'name' => $episode->title,
|
||||||
'image' => $episode->cover->feed_url,
|
'image' => $episode->cover->feed_url,
|
||||||
'description' => $episode->description,
|
'description' => $episode->description,
|
||||||
'datePublished' => $episode->published_at->format(DATE_ISO8601),
|
'datePublished' => $episode->published_at->format(DATE_ATOM),
|
||||||
'timeRequired' => iso8601_duration($episode->audio->duration),
|
'timeRequired' => iso8601_duration($episode->audio->duration),
|
||||||
'duration' => iso8601_duration($episode->audio->duration),
|
'duration' => iso8601_duration($episode->audio->duration),
|
||||||
'associatedMedia' => new Thing('MediaObject', [
|
'associatedMedia' => new Thing('MediaObject', [
|
||||||
@ -93,9 +92,10 @@ if (! function_exists('get_episode_metatags')) {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
|
$head = service('html_head');
|
||||||
|
|
||||||
$metatags
|
$head
|
||||||
->title($episode->title)
|
->title($episode->title)
|
||||||
->description(esc($episode->description))
|
->description(esc($episode->description))
|
||||||
->image((string) $episode->cover->og_url, 'player')
|
->image((string) $episode->cover->og_url, 'player')
|
||||||
@ -106,35 +106,34 @@ if (! function_exists('get_episode_metatags')) {
|
|||||||
->og('locale', $episode->podcast->language_code)
|
->og('locale', $episode->podcast->language_code)
|
||||||
->og('audio', $episode->audio_opengraph_url)
|
->og('audio', $episode->audio_opengraph_url)
|
||||||
->og('audio:type', $episode->audio->file_mimetype)
|
->og('audio:type', $episode->audio->file_mimetype)
|
||||||
->meta('article:published_time', $episode->published_at->format(DATE_ISO8601))
|
->meta('article:published_time', $episode->published_at->format(DATE_ATOM))
|
||||||
->meta('article:modified_time', $episode->updated_at->format(DATE_ISO8601))
|
->meta('article:modified_time', $episode->updated_at->format(DATE_ATOM))
|
||||||
->twitter('audio:partner', $episode->podcast->publisher ?? '')
|
->twitter('audio:partner', $episode->podcast->publisher ?? '')
|
||||||
->twitter('audio:artist_name', esc($episode->podcast->owner_name))
|
->twitter('audio:artist_name', esc($episode->podcast->owner_name))
|
||||||
->twitter('player', $episode->getEmbedUrl('light'))
|
->twitter('player', $episode->getEmbedUrl('light'))
|
||||||
->twitter('player:width', (string) config('Embed')->width)
|
->twitter('player:width', (string) config('Embed')->width)
|
||||||
->twitter('player:height', (string) config('Embed')->height)
|
->twitter('player:height', (string) config('Embed')->height)
|
||||||
->push('link', [
|
->tag('link', null, [
|
||||||
'rel' => 'alternate',
|
'rel' => 'alternate',
|
||||||
'type' => 'application/activity+json',
|
'type' => 'application/activity+json',
|
||||||
'href' => url_to('episode', $episode->podcast->handle, $episode->slug),
|
'href' => $episode->link,
|
||||||
]);
|
])
|
||||||
|
->appendRawContent('<link rel="alternate" type="application/json+oembed" href="' . base_url(
|
||||||
return $metatags->__toString() . PHP_EOL . '<link rel="alternate" type="application/json+oembed" href="' . base_url(
|
|
||||||
route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug)
|
route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug)
|
||||||
) . '" title="' . esc(
|
) . '" title="' . esc(
|
||||||
$episode->title
|
$episode->title
|
||||||
) . ' oEmbed json" />' . PHP_EOL . '<link rel="alternate" type="text/xml+oembed" href="' . base_url(
|
) . ' oEmbed json" />' . '<link rel="alternate" type="text/xml+oembed" href="' . base_url(
|
||||||
route_to('episode-oembed-xml', $episode->podcast->handle, $episode->slug)
|
route_to('episode-oembed-xml', $episode->podcast->handle, $episode->slug)
|
||||||
) . '" title="' . esc($episode->title) . ' oEmbed xml" />' . PHP_EOL . $schema->__toString();
|
) . '" title="' . esc($episode->title) . ' oEmbed xml" />' . $schema);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('get_post_metatags')) {
|
if (! function_exists('set_post_metatags')) {
|
||||||
function get_post_metatags(Post $post): string
|
function set_post_metatags(Post $post): void
|
||||||
{
|
{
|
||||||
$socialMediaPosting = new Thing('SocialMediaPosting', [
|
$socialMediaPosting = new Thing('SocialMediaPosting', [
|
||||||
'@id' => url_to('post', esc($post->actor->username), $post->id),
|
'@id' => url_to('post', esc($post->actor->username), $post->id),
|
||||||
'datePublished' => $post->published_at->format(DATE_ISO8601),
|
'datePublished' => $post->published_at->format(DATE_ATOM),
|
||||||
'author' => new Thing('Person', [
|
'author' => new Thing('Person', [
|
||||||
'name' => $post->actor->display_name,
|
'name' => $post->actor->display_name,
|
||||||
'url' => $post->actor->uri,
|
'url' => $post->actor->uri,
|
||||||
@ -162,8 +161,10 @@ if (! function_exists('get_post_metatags')) {
|
|||||||
|
|
||||||
$schema = new Schema($socialMediaPosting);
|
$schema = new Schema($socialMediaPosting);
|
||||||
|
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
$metatags
|
$head = service('html_head');
|
||||||
|
|
||||||
|
$head
|
||||||
->title(lang('Post.title', [
|
->title(lang('Post.title', [
|
||||||
'actorDisplayName' => $post->actor->display_name,
|
'actorDisplayName' => $post->actor->display_name,
|
||||||
]))
|
]))
|
||||||
@ -171,18 +172,16 @@ if (! function_exists('get_post_metatags')) {
|
|||||||
->image($post->actor->avatar_image_url)
|
->image($post->actor->avatar_image_url)
|
||||||
->canonical((string) current_url())
|
->canonical((string) current_url())
|
||||||
->og('site_name', esc(service('settings')->get('App.siteName')))
|
->og('site_name', esc(service('settings')->get('App.siteName')))
|
||||||
->push('link', [
|
->tag('link', null, [
|
||||||
'rel' => 'alternate',
|
'rel' => 'alternate',
|
||||||
'type' => 'application/activity+json',
|
'type' => 'application/activity+json',
|
||||||
'href' => url_to('post', esc($post->actor->username), $post->id),
|
'href' => url_to('post', esc($post->actor->username), $post->id),
|
||||||
]);
|
])->appendRawContent((string) $schema);
|
||||||
|
|
||||||
return $metatags->__toString() . PHP_EOL . $schema->__toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('get_episode_comment_metatags')) {
|
if (! function_exists('set_episode_comment_metatags')) {
|
||||||
function get_episode_comment_metatags(EpisodeComment $episodeComment): string
|
function set_episode_comment_metatags(EpisodeComment $episodeComment): void
|
||||||
{
|
{
|
||||||
$schema = new Schema(new Thing('SocialMediaPosting', [
|
$schema = new Schema(new Thing('SocialMediaPosting', [
|
||||||
'@id' => url_to(
|
'@id' => url_to(
|
||||||
@ -191,7 +190,7 @@ if (! function_exists('get_episode_comment_metatags')) {
|
|||||||
$episodeComment->episode->slug,
|
$episodeComment->episode->slug,
|
||||||
$episodeComment->id
|
$episodeComment->id
|
||||||
),
|
),
|
||||||
'datePublished' => $episodeComment->created_at->format(DATE_ISO8601),
|
'datePublished' => $episodeComment->created_at->format(DATE_ATOM),
|
||||||
'author' => new Thing('Person', [
|
'author' => new Thing('Person', [
|
||||||
'name' => $episodeComment->actor->display_name,
|
'name' => $episodeComment->actor->display_name,
|
||||||
'url' => $episodeComment->actor->uri,
|
'url' => $episodeComment->actor->uri,
|
||||||
@ -200,8 +199,10 @@ if (! function_exists('get_episode_comment_metatags')) {
|
|||||||
'upvoteCount' => $episodeComment->likes_count,
|
'upvoteCount' => $episodeComment->likes_count,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
$metatags
|
$head = service('html_head');
|
||||||
|
|
||||||
|
$head
|
||||||
->title(lang('Comment.title', [
|
->title(lang('Comment.title', [
|
||||||
'actorDisplayName' => $episodeComment->actor->display_name,
|
'actorDisplayName' => $episodeComment->actor->display_name,
|
||||||
'episodeTitle' => $episodeComment->episode->title,
|
'episodeTitle' => $episodeComment->episode->title,
|
||||||
@ -210,7 +211,7 @@ if (! function_exists('get_episode_comment_metatags')) {
|
|||||||
->image($episodeComment->actor->avatar_image_url)
|
->image($episodeComment->actor->avatar_image_url)
|
||||||
->canonical((string) current_url())
|
->canonical((string) current_url())
|
||||||
->og('site_name', esc(service('settings')->get('App.siteName')))
|
->og('site_name', esc(service('settings')->get('App.siteName')))
|
||||||
->push('link', [
|
->tag('link', null, [
|
||||||
'rel' => 'alternate',
|
'rel' => 'alternate',
|
||||||
'type' => 'application/activity+json',
|
'type' => 'application/activity+json',
|
||||||
'href' => url_to(
|
'href' => url_to(
|
||||||
@ -219,17 +220,16 @@ if (! function_exists('get_episode_comment_metatags')) {
|
|||||||
$episodeComment->episode->slug,
|
$episodeComment->episode->slug,
|
||||||
$episodeComment->id
|
$episodeComment->id
|
||||||
),
|
),
|
||||||
]);
|
])->appendRawContent((string) $schema);
|
||||||
|
|
||||||
return $metatags->__toString() . PHP_EOL . $schema->__toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('get_follow_metatags')) {
|
if (! function_exists('set_follow_metatags')) {
|
||||||
function get_follow_metatags(Actor $actor): string
|
function set_follow_metatags(Actor $actor): void
|
||||||
{
|
{
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
$metatags
|
$head = service('html_head');
|
||||||
|
$head
|
||||||
->title(lang('Podcast.followTitle', [
|
->title(lang('Podcast.followTitle', [
|
||||||
'actorDisplayName' => $actor->display_name,
|
'actorDisplayName' => $actor->display_name,
|
||||||
]))
|
]))
|
||||||
@ -237,16 +237,15 @@ if (! function_exists('get_follow_metatags')) {
|
|||||||
->image($actor->avatar_image_url)
|
->image($actor->avatar_image_url)
|
||||||
->canonical((string) current_url())
|
->canonical((string) current_url())
|
||||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||||
|
|
||||||
return $metatags->__toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('get_remote_actions_metatags')) {
|
if (! function_exists('set_remote_actions_metatags')) {
|
||||||
function get_remote_actions_metatags(Post $post, string $action): string
|
function set_remote_actions_metatags(Post $post, string $action): void
|
||||||
{
|
{
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
$metatags
|
$head = service('html_head');
|
||||||
|
$head
|
||||||
->title(lang('Fediverse.' . $action . '.title', [
|
->title(lang('Fediverse.' . $action . '.title', [
|
||||||
'actorDisplayName' => $post->actor->display_name,
|
'actorDisplayName' => $post->actor->display_name,
|
||||||
],))
|
],))
|
||||||
@ -254,31 +253,30 @@ if (! function_exists('get_remote_actions_metatags')) {
|
|||||||
->image($post->actor->avatar_image_url)
|
->image($post->actor->avatar_image_url)
|
||||||
->canonical((string) current_url())
|
->canonical((string) current_url())
|
||||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||||
|
|
||||||
return $metatags->__toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('get_home_metatags')) {
|
if (! function_exists('set_home_metatags')) {
|
||||||
function get_home_metatags(): string
|
function set_home_metatags(): void
|
||||||
{
|
{
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
$metatags
|
$head = service('html_head');
|
||||||
|
$head
|
||||||
->title(service('settings')->get('App.siteName'))
|
->title(service('settings')->get('App.siteName'))
|
||||||
->description(esc(service('settings')->get('App.siteDescription')))
|
->description(esc(service('settings')->get('App.siteDescription')))
|
||||||
->image(get_site_icon_url('512'))
|
->image(get_site_icon_url('512'))
|
||||||
->canonical((string) current_url())
|
->canonical((string) current_url())
|
||||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||||
|
|
||||||
return $metatags->__toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('get_page_metatags')) {
|
if (! function_exists('set_page_metatags')) {
|
||||||
function get_page_metatags(Page $page): string
|
function set_page_metatags(Page $page): void
|
||||||
{
|
{
|
||||||
$metatags = new MetaTags();
|
/** @var HtmlHead $head */
|
||||||
$metatags
|
$head = service('html_head');
|
||||||
|
$head
|
||||||
->title(
|
->title(
|
||||||
$page->title . service('settings')->get('App.siteTitleSeparator') . service(
|
$page->title . service('settings')->get('App.siteTitleSeparator') . service(
|
||||||
'settings'
|
'settings'
|
||||||
@ -289,7 +287,6 @@ if (! function_exists('get_page_metatags')) {
|
|||||||
->canonical((string) current_url())
|
->canonical((string) current_url())
|
||||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||||
|
|
||||||
return $metatags->__toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
188
app/Libraries/HtmlHead.php
Normal file
188
app/Libraries/HtmlHead.php
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Libraries;
|
||||||
|
|
||||||
|
use App\Controllers\WebmanifestController;
|
||||||
|
use Override;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired by https://github.com/melbahja/seo
|
||||||
|
*/
|
||||||
|
class HtmlHead implements Stringable
|
||||||
|
{
|
||||||
|
protected ?string $title = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array{name:string,value:string|null,attributes:array<string,string|null>}[]
|
||||||
|
*/
|
||||||
|
protected array $tags = [];
|
||||||
|
|
||||||
|
protected string $rawContent = '';
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
helper('misc');
|
||||||
|
$this
|
||||||
|
->tag('meta', null, [
|
||||||
|
'charset' => 'UTF-8',
|
||||||
|
])
|
||||||
|
->meta('viewport', 'width=device-width, initial-scale=1.0')
|
||||||
|
->tag('link', null, [
|
||||||
|
'rel' => 'icon',
|
||||||
|
'type' => 'image/x-icon',
|
||||||
|
'href' => get_site_icon_url('ico'),
|
||||||
|
])
|
||||||
|
->tag('link', null, [
|
||||||
|
'rel' => 'apple-touch-icon',
|
||||||
|
'href' => get_site_icon_url('180'),
|
||||||
|
])
|
||||||
|
->tag('link', null, [
|
||||||
|
'rel' => 'manifest',
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
'href' => isset($podcast) ? route_to('podcast-webmanifest', esc($podcast->handle)) : route_to(
|
||||||
|
'webmanifest'
|
||||||
|
),
|
||||||
|
])
|
||||||
|
->meta(
|
||||||
|
'theme-color',
|
||||||
|
WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme']
|
||||||
|
)
|
||||||
|
->tag('link', null, [
|
||||||
|
'rel' => 'stylesheet',
|
||||||
|
'type' => 'text/css',
|
||||||
|
'href' => route_to('themes-colors-css'),
|
||||||
|
])
|
||||||
|
->appendRawContent(<<<HTML
|
||||||
|
<script>
|
||||||
|
// Check that service workers are supported
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
// Use the window load event to keep the page load performant
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('/sw.js');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
HTML);
|
||||||
|
|
||||||
|
if ($this->title) {
|
||||||
|
$this->tag('title', esc($this->title));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url_is(route_to('admin') . '*') || url_is(base_url(config('Auth')->gateway) . '*')) {
|
||||||
|
// restricted admin and auth areas, do not index
|
||||||
|
$this->meta('robots', 'noindex');
|
||||||
|
} else {
|
||||||
|
// public website, set siteHead hook only there
|
||||||
|
service('plugins')
|
||||||
|
->siteHead($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
$head = '<head>';
|
||||||
|
foreach ($this->tags as $tag) {
|
||||||
|
if ($tag['value'] === null) {
|
||||||
|
$head .= <<<HTML
|
||||||
|
<{$tag['name']}{$this->stringify_attributes($tag['attributes'])}/>
|
||||||
|
HTML;
|
||||||
|
} else {
|
||||||
|
$head .= <<<HTML
|
||||||
|
<{$tag['name']} {$this->stringify_attributes($tag['attributes'])}>{$tag['value']}</{$tag['name']}>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$head .= $this->rawContent . '</head>';
|
||||||
|
|
||||||
|
// reset head for next render
|
||||||
|
$this->title = null;
|
||||||
|
$this->tags = [];
|
||||||
|
$this->rawContent = '';
|
||||||
|
|
||||||
|
return $head;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function title(string $title): self
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
return $this->meta('title', $title)
|
||||||
|
->og('title', $title)
|
||||||
|
->twitter('title', $title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(string $desc): self
|
||||||
|
{
|
||||||
|
return $this->meta('description', $desc)
|
||||||
|
->og('description', $desc)
|
||||||
|
->twitter('description', $desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function image(string $url, string $card = 'summary_large_image'): self
|
||||||
|
{
|
||||||
|
return $this->og('image', $url)
|
||||||
|
->twitter('card', $card)
|
||||||
|
->twitter('image', $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canonical(string $url): self
|
||||||
|
{
|
||||||
|
return $this->tag('link', null, [
|
||||||
|
'rel' => 'canonical',
|
||||||
|
'href' => $url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function twitter(string $name, string $value): self
|
||||||
|
{
|
||||||
|
$this->meta("twitter:{$name}", $value);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string,string|null> $attributes
|
||||||
|
*/
|
||||||
|
public function tag(string $name, ?string $value = null, array $attributes = []): self
|
||||||
|
{
|
||||||
|
$this->tags[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'value' => $value,
|
||||||
|
'attributes' => $attributes,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function meta(string $name, string $content): self
|
||||||
|
{
|
||||||
|
$this->tag('meta', null, [
|
||||||
|
'name' => $name,
|
||||||
|
'content' => $content,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function og(string $name, string $content): self
|
||||||
|
{
|
||||||
|
$this->meta('og:' . $name, $content);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appendRawContent(string $content): self
|
||||||
|
{
|
||||||
|
$this->rawContent .= $content;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string|null> $attributes
|
||||||
|
*/
|
||||||
|
private function stringify_attributes(array $attributes): string
|
||||||
|
{
|
||||||
|
return stringify_attributes($attributes);
|
||||||
|
}
|
||||||
|
}
|
@ -229,8 +229,6 @@ class EpisodeCommentModel extends UuidModel
|
|||||||
$episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC'
|
$episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC'
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIXME:?
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
return $this->convertUuidFieldsToStrings(
|
return $this->convertUuidFieldsToStrings(
|
||||||
$allEpisodeComments->getCustomResultObject($this->tempReturnType),
|
$allEpisodeComments->getCustomResultObject($this->tempReturnType),
|
||||||
$this->tempReturnType
|
$this->tempReturnType
|
||||||
|
@ -3,17 +3,17 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @icon('funding:buymeacoffee')
|
* @icon("funding:buymeacoffee")
|
||||||
* @icon('funding:donorbox')
|
* @icon("funding:donorbox")
|
||||||
* @icon('funding:gofundme')
|
* @icon("funding:gofundme")
|
||||||
* @icon('funding:helloasso')
|
* @icon("funding:helloasso")
|
||||||
* @icon('funding:indiegogo')
|
* @icon("funding:indiegogo")
|
||||||
* @icon('funding:kickstarter')
|
* @icon("funding:kickstarter")
|
||||||
* @icon('funding:kisskissbankbank')
|
* @icon("funding:kisskissbankbank")
|
||||||
* @icon('funding:kofi')
|
* @icon("funding:kofi")
|
||||||
* @icon('funding:liberapay')
|
* @icon("funding:liberapay")
|
||||||
* @icon('funding:patreon')
|
* @icon("funding:patreon")
|
||||||
* @icon('funding:paypal')
|
* @icon("funding:paypal")
|
||||||
* @icon('funding:tipeee')
|
* @icon("funding:tipeee")
|
||||||
* @icon('funding:ulule')
|
* @icon("funding:ulule")
|
||||||
*/
|
*/
|
||||||
|
@ -3,46 +3,46 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @icon('podcasting:amazon')
|
* @icon("podcasting:amazon")
|
||||||
* @icon('podcasting:antennapod')
|
* @icon("podcasting:antennapod")
|
||||||
* @icon('podcasting:anytime')
|
* @icon("podcasting:anytime")
|
||||||
* @icon('podcasting:apple')
|
* @icon("podcasting:apple")
|
||||||
* @icon('podcasting:blubrry')
|
* @icon("podcasting:blubrry")
|
||||||
* @icon('podcasting:breez')
|
* @icon("podcasting:breez")
|
||||||
* @icon('podcasting:castamatic')
|
* @icon("podcasting:castamatic")
|
||||||
* @icon('podcasting:castbox')
|
* @icon("podcasting:castbox")
|
||||||
* @icon('podcasting:castopod')
|
* @icon("podcasting:castopod")
|
||||||
* @icon('podcasting:castro')
|
* @icon("podcasting:castro")
|
||||||
* @icon('podcasting:deezer')
|
* @icon("podcasting:deezer")
|
||||||
* @icon('podcasting:episodes-fm')
|
* @icon("podcasting:episodes-fm")
|
||||||
* @icon('podcasting:fountain')
|
* @icon("podcasting:fountain")
|
||||||
* @icon('podcasting:fyyd')
|
* @icon("podcasting:fyyd")
|
||||||
* @icon('podcasting:gpodder')
|
* @icon("podcasting:gpodder")
|
||||||
* @icon('podcasting:ivoox')
|
* @icon("podcasting:ivoox")
|
||||||
* @icon('podcasting:listennotes')
|
* @icon("podcasting:listennotes")
|
||||||
* @icon('podcasting:overcast')
|
* @icon("podcasting:overcast")
|
||||||
* @icon('podcasting:playerfm')
|
* @icon("podcasting:playerfm")
|
||||||
* @icon('podcasting:plink')
|
* @icon("podcasting:plink")
|
||||||
* @icon('podcasting:pocketcasts')
|
* @icon("podcasting:pocketcasts")
|
||||||
* @icon('podcasting:podbean')
|
* @icon("podcasting:podbean")
|
||||||
* @icon('podcasting:podcastaddict')
|
* @icon("podcasting:podcastaddict")
|
||||||
* @icon('podcasting:podcastguru')
|
* @icon("podcasting:podcastguru")
|
||||||
* @icon('podcasting:podcastindex')
|
* @icon("podcasting:podcastindex")
|
||||||
* @icon('podcasting:podchaser')
|
* @icon("podcasting:podchaser")
|
||||||
* @icon('podcasting:podcloud')
|
* @icon("podcasting:podcloud")
|
||||||
* @icon('podcasting:podfriend')
|
* @icon("podcasting:podfriend")
|
||||||
* @icon('podcasting:podinstall')
|
* @icon("podcasting:podinstall")
|
||||||
* @icon('podcasting:podlink')
|
* @icon("podcasting:podlink")
|
||||||
* @icon('podcasting:podlp')
|
* @icon("podcasting:podlp")
|
||||||
* @icon('podcasting:podnews')
|
* @icon("podcasting:podnews")
|
||||||
* @icon('podcasting:podtail')
|
* @icon("podcasting:podtail")
|
||||||
* @icon('podcasting:podverse')
|
* @icon("podcasting:podverse")
|
||||||
* @icon('podcasting:radiopublic')
|
* @icon("podcasting:radiopublic")
|
||||||
* @icon('podcasting:sphinxchat')
|
* @icon("podcasting:sphinxchat")
|
||||||
* @icon('podcasting:spotify')
|
* @icon("podcasting:spotify")
|
||||||
* @icon('podcasting:spreaker')
|
* @icon("podcasting:spreaker")
|
||||||
* @icon('podcasting:truefans')
|
* @icon("podcasting:truefans")
|
||||||
* @icon('podcasting:tsacdop')
|
* @icon("podcasting:tsacdop")
|
||||||
* @icon('podcasting:tunein')
|
* @icon("podcasting:tunein")
|
||||||
* @icon('podcasting:youtube-music')
|
* @icon("podcasting:youtube-music")
|
||||||
*/
|
*/
|
||||||
|
@ -3,26 +3,26 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @icon('social:bluesky')
|
* @icon("social:bluesky")
|
||||||
* @icon('social:discord')
|
* @icon("social:discord")
|
||||||
* @icon('social:facebook')
|
* @icon("social:facebook")
|
||||||
* @icon('social:funkwhale')
|
* @icon("social:funkwhale")
|
||||||
* @icon('social:instagram')
|
* @icon("social:instagram")
|
||||||
* @icon('social:linkedin')
|
* @icon("social:linkedin")
|
||||||
* @icon('social:mastodon')
|
* @icon("social:mastodon")
|
||||||
* @icon('social:matrix')
|
* @icon("social:matrix")
|
||||||
* @icon('social:misskey')
|
* @icon("social:misskey")
|
||||||
* @icon('social:mobilizon')
|
* @icon("social:mobilizon")
|
||||||
* @icon('social:peertube')
|
* @icon("social:peertube")
|
||||||
* @icon('social:pixelfed')
|
* @icon("social:pixelfed")
|
||||||
* @icon('social:pleroma')
|
* @icon("social:pleroma")
|
||||||
* @icon('social:plume')
|
* @icon("social:plume")
|
||||||
* @icon('social:slack')
|
* @icon("social:slack")
|
||||||
* @icon('social:telegram')
|
* @icon("social:telegram")
|
||||||
* @icon('social:threads')
|
* @icon("social:threads")
|
||||||
* @icon('social:tiktok')
|
* @icon("social:tiktok")
|
||||||
* @icon('social:twitch')
|
* @icon("social:twitch")
|
||||||
* @icon('social:writefreely')
|
* @icon("social:writefreely")
|
||||||
* @icon('social:x')
|
* @icon("social:x")
|
||||||
* @icon('social:youtube')
|
* @icon("social:youtube")
|
||||||
*/
|
*/
|
||||||
|
@ -21,7 +21,7 @@ import Tooltip from "./modules/Tooltip";
|
|||||||
import ValidateFileSize from "./modules/ValidateFileSize";
|
import ValidateFileSize from "./modules/ValidateFileSize";
|
||||||
import "./modules/video-clip-previewer";
|
import "./modules/video-clip-previewer";
|
||||||
import VideoClipBuilder from "./modules/VideoClipBuilder";
|
import VideoClipBuilder from "./modules/VideoClipBuilder";
|
||||||
import "./modules/xml-editor";
|
import "./modules/code-editor";
|
||||||
import "@patternfly/elements/pf-tabs/pf-tabs.js";
|
import "@patternfly/elements/pf-tabs/pf-tabs.js";
|
||||||
import FieldArray from "./modules/FieldArray";
|
import FieldArray from "./modules/FieldArray";
|
||||||
|
|
||||||
|
222
app/Resources/js/modules/code-editor.ts
Normal file
222
app/Resources/js/modules/code-editor.ts
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import { indentWithTab } from "@codemirror/commands";
|
||||||
|
import { html as htmlLang } from "@codemirror/lang-html";
|
||||||
|
import { xml } from "@codemirror/lang-xml";
|
||||||
|
import {
|
||||||
|
defaultHighlightStyle,
|
||||||
|
syntaxHighlighting,
|
||||||
|
} from "@codemirror/language";
|
||||||
|
import { Compartment, EditorState, Extension } from "@codemirror/state";
|
||||||
|
import { keymap, ViewUpdate } from "@codemirror/view";
|
||||||
|
import { basicSetup, EditorView } from "codemirror";
|
||||||
|
import { prettify as prettifyHTML, minify as minifyHTML } from "htmlfy";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
queryAssignedNodes,
|
||||||
|
state,
|
||||||
|
} from "lit/decorators.js";
|
||||||
|
import xmlFormat from "xml-formatter";
|
||||||
|
|
||||||
|
const language = new Compartment();
|
||||||
|
|
||||||
|
@customElement("code-editor")
|
||||||
|
export class XMLEditor extends LitElement {
|
||||||
|
@queryAssignedNodes({ slot: "textarea" })
|
||||||
|
_textarea!: NodeListOf<HTMLTextAreaElement>;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
lang = "html";
|
||||||
|
|
||||||
|
@state()
|
||||||
|
editorState!: EditorState;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
editorView!: EditorView;
|
||||||
|
|
||||||
|
_textareaEvents = [
|
||||||
|
{
|
||||||
|
events: ["focus", "invalid"],
|
||||||
|
onEvent: (_: Event, editor: EditorView) => {
|
||||||
|
// focus editor when textarea is focused or invalid
|
||||||
|
editor.focus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
firstUpdated(): void {
|
||||||
|
const minHeightEditor = EditorView.baseTheme({
|
||||||
|
".cm-content, .cm-gutter": {
|
||||||
|
minHeight: this._textarea[0].clientHeight + "px",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const extensions: Extension[] = [
|
||||||
|
basicSetup,
|
||||||
|
keymap.of([indentWithTab]),
|
||||||
|
minHeightEditor,
|
||||||
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
|
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
|
||||||
|
if (viewUpdate.docChanged) {
|
||||||
|
// Document changed, minify and update textarea value
|
||||||
|
switch (this.lang) {
|
||||||
|
case "xml":
|
||||||
|
this._textarea[0].value = minifyXML(
|
||||||
|
viewUpdate.state.doc.toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "html":
|
||||||
|
this._textarea[0].value = minifyHTML(
|
||||||
|
viewUpdate.state.doc.toString()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._textarea[0].value = viewUpdate.state.doc.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
let editorContents = "";
|
||||||
|
switch (this.lang) {
|
||||||
|
case "xml":
|
||||||
|
editorContents = formatXML(this._textarea[0].value);
|
||||||
|
extensions.push(language.of(xml()));
|
||||||
|
break;
|
||||||
|
case "html":
|
||||||
|
editorContents = prettifyHTML(this._textarea[0].value);
|
||||||
|
extensions.push(language.of(htmlLang()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editorState = EditorState.create({
|
||||||
|
doc: editorContents,
|
||||||
|
extensions: extensions,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.editorView = new EditorView({
|
||||||
|
state: this.editorState,
|
||||||
|
root: this.shadowRoot as ShadowRoot,
|
||||||
|
parent: this.shadowRoot as ShadowRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
// hide textarea
|
||||||
|
this._textarea[0].style.position = "absolute";
|
||||||
|
this._textarea[0].style.opacity = "0";
|
||||||
|
this._textarea[0].style.zIndex = "-9999";
|
||||||
|
this._textarea[0].style.pointerEvents = "none";
|
||||||
|
|
||||||
|
for (const event of this._textareaEvents) {
|
||||||
|
event.events.forEach((name) => {
|
||||||
|
this._textarea[0].addEventListener(name, (e) =>
|
||||||
|
event.onEvent(e, this.editorView)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
|
||||||
|
for (const event of this._textareaEvents) {
|
||||||
|
event.events.forEach((name) => {
|
||||||
|
this._textarea[0].removeEventListener(name, (e) =>
|
||||||
|
event.onEvent(e, this.editorView)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
.cm-editor {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 3px solid hsl(var(--color-border-contrast));
|
||||||
|
background-color: hsl(var(--color-background-elevated));
|
||||||
|
}
|
||||||
|
.cm-editor.cm-focused {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px hsl(var(--color-background-elevated)),
|
||||||
|
0 0 0 calc(4px) hsl(var(--color-accent-base));
|
||||||
|
}
|
||||||
|
.cm-gutters {
|
||||||
|
background-color: hsl(var(--color-background-elevated)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-activeLine {
|
||||||
|
background-color: hsla(
|
||||||
|
var(--color-background-highlight) / 0.25
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-activeLineGutter {
|
||||||
|
background-color: hsl(var(--color-background-highlight)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ͼ4 .cm-line {
|
||||||
|
caret-color: hsl(var(--color-text-base)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ͼ1 .cm-cursor {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
render(): TemplateResult<1> {
|
||||||
|
return html`<slot name="textarea"></slot>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatXML(contents: string) {
|
||||||
|
if (contents === "") {
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
let editorContents = "";
|
||||||
|
try {
|
||||||
|
editorContents = xmlFormat(contents, {
|
||||||
|
indentation: " ",
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// xml doesn't have a root node
|
||||||
|
editorContents = xmlFormat("<root>" + contents + "</root>", {
|
||||||
|
indentation: " ",
|
||||||
|
});
|
||||||
|
// remove root, unnecessary lines and indents
|
||||||
|
editorContents = editorContents
|
||||||
|
.replace(/^<root>/, "")
|
||||||
|
.replace(/<\/root>$/, "")
|
||||||
|
.replace(/^\s*[\r\n]/gm, "")
|
||||||
|
.replace(/[\r\n] {2}/gm, "\r\n")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return editorContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
function minifyXML(contents: string) {
|
||||||
|
if (contents === "") {
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
let minifiedContent = "";
|
||||||
|
try {
|
||||||
|
minifiedContent = xmlFormat.minify(contents, {
|
||||||
|
collapseContent: true,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
minifiedContent = xmlFormat.minify(`<root>${contents}</root>`, {
|
||||||
|
collapseContent: true,
|
||||||
|
});
|
||||||
|
// remove root
|
||||||
|
minifiedContent = minifiedContent
|
||||||
|
.replace(/^<root>/, "")
|
||||||
|
.replace(/<\/root>$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return minifiedContent;
|
||||||
|
}
|
@ -1,125 +0,0 @@
|
|||||||
import { indentWithTab } from "@codemirror/commands";
|
|
||||||
import { xml } from "@codemirror/lang-xml";
|
|
||||||
import {
|
|
||||||
defaultHighlightStyle,
|
|
||||||
syntaxHighlighting,
|
|
||||||
} from "@codemirror/language";
|
|
||||||
import { Compartment, EditorState } from "@codemirror/state";
|
|
||||||
import { keymap, ViewUpdate } from "@codemirror/view";
|
|
||||||
import { basicSetup, EditorView } from "codemirror";
|
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, queryAssignedNodes, state } from "lit/decorators.js";
|
|
||||||
import prettifyXML from "xml-formatter";
|
|
||||||
|
|
||||||
const language = new Compartment();
|
|
||||||
|
|
||||||
@customElement("xml-editor")
|
|
||||||
export class XMLEditor extends LitElement {
|
|
||||||
@queryAssignedNodes({ slot: "textarea" })
|
|
||||||
_textarea!: NodeListOf<HTMLTextAreaElement>;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
editorState!: EditorState;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
editorView!: EditorView;
|
|
||||||
|
|
||||||
firstUpdated(): void {
|
|
||||||
const minHeightEditor = EditorView.theme({
|
|
||||||
".cm-content, .cm-gutter": {
|
|
||||||
minHeight: this._textarea[0].clientHeight + "px",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let editorContents = "";
|
|
||||||
if (this._textarea[0].value) {
|
|
||||||
try {
|
|
||||||
editorContents = prettifyXML(this._textarea[0].value, {
|
|
||||||
indentation: " ",
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// xml doesn't have a root node
|
|
||||||
editorContents = prettifyXML(
|
|
||||||
"<root>" + this._textarea[0].value + "</root>",
|
|
||||||
{
|
|
||||||
indentation: " ",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// remove root, unnecessary lines and indents
|
|
||||||
editorContents = editorContents
|
|
||||||
.replace(/^<root>/, "")
|
|
||||||
.replace(/<\/root>$/, "")
|
|
||||||
.replace(/^\s*[\r\n]/gm, "")
|
|
||||||
.replace(/[\r\n] {2}/gm, "\r\n")
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.editorState = EditorState.create({
|
|
||||||
doc: editorContents,
|
|
||||||
extensions: [
|
|
||||||
basicSetup,
|
|
||||||
keymap.of([indentWithTab]),
|
|
||||||
language.of(xml()),
|
|
||||||
minHeightEditor,
|
|
||||||
syntaxHighlighting(defaultHighlightStyle),
|
|
||||||
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
|
|
||||||
if (viewUpdate.docChanged) {
|
|
||||||
// Document changed, update textarea value
|
|
||||||
this._textarea[0].value = viewUpdate.state.doc.toString();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.editorView = new EditorView({
|
|
||||||
state: this.editorState,
|
|
||||||
root: this.shadowRoot as ShadowRoot,
|
|
||||||
parent: this.shadowRoot as ShadowRoot,
|
|
||||||
});
|
|
||||||
|
|
||||||
// hide textarea
|
|
||||||
this._textarea[0].style.position = "absolute";
|
|
||||||
this._textarea[0].style.opacity = "0";
|
|
||||||
this._textarea[0].style.zIndex = "-9999";
|
|
||||||
this._textarea[0].style.pointerEvents = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
.cm-editor {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 3px solid hsl(var(--color-border-contrast));
|
|
||||||
background-color: hsl(var(--color-background-elevated));
|
|
||||||
}
|
|
||||||
.cm-editor.cm-focused {
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 2px hsl(var(--color-background-elevated)),
|
|
||||||
0 0 0 calc(4px) hsl(var(--color-accent-base));
|
|
||||||
}
|
|
||||||
.cm-gutters {
|
|
||||||
background-color: hsl(var(--color-background-elevated)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-activeLine {
|
|
||||||
background-color: hsl(var(--color-background-highlight)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-activeLineGutter {
|
|
||||||
background-color: hsl(var(--color-background-highlight)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ͼ4 .cm-line {
|
|
||||||
caret-color: hsl(var(--color-text-base)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ͼ1 .cm-cursor {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
render(): TemplateResult<1> {
|
|
||||||
return html`<slot name="textarea"></slot>`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,19 +30,19 @@ class Alert extends Component
|
|||||||
$variantData = match ($this->variant) {
|
$variantData = match ($this->variant) {
|
||||||
'success' => [
|
'success' => [
|
||||||
'class' => 'text-pine-900 bg-pine-100 border-pine-300',
|
'class' => 'text-pine-900 bg-pine-100 border-pine-300',
|
||||||
'glyph' => 'check-fill', // @icon('check-fill')
|
'glyph' => 'check-fill', // @icon("check-fill")
|
||||||
],
|
],
|
||||||
'danger' => [
|
'danger' => [
|
||||||
'class' => 'text-red-900 bg-red-100 border-red-300',
|
'class' => 'text-red-900 bg-red-100 border-red-300',
|
||||||
'glyph' => 'close-fill', // @icon('close-fill')
|
'glyph' => 'close-fill', // @icon("close-fill")
|
||||||
],
|
],
|
||||||
'warning' => [
|
'warning' => [
|
||||||
'class' => 'text-yellow-900 bg-yellow-100 border-yellow-300',
|
'class' => 'text-yellow-900 bg-yellow-100 border-yellow-300',
|
||||||
'glyph' => 'alert-fill', // @icon('alert-fill')
|
'glyph' => 'alert-fill', // @icon("alert-fill")
|
||||||
],
|
],
|
||||||
default => [
|
default => [
|
||||||
'class' => 'text-blue-900 bg-blue-100 border-blue-300',
|
'class' => 'text-blue-900 bg-blue-100 border-blue-300',
|
||||||
'glyph' => 'error-warning-fill', // @icon('error-warning-fill')
|
'glyph' => 'error-warning-fill', // @icon("error-warning-fill")
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,20 +6,22 @@ namespace App\Views\Components\Forms;
|
|||||||
|
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
class XMLEditor extends FormComponent
|
class CodeEditor extends FormComponent
|
||||||
{
|
{
|
||||||
protected array $props = ['content'];
|
protected array $props = ['content', 'lang'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected array $attributes = [
|
protected array $attributes = [
|
||||||
'rows' => '5',
|
'rows' => '6',
|
||||||
'class' => 'textarea',
|
'class' => 'textarea',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected string $content = '';
|
protected string $content = '';
|
||||||
|
|
||||||
|
protected string $lang = '';
|
||||||
|
|
||||||
public function setContent(string $value): void
|
public function setContent(string $value): void
|
||||||
{
|
{
|
||||||
$this->content = htmlspecialchars_decode($value);
|
$this->content = htmlspecialchars_decode($value);
|
||||||
@ -32,7 +34,7 @@ class XMLEditor extends FormComponent
|
|||||||
$textarea = form_textarea($this->attributes, $this->content);
|
$textarea = form_textarea($this->attributes, $this->content);
|
||||||
|
|
||||||
return <<<HTML
|
return <<<HTML
|
||||||
<xml-editor>{$textarea}</xml-editor>
|
<code-editor lang="{$this->lang}">{$textarea}</code-editor>
|
||||||
HTML;
|
HTML;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Views\Decorators;
|
|
||||||
|
|
||||||
use CodeIgniter\View\ViewDecoratorInterface;
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
class SiteHead implements ViewDecoratorInterface
|
|
||||||
{
|
|
||||||
private static int $renderedCount = 0;
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public static function decorate(string $html): string
|
|
||||||
{
|
|
||||||
if (url_is(config('Admin')->gateway . '*') || url_is(config('Install')->gateway)) {
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (static::$renderedCount > 0) {
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
ob_start(); // Start output buffering
|
|
||||||
// run hook to add tags to <head>
|
|
||||||
service('plugins')->siteHead();
|
|
||||||
$metaTags = ob_get_contents(); // Store buffer in variable
|
|
||||||
ob_end_clean();
|
|
||||||
|
|
||||||
if (str_contains($html, '</head>')) {
|
|
||||||
$html = str_replace('</head>', "\n\t{$metaTags}\n</head>", $html);
|
|
||||||
++static::$renderedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,37 +9,37 @@
|
|||||||
"php": "^8.3",
|
"php": "^8.3",
|
||||||
"adaures/ipcat-php": "^v1.0.0",
|
"adaures/ipcat-php": "^v1.0.0",
|
||||||
"adaures/podcast-persons-taxonomy": "^v1.0.1",
|
"adaures/podcast-persons-taxonomy": "^v1.0.1",
|
||||||
"aws/aws-sdk-php": "^3.325.2",
|
"aws/aws-sdk-php": "^3.334.7",
|
||||||
"chrisjean/php-ico": "^1.0.4",
|
"chrisjean/php-ico": "^1.0.4",
|
||||||
"cocur/slugify": "^v4.6.0",
|
"cocur/slugify": "^v4.6.0",
|
||||||
"codeigniter4/framework": "v4.5.5",
|
"codeigniter4/framework": "v4.5.5",
|
||||||
"codeigniter4/settings": "v2.2.0",
|
"codeigniter4/settings": "v2.2.0",
|
||||||
"codeigniter4/shield": "v1.1.0",
|
"codeigniter4/shield": "v1.1.0",
|
||||||
"codeigniter4/tasks": "dev-develop",
|
"codeigniter4/tasks": "dev-develop",
|
||||||
"geoip2/geoip2": "v3.0.0",
|
"geoip2/geoip2": "v3.1.0",
|
||||||
"james-heinrich/getid3": "^2.0.0-beta6",
|
"james-heinrich/getid3": "^2.0.0-beta6",
|
||||||
"league/commonmark": "^2.5.3",
|
"league/commonmark": "^2.6.0",
|
||||||
"league/html-to-markdown": "5.1.1",
|
"league/html-to-markdown": "5.1.1",
|
||||||
"melbahja/seo": "^v2.1.1",
|
"melbahja/seo": "^v2.1.1",
|
||||||
"michalsn/codeigniter4-uuid": "v1.1.0",
|
"michalsn/codeigniter4-uuid": "v1.1.0",
|
||||||
"mpratt/embera": "^2.0.41",
|
"mpratt/embera": "^2.0.41",
|
||||||
"opawg/user-agents-v2-php": "dev-main",
|
"opawg/user-agents-v2-php": "dev-main",
|
||||||
"phpseclib/phpseclib": "~2.0.47",
|
"phpseclib/phpseclib": "~2.0.48",
|
||||||
"vlucas/phpdotenv": "v5.6.1",
|
"vlucas/phpdotenv": "v5.6.1",
|
||||||
"whichbrowser/parser": "^v2.1.8",
|
"whichbrowser/parser": "^v2.1.8",
|
||||||
"yassinedoghri/php-icons": "^v1.2.0",
|
"yassinedoghri/php-icons": "^v1.2.0",
|
||||||
"yassinedoghri/podcast-feed": "dev-main"
|
"yassinedoghri/podcast-feed": "dev-main"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"captainhook/captainhook": "^5.23.6",
|
"captainhook/captainhook": "^5.24.1",
|
||||||
"codeigniter/phpstan-codeigniter": "v1.4.3",
|
"codeigniter/phpstan-codeigniter": "v1.5.1",
|
||||||
"mikey179/vfsstream": "^v1.6.12",
|
"mikey179/vfsstream": "^v1.6.12",
|
||||||
"phpstan/extension-installer": "^1.4.3",
|
"phpstan/extension-installer": "^1.4.3",
|
||||||
"phpstan/phpstan": "^1.12.7",
|
"phpstan/phpstan": "^2.0.3",
|
||||||
"phpunit/phpunit": "^10.5.38",
|
"phpunit/phpunit": "^11.5.1",
|
||||||
"rector/rector": "^1.2.9",
|
"rector/rector": "^2.0.3",
|
||||||
"symplify/coding-standard": "^12.2.3",
|
"symplify/coding-standard": "^12.2.3",
|
||||||
"symplify/easy-coding-standard": "^12.3.6"
|
"symplify/easy-coding-standard": "^12.5.4"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
961
composer.lock
generated
961
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -37,7 +37,7 @@ RSS feed.
|
|||||||
Here is a good place to add new tags to the generated channel.
|
Here is a good place to add new tags to the generated channel.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
|
public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void
|
||||||
{
|
{
|
||||||
// …
|
// …
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ feed.
|
|||||||
Here is a good place to add new tags to the generated item.
|
Here is a good place to add new tags to the generated item.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
public function rssAfterItem(Epsiode $episode, SimpleRSSElement $item): void
|
public function rssAfterItem(Epsiode $episode, RssFeed $item): void
|
||||||
{
|
{
|
||||||
// …
|
// …
|
||||||
}
|
}
|
||||||
@ -75,11 +75,11 @@ public function rssAfterItem(Epsiode $episode, SimpleRSSElement $item): void
|
|||||||
|
|
||||||
This hook is executed in the public pages' `<head>` tag.
|
This hook is executed in the public pages' `<head>` tag.
|
||||||
|
|
||||||
This is a good place to add meta tags and third-party scripts to Castopod's
|
This is a good place to add meta tags, custom styles, and third-party scripts to
|
||||||
public pages.
|
Castopod's public pages.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
public function siteHead(): void
|
public function siteHead(HtmlHead $head): void
|
||||||
{
|
{
|
||||||
// …
|
// …
|
||||||
}
|
}
|
||||||
|
@ -102,8 +102,8 @@ each property being a field key and the value being a `Field` object.
|
|||||||
A field is a form element:
|
A field is a form element:
|
||||||
|
|
||||||
| Property | Type | Note |
|
| Property | Type | Note |
|
||||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||||
| `type` | `checkbox` \| `datetime` \| `email` \| `group` \| `markdown` \| `number` \| `radio-group` \| `select-multiple` \| `select` \| `text` \| `textarea` \| `toggler` \| `url` | Default is `text` |
|
| `type` | `checkbox` \| `datetime` \| `email` \| `group` \| `html` \| `markdown` \| `number` \| `radio-group` \| `rss` \| `select-multiple` \| `select` \| `text` \| `textarea` \| `toggler` \| `url` | Default is `text` |
|
||||||
| `label` (required) | `string` | Can be translated (see i18n) |
|
| `label` (required) | `string` | Can be translated (see i18n) |
|
||||||
| `hint` | `string` | Can be translated (see i18n) |
|
| `hint` | `string` | Can be translated (see i18n) |
|
||||||
| `helper` | `string` | Can be translated (see i18n) |
|
| `helper` | `string` | Can be translated (see i18n) |
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
title: 验证 & 授权
|
title: 验证 & 授权
|
||||||
---
|
---
|
||||||
|
|
||||||
Castopod 使用 `codeigniter/shield` 处理身份验证和授权 与自定义规则。 角色和权限
|
Castopod 使用 `codeigniter/shield`
|
||||||
在两个级别上定义: Roles and permissions are defined at two levels:
|
处理身份验证和授权 与自定义规则。 角色和权限在两个级别上定义: Roles and
|
||||||
|
permissions are defined at two levels:
|
||||||
|
|
||||||
1. [实例范围](#1-instance-wide-roles-and-permissions)
|
1. [实例范围](#1-instance-wide-roles-and-permissions)
|
||||||
2. [每个播客](#2-per-podcast-roles-and-permissions)
|
2. [每个播客](#2-per-podcast-roles-and-permissions)
|
||||||
|
@ -4,15 +4,12 @@ title: 官方 Docker 镜像
|
|||||||
|
|
||||||
Castopod 在其自动构建期间会将 3 个 Docker 映像推送到 Docker Hub :
|
Castopod 在其自动构建期间会将 3 个 Docker 映像推送到 Docker Hub :
|
||||||
|
|
||||||
- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod);一个使
|
- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod);一个使用 nginx 单元的整合 Castopod 镜像
|
||||||
用 nginx 单元的整合 Castopod 镜像
|
- [**`castopod/app`**](https://hub.docker.com/r/castopod/app):应用程序包,包含所有 Castopod 依赖关系
|
||||||
- [**`castopod/app`**](https://hub.docker.com/r/castopod/app):应用程序包,包含
|
- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server):Castopod 的 Nginx 配置
|
||||||
所有 Castopod 依赖关系
|
|
||||||
- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server):Castopod
|
|
||||||
的 Nginx 配置
|
|
||||||
|
|
||||||
此外,Castopod 需要一个与 MySQL 兼容的数据库。 Redis 数据库 可以添加为缓存处理
|
此外,Castopod 需要一个与 MySQL 兼容的数据库。 Redis 数据库 可以添加为缓存处理器。 A
|
||||||
器。 A Redis database can be added as a cache handler.
|
Redis database can be added as a cache handler.
|
||||||
|
|
||||||
## 目前支持的标签
|
## 目前支持的标签
|
||||||
|
|
||||||
|
@ -35,9 +35,8 @@ PHP version 8.3 or higher is required, with the following extensions installed:
|
|||||||
|
|
||||||
> 我们建议使用 [MariaDB](https://mariadb.org)。
|
> 我们建议使用 [MariaDB](https://mariadb.org)。
|
||||||
|
|
||||||
你需要填写服务器主机名、数据库名称、用户名和密码才能完成安装过程。 如果没有这
|
你需要填写服务器主机名、数据库名称、用户名和密码才能完成安装过程。 如果没有这些,请与你的服务器管理员联系。 If
|
||||||
些,请与你的服务器管理员联系。 If you do not have these, please contact your
|
you do not have these, please contact your server administrator.
|
||||||
server administrator.
|
|
||||||
|
|
||||||
#### 权限
|
#### 权限
|
||||||
|
|
||||||
@ -46,8 +45,8 @@ server administrator.
|
|||||||
|
|
||||||
### (可选)FFmpeg v4.1.8 或更高版本,用于视频素材
|
### (可选)FFmpeg v4.1.8 或更高版本,用于视频素材
|
||||||
|
|
||||||
如果你需要视频素材,则需要 [FFFmpeg](https://www.ffmpeg.org/) 4.1.8 或更高版本。
|
如果你需要视频素材,则需要 [FFFmpeg](https://www.ffmpeg.org/)
|
||||||
必须安装以下扩展: The following extensions must be installed:
|
4.1.8 或更高版本。必须安装以下扩展: The following extensions must be installed:
|
||||||
|
|
||||||
- **FreeType 2** 来自库
|
- **FreeType 2** 来自库
|
||||||
[gd](https://www.php.net/manual/en/image.installation.php)
|
[gd](https://www.php.net/manual/en/image.installation.php)
|
||||||
@ -63,11 +62,11 @@ server administrator.
|
|||||||
### Pre-requisites
|
### Pre-requisites
|
||||||
|
|
||||||
0. 需要一台已经实现 [环境要求](#requirements)的 Web 服务器
|
0. 需要一台已经实现 [环境要求](#requirements)的 Web 服务器
|
||||||
1. 为 Castopod 创建一个 MySQL 数据库,其中用户具有访问和修改权限(有关详细信息,
|
1. 为 Castopod 创建一个 MySQL 数据库,其中用户具有访问和修改权限(有关详细信息,请参阅
|
||||||
请参阅 [MySQL 兼容数据库](#mysql-compatible-database))。
|
[MySQL 兼容数据库](#mysql-compatible-database))。
|
||||||
2. 使用 _SSL 证书_ 在您的域激活 HTTPS。
|
2. 使用 _SSL 证书_ 在您的域激活 HTTPS。
|
||||||
3. 下载最新的 [Castopod](https://castopod.org/) 到 web 服务器并解压(如果尚未下
|
3. 下载最新的 [Castopod](https://castopod.org/)
|
||||||
载)。
|
到 web 服务器并解压(如果尚未下载)。
|
||||||
- ⚠️ 将 web 服务器根目录设置为 `castopod` 文件夹中的 `public/` 子文件夹。
|
- ⚠️ 将 web 服务器根目录设置为 `castopod` 文件夹中的 `public/` 子文件夹。
|
||||||
4. 在 Web 服务器上为各种后台进程添加 **cron 任务** (相应地替换路径):
|
4. 在 Web 服务器上为各种后台进程添加 **cron 任务** (相应地替换路径):
|
||||||
|
|
||||||
@ -85,16 +84,16 @@ server administrator.
|
|||||||
|
|
||||||
### (推荐) 安装向导
|
### (推荐) 安装向导
|
||||||
|
|
||||||
1. 前往你最喜欢的浏览器并跳转至安装向导页面
|
1. 前往你最喜欢的浏览器并跳转至安装向导页面 (`https://your_domain_name.com/cp-install`)运行 Castopod 安装脚本。
|
||||||
(`https://your_domain_name.com/cp-install`)运行 Castopod 安装脚本。
|
|
||||||
2. 请按照屏幕上的说明进行操作。
|
2. 请按照屏幕上的说明进行操作。
|
||||||
3. 开始播客!
|
3. 开始播客!
|
||||||
|
|
||||||
<Aside>
|
<Aside>
|
||||||
|
|
||||||
The install script writes a `.env` file in the package root. 安装脚本将会在根目
|
The install script writes a `.env` file in the package
|
||||||
录中创建一个 `.env` 文件并写入数据。 如果你不能执行安装向导,那么可以基于
|
root. 安装脚本将会在根目录中创建一个 `.env`
|
||||||
`.env.example` 文件手动创建和编辑 `.env` 文件。
|
文件并写入数据。 如果你不能执行安装向导,那么可以基于 `.env.example`
|
||||||
|
文件手动创建和编辑 `.env` 文件。
|
||||||
|
|
||||||
</Aside>
|
</Aside>
|
||||||
|
|
||||||
@ -147,9 +146,9 @@ email.SMTPPass="你的邮件密码"
|
|||||||
|
|
||||||
### 媒体存储
|
### 媒体存储
|
||||||
|
|
||||||
By default, files are saved to the `public/media` folder using the file system.
|
By default, files are saved to the `public/media` folder using the file
|
||||||
默认情况下,文件使用文件系统保存到 `公共/媒体` 文件夹中。 如果您需要将 `media`
|
system. 默认情况下,文件使用文件系统保存到 `公共/媒体` 文件夹中。 如果您需要将
|
||||||
文件夹重新定位到其他位置,您可以在您的 `.env` 文件中指定它,如下所示:
|
`media` 文件夹重新定位到其他位置,您可以在您的 `.env` 文件中指定它,如下所示:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# […]
|
# […]
|
||||||
@ -194,9 +193,9 @@ media.s3.region="your_s3_region"
|
|||||||
|
|
||||||
### 使用 YunoHost 安装
|
### 使用 YunoHost 安装
|
||||||
|
|
||||||
[YunoHost](https://yunohost.org/) 是一个基于 Debian GNU/Linux 的发行版,由免费和
|
[YunoHost](https://yunohost.org/) 是一个基于 Debian
|
||||||
开源软件包组成。 它可以为你解决自托管的困难。 It manages the hardships of
|
GNU/Linux 的发行版,由免费和开源软件包组成。 它可以为你解决自托管的困难。 It
|
||||||
self-hosting for you.
|
manages the hardships of self-hosting for you.
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-4">
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
|
|
||||||
|
@ -5,8 +5,7 @@ title: 安全问题
|
|||||||
Castopod 构建于 [CodeIgniter4](https://codeigniter.com/), PHP 框架上,鼓励
|
Castopod 构建于 [CodeIgniter4](https://codeigniter.com/), PHP 框架上,鼓励
|
||||||
[更好的安全实践](https://codeigniter.com/user_guide/concepts/security.html)。
|
[更好的安全实践](https://codeigniter.com/user_guide/concepts/security.html)。
|
||||||
|
|
||||||
为了最大限度地提高你实例的安全性并防止任何恶意攻击。 我们 建议你在安装或更新后检
|
为了最大限度地提高你实例的安全性并防止任何恶意攻击。 我们 建议你在安装或更新后检查所有的 Castopod 文件权限(避免任何之前的权限错误):
|
||||||
查所有的 Castopod 文件权限(避免任何之前的权限错误):
|
|
||||||
|
|
||||||
- `writable/` 文件夹权限为 **可读** 和 **可写**。
|
- `writable/` 文件夹权限为 **可读** 和 **可写**。
|
||||||
- `public/media/` 文件夹权限为 **可读** 和 **可写**。
|
- `public/media/` 文件夹权限为 **可读** 和 **可写**。
|
||||||
|
@ -4,8 +4,7 @@ title: 如何更新 Castopod ?
|
|||||||
|
|
||||||
import { Aside } from "@astrojs/starlight/components";
|
import { Aside } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
安装 Castopod 后,你可能希望将实例更新到最新版本 版本以享受最新功能 ✨, 修复错误
|
安装 Castopod 后,你可能希望将实例更新到最新版本 版本以享受最新功能 ✨, 修复错误 🐛 和性能提升 ⚡。
|
||||||
🐛 和性能提升 ⚡。
|
|
||||||
|
|
||||||
## 更新说明
|
## 更新说明
|
||||||
|
|
||||||
@ -13,14 +12,14 @@ import { Aside } from "@astrojs/starlight/components";
|
|||||||
|
|
||||||
- 参看. [我应该在更新前进行备份吗?](#should-i-make-a-backup-before-updating)
|
- 参看. [我应该在更新前进行备份吗?](#should-i-make-a-backup-before-updating)
|
||||||
|
|
||||||
1. 前往 [发布页面](https://code.castopod.org/adaures/castopod/-/releases) 和 查
|
1. 前往 [发布页面](https://code.castopod.org/adaures/castopod/-/releases)
|
||||||
看您的实例是否是最新的 Castopod 版本
|
和 查看您的实例是否是最新的 Castopod 版本
|
||||||
|
|
||||||
- 参看
|
- 参看
|
||||||
[我在哪里可以找到我的 Castopod 版本?](#where-can-i-find-my-castopod-version)
|
[我在哪里可以找到我的 Castopod 版本?](#where-can-i-find-my-castopod-version)
|
||||||
|
|
||||||
2. 下载名为`Castopod Package`的最新发布包,你可以在 `zip` 或 `tar.gz` 压缩包之间
|
2. 下载名为`Castopod Package`的最新发布包,你可以在 `zip` 或 `tar.gz`
|
||||||
选择
|
压缩包之间选择
|
||||||
|
|
||||||
- ⚠️ 请确保你下载的是 Castopod 软件包而 **不是** 源代码
|
- ⚠️ 请确保你下载的是 Castopod 软件包而 **不是** 源代码
|
||||||
- 请注意,你还可以从 [castopod.org](https://castopod.org/)
|
- 请注意,你还可以从 [castopod.org](https://castopod.org/)
|
||||||
@ -83,8 +82,9 @@ them sequentially, from the oldest to the newest.
|
|||||||
|
|
||||||
1. 下载最新版本,覆盖您的文件,同时保留 `.env` 文件和 `public/media` 文件夹。
|
1. 下载最新版本,覆盖您的文件,同时保留 `.env` 文件和 `public/media` 文件夹。
|
||||||
|
|
||||||
2. 从 `v1.0.0-alpha.43` 开始,按顺序执行每个版本更新指令(从老版本到 最新版本),
|
2. 从 `v1.0.0-alpha.43`
|
||||||
然后是 `v1.0.0-alpha.44`,`v1.0.0-alpha.45`,…,直到 `v1.0.0-beta.1`。
|
开始,按顺序执行每个版本更新指令(从老版本到 最新版本),然后是
|
||||||
|
`v1.0.0-alpha.44`,`v1.0.0-alpha.45`,…,直到 `v1.0.0-beta.1`。
|
||||||
|
|
||||||
3. ✨ 享受你的新实例, 你已经更新完毕!
|
3. ✨ 享受你的新实例, 你已经更新完毕!
|
||||||
|
|
||||||
|
@ -4,11 +4,10 @@ title: 欢迎 👋
|
|||||||
|
|
||||||
import { LinkCard } from "@astrojs/starlight/components";
|
import { LinkCard } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
Castopod 是一个免费的开源播客托管平台,为那些想要和听众接触与互动的播客们制作
|
Castopod 是一个免费的开源播客托管平台,为那些想要和听众接触与互动的播客们制作的。
|
||||||
的。
|
|
||||||
|
|
||||||
Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) 构建, 这是一
|
Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/)
|
||||||
个强大的 PHP 框架,并且占用极小。
|
构建, 这是一个强大的 PHP 框架,并且占用极小。
|
||||||
|
|
||||||
<LinkCard title="安装" href="./getting-started/install" />
|
<LinkCard title="安装" href="./getting-started/install" />
|
||||||
|
|
||||||
@ -44,28 +43,24 @@ Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) 构
|
|||||||
- 📤 也支持将播客移出 Castopod
|
- 📤 也支持将播客移出 Castopod
|
||||||
- 🔀 多租户:根据需要托管任意数量的播客
|
- 🔀 多租户:根据需要托管任意数量的播客
|
||||||
- 👥 多用户:添加贡献者并设置角色
|
- 👥 多用户:添加贡献者并设置角色
|
||||||
- 🌎 i18n 支持:翻译成英语,法语,波兰语,德语,巴西葡萄牙语和西班牙语
|
- 🌎
|
||||||
...[还有更多](https://translate.castopod.org)! and
|
i18n 支持:翻译成英语,法语,波兰语,德语,巴西葡萄牙语和西班牙语 ...[还有更多](https://translate.castopod.org)! and
|
||||||
[many more](https://translate.castopod.org)!
|
[many more](https://translate.castopod.org)!
|
||||||
|
|
||||||
## 创作动机
|
## 创作动机
|
||||||
|
|
||||||
播客生态系统本质上是去中心化的:你可以创建自己的播客 RSS 文件,将其发布到网络上
|
播客生态系统本质上是去中心化的:你可以创建自己的播客 RSS 文件,将其发布到网络上并在线共享。
|
||||||
并在线共享。
|
|
||||||
|
|
||||||
事实上,它是唯一长期保持这种状态的媒体之一。
|
事实上,它是唯一长期保持这种状态的媒体之一。
|
||||||
|
|
||||||
随着习惯的发展,越来越多的人开始接触播客:允许创作者寻找新的方式来分享他们的想
|
随着习惯的发展,越来越多的人开始接触播客:允许创作者寻找新的方式来分享他们的想法,或是让听众获得更好的内容。
|
||||||
法,或是让听众获得更好的内容。
|
|
||||||
|
|
||||||
随着播客的使用越来越广泛,一些公司正试图控制播客与集中化。
|
随着播客的使用越来越广泛,一些公司正试图控制播客与集中化。
|
||||||
|
|
||||||
Castopod 的创建旨在提供一种开放且可持续的替代方案来托管你的播客,促进权力下放,
|
Castopod 的创建旨在提供一种开放且可持续的替代方案来托管你的播客,促进权力下放,确保播客可以用创造力表达自己。
|
||||||
确保播客可以用创造力表达自己。
|
|
||||||
|
|
||||||
此项目由开源社区推动的,特别是
|
此项目由开源社区推动的,特别是由[联邦宇宙](https://fediverse.party/en/fediverse/)
|
||||||
由[联邦宇宙](https://fediverse.party/en/fediverse/) 和
|
和 [播客 2.0](https://podcastindex.org/) 推动。
|
||||||
[播客 2.0](https://podcastindex.org/) 推动。
|
|
||||||
|
|
||||||
## 与其他解决方案的对比
|
## 与其他解决方案的对比
|
||||||
|
|
||||||
@ -75,51 +70,42 @@ gauge whether Castopod is the right fit for you.
|
|||||||
|
|
||||||
### Castopod 对比 Wordpress
|
### Castopod 对比 Wordpress
|
||||||
|
|
||||||
Castopod 经常被称为 “播客中的 Wordpress”,因为两者有很多相似之处。 在某些方面,
|
Castopod 经常被称为 “播客中的 Wordpress”,因为两者有很多相似之处。 在某些方面,确实如此。 实际上,Castopod 受到 WordPress 生态的极大启发,看到了采用社区的易用性以及运行它的网站数量。 In
|
||||||
确实如此。 实际上,Castopod 受到 WordPress 生态的极大启发,看到了采用社区的易用
|
some ways this is true. And actually, Castopod was greatly inspired by the
|
||||||
性以及运行它的网站数量。 In some ways this is true. And actually, Castopod was
|
Wordpress ecosystem, seeing the ease of adoption from the community and the
|
||||||
greatly inspired by the Wordpress ecosystem, seeing the ease of adoption from
|
number of websites running it.
|
||||||
the community and the number of websites running it.
|
|
||||||
|
|
||||||
就像 Wordpress 一样,Castopod 是免费 & 开源的,PHP 构建并使用 MySQL 数据库,可以
|
就像 Wordpress 一样,Castopod 是免费 & 开源的,PHP 构建并使用 MySQL 数据库,可以在大多数 Web 服务器上轻松安装。
|
||||||
在大多数 Web 服务器上轻松安装。
|
|
||||||
|
|
||||||
Wordpress 是创建你的网站,并使用插件扩展以获得想要内容的好办法。 这是一个成熟的
|
Wordpress 是创建你的网站,并使用插件扩展以获得想要内容的好办法。 这是一个成熟的 CMS,可以帮助你在线访问任何类型的网站。 It
|
||||||
CMS,可以帮助你在线访问任何类型的网站。 It is a full fledged CMS that helps you
|
is a full fledged CMS that helps you get any type of website online.
|
||||||
get any type of website online.
|
|
||||||
|
|
||||||
另一方面,Castopod 旨在专门满足播客的需求,专注于播客,而不是其他。 你不需要任何
|
另一方面,Castopod 旨在专门满足播客的需求,专注于播客,而不是其他。 你不需要任何插件即可轻松开始播客之旅。 You
|
||||||
插件即可轻松开始播客之旅。 You don't need any plugin to get you started on your
|
don't need any plugin to get you started on your podcasting journey.
|
||||||
podcasting journey.
|
|
||||||
|
|
||||||
还拥有对播客的独特优化:从播客的创建和新剧集的发布一直到广播,营销和分析。
|
还拥有对播客的独特优化:从播客的创建和新剧集的发布一直到广播,营销和分析。
|
||||||
|
|
||||||
最后,根据你的需要,Wordpress 和 Castopod 甚至可以共存,因为他们有相同的配置环
|
最后,根据你的需要,Wordpress 和 Castopod 甚至可以共存,因为他们有相同的配置环境!
|
||||||
境!
|
|
||||||
|
|
||||||
### Castopod 对比 Funkwhale
|
### Castopod 对比 Funkwhale
|
||||||
|
|
||||||
Funkwhale 是一个自托管、现代界面、免费开源的音乐服务器。 就像 Castopod 一
|
Funkwhale 是一个自托管、现代界面、免费开源的音乐服务器。 就像 Castopod 一样,Funkwhale 也位于联邦宇宙中,这是一个去中心化的社交网络,允许两者的互联。Just
|
||||||
样,Funkwhale 也位于联邦宇宙中,这是一个去中心化的社交网络,允许两者的互联。Just
|
|
||||||
as Castopod, Funkwhale is on the fediverse, a decentralized social network
|
as Castopod, Funkwhale is on the fediverse, a decentralized social network
|
||||||
allowing interoperability between the two.
|
allowing interoperability between the two.
|
||||||
|
|
||||||
Funkwhale 最初是围绕音乐制作的。 后来,随着项目的发展,引入了托管播客的能力。And
|
Funkwhale 最初是围绕音乐制作的。 后来,随着项目的发展,引入了托管播客的能力。And
|
||||||
later on, as the project evolved, the ability to host podcasts was introduced.
|
later on, as the project evolved, the ability to host podcasts was introduced.
|
||||||
|
|
||||||
与 Funkwhale 不同,Castopod 是只围绕播客设计和构建的。 这样可以更简单地实现与播
|
与 Funkwhale 不同,Castopod 是只围绕播客设计和构建的。 这样可以更简单地实现与播客相关的生态系统,例如播客 2.0 功能(报表、 章节、地点、人员…)。 This
|
||||||
客相关的生态系统,例如播客 2.0 功能(报表、 章节、地点、人员…)。 This allows
|
allows easier implementation for features related to the podcasting ecosystem,
|
||||||
easier implementation for features related to the podcasting ecosystem, such as
|
such as the podcasting 2.0 features (transcripts, chapters, locations, persons,
|
||||||
the podcasting 2.0 features (transcripts, chapters, locations, persons, …).
|
…).
|
||||||
|
|
||||||
因此,如果你想托管你的音乐库,你可能应该使用 Funkwhale,如果您想主持一个播客,请
|
因此,如果你想托管你的音乐库,你可能应该使用 Funkwhale,如果您想主持一个播客,请使用 Castopod。
|
||||||
使用 Castopod。
|
|
||||||
|
|
||||||
### Castopod 与其他播客
|
### Castopod 与其他播客
|
||||||
|
|
||||||
有许多非常棒地解决方案可供你托管播客,并
|
有许多非常棒地解决方案可供你托管播客,并且[很多](https://podcastindex.org/apps)正在搭上播客 2.0 的便车,就像 Castopod 一样!
|
||||||
且[很多](https://podcastindex.org/apps)正在搭上播客 2.0 的便车,就像 Castopod 一
|
|
||||||
样!
|
|
||||||
|
|
||||||
这些解决方案各不相同,你可以对比 [功能列表](#features)。
|
这些解决方案各不相同,你可以对比 [功能列表](#features)。
|
||||||
|
|
||||||
@ -129,34 +115,30 @@ the podcasting 2.0 features (transcripts, chapters, locations, persons, …).
|
|||||||
full control over what you produce. Also, as it is open-source, you can even
|
full control over what you produce. Also, as it is open-source, you can even
|
||||||
customize it as you wish.
|
customize it as you wish.
|
||||||
|
|
||||||
- Castopod 是目前唯一一个同时集成去中心化的,带有 ActivePub 的社交网络以及很多播
|
- Castopod 是目前唯一一个同时集成去中心化的,带有 ActivePub 的社交网络以及很多播客 2.0 功能集成的解决方案,希望弥合两者之间的差距。
|
||||||
客 2.0 功能集成的解决方案,希望弥合两者之间的差距。
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
喜欢 Castopod 并且想帮忙吗? 请查看以下文档以帮助你入门。 请查看以下文档以帮助你
|
喜欢 Castopod 并且想帮忙吗? 请查看以下文档以帮助你入门。 请查看以下文档以帮助你入门。
|
||||||
入门。
|
|
||||||
|
|
||||||
### 行为准则
|
### 行为准则
|
||||||
|
|
||||||
Castopod has adopted a Code of Conduct that we expect project participants to
|
Castopod has adopted a Code of Conduct that we expect project participants to
|
||||||
adhere to. Castopod 已经通过了一项行为准则,并希望所有的参与者都能够遵循本行为准
|
adhere to.
|
||||||
则。 请阅
|
Castopod 已经通过了一项行为准则,并希望所有的参与者都能够遵循本行为准则。 请阅读[行为准则](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md)
|
||||||
读[行为准则](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md)
|
|
||||||
以便了解哪些行为被允许,哪些行为不会被容忍。
|
以便了解哪些行为被允许,哪些行为不会被容忍。
|
||||||
|
|
||||||
### 贡献指南
|
### 贡献指南
|
||||||
|
|
||||||
阅读我们的 [贡献指南](../contributing/guidelines.md) ,了解我们的开发过程。 提出
|
阅读我们的 [贡献指南](../contributing/guidelines.md)
|
||||||
错 误修正和改进想法,以及如何构建和测试 Castopod 。
|
,了解我们的开发过程。 提出错 误修正和改进想法,以及如何构建和测试 Castopod 。
|
||||||
|
|
||||||
## 联系
|
## 联系
|
||||||
|
|
||||||
你可以联系我们寻求帮助或提出任何问题:
|
你可以联系我们寻求帮助或提出任何问题:
|
||||||
|
|
||||||
- [Discord](https://castopod.org/discord) (用于与开发人员和社区直接互动)
|
- [Discord](https://castopod.org/discord) (用于与开发人员和社区直接互动)
|
||||||
- [问题跟踪器](https://code.castopod.org/adaures/castopod/-/issues)(用于功能请
|
- [问题跟踪器](https://code.castopod.org/adaures/castopod/-/issues)(用于功能请求和错误报告)
|
||||||
求和错误报告)
|
|
||||||
|
|
||||||
或者,你可以在社交媒体上关注我们,以获取有关 Castopod 的新闻:
|
或者,你可以在社交媒体上关注我们,以获取有关 Castopod 的新闻:
|
||||||
|
|
||||||
@ -168,8 +150,8 @@ adhere to. Castopod 已经通过了一项行为准则,并希望所有的参与
|
|||||||
## 赞助商
|
## 赞助商
|
||||||
|
|
||||||
The ongoing development of Castopod is made possible with the support of its
|
The ongoing development of Castopod is made possible with the support of its
|
||||||
backers. Castopod 的发展离不开赞助商的支持。 如果你想要帮助我们,请考
|
backers.
|
||||||
虑[赞助 Castopod 的开发](https://opencollective.com/castopod/contribute).
|
Castopod 的发展离不开赞助商的支持。 如果你想要帮助我们,请考虑[赞助 Castopod 的开发](https://opencollective.com/castopod/contribute).
|
||||||
|
|
||||||
[](https://adaures.com/)
|
[](https://adaures.com/)
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
title: 認證 & 授權
|
title: 認證 & 授權
|
||||||
---
|
---
|
||||||
|
|
||||||
Castopod 使用 `codeigniter/shield` 處理身分認證和授權 與自定義規則。 腳色和權限
|
Castopod 使用 `codeigniter/shield`
|
||||||
在定義為兩個層級: Roles and permissions are defined at two levels:
|
處理身分認證和授權 與自定義規則。 腳色和權限在定義為兩個層級: Roles and
|
||||||
|
permissions are defined at two levels:
|
||||||
|
|
||||||
1. [實例範圍](#1-instance-wide-roles-and-permissions)
|
1. [實例範圍](#1-instance-wide-roles-and-permissions)
|
||||||
2. [每個播客](#2-per-podcast-roles-and-permissions)
|
2. [每個播客](#2-per-podcast-roles-and-permissions)
|
||||||
|
@ -25,6 +25,7 @@ class AboutController extends BaseController
|
|||||||
'languages' => implode(', ', config('App')->supportedLocales),
|
'languages' => implode(', ', config('App')->supportedLocales),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('AboutCastopod.title'));
|
||||||
return view('settings/about', [
|
return view('settings/about', [
|
||||||
'info' => $instanceInfo,
|
'info' => $instanceInfo,
|
||||||
]);
|
]);
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\Admin\Controllers;
|
namespace Modules\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Libraries\HtmlHead;
|
||||||
use CodeIgniter\Controller;
|
use CodeIgniter\Controller;
|
||||||
use CodeIgniter\HTTP\IncomingRequest;
|
use CodeIgniter\HTTP\IncomingRequest;
|
||||||
use CodeIgniter\HTTP\RequestInterface;
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
@ -41,4 +42,16 @@ abstract class BaseController extends Controller
|
|||||||
|
|
||||||
Theme::setTheme('admin');
|
Theme::setTheme('admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function setHtmlHead(string $title): void
|
||||||
|
{
|
||||||
|
/** @var HtmlHead $head */
|
||||||
|
$head = service('html_head');
|
||||||
|
|
||||||
|
$head
|
||||||
|
->title($title . ' | Castopod Admin')
|
||||||
|
->description(
|
||||||
|
'Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ class DashboardController extends BaseController
|
|||||||
'onlyPodcastId' => $onlyPodcastId,
|
'onlyPodcastId' => $onlyPodcastId,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Dashboard.home'));
|
||||||
return view('dashboard', $data);
|
return view('dashboard', $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,7 @@ class EpisodeController extends BaseController
|
|||||||
'query' => $query,
|
'query' => $query,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.all_podcast_episodes'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -128,6 +129,7 @@ class EpisodeController extends BaseController
|
|||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->episode->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -148,6 +150,8 @@ class EpisodeController extends BaseController
|
|||||||
$currentSeasonNumber
|
$currentSeasonNumber
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.create'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -261,6 +265,7 @@ class EpisodeController extends BaseController
|
|||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.edit'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -409,6 +414,7 @@ class EpisodeController extends BaseController
|
|||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.publish'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -521,6 +527,7 @@ class EpisodeController extends BaseController
|
|||||||
->first(),
|
->first(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.publish_edit'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -670,11 +677,11 @@ class EpisodeController extends BaseController
|
|||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.publish_date_edit'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->title,
|
0 => $this->podcast->title,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return view('episode/publish_date_edit', $data);
|
return view('episode/publish_date_edit', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,6 +753,7 @@ class EpisodeController extends BaseController
|
|||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.unpublish'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->title,
|
0 => $this->podcast->title,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -822,6 +830,7 @@ class EpisodeController extends BaseController
|
|||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.delete'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -928,6 +937,7 @@ class EpisodeController extends BaseController
|
|||||||
'themes' => EpisodeModel::$themes,
|
'themes' => EpisodeModel::$themes,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Episode.embed.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
|
@ -58,6 +58,8 @@ class EpisodePersonController extends BaseController
|
|||||||
'personOptions' => (new PersonModel())->getPersonOptions(),
|
'personOptions' => (new PersonModel())->getPersonOptions(),
|
||||||
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Person.episode_form.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
|
@ -26,6 +26,7 @@ class FediverseController extends BaseController
|
|||||||
$blockedActors = model('ActorModel', false)
|
$blockedActors = model('ActorModel', false)
|
||||||
->getBlockedActors();
|
->getBlockedActors();
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Fediverse.blocked_actors'));
|
||||||
return view('fediverse/blocked_actors', [
|
return view('fediverse/blocked_actors', [
|
||||||
'blockedActors' => $blockedActors,
|
'blockedActors' => $blockedActors,
|
||||||
]);
|
]);
|
||||||
@ -38,6 +39,7 @@ class FediverseController extends BaseController
|
|||||||
$blockedDomains = model('BlockedDomainModel', false)
|
$blockedDomains = model('BlockedDomainModel', false)
|
||||||
->getBlockedDomains();
|
->getBlockedDomains();
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Fediverse.blocked_domains'));
|
||||||
return view('fediverse/blocked_domains', [
|
return view('fediverse/blocked_domains', [
|
||||||
'blockedDomains' => $blockedDomains,
|
'blockedDomains' => $blockedDomains,
|
||||||
]);
|
]);
|
||||||
|
@ -66,10 +66,10 @@ class NotificationController extends BaseController
|
|||||||
'pager' => $notifications->pager,
|
'pager' => $notifications->pager,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Notifications.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return view('podcast/notifications', $data);
|
return view('podcast/notifications', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ class PageController extends BaseController
|
|||||||
|
|
||||||
public function list(): string
|
public function list(): string
|
||||||
{
|
{
|
||||||
|
$this->setHtmlHead(lang('Page.all_pages'));
|
||||||
$data = [
|
$data = [
|
||||||
'pages' => (new PageModel())->findAll(),
|
'pages' => (new PageModel())->findAll(),
|
||||||
];
|
];
|
||||||
@ -43,6 +44,7 @@ class PageController extends BaseController
|
|||||||
|
|
||||||
public function view(): string
|
public function view(): string
|
||||||
{
|
{
|
||||||
|
$this->setHtmlHead($this->page->title);
|
||||||
return view('page/view', [
|
return view('page/view', [
|
||||||
'page' => $this->page,
|
'page' => $this->page,
|
||||||
]);
|
]);
|
||||||
@ -52,6 +54,7 @@ class PageController extends BaseController
|
|||||||
{
|
{
|
||||||
helper('form');
|
helper('form');
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Page.create'));
|
||||||
return view('page/create');
|
return view('page/create');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +86,7 @@ class PageController extends BaseController
|
|||||||
{
|
{
|
||||||
helper('form');
|
helper('form');
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Page.edit'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->page->title,
|
0 => $this->page->title,
|
||||||
]);
|
]);
|
||||||
|
@ -42,6 +42,7 @@ class PersonController extends BaseController
|
|||||||
->findAll(),
|
->findAll(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Person.all_persons'));
|
||||||
return view('person/list', $data);
|
return view('person/list', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ class PersonController extends BaseController
|
|||||||
'person' => $this->person,
|
'person' => $this->person,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->person->full_name);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->person->full_name,
|
0 => $this->person->full_name,
|
||||||
]);
|
]);
|
||||||
@ -61,6 +63,7 @@ class PersonController extends BaseController
|
|||||||
{
|
{
|
||||||
helper(['form']);
|
helper(['form']);
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Person.create'));
|
||||||
return view('person/create');
|
return view('person/create');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +115,7 @@ class PersonController extends BaseController
|
|||||||
'person' => $this->person,
|
'person' => $this->person,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Person.edit'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->person->full_name,
|
0 => $this->person->full_name,
|
||||||
]);
|
]);
|
||||||
|
@ -68,6 +68,7 @@ class PodcastController extends BaseController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.all_podcasts'));
|
||||||
return view('podcast/list', $data);
|
return view('podcast/list', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +78,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -89,6 +91,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -101,6 +104,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -113,6 +117,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -125,6 +130,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -137,6 +143,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -149,6 +156,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -161,6 +169,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead($this->podcast->title);
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -180,6 +189,7 @@ class PodcastController extends BaseController
|
|||||||
'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
|
'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.create'));
|
||||||
return view('podcast/create', $data);
|
return view('podcast/create', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,6 +280,7 @@ class PodcastController extends BaseController
|
|||||||
'categoryOptions' => $categoryOptions,
|
'categoryOptions' => $categoryOptions,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.edit'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -410,6 +421,7 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.delete'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -578,10 +590,10 @@ class PodcastController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.publish'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return view('podcast/publish', $data);
|
return view('podcast/publish', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,10 +725,10 @@ class PodcastController extends BaseController
|
|||||||
->first(),
|
->first(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.publish_edit'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return view('podcast/publish_edit', $data);
|
return view('podcast/publish_edit', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@ class PodcastPersonController extends BaseController
|
|||||||
'personOptions' => (new PersonModel())->getPersonOptions(),
|
'personOptions' => (new PersonModel())->getPersonOptions(),
|
||||||
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Person.podcast_form.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
|
@ -30,6 +30,7 @@ class SettingsController extends BaseController
|
|||||||
public function index(): string
|
public function index(): string
|
||||||
{
|
{
|
||||||
helper('form');
|
helper('form');
|
||||||
|
$this->setHtmlHead(lang('Settings.title'));
|
||||||
return view('settings/general');
|
return view('settings/general');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +203,7 @@ class SettingsController extends BaseController
|
|||||||
public function theme(): string
|
public function theme(): string
|
||||||
{
|
{
|
||||||
helper('form');
|
helper('form');
|
||||||
|
$this->setHtmlHead(lang('Settings.theme.title'));
|
||||||
return view('settings/theme');
|
return view('settings/theme');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ class SoundbiteController extends BaseController
|
|||||||
'pager' => $soundbitesBuilder->pager,
|
'pager' => $soundbitesBuilder->pager,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Soundbite.list.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -92,6 +93,7 @@ class SoundbiteController extends BaseController
|
|||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Soundbite.form.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
|
@ -82,6 +82,7 @@ class VideoClipsController extends BaseController
|
|||||||
'pager' => $videoClipsBuilder->pager,
|
'pager' => $videoClipsBuilder->pager,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('VideoClip.list.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -99,6 +100,9 @@ class VideoClipsController extends BaseController
|
|||||||
'videoClip' => $videoClip,
|
'videoClip' => $videoClip,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('VideoClip.title', [
|
||||||
|
'videoClipLabel' => esc($videoClip->title),
|
||||||
|
]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->episode->title,
|
1 => $this->episode->title,
|
||||||
@ -128,9 +132,10 @@ class VideoClipsController extends BaseController
|
|||||||
'transcript' => $this->episode->transcript instanceof Transcript,
|
'transcript' => $this->episode->transcript instanceof Transcript,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('VideoClip.form.title'));
|
||||||
|
|
||||||
if (in_array(false, $checks, true)) {
|
if (in_array(false, $checks, true)) {
|
||||||
$data['checks'] = $checks;
|
$data['checks'] = $checks;
|
||||||
|
|
||||||
return view('episode/video_clips_requirements', $data);
|
return view('episode/video_clips_requirements', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,15 +2,13 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use CodeIgniter\Router\RouteCollection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2021 Ad Aures
|
* @copyright 2021 Ad Aures
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @var RouteCollection $routes */
|
/** @var \CodeIgniter\Router\RouteCollection $routes */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analytics routes file
|
* Analytics routes file
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
use Opawg\UserAgentsV2Php\UserAgentsRSS;
|
use Opawg\UserAgentsV2Php\UserAgentsRSS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||||||
namespace Modules\Analytics\Entities;
|
namespace Modules\Analytics\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
|
@ -286,8 +286,7 @@ if (! function_exists('podcast_hit')) {
|
|||||||
$parts = explode('-', $range);
|
$parts = explode('-', $range);
|
||||||
$downloadedBytes += array_key_exists(1, $parts)
|
$downloadedBytes += array_key_exists(1, $parts)
|
||||||
? $fileSize
|
? $fileSize
|
||||||
: (int) $parts[1] -
|
: (int) $parts[1] - (int) $parts[0];
|
||||||
(array_key_exists(0, $parts) ? 0 : (int) $parts[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ class ContributorController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Contributor.podcast_contributors'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -69,6 +70,10 @@ class ContributorController extends BaseController
|
|||||||
'contributor' => (new UserModel())->getPodcastContributor($this->contributor->id, $this->podcast->id),
|
'contributor' => (new UserModel())->getPodcastContributor($this->contributor->id, $this->podcast->id),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Contributor.view', [
|
||||||
|
'username' => esc($this->contributor->username),
|
||||||
|
'podcastTitle' => esc($this->podcast->title),
|
||||||
|
]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->contributor->username,
|
1 => $this->contributor->username,
|
||||||
@ -113,6 +118,7 @@ class ContributorController extends BaseController
|
|||||||
'roleOptions' => $roleOptions,
|
'roleOptions' => $roleOptions,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Contributor.add_contributor', [esc($this->podcast->title)]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -170,6 +176,7 @@ class ContributorController extends BaseController
|
|||||||
'roleOptions' => $roleOptions,
|
'roleOptions' => $roleOptions,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Contributor.edit_role', [esc($this->contributor->username)]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->contributor->username,
|
1 => $this->contributor->username,
|
||||||
@ -208,6 +215,9 @@ class ContributorController extends BaseController
|
|||||||
'contributor' => $this->contributor,
|
'contributor' => $this->contributor,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Contributor.delete_form.title', [
|
||||||
|
'contributor' => $this->contributor->username,
|
||||||
|
]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => $this->contributor->username,
|
1 => $this->contributor->username,
|
||||||
|
@ -18,6 +18,7 @@ class MyAccountController extends BaseController
|
|||||||
{
|
{
|
||||||
public function index(): string
|
public function index(): string
|
||||||
{
|
{
|
||||||
|
$this->setHtmlHead(lang('MyAccount.info'));
|
||||||
return view('my_account/view');
|
return view('my_account/view');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ class MyAccountController extends BaseController
|
|||||||
{
|
{
|
||||||
helper('form');
|
helper('form');
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('MyAccount.changePassword'));
|
||||||
return view('my_account/change_password');
|
return view('my_account/change_password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ class UserController extends BaseController
|
|||||||
'users' => (new UserModel())->findAll(),
|
'users' => (new UserModel())->findAll(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('User.all_users'));
|
||||||
return view('user/list', $data);
|
return view('user/list', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +52,9 @@ class UserController extends BaseController
|
|||||||
'user' => $this->user,
|
'user' => $this->user,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('User.view', [
|
||||||
|
'username' => esc($this->user->username),
|
||||||
|
]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->user->username,
|
0 => $this->user->username,
|
||||||
]);
|
]);
|
||||||
@ -76,6 +80,7 @@ class UserController extends BaseController
|
|||||||
'roleOptions' => $roleOptions,
|
'roleOptions' => $roleOptions,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('User.create'));
|
||||||
return view('user/create', $data);
|
return view('user/create', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +187,9 @@ class UserController extends BaseController
|
|||||||
'roleOptions' => $roleOptions,
|
'roleOptions' => $roleOptions,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('User.edit_role', [
|
||||||
|
'username' => esc($this->user->username),
|
||||||
|
]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->user->username,
|
0 => $this->user->username,
|
||||||
]);
|
]);
|
||||||
@ -221,6 +229,9 @@ class UserController extends BaseController
|
|||||||
'user' => $this->user,
|
'user' => $this->user,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('User.delete_form.title', [
|
||||||
|
'user' => $this->user->username,
|
||||||
|
]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->user->username,
|
0 => $this->user->username,
|
||||||
]);
|
]);
|
||||||
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\Fediverse\Entities;
|
namespace Modules\Fediverse\Entities;
|
||||||
|
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
use Michalsn\Uuid\UuidEntity;
|
use Michalsn\Uuid\UuidEntity;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ if (! function_exists('split_handle')) {
|
|||||||
/**
|
/**
|
||||||
* Splits handle into its parts (username, host and port)
|
* Splits handle into its parts (username, host and port)
|
||||||
*
|
*
|
||||||
* @return array<string, string>|false
|
* @return array{0:string,username:non-empty-string,1:non-empty-string,domain:non-empty-string,2:non-empty-string,port?:non-falsy-string,3?:non-falsy-string}
|
||||||
*/
|
*/
|
||||||
function split_handle(string $handle): array | false
|
function split_handle(string $handle): array | false
|
||||||
{
|
{
|
||||||
|
@ -101,7 +101,7 @@ class WebFinger
|
|||||||
/**
|
/**
|
||||||
* Split resource into its parts (username, domain)
|
* Split resource into its parts (username, domain)
|
||||||
*
|
*
|
||||||
* @return array<string, string>|false
|
* @return array{0:string,username:non-empty-string,1:non-empty-string,2:non-empty-string,domain:non-falsy-string,3:non-falsy-string,4:non-falsy-string,5?:non-falsy-string}
|
||||||
*/
|
*/
|
||||||
private function splitResource(string $resource): bool|array
|
private function splitResource(string $resource): bool|array
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,7 @@ use RuntimeException;
|
|||||||
* @property string $file_extension
|
* @property string $file_extension
|
||||||
* @property int $file_size
|
* @property int $file_size
|
||||||
* @property string $file_mimetype
|
* @property string $file_mimetype
|
||||||
* @property array|null $file_metadata
|
* @property array<mixed>|null $file_metadata
|
||||||
* @property 'image'|'audio'|'video'|'document' $type
|
* @property 'image'|'audio'|'video'|'document' $type
|
||||||
* @property string|null $description
|
* @property string|null $description
|
||||||
* @property string|null $language_code
|
* @property string|null $language_code
|
||||||
|
@ -16,7 +16,7 @@ use GdImage;
|
|||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property array $sizes
|
* @property array<string, array<string, int|string>> $sizes
|
||||||
*/
|
*/
|
||||||
class Image extends BaseMedia
|
class Image extends BaseMedia
|
||||||
{
|
{
|
||||||
|
@ -34,7 +34,7 @@ if (! function_exists('download_file')) {
|
|||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['User-Agent: Castopod/' . CP_VERSION]);
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['User-Agent: Castopod/' . CP_VERSION]);
|
||||||
|
|
||||||
// follow redirects up to 20, like Apple Podcasts
|
// follow redirects up to 20, like Apple Podcasts
|
||||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
|
curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
|
||||||
|
|
||||||
curl_exec($ch);
|
curl_exec($ch);
|
||||||
|
@ -55,10 +55,10 @@ class PlatformController extends BaseController
|
|||||||
'platforms' => (new PlatformModel())->getPlatformsWithData($this->podcast->id, $platformType),
|
'platforms' => (new PlatformModel())->getPlatformsWithData($this->podcast->id, $platformType),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang("Platforms.title.{$platformType}"));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return view('podcast/platforms', $data);
|
return view('podcast/platforms', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ class PlatformModel extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<array<string, string|int>> $data
|
* @param array<array<string, bool|int|string|null>> $data
|
||||||
*
|
*
|
||||||
* @return int|false Number of rows inserted or FALSE on failure
|
* @return int|false Number of rows inserted or FALSE on failure
|
||||||
*/
|
*/
|
||||||
|
@ -458,7 +458,7 @@ class Platforms
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return null|array{label:string,home_url:string,submit_url:?string}>
|
* @return null|array{label:string,home_url:string,submit_url:?string}
|
||||||
*/
|
*/
|
||||||
public function findPlatformBySlug(string $type, string $slug): ?array
|
public function findPlatformBySlug(string $type, string $slug): ?array
|
||||||
{
|
{
|
||||||
|
@ -16,10 +16,10 @@ class CreatePlugin extends BaseCommand
|
|||||||
{
|
{
|
||||||
protected const HOOKS_IMPORTS = [
|
protected const HOOKS_IMPORTS = [
|
||||||
'rssBeforeChannel' => ['use App\Entities\Podcast;'],
|
'rssBeforeChannel' => ['use App\Entities\Podcast;'],
|
||||||
'rssAfterChannel' => ['use App\Entities\Podcast;', 'use App\Libraries\SimpleRSSElement;'],
|
'rssAfterChannel' => ['use App\Entities\Podcast;', 'use App\Libraries\RssFeed;'],
|
||||||
'rssBeforeItem' => ['use App\Entities\Episode;'],
|
'rssBeforeItem' => ['use App\Entities\Episode;'],
|
||||||
'rssAfterItem' => ['use App\Entities\Episode;', 'use App\Libraries\SimpleRSSElement;'],
|
'rssAfterItem' => ['use App\Entities\Episode;', 'use App\Libraries\RssFeed;'],
|
||||||
'siteHead' => [],
|
'siteHead' => ['use use App\Libraries\HtmlHead'],
|
||||||
];
|
];
|
||||||
|
|
||||||
protected const HOOKS_METHODS = [
|
protected const HOOKS_METHODS = [
|
||||||
@ -27,7 +27,7 @@ class CreatePlugin extends BaseCommand
|
|||||||
{
|
{
|
||||||
// YOUR CODE HERE
|
// YOUR CODE HERE
|
||||||
}',
|
}',
|
||||||
'rssAfterChannel' => ' public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
|
'rssAfterChannel' => ' public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void
|
||||||
{
|
{
|
||||||
// YOUR CODE HERE
|
// YOUR CODE HERE
|
||||||
}',
|
}',
|
||||||
@ -35,11 +35,11 @@ class CreatePlugin extends BaseCommand
|
|||||||
{
|
{
|
||||||
// YOUR CODE HERE
|
// YOUR CODE HERE
|
||||||
}',
|
}',
|
||||||
'rssAfterItem' => ' public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void
|
'rssAfterItem' => ' public function rssAfterItem(Episode $episode, RssFeed $item): void
|
||||||
{
|
{
|
||||||
// YOUR CODE HERE
|
// YOUR CODE HERE
|
||||||
}',
|
}',
|
||||||
'siteHead' => ' public function siteHead(): void
|
'siteHead' => ' public function siteHead(HtmlHead $head): void
|
||||||
{
|
{
|
||||||
// YOUR CODE HERE
|
// YOUR CODE HERE
|
||||||
}',
|
}',
|
||||||
|
@ -38,6 +38,7 @@ class PluginController extends BaseController
|
|||||||
|
|
||||||
$pager_links = $pager->makeLinks($page, $perPage, $total);
|
$pager_links = $pager->makeLinks($page, $perPage, $total);
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Plugins.installed'));
|
||||||
return view('plugins/installed', [
|
return view('plugins/installed', [
|
||||||
'total' => $total,
|
'total' => $total,
|
||||||
'plugins' => $this->plugins->getPlugins($page, $perPage),
|
'plugins' => $this->plugins->getPlugins($page, $perPage),
|
||||||
@ -47,8 +48,9 @@ class PluginController extends BaseController
|
|||||||
|
|
||||||
public function vendor(string $vendor): string
|
public function vendor(string $vendor): string
|
||||||
{
|
{
|
||||||
|
|
||||||
$vendorPlugins = $this->plugins->getVendorPlugins($vendor);
|
$vendorPlugins = $this->plugins->getVendorPlugins($vendor);
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Plugins.installed'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
$vendor => $vendor,
|
$vendor => $vendor,
|
||||||
]);
|
]);
|
||||||
@ -68,6 +70,7 @@ class PluginController extends BaseController
|
|||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->setHtmlHead($plugin->getTitle());
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
$vendor => $vendor,
|
$vendor => $vendor,
|
||||||
$package => $package,
|
$package => $package,
|
||||||
@ -137,6 +140,10 @@ class PluginController extends BaseController
|
|||||||
$data['fields'] = $fields;
|
$data['fields'] = $fields;
|
||||||
|
|
||||||
helper('form');
|
helper('form');
|
||||||
|
$this->setHtmlHead(lang('Plugins.settingsTitle', [
|
||||||
|
'pluginTitle' => $plugin->getTitle(),
|
||||||
|
'type' => $type,
|
||||||
|
]));
|
||||||
replace_breadcrumb_params($breadcrumbReplacements);
|
replace_breadcrumb_params($breadcrumbReplacements);
|
||||||
return view('plugins/settings', $data);
|
return view('plugins/settings', $data);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ namespace Modules\Plugins\Core;
|
|||||||
|
|
||||||
use App\Entities\Episode;
|
use App\Entities\Episode;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
|
use App\Libraries\HtmlHead;
|
||||||
use App\Libraries\RssFeed;
|
use App\Libraries\RssFeed;
|
||||||
use CodeIgniter\HTTP\URI;
|
use CodeIgniter\HTTP\URI;
|
||||||
use League\CommonMark\Environment\Environment;
|
use League\CommonMark\Environment\Environment;
|
||||||
@ -101,7 +102,7 @@ abstract class BasePlugin implements PluginInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function siteHead(): void
|
public function siteHead(HtmlHead $head): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +265,7 @@ abstract class BasePlugin implements PluginInterface
|
|||||||
return $title;
|
return $title;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getDescription(): ?string
|
final public function getDescription(): string
|
||||||
{
|
{
|
||||||
$key = sprintf('Plugin.%s.description', $this->key);
|
$key = sprintf('Plugin.%s.description', $this->key);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ namespace Modules\Plugins\Core;
|
|||||||
|
|
||||||
use App\Entities\Episode;
|
use App\Entities\Episode;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
|
use App\Libraries\HtmlHead;
|
||||||
use App\Libraries\RssFeed;
|
use App\Libraries\RssFeed;
|
||||||
|
|
||||||
interface PluginInterface
|
interface PluginInterface
|
||||||
@ -18,5 +19,5 @@ interface PluginInterface
|
|||||||
|
|
||||||
public function rssAfterItem(Episode $episode, RssFeed $item): void;
|
public function rssAfterItem(Episode $episode, RssFeed $item): void;
|
||||||
|
|
||||||
public function siteHead(): void;
|
public function siteHead(HtmlHead $head): void;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ namespace Modules\Plugins\Core;
|
|||||||
|
|
||||||
use App\Entities\Episode;
|
use App\Entities\Episode;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
|
use App\Libraries\HtmlHead;
|
||||||
use App\Libraries\RssFeed;
|
use App\Libraries\RssFeed;
|
||||||
use Config\Database;
|
use Config\Database;
|
||||||
use Modules\Plugins\Config\Plugins as PluginsConfig;
|
use Modules\Plugins\Config\Plugins as PluginsConfig;
|
||||||
@ -15,7 +16,7 @@ use Modules\Plugins\Config\Plugins as PluginsConfig;
|
|||||||
* @method void rssAfterChannel(Podcast $podcast, RssFeed $channel)
|
* @method void rssAfterChannel(Podcast $podcast, RssFeed $channel)
|
||||||
* @method void rssBeforeItem(Episode $episode)
|
* @method void rssBeforeItem(Episode $episode)
|
||||||
* @method void rssAfterItem(Episode $episode, RssFeed $item)
|
* @method void rssAfterItem(Episode $episode, RssFeed $item)
|
||||||
* @method void siteHead()
|
* @method void siteHead(HtmlHead $head)
|
||||||
*/
|
*/
|
||||||
class Plugins
|
class Plugins
|
||||||
{
|
{
|
||||||
@ -29,6 +30,7 @@ class Plugins
|
|||||||
'datetime' => ['valid_date[Y-m-d H:i]'],
|
'datetime' => ['valid_date[Y-m-d H:i]'],
|
||||||
'email' => ['valid_email'],
|
'email' => ['valid_email'],
|
||||||
'group' => ['permit_empty', 'is_list'],
|
'group' => ['permit_empty', 'is_list'],
|
||||||
|
'html' => ['string'],
|
||||||
'markdown' => ['string'],
|
'markdown' => ['string'],
|
||||||
'number' => ['integer'],
|
'number' => ['integer'],
|
||||||
'radio-group' => ['string'],
|
'radio-group' => ['string'],
|
||||||
|
@ -7,7 +7,7 @@ namespace Modules\Plugins\Manifest;
|
|||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property 'checkbox'|'datetime'|'email'|'group'|'markdown'|'number'|'radio-group'|'rss'|'select-multiple'|'select'|'text'|'textarea'|'toggler'|'url' $type
|
* @property 'checkbox'|'datetime'|'email'|'group'|'html'|'markdown'|'number'|'radio-group'|'rss'|'select-multiple'|'select'|'text'|'textarea'|'toggler'|'url' $type
|
||||||
* @property string $key
|
* @property string $key
|
||||||
* @property string $label
|
* @property string $label
|
||||||
* @property string $hint
|
* @property string $hint
|
||||||
@ -20,7 +20,7 @@ use Override;
|
|||||||
class Field extends ManifestObject
|
class Field extends ManifestObject
|
||||||
{
|
{
|
||||||
protected const VALIDATION_RULES = [
|
protected const VALIDATION_RULES = [
|
||||||
'type' => 'permit_empty|in_list[checkbox,datetime,email,group,markdown,number,radio-group,rss,select-multiple,select,text,textarea,toggler,url]',
|
'type' => 'permit_empty|in_list[checkbox,datetime,email,group,html,markdown,number,radio-group,rss,select-multiple,select,text,textarea,toggler,url]',
|
||||||
'key' => 'required|alpha_dash',
|
'key' => 'required|alpha_dash',
|
||||||
'label' => 'required|string',
|
'label' => 'required|string',
|
||||||
'hint' => 'permit_empty|string',
|
'hint' => 'permit_empty|string',
|
||||||
|
@ -178,6 +178,7 @@
|
|||||||
"datetime",
|
"datetime",
|
||||||
"email",
|
"email",
|
||||||
"group",
|
"group",
|
||||||
|
"html",
|
||||||
"markdown",
|
"markdown",
|
||||||
"number",
|
"number",
|
||||||
"radio-group",
|
"radio-group",
|
||||||
|
@ -160,7 +160,7 @@ class PodcastImport extends BaseCommand
|
|||||||
|
|
||||||
$podcastModel = new PodcastModel();
|
$podcastModel = new PodcastModel();
|
||||||
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||||
throw new Exception((string) print_r($podcastModel->errors()));
|
throw new Exception(print_r($podcastModel->errors(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
CLI::showProgress(false);
|
CLI::showProgress(false);
|
||||||
@ -260,7 +260,7 @@ class PodcastImport extends BaseCommand
|
|||||||
$podcastModel = new PodcastModel();
|
$podcastModel = new PodcastModel();
|
||||||
if (! ($podcastId = $podcastModel->insert($podcast, true))) {
|
if (! ($podcastId = $podcastModel->insert($podcast, true))) {
|
||||||
$db->transRollback();
|
$db->transRollback();
|
||||||
throw new Exception((string) print_r($podcastModel->errors()));
|
throw new Exception(print_r($podcastModel->errors(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
$podcast->id = $podcastId;
|
$podcast->id = $podcastId;
|
||||||
@ -326,7 +326,7 @@ class PodcastImport extends BaseCommand
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (! $newPersonId = $personModel->insert($newPodcastPerson)) {
|
if (! $newPersonId = $personModel->insert($newPodcastPerson)) {
|
||||||
throw new Exception((string) print_r($personModel->errors()));
|
throw new Exception(print_r($personModel->errors(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,7 +353,7 @@ class PodcastImport extends BaseCommand
|
|||||||
$personGroupSlug,
|
$personGroupSlug,
|
||||||
$personRoleSlug
|
$personRoleSlug
|
||||||
)) {
|
)) {
|
||||||
throw new Exception((string) print_r($podcastPersonModel->errors()));
|
throw new Exception(print_r($podcastPersonModel->errors(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,7 +498,7 @@ class PodcastImport extends BaseCommand
|
|||||||
|
|
||||||
if (! ($episodeId = $episodeModel->insert($episode, true))) {
|
if (! ($episodeId = $episodeModel->insert($episode, true))) {
|
||||||
$db->transRollback();
|
$db->transRollback();
|
||||||
throw new Exception((string) print_r($episodeModel->errors()));
|
throw new Exception(print_r($episodeModel->errors(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->importEpisodePersons($episodeId, $item->podcast_persons);
|
$this->importEpisodePersons($episodeId, $item->podcast_persons);
|
||||||
@ -546,7 +546,7 @@ class PodcastImport extends BaseCommand
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (! ($newPersonId = $personModel->insert($newPerson))) {
|
if (! ($newPersonId = $personModel->insert($newPerson))) {
|
||||||
throw new Exception((string) print_r($personModel->errors()));
|
throw new Exception(print_r($personModel->errors(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,7 +574,7 @@ class PodcastImport extends BaseCommand
|
|||||||
$personGroupSlug,
|
$personGroupSlug,
|
||||||
$personRoleSlug
|
$personRoleSlug
|
||||||
)) {
|
)) {
|
||||||
throw new Exception((string) print_r($episodePersonModel->errors()));
|
throw new Exception(print_r($episodePersonModel->errors(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ class PodcastImportController extends BaseController
|
|||||||
{
|
{
|
||||||
helper('podcast_import');
|
helper('podcast_import');
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.all_imports'));
|
||||||
return view('import/queue', [
|
return view('import/queue', [
|
||||||
'podcastImportsQueue' => get_import_tasks(),
|
'podcastImportsQueue' => get_import_tasks(),
|
||||||
]);
|
]);
|
||||||
@ -41,6 +42,7 @@ class PodcastImportController extends BaseController
|
|||||||
|
|
||||||
helper('podcast_import');
|
helper('podcast_import');
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.all_imports'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $podcast->at_handle,
|
0 => $podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -63,6 +65,7 @@ class PodcastImportController extends BaseController
|
|||||||
'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
|
'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Podcast.import'));
|
||||||
return view('import/add_to_queue', $data);
|
return view('import/add_to_queue', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +115,7 @@ class PodcastImportController extends BaseController
|
|||||||
|
|
||||||
helper('form');
|
helper('form');
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('PodcastImport.syncForm.title'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $podcast->at_handle,
|
0 => $podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
|
@ -21,10 +21,6 @@ if (! function_exists('get_import_tasks')) {
|
|||||||
$podcastImportsQueue = service('settings')
|
$podcastImportsQueue = service('settings')
|
||||||
->get('Import.queue') ?? [];
|
->get('Import.queue') ?? [];
|
||||||
|
|
||||||
if (! is_array($podcastImportsQueue)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($podcastHandle !== null) {
|
if ($podcastHandle !== null) {
|
||||||
$podcastImportsQueue = array_filter(
|
$podcastImportsQueue = array_filter(
|
||||||
$podcastImportsQueue,
|
$podcastImportsQueue,
|
||||||
@ -48,6 +44,6 @@ if (! function_exists('get_import_tasks')) {
|
|||||||
return $a->created_at->isAfter($b->created_at) ? -1 : 1;
|
return $a->created_at->isAfter($b->created_at) ? -1 : 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
return array_values($podcastImportsQueue);
|
return $podcastImportsQueue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ class SubscriptionController extends BaseController
|
|||||||
|
|
||||||
helper('form');
|
helper('form');
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Subscription.podcast_subscriptions'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -109,6 +110,7 @@ class SubscriptionController extends BaseController
|
|||||||
'subscription' => $this->subscription,
|
'subscription' => $this->subscription,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Subscription.view', [$this->subscription->id]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => '#' . $this->subscription->id,
|
1 => '#' . $this->subscription->id,
|
||||||
@ -124,6 +126,7 @@ class SubscriptionController extends BaseController
|
|||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Subscription.add', [esc($this->podcast->title)]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
]);
|
]);
|
||||||
@ -250,6 +253,7 @@ class SubscriptionController extends BaseController
|
|||||||
'subscription' => $this->subscription,
|
'subscription' => $this->subscription,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Subscription.edit', [esc($this->podcast->title)]));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => '#' . $this->subscription->id,
|
1 => '#' . $this->subscription->id,
|
||||||
@ -318,6 +322,7 @@ class SubscriptionController extends BaseController
|
|||||||
'subscription' => $this->subscription,
|
'subscription' => $this->subscription,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Subscription.suspend'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => '#' . $this->subscription->id,
|
1 => '#' . $this->subscription->id,
|
||||||
@ -413,6 +418,7 @@ class SubscriptionController extends BaseController
|
|||||||
'subscription' => $this->subscription,
|
'subscription' => $this->subscription,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->setHtmlHead(lang('Subscription.delete'));
|
||||||
replace_breadcrumb_params([
|
replace_breadcrumb_params([
|
||||||
0 => $this->podcast->at_handle,
|
0 => $this->podcast->at_handle,
|
||||||
1 => '#' . $this->subscription->id,
|
1 => '#' . $this->subscription->id,
|
||||||
|
@ -58,9 +58,7 @@ class Subscription extends Entity
|
|||||||
|
|
||||||
public function getStatus(): string
|
public function getStatus(): string
|
||||||
{
|
{
|
||||||
return ($this->expires_at instanceof Time && $this->expires_at->isBefore(
|
return $this->expires_at->isBefore(Time::now()) ? 'expired' : $this->attributes['status'];
|
||||||
Time::now()
|
|
||||||
)) ? 'expired' : $this->attributes['status'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
55
package.json
55
package.json
@ -31,70 +31,71 @@
|
|||||||
"@amcharts/amcharts4": "^4.10.39",
|
"@amcharts/amcharts4": "^4.10.39",
|
||||||
"@amcharts/amcharts4-geodata": "^4.1.30",
|
"@amcharts/amcharts4-geodata": "^4.1.30",
|
||||||
"@codemirror/commands": "^6.7.1",
|
"@codemirror/commands": "^6.7.1",
|
||||||
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-xml": "^6.1.0",
|
"@codemirror/lang-xml": "^6.1.0",
|
||||||
"@codemirror/language": "^6.10.3",
|
"@codemirror/language": "^6.10.7",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.5.0",
|
||||||
"@codemirror/view": "^6.34.2",
|
"@codemirror/view": "^6.35.3",
|
||||||
"@floating-ui/dom": "^1.6.12",
|
"@floating-ui/dom": "^1.6.12",
|
||||||
"@github/clipboard-copy-element": "^1.3.0",
|
"@github/clipboard-copy-element": "^1.3.0",
|
||||||
"@github/hotkey": "^3.1.1",
|
"@github/hotkey": "^3.1.1",
|
||||||
"@github/markdown-toolbar-element": "^2.2.3",
|
"@github/markdown-toolbar-element": "^2.2.3",
|
||||||
"@github/relative-time-element": "^4.4.3",
|
"@github/relative-time-element": "^4.4.4",
|
||||||
"@patternfly/elements": "^4.0.2",
|
"@patternfly/elements": "^4.0.2",
|
||||||
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
|
|
||||||
"@vime/core": "^5.4.1",
|
"@vime/core": "^5.4.1",
|
||||||
"choices.js": "^11.0.2",
|
"choices.js": "^11.0.2",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
|
"htmlfy": "^0.5.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet.markercluster": "^1.5.3",
|
"leaflet.markercluster": "^1.5.3",
|
||||||
"lit": "^3.2.1",
|
"lit": "^3.2.1",
|
||||||
"marked": "^14.1.3",
|
"marked": "^15.0.4",
|
||||||
"wavesurfer.js": "^7.8.8",
|
"wavesurfer.js": "^7.8.11",
|
||||||
"xml-formatter": "^3.6.3"
|
"xml-formatter": "^3.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^19.5.0",
|
"@commitlint/cli": "^19.6.1",
|
||||||
"@commitlint/config-conventional": "^19.5.0",
|
"@commitlint/config-conventional": "^19.6.0",
|
||||||
"@csstools/css-tokenizer": "^3.0.3",
|
"@csstools/css-tokenizer": "^3.0.3",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.17.0",
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/exec": "^6.0.3",
|
"@semantic-release/exec": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"@semantic-release/gitlab": "^13.2.1",
|
"@semantic-release/gitlab": "^13.2.3",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/eslint__js": "^8.42.3",
|
"@types/eslint__js": "^8.42.3",
|
||||||
"@types/leaflet": "^1.9.14",
|
"@types/leaflet": "^1.9.15",
|
||||||
"all-contributors-cli": "^6.26.1",
|
"all-contributors-cli": "^6.26.1",
|
||||||
"commitizen": "^4.3.1",
|
"commitizen": "^4.3.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cssnano": "^7.0.6",
|
"cssnano": "^7.0.6",
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"globals": "^15.12.0",
|
"globals": "^15.13.0",
|
||||||
"husky": "^9.1.6",
|
"husky": "^9.1.7",
|
||||||
"is-ci": "^3.0.1",
|
"is-ci": "^4.1.0",
|
||||||
"lint-staged": "^15.2.10",
|
"lint-staged": "^15.2.11",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.49",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-nesting": "^13.0.1",
|
"postcss-nesting": "^13.0.1",
|
||||||
"postcss-preset-env": "^10.0.9",
|
"postcss-preset-env": "^10.1.2",
|
||||||
"postcss-reporter": "^7.1.0",
|
"postcss-reporter": "^7.1.0",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.4.2",
|
||||||
"prettier-plugin-organize-imports": "^4.1.0",
|
"prettier-plugin-organize-imports": "^4.1.0",
|
||||||
"semantic-release": "^24.2.0",
|
"semantic-release": "^24.2.0",
|
||||||
"stylelint": "^16.10.0",
|
"stylelint": "^16.12.0",
|
||||||
"stylelint-config-standard": "^36.0.1",
|
"stylelint-config-standard": "^36.0.1",
|
||||||
"svgo": "^3.3.2",
|
"svgo": "^3.3.2",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.16",
|
||||||
"typescript": "~5.5.4",
|
"typescript": "~5.7.2",
|
||||||
"typescript-eslint": "^8.13.0",
|
"typescript-eslint": "^8.18.1",
|
||||||
"vite": "^5.4.10",
|
"vite": "^6.0.3",
|
||||||
"vite-plugin-pwa": "^0.20.5",
|
"vite-plugin-pwa": "^0.21.1",
|
||||||
"workbox-build": "^7.3.0",
|
"workbox-build": "^7.3.0",
|
||||||
"workbox-core": "^7.3.0",
|
"workbox-core": "^7.3.0",
|
||||||
"workbox-routing": "^7.3.0",
|
"workbox-routing": "^7.3.0",
|
||||||
|
@ -50,3 +50,4 @@ parameters:
|
|||||||
ignoreErrors:
|
ignoreErrors:
|
||||||
- '#^Call to an undefined method CodeIgniter\\Cache\\CacheInterface\:\:deleteMatching\(\)#'
|
- '#^Call to an undefined method CodeIgniter\\Cache\\CacheInterface\:\:deleteMatching\(\)#'
|
||||||
- identifier: missingType.generics
|
- identifier: missingType.generics
|
||||||
|
- identifier: property.readOnlyByPhpDocDefaultValue
|
@ -1,6 +1,7 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
<phpunit
|
<phpunit
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd"
|
||||||
bootstrap="vendor/codeigniter4/framework/system/Test/bootstrap.php"
|
bootstrap="vendor/codeigniter4/framework/system/Test/bootstrap.php"
|
||||||
backupGlobals="false"
|
backupGlobals="false"
|
||||||
beStrictAboutOutputDuringTests="true"
|
beStrictAboutOutputDuringTests="true"
|
||||||
@ -8,9 +9,14 @@
|
|||||||
columns="max"
|
columns="max"
|
||||||
failOnRisky="true"
|
failOnRisky="true"
|
||||||
failOnWarning="true"
|
failOnWarning="true"
|
||||||
cacheDirectory="build/.phpunit.cache">
|
displayDetailsOnTestsThatTriggerDeprecations="true"
|
||||||
|
displayDetailsOnTestsThatTriggerErrors="true"
|
||||||
|
displayDetailsOnTestsThatTriggerNotices="true"
|
||||||
|
displayDetailsOnTestsThatTriggerWarnings="true"
|
||||||
|
displayDetailsOnPhpunitDeprecations="true"
|
||||||
|
cacheDirectory="build/.phpunit.cache"
|
||||||
|
>
|
||||||
<coverage
|
<coverage
|
||||||
includeUncoveredFiles="true"
|
|
||||||
pathCoverage="false"
|
pathCoverage="false"
|
||||||
ignoreDeprecatedCodeUnits="true"
|
ignoreDeprecatedCodeUnits="true"
|
||||||
disableCodeCoverageIgnore="true">
|
disableCodeCoverageIgnore="true">
|
||||||
|
2135
pnpm-lock.yaml
generated
2135
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
require("postcss-reporter"),
|
require("postcss-reporter"),
|
||||||
require("tailwindcss/nesting")(require("postcss-nesting")),
|
|
||||||
require("tailwindcss"),
|
require("tailwindcss"),
|
||||||
require("postcss-preset-env")({
|
require("postcss-preset-env")({
|
||||||
stage: 4,
|
stage: 4,
|
||||||
|
@ -45,6 +45,7 @@ class ExampleDatabaseTest extends CIUnitTestCase
|
|||||||
$model->delete($object->id);
|
$model->delete($object->id);
|
||||||
|
|
||||||
// The model should no longer find it
|
// The model should no longer find it
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
$this->assertNull($model->find($object->id));
|
$this->assertNull($model->find($object->id));
|
||||||
|
|
||||||
// ... but it should still be in the database
|
// ... but it should still be in the database
|
||||||
|
@ -8,6 +8,7 @@ use CodeIgniter\Database\Seeder;
|
|||||||
use CodeIgniter\Test\CIUnitTestCase;
|
use CodeIgniter\Test\CIUnitTestCase;
|
||||||
use CodeIgniter\Test\DatabaseTestTrait;
|
use CodeIgniter\Test\DatabaseTestTrait;
|
||||||
use CodeIgniter\Test\FeatureTestTrait;
|
use CodeIgniter\Test\FeatureTestTrait;
|
||||||
|
use Override;
|
||||||
use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
|
use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
|
||||||
|
|
||||||
class EpisodeTest extends CIUnitTestCase
|
class EpisodeTest extends CIUnitTestCase
|
||||||
@ -45,11 +46,12 @@ class EpisodeTest extends CIUnitTestCase
|
|||||||
*/
|
*/
|
||||||
private array $episode = [];
|
private array $episode = [];
|
||||||
|
|
||||||
private readonly string $apiUrl;
|
private string $apiUrl = '';
|
||||||
|
|
||||||
public function __construct(?string $name = null)
|
#[Override]
|
||||||
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::__construct($name);
|
parent::setUp();
|
||||||
|
|
||||||
$this->episode = FakeSinglePodcastApiSeeder::episode();
|
$this->episode = FakeSinglePodcastApiSeeder::episode();
|
||||||
|
|
||||||
@ -59,6 +61,15 @@ class EpisodeTest extends CIUnitTestCase
|
|||||||
->gateway;
|
->gateway;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
|
||||||
|
restore_error_handler();
|
||||||
|
restore_exception_handler();
|
||||||
|
}
|
||||||
|
|
||||||
public function testList(): void
|
public function testList(): void
|
||||||
{
|
{
|
||||||
$result = $this->call('get', $this->apiUrl . 'episodes');
|
$result = $this->call('get', $this->apiUrl . 'episodes');
|
||||||
|
@ -8,6 +8,7 @@ use CodeIgniter\Database\Seeder;
|
|||||||
use CodeIgniter\Test\CIUnitTestCase;
|
use CodeIgniter\Test\CIUnitTestCase;
|
||||||
use CodeIgniter\Test\DatabaseTestTrait;
|
use CodeIgniter\Test\DatabaseTestTrait;
|
||||||
use CodeIgniter\Test\FeatureTestTrait;
|
use CodeIgniter\Test\FeatureTestTrait;
|
||||||
|
use Override;
|
||||||
use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
|
use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
|
||||||
|
|
||||||
class PodcastTest extends CIUnitTestCase
|
class PodcastTest extends CIUnitTestCase
|
||||||
@ -45,11 +46,13 @@ class PodcastTest extends CIUnitTestCase
|
|||||||
*/
|
*/
|
||||||
private array $podcast = [];
|
private array $podcast = [];
|
||||||
|
|
||||||
private readonly string $podcastApiUrl;
|
private string $podcastApiUrl = '';
|
||||||
|
|
||||||
public function __construct(?string $name = null)
|
#[Override]
|
||||||
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::__construct($name);
|
parent::setUp();
|
||||||
|
|
||||||
$this->podcast = FakeSinglePodcastApiSeeder::podcast();
|
$this->podcast = FakeSinglePodcastApiSeeder::podcast();
|
||||||
$this->podcast['created_at'] = [];
|
$this->podcast['created_at'] = [];
|
||||||
$this->podcast['updated_at'] = [];
|
$this->podcast['updated_at'] = [];
|
||||||
@ -57,6 +60,15 @@ class PodcastTest extends CIUnitTestCase
|
|||||||
->gateway;
|
->gateway;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
|
||||||
|
restore_error_handler();
|
||||||
|
restore_exception_handler();
|
||||||
|
}
|
||||||
|
|
||||||
public function testList(): void
|
public function testList(): void
|
||||||
{
|
{
|
||||||
$result = $this->call('get', $this->podcastApiUrl . 'podcasts');
|
$result = $this->call('get', $this->podcastApiUrl . 'podcasts');
|
||||||
|
@ -6,6 +6,7 @@ namespace Tests\Modules\Plugins;
|
|||||||
|
|
||||||
use App\Entities\Episode;
|
use App\Entities\Episode;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
|
use App\Libraries\HtmlHead;
|
||||||
use App\Libraries\RssFeed;
|
use App\Libraries\RssFeed;
|
||||||
use CodeIgniter\Test\CIUnitTestCase;
|
use CodeIgniter\Test\CIUnitTestCase;
|
||||||
use CodeIgniter\Test\DatabaseTestTrait;
|
use CodeIgniter\Test\DatabaseTestTrait;
|
||||||
@ -111,11 +112,21 @@ final class PluginsTest extends CIUnitTestCase
|
|||||||
self::$plugins->runHook('rssAfterItem', [$episode, $item]);
|
self::$plugins->runHook('rssAfterItem', [$episode, $item]);
|
||||||
$this->assertFalse(empty($item->efoo));
|
$this->assertFalse(empty($item->efoo));
|
||||||
|
|
||||||
ob_start();
|
$head = new HtmlHead();
|
||||||
self::$plugins->runHook('siteHead', []);
|
self::$plugins->runHook('siteHead', [$head]);
|
||||||
$result = ob_get_contents();
|
|
||||||
ob_end_clean(); //Discard output buffer
|
$this->assertEquals(
|
||||||
$this->assertEquals('hello', $result);
|
(string) $head,
|
||||||
|
'<head> <title >foo</title> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="icon" type="image/x-icon" href="http://localhost:8080/favicon.ico"/> <link rel="apple-touch-icon" href="http://localhost:8080/icon-180.png"/> <link rel="manifest" href="/manifest.webmanifest"/> <meta name="theme-color" content="#009486"/> <link rel="stylesheet" type="text/css" href="/themes/colors"/> <title >foo</title> <script>
|
||||||
|
// Check that service workers are supported
|
||||||
|
if (\'serviceWorker\' in navigator) {
|
||||||
|
// Use the window load event to keep the page load performant
|
||||||
|
window.addEventListener(\'load\', () => {
|
||||||
|
navigator.serviceWorker.register(\'/sw.js\');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script></head>'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRunHooksInactive(): void
|
public function testRunHooksInactive(): void
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user