<?php
|
namespace JVBase\importers;
|
|
use WP_Error;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
/**
|
* JaneApp Client List Importer
|
*
|
* Imports client data from JaneApp CSV exports and maps to WordPress users
|
*/
|
class JaneAppClientImporter
|
{
|
protected $wpdb;
|
protected string $jane_clients_table;
|
protected array $import_stats = [];
|
|
// CSV column mapping
|
protected array $column_map = [
|
'patient_guid' => '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;
|
}
|
}
|