get_session( $session_id ); $effective_session_id = $session_id; // Migrate legacy history on read if no session exists but post has legacy data. if ( ! $session && $post_id > 0 ) { $legacy_history = get_post_meta( $post_id, '_wpaw_chat_history', true ); $is_migrated = get_post_meta( $post_id, '_wpaw_chat_history_migrated', true ); if ( ! empty( $legacy_history ) && empty( $is_migrated ) ) { $migrated_session_id = $this->migrate_legacy_chat_history( $post_id, $session_id ); // Use the session_id returned from migration (may be newly created) $effective_session_id = is_string( $migrated_session_id ) ? $migrated_session_id : $session_id; $session = $manager->get_session( $effective_session_id ); } } if ( ! $session ) { return $this->get_empty_context( $effective_session_id, $post_id ); } // If post_id is provided, get post-specific data $post_data = array(); if ( $post_id > 0 ) { $post_data = $this->get_post_context( $post_id ); } return array( 'session_id' => $effective_session_id, 'post_id' => $post_id, 'messages' => $session['messages'] ?? array(), 'context' => $session['context'] ?? array(), 'plan' => $post_data['plan'] ?? null, 'post_config' => $post_data['post_config'] ?? $this->get_default_post_config(), 'title' => $session['title'] ?? '', 'focus_keyword' => $session['focus_keyword'] ?? '', 'status' => $session['status'] ?? 'active', ); } /** * Get empty context structure. * * @param string $session_id Session ID. * @param int $post_id Post ID. * @return array Empty context. */ private function get_empty_context( $session_id, $post_id ) { return array( 'session_id' => $session_id, 'post_id' => $post_id, 'messages' => array(), 'context' => array(), 'plan' => null, 'post_config' => $this->get_default_post_config(), 'title' => '', 'focus_keyword' => '', 'status' => 'active', ); } /** * Get post-specific context (plan, config). * * @param int $post_id Post ID. * @return array Post context. */ public function get_post_context( $post_id ) { $plan = get_post_meta( $post_id, '_wpaw_plan', true ); if ( ! is_array( $plan ) ) { $plan = null; } $post_config = get_post_meta( $post_id, '_wpaw_post_config', true ); if ( ! is_array( $post_config ) ) { $post_config = $this->get_default_post_config(); } return array( 'plan' => $plan, 'post_config' => $post_config, ); } /** * Save messages to session. * * @param string $session_id Session ID. * @param array $messages Messages array. * @return bool Success. */ public function save_messages( $session_id, $messages ) { $manager = WP_Agentic_Writer_Conversation_Manager::get_instance(); return $manager->update_messages( $session_id, $messages ); } /** * Add a message to the session. * * @param string $session_id Session ID. * @param array $message Message data. * @return bool Success. */ public function add_message( $session_id, $message ) { $manager = WP_Agentic_Writer_Conversation_Manager::get_instance(); $session = $manager->get_session( $session_id ); if ( ! $session ) { return false; } $messages = $session['messages'] ?? array(); $messages[] = $message; return $manager->update_messages( $session_id, $messages ); } /** * Save plan to post meta. * * @param int $post_id Post ID. * @param array $plan Plan data. * @return bool Success. */ public function save_plan( $post_id, $plan ) { if ( $post_id <= 0 ) { return false; } return update_post_meta( $post_id, '_wpaw_plan', $plan ) !== false; } /** * Get plan from post meta. * * @param int $post_id Post ID. * @return array|null Plan or null. */ public function get_plan( $post_id ) { if ( $post_id <= 0 ) { return null; } $plan = get_post_meta( $post_id, '_wpaw_plan', true ); return is_array( $plan ) ? $plan : null; } /** * Save post config to post meta. * * @param int $post_id Post ID. * @param array $post_config Post config. * @return bool Success. */ public function save_post_config( $post_id, $post_config ) { if ( $post_id <= 0 ) { return false; } return update_post_meta( $post_id, '_wpaw_post_config', $post_config ) !== false; } /** * Get post config from post meta. * * @param int $post_id Post ID. * @return array Post config. */ public function get_post_config( $post_id ) { if ( $post_id <= 0 ) { return $this->get_default_post_config(); } $config = get_post_meta( $post_id, '_wpaw_post_config', true ); return is_array( $config ) ? wp_parse_args( $config, $this->get_default_post_config() ) : $this->get_default_post_config(); } /** * Get default post config. * * @return array Default config. */ public function get_default_post_config() { $settings = get_option( 'wp_agentic_writer_settings', array() ); return array( 'article_length' => 'medium', 'language' => 'auto', 'tone' => '', 'audience' => '', 'experience_level' => 'general', 'include_images' => true, 'web_search' => false, 'default_mode' => 'writing', 'seo_focus_keyword' => '', 'seo_secondary_keywords' => '', 'seo_meta_description' => '', 'seo_enabled' => false, ); } /** * Migrate legacy chat history from post meta to session. * * @param int $post_id Post ID. * @param string $session_id Optional session ID to use. If not provided and no * session exists, creates a new one. * @return string|false Session ID used for migration, or false on failure. */ public function migrate_legacy_chat_history( $post_id, $session_id = '' ) { if ( $post_id <= 0 ) { return false; } $legacy_history = get_post_meta( $post_id, '_wpaw_chat_history', true ); if ( empty( $legacy_history ) || ! is_array( $legacy_history ) ) { return $session_id ?: true; // Nothing to migrate, return existing or true } $manager = WP_Agentic_Writer_Conversation_Manager::get_instance(); // Try to find existing session for this post if no session_id provided $sessions = array(); if ( empty( $session_id ) ) { $sessions = $manager->get_sessions_for_post( $post_id ); } if ( ! empty( $sessions ) ) { // Append to existing session $session = $sessions[0]; $existing_messages = $session['messages'] ?? array(); // Merge legacy messages (avoid duplicates based on content hash) $existing_hashes = array(); foreach ( $existing_messages as $msg ) { $existing_hashes[] = $this->get_message_hash( $msg ); } foreach ( $legacy_history as $msg ) { $hash = $this->get_message_hash( $msg ); if ( ! in_array( $hash, $existing_hashes, true ) ) { $existing_messages[] = $msg; } } $manager->update_messages( $session['session_id'], $existing_messages ); $migrated_session_id = $session['session_id']; } else { // Create new session with legacy messages $new_session_id = $manager->create_session( array( 'post_id' => $post_id, 'messages' => $legacy_history, 'title' => 'Migrated from legacy', ) ); $migrated_session_id = is_string( $new_session_id ) ? $new_session_id : ( ! empty( $session_id ) ? $session_id : '' ); } // Delete legacy meta after successful migration and mark as migrated. delete_post_meta( $post_id, '_wpaw_chat_history' ); update_post_meta( $post_id, '_wpaw_chat_history_migrated', current_time( 'mysql' ) ); return $migrated_session_id; } /** * Get hash for message deduplication. * * @param array $message Message. * @return string Hash. */ private function get_message_hash( $message ) { $content = $message['content'] ?? ''; $role = $message['role'] ?? ''; return md5( $role . ':' . $content ); } /** * Clear context for a session and post. * * @param string $session_id Session ID. * @param int $post_id Post ID. * @return bool Success. */ public function clear_context( $session_id, $post_id = 0 ) { $manager = WP_Agentic_Writer_Conversation_Manager::get_instance(); // Clear specific session if provided if ( $session_id ) { $manager->update_messages( $session_id, array() ); } elseif ( $post_id > 0 ) { // No session_id provided - clear all active sessions for this post $sessions = $manager->get_sessions_for_post( $post_id ); foreach ( $sessions as $session ) { if ( ! empty( $session['session_id'] ) && 'active' === ( $session['status'] ?? '' ) ) { $manager->update_messages( $session['session_id'], array() ); } } } // Clear post meta if post_id provided if ( $post_id > 0 ) { delete_post_meta( $post_id, '_wpaw_plan' ); delete_post_meta( $post_id, '_wpaw_memory' ); delete_post_meta( $post_id, '_wpaw_chat_history' ); // Keep _wpaw_post_config as it's user settings } return true; } /** * Get context summary for display. * * @param array $context Context data. * @return string Human-readable summary. */ public function get_context_summary( $context ) { $parts = array(); // Message count $msg_count = count( $context['messages'] ?? array() ); $parts[] = sprintf( _n( '%d message', '%d messages', $msg_count, 'wp-agentic-writer' ), $msg_count ); // Plan status if ( ! empty( $context['plan'] ) ) { $sections = count( $context['plan']['sections'] ?? array() ); $parts[] = sprintf( _n( '%d section in plan', '%d sections in plan', $sections, 'wp-agentic-writer' ), $sections ); } else { $parts[] = 'No plan'; } // Focus keyword if ( ! empty( $context['focus_keyword'] ) ) { $parts[] = 'Focus: ' . $context['focus_keyword']; } return implode( ' | ', $parts ); } /** * Build context for AI prompt. * * @param array $context Context data. * @param int $max_messages Maximum messages to include. * @return array Messages for AI. */ public function build_ai_context( $context, $max_messages = 20 ) { $messages = $context['messages'] ?? array(); // Limit to most recent messages if ( count( $messages ) > $max_messages ) { $messages = array_slice( $messages, -$max_messages ); } // Add context summary if available $context_summary = array( 'role' => 'system', 'content' => 'Current context: ' . $this->get_context_summary( $context ), ); return array_merge( array( $context_summary ), $messages ); } }