"). */ public const SCREEN_HOOK = 'settings_page_' . self::SLUG; /** @var array */ private array $tabs = []; /** * @param list $tabs The first tab is the default. * @param string $logoUrl Loosen-IT logo shown top right of the page ('' = none). * @param string $assetsUrl URL of the plugin's assets/ directory ('' = no own CSS/JS). * @param string $version Asset version for cache busting. */ public function __construct( array $tabs, private string $logoUrl = '', private string $assetsUrl = '', private string $version = '' ) { foreach ($tabs as $tab) { $this->tabs[$tab->slug()] = $tab; } } public function register(): void { add_action('admin_menu', [$this, 'addMenu']); add_action('admin_init', [$this, 'registerSettings']); add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']); foreach ($this->tabs as $tab) { $tab->registerActions(); } } public function addMenu(): void { add_options_page( __('LIT MCP', 'wp-lit-mcp'), __('LIT MCP', 'wp-lit-mcp'), 'manage_options', self::SLUG, [$this, 'render'] ); } public function registerSettings(): void { foreach ($this->tabs as $tab) { $tab->registerSettings(); } } /** * Own CSS and a small script, only on this page. The script builds on WordPress' own * clipboard.js and wp.a11y.speak (as the media modal's "Copy URL") and postbox.js. */ public function enqueueAssets(string $hook): void { if ($hook !== self::SCREEN_HOOK || $this->assetsUrl === '') { return; } $base = rtrim($this->assetsUrl, '/'); wp_enqueue_style('lit-mcp-admin', $base . '/css/admin.css', [], $this->version); $deps = ['clipboard', 'wp-a11y']; if ($this->currentTab() === 'tools') { // Collapsible tool groups use WordPress' own postbox toggles (remembered per user). $deps[] = 'postbox'; } wp_enqueue_script('lit-mcp-admin', $base . '/js/admin.js', $deps, $this->version, true); wp_localize_script('lit-mcp-admin', 'litMcpAdmin', [ 'copied' => __('Die URL wurde in die Zwischenablage kopiert.', 'wp-lit-mcp'), ]); } /** @return list */ public function tabSlugs(): array { return array_keys($this->tabs); } public function currentTab(): string { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab switch. $requested = isset($_GET['tab']) ? sanitize_key(wp_unslash((string) $_GET['tab'])) : ''; return isset($this->tabs[$requested]) ? $requested : (string) array_key_first($this->tabs); } /** @param array $args */ public static function url(string $tab = '', array $args = []): string { $query = array_merge(['page' => self::SLUG], $tab !== '' ? ['tab' => $tab] : [], $args); return add_query_arg($query, admin_url('options-general.php')); } public function render(): void { if (! current_user_can('manage_options')) { return; } $current = $this->currentTab(); echo '
'; echo '

' . esc_html__('LIT MCP', 'wp-lit-mcp') . '

'; $this->renderLogo(); // Admin notices of other plugins are moved right after this marker – above the tabs. echo '

'; $this->renderNotice(); printf(''; $this->tabs[$current]->render(); echo '
'; } private function renderLogo(): void { if ($this->logoUrl === '') { return; } printf( '' . '' . ' %s', esc_url($this->logoUrl), esc_html__('(öffnet in einem neuen Tab)', 'wp-lit-mcp') ); } private function renderNotice(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. $key = isset($_GET['lit_mcp_notice']) ? sanitize_key(wp_unslash((string) $_GET['lit_mcp_notice'])) : ''; $notice = match ($key) { 'revoked' => ['success', __('Verbindung widerrufen.', 'wp-lit-mcp')], 'revoke_failed' => ['error', __('Die Verbindung konnte nicht widerrufen werden.', 'wp-lit-mcp')], 'cleared' => ['success', __('Protokoll geleert.', 'wp-lit-mcp')], default => null, }; if ($notice !== null) { printf( '

%s

', esc_attr($notice[0]), esc_html($notice[1]) ); } } }