557 lines
12 KiB
PHP
557 lines
12 KiB
PHP
<?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();
|
|
}
|
|
}
|