From 0f8c5b268433aa97650fc425b438e3fe6bec0388 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 Feb 2026 10:06:40 +0000 Subject: [PATCH] =?UTF-8?q?Dateien=20nach=20=E2=80=9Ebavarian-rank-engine/?= =?UTF-8?q?includes/Admin=E2=80=9C=20hochladen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../includes/Admin/MetaPage.php | 100 +++++++++++++ .../includes/Admin/ProviderPage.php | 140 +++++++++++++++++ .../includes/Admin/RobotsPage.php | 41 +++++ .../includes/Admin/SeoWidget.php | 72 +++++++++ .../includes/Admin/SettingsPage.php | 141 ++++++++++++++++++ 5 files changed, 494 insertions(+) create mode 100644 bavarian-rank-engine/includes/Admin/MetaPage.php create mode 100644 bavarian-rank-engine/includes/Admin/ProviderPage.php create mode 100644 bavarian-rank-engine/includes/Admin/RobotsPage.php create mode 100644 bavarian-rank-engine/includes/Admin/SeoWidget.php create mode 100644 bavarian-rank-engine/includes/Admin/SettingsPage.php diff --git a/bavarian-rank-engine/includes/Admin/MetaPage.php b/bavarian-rank-engine/includes/Admin/MetaPage.php new file mode 100644 index 0000000..1a8cfbf --- /dev/null +++ b/bavarian-rank-engine/includes/Admin/MetaPage.php @@ -0,0 +1,100 @@ + array( $this, 'sanitize' ), + ) + ); + } + + public function enqueue_assets( string $hook ): void { + if ( $hook !== 'bavarian-rank_page_bre-meta' ) { + return; + } + wp_enqueue_style( 'bre-admin', BRE_URL . 'assets/admin.css', array(), BRE_VERSION ); + wp_enqueue_script( 'bre-admin', BRE_URL . 'assets/admin.js', array( 'jquery' ), BRE_VERSION, true ); + wp_localize_script( + 'bre-admin', + 'breAdmin', + array( + 'nonce' => wp_create_nonce( 'bre_admin' ), + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + ) + ); + } + + public function sanitize( mixed $input ): array { + $input = is_array( $input ) ? $input : array(); + $clean = array(); + + $clean['meta_auto_enabled'] = ! empty( $input['meta_auto_enabled'] ); + $clean['token_mode'] = in_array( $input['token_mode'] ?? '', array( 'limit', 'full' ), true ) + ? $input['token_mode'] : 'limit'; + $clean['token_limit'] = max( 100, (int) ( $input['token_limit'] ?? 1000 ) ); + $clean['prompt'] = sanitize_textarea_field( $input['prompt'] ?? SettingsPage::getDefaultPrompt() ); + + $all_post_types = array_keys( get_post_types( array( 'public' => true ) ) ); + $clean['meta_post_types'] = array_values( + array_intersect( + array_map( 'sanitize_key', (array) ( $input['meta_post_types'] ?? array() ) ), + $all_post_types + ) + ); + + $schema_types = array( 'organization', 'author', 'speakable', 'article_about', 'breadcrumb', 'ai_meta_tags' ); + $clean['schema_enabled'] = array_values( + array_intersect( + array_map( 'sanitize_key', (array) ( $input['schema_enabled'] ?? array() ) ), + $schema_types + ) + ); + + $org_raw = $input['schema_same_as']['organization'] ?? ''; + if ( is_array( $org_raw ) ) { + $org_raw = implode( "\n", $org_raw ); + } + $clean['schema_same_as'] = array( + 'organization' => array_values( + array_filter( + array_map( + 'esc_url_raw', + array_map( 'trim', explode( "\n", $org_raw ) ) + ) + ) + ), + ); + + return $clean; + } + + public function render(): void { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + $settings = SettingsPage::getSettings(); + $post_types = get_post_types( array( 'public' => true ), 'objects' ); + $schema_labels = array( + 'organization' => __( 'Organization (sameAs Social Profiles)', 'bavarian-rank-engine' ), + 'author' => __( 'Author (sameAs Profile Links)', 'bavarian-rank-engine' ), + 'speakable' => __( 'Speakable (for AI assistants)', 'bavarian-rank-engine' ), + 'article_about' => __( 'Article about/mentions', 'bavarian-rank-engine' ), + 'breadcrumb' => __( 'BreadcrumbList', 'bavarian-rank-engine' ), + 'ai_meta_tags' => __( 'AI-optimized Meta Tags (max-snippet etc.)', 'bavarian-rank-engine' ), + ); + include BRE_DIR . 'includes/Admin/views/meta.php'; + } +} diff --git a/bavarian-rank-engine/includes/Admin/ProviderPage.php b/bavarian-rank-engine/includes/Admin/ProviderPage.php new file mode 100644 index 0000000..787d809 --- /dev/null +++ b/bavarian-rank-engine/includes/Admin/ProviderPage.php @@ -0,0 +1,140 @@ + 'https://openai.com/de-DE/api/pricing', + 'anthropic' => 'https://platform.claude.com/docs/en/about-claude/pricing', + 'gemini' => 'https://ai.google.dev/gemini-api/docs/pricing?hl=de', + 'grok' => 'https://docs.x.ai/developers/models', + ); + + public function register(): void { + add_action( 'admin_init', array( $this, 'register_settings' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); + add_action( 'wp_ajax_bre_test_connection', array( $this, 'ajax_test_connection' ) ); + add_action( 'wp_ajax_bre_get_default_prompt', array( $this, 'ajax_get_default_prompt' ) ); + } + + public function register_settings(): void { + register_setting( + 'bre_provider', + SettingsPage::OPTION_KEY_PROVIDER, + array( + 'sanitize_callback' => array( $this, 'sanitize' ), + ) + ); + } + + public function enqueue_assets( string $hook ): void { + if ( $hook !== 'bavarian-rank_page_bre-provider' ) { + return; + } + wp_enqueue_style( 'bre-admin', BRE_URL . 'assets/admin.css', array(), BRE_VERSION ); + wp_enqueue_script( 'bre-admin', BRE_URL . 'assets/admin.js', array( 'jquery' ), BRE_VERSION, true ); + wp_localize_script( + 'bre-admin', + 'breAdmin', + array( + 'nonce' => wp_create_nonce( 'bre_admin' ), + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + ) + ); + } + + public function sanitize( mixed $input ): array { + $input = is_array( $input ) ? $input : array(); + $raw_ex = get_option( SettingsPage::OPTION_KEY_PROVIDER, array() ); + $existing = is_array( $raw_ex ) ? $raw_ex : array(); + $clean = array(); + + $clean['provider'] = sanitize_key( $input['provider'] ?? 'openai' ); + + $clean['api_keys'] = array(); + foreach ( ( $input['api_keys'] ?? array() ) as $provider_id => $raw ) { + $provider_id = sanitize_key( $provider_id ); + $raw = sanitize_text_field( $raw ); + if ( $raw !== '' ) { + $clean['api_keys'][ $provider_id ] = KeyVault::encrypt( $raw ); + } elseif ( isset( $existing['api_keys'][ $provider_id ] ) ) { + $clean['api_keys'][ $provider_id ] = $existing['api_keys'][ $provider_id ]; + } + } + + $clean['models'] = array(); + foreach ( ( $input['models'] ?? array() ) as $provider_id => $model ) { + $clean['models'][ sanitize_key( $provider_id ) ] = sanitize_text_field( $model ); + } + + $clean['costs'] = array(); + foreach ( ( $input['costs'] ?? array() ) as $provider_id => $models ) { + $provider_id = sanitize_key( $provider_id ); + foreach ( (array) $models as $model_id => $prices ) { + $in = (float) str_replace( ',', '.', $prices['input'] ?? '0' ); + $out = (float) str_replace( ',', '.', $prices['output'] ?? '0' ); + if ( $in > 0 || $out > 0 ) { + $clean['costs'][ $provider_id ][ sanitize_text_field( $model_id ) ] = array( + 'input' => max( 0.0, $in ), + 'output' => max( 0.0, $out ), + ); + } + } + } + + return $clean; + } + + public function ajax_test_connection(): void { + check_ajax_referer( 'bre_admin', 'nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( __( 'Insufficient permissions.', 'bavarian-rank-engine' ) ); + } + $provider_id = sanitize_key( $_POST['provider'] ?? '' ); + $settings = SettingsPage::getSettings(); + $api_key = $settings['api_keys'][ $provider_id ] ?? ''; + if ( empty( $api_key ) ) { + wp_send_json_error( __( 'No API key saved. Please save first.', 'bavarian-rank-engine' ) ); + } + $provider = ProviderRegistry::instance()->get( $provider_id ); + if ( ! $provider ) { + wp_send_json_error( __( 'Unknown provider.', 'bavarian-rank-engine' ) ); + } + $result = $provider->testConnection( $api_key ); + if ( $result['success'] ) { + wp_send_json_success( $result['message'] ); + } else { + wp_send_json_error( $result['message'] ); + } + } + + public function ajax_get_default_prompt(): void { + check_ajax_referer( 'bre_admin', 'nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error(); + } + wp_send_json_success( SettingsPage::getDefaultPrompt() ); + } + + public function render(): void { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + $settings = SettingsPage::getSettings(); + $providers = ProviderRegistry::instance()->all(); + $masked_keys = array(); + $raw_settings = get_option( SettingsPage::OPTION_KEY_PROVIDER, array() ); + foreach ( ( $raw_settings['api_keys'] ?? array() ) as $id => $stored ) { + $plain = KeyVault::decrypt( $stored ); + $masked_keys[ $id ] = KeyVault::mask( $plain ); + } + $pricing_urls = self::PRICING_URLS; + include BRE_DIR . 'includes/Admin/views/provider.php'; + } +} diff --git a/bavarian-rank-engine/includes/Admin/RobotsPage.php b/bavarian-rank-engine/includes/Admin/RobotsPage.php new file mode 100644 index 0000000..6fa20a4 --- /dev/null +++ b/bavarian-rank-engine/includes/Admin/RobotsPage.php @@ -0,0 +1,41 @@ + $blocked ); + } + + public function render(): void { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + $settings = RobotsTxt::getSettings(); + include BRE_DIR . 'includes/Admin/views/robots.php'; + } +} diff --git a/bavarian-rank-engine/includes/Admin/SeoWidget.php b/bavarian-rank-engine/includes/Admin/SeoWidget.php new file mode 100644 index 0000000..8bb0950 --- /dev/null +++ b/bavarian-rank-engine/includes/Admin/SeoWidget.php @@ -0,0 +1,72 @@ +post_title ); + ?> +
+ + + + + + + + + + + + + +
+ / 60 +
+
+ +
+
+ + +
+
+ 'openai', + 'api_keys' => array(), + 'models' => array(), + 'meta_auto_enabled' => true, + 'meta_post_types' => array( 'post', 'page' ), + 'token_mode' => 'limit', + 'token_limit' => 1000, + 'prompt' => self::getDefaultPrompt(), + 'schema_enabled' => array(), + 'schema_same_as' => array(), + 'costs' => array(), + ); + + $saved_provider = get_option( self::OPTION_KEY_PROVIDER, array() ); + $saved_provider = is_array( $saved_provider ) ? $saved_provider : array(); + + $saved_meta = get_option( self::OPTION_KEY_META, array() ); + $saved_meta = is_array( $saved_meta ) ? $saved_meta : array(); + + $settings = array_merge( $defaults, $saved_provider, $saved_meta ); + + foreach ( $settings['api_keys'] as $id => $stored ) { + $decrypted = KeyVault::decrypt( $stored ); + // Fallback: if decrypt returns empty, the stored value is a legacy plain-text key + $settings['api_keys'][ $id ] = $decrypted !== '' ? $decrypted : $stored; + } + + return $settings; + } + + public static function getDefaultPrompt(): string { + return 'Schreibe eine SEO-optimierte Meta-Beschreibung für den folgenden Artikel.' . "\n" + . 'Die Beschreibung soll für menschliche Leser verständlich und hilfreich sein,' . "\n" + . 'den Inhalt treffend zusammenfassen und zwischen 150 und 160 Zeichen lang sein.' . "\n" + . 'Schreibe die Meta-Beschreibung auf {language}.' . "\n" + . 'Antworte ausschließlich mit der Meta-Beschreibung, ohne Erklärung.' . "\n\n" + . 'Titel: {title}' . "\n" + . 'Inhalt: {content}'; + } + + /** + * Kept for backwards compatibility — used by BulkPage and tests. + * Validates and sanitises a combined settings array (provider + meta fields). + */ + public function sanitize_settings( mixed $input ): array { + $input = is_array( $input ) ? $input : array(); + $raw_existing = get_option( self::OPTION_KEY_PROVIDER, array() ); + $existing = is_array( $raw_existing ) ? $raw_existing : array(); + $clean = array(); + + $clean['provider'] = sanitize_key( $input['provider'] ?? 'openai' ); + $clean['meta_auto_enabled'] = ! empty( $input['meta_auto_enabled'] ); + $clean['token_mode'] = in_array( $input['token_mode'] ?? '', array( 'limit', 'full' ), true ) + ? $input['token_mode'] : 'limit'; + $clean['token_limit'] = max( 100, (int) ( $input['token_limit'] ?? 1000 ) ); + $clean['prompt'] = sanitize_textarea_field( $input['prompt'] ?? self::getDefaultPrompt() ); + + $clean['api_keys'] = array(); + foreach ( ( $input['api_keys'] ?? array() ) as $provider_id => $raw ) { + $provider_id = sanitize_key( $provider_id ); + $raw = sanitize_text_field( $raw ); + if ( $raw !== '' ) { + $clean['api_keys'][ $provider_id ] = KeyVault::encrypt( $raw ); + } elseif ( isset( $existing['api_keys'][ $provider_id ] ) ) { + $clean['api_keys'][ $provider_id ] = $existing['api_keys'][ $provider_id ]; + } + } + + $clean['models'] = array(); + foreach ( ( $input['models'] ?? array() ) as $provider_id => $model ) { + $clean['models'][ sanitize_key( $provider_id ) ] = sanitize_text_field( $model ); + } + + $clean['costs'] = array(); + foreach ( ( $input['costs'] ?? array() ) as $provider_id => $models ) { + $provider_id = sanitize_key( $provider_id ); + foreach ( (array) $models as $model_id => $prices ) { + $model_id = sanitize_text_field( $model_id ); + $clean['costs'][ $provider_id ][ $model_id ] = array( + 'input' => max( 0.0, (float) ( $prices['input'] ?? 0 ) ), + 'output' => max( 0.0, (float) ( $prices['output'] ?? 0 ) ), + ); + } + } + + $all_post_types = array_keys( get_post_types( array( 'public' => true ) ) ); + $clean['meta_post_types'] = array_values( + array_intersect( + array_map( 'sanitize_key', (array) ( $input['meta_post_types'] ?? array() ) ), + $all_post_types + ) + ); + + $schema_types = array( 'organization', 'author', 'speakable', 'article_about', 'breadcrumb', 'ai_meta_tags' ); + $clean['schema_enabled'] = array_values( + array_intersect( + array_map( 'sanitize_key', (array) ( $input['schema_enabled'] ?? array() ) ), + $schema_types + ) + ); + + $org_raw = $input['schema_same_as']['organization'] ?? ''; + if ( is_array( $org_raw ) ) { + $org_raw = implode( "\n", $org_raw ); + } + $clean['schema_same_as'] = array( + 'organization' => array_values( + array_filter( + array_map( + 'esc_url_raw', + array_map( 'trim', explode( "\n", $org_raw ) ) + ) + ) + ), + ); + + return $clean; + } +}