feat: consolidate docs, backend/session infra, and settings updates
This commit is contained in:
556
includes/class-conversation-manager.php
Normal file
556
includes/class-conversation-manager.php
Normal file
@@ -0,0 +1,556 @@
|
||||
<?php
|
||||
/**
|
||||
* Conversation Manager
|
||||
*
|
||||
* Handles session-based chat history with MySQL table storage.
|
||||
* Supports both post-linked and standalone sessions.
|
||||
*
|
||||
* @package WP_Agentic_Writer
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_Agentic_Writer_Conversation_Manager {
|
||||
|
||||
/**
|
||||
* Singleton instance
|
||||
*
|
||||
* @var WP_Agentic_Writer_Conversation_Manager
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Database table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $table_name;
|
||||
|
||||
/**
|
||||
* Current session ID
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $current_session_id = null;
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*
|
||||
* @return WP_Agentic_Writer_Conversation_Manager
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
global $wpdb;
|
||||
$this->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 = 'active' 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 * 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user