mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-22 16:51:20 +00:00
feat(components): add custom view renderer with ComponentRenderer adapted from bonfire2
- update Component class structure and remove component helper function and ComponentLoader - update residual activitypub naming to fediverse
This commit is contained in:
parent
5083cd2fda
commit
a95de8bab0
3
.gitignore
vendored
3
.gitignore
vendored
@ -154,8 +154,7 @@ public/media/persons/*
|
||||
!public/media/persons/index.html
|
||||
|
||||
# Generated files
|
||||
app/Language/en/PersonsTaxonomy.php
|
||||
app/Language/fr/PersonsTaxonomy.php
|
||||
modules/Admin/Language/*/PersonsTaxonomy.php
|
||||
|
||||
#-------------------------
|
||||
# Docker volumes
|
||||
|
@ -43,16 +43,14 @@ class Autoload extends AutoloadConfig
|
||||
*/
|
||||
public $psr4 = [
|
||||
APP_NAMESPACE => APPPATH,
|
||||
'Modules' => ROOTPATH . 'modules',
|
||||
'Modules\Admin' => ROOTPATH . 'modules/Admin',
|
||||
'Modules\Auth' => ROOTPATH . 'modules/Auth',
|
||||
'Modules\Analytics' => ROOTPATH . 'modules/Analytics',
|
||||
'Modules\Install' => ROOTPATH . 'modules/Install',
|
||||
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse',
|
||||
'Config' => APPPATH . 'Config',
|
||||
'ActivityPub' => APPPATH . 'Libraries/ActivityPub',
|
||||
'Analytics' => APPPATH . 'Libraries/Analytics',
|
||||
'ViewComponents' => APPPATH . 'Libraries/ViewComponents',
|
||||
'Modules' => ROOTPATH . 'modules/',
|
||||
'Modules\Admin' => ROOTPATH . 'modules/Admin/',
|
||||
'Modules\Auth' => ROOTPATH . 'modules/Auth/',
|
||||
'Modules\Analytics' => ROOTPATH . 'modules/Analytics/',
|
||||
'Modules\Install' => ROOTPATH . 'modules/Install/',
|
||||
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/',
|
||||
'Config' => APPPATH . 'Config/',
|
||||
'ViewComponents' => APPPATH . 'Libraries/ViewComponents/',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -76,7 +76,7 @@ Events::on('logout', function (User $user): void {
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* ActivityPub events
|
||||
* Fediverse events
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
/**
|
||||
|
@ -9,7 +9,7 @@ use CodeIgniter\Filters\CSRF;
|
||||
use CodeIgniter\Filters\DebugToolbar;
|
||||
use CodeIgniter\Filters\Honeypot;
|
||||
use Modules\Auth\Filters\PermissionFilter;
|
||||
use Modules\Fediverse\Filters\ActivityPubFilter;
|
||||
use Modules\Fediverse\Filters\FediverseFilter;
|
||||
use Myth\Auth\Filters\LoginFilter;
|
||||
use Myth\Auth\Filters\RoleFilter;
|
||||
|
||||
@ -27,7 +27,7 @@ class Filters extends BaseConfig
|
||||
'login' => LoginFilter::class,
|
||||
'role' => RoleFilter::class,
|
||||
'permission' => PermissionFilter::class,
|
||||
'activity-pub' => ActivityPubFilter::class,
|
||||
'activity-pub' => FediverseFilter::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -7,11 +7,14 @@ namespace Config;
|
||||
use App\Libraries\Breadcrumb;
|
||||
use App\Libraries\Negotiate;
|
||||
use App\Libraries\Router;
|
||||
use App\Libraries\View;
|
||||
use App\Libraries\Vite;
|
||||
use CodeIgniter\Config\BaseService;
|
||||
use CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\Router\RouteCollectionInterface;
|
||||
use Config\Services as AppServices;
|
||||
use Config\View as ViewConfig;
|
||||
|
||||
/**
|
||||
* Services Configuration file.
|
||||
@ -46,6 +49,24 @@ class Services extends BaseService
|
||||
return new Router($routes, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Renderer class is the class that actually displays a file to the user. The default View class within
|
||||
* CodeIgniter is intentionally simple, but this service could easily be replaced by a template engine if the user
|
||||
* needed to.
|
||||
*/
|
||||
public static function renderer(?string $viewPath = null, ?ViewConfig $config = null, bool $getShared = true): View
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('renderer', $viewPath, $config);
|
||||
}
|
||||
|
||||
$viewPath = $viewPath ?: config('Paths')
|
||||
->viewDirectory;
|
||||
$config = $config ?? config('View');
|
||||
|
||||
return new View($config, $viewPath, AppServices::locator(), CI_DEBUG, AppServices::logger());
|
||||
}
|
||||
|
||||
/**
|
||||
* The Negotiate class provides the content negotiation features for working the request to determine correct
|
||||
* language, encoding, charset, and more.
|
||||
|
@ -11,9 +11,9 @@ declare(strict_types=1);
|
||||
namespace App\Controllers;
|
||||
|
||||
use Modules\Analytics\AnalyticsTrait;
|
||||
use Modules\Fediverse\Controllers\ActorController as ActivityPubActorController;
|
||||
use Modules\Fediverse\Controllers\ActorController as FediverseActorController;
|
||||
|
||||
class ActorController extends ActivityPubActorController
|
||||
class ActorController extends FediverseActorController
|
||||
{
|
||||
use AnalyticsTrait;
|
||||
|
||||
|
@ -127,6 +127,9 @@ class EpisodeCommentController extends BaseController
|
||||
->setBody($commentObject->toJSON());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function replies(): Response
|
||||
{
|
||||
/**
|
||||
|
@ -222,7 +222,7 @@ class EpisodeController extends BaseController
|
||||
$episodeComments = model('PostModel')
|
||||
->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
|
||||
return $builder->select('id')
|
||||
->from('activitypub_posts')
|
||||
->from(config('Fediverse')->tablesPrefix . 'posts')
|
||||
->where('episode_id', $this->episode->id);
|
||||
})
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
|
@ -21,10 +21,10 @@ use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Modules\Analytics\AnalyticsTrait;
|
||||
use Modules\Fediverse\Controllers\PostController as ActivityPubPostController;
|
||||
use Modules\Fediverse\Entities\Post as ActivityPubPost;
|
||||
use Modules\Fediverse\Controllers\PostController as FediversePostController;
|
||||
use Modules\Fediverse\Entities\Post as FediversePost;
|
||||
|
||||
class PostController extends ActivityPubPostController
|
||||
class PostController extends FediversePostController
|
||||
{
|
||||
use AnalyticsTrait;
|
||||
|
||||
@ -35,7 +35,7 @@ class PostController extends ActivityPubPostController
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc'];
|
||||
protected $helpers = ['auth', 'fediverse', 'svg', 'components', 'misc'];
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
@ -162,7 +162,7 @@ class PostController extends ActivityPubPostController
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$newPost = new ActivityPubPost([
|
||||
$newPost = new FediversePost([
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'in_reply_to_id' => $this->post->id,
|
||||
'message' => $this->request->getPost('message'),
|
||||
|
@ -196,7 +196,7 @@ class AddPodcasts extends Migration
|
||||
$this->forge->addUniqueKey('handle');
|
||||
$this->forge->addUniqueKey('guid');
|
||||
$this->forge->addUniqueKey('actor_id');
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', config('Fediverse')->tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('category_id', 'categories', 'id');
|
||||
$this->forge->addForeignKey('language_code', 'languages', 'code');
|
||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddEpisodeIdToPosts Adds episode_id field to activitypub_posts table in database
|
||||
* Class AddEpisodeIdToPosts Adds episode_id field to posts table in database
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -19,18 +19,26 @@ class AddEpisodeIdToPosts extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$createQuery = <<<CODE_SAMPLE
|
||||
ALTER TABLE {$prefix}activitypub_posts
|
||||
ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
|
||||
ADD COLUMN `episode_id` INT UNSIGNED NULL AFTER `replies_count`,
|
||||
ADD FOREIGN KEY {$prefix}activitypub_posts_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
|
||||
ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
|
||||
CODE_SAMPLE;
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropForeignKey('activitypub_posts', 'activitypub_posts_episode_id_foreign');
|
||||
$this->forge->dropColumn('activitypub_posts', 'episode_id');
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->dropForeignKey(
|
||||
$fediverseTablesPrefix . 'posts',
|
||||
$fediverseTablesPrefix . 'posts_episode_id_foreign'
|
||||
);
|
||||
$this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'episode_id');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddCreatedByToPosts Adds created_by field to activitypub_posts table in database
|
||||
* Class AddCreatedByToPosts Adds created_by field to posts table in database
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -19,18 +19,26 @@ class AddCreatedByToPosts extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$createQuery = <<<CODE_SAMPLE
|
||||
ALTER TABLE {$prefix}activitypub_posts
|
||||
ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
|
||||
ADD COLUMN `created_by` INT UNSIGNED AFTER `episode_id`,
|
||||
ADD FOREIGN KEY {$prefix}activitypub_posts_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
|
||||
ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
|
||||
CODE_SAMPLE;
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropForeignKey('activitypub_posts', 'activitypub_posts_created_by_foreign');
|
||||
$this->forge->dropColumn('activitypub_posts', 'created_by');
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->dropForeignKey(
|
||||
$fediverseTablesPrefix . 'posts',
|
||||
$fediverseTablesPrefix . 'posts_created_by_foreign'
|
||||
);
|
||||
$this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'created_by');
|
||||
}
|
||||
}
|
||||
|
@ -65,9 +65,13 @@ class AddEpisodeComments extends Migration
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||
$this->forge->createTable('episode_comments');
|
||||
}
|
||||
|
@ -28,9 +28,13 @@ class AddLikes extends Migration
|
||||
'constraint' => 16,
|
||||
],
|
||||
]);
|
||||
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
|
||||
$this->forge->addPrimaryKey(['actor_id', 'comment_id']);
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('comment_id', 'episode_comments', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('likes');
|
||||
}
|
||||
|
@ -238,13 +238,13 @@ class AuthSeeder extends Seeder
|
||||
[
|
||||
'name' => 'block_actors',
|
||||
'description' =>
|
||||
'Block an activitypub actors from interacting with the instance.',
|
||||
'Block fediverse actors from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'block_domains',
|
||||
'description' =>
|
||||
'Block an activitypub domains from interacting with the instance.',
|
||||
'Block fediverse domains from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
|
@ -11,14 +11,14 @@ declare(strict_types=1);
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Models\PodcastModel;
|
||||
use Modules\Fediverse\Entities\Actor as ActivityPubActor;
|
||||
use Modules\Fediverse\Entities\Actor as FediverseActor;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @property Podcast|null $podcast
|
||||
* @property boolean $is_podcast
|
||||
*/
|
||||
class Actor extends ActivityPubActor
|
||||
class Actor extends FediverseActor
|
||||
{
|
||||
protected ?Podcast $podcast = null;
|
||||
|
||||
|
@ -134,7 +134,7 @@ class EpisodeComment extends UuidEntity
|
||||
|
||||
public function setMessage(string $message): static
|
||||
{
|
||||
helper('activitypub');
|
||||
helper('fediverse');
|
||||
|
||||
$messageWithoutTags = strip_tags($message);
|
||||
|
||||
|
@ -11,14 +11,14 @@ declare(strict_types=1);
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Models\EpisodeModel;
|
||||
use Modules\Fediverse\Entities\Post as ActivityPubPost;
|
||||
use Modules\Fediverse\Entities\Post as FediversePost;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @property int|null $episode_id
|
||||
* @property Episode|null $episode
|
||||
*/
|
||||
class Post extends ActivityPubPost
|
||||
class Post extends FediversePost
|
||||
{
|
||||
protected ?Episode $episode = null;
|
||||
|
||||
|
@ -18,7 +18,7 @@ if (! function_exists('save_media')) {
|
||||
/**
|
||||
* Saves a file to the corresponding podcast folder in `public/media`
|
||||
*/
|
||||
function save_media(File|UploadedFile $file, string $folder = '', string $filename = ''): string
|
||||
function save_media(File | UploadedFile $file, string $folder = '', string $filename = ''): string
|
||||
{
|
||||
if (($extension = $file->getExtension()) !== '') {
|
||||
$filename = $filename . '.' . $extension;
|
||||
|
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'your_handle' => 'Your handle',
|
||||
'your_handle_hint' => 'Enter the @username@domain you want to act from.',
|
||||
'follow' => [
|
||||
'label' => 'Follow',
|
||||
'title' => 'Follow {actorDisplayName}',
|
||||
'subtitle' => 'You are going to follow:',
|
||||
'accountNotFound' => 'The account could not be found.',
|
||||
'submit' => 'Proceed to follow',
|
||||
],
|
||||
'favourite' => [
|
||||
'title' => "Favourite {actorDisplayName}'s post",
|
||||
'subtitle' => 'You are going to favourite:',
|
||||
'submit' => 'Proceed to favourite',
|
||||
],
|
||||
'reblog' => [
|
||||
'title' => "Share {actorDisplayName}'s post",
|
||||
'subtitle' => 'You are going to share:',
|
||||
'submit' => 'Proceed to share',
|
||||
],
|
||||
'reply' => [
|
||||
'title' => "Reply to {actorDisplayName}'s post",
|
||||
'subtitle' => 'You are going to reply to:',
|
||||
'submit' => 'Proceed to reply',
|
||||
],
|
||||
];
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'dashboard' => 'Admin dashboard',
|
||||
'welcome_message' => 'Welcome to the admin area!',
|
||||
'choose_interact' => 'Choose how to interact',
|
||||
];
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'label' => 'breadcrumb',
|
||||
config('Admin')
|
||||
->gateway => 'Home',
|
||||
'podcasts' => 'podcasts',
|
||||
'episodes' => 'episodes',
|
||||
'contributors' => 'contributors',
|
||||
'pages' => 'pages',
|
||||
'add' => 'add',
|
||||
'new' => 'new',
|
||||
'edit' => 'edit',
|
||||
'persons' => 'persons',
|
||||
'publish' => 'publish',
|
||||
'publish-edit' => 'edit publication',
|
||||
'unpublish' => 'unpublish',
|
||||
'fediverse' => 'fediverse',
|
||||
'block-lists' => 'block lists',
|
||||
'users' => 'users',
|
||||
'my-account' => 'my account',
|
||||
'change-password' => 'change password',
|
||||
'import' => 'feed import',
|
||||
'platforms' => 'platforms',
|
||||
'social' => 'social networks',
|
||||
'funding' => 'funding',
|
||||
'analytics' => 'analytics',
|
||||
'locations' => 'locations',
|
||||
'webpages' => 'web pages',
|
||||
'unique-listeners' => 'unique listeners',
|
||||
'players' => 'players',
|
||||
'listening-time' => 'listening time',
|
||||
'time-periods' => 'time periods',
|
||||
'soundbites' => 'soundbites',
|
||||
'embeddable-player' => 'embeddable player',
|
||||
];
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'by_service_weekly' => 'Episode downloads by service (for the past week)',
|
||||
'by_player_weekly' => 'Episode downloads by player (for the past week)',
|
||||
'by_player_yearly' => 'Episode downloads by player (for the past year)',
|
||||
'by_device_weekly' => 'Episode downloads by device (for the past week)',
|
||||
'by_os_weekly' => 'Episode downloads by O.S. (for the past week)',
|
||||
'podcast_by_region' => 'Episode downloads by region (for the past week)',
|
||||
'unique_daily_listeners' => 'Daily unique listeners',
|
||||
'unique_monthly_listeners' => 'Monthly unique listeners',
|
||||
'by_browser' => 'Web pages usage by browser (for the past week)',
|
||||
'podcast_by_day' => 'Episode daily downloads',
|
||||
'podcast_by_month' => 'Episode monthly downloads',
|
||||
'episode_by_day' => 'Episode daily downloads (first 60 days)',
|
||||
'episode_by_month' => 'Episode monthly downloads',
|
||||
'episodes_by_day' =>
|
||||
'5 latest episodes downloads (during their first 60 days)',
|
||||
'by_country_weekly' => 'Episode downloads by country (for the past week)',
|
||||
'by_country_yearly' => 'Episode downloads by country (for the past year)',
|
||||
'by_domain_weekly' => 'Web pages visits by source (for the past week)',
|
||||
'by_domain_yearly' => 'Web pages visits by source (for the past year)',
|
||||
'by_entry_page' => 'Web pages visits by landing page (for the past week)',
|
||||
'podcast_bots' => 'Bots (crawlers)',
|
||||
'daily_listening_time' => 'Daily cumulative listening time',
|
||||
'monthly_listening_time' => 'Monthly cumulative listening time',
|
||||
'by_weekday' => 'By week day (for the past 60 days)',
|
||||
'by_hour' => 'By time of day (for the past 60 days)',
|
||||
'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)',
|
||||
];
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'your_handle' => 'Votre pseudonyme',
|
||||
'your_handle_hint' =>
|
||||
'Entrez le @utilisateur@domaine avec lequel vous voulez interagir.',
|
||||
'follow' => [
|
||||
'label' => 'Suivre',
|
||||
'title' => 'Suivre {actorDisplayName}',
|
||||
'subtitle' => 'Vous allez suivre :',
|
||||
'accountNotFound' => 'Le compte n’a pas pu être trouvé.',
|
||||
'submit' => 'Poursuivre',
|
||||
],
|
||||
'favourite' => [
|
||||
'title' => 'Mettez la publication de {actorDisplayName} en favori',
|
||||
'subtitle' => 'Vous allez mettre en favori :',
|
||||
'submit' => 'Poursuivre',
|
||||
],
|
||||
'reblog' => [
|
||||
'title' => 'Partagez la publication de {actorDisplayName}',
|
||||
'subtitle' => 'Vous allez partager :',
|
||||
'submit' => 'Poursuivre',
|
||||
],
|
||||
'reply' => [
|
||||
'title' => 'Répondre à la publication de {actorDisplayName}',
|
||||
'subtitle' => 'Vous allez répondre à :',
|
||||
'submit' => 'Poursuivre',
|
||||
],
|
||||
];
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'dashboard' => 'Tableau de bord',
|
||||
'welcome_message' => 'Bienvenue dans l’administration !',
|
||||
];
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'label' => 'Fil d’Ariane',
|
||||
config('Admin')
|
||||
->gateway => 'Accueil',
|
||||
'podcasts' => 'podcasts',
|
||||
'episodes' => 'épisodes',
|
||||
'contributors' => 'contributeurs',
|
||||
'pages' => 'pages',
|
||||
'add' => 'ajouter',
|
||||
'new' => 'créer',
|
||||
'edit' => 'modifier',
|
||||
'persons' => 'intervenants',
|
||||
'publish' => 'publier',
|
||||
'publish-edit' => 'modifier la publication',
|
||||
'unpublish' => 'dépublier',
|
||||
'fediverse' => 'fédiverse',
|
||||
'block-lists' => 'listes de blocage',
|
||||
'users' => 'utilisateurs',
|
||||
'my-account' => 'mon compte',
|
||||
'change-password' => 'changer le mot de passe',
|
||||
'import' => 'importer un flux',
|
||||
'platforms' => 'plateformes',
|
||||
'social' => 'réseaux sociaux',
|
||||
'funding' => 'financement',
|
||||
'analytics' => 'mesures d’audience',
|
||||
'locations' => 'localisations',
|
||||
'webpages' => 'pages web',
|
||||
'unique-listeners' => 'auditeurs uniques',
|
||||
'players' => 'lecteurs',
|
||||
'listening-time' => 'drée d’écoute',
|
||||
'time-periods' => 'périodes',
|
||||
'soundbites' => 'extraits sonores',
|
||||
'embeddable-player' => 'lecteur intégré',
|
||||
];
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'by_service_weekly' =>
|
||||
'Téléchargements d’épisodes par service (sur la dernière semaine)',
|
||||
'by_player_weekly' =>
|
||||
'Téléchargements d’épisodes par lecteur (sur la dernière semaine)',
|
||||
'by_player_yearly' =>
|
||||
'Téléchargements d’épisodes par lecteur (sur la dernière année)',
|
||||
'by_device_weekly' =>
|
||||
'Téléchargements d’épisodes par appareil (sur la dernière semaine)',
|
||||
'by_os_weekly' =>
|
||||
'Téléchargements d’épisodes par OS (sur la dernière semaine)',
|
||||
'podcast_by_region' =>
|
||||
'Téléchargements d’épisodes par région (sur la dernière semaine)',
|
||||
'unique_daily_listeners' => 'Auditeurs uniques quotidiens',
|
||||
'unique_monthly_listeners' => 'Auditeurs uniques mensuels',
|
||||
'by_browser' =>
|
||||
'Fréquentation des pages web par navigateur (sur la dernière semaine)',
|
||||
'podcast_by_day' => 'Téléchargements quotidiens d’épisodes',
|
||||
'podcast_by_month' => 'Téléchargements mensuels d’épisodes',
|
||||
'episode_by_day' =>
|
||||
'Téléchargements quotidiens de l’épisode (sur les 60 premiers jours)',
|
||||
'episode_by_month' => 'Téléchargements mensuels de l’épisode',
|
||||
'episodes_by_day' =>
|
||||
'Téléchargements des 5 derniers épisodes (sur les 60 premiers jours)',
|
||||
'by_country_weekly' =>
|
||||
'Téléchargement d’épisodes par pays (sur la dernière semaine)',
|
||||
'by_country_yearly' =>
|
||||
'Téléchargement d’épisodes par pays (sur la dernière année)',
|
||||
'by_domain_weekly' =>
|
||||
'Fréquentation des pages web par origine (sur la dernière semaine)',
|
||||
'by_domain_yearly' =>
|
||||
'Fréquentation des pages web par origine (sur la dernière année)',
|
||||
'by_entry_page' =>
|
||||
'Fréquentation des pages web par page d’entrée (sur la dernière semaine)',
|
||||
'podcast_bots' => 'Robots (bots)',
|
||||
'daily_listening_time' => 'Durée quotidienne d’écoute cumulée',
|
||||
'monthly_listening_time' => 'Durée mensuelle d’écoute cumulée',
|
||||
'by_weekday' => 'Par jour de la semaine (sur les 60 derniers jours)',
|
||||
'by_hour' => 'Par heure de la journée (sur les 60 derniers jours)',
|
||||
'podcast_by_bandwidth' => 'Bande passante quotidienne consommée (en Mo)',
|
||||
];
|
@ -11,9 +11,9 @@ declare(strict_types=1);
|
||||
namespace App\Libraries;
|
||||
|
||||
use App\Entities\Post;
|
||||
use Modules\Fediverse\Objects\NoteObject as ActivityPubNoteObject;
|
||||
use Modules\Fediverse\Objects\NoteObject as FediverseNoteObject;
|
||||
|
||||
class NoteObject extends ActivityPubNoteObject
|
||||
class NoteObject extends FediverseNoteObject
|
||||
{
|
||||
/**
|
||||
* @param Post $post
|
||||
|
143
app/Libraries/View.php
Normal file
143
app/Libraries/View.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use CodeIgniter\Debug\Toolbar\Collectors\Views;
|
||||
use CodeIgniter\Filters\DebugToolbar;
|
||||
use CodeIgniter\View\Exceptions\ViewException;
|
||||
use CodeIgniter\View\View as CodeIgniterView;
|
||||
use Config\Toolbar;
|
||||
|
||||
class View extends CodeIgniterView
|
||||
{
|
||||
/**
|
||||
* Builds the output based upon a file name and any
|
||||
* data that has already been set.
|
||||
*
|
||||
* Valid $options:
|
||||
* - cache Number of seconds to cache for
|
||||
* - cache_name Name to use for cache
|
||||
*
|
||||
* @param string $view File name of the view source
|
||||
* @param array<string, mixed>|null $options Reserved for 3rd-party uses since
|
||||
* it might be needed to pass additional info
|
||||
* to other template engines.
|
||||
* @param bool|null $saveData If true, saves data for subsequent calls,
|
||||
* if false, cleans the data after displaying,
|
||||
* if null, uses the config setting.
|
||||
*/
|
||||
public function render(string $view, ?array $options = null, ?bool $saveData = null): string
|
||||
{
|
||||
$this->renderVars['start'] = microtime(true);
|
||||
|
||||
// Store the results here so even if
|
||||
// multiple views are called in a view, it won't
|
||||
// clean it unless we mean it to.
|
||||
$saveData = $saveData ?? $this->saveData;
|
||||
$fileExt = pathinfo($view, PATHINFO_EXTENSION);
|
||||
$realPath = $fileExt === '' ? $view . '.php' : $view; // allow Views as .html, .tpl, etc (from CI3)
|
||||
$this->renderVars['view'] = $realPath;
|
||||
$this->renderVars['options'] = $options ?? [];
|
||||
|
||||
// Was it cached?
|
||||
if (isset($this->renderVars['options']['cache'])) {
|
||||
$cacheName = $this->renderVars['options']['cache_name'] ?? str_replace(
|
||||
'.php',
|
||||
'',
|
||||
$this->renderVars['view']
|
||||
);
|
||||
$cacheName = str_replace(['\\', '/'], '', $cacheName);
|
||||
|
||||
$this->renderVars['cacheName'] = $cacheName;
|
||||
|
||||
if ($output = cache($this->renderVars['cacheName'])) {
|
||||
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
$this->renderVars['file'] = $this->viewPath . $this->renderVars['view'];
|
||||
|
||||
if (! is_file($this->renderVars['file'])) {
|
||||
$this->renderVars['file'] = $this->loader->locateFile(
|
||||
$this->renderVars['view'],
|
||||
'Views',
|
||||
$fileExt === '' ? 'php' : $fileExt
|
||||
);
|
||||
}
|
||||
|
||||
// locateFile will return an empty string if the file cannot be found.
|
||||
if ($this->renderVars['file'] === '') {
|
||||
throw ViewException::forInvalidFile($this->renderVars['view']);
|
||||
}
|
||||
|
||||
// Make our view data available to the view.
|
||||
$this->tempData = $this->tempData ?? $this->data;
|
||||
|
||||
if ($saveData) {
|
||||
$this->data = $this->tempData;
|
||||
}
|
||||
|
||||
// Save current vars
|
||||
$renderVars = $this->renderVars;
|
||||
|
||||
$output = (function (): string {
|
||||
/** @phpstan-ignore-next-line */
|
||||
extract($this->tempData);
|
||||
ob_start();
|
||||
include $this->renderVars['file'];
|
||||
|
||||
return ob_get_clean() ?: '';
|
||||
})();
|
||||
|
||||
// Get back current vars
|
||||
$this->renderVars = $renderVars;
|
||||
|
||||
// When using layouts, the data has already been stored
|
||||
// in $this->sections, and no other valid output
|
||||
// is allowed in $output so we'll overwrite it.
|
||||
if ($this->layout !== null && $this->sectionStack === []) {
|
||||
$layoutView = $this->layout;
|
||||
$this->layout = null;
|
||||
// Save current vars
|
||||
$renderVars = $this->renderVars;
|
||||
$output = $this->render($layoutView, $options, $saveData);
|
||||
// Get back current vars
|
||||
$this->renderVars = $renderVars;
|
||||
}
|
||||
|
||||
$output = service('components')
|
||||
->setCurrentView($view)
|
||||
->render($output);
|
||||
|
||||
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
|
||||
|
||||
if (($this->debug && (! isset($options['debug']) || $options['debug'] === true))
|
||||
&& in_array(DebugToolbar::class, service('filters')->getFiltersClass()['after'], true)
|
||||
) {
|
||||
$toolbarCollectors = config(Toolbar::class)->collectors;
|
||||
|
||||
if (in_array(Views::class, $toolbarCollectors, true)) {
|
||||
// Clean up our path names to make them a little cleaner
|
||||
$this->renderVars['file'] = clean_path($this->renderVars['file']);
|
||||
$this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file'];
|
||||
|
||||
$output = '<!-- DEBUG-VIEW START ' . $this->renderVars['file'] . ' -->' . PHP_EOL
|
||||
. $output . PHP_EOL
|
||||
. '<!-- DEBUG-VIEW ENDED ' . $this->renderVars['file'] . ' -->' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
// Should we cache?
|
||||
if (isset($this->renderVars['options']['cache'])) {
|
||||
cache()->save($this->renderVars['cacheName'], $output, (int) $this->renderVars['options']['cache']);
|
||||
}
|
||||
|
||||
$this->tempData = null;
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ namespace ViewComponents;
|
||||
|
||||
class Component implements ComponentInterface
|
||||
{
|
||||
protected string $slot = '';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
@ -14,21 +16,33 @@ class Component implements ComponentInterface
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $properties
|
||||
* @param array<string, string> $attributes
|
||||
*/
|
||||
public function __construct(
|
||||
protected array $properties,
|
||||
array $attributes
|
||||
) {
|
||||
// overwrite default properties if set
|
||||
foreach ($properties as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
public function __construct(array $attributes)
|
||||
{
|
||||
if ($attributes !== []) {
|
||||
$this->hydrate($attributes);
|
||||
}
|
||||
// overwrite default attributes if set
|
||||
|
||||
$this->attributes = array_merge($this->attributes, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $attributes
|
||||
*/
|
||||
public function hydrate(array $attributes): void
|
||||
{
|
||||
foreach ($attributes as $name => $value) {
|
||||
$method = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $name)));
|
||||
if (is_callable([$this, $method])) {
|
||||
$this->{$method}($value);
|
||||
} else {
|
||||
$this->{$name} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return static::class . ': RENDER METHOD NOT IMPLEMENTED';
|
||||
|
@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ViewComponents;
|
||||
|
||||
use ViewComponents\Config\ViewComponents;
|
||||
use ViewComponents\Exceptions\ComponentNotFoundException;
|
||||
|
||||
class ComponentLoader
|
||||
{
|
||||
protected ViewComponents $config;
|
||||
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $properties = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $attributes = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = config('ViewComponents');
|
||||
}
|
||||
|
||||
public function __get(string $property): mixed
|
||||
{
|
||||
if (property_exists($this, $property)) {
|
||||
return $this->{$property};
|
||||
}
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
public function __set(string $property, mixed $value)
|
||||
{
|
||||
if (property_exists($this, $property)) {
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ComponentNotFoundException
|
||||
*/
|
||||
public function load(): string
|
||||
{
|
||||
// first, check if there exists a component class to load in class components path
|
||||
if (file_exists("{$this->config->classComponentsPath}/{$this->name}.php")) {
|
||||
return $this->loadComponentClass();
|
||||
}
|
||||
|
||||
// check for the existence of a view file if no component class has been found
|
||||
// component view files are camel case
|
||||
$camelCaseName = strtolower(preg_replace('~(?<!^)(?<!\/)[A-Z]~', '_$0', $this->name) ?? '');
|
||||
|
||||
if (file_exists("{$this->config->componentsViewPath}/{$camelCaseName}.php")) {
|
||||
return $this->loadComponentView($camelCaseName);
|
||||
}
|
||||
|
||||
throw new ComponentNotFoundException("Could not find component \"{$this->name}\"");
|
||||
}
|
||||
|
||||
private function loadComponentClass(): string
|
||||
{
|
||||
$classComponentsNamespace = $this->config->classComponentsNamespace;
|
||||
|
||||
$namespacedName = str_replace('/', '\\', $this->name);
|
||||
$componentClassNamespace = "{$classComponentsNamespace}\\{$namespacedName}";
|
||||
|
||||
$component = new $componentClassNamespace($this->properties, $this->attributes);
|
||||
return $component->render();
|
||||
}
|
||||
|
||||
private function loadComponentView(string $name): string
|
||||
{
|
||||
$viewData = [...$this->properties, ...$this->attributes];
|
||||
|
||||
return view("components/{$name}", $viewData);
|
||||
}
|
||||
}
|
264
app/Libraries/ViewComponents/ComponentRenderer.php
Normal file
264
app/Libraries/ViewComponents/ComponentRenderer.php
Normal file
@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ViewComponents;
|
||||
|
||||
use RuntimeException;
|
||||
use ViewComponents\Config\ViewComponents;
|
||||
|
||||
/**
|
||||
* Borrowed and adapted from https://github.com/lonnieezell/Bonfire2/
|
||||
*/
|
||||
class ComponentRenderer
|
||||
{
|
||||
protected ViewComponents $config;
|
||||
|
||||
/**
|
||||
* File name of the view source
|
||||
*/
|
||||
protected string $currentView;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = config('ViewComponents');
|
||||
}
|
||||
|
||||
public function setCurrentView(string $view): self
|
||||
{
|
||||
$this->currentView = $view;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render(string $output): string
|
||||
{
|
||||
// Try to locate any custom tags, with PascalCase names like: Button, Label, etc.
|
||||
service('timer')
|
||||
->start('self-closing');
|
||||
$output = $this->renderSelfClosingTags($output);
|
||||
service('timer')
|
||||
->stop('self-closing');
|
||||
|
||||
service('timer')
|
||||
->start('paired-tags');
|
||||
$output = $this->renderPairedTags($output);
|
||||
service('timer')
|
||||
->stop('paired-tags');
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and renders self-closing tags, i.e. <Foo />
|
||||
*/
|
||||
private function renderSelfClosingTags(string $output): string
|
||||
{
|
||||
// Pattern borrowed and adapted from Laravel's ComponentTagCompiler
|
||||
// Should match any Component tags <Component />
|
||||
$pattern = "/
|
||||
<
|
||||
\s*
|
||||
(?<name>[A-Z][A-Za-z0-9\.]*?)
|
||||
\s*
|
||||
(?<attributes>
|
||||
(?:
|
||||
\s+
|
||||
(?:
|
||||
(?:
|
||||
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
|
||||
)
|
||||
|
|
||||
(?:
|
||||
[\w\-:.@]+
|
||||
(
|
||||
=
|
||||
(?:
|
||||
\\\"[^\\\"]*\\\"
|
||||
|
|
||||
\'[^\']*\'
|
||||
|
|
||||
[^\'\\\"=<>]+
|
||||
)
|
||||
)?
|
||||
)
|
||||
)
|
||||
)*
|
||||
\s*
|
||||
)
|
||||
\/>
|
||||
/x";
|
||||
|
||||
/*
|
||||
$matches[0] = full tags matched
|
||||
$matches[name] = tag name
|
||||
$matches[attributes] = array of attribute string (class="foo")
|
||||
*/
|
||||
return preg_replace_callback($pattern, function ($match): string {
|
||||
$view = $this->locateView($match['name']);
|
||||
$attributes = $this->parseAttributes($match['attributes']);
|
||||
|
||||
$component = $this->factory($match['name'], $view, $attributes);
|
||||
|
||||
return $component instanceof Component
|
||||
? $component->render()
|
||||
: $this->renderView($view, $attributes);
|
||||
}, $output) ?? '';
|
||||
}
|
||||
|
||||
private function renderPairedTags(string $output): string
|
||||
{
|
||||
$pattern = '/<\s*(?<name>[A-Z][A-Za-z0-9\.]*?)(?<attributes>[\s\S\=\'\"]*)>(?<slot>.*)<\/\s*\1\s*>/uUsm';
|
||||
|
||||
/*
|
||||
$matches[0] = full tags matched and all of its content
|
||||
$matches[name] = pascal cased tag name
|
||||
$matches[attributes] = string of tag attributes (class="foo")
|
||||
$matches[slot] = the content inside the tags
|
||||
*/
|
||||
return preg_replace_callback($pattern, function ($match): string {
|
||||
$view = $this->locateView($match['name']);
|
||||
$attributes = $this->parseAttributes($match['attributes']);
|
||||
$attributes['slot'] = $match['slot'];
|
||||
|
||||
$component = $this->factory($match['name'], $view, $attributes);
|
||||
|
||||
return $component instanceof Component
|
||||
? $component->render()
|
||||
: $this->renderView($view, $attributes);
|
||||
}, $output) ?? (string) preg_last_error();
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the view file used to render the component. The file's name must match the name of the component.
|
||||
*
|
||||
* Looks for class and view file components in the current module before checking the default app module
|
||||
*/
|
||||
private function locateView(string $name): string
|
||||
{
|
||||
// TODO: Is there a better way to locate components local to current module?
|
||||
$modulesToDiscover = [APPPATH];
|
||||
foreach (config('Autoload')->psr4 as $namespace => $path) {
|
||||
if (str_starts_with($this->currentView, $namespace)) {
|
||||
array_unshift($modulesToDiscover, $path);
|
||||
}
|
||||
}
|
||||
|
||||
$namePath = str_replace('.', '/', $name);
|
||||
|
||||
foreach ($modulesToDiscover as $basePath) {
|
||||
// Look for a class component first
|
||||
$filePath = $basePath . $this->config->classComponentsPath . '/' . $namePath . '.php';
|
||||
|
||||
if (is_file($filePath)) {
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
$camelCaseName = strtolower(preg_replace('~(?<!^)(?<!\/)[A-Z]~', '_$0', $namePath) ?? '');
|
||||
$filePath = $basePath . $this->config->viewFileComponentsPath . '/' . $camelCaseName . '.php';
|
||||
|
||||
if (is_file($filePath)) {
|
||||
return $filePath;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("View not found for component: {$name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string to grab any key/value pairs, HTML attributes.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function parseAttributes(string $attributeString): array
|
||||
{
|
||||
// Pattern borrowed from Laravel's ComponentTagCompiler
|
||||
$pattern = '/
|
||||
(?<attribute>[\w\-:.@]+)
|
||||
(
|
||||
=
|
||||
(?<value>
|
||||
(
|
||||
\"[^\"]+\"
|
||||
|
|
||||
\'[^\']+\'
|
||||
|
|
||||
\\\'[^\\\']+\\\'
|
||||
|
|
||||
[^\s>]+
|
||||
)
|
||||
)
|
||||
)?
|
||||
/x';
|
||||
|
||||
if (! preg_match_all($pattern, $attributeString, $matches, PREG_SET_ORDER)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
/**
|
||||
* @var array<string, string> $match
|
||||
*/
|
||||
foreach ($matches as $match) {
|
||||
$attributes[$match['attribute']] = $this->stripQuotes($match['value']);
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to locate the view and/or class that will be used to render this component. By default, the only thing
|
||||
* that is needed is a view, but a Component class can also be found if more power is needed.
|
||||
*
|
||||
* If a class is used, the name is expected to be <viewName>Component.php
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
private function factory(string $name, string $view, array $attributes): ?Component
|
||||
{
|
||||
// Locate the class in the same folder as the view
|
||||
$class = $name . '.php';
|
||||
$filePath = str_replace($name . '.php', $class, $view);
|
||||
|
||||
if ($filePath === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! file_exists($filePath)) {
|
||||
return null;
|
||||
}
|
||||
$className = service('locator')
|
||||
->getClassname($filePath);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
if (! class_exists($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $className($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the view when no corresponding class has been found.
|
||||
*
|
||||
* @param array<string, string> $data
|
||||
*/
|
||||
private function renderView(string $view, array $data): string
|
||||
{
|
||||
return (function (string $view, $data): string {
|
||||
/** @phpstan-ignore-next-line */
|
||||
extract($data);
|
||||
ob_start();
|
||||
eval('?>' . file_get_contents($view));
|
||||
return ob_get_clean() ?: '';
|
||||
})($view, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes surrounding quotes from a string.
|
||||
*/
|
||||
private function stripQuotes(string $string): string
|
||||
{
|
||||
return trim($string, "\'\"");
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace ViewComponents\Config;
|
||||
|
||||
use CodeIgniter\Config\BaseService;
|
||||
use ViewComponents\ComponentLoader;
|
||||
use ViewComponents\ComponentRenderer;
|
||||
|
||||
/**
|
||||
* Services Configuration file.
|
||||
@ -19,12 +19,12 @@ use ViewComponents\ComponentLoader;
|
||||
*/
|
||||
class Services extends BaseService
|
||||
{
|
||||
public static function viewcomponents(bool $getShared = true): ComponentLoader
|
||||
public static function components(bool $getShared = true): ComponentRenderer
|
||||
{
|
||||
if ($getShared) {
|
||||
return self::getSharedInstance('viewcomponents');
|
||||
return self::getSharedInstance('components');
|
||||
}
|
||||
|
||||
return new ComponentLoader();
|
||||
return new ComponentRenderer();
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,7 @@ use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
class ViewComponents extends BaseConfig
|
||||
{
|
||||
public string $classComponentsNamespace = APP_NAMESPACE . '\View\Components';
|
||||
public string $classComponentsPath = 'View/Components';
|
||||
|
||||
public string $classComponentsPath = APPPATH . 'View/Components';
|
||||
|
||||
public string $componentsViewPath = APPPATH . 'Views/components';
|
||||
public string $viewFileComponentsPath = 'Views/components';
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
if (! function_exists('component')) {
|
||||
/**
|
||||
* Loads the specified class or view file component in the parameters
|
||||
*
|
||||
* @param array<string, array<string, mixed>> $properties
|
||||
* @param array<string, array<string, mixed>> $attributes
|
||||
*/
|
||||
function component(string $name, array $properties = [], array $attributes = []): string
|
||||
{
|
||||
$componentLoader = service('viewcomponents');
|
||||
|
||||
$componentLoader->name = $name;
|
||||
$componentLoader->properties = $properties;
|
||||
$componentLoader->attributes = $attributes;
|
||||
|
||||
return $componentLoader->load();
|
||||
}
|
||||
}
|
@ -11,9 +11,9 @@ declare(strict_types=1);
|
||||
namespace App\Models;
|
||||
|
||||
use App\Entities\Actor;
|
||||
use Modules\Fediverse\Models\ActorModel as ActivityPubActorModel;
|
||||
use Modules\Fediverse\Models\ActorModel as FediverseActorModel;
|
||||
|
||||
class ActorModel extends ActivityPubActorModel
|
||||
class ActorModel extends FediverseActorModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
@ -141,13 +141,13 @@ class EpisodeCommentModel extends UuidModel
|
||||
])
|
||||
->getCompiledSelect();
|
||||
|
||||
$episodePostsReplies = $this->db->table('activitypub_posts')
|
||||
$episodePostsReplies = $this->db->table(config('Fediverse')->tablesPrefix . 'posts')
|
||||
->select(
|
||||
'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, replies_count, published_at as created_at, created_by, 1 as is_from_post'
|
||||
)
|
||||
->whereIn('in_reply_to_id', function (BaseBuilder $builder) use (&$episodeId): BaseBuilder {
|
||||
return $builder->select('id')
|
||||
->from('activitypub_posts')
|
||||
->from(config('Fediverse')->tablesPrefix . 'posts')
|
||||
->where('episode_id', $episodeId);
|
||||
})
|
||||
->where('`created_at` <= NOW()', null, false)
|
||||
|
@ -11,9 +11,9 @@ declare(strict_types=1);
|
||||
namespace App\Models;
|
||||
|
||||
use App\Entities\Post;
|
||||
use Modules\Fediverse\Models\PostModel as ActivityPubPostModel;
|
||||
use Modules\Fediverse\Models\PostModel as FediversePostModel;
|
||||
|
||||
class PostModel extends ActivityPubPostModel
|
||||
class PostModel extends FediversePostModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
@ -25,7 +25,7 @@ class Button extends Component
|
||||
public function render(): string
|
||||
{
|
||||
$baseClass =
|
||||
'inline-flex items-center font-semibold shadow-xs rounded-full focus:outline-none focus:ring';
|
||||
'inline-flex items-center justify-center font-semibold shadow-xs rounded-full focus:outline-none focus:ring';
|
||||
|
||||
$variantClass = [
|
||||
'default' => 'text-black bg-gray-300 hover:bg-gray-400',
|
||||
@ -73,11 +73,11 @@ class Button extends Component
|
||||
}
|
||||
|
||||
if ($this->iconLeft !== '') {
|
||||
$this->label = icon($this->iconLeft, 'mr-2') . $this->label;
|
||||
$this->slot = '<Icon glyph="' . $this->iconLeft . '" class="mr-2" />' . $this->slot;
|
||||
}
|
||||
|
||||
if ($this->iconRight !== '') {
|
||||
$this->label .= icon($this->iconRight, 'ml-2');
|
||||
$this->slot .= '<Icon glyph="' . $this->iconRight . '" class="ml-2" />';
|
||||
}
|
||||
|
||||
if ($this->uri !== '') {
|
||||
@ -91,8 +91,8 @@ class Button extends Component
|
||||
];
|
||||
$attributes = stringify_attributes(array_merge($defaultButtonAttributes, $this->attributes));
|
||||
|
||||
return <<<CODE_SAMPLE
|
||||
<button class="{$buttonClass}" {$attributes}>{$this->label}</button>
|
||||
CODE_SAMPLE;
|
||||
return <<<HTML
|
||||
<button class="{$buttonClass}" {$attributes}>{$this->slot}</button>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Forms;
|
||||
|
||||
use ViewComponents\Component;
|
||||
|
||||
class Input extends Component
|
||||
{
|
||||
public function render(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
@ -14,16 +14,18 @@ class Label extends Component
|
||||
protected array $attributes = [
|
||||
'for' => '',
|
||||
'name' => '',
|
||||
'value' => '',
|
||||
'class' => '',
|
||||
];
|
||||
|
||||
protected string $text = '';
|
||||
|
||||
protected string $hint = '';
|
||||
|
||||
protected bool $isOptional = false;
|
||||
|
||||
public function setIsOptional(string $value): void
|
||||
{
|
||||
$this->isOptional = $value === 'true';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$labelClass = $this->attributes['class'];
|
||||
@ -35,8 +37,8 @@ class Label extends Component
|
||||
')</small>' : '';
|
||||
$hint = $this->hint !== '' ? hint_tooltip($this->hint, 'ml-1') : '';
|
||||
|
||||
return <<<CODE_SAMPLE
|
||||
<label class="{$labelClass}" {$attributes}>{$this->text}{$optionalText}{$hint}</label>
|
||||
CODE_SAMPLE;
|
||||
return <<<HTML
|
||||
<label class="{$labelClass}" {$attributes}>{$this->slot}{$optionalText}{$hint}</label>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ use ViewComponents\Component;
|
||||
|
||||
class MarkdownEditor extends Component
|
||||
{
|
||||
protected string $content = '';
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$editorClass = 'w-full flex flex-col bg-white border border-gray-500 focus-within:ring-1 focus-within:ring-blue-600';
|
||||
@ -64,7 +62,7 @@ class MarkdownEditor extends Component
|
||||
'</markdown-toolbar>' .
|
||||
'</header>' .
|
||||
'<div class="relative">' .
|
||||
form_textarea($this->attributes, $this->content) .
|
||||
form_textarea($this->attributes, $this->slot) .
|
||||
'<markdown-preview for="' . $this->attributes['id'] . '" class="absolute top-0 left-0 hidden w-full h-full p-2 overflow-y-auto prose bg-gray-50" showClass="bg-white"></markdown-preview>' .
|
||||
'</div>' .
|
||||
'<footer class="flex px-2 py-1 bg-gray-100 border-t">' .
|
||||
|
@ -18,6 +18,16 @@ class MultiSelect extends Component
|
||||
*/
|
||||
protected array $selected = [];
|
||||
|
||||
public function setOptions(string $value): void
|
||||
{
|
||||
$this->options = json_decode(html_entity_decode($value), true);
|
||||
}
|
||||
|
||||
public function setSelected(string $selected): void
|
||||
{
|
||||
$this->selected = json_decode($selected);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$defaultAttributes = [
|
||||
|
@ -29,21 +29,30 @@ class Toggler extends Component
|
||||
|
||||
protected bool $checked = false;
|
||||
|
||||
public function setChecked(string $value): void
|
||||
{
|
||||
$this->checked = $value !== '';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
unset($this->attributes['checked']);
|
||||
|
||||
$wrapperClass = $this->attributes['class'];
|
||||
unset($this->attributes['class']);
|
||||
|
||||
$this->attributes['class'] = 'form-switch';
|
||||
|
||||
helper('form');
|
||||
|
||||
$checkbox = form_checkbox($this->attributes, $this->attributes['value'], $this->checked);
|
||||
$hint = $this->hint !== '' ? hint_tooltip(lang('Podcast.form.lock_hint'), 'ml-1') : '';
|
||||
return <<<CODE_SAMPLE
|
||||
$hint = $this->hint === '' ? '' : hint_tooltip($this->hint, 'ml-1');
|
||||
return <<<HTML
|
||||
<label class="relative inline-flex items-center {$wrapperClass}">
|
||||
{$checkbox}
|
||||
<span class="form-switch-slider"></span>
|
||||
<span class="ml-2">{$this->label}{$hint}</span>
|
||||
<span class="ml-2">{$this->slot}{$hint}</span>
|
||||
</label>
|
||||
CODE_SAMPLE;
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
@ -8,23 +8,22 @@ use ViewComponents\Component;
|
||||
|
||||
class XMLEditor extends Component
|
||||
{
|
||||
protected string $content = '';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $attributes = [
|
||||
'slot' => 'textarea',
|
||||
'rows' => '5',
|
||||
'class' => 'textarea',
|
||||
];
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$textarea = form_textarea($this->attributes, $this->content);
|
||||
$content = $this->slot;
|
||||
$this->attributes['slot'] = 'textarea';
|
||||
$textarea = form_textarea($this->attributes, $content);
|
||||
|
||||
return <<<CODE_SAMPLE
|
||||
return <<<HTML
|
||||
<xml-editor>{$textarea}</time-ago>
|
||||
CODE_SAMPLE;
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Exception;
|
||||
use ViewComponents\Component;
|
||||
|
||||
class Icon extends Component
|
||||
@ -12,16 +13,16 @@ class Icon extends Component
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$svgContents = file_get_contents('assets/icons/' . $this->glyph . '.svg');
|
||||
|
||||
if ($svgContents) {
|
||||
if ($this->attributes['class'] !== '') {
|
||||
$svgContents = str_replace('<svg', '<svg class="' . $this->attributes['class'] . '"', $svgContents);
|
||||
}
|
||||
|
||||
return $svgContents;
|
||||
try {
|
||||
$svgContents = file_get_contents('assets/icons/' . $this->glyph . '.svg');
|
||||
} catch (Exception) {
|
||||
return '□';
|
||||
}
|
||||
|
||||
return '□';
|
||||
if ($this->attributes['class'] !== '') {
|
||||
$svgContents = str_replace('<svg', '<svg class="' . $this->attributes['class'] . '"', $svgContents);
|
||||
}
|
||||
|
||||
return $svgContents;
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,7 @@
|
||||
Sorry! Cannot seem to find the page you were looking for.
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<?= button(lang('Common.go_back'), previous_url(), [
|
||||
'variant' => 'primary',
|
||||
'iconLeft' => 'arrow-left',
|
||||
]) ?>
|
||||
<button class="inline-flex items-center justify-center px-3 py-1 text-sm font-semibold text-white rounded-full shadow-xs focus:outline-none focus:ring md:px-4 md:py-2 md:text-base bg-pine-700 hover:bg-pine-800"><?= lang('Common.go_back') ?></button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -12,4 +12,4 @@
|
||||
['class' => 'inline-flex items-center text-xs hover:underline']
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
</footer>
|
||||
|
@ -21,4 +21,4 @@
|
||||
['class' => 'inline-flex items-center text-xs hover:underline']
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
</footer>
|
||||
|
@ -12,4 +12,4 @@
|
||||
['class' => 'inline-flex items-center text-xs hover:underline']
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
</footer>
|
||||
|
@ -21,4 +21,4 @@
|
||||
['class' => 'inline-flex items-center text-xs hover:underline']
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
</footer>
|
||||
|
@ -17,4 +17,4 @@
|
||||
],
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
</footer>
|
||||
|
@ -14,4 +14,4 @@
|
||||
],
|
||||
) ?>
|
||||
</form>
|
||||
</footer>
|
||||
</footer>
|
||||
|
@ -40,4 +40,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</article>
|
||||
|
@ -30,7 +30,7 @@
|
||||
<body class="flex flex-col min-h-screen bg-pine-50">
|
||||
<header class="flex flex-col items-center mb-8">
|
||||
<h1 class="w-full pt-8 pb-32 text-center text-white bg-pine-900"><?= lang(
|
||||
'ActivityPub.follow.subtitle',
|
||||
'Fediverse.follow.subtitle',
|
||||
) ?></h1>
|
||||
<div class="flex flex-col w-full max-w-xs -mt-24 overflow-hidden bg-white shadow rounded-xl">
|
||||
<img src="<?= $actor->cover_image_url ?>" alt="" class="object-cover w-full h-32 bg-pine-800" />
|
||||
@ -54,10 +54,10 @@
|
||||
<?= view('_message_block') ?>
|
||||
|
||||
<?= form_label(
|
||||
lang('ActivityPub.your_handle'),
|
||||
lang('Fediverse.your_handle'),
|
||||
'handle',
|
||||
[],
|
||||
lang('ActivityPub.your_handle_hint'),
|
||||
lang('Fediverse.your_handle_hint'),
|
||||
) ?>
|
||||
<?= form_input([
|
||||
'id' => 'handle',
|
||||
@ -68,7 +68,7 @@
|
||||
]) ?>
|
||||
|
||||
<?= button(
|
||||
lang('ActivityPub.follow.submit'),
|
||||
lang('Fediverse.follow.submit'),
|
||||
'',
|
||||
['variant' => 'primary'],
|
||||
['type' => 'submit', 'class' => 'self-end'],
|
||||
|
@ -6,12 +6,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
|
||||
<title><?= lang('ActivityPub.' . $action . '.title', [
|
||||
<title><?= lang('Fediverse.' . $action . '.title', [
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
]) ?></title>
|
||||
<meta name="description" content="<?= $post->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang(
|
||||
'ActivityPub.' . $action . '.title',
|
||||
'Fediverse.' . $action . '.title',
|
||||
[
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
],
|
||||
@ -31,7 +31,7 @@
|
||||
<body class="min-h-screen mx-auto bg-pine-50">
|
||||
<header class="pt-8 pb-32 bg-pine-900">
|
||||
<h1 class="text-lg font-semibold text-center text-white"><?= lang(
|
||||
'ActivityPub.' . $action . '.subtitle',
|
||||
'Fediverse.' . $action . '.subtitle',
|
||||
) ?></h1>
|
||||
</header>
|
||||
<main class="flex-1 max-w-xl px-4 pb-8 mx-auto -mt-24">
|
||||
@ -45,10 +45,10 @@
|
||||
<?= view('_message_block') ?>
|
||||
|
||||
<?= form_label(
|
||||
lang('ActivityPub.your_handle'),
|
||||
lang('Fediverse.your_handle'),
|
||||
'handle',
|
||||
[],
|
||||
lang('ActivityPub.your_handle_hint'),
|
||||
lang('Fediverse.your_handle_hint'),
|
||||
) ?>
|
||||
<?= form_input([
|
||||
'id' => 'handle',
|
||||
@ -59,7 +59,7 @@
|
||||
]) ?>
|
||||
|
||||
<?= button(
|
||||
lang('ActivityPub.' . $action . '.submit'),
|
||||
lang('Fediverse.' . $action . '.submit'),
|
||||
'',
|
||||
['variant' => 'primary'],
|
||||
['type' => 'submit', 'class' => 'self-end'],
|
||||
|
@ -23,16 +23,16 @@
|
||||
"essence/essence": "^3.5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsstream": "^1.6.9",
|
||||
"phpunit/phpunit": "^9.5.8",
|
||||
"rector/rector": "^0.11.46",
|
||||
"captainhook/captainhook": "^5.10.1",
|
||||
"phpstan/phpstan": "^0.12.94",
|
||||
"mikey179/vfsstream": "^v1.6.8",
|
||||
"phpunit/phpunit": "^9.5.4",
|
||||
"rector/rector": "^0.11.5",
|
||||
"captainhook/captainhook": "^5.10.0",
|
||||
"phpstan/phpstan": "^0.12.85",
|
||||
"phpstan/extension-installer": "^1.1.0",
|
||||
"symplify/phpstan-extensions": "^9.4.27",
|
||||
"symplify/easy-coding-standard": "^9.4.27",
|
||||
"symplify/coding-standard": "^9.4.27",
|
||||
"rector/phpstan-rules": "^0.3.4"
|
||||
"rector/rector-phpstan-rules": "^0.2.9",
|
||||
"symplify/phpstan-extensions": "^v9.3.12",
|
||||
"symplify/easy-coding-standard": "^v9.3.12",
|
||||
"symplify/coding-standard": "^v9.3.12"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -59,8 +59,8 @@
|
||||
"@php vendor/opawg/user-agents-php/src/UserAgentsGenerate.php > vendor/opawg/user-agents-php/src/UserAgents.php",
|
||||
"@php vendor/opawg/user-agents-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-php/src/UserAgentsRSS.php",
|
||||
"@php vendor/podlibre/ipcat/IpDbGenerate.php > vendor/podlibre/ipcat/IpDb.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > app/Language/en/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-fr.json > app/Language/fr/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > modules/Admin/Language/en/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-fr.json > modules/Admin/Language/fr/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/ReversedTaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > vendor/podlibre/podcast-namespace/src/ReversedTaxonomy.php"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
@ -68,8 +68,8 @@
|
||||
"@php vendor/opawg/user-agents-php/src/UserAgentsGenerate.php > vendor/opawg/user-agents-php/src/UserAgents.php",
|
||||
"@php vendor/opawg/user-agents-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-php/src/UserAgentsRSS.php",
|
||||
"@php vendor/podlibre/ipcat/IpDbGenerate.php > vendor/podlibre/ipcat/IpDb.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > app/Language/en/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-fr.json > app/Language/fr/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > modules/Admin/Language/en/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-fr.json > modules/Admin/Language/fr/PersonsTaxonomy.php",
|
||||
"@php vendor/podlibre/podcast-namespace/src/ReversedTaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > vendor/podlibre/podcast-namespace/src/ReversedTaxonomy.php"
|
||||
]
|
||||
},
|
||||
|
1573
composer.lock
generated
1573
composer.lock
generated
File diff suppressed because it is too large
Load Diff
5
ecs.php
5
ecs.php
@ -22,10 +22,11 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
__DIR__ . '/modules/**/Views/*',
|
||||
|
||||
// skip specific generated files
|
||||
__DIR__ . '/app/Language/*/PersonsTaxonomy.php',
|
||||
__DIR__ . '/modules/Admin/Language/*/PersonsTaxonomy.php',
|
||||
|
||||
StandardizeHereNowDocKeywordFixer::class => [
|
||||
__DIR__ . '/app/Views/Components',
|
||||
__DIR__ . '/app/View/Components',
|
||||
__DIR__ . '/modules/**/View/Components',
|
||||
]
|
||||
]);
|
||||
|
||||
|
@ -824,9 +824,6 @@ class EpisodeController extends BaseController
|
||||
|
||||
public function attemptCommentReply(string $commentId): RedirectResponse
|
||||
{
|
||||
// var_dump($commentId);
|
||||
// die();
|
||||
|
||||
$rules = [
|
||||
'message' => 'required|max_length[500]',
|
||||
];
|
||||
|
@ -55,7 +55,10 @@ class PodcastPlatformController extends BaseController
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
]);
|
||||
return view('Modules\Admin\Views\podcast\platforms', $data);
|
||||
|
||||
$view = view('Modules\Admin\Views\podcast\platforms', $data);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
public function attemptPlatformsUpdate(string $platformType): RedirectResponse
|
||||
|
@ -15,8 +15,8 @@
|
||||
'class' => 'flex flex-col max-w-sm',
|
||||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<?= component('Forms/Label', ['text' => lang('Contributor.form.user')], ['for' => 'user']) ?>
|
||||
|
||||
<Forms.Label for="user"><?= lang('Contributor.form.user') ?></Forms.Label>
|
||||
<?= form_dropdown('user', $userOptions, [old('user', '')], [
|
||||
'id' => 'user',
|
||||
'class' => 'form-select mb-4',
|
||||
@ -24,7 +24,7 @@
|
||||
'placeholder' => lang('Contributor.form.user_placeholder')
|
||||
]) ?>
|
||||
|
||||
<?= component('Forms/Label', ['text' => lang('Contributor.form.role')], ['for' => 'role']) ?>
|
||||
<Forms.Label for="role"><?= lang('Contributor.form.role') ?></Forms.Label>
|
||||
<?= form_dropdown('role', $roleOptions, [old('role', '')], [
|
||||
'id' => 'role',
|
||||
'class' => 'form-select mb-4',
|
||||
|
@ -16,7 +16,7 @@
|
||||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<?= component('Forms/Label', ['text' => lang('Contributor.form.role')], ['for' => 'role']) ?>
|
||||
<Forms.Label for="role"><?= lang('Contributor.form.role') ?></Forms.Label>
|
||||
<?= form_dropdown('role', $roleOptions, [old('role', $contributorGroupId)], [
|
||||
'id' => 'role',
|
||||
'class' => 'form-select mb-4',
|
||||
|
@ -28,11 +28,7 @@
|
||||
lang('Episode.form.info_section_subtitle'),
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' => lang('Episode.form.audio_file'), 'hint' => lang('Episode.form.audio_file_hint')],
|
||||
['for' => 'audio_file'],
|
||||
) ?>
|
||||
<Forms.Label for="audio_file" hint="<?= lang('Episode.form.audio_file_hint') ?>"><?= lang('Episode.form.audio_file') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'audio_file',
|
||||
'name' => 'audio_file',
|
||||
@ -42,15 +38,7 @@
|
||||
'accept' => '.mp3,.m4a',
|
||||
]) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.image'),
|
||||
'hint' => lang('Episode.form.image_hint'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'image'],
|
||||
) ?>
|
||||
<Forms.Label for="image" hint="<?= lang('Episode.form.image_hint') ?>" isOptional="true"><?= lang('Episode.form.image') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'image',
|
||||
'name' => 'image',
|
||||
@ -62,12 +50,7 @@
|
||||
'Common.forms.image_size_hint',
|
||||
) ?></small>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' => lang('Episode.form.title'),
|
||||
'hint' => lang('Episode.form.title_hint')],
|
||||
['for' => 'title'],
|
||||
) ?>
|
||||
<Forms.Label for="title" hint="<?= lang('Episode.form.title_hint') ?>"><?= lang('Episode.form.title') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'title',
|
||||
'name' => 'title',
|
||||
@ -77,11 +60,7 @@
|
||||
'data-slugify' => 'title',
|
||||
]) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' => lang('Episode.form.permalink')],
|
||||
['for' => 'slug']
|
||||
) ?>
|
||||
<Forms.Label for="slug"><?= lang('Episode.form.permalink') ?></Forms.Label>
|
||||
<permalink-edit class="inline-flex items-center mb-4 text-xs" edit-label="<?= lang('Common.edit') ?>" copy-label="<?= lang('Common.copy') ?>" copied-label="<?= lang('Common.copied') ?>">
|
||||
<span slot="domain"><?= base_url('/@'. $podcast->handle . '/episodes' ) . '/' ?></span>
|
||||
<?= form_input([
|
||||
@ -97,7 +76,7 @@
|
||||
|
||||
<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row">
|
||||
<div class="flex flex-col flex-1">
|
||||
<?= component('Forms/Label', ['text' => lang('Episode.form.season_number')], ['for' => 'season_number']) ?>
|
||||
<Forms.Label for="season_number"><?= lang('Episode.form.season_number') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'season_number',
|
||||
'name' => 'season_number',
|
||||
@ -107,7 +86,7 @@
|
||||
]) ?>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1">
|
||||
<?= component('Forms/Label', ['text' => lang('Episode.form.episode_number')], ['for' => 'episode_number']) ?>
|
||||
<Forms.Label for="episode_number"><?= lang('Episode.form.episode_number') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'episode_number',
|
||||
'name' => 'episode_number',
|
||||
@ -204,46 +183,13 @@
|
||||
) ?>
|
||||
|
||||
<div class="mb-4">
|
||||
<?= component('Forms/Label', ['text' => lang('Episode.form.description')], ['for' => 'description']) ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old('description', '', false),
|
||||
],
|
||||
[
|
||||
'id' => 'description',
|
||||
'name' => 'description',
|
||||
'required' => 'required',
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label for="description"><?= lang('Episode.form.description') ?></Forms.Label>
|
||||
<Forms.MarkdownEditor id="description" name="description" required="required"><?= old('description', '', false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.description_footer'),
|
||||
'hint' => lang('Episode.form.description_footer_hint'),
|
||||
'isOptional' => true
|
||||
],
|
||||
[
|
||||
'for' => 'description_footer'
|
||||
],
|
||||
) ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old(
|
||||
'description_footer',
|
||||
$podcast->episode_description_footer_markdown ?? '',
|
||||
false,
|
||||
),
|
||||
],
|
||||
[
|
||||
'id' => 'description_footer',
|
||||
'name' => 'description_footer',
|
||||
'rows' => 6
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label for="description_footer" hint="<?= lang('Episode.form.description_footer_hint') ?>" isOptional="true"><?= lang('Episode.form.description_footer') ?></Forms.Label>
|
||||
<Forms.MarkdownEditor id="description_footer" name="description_footer" rows="6"><?= old('description_footer', $podcast->episode_description_footer_markdown ?? '', false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
@ -253,14 +199,7 @@
|
||||
lang('Episode.form.location_section_subtitle'),
|
||||
) ?>
|
||||
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.location_name'),
|
||||
'hint' => lang('Episode.form.location_name_hint'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'location_name'],
|
||||
) ?>
|
||||
<Forms.Label for="location_name" hint="<?= lang('Episode.form.location_name_hint') ?>" isOptional="true"><?= lang('Episode.form.location_name') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'location_name',
|
||||
'name' => 'location_name',
|
||||
@ -301,13 +240,7 @@
|
||||
|
||||
<div class="py-2 tab-panels">
|
||||
<section id="transcript-file-upload" class="flex items-center tab-panel">
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.transcript_file'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'transcript_file', 'class' => 'sr-only'],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="transcript_file" isOptional="true"><?= lang('Episode.form.transcript_file') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'transcript_file',
|
||||
'name' => 'transcript_file',
|
||||
@ -317,13 +250,7 @@
|
||||
]) ?>
|
||||
</section>
|
||||
<section id="transcript-file-remote-url" class="tab-panel">
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.transcript_file_remote_url'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'transcript_file_remote_url', 'class' => 'sr-only'],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="transcript_file_remote_url" isOptional="true"><?= lang('Episode.form.transcript_file_remote_url') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'transcript_file_remote_url',
|
||||
'name' => 'transcript_file_remote_url',
|
||||
@ -364,13 +291,7 @@
|
||||
|
||||
<div class="py-2 tab-panels">
|
||||
<section id="chapters-file-upload" class="flex items-center tab-panel">
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.chapters_file'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'chapters_file', 'class' => 'sr-only'],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="chapters_file" isOptional="true"><?= lang('Episode.form.chapters_file') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'chapters_file',
|
||||
'name' => 'chapters_file',
|
||||
@ -380,16 +301,7 @@
|
||||
]) ?>
|
||||
</section>
|
||||
<section id="chapters-file-remote-url" class="tab-panel">
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.chapters_file_remote_url'),
|
||||
'isOptional' => true
|
||||
],
|
||||
[
|
||||
'for' => 'chapters_file_remote_url',
|
||||
'class' => 'sr-only'
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="chapters_file_remote_url" isOptional="true"><?= lang('Episode.form.chapters_file_remote_url') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'chapters_file_remote_url',
|
||||
'name' => 'chapters_file_remote_url',
|
||||
@ -409,38 +321,12 @@
|
||||
lang('Episode.form.advanced_section_title'),
|
||||
lang('Episode.form.advanced_section_subtitle'),
|
||||
) ?>
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.custom_rss'),
|
||||
'hint' => lang('Episode.form.custom_rss_hint'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'custom_rss']
|
||||
) ?>
|
||||
<?= component('Forms/XMLEditor',
|
||||
[
|
||||
'content' => old('custom_rss', '')
|
||||
],
|
||||
[
|
||||
'id' => 'custom_rss',
|
||||
'name' => 'custom_rss',
|
||||
]
|
||||
) ?>
|
||||
<Forms.Label for="custom_rss" hint="<?= lang('Episode.form.custom_rss_hint') ?>" isOptional="true"><?= lang('Episode.form.custom_rss') ?></Forms.Label>
|
||||
<Forms.XMLEditor id="custom_rss" name="custom_rss"><?= old('custom_rss', '', false) ?></Forms.XMLEditor>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Episode.form.block'),
|
||||
'hint' => lang('Episode.form.block_hint')
|
||||
],
|
||||
[
|
||||
'id' => 'block',
|
||||
'name' => 'block',
|
||||
'value' => 'yes',
|
||||
'checked' => old('block', false),
|
||||
]
|
||||
) ?>
|
||||
<Forms.Toggler id="block" name="block" value="yes" checked="<?= old('block', false) ?>" hint="<?= lang('Episode.form.block_hint') ?>"><?= lang('Episode.form.block') ?></Forms.Toggler>
|
||||
|
||||
<?= button(
|
||||
lang('Episode.form.submit_create'),
|
||||
|
@ -35,12 +35,7 @@
|
||||
/>',
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' =>
|
||||
lang('Episode.form.audio_file'), 'hint' => lang('Episode.form.audio_file_hint'),],
|
||||
['for' => 'audio_file'],
|
||||
) ?>
|
||||
<Forms.Label for="audio_file" hint="<?= lang('Episode.form.audio_file_hint') ?>"><?= lang('Episode.form.audio_file') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'audio_file',
|
||||
'name' => 'audio_file',
|
||||
@ -49,14 +44,7 @@
|
||||
'accept' => '.mp3,.m4a',
|
||||
]) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' =>
|
||||
lang('Episode.form.image'), 'hint' => lang('Episode.form.image_hint'), 'isOptional' => true,],
|
||||
['for' =>
|
||||
'image',]
|
||||
) ?>
|
||||
|
||||
<Forms.Label for="image" hint="<?= lang('Episode.form.image_hint') ?>" isOptional="true"><?= lang('Episode.form.image') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'image',
|
||||
'name' => 'image',
|
||||
@ -68,13 +56,7 @@
|
||||
'Common.forms.image_size_hint',
|
||||
) ?></small>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' =>
|
||||
lang('Episode.form.title'), 'hint' => lang('Episode.form.title_hint'),],
|
||||
['for' =>
|
||||
'title',]
|
||||
) ?>
|
||||
<Forms.Label for="title" hint="<?= lang('Episode.form.title_hint') ?>"><?= lang('Episode.form.title') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'title',
|
||||
'name' => 'title',
|
||||
@ -84,13 +66,7 @@
|
||||
'data-slugify' => 'title',
|
||||
]) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.permalink')
|
||||
],
|
||||
['for' => 'slug',]
|
||||
) ?>
|
||||
<Forms.Label for="slug"><?= lang('Episode.form.permalink') ?></Forms.Label>
|
||||
<permalink-edit class="inline-flex items-center mb-4 text-xs" edit-label="<?= lang('Common.edit') ?>" copy-label="<?= lang('Common.copy') ?>" copied-label="<?= lang('Common.copied') ?>">
|
||||
<span slot="domain"><?= base_url('/@' . $podcast->handle . '/episodes') . '/' ?></span>
|
||||
<?= form_input([
|
||||
@ -106,7 +82,7 @@
|
||||
|
||||
<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row">
|
||||
<div class="flex flex-col flex-1">
|
||||
<?= component('Forms/Label', ['text' => lang('Episode.form.season_number')], ['for' => 'season_number']) ?>
|
||||
<Forms.Label for="season_number"><?= lang('Episode.form.season_number') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'season_number',
|
||||
'name' => 'season_number',
|
||||
@ -116,7 +92,7 @@
|
||||
]) ?>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1">
|
||||
<?= component('Forms/Label', ['text' => lang('Episode.form.episode_number')], ['for' => 'episode_number']) ?>
|
||||
<Forms.Label for="episode_number"><?= lang('Episode.form.episode_number') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'episode_number',
|
||||
'name' => 'episode_number',
|
||||
@ -216,44 +192,13 @@
|
||||
) ?>
|
||||
|
||||
<div class="mb-4">
|
||||
<?= component('Forms/Label', ['text' => lang('Episode.form.description')], ['for' => 'description']) ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old('description', $episode->description_markdown, false),
|
||||
],
|
||||
[
|
||||
'id' => 'description',
|
||||
'name' => 'description',
|
||||
'required' => 'required',
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label for="description"><?= lang('Episode.form.description') ?></Forms.Label>
|
||||
<Forms.MarkdownEditor id="description" name="description" required="required"><?= old('description', $episode->description_markdown, false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<?= component('Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.description_footer'),
|
||||
'hint' => lang('Episode.form.description_footer_hint'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'description_footer'],
|
||||
) ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old(
|
||||
'description_footer',
|
||||
$podcast->episode_description_footer_markdown ?? '',
|
||||
false,
|
||||
),
|
||||
],
|
||||
[
|
||||
'id' => 'description_footer',
|
||||
'name' => 'description_footer',
|
||||
'rows' => 6
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label for="description_footer" hint="<?= lang('Episode.form.description_footer_hint') ?>" isOptional="true"><?= lang('Episode.form.description_footer') ?></Forms.Label>
|
||||
<Forms.MarkdownEditor id="description_footer" name="description_footer" rows="6"><?= old('description_footer', $podcast->episode_description_footer_markdown ?? '', false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
@ -263,14 +208,7 @@
|
||||
lang('Episode.form.location_section_subtitle'),
|
||||
) ?>
|
||||
|
||||
<?= component('Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.location_name'),
|
||||
'hint' => lang('Episode.form.location_name_hint'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'location_name']
|
||||
) ?>
|
||||
<Forms.Label for="location_name" hint="<?= lang('Episode.form.location_name_hint') ?>" isOptional="true"><?= lang('Episode.form.location_name') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'location_name',
|
||||
'name' => 'location_name',
|
||||
@ -342,13 +280,7 @@
|
||||
) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.transcript_file'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'transcript_file', 'class' => 'sr-only'],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="transcript_file" isOptional="true"><?= lang('Episode.form.transcript_file') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'transcript_file',
|
||||
'name' => 'transcript_file',
|
||||
@ -358,13 +290,7 @@
|
||||
]) ?>
|
||||
</section>
|
||||
<section id="transcript-file-remote-url" class="tab-panel">
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.transcript_file_remote_url'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'transcript_file_remote_url', 'class' => 'sr-only'],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="transcript_file_remote_url" isOptional="true"><?= lang('Episode.form.transcript_file_remote_url') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'transcript_file_remote_url',
|
||||
'name' => 'transcript_file_remote_url',
|
||||
@ -434,13 +360,7 @@
|
||||
) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.chapters_file'),
|
||||
'isOptional' => true
|
||||
],
|
||||
['for' => 'chapters_file', 'class' => 'sr-only'],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="chapters_file" isOptional="true"><?= lang('Episode.form.chapters_file') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'chapters_file',
|
||||
'name' => 'chapters_file',
|
||||
@ -450,16 +370,7 @@
|
||||
]) ?>
|
||||
</section>
|
||||
<section id="chapters-file-remote-url" class="tab-panel">
|
||||
<?= component( 'Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.chapters_file_remote_url'),
|
||||
'isOptional' => true
|
||||
],
|
||||
[
|
||||
'for' => 'chapters_file_remote_url',
|
||||
'class' => 'sr-only'
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label class="sr-only" for="chapters_file_remote_url" isOptional="true"><?= lang('Episode.form.chapters_file_remote_url') ?></Forms.Label>
|
||||
<?= form_input([
|
||||
'id' => 'chapters_file_remote_url',
|
||||
'name' => 'chapters_file_remote_url',
|
||||
@ -482,40 +393,12 @@
|
||||
lang('Episode.form.advanced_section_title'),
|
||||
lang('Episode.form.advanced_section_subtitle'),
|
||||
) ?>
|
||||
<?= component('Forms/Label',
|
||||
[
|
||||
'text' => lang('Episode.form.custom_rss'),
|
||||
'hint' => lang('Episode.form.custom_rss_hint'),
|
||||
'isOptional' => true,
|
||||
],
|
||||
[
|
||||
'for' => 'custom_rss',
|
||||
]
|
||||
) ?>
|
||||
<?= component('Forms/XMLEditor',
|
||||
[
|
||||
'content' => old('custom_rss', $episode->custom_rss_string)
|
||||
],
|
||||
[
|
||||
'id' => 'custom_rss',
|
||||
'name' => 'custom_rss',
|
||||
]
|
||||
) ?>
|
||||
<Forms.Label for="custom_rss" hint="<?= lang('Episode.form.custom_rss_hint') ?>" isOptional="true"><?= lang('Episode.form.custom_rss') ?></Forms.Label>
|
||||
<Forms.XMLEditor id="custom_rss" name="custom_rss"><?= old('custom_rss', $episode->custom_rss_string, false) ?></Forms.XMLEditor>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Episode.form.block'),
|
||||
'hint' => lang('Episode.form.block_hint')
|
||||
],
|
||||
[
|
||||
'id' => 'block',
|
||||
'name' => 'block',
|
||||
'value' => 'yes',
|
||||
'checked' => old('block', $episode->is_blocked),
|
||||
]
|
||||
) ?>
|
||||
<Forms.Toggler id="block" name="block" value="yes" checked="<?= old('block', $episode->is_blocked) ?>" hint="<?= lang('Episode.form.block_hint') ?>"><?= lang('Episode.form.block') ?></Forms.Toggler>
|
||||
|
||||
<?= button(
|
||||
lang('Episode.form.submit_edit'),
|
||||
|
@ -95,28 +95,11 @@
|
||||
lang('Person.episode_form.add_section_subtitle'),
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' => lang('Person.episode_form.persons'), 'hint' => lang('Person.episode_form.persons_hint')],
|
||||
['for' => 'persons'],
|
||||
) ?>
|
||||
<?= component('Forms/MultiSelect', ['options' => $personOptions, 'selected' => old('persons', [])], [
|
||||
'id' => 'persons',
|
||||
'name' => 'persons[]',
|
||||
'class' => 'mb-4',
|
||||
'required' => 'required',
|
||||
]) ?>
|
||||
<Forms.Label for="persons" hint="<?= lang('Person.episode_form.persons_hint') ?>"><?= lang('Person.episode_form.persons') ?></Forms.Label>
|
||||
<Forms.MultiSelect id="persons" name="persons[]" class="mb-4" required="required" options="<?= htmlspecialchars(json_encode($personOptions)) ?>" selected="<?= htmlspecialchars(json_encode(old('persons', []))) ?>"/>
|
||||
|
||||
<?= component(
|
||||
'Forms/Label',
|
||||
['text' => lang('Person.episode_form.roles'), 'hint' => lang('Person.episode_form.roles_hint'), 'isOptional' => true],
|
||||
['for' => 'roles'],
|
||||
) ?>
|
||||
<?= component('Forms/MultiSelect', ['options' => $taxonomyOptions, 'selected' => old('roles', [])], [
|
||||
'id' => 'roles',
|
||||
'name' => 'roles[]',
|
||||
'class' => 'mb-4',
|
||||
]) ?>
|
||||
<Forms.Label for="roles" hint="<?= lang('Person.episode_form.roles_hint') ?>" isOptional="true"><?= lang('Person.episode_form.roles') ?></Forms.Label>
|
||||
<Forms.MultiSelect id="roles" name="roles[]" class="mb-4" options="<?= htmlspecialchars(json_encode($taxonomyOptions)) ?>" selected="<?= htmlspecialchars(json_encode(old('roles', []))) ?>"/>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
<?= button(
|
||||
|
@ -189,4 +189,4 @@
|
||||
|
||||
<?= form_close() ?>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
@ -46,18 +46,7 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<?= form_label(lang('Page.form.content'), 'content') ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old('content', '', false)
|
||||
],
|
||||
[
|
||||
'id' => 'content',
|
||||
'name' => 'content',
|
||||
'required' => 'required',
|
||||
'rows' => 20
|
||||
],
|
||||
) ?>
|
||||
<Forms.MarkdownEditor id="content" name="content" required="required" rows="20"><?= old('content', '', false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -46,17 +46,7 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<?= form_label(lang('Page.form.content'), 'content') ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old('content', $page->content_markdown, false),
|
||||
],
|
||||
[
|
||||
'id' => 'content',
|
||||
'name' => 'content',
|
||||
'required' => 'required',
|
||||
],
|
||||
) ?>
|
||||
<Forms.MarkdownEditor id="content" name="content" required="required"><?= old('content', $page->content_markdown, false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
<?= button(
|
||||
|
@ -1,3 +1,6 @@
|
||||
<?php
|
||||
?>
|
||||
|
||||
<?= $this->extend('Modules\Admin\Views\_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
@ -81,18 +84,8 @@
|
||||
<?= form_fieldset_close() ?>
|
||||
|
||||
<div class="mb-4">
|
||||
<?= form_label(lang('Podcast.form.description'), 'description') ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old('description', '', false),
|
||||
],
|
||||
[
|
||||
'id' => 'description',
|
||||
'name' => 'description',
|
||||
'required' => 'required',
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label for="description"><?= lang('Podcast.form.description') ?></Forms.Label>
|
||||
<Forms.MarkdownEditor id="description" name="description" required="required"><?= old('description', '', false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
@ -125,12 +118,13 @@
|
||||
'',
|
||||
true,
|
||||
) ?>
|
||||
<?= component('Forms/MultiSelect', ['options' => $categoryOptions, 'selected' => old('other_categories', [])], [
|
||||
'id' => 'other_categories',
|
||||
'name' => 'other_categories[]',
|
||||
'class' => 'mb-4',
|
||||
'data-max-item-count' => '2',
|
||||
]) ?>
|
||||
<Forms.MultiSelect
|
||||
id="other_categories"
|
||||
name="other_categories[]"
|
||||
class="mb-4"
|
||||
data-max-item-count="2"
|
||||
selected="<?= htmlspecialchars(json_encode(old('other_categories', []))) ?>"
|
||||
options="<?= htmlspecialchars(json_encode($categoryOptions)) ?>" />
|
||||
|
||||
<?= form_fieldset('', ['class' => 'mb-4']) ?>
|
||||
<legend>
|
||||
@ -339,15 +333,8 @@
|
||||
lang('Podcast.form.custom_rss_hint'),
|
||||
true,
|
||||
) ?>
|
||||
<?= component('Forms/XMLEditor',
|
||||
[
|
||||
'content' => old('custom_rss', '')
|
||||
],
|
||||
[
|
||||
'id' => 'custom_rss',
|
||||
'name' => 'custom_rss',
|
||||
]
|
||||
) ?>
|
||||
<Forms.XMLEditor id="custom_rss" name="custom_rss"><?= old('custom_rss', '', false) ?></Forms.XMLEditor>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= form_section(
|
||||
@ -355,47 +342,15 @@
|
||||
lang('Podcast.form.status_section_subtitle'),
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Podcast.form.lock'),
|
||||
'hint' => lang('Podcast.form.lock_hint'),
|
||||
],
|
||||
[
|
||||
'id' => 'lock',
|
||||
'name' => 'lock',
|
||||
'value' => 'yes',
|
||||
'checked' => old('complete', true),
|
||||
'class' => 'mb-2'
|
||||
]
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Podcast.form.block'),
|
||||
],
|
||||
[
|
||||
'id' => 'block',
|
||||
'name' => 'block',
|
||||
'value' => 'yes',
|
||||
'checked' => old('block', false),
|
||||
'class' => 'mb-2'
|
||||
]
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Podcast.form.complete'),
|
||||
],
|
||||
[
|
||||
'id' => 'complete',
|
||||
'name' => 'complete',
|
||||
'value' => 'yes',
|
||||
'checked' => old('complete', false),
|
||||
]
|
||||
) ?>
|
||||
<Forms.Toggler class="mb-2" id="lock" name="lock" value="yes" checked="<?= old('complete', true) ?>" hint="<?= lang('Podcast.form.lock_hint') ?>">
|
||||
<?= lang('Podcast.form.lock') ?>
|
||||
</Forms.Toggler>
|
||||
<Forms.Toggler class="mb-2" id="block" name="block" value="yes" checked="<?= old('complete', false) ?>">
|
||||
<?= lang('Podcast.form.block') ?>
|
||||
</Forms.Toggler>
|
||||
<Forms.Toggler id="complete" name="complete" value="yes" checked="<?= old('complete', false) ?>">
|
||||
<?= lang('Podcast.form.complete') ?>
|
||||
</Forms.Toggler>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
@ -406,7 +361,6 @@
|
||||
['type' => 'submit', 'class' => 'self-end'],
|
||||
) ?>
|
||||
|
||||
|
||||
<?= form_close() ?>
|
||||
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
<?php
|
||||
?>
|
||||
|
||||
<?= $this->extend('Modules\Admin\Views\_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
@ -11,7 +14,7 @@
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<?= form_open_multipart(route_to('podcast-edit', $podcast->id), [
|
||||
<?= form_open_multipart((string) route_to('podcast-edit', $podcast->id), [
|
||||
'method' => 'post',
|
||||
'class' => 'flex flex-col',
|
||||
]) ?>
|
||||
@ -24,7 +27,6 @@
|
||||
) ?>
|
||||
|
||||
<?= form_label(lang('Podcast.form.image'), 'image') ?>
|
||||
|
||||
<img src="<?= $podcast->image->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="object-cover w-32 h-32" />
|
||||
<?= form_input([
|
||||
'id' => 'image',
|
||||
@ -71,18 +73,8 @@
|
||||
<?= form_fieldset_close() ?>
|
||||
|
||||
<div class="mb-4">
|
||||
<?= form_label(lang('Podcast.form.description'), 'description') ?>
|
||||
<?= component(
|
||||
'Forms/MarkdownEditor',
|
||||
[
|
||||
'content' => old('description', $podcast->description_markdown, false)
|
||||
],
|
||||
[
|
||||
'id' => 'description',
|
||||
'name' => 'description',
|
||||
'required' => 'required',
|
||||
],
|
||||
) ?>
|
||||
<Forms.Label for="description"><?= lang('Podcast.form.description') ?></Forms.Label>
|
||||
<Forms.MarkdownEditor id="description" name="description" required="required"><?= old('description', $podcast->description_markdown, false) ?></Forms.MarkdownEditor>
|
||||
</div>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
@ -125,12 +117,13 @@ lang('Podcast.form.classification_section_subtitle'),
|
||||
true,
|
||||
) ?>
|
||||
|
||||
<?= component('Forms/MultiSelect', ['options' => $categoryOptions, 'selected' => old('other_categories', $podcast->other_categories_ids)], [
|
||||
'id' => 'other_categories',
|
||||
'name' => 'other_categories[]',
|
||||
'class' => 'mb-4',
|
||||
'data-max-item-count' => '2',
|
||||
]) ?>
|
||||
<Forms.MultiSelect
|
||||
id="other_categories"
|
||||
name="other_categories[]"
|
||||
class="mb-4"
|
||||
data-max-item-count="2"
|
||||
selected="<?= json_encode(old('other_categories', $podcast->other_categories_ids)) ?>"
|
||||
options="<?= htmlspecialchars(json_encode($categoryOptions)) ?>" />
|
||||
|
||||
<?= form_fieldset('', ['class' => 'mb-4']) ?>
|
||||
<legend><?= lang('Podcast.form.parental_advisory.label') .
|
||||
@ -349,16 +342,7 @@ lang('Podcast.form.classification_section_subtitle'),
|
||||
lang('Podcast.form.custom_rss_hint'),
|
||||
true,
|
||||
) ?>
|
||||
|
||||
<?= component('Forms/XMLEditor',
|
||||
[
|
||||
'content' => old('custom_rss', $podcast->custom_rss_string)
|
||||
],
|
||||
[
|
||||
'id' => 'custom_rss',
|
||||
'name' => 'custom_rss',
|
||||
]
|
||||
) ?>
|
||||
<Forms.XMLEditor id="custom_rss" name="custom_rss"><?= old('custom_rss', $podcast->custom_rss_string, false) ?></Forms.XMLEditor>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
@ -367,61 +351,21 @@ lang('Podcast.form.classification_section_subtitle'),
|
||||
lang('Podcast.form.status_section_subtitle'),
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Podcast.form.lock'),
|
||||
'hint' => lang('Podcast.form.lock_hint'),
|
||||
],
|
||||
[
|
||||
'id' => 'lock',
|
||||
'name' => 'lock',
|
||||
'value' => 'yes',
|
||||
'checked' => old('complete', $podcast->is_locked),
|
||||
'class' => 'mb-2'
|
||||
]
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Podcast.form.block'),
|
||||
],
|
||||
[
|
||||
'id' => 'block',
|
||||
'name' => 'block',
|
||||
'value' => 'yes',
|
||||
'checked' => old('block', $podcast->is_blocked),
|
||||
'class' => 'mb-2'
|
||||
]
|
||||
) ?>
|
||||
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Podcast.form.complete'),
|
||||
],
|
||||
[
|
||||
'id' => 'complete',
|
||||
'name' => 'complete',
|
||||
'value' => 'yes',
|
||||
'checked' => old('complete', $podcast->is_completed),
|
||||
]
|
||||
) ?>
|
||||
<Forms.Toggler class="mb-2" id="lock" name="lock" value="yes" checked="<?= old('complete', $podcast->is_locked) ?>" hint="<?= lang('Podcast.form.lock_hint') ?>">
|
||||
<?= lang('Podcast.form.lock') ?>
|
||||
</Forms.Toggler>
|
||||
<Forms.Toggler class="mb-2" id="block" name="block" value="yes" checked="<?= old('complete', $podcast->is_blocked) ?>">
|
||||
<?= lang('Podcast.form.block') ?>
|
||||
</Forms.Toggler>
|
||||
<Forms.Toggler id="complete" name="complete" value="yes" checked="<?= old('complete', $podcast->is_completed) ?>">
|
||||
<?= lang('Podcast.form.complete') ?>
|
||||
</Forms.Toggler>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= component(
|
||||
'Button',
|
||||
[
|
||||
'label' => lang('Podcast.form.submit_edit'),
|
||||
'variant' => 'primary',
|
||||
],
|
||||
[
|
||||
'type' => 'submit',
|
||||
'class' => 'self-end'
|
||||
]
|
||||
) ?>
|
||||
<Button variant="primary" type="submit" class="self-end" iconLeft="heart">
|
||||
<?= lang('Podcast.form.submit_edit') ?>
|
||||
</Button>
|
||||
|
||||
<?= form_close() ?>
|
||||
|
||||
|
@ -99,13 +99,7 @@
|
||||
[],
|
||||
lang('Person.podcast_form.persons_hint'),
|
||||
) ?>
|
||||
<?= component('Forms/MultiSelect', ['options' => $personOptions, 'selected' => old('persons', [])], [
|
||||
'id' => 'persons',
|
||||
'name' => 'persons[]',
|
||||
'class' => 'mb-4',
|
||||
'required' => 'required',
|
||||
'data-max-item-count' => '2',
|
||||
]) ?>
|
||||
<Forms.MultiSelect id="persons" name="persons[]" class="mb-4" required="required" options="<?= htmlspecialchars(json_encode($personOptions)) ?>" selected="<?= htmlspecialchars(json_encode(old('persons', []))) ?>"/>
|
||||
|
||||
<?= form_label(
|
||||
lang('Person.podcast_form.roles'),
|
||||
@ -114,11 +108,7 @@
|
||||
lang('Person.podcast_form.roles_hint'),
|
||||
true,
|
||||
) ?>
|
||||
<?= component('Forms/MultiSelect', ['options' => $taxonomyOptions, 'selected' => old('roles', [])], [
|
||||
'id' => 'roles',
|
||||
'name' => 'roles[]',
|
||||
'class' => 'mb-4',
|
||||
]) ?>
|
||||
<Forms.MultiSelect id="roles" name="roles[]" class="mb-4" options="<?= htmlspecialchars(json_encode($taxonomyOptions)) ?>" selected="<?= htmlspecialchars(json_encode(old('roles', []))) ?>"/>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
<?= button(
|
||||
|
@ -103,40 +103,8 @@
|
||||
'type' => 'text',
|
||||
'placeholder' => lang("Platforms.description.{$platform->type}"),
|
||||
]) ?>
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Platforms.visible'),
|
||||
],
|
||||
[
|
||||
'id' => $platform->slug . '_visible',
|
||||
'name' => 'platforms[' . $platform->slug . '][visible]',
|
||||
'value' => 'yes',
|
||||
'checked' => old(
|
||||
$platform->slug . '_visible',
|
||||
$platform->is_visible ? $platform->is_visible : false,
|
||||
),
|
||||
'class' => 'text-sm mb-1'
|
||||
]
|
||||
) ?>
|
||||
<?= component(
|
||||
'Forms/Toggler',
|
||||
[
|
||||
'label' => lang('Platforms.on_embeddable_player'),
|
||||
],
|
||||
[
|
||||
'id' => $platform->slug . '_on_embeddable_player',
|
||||
'name' => 'platforms[' . $platform->slug . '][on_embeddable_player]',
|
||||
'value' => 'yes',
|
||||
'checked' => old(
|
||||
$platform->slug . '_on_embeddable_player',
|
||||
$platform->is_on_embeddable_player
|
||||
? $platform->is_on_embeddable_player
|
||||
: false,
|
||||
),
|
||||
'class' => 'text-sm'
|
||||
]
|
||||
) ?>
|
||||
<Forms.Toggler class="mb-1 text-sm" id="<?= $platform->slug . '_visible' ?>" name="<?= 'platforms[' . $platform->slug . '][visible]'?>" value="yes" checked="<?= old($platform->slug . '_visible', $platform->is_visible ? $platform->is_visible : false ) ?>"><?= lang('Platforms.visible') ?></Forms.Toggler>
|
||||
<Forms.Toggler class="text-sm" id="<?= $platform->slug . '_on_embeddable_player' ?>" name="<?= 'platforms[' . $platform->slug . '][on_embeddable_player]'?>" value="yes" checked="<?= old($platform->slug . '_on_embeddable_player', $platform->is_on_embeddable_player ? $platform->is_on_embeddable_player : false ) ?>"><?= lang('Platforms.on_embeddable_player') ?></Forms.Toggler>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -17,11 +17,7 @@
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<?= form_label(lang('User.form.roles'), 'roles') ?>
|
||||
<?= component('Forms/MultiSelect', ['options' => $roleOptions, 'selected' => $user->roles], [
|
||||
'id' => 'roles',
|
||||
'name' => 'roles[]',
|
||||
'class' => 'mb-4',
|
||||
]) ?>
|
||||
<Forms.MultiSelect id="roles" name="roles[]" class="mb-4" required="required" options="<?= htmlspecialchars(json_encode($roleOptions)) ?>" selected="<?= htmlspecialchars(json_encode($user->roles)) ?>"/>
|
||||
|
||||
<?= button(
|
||||
lang('User.form.submit_edit'),
|
||||
|
@ -30,7 +30,7 @@ class Analytics extends BaseConfig
|
||||
*
|
||||
* @param string|string[] $audioFilePath
|
||||
*/
|
||||
public function getAudioFileUrl(string|array $audioFilePath): string
|
||||
public function getAudioFileUrl(string | array $audioFilePath): string
|
||||
{
|
||||
helper('media');
|
||||
|
||||
|
@ -238,13 +238,13 @@ class AuthSeeder extends Seeder
|
||||
[
|
||||
'name' => 'block_actors',
|
||||
'description' =>
|
||||
'Block an activitypub actors from interacting with the instance.',
|
||||
'Block fediverse actors from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'block_domains',
|
||||
'description' =>
|
||||
'Block an activitypub domains from interacting with the instance.',
|
||||
'Block fediverse domains from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
|
@ -18,7 +18,7 @@ class Fediverse extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* ActivityPub Objects
|
||||
* Fediverse Objects
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
public string $actorObject = ActorObject::class;
|
||||
@ -38,6 +38,8 @@ class Fediverse extends BaseConfig
|
||||
|
||||
public string $defaultCoverImageMimetype = 'image/jpeg';
|
||||
|
||||
public string $tablesPrefix = 'fediverse_';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Cache options
|
||||
|
@ -18,7 +18,7 @@ $routes->addPlaceholder(
|
||||
$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply');
|
||||
|
||||
/**
|
||||
* ActivityPub routes file
|
||||
* Fediverse routes file
|
||||
*/
|
||||
|
||||
$routes->group('', [
|
||||
|
@ -26,11 +26,11 @@ class ActorController extends Controller
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['activitypub'];
|
||||
protected $helpers = ['fediverse'];
|
||||
|
||||
protected Actor $actor;
|
||||
|
||||
protected ActivityPub $config;
|
||||
protected Fediverse $config;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -298,11 +298,14 @@ class ActorController extends Controller
|
||||
*/
|
||||
public function followers(): ResponseInterface
|
||||
{
|
||||
$tablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
// get followers for a specific actor
|
||||
$followers = model('ActorModel')
|
||||
->join('activitypub_follows', 'activitypub_follows.actor_id = id', 'inner')
|
||||
->where('activitypub_follows.target_actor_id', $this->actor->id)
|
||||
->orderBy('activitypub_follows.created_at', 'DESC');
|
||||
->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner')
|
||||
->where($tablesPrefix . 'follows.target_actor_id', $this->actor->id)
|
||||
->orderBy($tablesPrefix . 'follows.created_at', 'DESC');
|
||||
|
||||
$pageNumber = (int) $this->request->getGet('page');
|
||||
|
||||
@ -343,7 +346,7 @@ class ActorController extends Controller
|
||||
helper('text');
|
||||
|
||||
// get webfinger data from actor
|
||||
// parse activityPub id to get actor and domain
|
||||
// parse actor id to get actor and domain
|
||||
// check if actor and domain exist
|
||||
|
||||
if (
|
||||
@ -353,7 +356,7 @@ class ActorController extends Controller
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', lang('ActivityPub.follow.accountNotFound'));
|
||||
->with('error', lang('Fediverse.follow.accountNotFound'));
|
||||
}
|
||||
|
||||
$ostatusKey = array_search(
|
||||
|
@ -18,7 +18,7 @@ class BlockController extends Controller
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['activitypub'];
|
||||
protected $helpers = ['fediverse'];
|
||||
|
||||
public function attemptBlockActor(): RedirectResponse
|
||||
{
|
||||
|
@ -16,6 +16,7 @@ use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Modules\Fediverse\Config\Fediverse;
|
||||
use Modules\Fediverse\Entities\Post;
|
||||
use Modules\Fediverse\Objects\OrderedCollectionObject;
|
||||
use Modules\Fediverse\Objects\OrderedCollectionPage;
|
||||
@ -25,11 +26,11 @@ class PostController extends Controller
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['activitypub'];
|
||||
protected $helpers = ['fediverse'];
|
||||
|
||||
protected Post $post;
|
||||
|
||||
protected ActivityPub $config;
|
||||
protected Fediverse $config;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -229,7 +230,7 @@ class PostController extends Controller
|
||||
helper('text');
|
||||
|
||||
// get webfinger data from actor
|
||||
// parse activityPub id to get actor and domain
|
||||
// parse actor id to get actor and domain
|
||||
// check if actor and domain exist
|
||||
if (
|
||||
! ($parts = split_handle($this->request->getPost('handle'))) ||
|
||||
@ -238,7 +239,7 @@ class PostController extends Controller
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', lang('ActivityPub.follow.accountNotFound'));
|
||||
->with('error', lang('Fediverse.follow.accountNotFound'));
|
||||
}
|
||||
|
||||
$ostatusKey = array_search(
|
||||
|
@ -17,7 +17,7 @@ class SchedulerController extends Controller
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['activitypub'];
|
||||
protected $helpers = ['fediverse'];
|
||||
|
||||
public function activity(): void
|
||||
{
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddActors Creates activitypub_actors table in database
|
||||
* Class AddActors Creates actors table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -113,11 +113,11 @@ class AddActors extends Migration
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addUniqueKey('uri');
|
||||
$this->forge->addUniqueKey(['username', 'domain']);
|
||||
$this->forge->createTable('activitypub_actors');
|
||||
$this->forge->createTable(config('Fediverse')->tablesPrefix . 'actors');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_actors');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'actors');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPosts Creates activitypub_posts table in database
|
||||
* Class AddPosts Creates posts table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -74,18 +74,22 @@ class AddPosts extends Migration
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
]);
|
||||
|
||||
$tablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addUniqueKey('uri');
|
||||
// FIXME: an actor must reblog a post only once
|
||||
// $this->forge->addUniqueKey(['actor_id', 'reblog_of_id']);
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('in_reply_to_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('reblog_of_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_posts');
|
||||
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('in_reply_to_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('reblog_of_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable($tablesPrefix . 'posts');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_posts');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddActivities Creates activitypub_activities table in database
|
||||
* Class AddActivities Creates activities table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -59,15 +59,19 @@ class AddActivities extends Migration
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
]);
|
||||
|
||||
$tablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('target_actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_activities');
|
||||
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable($tablesPrefix . 'activities');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_activities');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'activities');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddFavourites Creates activitypub_favourites table in database
|
||||
* Class AddFavourites Creates favourites table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -28,15 +28,19 @@ class AddFavourites extends Migration
|
||||
'constraint' => 16,
|
||||
],
|
||||
]);
|
||||
|
||||
$tablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
|
||||
$this->forge->addPrimaryKey(['actor_id', 'post_id']);
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_favourites');
|
||||
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable($tablesPrefix . 'favourites');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_favourites');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'favourites');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddFollowers Creates activitypub_followers table in database
|
||||
* Class AddFollowers Creates followers table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -30,15 +30,19 @@ class AddFollowers extends Migration
|
||||
'comment' => 'Actor that is followed',
|
||||
],
|
||||
]);
|
||||
|
||||
$tablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
|
||||
$this->forge->addPrimaryKey(['actor_id', 'target_actor_id']);
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('target_actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_follows');
|
||||
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable($tablesPrefix . 'follows');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_follows');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'follows');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPreviewCards Creates activitypub_preview_cards table in database
|
||||
* Class AddPreviewCards Creates preview_cards table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -75,11 +75,11 @@ class AddPreviewCards extends Migration
|
||||
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addUniqueKey('url');
|
||||
$this->forge->createTable('activitypub_preview_cards');
|
||||
$this->forge->createTable(config('Fediverse')->tablesPrefix . 'preview_cards');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_preview_cards');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'preview_cards');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPostsPreviewCards Creates activitypub_posts_preview_cards table in database
|
||||
* Class AddPostsPreviewCards Creates posts_preview_cards table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -29,14 +29,17 @@ class AddPostsPreviewCards extends Migration
|
||||
],
|
||||
]);
|
||||
|
||||
$tablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addPrimaryKey(['post_id', 'preview_card_id']);
|
||||
$this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('preview_card_id', 'activitypub_preview_cards', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_posts_preview_cards');
|
||||
$this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('preview_card_id', $tablesPrefix . 'preview_cards', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable($tablesPrefix . 'posts_preview_cards');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_posts_preview_cards');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts_preview_cards');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddBlockedDomains Creates activitypub_blocked_domains table in database
|
||||
* Class AddBlockedDomains Creates blocked_domains table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
@ -28,11 +28,11 @@ class AddBlockedDomains extends Migration
|
||||
],
|
||||
]);
|
||||
$this->forge->addPrimaryKey('name');
|
||||
$this->forge->createTable('activitypub_blocked_domains');
|
||||
$this->forge->createTable(config('Fediverse')->tablesPrefix . 'blocked_domains');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_blocked_domains');
|
||||
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'blocked_domains');
|
||||
}
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ class Post extends UuidEntity
|
||||
|
||||
public function setMessage(string $message): static
|
||||
{
|
||||
helper('activitypub');
|
||||
helper('fediverse');
|
||||
|
||||
$messageWithoutTags = strip_tags($message);
|
||||
|
||||
|
@ -13,7 +13,7 @@ use Config\Services;
|
||||
use Exception;
|
||||
use Modules\Fediverse\HttpSignature;
|
||||
|
||||
class ActivityPubFilter implements FilterInterface
|
||||
class FediverseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Do whatever processing this filter needs to do. By default it should not return anything during normal execution.
|
@ -13,15 +13,14 @@ namespace Modules\Fediverse\Models;
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use DateTimeInterface;
|
||||
use Michalsn\Uuid\UuidModel;
|
||||
use Modules\Fediverse\Entities\Activity;
|
||||
|
||||
class ActivityModel extends UuidModel
|
||||
class ActivityModel extends BaseUuidModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'activitypub_activities';
|
||||
protected $table = 'activities';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
@ -11,15 +11,14 @@ declare(strict_types=1);
|
||||
namespace Modules\Fediverse\Models;
|
||||
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Model;
|
||||
use Modules\Fediverse\Entities\Actor;
|
||||
|
||||
class ActorModel extends Model
|
||||
class ActorModel extends BaseModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'activitypub_actors';
|
||||
protected $table = 'actors';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
@ -80,7 +79,7 @@ class ActorModel extends Model
|
||||
public function getActorByUsername(string $username, ?string $domain = null): ?Actor
|
||||
{
|
||||
// TODO: is there a better way?
|
||||
helper('activitypub');
|
||||
helper('fediverse');
|
||||
|
||||
if (! $domain) {
|
||||
$domain = get_current_domain();
|
||||
@ -129,8 +128,10 @@ class ActorModel extends Model
|
||||
config('Fediverse')
|
||||
->cachePrefix . "actor#{$actorId}_followers";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->join('activitypub_follows', 'activitypub_follows.actor_id = id', 'inner')
|
||||
->where('activitypub_follows.target_actor_id', $actorId)
|
||||
$tablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
$found = $this->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner')
|
||||
->where($tablesPrefix . 'follows.target_actor_id', $actorId)
|
||||
->findAll();
|
||||
|
||||
cache()
|
||||
|
26
modules/Fediverse/Models/BaseModel.php
Normal file
26
modules/Fediverse/Models/BaseModel.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Fediverse\Models;
|
||||
|
||||
use CodeIgniter\Database\ConnectionInterface;
|
||||
use CodeIgniter\Model;
|
||||
use CodeIgniter\Validation\ValidationInterface;
|
||||
|
||||
class BaseModel extends Model
|
||||
{
|
||||
/**
|
||||
* Model constructor.
|
||||
*
|
||||
* @param ConnectionInterface|null $db DB Connection
|
||||
* @param ValidationInterface|null $validation Validation
|
||||
*/
|
||||
public function __construct(ConnectionInterface &$db = null, ValidationInterface $validation = null)
|
||||
{
|
||||
parent::__construct($db, $validation);
|
||||
|
||||
$this->table = config('Fediverse')
|
||||
->tablesPrefix . $this->table;
|
||||
}
|
||||
}
|
20
modules/Fediverse/Models/BaseUuidModel.php
Normal file
20
modules/Fediverse/Models/BaseUuidModel.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Fediverse\Models;
|
||||
|
||||
use CodeIgniter\Database\ConnectionInterface;
|
||||
use CodeIgniter\Validation\ValidationInterface;
|
||||
use Michalsn\Uuid\UuidModel;
|
||||
|
||||
class BaseUuidModel extends UuidModel
|
||||
{
|
||||
public function __construct(ConnectionInterface &$db = null, ValidationInterface $validation = null)
|
||||
{
|
||||
parent::__construct($db, $validation);
|
||||
|
||||
$this->table = config('Fediverse')
|
||||
->tablesPrefix . $this->table;
|
||||
}
|
||||
}
|
@ -15,12 +15,12 @@ use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Model;
|
||||
use Modules\Fediverse\Entities\BlockedDomain;
|
||||
|
||||
class BlockedDomainModel extends Model
|
||||
class BlockedDomainModel extends BaseModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'activitypub_blocked_domains';
|
||||
protected $table = 'blocked_domains';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
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