table_name = $wpdb->prefix . 'wpaw_conversations'; } /** * Generate a unique session ID * * @return string */ public function generate_session_id() { return substr( md5( uniqid( wp_rand(), true ) ), 0, 16 ); } /** * Create a new conversation session * * @param array $data Session data. * @return string|WP_Error Session ID or error. */ public function create_session( $data = array() ) { global $wpdb; $session_id = $this->generate_session_id(); $user_id = get_current_user_id(); $result = $wpdb->insert( $this->table_name, array( 'session_id' => $session_id, 'user_id' => $user_id, 'post_id' => isset( $data['post_id'] ) ? (int) $data['post_id'] : 0, 'title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '', 'focus_keyword' => isset( $data['focus_keyword'] ) ? sanitize_text_field( $data['focus_keyword'] ) : '', 'messages' => isset( $data['messages'] ) ? json_encode( $data['messages'] ) : '[]', 'context' => isset( $data['context'] ) ? json_encode( $data['context'] ) : '{}', 'status' => 'active', ), array( '%s', '%d', '%d', '%s', '%s', '%s', '%s', '%s' ) ); if ( false === $result ) { return new WP_Error( 'db_error', __( 'Failed to create session.', 'wp-agentic-writer' ), array( 'status' => 500 ) ); } $this->current_session_id = $session_id; return $session_id; } /** * Check if current user can access a session * * @param string $session_id Session ID. * @return bool True if user can access. */ public function current_user_can_access( $session_id ) { $session = $this->get_session( $session_id ); if ( ! $session ) { return false; } $current_user_id = get_current_user_id(); // User owns this session if ( (int) $session['user_id'] === $current_user_id ) { return true; } // For post-linked sessions, check if user can edit the post if ( ! empty( $session['post_id'] ) ) { $post_id = (int) $session['post_id']; if ( $post_id > 0 && current_user_can( 'edit_post', $post_id ) ) { return true; } } return false; } /** * Get a session by session ID (with authorization check) * * @param string $session_id Session ID. * @return array|null Session data or null if not found/not authorized. */ public function get_session( $session_id ) { global $wpdb; $session = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE session_id = %s", $session_id ), ARRAY_A ); if ( ! $session ) { return null; } // Decode JSON fields $session['messages'] = json_decode( $session['messages'], true ) ?: array(); $session['context'] = json_decode( $session['context'], true ) ?: array(); return $session; } /** * Get a session by session ID (public - for internal use only) * Use this only when authorization is handled separately * * @param string $session_id Session ID. * @return array|null Session data or null if not found. */ public function get_session_unchecked( $session_id ) { global $wpdb; $session = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE session_id = %s", $session_id ), ARRAY_A ); if ( ! $session ) { return null; } // Decode JSON fields $session['messages'] = json_decode( $session['messages'], true ) ?: array(); $session['context'] = json_decode( $session['context'], true ) ?: array(); return $session; } /** * Get session by post ID * * @param int $post_id Post ID. * @return array|null Session data or null. */ public function get_session_by_post_id( $post_id ) { global $wpdb; $session = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE post_id = %d AND status != 'archived' ORDER BY updated_at DESC LIMIT 1", $post_id ), ARRAY_A ); if ( ! $session ) { return null; } $session['messages'] = json_decode( $session['messages'], true ) ?: array(); $session['context'] = json_decode( $session['context'], true ) ?: array(); return $session; } /** * Get all active sessions for current user * * @param string $status Status filter (active, completed, archived). * @param int $limit Number of results. * @return array Sessions list. */ public function get_user_sessions( $status = 'active', $limit = 20 ) { global $wpdb; $user_id = get_current_user_id(); $posts_table = $wpdb->posts; $sessions = $wpdb->get_results( $wpdb->prepare( "SELECT c.id, c.session_id, c.post_id, c.title, c.focus_keyword, c.status, c.created_at, c.updated_at, JSON_LENGTH(c.messages) as message_count, COALESCE(p.post_status, '') as post_status FROM {$this->table_name} c LEFT JOIN {$posts_table} p ON p.ID = c.post_id WHERE c.user_id = %d AND c.status = %s ORDER BY updated_at DESC LIMIT %d", $user_id, $status, $limit ), ARRAY_A ); return $sessions ?: array(); } /** * Get all uncompleted sessions (post_id = 0) for current user * * @param int $limit Number of results. * @return array Sessions list. */ public function get_uncompleted_sessions( $limit = 20 ) { global $wpdb; $user_id = get_current_user_id(); $sessions = $wpdb->get_results( $wpdb->prepare( "SELECT id, session_id, post_id, title, focus_keyword, status, created_at, updated_at, JSON_LENGTH(messages) as message_count FROM {$this->table_name} WHERE user_id = %d AND post_id = 0 AND status = 'active' ORDER BY updated_at DESC LIMIT %d", $user_id, $limit ), ARRAY_A ); return $sessions ?: array(); } /** * Update session messages * * @param string $session_id Session ID. * @param array $messages Messages array. * @return bool True on success. */ public function update_messages( $session_id, $messages ) { global $wpdb; $result = $wpdb->update( $this->table_name, array( 'messages' => json_encode( $messages ), 'updated_at' => current_time( 'mysql' ), ), array( 'session_id' => $session_id ), array( '%s', '%s' ), array( '%s' ) ); return false !== $result; } /** * Update session context * * @param string $session_id Session ID. * @param array $context Context data. * @return bool True on success. */ public function update_context( $session_id, $context ) { global $wpdb; $result = $wpdb->update( $this->table_name, array( 'context' => json_encode( $context ), 'updated_at' => current_time( 'mysql' ), ), array( 'session_id' => $session_id ), array( '%s', '%s' ), array( '%s' ) ); return false !== $result; } /** * Link session to a post * * @param string $session_id Session ID. * @param int $post_id Post ID. * @return bool True on success. */ public function link_to_post( $session_id, $post_id ) { global $wpdb; $result = $wpdb->update( $this->table_name, array( 'post_id' => (int) $post_id, 'updated_at' => current_time( 'mysql' ), ), array( 'session_id' => $session_id ), array( '%d', '%s' ), array( '%s' ) ); return false !== $result; } /** * Update session title * * @param string $session_id Session ID. * @param string $title New title. * @return bool True on success. */ public function update_title( $session_id, $title ) { global $wpdb; $result = $wpdb->update( $this->table_name, array( 'title' => sanitize_text_field( $title ), 'updated_at' => current_time( 'mysql' ), ), array( 'session_id' => $session_id ), array( '%s', '%s' ), array( '%s' ) ); return false !== $result; } /** * Update focus keyword * * @param string $session_id Session ID. * @param string $focus_keyword Focus keyword. * @return bool True on success. */ public function update_focus_keyword( $session_id, $focus_keyword ) { global $wpdb; $result = $wpdb->update( $this->table_name, array( 'focus_keyword' => sanitize_text_field( $focus_keyword ), 'updated_at' => current_time( 'mysql' ), ), array( 'session_id' => $session_id ), array( '%s', '%s' ), array( '%s' ) ); return false !== $result; } /** * Mark session as completed * * @param string $session_id Session ID. * @return bool True on success. */ public function mark_completed( $session_id ) { global $wpdb; $result = $wpdb->update( $this->table_name, array( 'status' => 'completed', 'updated_at' => current_time( 'mysql' ), ), array( 'session_id' => $session_id ), array( '%s', '%s' ), array( '%s' ) ); return false !== $result; } /** * Delete a session * * @param string $session_id Session ID. * @return bool True on success. */ public function delete_session( $session_id ) { global $wpdb; $result = $wpdb->delete( $this->table_name, array( 'session_id' => $session_id ), array( '%s' ) ); return false !== $result; } /** * Get or create session for post * * @param int $post_id Post ID (can be 0 for new posts). * @return array Session data with session_id. */ public function get_or_create_session_for_post( $post_id = 0 ) { // Try to find existing session for this post if ( $post_id > 0 ) { $session = $this->get_session_by_post_id( $post_id ); if ( $session ) { return $session; } } // Create new session $session_id = $this->create_session( array( 'post_id' => $post_id ) ); if ( is_wp_error( $session_id ) ) { return null; } return $this->get_session( $session_id ); } /** * Set current session ID * * @param string $session_id Session ID. */ public function set_current_session( $session_id ) { $this->current_session_id = $session_id; } /** * Get current session ID * * @return string|null */ public function get_current_session_id() { return $this->current_session_id; } /** * Get current session data * * @return array|null */ public function get_current_session() { if ( ! $this->current_session_id ) { return null; } return $this->get_session( $this->current_session_id ); } /** * Check if current session has post ID * * @return bool */ public function current_session_has_post() { $session = $this->get_current_session(); return $session && $session['post_id'] > 0; } /** * Check if editor has content (for auto-save decision) * * @param int $post_id Post ID. * @return bool True if post has content blocks. */ public function post_has_content( $post_id ) { if ( $post_id <= 0 ) { return false; } $post = get_post( $post_id ); if ( ! $post ) { return false; } // Check if post has any blocks or content $blocks = parse_blocks( $post->post_content ); return ! empty( $blocks ); } /** * Get all sessions for a specific post. * * @param int $post_id Post ID. * @return array Sessions array. */ public function get_sessions_for_post( $post_id ) { global $wpdb; if ( $post_id <= 0 ) { return array(); } $sessions = $wpdb->get_results( $wpdb->prepare( "SELECT *, JSON_LENGTH(messages) as message_count FROM {$this->table_name} WHERE post_id = %d ORDER BY updated_at DESC", $post_id ), ARRAY_A ); // Decode JSON fields for each session foreach ( $sessions as &$session ) { $session['messages'] = json_decode( $session['messages'], true ) ?: array(); $session['context'] = json_decode( $session['context'], true ) ?: array(); } return $sessions ?: array(); } }