aistma_log_manager = new AISTMA_Log_Manager(); } /** * Validate prompts data before saving. * * @param array $data The prompts data to validate. * @return array Validation result with 'valid' boolean and 'message' string. */ private function validate_prompts_data( $data ) { // Check if prompts array exists if ( ! isset( $data['prompts'] ) || ! is_array( $data['prompts'] ) ) { return array( 'valid' => true, // Allow saving with no prompts 'message' => __( 'No prompts to validate', 'ai-story-maker' ) ); } // Check each prompt - if it has any changes (active, category, photos, auto_publish), it must have text foreach ( $data['prompts'] as $index => $prompt ) { // Check if prompt has any meaningful changes (not just empty text) $has_changes = false; // Check if prompt is marked as active if ( isset( $prompt['active'] ) && $prompt['active'] ) { $has_changes = true; } // Check if prompt has a category selected if ( isset( $prompt['category'] ) && ! empty( trim( $prompt['category'] ) ) ) { $has_changes = true; } // Check if prompt has photos configured if ( isset( $prompt['photos'] ) && $prompt['photos'] > 0 ) { $has_changes = true; } // Check if prompt has auto_publish enabled if ( isset( $prompt['auto_publish'] ) && $prompt['auto_publish'] ) { $has_changes = true; } // If prompt has changes but no text content, it's invalid if ( $has_changes && ( ! isset( $prompt['text'] ) || empty( trim( $prompt['text'] ) ) ) ) { return array( 'valid' => false, 'message' => sprintf( // translators: %d is the prompt number (1-based index) __( 'Prompt #%d has settings configured but no text content. Please provide text content or remove the settings.', 'ai-story-maker' ), $index + 1 ) ); } } return array( 'valid' => true, 'message' => __( 'Validation passed', 'ai-story-maker' ) ); } /** * Sanitize and escape prompt data for JSON storage. * * @param array $data The prompts data to sanitize. * @return array Sanitized prompts data. */ private function sanitize_prompts_data( $data ) { if ( ! is_array( $data ) ) { return array(); } // Sanitize default_settings if ( isset( $data['default_settings'] ) && is_array( $data['default_settings'] ) ) { foreach ( $data['default_settings'] as $key => $value ) { // Sanitize text content and escape special characters $data['default_settings'][ $key ] = $this->sanitize_text_for_json( $value ); } } // Sanitize prompts array if ( isset( $data['prompts'] ) && is_array( $data['prompts'] ) ) { foreach ( $data['prompts'] as $index => $prompt ) { if ( is_array( $prompt ) ) { // Sanitize text field (main prompt content) if ( isset( $prompt['text'] ) ) { $data['prompts'][ $index ]['text'] = $this->sanitize_text_for_json( $prompt['text'] ); } // Sanitize category field if ( isset( $prompt['category'] ) ) { $data['prompts'][ $index ]['category'] = sanitize_text_field( $prompt['category'] ); } // Sanitize keywords field if ( isset( $prompt['keywords'] ) ) { $data['prompts'][ $index ]['keywords'] = sanitize_text_field( $prompt['keywords'] ); } // Sanitize numeric fields if ( isset( $prompt['photos'] ) ) { $data['prompts'][ $index ]['photos'] = absint( $prompt['photos'] ); } // Sanitize boolean fields if ( isset( $prompt['active'] ) ) { $data['prompts'][ $index ]['active'] = (bool) $prompt['active']; } if ( isset( $prompt['auto_publish'] ) ) { $data['prompts'][ $index ]['auto_publish'] = (bool) $prompt['auto_publish']; } // Sanitize prompt_id if ( isset( $prompt['prompt_id'] ) ) { $data['prompts'][ $index ]['prompt_id'] = sanitize_text_field( $prompt['prompt_id'] ); } } } } return $data; } /** * Sanitize text content for JSON storage. * * Removes all HTML tags while preserving special characters (quotes, apostrophes, etc.) * that are important for natural language prompts sent to APIs. * * @param string $text The text to sanitize. * @return string Sanitized text. */ private function sanitize_text_for_json( $text ) { if ( ! is_string( $text ) ) { return ''; } // Strip all HTML tags to prevent injection, while preserving text content and special characters $text = wp_strip_all_tags( $text ); // Normalize whitespace but preserve line breaks for better readability $text = preg_replace( '/[ \t]+/', ' ', $text ); // Normalize spaces and tabs $text = preg_replace( '/\r\n|\r|\n/', "\n", $text ); // Normalize line endings to \n $text = trim( $text ); // Remove leading/trailing whitespace // Don't escape JSON characters here - let wp_json_encode handle it properly // This prevents double-escaping and maintains proper JSON structure return $text; } /** * Renders the Prompt Editor admin page. * * This method handles form submission, sanitizes and updates prompt settings, * then passes data to the view. * * @return void */ public function aistma_prompt_editor_render() { // Handle prompt submission. if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['save_prompts_v2'] ) ) { check_admin_referer( 'save_story_prompts', 'story_prompts_nonce' ); // Don't sanitize the JSON string itself - just decode it // Sanitization of individual values happens later in sanitize_prompts_data() $raw_prompts_input = isset( $_POST['prompts'] ) ? wp_unslash( $_POST['prompts'] ) : ''; $updated_prompts = $raw_prompts_input ? json_decode( $raw_prompts_input, true ) : array(); // Get system_content from form if provided (sanitize via sanitize_prompts_data) $system_content = isset( $_POST['system_content'] ) ? wp_unslash( $_POST['system_content'] ) : ''; // Check for JSON decode errors if ( json_last_error() !== JSON_ERROR_NONE ) { echo '
❌ ' . esc_html__( 'Error: Invalid JSON data received. Please try again.', 'ai-story-maker' ) . ' JSON Error: ' . esc_html( json_last_error_msg() ) . '
❌ ' . esc_html__( 'Validation Error: ', 'ai-story-maker' ) . esc_html( $validation_result['message'] ) . '
✅ ' . esc_html__( 'Prompts saved successfully!', 'ai-story-maker' ) . '