terminate = $terminate ?? static function (): void { exit; }; } public function slug(): string { return 'protokoll'; } public function label(): string { return __('Protokoll', 'wp-lit-mcp'); } public function registerSettings(): void { } public function registerActions(): void { add_action('admin_post_' . self::CLEAR_ACTION, [$this, 'handleClear']); } /** @return array{ability: string, user_id: int} */ public function filters(): array { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only list filters. $ability = isset($_GET['ability']) ? sanitize_text_field(wp_unslash((string) $_GET['ability'])) : ''; $userId = isset($_GET['user_id']) ? absint($_GET['user_id']) : 0; // phpcs:enable WordPress.Security.NonceVerification.Recommended return [ 'ability' => preg_match('#^[a-z0-9-]+/[a-z0-9-]+$#', $ability) === 1 ? $ability : '', 'user_id' => $userId, ]; } /** * Additional whitelisted filters (ruling C5/Q13: ability, result, user, date range). * Kept separate from filters() so that method's return shape stays exactly what is * unit-tested (and what earlier callers of this tab may already rely on). * * @return array{result: string, date_from: string, date_to: string} */ public function extraFilters(): array { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only list filters. $result = isset($_GET['result']) ? sanitize_text_field(wp_unslash((string) $_GET['result'])) : ''; $dateFrom = isset($_GET['date_from']) ? sanitize_text_field(wp_unslash((string) $_GET['date_from'])) : ''; $dateTo = isset($_GET['date_to']) ? sanitize_text_field(wp_unslash((string) $_GET['date_to'])) : ''; // phpcs:enable WordPress.Security.NonceVerification.Recommended return [ 'result' => in_array($result, self::RESULTS, true) ? $result : '', 'date_from' => self::isDate($dateFrom) ? $dateFrom : '', 'date_to' => self::isDate($dateTo) ? $dateTo : '', ]; } private static function isDate(string $value): bool { return preg_match('#^\d{4}-\d{2}-\d{2}$#', $value) === 1; } public function render(): void { if (! class_exists('WP_List_Table')) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } $filters = $this->filters(); $extra = $this->extraFilters(); printf( '

%s

', esc_html(sprintf( /* translators: %d: number of days */ __('Einträge werden nach %d Tagen automatisch gelöscht (einstellbar im Tab „Limits“).', 'wp-lit-mcp'), $this->limits->logRetentionDays() )) ); printf('

%s

', esc_html__('Das Protokoll enthält nur Verweise, keine Inhalte.', 'wp-lit-mcp')); $this->renderFilterForm($filters, $extra); $table = new LogListTable($this->log, $filters + $extra); $table->prepare_items(); $table->display(); $this->renderClearForm(); } /** * @param array{ability: string, user_id: int} $filters * @param array{result: string, date_from: string, date_to: string} $extra */ private function renderFilterForm(array $filters, array $extra): void { echo '
'; printf('', esc_attr(SettingsPage::SLUG)); printf('', esc_attr($this->slug())); echo ' '; wp_dropdown_users([ 'name' => 'user_id', 'show_option_all' => __('Alle Benutzer', 'wp-lit-mcp'), 'selected' => $filters['user_id'], ]); echo ' '; echo ' '; printf( ' ', esc_html__('von', 'wp-lit-mcp'), esc_attr($extra['date_from']) ); printf( ' ', esc_html__('bis', 'wp-lit-mcp'), esc_attr($extra['date_to']) ); submit_button(__('Filtern', 'wp-lit-mcp'), 'secondary', '', false); echo '
'; } private function renderClearForm(): void { printf('
', esc_url(admin_url('admin-post.php'))); printf('', esc_attr(self::CLEAR_ACTION)); wp_nonce_field(self::CLEAR_ACTION); $confirmMessage = esc_js(__( 'Das gesamte Protokoll unwiderruflich löschen?', 'wp-lit-mcp' )); submit_button( __('Protokoll leeren', 'wp-lit-mcp'), 'delete', 'submit', false, ['onclick' => "return window.confirm('" . $confirmMessage . "');"] ); echo '
'; } public function handleClear(): void { if (! current_user_can('manage_options')) { wp_die(esc_html__('Keine Berechtigung.', 'wp-lit-mcp'), '', ['response' => 403]); } check_admin_referer(self::CLEAR_ACTION); $this->log->clear(); wp_safe_redirect(SettingsPage::url($this->slug(), ['lit_mcp_notice' => 'cleared'])); ($this->terminate)(); } }