'patient_guid', 'first_name' => 'First Name', 'last_name' => 'Last Name', 'email' => 'Email', ]; public function __construct() { global $wpdb; $this->wpdb = $wpdb; $this->jane_clients_table = $wpdb->prefix . BASE . 'jane_clients'; } /** * Import client list from CSV file * * @param string $file_path Path to the CSV file * @param array $options Import options (e.g., update_existing, send_welcome_email) * @return array Import results with stats and errors */ public function importFromCSV(string $file_path, array $options = []): array { // Initialize stats $this->import_stats = [ 'total_rows' => 0, 'processed' => 0, 'created' => 0, 'updated' => 0, 'skipped' => 0, 'errors' => [], 'unmatched_emails' => [] ]; // Validate file exists if (!file_exists($file_path)) { return new WP_Error('file_not_found', 'CSV file not found'); } // Parse options $update_existing = $options['update_existing'] ?? true; $send_welcome_email = $options['send_welcome_email'] ?? false; $create_users = $options['create_users'] ?? true; // Open and parse CSV $handle = fopen($file_path, 'r'); if (!$handle) { return new WP_Error('file_open_error', 'Could not open CSV file'); } // Get header row $headers = fgetcsv($handle); if (!$headers) { fclose($handle); return new WP_Error('invalid_csv', 'CSV file is empty or invalid'); } // Map column indices $column_indices = $this->mapColumnIndices($headers); if (is_wp_error($column_indices)) { fclose($handle); return $column_indices; } // Start transaction for data integrity $this->wpdb->query('START TRANSACTION'); try { // Process each row while (($row = fgetcsv($handle)) !== false) { $this->import_stats['total_rows']++; $result = $this->processClientRow($row, $column_indices, [ 'update_existing' => $update_existing, 'send_welcome_email' => $send_welcome_email, 'create_users' => $create_users ]); if (is_wp_error($result)) { $this->import_stats['errors'][] = [ 'row' => $this->import_stats['total_rows'], 'error' => $result->get_error_message() ]; $this->import_stats['skipped']++; } else { $this->import_stats['processed']++; if ($result['action'] === 'created') { $this->import_stats['created']++; } elseif ($result['action'] === 'updated') { $this->import_stats['updated']++; } } } // Commit transaction $this->wpdb->query('COMMIT'); } catch (\Exception $e) { // Rollback on error $this->wpdb->query('ROLLBACK'); fclose($handle); return new WP_Error('import_error', 'Import failed: ' . $e->getMessage()); } fclose($handle); return $this->import_stats; } /** * Map CSV column headers to internal field names * * @param array $headers CSV header row * @return array|WP_Error Column index mapping or error */ protected function mapColumnIndices(array $headers): array|WP_Error { $indices = []; foreach ($this->column_map as $field => $csv_column) { $index = array_search($csv_column, $headers); if ($index === false) { return new WP_Error( 'missing_column', sprintf('Required column "%s" not found in CSV', $csv_column) ); } $indices[$field] = $index; } return $indices; } /** * Process a single client row from CSV * * @param array $row CSV row data * @param array $column_indices Column mapping * @param array $options Processing options * @return array|WP_Error Result of processing */ protected function processClientRow(array $row, array $column_indices, array $options): array|WP_Error { // Extract data from row $patient_guid = trim($row[$column_indices['patient_guid']] ?? ''); $first_name = trim($row[$column_indices['first_name']] ?? ''); $last_name = trim($row[$column_indices['last_name']] ?? ''); $email = trim($row[$column_indices['email']] ?? ''); // Validate required fields if (empty($patient_guid) || empty($email)) { return new WP_Error('invalid_data', 'Missing patient_guid or email'); } // Sanitize email $email = sanitize_email($email); if (!is_email($email)) { return new WP_Error('invalid_email', 'Invalid email address: ' . $email); } // Check if client already exists in mapping table $existing_mapping = $this->getClientByGuid($patient_guid); // Find or create WordPress user $user = get_user_by('email', $email); if (!$user && $options['create_users']) { // Create new user $user_id = $this->createWordPressUser($email, $first_name, $last_name, $options['send_welcome_email']); if (is_wp_error($user_id)) { return $user_id; } $user = get_user_by('ID', $user_id); $action = 'created'; } elseif (!$user) { // User doesn't exist and we're not creating users $this->import_stats['unmatched_emails'][] = $email; return new WP_Error('user_not_found', 'User not found and create_users is false'); } else { $action = 'existing'; } // Update or insert client mapping if ($existing_mapping) { if ($options['update_existing']) { $this->updateClientMapping($existing_mapping->id, [ 'user_id' => $user->ID, 'first_name' => $first_name, 'last_name' => $last_name, 'email' => $email ]); $action = 'updated'; } } else { $this->insertClientMapping([ 'patient_guid' => $patient_guid, 'user_id' => $user->ID, 'first_name' => $first_name, 'last_name' => $last_name, 'email' => $email ]); if ($action !== 'created') { $action = 'mapped'; } } return [ 'action' => $action, 'user_id' => $user->ID, 'patient_guid' => $patient_guid ]; } /** * Create a new WordPress user * * @param string $email User email * @param string $first_name First name * @param string $last_name Last name * @param bool $send_welcome_email Whether to send welcome email * @return int|WP_Error User ID or error */ protected function createWordPressUser(string $email, string $first_name, string $last_name, bool $send_welcome_email = false): int|WP_Error { // Generate username from email $username = $this->generateUsername($email); // Generate random password $password = wp_generate_password(12, true, true); $userdata = [ 'user_login' => $username, 'user_email' => $email, 'user_pass' => $password, 'first_name' => $first_name, 'last_name' => $last_name, 'display_name' => trim($first_name . ' ' . $last_name), 'role' => apply_filters(BASE . 'jane_import_default_role', 'customer') ]; $user_id = wp_insert_user($userdata); if (is_wp_error($user_id)) { return $user_id; } // Send welcome email if requested if ($send_welcome_email) { wp_send_new_user_notifications($user_id, 'both'); } do_action(BASE . 'jane_client_created', $user_id, $userdata); return $user_id; } /** * Generate unique username from email * * @param string $email Email address * @return string Unique username */ protected function generateUsername(string $email): string { $base_username = sanitize_user(substr($email, 0, strpos($email, '@'))); $username = $base_username; $counter = 1; while (username_exists($username)) { $username = $base_username . $counter; $counter++; } return $username; } /** * Get client by patient GUID * * @param string $patient_guid Patient GUID * @return object|null Client data or null */ protected function getClientByGuid(string $patient_guid): ?object { return $this->wpdb->get_row($this->wpdb->prepare( "SELECT * FROM {$this->jane_clients_table} WHERE patient_guid = %s", $patient_guid )); } /** * Get client by user ID * * @param int $user_id WordPress user ID * @return object|null Client data or null */ public function getClientByUserId(int $user_id): ?object { return $this->wpdb->get_row($this->wpdb->prepare( "SELECT * FROM {$this->jane_clients_table} WHERE user_id = %d", $user_id )); } /** * Insert new client mapping * * @param array $data Client data * @return int|false Insert ID or false on failure */ protected function insertClientMapping(array $data): int|false { $result = $this->wpdb->insert( $this->jane_clients_table, $data, ['%s', '%d', '%s', '%s', '%s'] ); return $result ? $this->wpdb->insert_id : false; } /** * Update existing client mapping * * @param int $id Mapping ID * @param array $data Updated data * @return bool Success */ protected function updateClientMapping(int $id, array $data): bool { return (bool) $this->wpdb->update( $this->jane_clients_table, $data, ['id' => $id], ['%d', '%s', '%s', '%s'], ['%d'] ); } /** * Get user ID by patient GUID * * @param string $patient_guid Patient GUID * @return int|null User ID or null if not found */ public function getUserIdByGuid(string $patient_guid): ?int { $client = $this->getClientByGuid($patient_guid); return $client ? (int) $client->user_id : null; } /** * Get import statistics * * @return array Import statistics */ public function getImportStats(): array { return $this->import_stats; } }