Jake Vanderwerf
10 days ago 97e7c319d656a5f05489ca996e249e7359303d4d
inc/importers/JaneAppClientImporter.php
@@ -17,7 +17,8 @@
   protected $wpdb;
   protected string $jane_clients_table;
   protected array $import_stats = [];
   protected int $lineNumber = 0;
   protected array $skipped_details = [];
   // CSV column mapping
   protected array $column_map = [
      'patient_guid' => 'patient_guid',
@@ -25,6 +26,7 @@
      'last_name' => 'Last Name',
      'email' => 'Email',
   ];
   protected array $headers = [];
   public function __construct()
   {
@@ -38,10 +40,13 @@
    *
    * @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
    * @return array|WP_Error Import results with stats and errors
    */
   public function importFromCSV(string $file_path, array $options = []): array
   public function importFromCSV(string $file_path, array $options = []): array|WP_Error
   {
      $this->skipped_details = [];
      $this->lineNumber = 0; // Reset line number
      // Initialize stats
      $this->import_stats = [
         'total_rows' => 0,
@@ -50,7 +55,8 @@
         'updated' => 0,
         'skipped' => 0,
         'errors' => [],
         'unmatched_emails' => []
         'unmatched_emails' => [],
         'skipped_details' => [] // Add this
      ];
      // Validate file exists
@@ -75,9 +81,10 @@
         fclose($handle);
         return new WP_Error('invalid_csv', 'CSV file is empty or invalid');
      }
      $this->headers = array_map('trim', $headers);
      // Map column indices
      $column_indices = $this->mapColumnIndices($headers);
      $column_indices = $this->mapColumnIndices($this->headers);
      if (is_wp_error($column_indices)) {
         fclose($handle);
         return $column_indices;
@@ -90,11 +97,13 @@
         // Process each row
         while (($row = fgetcsv($handle)) !== false) {
            $this->import_stats['total_rows']++;
            $this->lineNumber++;
            $result = $this->processClientRow($row, $column_indices, [
               'update_existing' => $update_existing,
               'send_welcome_email' => $send_welcome_email,
               'create_users' => $create_users
               'create_users' => $create_users,
               'default_role' => $options['default_role'] ?? null
            ]);
            if (is_wp_error($result)) {
@@ -125,6 +134,9 @@
      fclose($handle);
      // Add skipped details to stats
      $this->import_stats['skipped_details'] = $this->skipped_details;
      return $this->import_stats;
   }
@@ -170,39 +182,90 @@
      // Validate required fields
      if (empty($patient_guid) || empty($email)) {
         $this->skipped_details[] = [
            'name' => $first_name . ' ' . $last_name,
            'guid'   => $patient_guid,
            'email' => $email,
            'reason' => 'Missing guid or email',
            'line' => $this->lineNumber
         ];
         return new WP_Error('invalid_data', 'Missing patient_guid or email');
      }
      // Sanitize email
      $email = sanitize_email($email);
      if (!is_email($email)) {
         $this->skipped_details[] = [
            'name' => $first_name . ' ' . $last_name,
            'guid'   => $patient_guid,
            'email' => $email,
            'reason' => 'Invalid Email',
            'line' => $this->lineNumber
         ];
         return new WP_Error('invalid_email', 'Invalid email address: ' . $email);
      }
      // Check if client already exists in mapping table
      $existing_mapping = $this->getClientByGuid($patient_guid);
      // Build full data array with all CSV columns for meta storage
      $data = [];
      foreach ($row as $index => $value) {
         $header = $this->headers[$index] ?? 'unknown_' . $index;
         $data[$header] = trim($value);
      }
      // Ensure these keys exist for backward compatibility
      $data['patient_guid'] = $patient_guid;
      $data['First Name'] = $first_name;
      $data['Last Name'] = $last_name;
      $data['Email'] = $email;
      // Find or create WordPress user
      $user = get_user_by('email', $email);
      $action = 'existing';
      if (!$user && $options['create_users']) {
      if ($user) {
         if ($options['update_existing']) {
            $this->updateExistingClient($user->ID, $data, $options);
            $action = 'updated';
         } else {
            $this->skipped_details[] = [
               'name' => $first_name . ' ' . $last_name,
               'email' => $email,
               'reason' => 'User already exists (update not enabled)',
               'line' => $this->lineNumber
            ];
            return new WP_Error('user_exists', 'User already exists');
         }
      } elseif ($options['create_users']) {
         // Create new user
         $user_id = $this->createWordPressUser($email, $first_name, $last_name, $options['send_welcome_email']);
         $user_id = $this->createClientUser($data, $options);
         if (is_wp_error($user_id)) {
            $this->skipped_details[] = [
               'name' => $first_name . ' ' . $last_name,
               'guid'   => $patient_guid,
               'email' => $email,
               'reason' => $user_id->get_error_message(),
               'line' => $this->lineNumber
            ];
            return $user_id;
         }
         $user = get_user_by('ID', $user_id);
         $action = 'created';
      } elseif (!$user) {
      } else {
         // User doesn't exist and we're not creating users
         $this->skipped_details[] = [
            'name' => $first_name . ' ' . $last_name,
            'guid'   => $patient_guid,
            'email' => $email,
            'reason' => 'User not found and create_users is false',
            'line' => $this->lineNumber
         ];
         $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
@@ -214,7 +277,6 @@
               'last_name' => $last_name,
               'email' => $email
            ]);
            $action = 'updated';
         }
      } else {
         $this->insertClientMapping([
@@ -224,9 +286,6 @@
            'last_name' => $last_name,
            'email' => $email
         ]);
         if ($action !== 'created') {
            $action = 'mapped';
         }
      }
      return [
@@ -237,49 +296,134 @@
   }
   /**
    * Create a new WordPress user
    * Create a new client user from Jane App data
    *
    * @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
    * @param array $data Client data from CSV
    * @param array $options Import options
    * @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
   protected function createClientUser(array $data, array $options)
   {
      $email = sanitize_email($data['Email']);
      $first_name = sanitize_text_field($data['First Name'] ?? '');
      $last_name = sanitize_text_field($data['Last Name'] ?? '');
      // Generate username from email
      $username = $this->generateUsername($email);
      $username = sanitize_user($email);
      // Generate random password
      $password = wp_generate_password(12, true, true);
      // Ensure unique username
      $base_username = $username;
      $counter = 1;
      while (username_exists($username)) {
         $username = $base_username . $counter;
         $counter++;
      }
      $userdata = [
      // Get the role from options with proper fallback
      $role = $options['default_role'] ?? get_option(BASE . 'client_import_role', BASE.'client');
      // Ensure role exists
      if (!get_role($role)) {
         return new WP_Error('invalid_role', 'Invalid role');
      }
      // Create user
      $user_data = [
         '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')
         'role' => $role,
         'user_pass' => wp_generate_password(16, true, true)
      ];
      $user_id = wp_insert_user($userdata);
      $user_id = wp_insert_user($user_data);
      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');
      // Store Jane App data as user meta
      $this->storeClientMeta($user_id, $data);
      // Send welcome email if enabled
      if ($options['send_welcome_email'] ?? false) {
         wp_new_user_notification($user_id, null, 'user');
      }
      do_action(BASE . 'jane_client_created', $user_id, $userdata);
      return $user_id;
   }
   /**
    * Store Jane App client data as user meta
    *
    * @param int $user_id
    * @param array $data
    */
   protected function storeClientMeta(int $user_id, array $data): void
   {
      // Store Jane App specific fields
      if (!empty($data['patient_guid'])) {
         update_user_meta($user_id, BASE . 'jane_patient_guid', sanitize_text_field($data['patient_guid']));
      }
      if (!empty($data['Patient Number'])) {
         update_user_meta($user_id, BASE . 'jane_patient_number', sanitize_text_field($data['Patient Number']));
      }
      if (!empty($data['Member Since'])) {
         update_user_meta($user_id, BASE . 'member_since', sanitize_text_field($data['Member Since']));
      }
      if (!empty($data['Mobile Phone'])) {
         update_user_meta($user_id, BASE . 'phone', sanitize_text_field($data['Mobile Phone']));
      }
      if (!empty($data['Birth Date'])) {
         update_user_meta($user_id, BASE . 'birth_date', sanitize_text_field($data['Birth Date']));
      }
      if (!empty($data['Referral Source'])) {
         update_user_meta($user_id, BASE . 'referral_source', sanitize_text_field($data['Referral Source']));
      }
      // Store full Jane App data as JSON for reference
      update_user_meta($user_id, BASE . 'jane_import_data', $data);
      update_user_meta($user_id, BASE . 'jane_import_date', current_time('mysql'));
   }
   /**
    * Update existing client with Jane App data
    *
    * @param int $user_id
    * @param array $data
    * @param array $options
    */
   protected function updateExistingClient(int $user_id, array $data, array $options): void
   {
      // Update user fields if they're empty
      $user_data = ['ID' => $user_id];
      $current_user = get_user_by('ID', $user_id);
      if (empty($current_user->first_name) && !empty($data['First Name'])) {
         $user_data['first_name'] = sanitize_text_field($data['First Name']);
      }
      if (empty($current_user->last_name) && !empty($data['Last Name'])) {
         $user_data['last_name'] = sanitize_text_field($data['Last Name']);
      }
      if (count($user_data) > 1) {
         wp_update_user($user_data);
      }
      // Always update meta data
      $this->storeClientMeta($user_id, $data);
   }
   /**
    * Generate unique username from email
    *
    * @param string $email Email address