>, done: bool} */ public function exportUser(int $userId, int $page = 1): array { if ($userId <= 0) { return ['data' => [], 'done' => true]; } $page = max(1, min(100000, $page)); $rows = $this->sessions->forExport($userId, $page, self::EXPORT_PAGE); $metadataOffset = ($page - 1) * self::EXPORT_METADATA_PAGE; $events = $this->events->recent($userId, self::EXPORT_METADATA_PAGE, $metadataOffset); $usage = $this->usage->exportHistory($userId, self::EXPORT_METADATA_PAGE, $metadataOffset); $data = []; if ($page === 1) { $instructions = $this->preferences->instructions($userId); if ($instructions !== '') { $data[] = [ 'group_id' => 'maze-graph-preferences', 'group_label' => __('MazeGraph preferences', 'maze-graph'), 'item_id' => 'maze-graph-preferences-' . $userId, 'data' => [['name' => __('Your instructions', 'maze-graph'), 'value' => $instructions]], ]; } $consentVersion = $this->preferences->consentVersion($userId); if ($consentVersion > 0) { $data[] = [ 'group_id' => 'maze-graph-preferences', 'group_label' => __('MazeGraph preferences', 'maze-graph'), 'item_id' => 'maze-graph-consent-' . $userId, 'data' => [ ['name' => __('Accepted data notice', 'maze-graph'), 'value' => __('Yes', 'maze-graph')], ['name' => __('Data notice version', 'maze-graph'), 'value' => (string) $consentVersion], ], ]; } if ($this->preferences->onboardingChecklistCompleted($userId)) { $data[] = [ 'group_id' => 'maze-graph-preferences', 'group_label' => __('MazeGraph preferences', 'maze-graph'), 'item_id' => 'maze-graph-onboarding-checklist-' . $userId, 'data' => [['name' => __('Getting-started checklist completed', 'maze-graph'), 'value' => __('Yes', 'maze-graph')]], ]; } if ($this->preferences->onboardingTourCompleted($userId)) { $data[] = [ 'group_id' => 'maze-graph-preferences', 'group_label' => __('MazeGraph preferences', 'maze-graph'), 'item_id' => 'maze-graph-onboarding-tour-' . $userId, 'data' => [['name' => __('Interface tour completed', 'maze-graph'), 'value' => __('Yes', 'maze-graph')]], ]; } if ($this->preferences->legacyOnboardingChecklistDismissed($userId)) { $data[] = [ 'group_id' => 'maze-graph-preferences', 'group_label' => __('MazeGraph preferences', 'maze-graph'), 'item_id' => 'maze-graph-onboarding-legacy-' . $userId, 'data' => [['name' => __('Prior getting-started checklist hidden setting', 'maze-graph'), 'value' => __('Yes', 'maze-graph')]], ]; } foreach ($this->notes->ofUser($userId) as $note) { $data[] = [ 'group_id' => 'maze-graph-notes', 'group_label' => __('MazeGraph notes', 'maze-graph'), 'item_id' => 'maze-graph-note-' . $note['id'], 'data' => [['name' => __('Note', 'maze-graph'), 'value' => $note['content']], ['name' => __('Saved', 'maze-graph'), 'value' => $note['created_at']]], ]; } } foreach ($rows as $row) { $lines = []; foreach (Transcript::visible($this->store->entries((int) $row['id'])) as $entry) { if (($entry['type'] ?? '') === 'message' && in_array($entry['role'] ?? '', ['user', 'assistant'], true)) { $text = Transcript::text($entry); if ($text !== '') { $lines[] = ($entry['role'] === 'user' ? __('You', 'maze-graph') : 'MazeGraph') . ': ' . $text; } } } $data[] = [ 'group_id' => 'maze-graph-sessions', 'group_label' => __('MazeGraph sessions', 'maze-graph'), 'item_id' => 'maze-graph-session-' . $row['uuid'], 'data' => [ ['name' => __('Title', 'maze-graph'), 'value' => Redactor::mask((string) $row['title'])], ['name' => __('Started', 'maze-graph'), 'value' => (string) SessionResource::time($row['created_at'] ?? null)], ['name' => __('Messages', 'maze-graph'), 'value' => implode("\n\n", $lines)], ], ]; } foreach ($events as $event) { $payload = json_decode((string) ($event['payload'] ?? ''), true); $eventName = (string) ($event['event_name'] ?? ''); if (!in_array($eventName, Event::NAMES, true)) { $eventName = ''; } $data[] = [ 'group_id' => 'maze-graph-activity', 'group_label' => __('MazeGraph activity', 'maze-graph'), 'item_id' => 'maze-graph-event-' . (int) ($event['id'] ?? 0), 'data' => [ ['name' => __('Event', 'maze-graph'), 'value' => $eventName], ['name' => __('Date', 'maze-graph'), 'value' => (string) ($event['created_at'] ?? '')], ['name' => __('Recorded details', 'maze-graph'), 'value' => is_array($payload) ? (string) wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : '{}'], ], ]; } foreach ($usage as $record) { $data[] = [ 'group_id' => 'maze-graph-usage', 'group_label' => __('MazeGraph usage', 'maze-graph'), 'item_id' => 'maze-graph-usage-' . $userId . '-' . $record['day'] . '-' . md5($record['model']), 'data' => [ ['name' => __('Day (UTC)', 'maze-graph'), 'value' => $record['day']], ['name' => __('Model', 'maze-graph'), 'value' => $record['model']], ['name' => __('Input tokens', 'maze-graph'), 'value' => (string) $record['tokens_in']], ['name' => __('Output tokens', 'maze-graph'), 'value' => (string) $record['tokens_out']], ['name' => __('Messages', 'maze-graph'), 'value' => (string) $record['messages']], ], ]; } return [ 'data' => $data, 'done' => count($rows) < self::EXPORT_PAGE && count($events) < self::EXPORT_METADATA_PAGE && count($usage) < self::EXPORT_METADATA_PAGE, ]; } /** Erase the person's MazeGraph records; site settings and other users' records are unaffected. */ public function eraseUser(int $userId): int { if ($userId <= 0) { return 0; } $eventCount = $this->events->countRecent($userId); $removed = $this->sessions->eraseUser($userId); $removed += $eventCount > 0 ? 1 : 0; $removed += $this->usage->forgetUser($userId); $removed += $this->memory->forgetUser($userId); $removed += $this->preferences->forget($userId) ? 1 : 0; return $removed; } }