false, 'message' => __('That review action is not allowed.', 'agentic-bridge')]; } self::redirect_with_message((string) ($result['message'] ?? (!empty($result['ok']) ? __('Review change updated.', 'agentic-bridge') : __('Review change could not be updated.', 'agentic-bridge'))), !empty($result['ok']) ? 'success' : 'error'); } public static function handle_controlled_answer(): void { self::assert_admin_post('thedotdev_agentic_bridge_controlled_answer'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $operation = sanitize_key((string) wp_unslash($_POST['controlled_answer_operation'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $record_id = sanitize_text_field((string) wp_unslash($_POST['record_id'] ?? '')); $actor_id = get_current_user_id(); if ($operation === 'create') { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $raw_steps = sanitize_textarea_field((string) wp_unslash($_POST['steps'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $post_id = absint(wp_unslash($_POST['post_id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $record_type = sanitize_key((string) wp_unslash($_POST['record_type'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $question = sanitize_text_field((string) wp_unslash($_POST['question'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $answer = sanitize_textarea_field((string) wp_unslash($_POST['answer'] ?? '')); $result = TheDotDevAgenticBridge_Controlled_Answerable_Questions::create($post_id, $record_type, $question, $answer, preg_split('/\\r?\\n/', $raw_steps) ?: [], $actor_id); } elseif ($operation === 'approve') { $result = TheDotDevAgenticBridge_Controlled_Answerable_Questions::approve($record_id, $actor_id); } elseif ($operation === 'reject') { $result = TheDotDevAgenticBridge_Controlled_Answerable_Questions::reject($record_id, $actor_id); } elseif ($operation === 'apply') { $result = TheDotDevAgenticBridge_Controlled_Answerable_Questions::apply($record_id, $actor_id); } elseif ($operation === 'verify') { $result = TheDotDevAgenticBridge_Controlled_Answerable_Questions::verify($record_id, $actor_id); } elseif ($operation === 'restore') { $result = TheDotDevAgenticBridge_Controlled_Answerable_Questions::restore($record_id, $actor_id); } else { $result = ['ok' => false, 'message' => __('That review action is not allowed.', 'agentic-bridge')]; } self::redirect_with_message((string) ($result['message'] ?? (!empty($result['ok']) ? __('Review change updated.', 'agentic-bridge') : __('Review change could not be updated.', 'agentic-bridge'))), !empty($result['ok']) ? 'success' : 'error'); } public static function handle_content_readiness_fix(): void { self::assert_admin_post('thedotdev_agentic_bridge_content_readiness_fix'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $operation = sanitize_key((string) wp_unslash($_POST['operation'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $record_id = sanitize_text_field((string) wp_unslash($_POST['record_id'] ?? '')); $actor = get_current_user_id(); if ($operation === 'create') { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $change = sanitize_key((string) wp_unslash($_POST['change'] ?? '')); $values = []; if ($change === 'set_fields') { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified above; every nested scalar is recursively sanitized immediately below. $submitted_values = self::sanitize_post_values(wp_unslash($_POST['values'] ?? [])); $raw_values = is_array($submitted_values) ? $submitted_values : []; $normalized = TheDotDevAgenticBridge_Content_Readiness_Review_UI::normalize_submitted_values($raw_values); if (empty($normalized['ok'])) { self::redirect_with_message( (string) ($normalized['message'] ?? __('The proposed settings could not be validated.', 'agentic-bridge')), 'error' ); } $values = is_array($normalized['values'] ?? null) ? $normalized['values'] : []; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $post_id = absint(wp_unslash($_POST['post_id'] ?? 0)); $result = TheDotDevAgenticBridge_Content_Readiness_Fix_Lifecycle::create($post_id, $change, $actor, $values); } elseif ($operation === 'approve') { $result = TheDotDevAgenticBridge_Content_Readiness_Fix_Lifecycle::approve($record_id, $actor); } elseif ($operation === 'reject') { $result = TheDotDevAgenticBridge_Content_Readiness_Fix_Lifecycle::reject($record_id, $actor); } elseif ($operation === 'apply') { $result = TheDotDevAgenticBridge_Content_Readiness_Fix_Lifecycle::apply($record_id, $actor); } elseif ($operation === 'verify') { $result = TheDotDevAgenticBridge_Content_Readiness_Fix_Lifecycle::verify($record_id, $actor); } elseif ($operation === 'restore') { $result = TheDotDevAgenticBridge_Content_Readiness_Fix_Lifecycle::restore($record_id, $actor); } else { $result = ['ok' => false, 'message' => __('That local readiness action is not allowed.', 'agentic-bridge')]; } self::redirect_with_message((string) ($result['message'] ?? (!empty($result['ok']) ? __('Local readiness change updated.', 'agentic-bridge') : __('Local readiness change could not be updated.', 'agentic-bridge'))), !empty($result['ok']) ? 'success' : 'error'); } public static function add_menu(): void { add_menu_page( __('Agentic Bridge', 'agentic-bridge'), __('Agentic Bridge', 'agentic-bridge'), 'manage_options', self::MENU_SLUG, [self::class, 'render_page'], self::menu_icon_url(), 58 ); } /** * @param list $links * @return list */ public static function plugin_action_links(array $links): array { $url = esc_url(self::admin_page_url()); array_unshift( $links, '' . esc_html__('Open Agentic Bridge', 'agentic-bridge') . '' ); return $links; } public static function redirect_legacy_settings(): void { if (!current_user_can('manage_options')) { return; } $args = []; $canonical_keys = [ 'thedotdev_agentic_bridge_view', 'thedotdev_agentic_bridge_notice', 'thedotdev_agentic_bridge_notice_type', 'thedotdev_agentic_bridge_preview_page', 'thedotdev_agentic_bridge_metadata_page', 'thedotdev_agentic_bridge_metadata_id', 'thedotdev_agentic_bridge_metadata_search', 'thedotdev_agentic_bridge_metadata_filter', 'thedotdev_agentic_bridge_severity', 'thedotdev_agentic_bridge_category', 'thedotdev_agentic_bridge_lifecycle', 'thedotdev_agentic_bridge_findings_q', 'thedotdev_agentic_bridge_finding', 'thedotdev_agentic_bridge_findings_page', 'thedotdev_agentic_bridge_settings_tab', ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Safe redirect of read-only admin query args. foreach ($canonical_keys as $key) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only legacy redirect passthrough. if (!isset($_GET[$key])) { continue; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized per-key below. $raw = wp_unslash($_GET[$key]); if (!is_string($raw) && !is_int($raw)) { continue; } $args[$key] = is_int($raw) ? (string) $raw : sanitize_text_field($raw); } wp_safe_redirect(self::admin_page_url($args)); exit; } public static function redirect_legacy_settings_request(): void { if (!is_admin() || !current_user_can('manage_options')) { return; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only global used for safe redirect. $pagenow = isset($GLOBALS['pagenow']) && is_string($GLOBALS['pagenow']) ? $GLOBALS['pagenow'] : ''; if ($pagenow !== 'admin.php' && $pagenow !== 'options-general.php') { return; } if (!isset($_GET['page'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only redirect gate. return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only redirect gate. $legacy_page = sanitize_key((string) wp_unslash($_GET['page'])); if ($legacy_page === self::MENU_SLUG) { return; } $legacy_slugs = [ self::LEGACY_MENU_SLUG => self::MENU_SLUG, 'tdab-ai-preview' => self::AI_PREVIEW_SLUG, // Compatibility-only redirect for pre-0.1.9 bookmarks. No generic submenu is registered. 'setup' => self::SETUP_WIZARD_SLUG, ]; if (!isset($legacy_slugs[$legacy_page])) { if ($pagenow === 'options-general.php' && $legacy_page === self::LEGACY_MENU_SLUG) { self::redirect_legacy_settings(); } return; } $target_page = $legacy_slugs[$legacy_page]; $args = ['page' => $target_page]; if ($target_page === self::MENU_SLUG) { self::redirect_legacy_settings(); } wp_safe_redirect(add_query_arg($args, admin_url('admin.php'))); exit; } /** @param array $args */ public static function admin_page_url(array $args = []): string { $args = array_merge(['page' => self::MENU_SLUG], $args); return add_query_arg($args, admin_url('admin.php')); } public static function ai_preview_url(): string { return add_query_arg(['page' => self::AI_PREVIEW_SLUG], admin_url('admin.php')); } public static function setup_url(): string { return add_query_arg(['page' => self::SETUP_WIZARD_SLUG], admin_url('admin.php')); } public static function menu_icon_url(): string { $svg = ''; return 'data:image/svg+xml;base64,' . base64_encode($svg); } public static function enqueue_assets(string $hook): void { $is_editor = in_array($hook, ['post.php', 'post-new.php'], true); $is_admin_app = !$is_editor && self::should_enqueue_admin_app_assets($hook); if (!$is_admin_app && !$is_editor) { return; } if (TheDotDevAgenticBridge_V2_Presentation::is_enabled()) { if ($is_admin_app) { TheDotDevAgenticBridge_V2_Presentation::enqueue_admin_assets($hook); } if ($is_editor) { TheDotDevAgenticBridge_V2_Presentation::enqueue_editor_assets(); self::enqueue_editor_script_config(); } return; } wp_enqueue_style('thedotdev-agentic-bridge-admin-fonts', THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_URL . 'assets/admin-fonts.css', [], THEDOTDEV_AGENTIC_BRIDGE_VERSION); wp_enqueue_style('thedotdev-agentic-bridge-admin', THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_URL . 'assets/admin.css', ['thedotdev-agentic-bridge-admin-fonts'], THEDOTDEV_AGENTIC_BRIDGE_VERSION); if ($is_admin_app) { wp_enqueue_style( 'thedotdev-agentic-bridge-overview-v2', THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_URL . 'assets/overview-v2.css', ['thedotdev-agentic-bridge-admin-fonts'], THEDOTDEV_AGENTIC_BRIDGE_VERSION ); wp_enqueue_script( 'thedotdev-agentic-bridge-admin', THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_URL . 'assets/admin.js', [], THEDOTDEV_AGENTIC_BRIDGE_VERSION, true ); } if ($is_editor) { wp_enqueue_style( 'thedotdev-agentic-bridge-admin-editor', THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_URL . 'assets/admin-editor.css', ['thedotdev-agentic-bridge-admin'], THEDOTDEV_AGENTIC_BRIDGE_VERSION ); self::enqueue_editor_script_config(); } } private static function enqueue_editor_script_config(): void { $editor_admin_ui = self::normalized_admin_ui_settings((TheDotDevAgenticBridge_Storage::get()['admin_ui'] ?? [])); $editor_thresholds = is_array($editor_admin_ui['thresholds'] ?? null) ? $editor_admin_ui['thresholds'] : []; $editor_strong_min = max(60, min(100, (int) ($editor_thresholds['strongScoreMin'] ?? 90))); $editor_building_min = max(20, min($editor_strong_min - 1, (int) ($editor_thresholds['buildingScoreMin'] ?? 50))); $editor_script_handle = TheDotDevAgenticBridge_V2_Presentation::is_enabled() ? TheDotDevAgenticBridge_V2_Presentation::EDITOR_SCRIPT_HANDLE : 'thedotdev-agentic-bridge-admin-editor'; if (!TheDotDevAgenticBridge_V2_Presentation::is_enabled()) { wp_enqueue_script( 'thedotdev-agentic-bridge-admin-editor', THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_URL . 'assets/admin-editor.js', ['wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-i18n', 'wp-plugins'], THEDOTDEV_AGENTIC_BRIDGE_VERSION, true ); } $intent_options = []; foreach (TheDotDevAgenticBridge_Content_Metadata::INTENTS as $intent_id) { $intent_options[] = [ 'value' => $intent_id, 'label' => TheDotDevAgenticBridge_Content_Metadata::intent_label($intent_id), ]; } $inclusion_options = []; foreach (TheDotDevAgenticBridge_Content_Metadata::INCLUSION_MODES as $mode_id) { $inclusion_options[] = [ 'value' => $mode_id, 'label' => TheDotDevAgenticBridge_Content_Metadata::inclusion_mode_label($mode_id), ]; } $action_options = []; foreach (TheDotDevAgenticBridge_Content_Metadata::ALLOWED_ACTION_IDS as $action_id) { $action_options[] = [ 'value' => $action_id, 'label' => TheDotDevAgenticBridge_Content_Metadata::action_label($action_id), ]; } $blocked_claim_options = []; foreach (TheDotDevAgenticBridge_Content_Metadata::BLOCKED_CLAIM_IDS as $claim_id) { $blocked_claim_options[] = [ 'value' => $claim_id, 'label' => TheDotDevAgenticBridge_Content_Metadata::blocked_claim_label($claim_id), ]; } if (function_exists('wp_localize_script')) { $editor_config = [ 'metaKey' => TheDotDevAgenticBridge_Content_Agent_Ready_Editor::META_CONFIG_JSON, 'scoreMetaKey' => TheDotDevAgenticBridge_Content_Agent_Ready_Editor::META_SCORE, 'scoreStrongMin' => $editor_strong_min, 'scoreBuildingMin' => $editor_building_min, 'maxCustomBlocked' => TheDotDevAgenticBridge_Content_Metadata::MAX_CUSTOM_BLOCKED, 'intentOptions' => $intent_options, 'inclusionOptions' => $inclusion_options, 'actionOptions' => $action_options, 'blockedClaimOptions' => $blocked_claim_options, 'strings' => [ 'panelTitle' => __('Agent Ready', 'agentic-bridge'), 'panelHelp' => __('Set page intent and include/exclude controls locally. Nothing is published from this sidebar without explicit artifact preview and publish steps in Agentic Bridge admin.', 'agentic-bridge'), 'scoreLabel' => __('Agent Ready score', 'agentic-bridge'), 'summaryLabel' => __('Agent summary', 'agentic-bridge'), 'summaryHelp' => __('Short summary used for agent clarity. Stored locally as metadata.', 'agentic-bridge'), 'intentLabel' => __('Page intent', 'agentic-bridge'), 'intentHelp' => __('Choose the clearest intent so agents know how to frame this page.', 'agentic-bridge'), 'inclusionLabel' => __('Inclusion mode', 'agentic-bridge'), 'inclusionHelp' => __('Control whether this page can appear in local artifact drafts. Saving here never publishes.', 'agentic-bridge'), 'inheritDefaultsLabel' => __('Use reviewed global defaults for metadata fields', 'agentic-bridge'), 'primarySectionLabel' => __('Summary & intent', 'agentic-bridge'), 'actionsSectionLabel' => __('What AI agents may understand', 'agentic-bridge'), 'actionsHelp' => __('Metadata-only capabilities that describe what agents may understand on this page.', 'agentic-bridge'), 'claimsSectionLabel' => __('Claims AI agents must not make', 'agentic-bridge'), 'claimsHelp' => __('Choose claims that should be blocked from AI-facing summaries and agent-map output.', 'agentic-bridge'), 'customBlockedLabel' => __('Custom blocked claim', 'agentic-bridge'), 'schemaSectionLabel' => __('Relationship context', 'agentic-bridge'), 'relationshipLabel' => __('Relationship label', 'agentic-bridge'), 'relationshipTypeLabel' => __('Relationship type', 'agentic-bridge'), 'relationshipNoneLabel' => __('None', 'agentic-bridge'), 'relationshipServiceLabel' => __('Service', 'agentic-bridge'), 'relationshipProductLabel' => __('Product', 'agentic-bridge'), 'advancedSectionLabel' => __('Artifacts & defaults', 'agentic-bridge'), 'advancedHelp' => __('Advanced inclusion controls stay local until you generate and publish artifacts from Agentic Bridge admin.', 'agentic-bridge'), 'saveBoundary' => __('Changes save with the page. Nothing publishes to /llms.txt or /agent-map.json from this sidebar.', 'agentic-bridge'), 'strongBand' => __('Strong', 'agentic-bridge'), 'buildingBand' => __('Needs work', 'agentic-bridge'), 'earlyBand' => __('Not ready', 'agentic-bridge'), 'noScoreState' => __('No local score yet. Run a local readiness check in Agentic Bridge admin to populate this panel.', 'agentic-bridge'), ], ]; wp_localize_script($editor_script_handle, 'tdabEditorConfig', $editor_config); wp_localize_script($editor_script_handle, 'thedotdevAgenticBridgeEditorConfig', $editor_config); } } public static function should_enqueue_admin_app_assets(string $hook): bool { if (in_array($hook, [ self::MENU_HOOK, self::AI_PREVIEW_HOOK, self::SETUP_WIZARD_HOOK, ], true)) { return true; } if ( str_ends_with($hook, '_page_' . self::MENU_SLUG) || str_ends_with($hook, '_page_' . self::AI_PREVIEW_SLUG) || str_ends_with($hook, '_page_' . self::SETUP_WIZARD_SLUG) ) { return true; } // admin.php and non–core-screen hooks (e.g. PHPUnit fixtures) may rely on ?page=. if (($hook === 'admin.php' || !str_ends_with($hook, '.php')) && self::is_admin_app_page()) { return true; } return false; } public static function current_admin_page_slug(): string { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- read-only admin page slug for asset gating. $raw = isset($_GET['page']) ? wp_unslash($_GET['page']) : ''; return is_string($raw) ? sanitize_key($raw) : ''; } public static function is_admin_app_page(string $page = ''): bool { $page = $page !== '' ? $page : self::current_admin_page_slug(); return in_array($page, [self::MENU_SLUG, self::AI_PREVIEW_SLUG, self::SETUP_WIZARD_SLUG], true); } /** * @param array $raw * @return array */ private static function normalized_admin_ui_settings(array $raw): array { $defaults = [ 'scanning' => ['priorityFindingsLimit' => 3, 'trendRuns' => 8, 'allowTargetedRecheck' => true], 'thresholds' => ['strongScoreMin' => 90, 'buildingScoreMin' => 50, 'attentionScoreMax' => 49], 'contentTypes' => ['post' => true, 'page' => true], 'display' => ['inventoryPerPage' => 50, 'findingsPerPage' => 25, 'density' => 'comfortable', 'defaultOpenEvidence' => false], 'advanced' => ['showDeveloperEvidence' => true, 'showCloudTab' => true], ]; $out = []; foreach ($defaults as $section => $default_values) { $incoming = (isset($raw[$section]) && is_array($raw[$section])) ? $raw[$section] : []; $out[$section] = array_merge($default_values, $incoming); } return $out; } private static function get_query_absint(string $key, int $default = 0): int { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- read-only query arg, sanitized below via absint(). $raw = isset($_GET[$key]) ? wp_unslash($_GET[$key]) : null; return (is_string($raw) || is_int($raw) || is_float($raw)) ? absint($raw) : $default; } private static function get_query_string(string $key, string $default = ''): string { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- read-only query arg, sanitized below via sanitize_text_field(). $raw = isset($_GET[$key]) ? wp_unslash($_GET[$key]) : null; return is_string($raw) ? sanitize_text_field($raw) : $default; } private static function get_query_key(string $key, string $default = ''): string { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- read-only query arg, sanitized below via sanitize_key(). $raw = isset($_GET[$key]) ? wp_unslash($_GET[$key]) : null; return is_string($raw) ? sanitize_key($raw) : $default; } private static function redirect_with_message(string $message, string $type = 'success'): void { $args = [ 'thedotdev_agentic_bridge_notice' => $message, 'thedotdev_agentic_bridge_notice_type' => ($type === 'error' ? 'error' : 'success'), ]; $base = self::safe_admin_return_url(); if ($base === '') { $base = self::admin_page_url(); } wp_safe_redirect(add_query_arg($args, $base)); exit; } private static function safe_admin_return_url(): string { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated return target only. $candidate = isset($_POST['_wp_http_referer']) ? wp_unslash($_POST['_wp_http_referer']) : ''; if (!is_string($candidate) || $candidate === '') { return ''; } $candidate = esc_url_raw($candidate); if ($candidate === '' || !self::is_plugin_admin_url($candidate)) { return ''; } return $candidate; } private static function is_plugin_admin_url(string $url): bool { $parts = wp_parse_url($url); if (!is_array($parts)) { return false; } $admin_parts = wp_parse_url(admin_url('admin.php')); if (!is_array($admin_parts)) { return false; } if (($parts['host'] ?? '') !== ($admin_parts['host'] ?? '')) { return false; } $path = untrailingslashit((string) ($parts['path'] ?? '')); $admin_path = untrailingslashit((string) ($admin_parts['path'] ?? '')); if ($path !== $admin_path) { return false; } $query = (string) ($parts['query'] ?? ''); if ($query === '') { return false; } parse_str($query, $args); if (!is_array($args)) { return false; } $page = sanitize_key((string) ($args['page'] ?? '')); return in_array( $page, [ self::MENU_SLUG, self::AI_PREVIEW_SLUG, self::SETUP_WIZARD_SLUG, ], true ); } private static function post_has_key(string $key): bool { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Caller verified nonce via assert_admin_post(). return array_key_exists($key, $_POST); } private static function post_is_set(string $key): bool { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Caller verified nonce via assert_admin_post(). return isset($_POST[$key]); } private static function raw_post_input(string $key): mixed { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Caller verified nonce via assert_admin_post(); strict domain validation follows. return wp_unslash($_POST[$key] ?? null); } private static function sanitize_post_values(mixed $value): mixed { if (is_array($value)) { $sanitized = []; foreach ($value as $key => $item) { $sanitized[$key] = self::sanitize_post_values($item); } return $sanitized; } if (!is_scalar($value)) { return ''; } return sanitize_text_field((string) $value); } private static function get_has_query_key(string $key): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin filter query arg. return isset($_GET[$key]); } private static function raw_get_input(string $key): mixed { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only admin filter; scalar type-check precedes sanitization. return wp_unslash($_GET[$key] ?? null); } private static function request_method(): string { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Value is normalized to an uppercase request verb. $raw_method = wp_unslash($_SERVER['REQUEST_METHOD'] ?? 'GET'); $method = is_string($raw_method) ? sanitize_key($raw_method) : 'get'; if ($method === '') { return 'GET'; } return strtoupper($method); } /** * Preview-only resolver intent preparation for pasted BrowseGrade input. * * @return array{ * submitted: bool, * inputMode: string, * input: string, * dedupeMode: string, * error: string, * plan: array{ * schemaVersion: string, * prepared: list>, * rejected: list> * } * }|null */ private static function resolution_preview_from_post(): ?array { if (self::request_method() !== 'POST') { return null; } if (!self::post_is_set('thedotdev_agentic_bridge_resolution_preview_submit')) { return null; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce checked below via wp_verify_nonce. $nonce = sanitize_text_field((string) wp_unslash($_POST['_wpnonce'] ?? '')); if ($nonce === '' || wp_verify_nonce($nonce, 'thedotdev_agentic_bridge_preview_resolution_plan') !== 1) { return [ 'submitted' => true, 'inputMode' => 'report_json', 'input' => '', 'dedupeMode' => 'by_finding_key', 'error' => __('Preview request was rejected: invalid or missing nonce.', 'agentic-bridge'), 'plan' => ['schemaVersion' => '1.0', 'prepared' => [], 'rejected' => []], ]; } $raw_input_mode = self::raw_post_input('thedotdev_agentic_bridge_resolution_input_mode'); $input_mode = is_string($raw_input_mode) ? sanitize_key($raw_input_mode) : 'report_json'; if (!in_array($input_mode, ['report_json', 'finding_keys'], true)) { $input_mode = 'report_json'; } $raw_dedupe_mode = self::raw_post_input('thedotdev_agentic_bridge_resolution_dedupe_mode'); $dedupe_mode = is_string($raw_dedupe_mode) ? sanitize_key($raw_dedupe_mode) : 'by_finding_key'; if (!in_array($dedupe_mode, ['by_finding_key', 'by_resolver_id', 'none'], true)) { $dedupe_mode = 'by_finding_key'; } $raw_input_post = self::raw_post_input('thedotdev_agentic_bridge_resolution_input'); $raw_input = is_string($raw_input_post) ? $raw_input_post : ''; $input = trim($raw_input); $error = ''; $plan = ['schemaVersion' => '1.0', 'prepared' => [], 'rejected' => []]; if ($input === '') { $error = __('Paste a BrowseGrade report JSON payload or finding keys first.', 'agentic-bridge'); } elseif ($input_mode === 'finding_keys') { $parts = preg_split('/[\r\n,]+/', $input) ?: []; $finding_keys = []; foreach ($parts as $part) { if (!is_string($part)) { continue; } $key = trim($part); if ($key === '') { continue; } $finding_keys[] = $key; } $plan = TheDotDevAgenticBridge_Resolution_Planner::prepare($finding_keys, ['dedupeMode' => $dedupe_mode]); } else { $decoded = json_decode($input, true); if ($decoded === null && strtolower(trim($input)) !== 'null') { $error = __('BrowseGrade report JSON could not be parsed. Check syntax and try again.', 'agentic-bridge'); } else { $plan = TheDotDevAgenticBridge_Resolution_Planner::prepare_from_browsegrade_report($decoded, ['dedupeMode' => $dedupe_mode]); } } return [ 'submitted' => true, 'inputMode' => $input_mode, 'input' => $input, 'dedupeMode' => $dedupe_mode, 'error' => $error, 'plan' => $plan, ]; } /** @return array */ private static function metadata_input_from_post(bool $is_content_item): array { return [ 'inclusionMode' => self::post_has_key('inclusion_mode') ? self::raw_post_input('inclusion_mode') : TheDotDevAgenticBridge_Content_Metadata::INCLUSION_INHERIT, 'inheritDefaults' => $is_content_item ? self::post_is_set('inherit_defaults') : false, 'intent' => self::post_has_key('intent') ? self::raw_post_input('intent') : 'general_information', 'allowedActions' => self::post_is_set('allowed_actions') ? self::raw_post_input('allowed_actions') : [], 'blockedClaims' => self::post_is_set('blocked_claims') ? self::raw_post_input('blocked_claims') : [], 'customBlockedClaims' => self::post_is_set('custom_blocked_claims') ? self::raw_post_input('custom_blocked_claims') : [], 'agentSummary' => self::post_has_key('agent_summary') ? self::raw_post_input('agent_summary') : '', 'relationshipLabel' => self::post_has_key('relationship_label') ? self::raw_post_input('relationship_label') : '', 'relationshipType' => self::post_has_key('relationship_type') ? self::raw_post_input('relationship_type') : '', ]; } public static function handle_download_readiness_pdf(): void { self::assert_admin_post('thedotdev_agentic_bridge_download_readiness_pdf'); $latest = TheDotDevAgenticBridge_Readiness_Store::latest(); if ($latest === null) { self::redirect_with_message( __('No local readiness scan is available to download. Run a local check first.', 'agentic-bridge'), 'error' ); } $generated_at = gmdate('c'); try { $bytes = TheDotDevAgenticBridge_Readiness_PDF::build($latest, [ 'siteName' => (string) get_bloginfo('name'), 'siteUrl' => (string) home_url('/'), 'pluginName' => 'Agentic Bridge', 'pluginVersion' => THEDOTDEV_AGENTIC_BRIDGE_VERSION, 'generatedAt' => $generated_at, 'markPath' => THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_DIR . TheDotDevAgenticBridge_Readiness_PDF::MARK_RELATIVE, ]); $filename = TheDotDevAgenticBridge_Readiness_PDF::safe_filename( (string) get_bloginfo('name'), gmdate('Ymd', strtotime($generated_at) ?: time()) ); } catch (Throwable) { self::redirect_with_message( __('Unable to generate the local readiness PDF report. Verify the plugin install is complete, then try again.', 'agentic-bridge'), 'error' ); } self::emit_pdf_download($bytes, $filename); } private static function emit_pdf_download(string $bytes, string $filename): void { status_header(200); nocache_headers(); header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . (string) strlen($bytes)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Exact PDF bytes. echo $bytes; exit; } private static function assert_admin_post(string $action): void { if (!current_user_can('manage_options')) { wp_die(esc_html__('Unauthorized', 'agentic-bridge')); } check_admin_referer($action); } public static function handle_confirm_connect(): void { self::assert_admin_post('thedotdev_agentic_bridge_confirm_connect'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $preview_id = sanitize_text_field(wp_unslash((string) ($_POST['preview_id'] ?? ''))); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $fingerprint = sanitize_text_field(wp_unslash((string) ($_POST['preview_fingerprint'] ?? ''))); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $pairing_code = sanitize_text_field(wp_unslash((string) ($_POST['pairing_code_confirm'] ?? ''))); if ($preview_id === '' || $fingerprint === '' || $pairing_code === '') { self::redirect_with_message(__('Confirm connect requires the preview binding and pairing code re-entry.', 'agentic-bridge'), 'error'); } $consent = TheDotDevAgenticBridge_Outbound_Preview::consume_pairing_consent($preview_id, $fingerprint, $pairing_code); if (!$consent['ok']) { self::redirect_with_message(__('Connect refused: preview is missing, stale, expired, or does not match the re-entered pairing code.', 'agentic-bridge'), 'error'); } $result = TheDotDevAgenticBridge_Connector::claim_site_with_payload($consent['destination'], $consent['payload_bytes']); if (is_wp_error($result)) { TheDotDevAgenticBridge_Outbound_Preview::discard_preview(TheDotDevAgenticBridge_Outbound_Preview::OP_PAIRING); self::redirect_with_message( /* translators: %s: error message from the control plane */ sprintf(__('Connection failed: %s', 'agentic-bridge'), $result->get_error_message()), 'error', ); } TheDotDevAgenticBridge_Outbound_Preview::discard_preview(TheDotDevAgenticBridge_Outbound_Preview::OP_PAIRING); TheDotDevAgenticBridge_Outbound_Preview::clear_cached_report(); self::redirect_with_message(__('Site connected successfully.', 'agentic-bridge')); } public static function handle_connect(): void { self::redirect_with_message(__('Generate a connection data preview and confirm that exact preview to connect.', 'agentic-bridge'), 'error'); } public static function handle_preview_snapshot(): void { self::assert_admin_post('thedotdev_agentic_bridge_preview_snapshot'); if (!TheDotDevAgenticBridge_Storage::is_connected()) { self::redirect_with_message(__('Snapshot preview requires a connected site.', 'agentic-bridge'), 'error'); } TheDotDevAgenticBridge_Connector::refresh_entitlements(); if (!TheDotDevAgenticBridge_Entitlements::can_cloud_sync()) { self::redirect_with_message(TheDotDevAgenticBridge_Entitlements::upgrade_notice_text(), 'error'); } $result = TheDotDevAgenticBridge_Outbound_Preview::generate_snapshot_preview(); if (!$result['ok']) { $message = match ($result['error']) { 'not_connected' => __('Snapshot preview requires a connected site.', 'agentic-bridge'), 'invalid_destination' => __('Connected API base URL is invalid.', 'agentic-bridge'), 'oversize' => __('Snapshot preview refused: payload exceeds safe size limits.', 'agentic-bridge'), default => __('Snapshot preview could not be generated.', 'agentic-bridge'), }; self::redirect_with_message($message, 'error'); } self::redirect_with_message(__('Snapshot data preview generated. Review exact JSON bytes before pushing.', 'agentic-bridge')); } public static function handle_confirm_push(): void { self::assert_admin_post('thedotdev_agentic_bridge_confirm_push'); TheDotDevAgenticBridge_Connector::refresh_entitlements(); if (!TheDotDevAgenticBridge_Entitlements::can_cloud_sync()) { self::redirect_with_message(TheDotDevAgenticBridge_Entitlements::upgrade_notice_text(), 'error'); } $raw_consent = self::raw_post_input('thedotdev_agentic_bridge_snapshot_consent'); if (!is_string($raw_consent)) { self::redirect_with_message( __('Push refused: explicit payload review consent is required.', 'agentic-bridge'), 'error', ); } $explicit_consent = sanitize_text_field($raw_consent); if (!hash_equals('1', $explicit_consent)) { self::redirect_with_message( __('Push refused: explicit payload review consent is required.', 'agentic-bridge'), 'error', ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $preview_id = sanitize_text_field(wp_unslash((string) ($_POST['preview_id'] ?? ''))); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $fingerprint = sanitize_text_field(wp_unslash((string) ($_POST['preview_fingerprint'] ?? ''))); if ($preview_id === '' || $fingerprint === '') { self::redirect_with_message(__('Push requires a bound snapshot preview ID and fingerprint.', 'agentic-bridge'), 'error'); } $consent = TheDotDevAgenticBridge_Outbound_Preview::consume_snapshot_consent($preview_id, $fingerprint); if (!$consent['ok']) { self::redirect_with_message(__('Push refused: snapshot preview is missing, stale, expired, or already used.', 'agentic-bridge'), 'error'); } $result = TheDotDevAgenticBridge_Connector::push_snapshot_to_bound($consent['bound'], $consent['payload_bytes']); if (is_wp_error($result)) { TheDotDevAgenticBridge_Outbound_Preview::discard_preview(TheDotDevAgenticBridge_Outbound_Preview::OP_SNAPSHOT); self::redirect_with_message( /* translators: %s: error message from the control plane */ sprintf(__('Sync failed: %s', 'agentic-bridge'), $result->get_error_message()), 'error', ); } TheDotDevAgenticBridge_Outbound_Preview::discard_preview(TheDotDevAgenticBridge_Outbound_Preview::OP_SNAPSHOT); self::redirect_with_message( /* translators: %s: audit score returned by the control plane */ sprintf(__('Snapshot synced. Score: %s', 'agentic-bridge'), (string) ($result['score'] ?? 'n/a')), ); } public static function handle_discard_outbound_preview(): void { self::assert_admin_post('thedotdev_agentic_bridge_discard_outbound_preview'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $operation = sanitize_key((string) wp_unslash($_POST['preview_operation'] ?? '')); if (!in_array($operation, [TheDotDevAgenticBridge_Outbound_Preview::OP_PAIRING, TheDotDevAgenticBridge_Outbound_Preview::OP_SNAPSHOT], true)) { self::redirect_with_message(__('Unknown preview operation.', 'agentic-bridge'), 'error'); } TheDotDevAgenticBridge_Outbound_Preview::discard_preview($operation); self::redirect_with_message(__('Outbound preview discarded.', 'agentic-bridge')); } public static function handle_refresh_report(): void { self::assert_admin_post('thedotdev_agentic_bridge_refresh_report'); if (!TheDotDevAgenticBridge_Storage::is_connected()) { self::redirect_with_message(__('Hosted report refresh requires a connected site.', 'agentic-bridge'), 'error'); } TheDotDevAgenticBridge_Connector::refresh_entitlements(); if (!TheDotDevAgenticBridge_Entitlements::can_hosted_report()) { self::redirect_with_message(TheDotDevAgenticBridge_Entitlements::upgrade_notice_text(), 'error'); } $remote = TheDotDevAgenticBridge_Connector::fetch_findings(); if (is_wp_error($remote)) { self::redirect_with_message( /* translators: %s: error message from the control plane */ sprintf(__('Report refresh failed: %s', 'agentic-bridge'), $remote->get_error_message()), 'error', ); } TheDotDevAgenticBridge_Outbound_Preview::cache_report($remote); self::redirect_with_message(__('Hosted report refreshed.', 'agentic-bridge')); } public static function handle_disconnect(): void { self::assert_admin_post('thedotdev_agentic_bridge_disconnect'); TheDotDevAgenticBridge_Storage::disconnect(); TheDotDevAgenticBridge_Entitlements::clear_snapshot(); TheDotDevAgenticBridge_Outbound_Preview::discard_preview(TheDotDevAgenticBridge_Outbound_Preview::OP_SNAPSHOT); TheDotDevAgenticBridge_Outbound_Preview::clear_cached_report(); self::redirect_with_message(__('Site disconnected.', 'agentic-bridge')); } public static function handle_save_metadata_defaults(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_metadata_defaults'); $result = TheDotDevAgenticBridge_Content_Metadata::save_defaults(self::metadata_input_from_post(false)); if (!$result['ok']) { self::redirect_with_message(__('Metadata defaults could not be saved. Check values for unsafe or invalid content.', 'agentic-bridge'), 'error'); } wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'content', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Metadata defaults saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_metadata_content(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_metadata_content'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $content_id = absint(wp_unslash($_POST['content_id'] ?? 0)); $result = TheDotDevAgenticBridge_Content_Metadata::save_content_config($content_id, self::metadata_input_from_post(true)); if (!$result['ok']) { self::redirect_with_message(__('Metadata could not be saved. Check values for unsafe or invalid content.', 'agentic-bridge'), 'error'); } wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'content', 'thedotdev_agentic_bridge_metadata_id' => $content_id, 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Page metadata saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_generate_preview(): void { self::assert_admin_post('thedotdev_agentic_bridge_generate_preview'); $result = TheDotDevAgenticBridge_Artifacts::generate_preview_draft(); if (!$result['ok']) { $message = match ($result['error']) { 'draft_oversize' => __('Preview refused: artifact exceeds safe size limits.', 'agentic-bridge'), 'draft_entry_cap_exceeded' => __('Preview refused: more than 50 artifact-eligible posts/pages exist after policy and metadata exclusions.', 'agentic-bridge'), default => __('Preview refused: artifact validation failed. Review metadata and eligible content.', 'agentic-bridge'), }; self::redirect_with_message($message, 'error'); } self::redirect_with_message(__('Artifact preview generated. Review exact output before publishing.', 'agentic-bridge')); } public static function handle_publish(): void { self::assert_admin_post('thedotdev_agentic_bridge_publish'); $result = TheDotDevAgenticBridge_Artifacts::publish_from_preview(); if (!$result['ok']) { $message = match ($result['error']) { 'stale_preview' => __('Publish refused: preview is stale. Regenerate preview first.', 'agentic-bridge'), 'apache_plain_route_bridge_unavailable', 'apache_plain_route_bridge_failed' => __( 'Publish refused: this site uses Apache with plain permalinks, and WordPress could not install the required .htaccess rewrite bridge for /llms.txt and /agent-map.json. Make the site home .htaccess writable (or switch to pretty permalinks), then regenerate preview and publish again.', 'agentic-bridge' ), default => __('Publish refused: no valid preview draft. Generate preview first.', 'agentic-bridge'), }; self::redirect_with_message($message, 'error'); } flush_rewrite_rules(); self::redirect_with_message(__('Artifacts published to /llms.txt and /agent-map.json.', 'agentic-bridge')); } public static function handle_rollback_artifact(): void { self::assert_admin_post('thedotdev_agentic_bridge_rollback_artifact'); $raw_confirm = self::raw_post_input('thedotdev_agentic_bridge_rollback_confirm'); if (!is_string($raw_confirm)) { self::redirect_with_message( __('Rollback refused: explicit confirmation is required before replacing published artifacts.', 'agentic-bridge'), 'error', ); } $explicit_confirm = sanitize_text_field($raw_confirm); if (!hash_equals('1', $explicit_confirm)) { self::redirect_with_message( __('Rollback refused: explicit confirmation is required before replacing published artifacts.', 'agentic-bridge'), 'error', ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $revision_index = absint(wp_unslash($_POST['revision_index'] ?? 0)); $result = TheDotDevAgenticBridge_Artifacts::rollback_revision($revision_index); if (!$result['ok']) { $message = match ($result['error']) { 'revision_unsafe' => __('Rollback refused: revision contains ineligible or unsafe content.', 'agentic-bridge'), 'revision_all_ineligible' => __('Rollback refused: revision entries are no longer eligible.', 'agentic-bridge'), default => __('Rollback refused: revision not found.', 'agentic-bridge'), }; self::redirect_with_message($message, 'error'); } self::redirect_with_message(__('Artifacts rolled back to the selected revision.', 'agentic-bridge')); } public static function handle_sync(): void { self::redirect_with_message(__('Generate a snapshot data preview and confirm that exact snapshot to push.', 'agentic-bridge'), 'error'); } public static function handle_run_readiness_scan(): void { self::assert_admin_post('thedotdev_agentic_bridge_run_readiness_scan'); $scanner = new TheDotDevAgenticBridge_Readiness_Scanner(); TheDotDevAgenticBridge_Readiness_Store::save_result($scanner->run_all()); self::redirect_with_message(__('Local Agentic Readiness scan completed.', 'agentic-bridge')); } public static function handle_save_local_rechecks(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_local_rechecks'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $enabled = isset($_POST['local_rechecks_enabled']); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $frequency = sanitize_key((string) wp_unslash($_POST['local_rechecks_frequency'] ?? '')); TheDotDevAgenticBridge_Local_Rechecks::configure($enabled, $frequency); self::redirect_with_message($enabled ? __('Local rechecks schedule saved.', 'agentic-bridge') : __('Local rechecks are paused.', 'agentic-bridge')); } public static function handle_run_local_recheck(): void { self::assert_admin_post('thedotdev_agentic_bridge_run_local_recheck'); TheDotDevAgenticBridge_Local_Rechecks::run('manual_local'); self::redirect_with_message(__('Local recheck completed. No changes were applied.', 'agentic-bridge')); } public static function handle_preview_crawler_policy(): void { self::assert_admin_post(TheDotDevAgenticBridge_Crawler_Access_Policy::ACTION_PREVIEW); $input=[]; foreach (TheDotDevAgenticBridge_Crawler_Access_Policy::BOTS as $bot=>$_) { /* phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. */ $input[$bot]=sanitize_key((string)wp_unslash($_POST['crawler_policy'][$bot] ?? 'inherit')); } $result=TheDotDevAgenticBridge_Crawler_Access_Policy::preview($input,get_current_user_id()); self::redirect_with_message($result['message'],!empty($result['ok'])?'success':'error'); } public static function handle_approve_crawler_policy(): void { self::assert_admin_post(TheDotDevAgenticBridge_Crawler_Access_Policy::ACTION_APPROVE); /* phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. */ $result=TheDotDevAgenticBridge_Crawler_Access_Policy::approve(sanitize_text_field((string)wp_unslash($_POST['proposal_id'] ?? '')),get_current_user_id()); self::redirect_with_message($result['message'],!empty($result['ok'])?'success':'error'); } public static function handle_apply_crawler_policy(): void { self::assert_admin_post(TheDotDevAgenticBridge_Crawler_Access_Policy::ACTION_APPLY); /* phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. */ $result=TheDotDevAgenticBridge_Crawler_Access_Policy::apply(sanitize_text_field((string)wp_unslash($_POST['proposal_id'] ?? '')),sanitize_text_field((string)wp_unslash($_POST['policy_fingerprint'] ?? '')),get_current_user_id()); self::redirect_with_message($result['message'],!empty($result['ok'])?'success':'error'); } public static function handle_rollback_crawler_policy(): void { self::assert_admin_post(TheDotDevAgenticBridge_Crawler_Access_Policy::ACTION_ROLLBACK); /* phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. */ $result=TheDotDevAgenticBridge_Crawler_Access_Policy::rollback(sanitize_text_field((string)wp_unslash($_POST['proposal_id'] ?? '')),get_current_user_id()); self::redirect_with_message($result['message'],!empty($result['ok'])?'success':'error'); } public static function handle_recheck_readiness_finding(): void { self::assert_admin_post('thedotdev_agentic_bridge_recheck_readiness_finding'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $check_id = sanitize_key((string) wp_unslash($_POST['check_id'] ?? '')); if ($check_id === '') { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $check_id = sanitize_key((string) wp_unslash($_POST['finding_id'] ?? '')); } if (!TheDotDevAgenticBridge_Readiness_Methodology::is_valid_check_id($check_id)) { self::redirect_with_message(__('Unknown readiness check id.', 'agentic-bridge'), 'error'); } $latest = TheDotDevAgenticBridge_Readiness_Store::latest(); if ($latest === null) { $scanner = new TheDotDevAgenticBridge_Readiness_Scanner(); TheDotDevAgenticBridge_Readiness_Store::save_result($scanner->run_all()); self::redirect_with_message(__('Local Agentic Readiness scan completed.', 'agentic-bridge')); } $scanner = new TheDotDevAgenticBridge_Readiness_Scanner(); $replacement = $scanner->run_check($check_id); TheDotDevAgenticBridge_Readiness_Store::save_result(TheDotDevAgenticBridge_Readiness_Store::recheck_finding($latest, $replacement)); self::redirect_with_message(__('Readiness finding rechecked.', 'agentic-bridge')); } public static function handle_preview_fix(): void { self::assert_admin_post('thedotdev_agentic_bridge_preview_fix'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $finding_id = sanitize_key((string) wp_unslash($_POST['finding_id'] ?? '')); if ($finding_id === '') { self::redirect_with_message(__('Fix preview requires a finding id.', 'agentic-bridge'), 'error'); } $result = TheDotDevAgenticBridge_Fix_Engine::preview_for_finding($finding_id); if (!$result['ok']) { self::redirect_with_message(__('This finding has no safe one-click fix yet, or fix preview is unavailable.', 'agentic-bridge'), 'error'); } wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'findings', 'thedotdev_agentic_bridge_finding' => $finding_id, 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Fix preview ready. Review exact changes, confirm, then apply.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_apply_fix(): void { self::assert_admin_post('thedotdev_agentic_bridge_apply_fix'); $confirm = self::raw_post_input('thedotdev_agentic_bridge_fix_confirm'); if (!is_string($confirm) || !hash_equals('1', sanitize_text_field($confirm))) { self::redirect_with_message(__('Fix apply refused: explicit confirmation is required.', 'agentic-bridge'), 'error'); } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $preview_id = sanitize_text_field((string) wp_unslash($_POST['preview_id'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $fingerprint = sanitize_text_field((string) wp_unslash($_POST['preview_fingerprint'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $finding_id = sanitize_key((string) wp_unslash($_POST['finding_id'] ?? '')); $result = TheDotDevAgenticBridge_Fix_Engine::apply_preview($preview_id, $fingerprint); if (!$result['ok']) { self::redirect_with_message(__('Fix apply failed or was deferred for safety. Review preview and ownership first.', 'agentic-bridge'), 'error'); } wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'findings', 'thedotdev_agentic_bridge_finding' => $finding_id, 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Fix applied, undo snapshot saved, and readiness re-scanned.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_approve_fix(): void { self::assert_admin_post('thedotdev_agentic_bridge_approve_fix'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $preview_id = sanitize_text_field((string) wp_unslash($_POST['preview_id'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $fingerprint = sanitize_text_field((string) wp_unslash($_POST['preview_fingerprint'] ?? '')); $result = TheDotDevAgenticBridge_Fix_Engine::approve_preview($preview_id, $fingerprint, get_current_user_id()); if (!$result['ok']) { self::redirect_with_message(__('Approval was refused because this preview is stale, deferred, or already approved.', 'agentic-bridge'), 'error'); } self::redirect_with_message(__('Named approval recorded. Apply this exact local change when ready.', 'agentic-bridge')); } public static function handle_undo_fix(): void { self::assert_admin_post('thedotdev_agentic_bridge_undo_fix'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $audit_id = sanitize_text_field((string) wp_unslash($_POST['audit_id'] ?? '')); $result = TheDotDevAgenticBridge_Fix_Engine::undo($audit_id); if (!$result['ok']) { self::redirect_with_message(__('Undo failed. The audit entry may already be reverted or missing.', 'agentic-bridge'), 'error'); } wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'findings', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Fix undone and readiness re-scanned.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } /** * Resolve the active admin view and which expensive datasets it may load. * * @return array{ * view: string, * needs_readiness: bool, * needs_content_inventory: bool, * needs_artifact_workspace: bool, * needs_outbound: bool, * needs_resolution_preview: bool * } */ private static function admin_view_requirements(): array { $view = self::get_query_key('thedotdev_agentic_bridge_view', ''); if ($view === '' && ( self::get_has_query_key('thedotdev_agentic_bridge_metadata_id') || self::get_has_query_key('thedotdev_agentic_bridge_metadata_page') || self::get_has_query_key('thedotdev_agentic_bridge_metadata_search') || self::get_has_query_key('thedotdev_agentic_bridge_metadata_filter') )) { $view = 'content'; } $has_scan = is_array(TheDotDevAgenticBridge_Readiness_Store::latest()); if ($view === 'findings' && !$has_scan) { $view = ''; } $is_settings = $view === 'settings'; $is_history = $view === 'history'; $is_s8 = $view === 'cloud'; $is_s6 = $view === 'privacy'; $is_s5 = $view === 'artifacts'; $is_s4 = $view === 'content'; $is_s3 = $has_scan && $view === 'findings'; $is_overview = $has_scan && $view === ''; return [ 'view' => $view, 'needs_readiness' => $has_scan || $is_history, 'needs_content_inventory' => $is_s4, // Safety needs the bounded revision list to decide whether a prior // locally published artifact can be restored. Do not make a // restore control disappear merely because its own tab is active. 'needs_artifact_workspace' => $is_s4 || $is_s5 || $view === 'safety', 'needs_outbound' => $is_s6 || $is_s8 || $is_settings, 'needs_resolution_preview' => $is_settings, ]; } public static function render_page(): void { if (!current_user_can('manage_options')) { return; } $view_requirements = self::admin_view_requirements(); $settings = TheDotDevAgenticBridge_Storage::get(); $admin_ui_settings = self::normalized_admin_ui_settings($settings['admin_ui'] ?? []); if ((wp_json_encode($admin_ui_settings) ?: '') !== (wp_json_encode($settings['admin_ui'] ?? null) ?: '')) { TheDotDevAgenticBridge_Storage::update(['admin_ui' => $admin_ui_settings]); $settings = TheDotDevAgenticBridge_Storage::get(); } $display_settings = is_array($admin_ui_settings['display'] ?? null) ? $admin_ui_settings['display'] : []; $preview_per_page = max(10, min(100, (int) ($display_settings['inventoryPerPage'] ?? TheDotDevAgenticBridge_Content_Policy::PREVIEW_PER_PAGE))); $cached_report = null; $latest_report = null; $findings = []; $pairing_preview = null; $snapshot_preview = null; $snapshot_preview_bytes = null; $outbound_excluded_categories = []; $report_fetched_at = ''; if ($view_requirements['needs_outbound']) { $cached_report = TheDotDevAgenticBridge_Outbound_Preview::cached_report(); $latest_report = is_array($cached_report) ? ($cached_report['latestReport'] ?? null) : null; $findings = is_array($cached_report) ? ($cached_report['findings'] ?? []) : []; $pairing_preview = TheDotDevAgenticBridge_Outbound_Preview::get_active_preview(TheDotDevAgenticBridge_Outbound_Preview::OP_PAIRING); $snapshot_preview = TheDotDevAgenticBridge_Outbound_Preview::get_active_preview(TheDotDevAgenticBridge_Outbound_Preview::OP_SNAPSHOT); $snapshot_preview_bytes = TheDotDevAgenticBridge_Outbound_Preview::get_stored_snapshot_payload_bytes(); $outbound_excluded_categories = TheDotDevAgenticBridge_Outbound_Preview::excluded_category_labels(); $report_fetched_at = is_array($cached_report) ? (string) ($cached_report['fetchedAt'] ?? '') : ''; } $preview_page = 1; $preview_total = 0; $preview_page_count = 1; $content_preview = []; $metadata_page = 1; $metadata_content_id = 0; $metadata_search = ''; $metadata_filter = ''; $metadata_inventory = []; $metadata_total = 0; $metadata_page_count = 1; $metadata_defaults = TheDotDevAgenticBridge_Content_Metadata::defaults(); $metadata_defaults_reviewed_at = ''; $metadata_detail_post = null; $metadata_detail_config = null; $content_review_candidates = []; $content_health_conditions = []; if ($view_requirements['needs_content_inventory']) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only pagination query arg. $preview_page = TheDotDevAgenticBridge_Content_Policy::clamp_preview_page(self::get_query_absint('thedotdev_agentic_bridge_preview_page', 1), $preview_per_page); $preview_total = TheDotDevAgenticBridge_Content_Policy::preview_total_count(); $preview_page_count = TheDotDevAgenticBridge_Content_Policy::preview_page_count($preview_per_page); $content_preview = TheDotDevAgenticBridge_Content_Policy::preview_rows($preview_page, $preview_per_page); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only metadata query args. $metadata_page = TheDotDevAgenticBridge_Content_Policy::clamp_preview_page(self::get_query_absint('thedotdev_agentic_bridge_metadata_page', 1), $preview_per_page); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only metadata query args. $metadata_content_id = self::get_query_absint('thedotdev_agentic_bridge_metadata_id', 0); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only metadata query args. $metadata_search = self::get_query_string('thedotdev_agentic_bridge_metadata_search'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only metadata query args. $metadata_filter = self::get_query_key('thedotdev_agentic_bridge_metadata_filter'); $metadata_inventory_result = TheDotDevAgenticBridge_Content_Metadata::inventory_query($metadata_page, $preview_per_page, $metadata_search, $metadata_filter); $metadata_inventory = $metadata_inventory_result['rows']; $metadata_total = $metadata_inventory_result['total']; $metadata_page = $metadata_inventory_result['page']; $metadata_page_count = $metadata_inventory_result['page_count']; $metadata_defaults = TheDotDevAgenticBridge_Content_Metadata::normalized_store()['defaults']; $metadata_defaults_reviewed_at = TheDotDevAgenticBridge_Content_Metadata::normalized_store()['defaultsReviewedAt']; $content_review_candidates = TheDotDevAgenticBridge_Content_Review_Queue::candidates(); $content_health = TheDotDevAgenticBridge_Content_Review_Queue::current_site_content_health((int) get_current_blog_id()); $content_review_candidates = array_merge($content_review_candidates, $content_health['candidates']); $content_health_conditions = $content_health['conditions']; $metadata_detail_post = $metadata_content_id > 0 ? get_post($metadata_content_id) : null; if ($metadata_detail_post instanceof WP_Post && TheDotDevAgenticBridge_Content_Policy::is_manageable_preview_post($metadata_content_id) && TheDotDevAgenticBridge_Content_Metadata::is_configurable_content($metadata_detail_post)) { $store = TheDotDevAgenticBridge_Content_Metadata::normalized_store(); $metadata_detail_config = $store['by_content_id'][(string) $metadata_content_id] ?? array_merge(TheDotDevAgenticBridge_Content_Metadata::defaults(), ['inheritDefaults' => true, 'inclusionMode' => TheDotDevAgenticBridge_Content_Metadata::INCLUSION_INHERIT]); } } $artifact_preview = TheDotDevAgenticBridge_Artifacts::preview_draft(); $artifact_preview_stale = TheDotDevAgenticBridge_Artifacts::is_preview_stale($artifact_preview); $artifact_change_summary = is_array($artifact_preview) ? TheDotDevAgenticBridge_Artifacts::preview_change_summary($artifact_preview) : ['added' => [], 'removed' => [], 'changed' => [], 'metadataChanged' => false]; $artifact_revisions = []; $artifact_publish_cap_exceeded = false; $artifact_publish_cap = TheDotDevAgenticBridge_Artifact_Validator::MAX_ENTRIES; $artifact_eligible_count = 0; if ($view_requirements['needs_artifact_workspace']) { $artifact_revisions = is_array($settings['artifact_revisions'] ?? null) ? $settings['artifact_revisions'] : []; $artifact_publish_cap_exceeded = TheDotDevAgenticBridge_Content_Metadata::artifact_publish_cap_exceeded(); $artifact_eligible_count = TheDotDevAgenticBridge_Content_Metadata::count_artifact_eligible_content($artifact_publish_cap + 1); } $readiness_scan = $view_requirements['needs_readiness'] ? TheDotDevAgenticBridge_Readiness_Store::latest() : null; $resolution_preview = $view_requirements['needs_resolution_preview'] ? self::resolution_preview_from_post() : null; include THEDOTDEV_AGENTIC_BRIDGE_PLUGIN_DIR . 'views/admin-page.php'; } public static function handle_save_settings(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_settings'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $raw_api_base_url = sanitize_text_field(wp_unslash((string) ($_POST['api_base_url'] ?? ''))); $api_base_url = $raw_api_base_url === '' ? '' : esc_url_raw($raw_api_base_url); if ($raw_api_base_url !== '' && ($api_base_url === '' || !TheDotDevAgenticBridge_Storage::is_valid_api_base_url($api_base_url))) { wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'connection', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('API base URL must be a valid http or https URL.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'error', 'thedotdev_agentic_bridge_invalid_api_base_url' => rawurlencode($raw_api_base_url), ])); exit; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $kill_switch = isset($_POST['kill_switch']); TheDotDevAgenticBridge_Storage::update([ 'api_base_url' => $api_base_url, 'kill_switch' => $kill_switch, ]); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'connection', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Connection settings saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_data_retention(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_data_retention'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). TheDotDevAgenticBridge_Storage::update([ // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). 'delete_data_on_uninstall' => isset($_POST['delete_data_on_uninstall']), ]); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'advanced', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Data retention preference saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_scanning_settings(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_scanning_settings'); $settings = TheDotDevAgenticBridge_Storage::get(); $admin_ui = self::normalized_admin_ui_settings($settings['admin_ui'] ?? []); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $priority_limit = absint(wp_unslash($_POST['priority_findings_limit'] ?? $admin_ui['scanning']['priorityFindingsLimit'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $trend_runs = absint(wp_unslash($_POST['trend_runs'] ?? $admin_ui['scanning']['trendRuns'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $allow_targeted_recheck = isset($_POST['allow_targeted_recheck']); if ($priority_limit < 1 || $priority_limit > 12) { self::redirect_with_message(__('Priority findings limit must be between 1 and 12.', 'agentic-bridge'), 'error'); } if ($trend_runs < 3 || $trend_runs > TheDotDevAgenticBridge_Readiness_Store::MAX_HISTORY_RUNS) { self::redirect_with_message( sprintf( /* translators: %d: max trend run count */ __('Trend run count must be between 3 and %d.', 'agentic-bridge'), TheDotDevAgenticBridge_Readiness_Store::MAX_HISTORY_RUNS ), 'error' ); } $admin_ui['scanning'] = [ 'priorityFindingsLimit' => $priority_limit, 'trendRuns' => $trend_runs, 'allowTargetedRecheck' => $allow_targeted_recheck, ]; TheDotDevAgenticBridge_Storage::update(['admin_ui' => $admin_ui]); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'scanning', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Scanning settings saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_threshold_settings(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_threshold_settings'); $settings = TheDotDevAgenticBridge_Storage::get(); $admin_ui = self::normalized_admin_ui_settings($settings['admin_ui'] ?? []); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $strong_min = absint(wp_unslash($_POST['strong_score_min'] ?? $admin_ui['thresholds']['strongScoreMin'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $building_min = absint(wp_unslash($_POST['building_score_min'] ?? $admin_ui['thresholds']['buildingScoreMin'])); if ($strong_min < 60 || $strong_min > 100) { self::redirect_with_message(__('Strong readiness threshold must be between 60 and 100.', 'agentic-bridge'), 'error'); } if ($building_min < 20 || $building_min >= $strong_min) { self::redirect_with_message(__('Building readiness threshold must be between 20 and the strong threshold minus 1.', 'agentic-bridge'), 'error'); } $admin_ui['thresholds'] = [ 'strongScoreMin' => $strong_min, 'buildingScoreMin' => $building_min, 'attentionScoreMax' => max(0, $building_min - 1), ]; TheDotDevAgenticBridge_Storage::update(['admin_ui' => $admin_ui]); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'thresholds', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Threshold settings saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_content_type_settings(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_content_type_settings'); $settings = TheDotDevAgenticBridge_Storage::get(); $admin_ui = self::normalized_admin_ui_settings($settings['admin_ui'] ?? []); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $allow_post = isset($_POST['allow_content_post']); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $allow_page = isset($_POST['allow_content_page']); if (!$allow_post && !$allow_page) { self::redirect_with_message(__('At least one content type must remain enabled.', 'agentic-bridge'), 'error'); } $admin_ui['contentTypes'] = [ 'post' => $allow_post, 'page' => $allow_page, ]; TheDotDevAgenticBridge_Storage::update(['admin_ui' => $admin_ui]); TheDotDevAgenticBridge_Artifacts::mark_preview_stale(); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'content-types', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Content type settings saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_display_settings(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_display_settings'); $settings = TheDotDevAgenticBridge_Storage::get(); $admin_ui = self::normalized_admin_ui_settings($settings['admin_ui'] ?? []); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $inventory_per_page = absint(wp_unslash($_POST['inventory_per_page'] ?? $admin_ui['display']['inventoryPerPage'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $findings_per_page = absint(wp_unslash($_POST['findings_per_page'] ?? $admin_ui['display']['findingsPerPage'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $density_raw = sanitize_key((string) wp_unslash($_POST['display_density'] ?? $admin_ui['display']['density'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $open_evidence = isset($_POST['default_open_evidence']); if ($inventory_per_page < 10 || $inventory_per_page > 100) { self::redirect_with_message(__('Inventory rows per page must be between 10 and 100.', 'agentic-bridge'), 'error'); } if ($findings_per_page < 5 || $findings_per_page > 50) { self::redirect_with_message(__('Findings per page must be between 5 and 50.', 'agentic-bridge'), 'error'); } if (!in_array($density_raw, ['comfortable', 'compact'], true)) { self::redirect_with_message(__('Display density must be comfortable or compact.', 'agentic-bridge'), 'error'); } $admin_ui['display'] = [ 'inventoryPerPage' => $inventory_per_page, 'findingsPerPage' => $findings_per_page, 'density' => $density_raw, 'defaultOpenEvidence' => $open_evidence, ]; TheDotDevAgenticBridge_Storage::update(['admin_ui' => $admin_ui]); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'display', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Display settings saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_advanced_settings(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_advanced_settings'); $settings = TheDotDevAgenticBridge_Storage::get(); $admin_ui = self::normalized_admin_ui_settings($settings['admin_ui'] ?? []); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $show_dev_evidence = isset($_POST['show_developer_evidence']); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $show_cloud_tab = isset($_POST['show_cloud_tab']); $admin_ui['advanced'] = [ 'showDeveloperEvidence' => $show_dev_evidence, 'showCloudTab' => $show_cloud_tab, ]; TheDotDevAgenticBridge_Storage::update(['admin_ui' => $admin_ui]); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_view' => 'settings', 'thedotdev_agentic_bridge_settings_tab' => 'advanced', 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Advanced settings saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_save_content_policy(): void { self::assert_admin_post('thedotdev_agentic_bridge_save_content_policy'); $settings = TheDotDevAgenticBridge_Storage::get(); $admin_ui = self::normalized_admin_ui_settings($settings['admin_ui'] ?? []); $display = is_array($admin_ui['display'] ?? null) ? $admin_ui['display'] : []; $preview_per_page = max(10, min(100, (int) ($display['inventoryPerPage'] ?? TheDotDevAgenticBridge_Content_Policy::PREVIEW_PER_PAGE))); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $preview_page = TheDotDevAgenticBridge_Content_Policy::clamp_preview_page(absint(wp_unslash($_POST['thedotdev_agentic_bridge_preview_page'] ?? 1)), $preview_per_page); $checked = []; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). if (isset($_POST['excluded_content_ids']) && is_array($_POST['excluded_content_ids'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verified in assert_admin_post(); IDs cast below. foreach (wp_unslash($_POST['excluded_content_ids']) as $id) { $checked[] = (int) $id; } } TheDotDevAgenticBridge_Content_Policy::apply_exclusion_submission($preview_page, $preview_per_page, $checked); TheDotDevAgenticBridge_Artifacts::mark_preview_stale(); wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_preview_page' => $preview_page, 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Content policy exclusions saved.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } public static function handle_preview_pairing(): void { self::assert_admin_post('thedotdev_agentic_bridge_preview_pairing'); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $raw_api_base_url = sanitize_text_field(wp_unslash((string) ($_POST['api_base_url'] ?? TheDotDevAgenticBridge_Storage::get()['api_base_url']))); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in assert_admin_post(). $pairing_code = sanitize_text_field(wp_unslash((string) ($_POST['pairing_code'] ?? ''))); if ($pairing_code === '') { self::redirect_with_message(__('Pairing code is required to generate a connection preview.', 'agentic-bridge'), 'error'); } $result = TheDotDevAgenticBridge_Outbound_Preview::generate_pairing_preview($raw_api_base_url, $pairing_code); if (is_wp_error($result)) { self::redirect_with_message($result->get_error_message(), 'error'); } wp_safe_redirect(self::admin_page_url([ 'thedotdev_agentic_bridge_preview_page' => 1, 'thedotdev_agentic_bridge_notice' => rawurlencode(__('Connection preview generated successfully.', 'agentic-bridge')), 'thedotdev_agentic_bridge_notice_type' => 'success', ])); exit; } }