Privacy - Source Excerpt 04
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/includes/Privacy/Privacy.php so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Privacy/Privacy.php
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('conversations') . ' WHERE owner_user_id = %d ORDER BY created_utc ASC, id ASC',
$user_id
);
$rows = $wpdb->get_results($sql, ARRAY_A);
if (!is_array($rows)) {
return [];
}
$items = [];
foreach ($rows as $row) {
$data = [];
$this->add_export_pair($data, __('Conversation ID', 'spiralist-workspace'), $row['id'] ?? 0);
$this->add_export_pair($data, __('Prompt ID', 'spiralist-workspace'), $row['prompt_post_id'] ?? 0);
$this->add_export_pair($data, __('Prompt title', 'spiralist-workspace'), $this->prompt_title((int) ($row['prompt_post_id'] ?? 0)));
$this->add_export_pair($data, __('Title', 'spiralist-workspace'), $row['title'] ?? '');
$this->add_export_pair($data, __('Visibility', 'spiralist-workspace'), $row['visibility'] ?? '');
$this->add_export_pair($data, __('Conversation settings JSON', 'spiralist-workspace'), $row['conversation_settings_json'] ?? '');
$this->add_export_pair($data, __('Runtime reference', 'spiralist-workspace'), $row['openai_reference'] ?? '');
$this->add_export_pair($data, __('Created UTC', 'spiralist-workspace'), $row['created_utc'] ?? '');
$this->add_export_pair($data, __('Updated UTC', 'spiralist-workspace'), $row['updated_utc'] ?? '');
$this->add_export_pair($data, __('Archived UTC', 'spiralist-workspace'), $row['archived_utc'] ?? '');
$items[] = $this->build_export_item(
'spiralist_workspace_conversations',
__('Workspace Conversations', 'spiralist-workspace'),
'workspace-conversation-' . (int) ($row['id'] ?? 0),
$data
);
}
return $items;
}
/**
* Returns conversation-message export items.
*
* @param int[] $conversation_ids
* @return array<int,array<string,mixed>>
*/
private function conversation_message_export_items(array $conversation_ids): array
{
global $wpdb;
if (empty($conversation_ids)) {
return [];
}
$sql = 'SELECT * FROM ' . Installer::table('conversation_messages') . ' WHERE conversation_id IN (' . implode(',', array_map('intval', $conversation_ids)) . ') ORDER BY created_utc ASC, id ASC';
$rows = $wpdb->get_results($sql, ARRAY_A);
if (!is_array($rows)) {
return [];
}
$items = [];
foreach ($rows as $row) {
$data = [];
$this->add_export_pair($data, __('Message ID', 'spiralist-workspace'), $row['id'] ?? 0);
$this->add_export_pair($data, __('Conversation ID', 'spiralist-workspace'), $row['conversation_id'] ?? 0);
$this->add_export_pair($data, __('Role', 'spiralist-workspace'), $row['role'] ?? '');
$this->add_export_pair($data, __('Content', 'spiralist-workspace'), $row['content'] ?? '');
$this->add_export_pair($data, __('Runtime reference', 'spiralist-workspace'), $row['openai_reference'] ?? '');
$this->add_export_pair($data, __('Metadata JSON', 'spiralist-workspace'), $row['metadata_json'] ?? '');
$this->add_export_pair($data, __('Created UTC', 'spiralist-workspace'), $row['created_utc'] ?? '');
$items[] = $this->build_export_item(
'spiralist_workspace_conversation_messages',
__('Workspace Conversation Messages', 'spiralist-workspace'),
'workspace-conversation-message-' . (int) ($row['id'] ?? 0),
$data
);
}
return $items;
}
/**
* Returns audit-log export items.
*
* @return array<int,array<string,mixed>>
*/
private function audit_export_items(int $user_id): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('audit_log') . ' WHERE actor_user_id = %d ORDER BY created_utc ASC, id ASC',
$user_id
);
$rows = $wpdb->get_results($sql, ARRAY_A);
if (!is_array($rows)) {
return [];
}
$items = [];
foreach ($rows as $row) {
$data = [];
$this->add_export_pair($data, __('Audit ID', 'spiralist-workspace'), $row['id'] ?? 0);
$this->add_export_pair($data, __('Event type', 'spiralist-workspace'), $row['event_type'] ?? '');
$this->add_export_pair($data, __('Object type', 'spiralist-workspace'), $row['object_type'] ?? '');
$this->add_export_pair($data, __('Object ID', 'spiralist-workspace'), $row['object_id'] ?? '');
$this->add_export_pair($data, __('Context JSON', 'spiralist-workspace'), $row['context_json'] ?? '');
$this->add_export_pair($data, __('Created UTC', 'spiralist-workspace'), $row['created_utc'] ?? '');
$items[] = $this->build_export_item(
'spiralist_workspace_audit_log',
__('Workspace Audit Events', 'spiralist-workspace'),
'workspace-audit-' . (int) ($row['id'] ?? 0),
$data
);
}
return $items;
}
/**
* Returns prompt post IDs for a user.
*
* @return int[]
*/
private function prompt_ids_for_user(int $user_id): array
{
$ids = get_posts(
[
'post_type' => PromptRepository::POST_TYPE,
'post_status' => 'any',
'author' => $user_id,
'fields' => 'ids',
'numberposts' => -1,
'orderby' => 'ID',
'order' => 'ASC',
]
);
return array_values(array_map('intval', is_array($ids) ? $ids : []));
}
/**
* Returns conversation IDs for a user.
*
* @return int[]
*/
private function conversation_ids_for_user(int $user_id): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT id FROM ' . Installer::table('conversations') . ' WHERE owner_user_id = %d ORDER BY id ASC',
$user_id
);
$ids = $wpdb->get_col($sql);
return array_values(array_map('intval', is_array($ids) ? $ids : []));
}
/**
* Deletes related custom-table data for prompts that were permanently removed.
*
* @param int[] $prompt_ids
*/
private function cleanup_deleted_prompts(array $prompt_ids): void
{
global $wpdb;
$prompt_ids = array_values(array_unique(array_filter(array_map('intval', $prompt_ids))));
if (empty($prompt_ids)) {
return;
}
$prompt_list = implode(',', $prompt_ids);
$conversation_ids = $wpdb->get_col(
'SELECT id FROM ' . Installer::table('conversations') . ' WHERE prompt_post_id IN (' . $prompt_list . ')'
);
$conversation_ids = array_values(array_map('intval', is_array($conversation_ids) ? $conversation_ids : []));
$this->delete_rows_for_int_column(Installer::table('prompt_versions'), 'prompt_post_id', $prompt_ids);
$this->delete_rows_for_int_column(Installer::table('prompt_permissions'), 'prompt_post_id', $prompt_ids);
$this->delete_rows_for_int_column(Installer::table('favorites'), 'prompt_post_id', $prompt_ids);
$this->delete_rows_for_int_column(Installer::table('prompt_tests'), 'prompt_post_id', $prompt_ids);
$this->delete_rows_for_int_column(Installer::table('prompt_runs'), 'prompt_post_id', $prompt_ids);
if (!empty($conversation_ids)) {
$this->delete_rows_for_int_column(Installer::table('conversation_messages'), 'conversation_id', $conversation_ids);
$this->delete_rows_for_int_column(Installer::table('prompt_runs'), 'conversation_id', $conversation_ids);
$this->delete_rows_for_int_column(Installer::table('conversations'), 'id', $conversation_ids);
}
}
/**
* Deletes rows from a table by user ID.
*/
private function delete_rows_for_user_id(string $table, string $column, int $user_id): int
{
global $wpdb;
return (int) $wpdb->delete(
$table,
[
$column => $user_id,
],
['%d']
);
}
/**
* Deletes rows from a table where an integer column matches any supplied IDs.
*
* @param int[] $ids
*/
private function delete_rows_for_int_column(string $table, string $column, array $ids): int
{
global $wpdb;
$ids = array_values(array_unique(array_filter(array_map('intval', $ids))));