plugin = $plugin; $this->setup_pages(); add_action( 'admin_init', array( $this, 'handle_save_settings_request' ) ); } /** * Defines the structure of the plugin's admin pages. * Populates the $this->pages array. */ private function setup_pages() { $this->pages = array( 'dashboard' => array( 'slug' => 'ainp-ai-native-publisher', // Updated main slug 'title' => __( 'Dashboard', 'ainp-ai-native-publisher' ), 'view' => 'tab-dashboard.php', ), 'sources' => array( 'slug' => 'ainp-sources', 'title' => __( 'Sources & General', 'ainp-ai-native-publisher' ), 'view' => 'tab-sources-settings.php', ), // 'fetch_url' removed as per dead code cleanup requirements. 'auto_search' => array( 'slug' => 'ainp-auto-search', 'title' => __( 'Automatic Search', 'ainp-ai-native-publisher' ), 'view' => 'tab-auto-search.php', ), 'api' => array( 'slug' => 'ainp-api-settings', 'title' => __( 'API Settings', 'ainp-ai-native-publisher' ), 'view' => 'tab-api-settings.php', ), 'prompts' => array( 'slug' => 'ainp-prompts', 'title' => __( 'AI Prompts', 'ainp-ai-native-publisher' ), 'view' => 'tab-ai-prompts.php', ), 'modules' => array( 'slug' => 'ainp-modules', 'title' => __( 'Modules', 'ainp-ai-native-publisher' ), 'view' => 'tab-modules.php', ), 'logs' => array( 'slug' => 'ainp-logs', 'title' => __( 'Status & Logs', 'ainp-ai-native-publisher' ), 'view' => 'tab-status-logs.php', ), 'diagnostics' => array( 'slug' => 'ainp-diagnostics', 'title' => __( 'Diagnostics', 'ainp-ai-native-publisher' ), 'view' => 'tab-diagnostics.php', ), 'advanced' => array( 'slug' => 'ainp-advanced', 'title' => __( 'Advanced', 'ainp-ai-native-publisher' ), 'view' => 'tab-advanced.php', ), ); } /** * Adds the main menu item and submenu pages to the WordPress admin menu. * Hooked to 'admin_menu'. */ public function admin_menu_page() { add_menu_page( __( 'AI Native Publisher', 'ainp-ai-native-publisher' ), __( 'AI Native Publisher', 'ainp-ai-native-publisher' ), 'manage_options', $this->pages['dashboard']['slug'], array( $this, 'render_page_content' ), 'dashicons-rss', 25 ); foreach ( $this->pages as $key => $page ) { add_submenu_page( $this->pages['dashboard']['slug'], $page['title'], $page['title'], 'manage_options', $page['slug'], array( $this, 'render_page_content' ) ); } } /** * Main handler for settings save requests on admin_init. * Performs security checks and delegates saving logic to specific methods. */ public function handle_save_settings_request() { $is_saving_sources = isset( $_POST['ainp_save_sources_settings'] ); $is_saving_general = isset( $_POST['ainp_save_general_settings'] ); $is_saving_generic = isset( $_POST['ainp_save_settings'] ); $is_saving_api = isset( $_POST['ainp_save_api_settings'] ); $is_saving_modules = isset( $_POST['ainp_save_modules_settings'] ); $is_saving_advanced = isset( $_POST['ainp_save_advanced_settings'] ); $is_saving_auto = isset( $_POST['ainp_save_auto_search_settings'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check is done via check_admin_referer below. $current_page_slug = isset( $_POST['current_page_slug'] ) ? sanitize_key( wp_unslash( $_POST['current_page_slug'] ) ) : null; if ( ! $is_saving_sources && ! $is_saving_general && ! $is_saving_generic && ! $is_saving_api && ! $is_saving_modules && ! $is_saving_advanced && ! $is_saving_auto ) { return; } if ( ! check_admin_referer( 'ainp_settings_nonce' ) ) { wp_die( esc_html__( 'Security check failed. Please try again.', 'ainp-ai-native-publisher' ) ); } if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to save these settings.', 'ainp-ai-native-publisher' ) ); } $new_options = $this->plugin->options; $settings_saved = false; // Delegate saving based on which button was pressed and the page slug. if ( $is_saving_sources && 'ainp-sources' === $current_page_slug ) { $new_options = $this->save_sources_settings( $new_options, $_POST ); $settings_saved = true; $this->plugin->logger->add_log_entry( 'info', __( 'Sources configuration saved.', 'ainp-ai-native-publisher' ) ); } if ( $is_saving_general && 'ainp-sources' === $current_page_slug ) { $new_options = $this->save_general_settings( $new_options, $_POST ); $settings_saved = true; $this->plugin->logger->add_log_entry( 'info', __( 'General settings saved.', 'ainp-ai-native-publisher' ) ); } if ( $is_saving_api ) { $new_options = $this->save_api_settings( $new_options, $_POST ); $settings_saved = true; $this->plugin->logger->add_log_entry( 'info', __( 'API settings saved.', 'ainp-ai-native-publisher' ) ); } elseif ( $is_saving_modules ) { $new_options = $this->save_modules_settings( $new_options, $_POST ); $settings_saved = true; $this->plugin->logger->add_log_entry( 'info', __( 'Module settings saved.', 'ainp-ai-native-publisher' ) ); } elseif ( $is_saving_advanced ) { $new_options = $this->save_advanced_settings( $new_options, $_POST ); $settings_saved = true; $this->plugin->logger->add_log_entry( 'info', __( 'Advanced settings saved.', 'ainp-ai-native-publisher' ) ); } elseif ( $is_saving_generic ) { switch ( $current_page_slug ) { case 'ainp-prompts': $new_options = $this->save_prompts_settings( $new_options, $_POST ); $settings_saved = true; break; case 'ainp-auto-search': $new_options = $this->save_auto_search_settings( $new_options, $_POST ); $settings_saved = true; break; } } if ( $settings_saved ) { update_option( 'ainp_settings', $new_options ); $this->plugin->load_options(); if ( isset( $this->plugin->cron_handler ) ) { $this->plugin->cron_handler->schedule_or_clear_auto_search(); } add_settings_error( 'ainp_notices', 'settings_updated', __( 'Settings saved successfully.', 'ainp-ai-native-publisher' ), 'success' ); } if ( $current_page_slug ) { wp_safe_redirect( admin_url( 'admin.php?page=' . $current_page_slug ) ); exit; } } /** * Saves Sources settings. * UPDATED: Removes quantitative limits on sources (Guideline 5 compliance). */ private function save_sources_settings( $options, $post_data ) { $sanitized_sources = array(); // No limit checks here. We save all valid sources provided by the user. if ( isset( $post_data['sources'] ) && is_array( $post_data['sources'] ) ) { foreach ( $post_data['sources'] as $source ) { if ( ! empty( $source['url'] ) ) { $sanitized_sources[] = array( 'url' => esc_url_raw( trim( wp_unslash( $source['url'] ) ) ), 'type' => 'rss', // FORCE RSS 'selector_links' => '', 'selector_title' => '', 'selector_content' => '', ); } } } $options['sources_config'] = $sanitized_sources; return $options; } /** * Saves General settings. */ private function save_general_settings( $options, $post_data ) { $options['enable_feed_cache'] = true; $options['feed_cache_duration'] = 300; $options['item_processing_timeout'] = 180; $options['respect_interval'] = 'disabled'; $options['default_post_status'] = isset( $post_data['ainp_default_post_status'] ) ? sanitize_key( wp_unslash( $post_data['ainp_default_post_status'] ) ) : 'draft'; $options['default_author_id'] = isset( $post_data['ainp_default_author_id'] ) ? absint( wp_unslash( $post_data['ainp_default_author_id'] ) ) : 1; $options['target_language'] = isset( $post_data['ainp_target_language'] ) ? sanitize_text_field( wp_unslash( $post_data['ainp_target_language'] ) ) : 'Portuguese (Brazil)'; return $options; } /** * Saves API settings. * ENFORCES FREE LIMITS: Force Groq/Unsplash providers. */ private function save_api_settings( $options, $post_data ) { $options['main_ia_provider'] = 'groq'; $options['main_image_provider'] = 'unsplash'; if ( array_key_exists( 'ainp_groq_api_key', $post_data ) ) { $options['groq_api_key'] = sanitize_text_field( wp_unslash( $post_data['ainp_groq_api_key'] ) ); } if ( array_key_exists( 'ainp_unsplash_access_key', $post_data ) ) { $options['unsplash_access_key'] = sanitize_text_field( wp_unslash( $post_data['ainp_unsplash_access_key'] ) ); } $options['groq_model_selection'] = 'llama-3.1-8b-instant'; $options['gemini_model_selection'] = 'gemini-1.5-flash-001'; return $options; } /** * Saves AI Prompts settings. */ private function save_prompts_settings( $options, $post_data ) { // Just return options without saving changes for prompts, essentially locking them. // Avoids showing an error unless user tried to change them, but since input fields should be read-only in view, this is a fallback security. return $options; } /** * Saves Modules settings. */ private function save_modules_settings( $options, $post_data ) { $options['module_rewrite_content'] = isset( $post_data['ainp_module_rewrite_content'] ); $options['module_generate_title'] = isset( $post_data['ainp_module_generate_title'] ); $options['module_generate_category'] = isset( $post_data['ainp_module_generate_category'] ); $options['module_generate_tags'] = isset( $post_data['ainp_module_generate_tags'] ); $options['module_generate_images'] = isset( $post_data['ainp_module_generate_images'] ); $options['module_source_credit'] = isset( $post_data['ainp_module_source_credit'] ); if ( array_key_exists( 'ainp_source_credit_format', $post_data ) ) { $options['source_credit_format'] = wp_kses_post( wp_unslash( $post_data['ainp_source_credit_format'] ) ); } $options['module_force_ia_image'] = false; $options['module_add_watermark'] = false; $options['module_keyword_filters'] = false; return $options; } /** * Saves Automatic Search settings. */ private function save_auto_search_settings( $options, $post_data ) { // Ensure free version limits are enforced silently $options['enable_auto_search'] = false; $options['auto_search_interval'] = 'disabled'; $options['auto_search_depth'] = '1'; return $options; } /** * Saves Advanced settings. */ private function save_advanced_settings( $options, $post_data ) { $options['enable_execution_timeout_guard'] = true; $options['delete_awaiting_image_on_completion'] = true; $options['image_processing_mode'] = 'async'; return $options; } /** * Enqueues admin-specific scripts and styles for plugin pages. */ public function enqueue_admin_scripts( $hook_suffix ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Page load check, no action performed. $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // Updated check for new slug prefix 'ainp-' or main slug 'ainp-ai-native-publisher' if ( strpos( $page, 'ainp-' ) !== 0 && 'ainp-ai-native-publisher' !== $page ) { return; } wp_enqueue_style( 'ainp-admin-style', AINP_PLUGIN_URL . 'css/admin-style.css', array(), AINP_VERSION ); wp_enqueue_script( 'ainp-admin-ui', AINP_PLUGIN_URL . 'js/admin-ui.js', array( 'jquery' ), AINP_VERSION, true ); wp_enqueue_script( 'ainp-admin-ajax', AINP_PLUGIN_URL . 'js/admin-ajax.js', array( 'jquery', 'ainp-admin-ui' ), AINP_VERSION, true ); wp_enqueue_script( 'ainp-admin-events', AINP_PLUGIN_URL . 'js/admin-events.js', array( 'jquery', 'ainp-admin-ajax' ), AINP_VERSION, true ); wp_localize_script( 'ainp-admin-ajax', 'ainp_ajax', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'ainp_nonce' ), 'maintenance_nonce' => wp_create_nonce( 'ainp_maintenance_nonce' ), 'i18n' => array( 'processing' => __( 'Processing...', 'ainp-ai-native-publisher' ), 'processing_inprogress' => __( 'An operation is already in progress. Please wait.', 'ainp-ai-native-publisher' ), 'success_generic' => __( 'Action completed successfully.', 'ainp-ai-native-publisher' ), 'error_generic' => __( 'An unknown error occurred. Please check the logs.', 'ainp-ai-native-publisher' ), 'error_ajax' => __( 'AJAX request failed. Check browser console and logs.', 'ainp-ai-native-publisher' ), 'status_cancelled' => __( 'Search cancelled by user.', 'ainp-ai-native-publisher' ), 'status_idle' => __( 'System is idle.', 'ainp-ai-native-publisher' ), 'url_missing' => __( 'Please enter a valid source URL.', 'ainp-ai-native-publisher' ), 'cancelling' => __( 'Cancelling...', 'ainp-ai-native-publisher' ), 'dismiss_notice' => __( 'Dismiss this notice.', 'ainp-ai-native-publisher' ), 'confirm_yes' => __( 'Yes', 'ainp-ai-native-publisher' ), 'confirm_no' => __( 'No', 'ainp-ai-native-publisher' ), 'cancel_confirm' => __( 'Are you sure you want to cancel the current process?', 'ainp-ai-native-publisher' ), 'confirm_reset_jobs' => __( 'Are you sure you want to stop the current fetch process? This cannot be undone.', 'ainp-ai-native-publisher' ), 'confirm_clear_logs' => __( 'Are you sure you want to delete the entire activity history? This cannot be undone.', 'ainp-ai-native-publisher' ), 'confirm_reset_status' => __( 'Are you sure you want to force the status to Idle? Use this only if the system is stuck.', 'ainp-ai-native-publisher' ), 'testing_apis' => __( 'Testing API connections...', 'ainp-ai-native-publisher' ), 'test_write_permission' => __( 'Test Uploads Folder Permission', 'ainp-ai-native-publisher' ), ), ) ); } /** * Renders the content of the currently selected admin page. */ public function render_page_content() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Page load check, no action performed. $current_slug = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : $this->pages['dashboard']['slug']; $current_page = null; foreach ( $this->pages as $page_config ) { if ( $page_config['slug'] === $current_slug ) { $current_page = $page_config; break; } } if ( ! $current_page ) { wp_die( esc_html__( 'Invalid plugin page specified.', 'ainp-ai-native-publisher' ) ); } $view_file = AINP_PLUGIN_DIR . 'admin/views/' . $current_page['view']; ?>

'; wp_nonce_field( 'ainp_settings_nonce' ); echo ''; } if ( file_exists( $view_file ) ) { include $view_file; } else { echo '

' . esc_html__( 'Error: The view file for this page could not be found.', 'ainp-ai-native-publisher' ) . '

'; } $pages_without_generic_save = array( 'ainp-ai-native-publisher', // Updated main slug 'ainp-logs', 'ainp-diagnostics', 'ainp-sources', 'ainp-api-settings', 'ainp-modules', 'ainp-advanced' ); $pages_with_custom_buttons = array( 'ainp-sources', 'ainp-api-settings', 'ainp-modules', 'ainp-advanced', 'ainp-auto-search' ); if ( $needs_main_form && ! in_array( $current_slug, $pages_without_generic_save, true ) && ! in_array( $current_slug, $pages_with_custom_buttons, true ) ) { submit_button( __( 'Save Changes', 'ainp-ai-native-publisher' ), 'primary', 'ainp_save_settings' ); } if ( $needs_main_form ) { echo ''; } ?>