save_conversation( $conversation_id, array( 'id' => $conversation_id, 'created_at' => time(), 'updated_at' => time(), 'session' => Aivexa_Security::get_session_token(), 'messages' => array(), 'response_ids' => array(), ) ); $this->increment_conversation_count(); return $conversation_id; } /** * Get conversation by ID. * * @param string $conversation_id Conversation ID. * @return array|null */ public function get_conversation( string $conversation_id ): ?array { if ( '' === $conversation_id ) { return null; } $data = get_transient( self::TRANSIENT_PREFIX . $conversation_id ); if ( ! is_array( $data ) ) { return null; } if ( ! $this->can_access_conversation( $data ) ) { return null; } return $data; } /** * Append a message. * * @param string $conversation_id Conversation ID. * @param array $message Message. */ public function append_message( string $conversation_id, array $message ): void { $conversation = $this->get_conversation( $conversation_id ); if ( null === $conversation ) { return; } $conversation['messages'][] = $message; $conversation['updated_at'] = time(); $max = (int) Aivexa_Settings::get( 'max_conversation_messages', 20 ); if ( count( $conversation['messages'] ) > $max ) { $conversation['messages'] = array_slice( $conversation['messages'], -$max ); } $this->save_conversation( $conversation_id, $conversation ); } /** * Store OpenAI response ID for multi-turn continuity. * * @param string $conversation_id Conversation ID. * @param string $response_id Response ID. */ public function append_response_id( string $conversation_id, string $response_id ): void { $conversation = $this->get_conversation( $conversation_id ); if ( null === $conversation ) { return; } $conversation['response_ids'][] = sanitize_text_field( $response_id ); $conversation['updated_at'] = time(); $this->save_conversation( $conversation_id, $conversation ); } /** * Get latest response ID. * * @param string $conversation_id Conversation ID. */ public function get_latest_response_id( string $conversation_id ): string { $conversation = $this->get_conversation( $conversation_id ); if ( null === $conversation || empty( $conversation['response_ids'] ) ) { return ''; } $ids = $conversation['response_ids']; return (string) end( $ids ); } /** * Clear conversation. * * @param string $conversation_id Conversation ID. */ public function clear_conversation( string $conversation_id ): bool { $conversation = $this->get_conversation( $conversation_id ); if ( null === $conversation ) { return false; } delete_transient( self::TRANSIENT_PREFIX . $conversation_id ); return true; } /** * Save conversation. * * @param string $conversation_id Conversation ID. * @param array $conversation Conversation data. */ private function save_conversation( string $conversation_id, array $conversation ): void { set_transient( self::TRANSIENT_PREFIX . $conversation_id, $conversation, self::TTL ); } /** * Verify session ownership. * * @param array $conversation Conversation. */ private function can_access_conversation( array $conversation ): bool { if ( current_user_can( 'manage_options' ) ) { return true; } $session = isset( $conversation['session'] ) ? (string) $conversation['session'] : ''; return $session === Aivexa_Security::get_session_token(); } /** * Increment stats counter. */ private function increment_conversation_count(): void { $stats = get_option( 'aivexa_stats', array() ); if ( ! is_array( $stats ) ) { $stats = array(); } $stats['total_conversations'] = isset( $stats['total_conversations'] ) ? (int) $stats['total_conversations'] + 1 : 1; update_option( 'aivexa_stats', $stats, false ); } /** * Get conversations associated with a session token. * * @param string $session Session token. * @return array> */ public function get_conversations_for_session( string $session ): array { global $wpdb; if ( '' === $session || ! $wpdb instanceof wpdb ) { return array(); } $like = $wpdb->esc_like( '_transient_' . self::TRANSIENT_PREFIX ) . '%'; $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s", $like ) ); $items = array(); if ( ! is_array( $rows ) ) { return $items; } foreach ( $rows as $row ) { if ( ! isset( $row->option_name, $row->option_value ) ) { continue; } $data = maybe_unserialize( $row->option_value ); if ( ! is_array( $data ) ) { continue; } if ( (string) ( $data['session'] ?? '' ) !== $session ) { continue; } $conversation_id = str_replace( '_transient_' . self::TRANSIENT_PREFIX, '', (string) $row->option_name ); $items[ $conversation_id ] = $data; } return $items; } }