get_var($wpdb->prepare( "SELECT COUNT(DISTINCT pm.meta_value) FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type IN ('easy-invoice', 'easy-invoice-quotes') AND pm.meta_key IN ('client_email', '_client_email') AND pm.meta_value != '' AND NOT EXISTS ( SELECT 1 FROM {$wpdb->users} u WHERE u.user_email = pm.meta_value )" )); if ($count > 0) { $this->log(sprintf('Found %d unique client emails that need migration to WordPress users', $count)); return true; } // Also check for any client data in meta that hasn't been migrated to user meta $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(DISTINCT pm.post_id) FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type IN ('easy-invoice', 'easy-invoice-quotes') AND pm.meta_key IN ( 'client_name', '_client_name', 'client_company', '_client_company', 'client_address', '_client_address', 'client_phone', '_client_phone', 'client_vat', '_client_vat' ) AND pm.meta_value != '' AND NOT EXISTS ( SELECT 1 FROM {$wpdb->postmeta} pm2 WHERE pm2.post_id = pm.post_id AND pm2.meta_key IN ('_easy_invoice_client_id', '_easy_invoice_quote_client_id') )" )); if ($count > 0) { $this->log(sprintf('Found %d documents with client data that needs migration', $count)); return true; } return false; } /** * Run the migration. * * @since 2.0.0 * @return array */ public function migrate(): array { try { global $wpdb; $this->log('Starting client migration'); // Get all unique client emails from invoices and quotes $client_emails = $wpdb->get_col($wpdb->prepare( "SELECT DISTINCT pm.meta_value FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type IN ('easy-invoice', 'easy-invoice-quotes') AND pm.meta_key IN ('client_email', '_client_email', 'easy_invoice_client_email', 'easy_invoice_quote_client_email') AND pm.meta_value != '' AND pm.meta_value != '0' AND pm.meta_value IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM {$wpdb->users} u WHERE u.user_email = pm.meta_value )" )); $migrated_count = 0; $errors = []; // Process each unique client email foreach ($client_emails as $email) { // Get all documents for this client $documents = $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_type FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type IN ('easy-invoice', 'easy-invoice-quotes') AND pm.meta_key IN ('client_email', '_client_email', 'easy_invoice_client_email', 'easy_invoice_quote_client_email') AND pm.meta_value = %s", $email )); if (empty($documents)) { continue; } // Get client data from the first document $first_doc = $documents[0]; $client_data = [ 'email' => $email, 'name' => '', 'company' => '', 'address' => '', 'phone' => '', 'vat' => '', 'website' => '', 'additional_info' => '' ]; // Try to get client details from meta $meta_keys = [ 'name' => ['client_name', '_client_name', 'easy_invoice_client_name', 'easy_invoice_quote_client_name', 'name', '_name', 'customer_name', '_customer_name'], 'company' => ['client_company', '_client_company', 'easy_invoice_client_company', 'easy_invoice_quote_client_company', 'company', '_company'], 'address' => ['client_address', '_client_address', 'easy_invoice_client_address', 'easy_invoice_quote_client_address', 'address', '_address'], 'phone' => ['client_phone', '_client_phone', 'easy_invoice_client_phone', 'easy_invoice_quote_client_phone', 'phone', '_phone'], 'vat' => ['client_vat', '_client_vat', 'easy_invoice_client_vat', 'easy_invoice_quote_client_vat', 'vat', '_vat'], 'website' => ['client_website', '_client_website', 'easy_invoice_client_website', 'easy_invoice_quote_client_website', 'website', '_website'], 'additional_info' => ['client_extra_info', '_client_extra_info', 'easy_invoice_client_extra_info', 'easy_invoice_quote_client_extra_info', 'extra_info', '_extra_info', 'additional_info', '_additional_info'] ]; foreach ($meta_keys as $field => $possible_keys) { foreach ($possible_keys as $key) { $value = get_post_meta($first_doc->ID, $key, true); if (!empty($value)) { $client_data[$field] = $value; break; } } } // Create or update WordPress user $user_id = $this->get_or_create_user($client_data); if ($user_id > 0) { // Update client ID in all documents foreach ($documents as $doc) { if ($doc->post_type === 'easy-invoice') { update_post_meta($doc->ID, '_easy_invoice_client_id', $user_id); // Also update the old meta key for backward compatibility update_post_meta($doc->ID, 'client_email', $client_data['email']); } else { update_post_meta($doc->ID, '_easy_invoice_quote_client_id', $user_id); // Also update the old meta key for backward compatibility update_post_meta($doc->ID, 'client_email', $client_data['email']); } // Store mapping for future reference update_option('easy_invoice_client_user_mapping_' . $doc->ID, $user_id); update_user_meta($user_id, '_easy_invoice_original_client_id', $doc->ID); } $migrated_count++; $this->log(sprintf('Successfully migrated client with email %s to user ID %d', $email, $user_id)); } else { $errors[] = sprintf('Failed to create/update user for client with email %s', $email); } } if (empty($errors)) { return [ 'success' => true, 'message' => sprintf('Successfully migrated %d clients', $migrated_count), 'count' => $migrated_count ]; } else { return [ 'success' => false, 'message' => 'Client migration completed with errors: ' . implode(', ', $errors), 'count' => $migrated_count ]; } } catch (\Exception $e) { $this->log('Client migration failed: ' . $e->getMessage(), 'error'); return [ 'success' => false, 'message' => $e->getMessage(), 'count' => 0 ]; } } /** * Migrate a single client to WordPress user. * * @since 2.0.0 * @param object $client Client post object * @return array */ private function migrate_single_client($client): array { try { // Get client meta with fallbacks for different key formats $meta_keys = [ 'name' => ['client_name', '_client_name'], 'email' => ['client_email', '_client_email'], 'company' => ['client_company', '_client_company'], 'address' => ['client_address', '_client_address'], 'phone' => ['client_phone', '_client_phone'], 'vat' => ['client_vat', '_client_vat'] ]; $client_data = []; foreach ($meta_keys as $key => $possible_keys) { foreach ($possible_keys as $meta_key) { $value = get_post_meta($client->ID, $meta_key, true); if (!empty($value)) { $client_data[$key] = $value; break; } } } // Use post title as name if no name meta found $client_name = $client_data['name'] ?? $client->post_title; $client_email = $client_data['email'] ?? ''; // If no email, generate a unique one if (empty($client_email)) { $client_email = 'client_' . $client->ID . '@example.com'; } // Process client name $name_data = $this->process_client_name($client_name); // Check if user already exists with this email $existing_user = get_user_by('email', $client_email); if ($existing_user) { // Update user meta $this->update_user_meta($existing_user->ID, [ 'business_client_name' => $client_name, // Full name as business name 'first_name' => $name_data['first_name'], 'last_name' => $name_data['last_name'], 'company' => $client_data['company'] ?? '', 'address' => $client_data['address'] ?? '', 'phone' => $client_data['phone'] ?? '', 'vat' => $client_data['vat'] ?? '', 'website' => $client_data['website'] ?? '', 'additional_info' => $client_data['additional_info'] ?? '' ]); // Store mapping update_option('easy_invoice_client_user_mapping_' . $client->ID, $existing_user->ID); // Store reverse mapping update_user_meta($existing_user->ID, '_easy_invoice_original_client_id', $client->ID); $this->log(sprintf('Updated existing user %d for client %d', $existing_user->ID, $client->ID)); return [ 'success' => true, 'message' => sprintf('Updated existing user %d for client %d', $existing_user->ID, $client->ID), 'user_id' => $existing_user->ID ]; } // Create username from email or name $username = sanitize_user(current(explode('@', $client_email)), true); if (username_exists($username)) { $username = $username . '_' . $client->ID; } // Create random password $password = wp_generate_password(); // Create user $user_data = [ 'user_login' => $username, 'user_email' => $client_email, 'user_pass' => $password, 'display_name' => $client_name, 'role' => 'easy_invoice_client' ]; $user_id = wp_insert_user($user_data); if (is_wp_error($user_id)) { return [ 'success' => false, 'message' => $user_id->get_error_message() ]; } // Add user meta $this->update_user_meta($user_id, [ 'business_client_name' => $client_name, // Set the client name as business name 'company' => $client_data['company'] ?? '', 'address' => $client_data['address'] ?? '', 'phone' => $client_data['phone'] ?? '', 'vat' => $client_data['vat'] ?? '', 'additional_info' => $client_data['additional_info'] ?? '' ]); // Store mappings update_option('easy_invoice_client_user_mapping_' . $client->ID, $user_id); update_user_meta($user_id, '_easy_invoice_original_client_id', $client->ID); $this->log(sprintf('Created user %d for client %d', $user_id, $client->ID)); return [ 'success' => true, 'message' => sprintf('Created user %d for client %d', $user_id, $client->ID), 'user_id' => $user_id ]; } catch (\Exception $e) { return [ 'success' => false, 'message' => $e->getMessage() ]; } } /** * Update user meta with client data. * * @since 2.0.0 * @param int $user_id User ID * @param array $meta Meta data * @return void */ private function update_user_meta(int $user_id, array $meta): void { foreach ($meta as $key => $value) { if (!empty($value)) { // Map the key to the correct meta key $meta_key = $this->get_meta_key($key); if ($meta_key) { update_user_meta($user_id, $meta_key, $value); } } } } /** * Get the correct meta key for a field. * * @since 2.0.0 * @param string $field Field name * @return string Meta key */ private function get_meta_key(string $field): string { $meta_keys = [ 'business_client_name' => '_easy_invoice_client_business_client_name', 'first_name' => '_easy_invoice_client_first_name', 'last_name' => '_easy_invoice_client_last_name', 'company' => '_easy_invoice_client_company', 'address' => '_easy_invoice_client_address', 'phone' => '_easy_invoice_client_phone', 'vat' => '_easy_invoice_client_vat', 'website' => '_easy_invoice_client_website', 'additional_info' => '_easy_invoice_client_extra_info' ]; return $meta_keys[$field] ?? ''; } /** * Process client name and split into first and last name. * * @since 2.0.0 * @param string $full_name Full client name * @return array Array with first_name and last_name */ private function process_client_name(string $full_name): array { $full_name = trim($full_name); if (empty($full_name)) { return ['first_name' => '', 'last_name' => '']; } // Split by space $name_parts = explode(' ', $full_name); if (count($name_parts) === 1) { // Only one name, use as first name return ['first_name' => $name_parts[0], 'last_name' => '']; } elseif (count($name_parts) === 2) { // Two names, first and last return ['first_name' => $name_parts[0], 'last_name' => $name_parts[1]]; } else { // More than two names, first name is first part, last name is everything else $first_name = array_shift($name_parts); $last_name = implode(' ', $name_parts); return ['first_name' => $first_name, 'last_name' => $last_name]; } } /** * Get user ID for client. * * @since 2.0.0 * @param int $client_id Old client ID * @return int User ID or 0 if not found */ /** * Get or create a WordPress user for a client. * * @since 2.0.0 * @param array $client_data Client data array with email, name, company, address, phone, vat * @return int User ID or 0 if failed */ public function get_or_create_user(array $client_data): int { if (empty($client_data['email'])) { return 0; } // Try to find existing user by email $user = get_user_by('email', $client_data['email']); if ($user) { // Process client name $name_data = $this->process_client_name($client_data['name'] ?? ''); // Update user meta $this->update_user_meta($user->ID, [ 'business_client_name' => $client_data['name'] ?? '', // Full name as business name 'first_name' => $name_data['first_name'], 'last_name' => $name_data['last_name'], 'company' => $client_data['company'] ?? '', 'address' => $client_data['address'] ?? '', 'phone' => $client_data['phone'] ?? '', 'vat' => $client_data['vat'] ?? '', 'website' => $client_data['website'] ?? '', 'additional_info' => $client_data['additional_info'] ?? '' ]); return $user->ID; } // Create new user $username = sanitize_user(current(explode('@', $client_data['email'])), true); if (username_exists($username)) { $username = $username . '_' . time(); } // Process client name $name_data = $this->process_client_name($client_data['name'] ?? ''); // If no name provided, use email username part $display_name = !empty($client_data['name']) ? $client_data['name'] : current(explode('@', $client_data['email'])); $user_data = [ 'user_login' => $username, 'user_email' => $client_data['email'], 'user_pass' => wp_generate_password(), 'display_name' => $display_name, 'first_name' => $name_data['first_name'], 'last_name' => $name_data['last_name'], 'role' => 'easy_invoice_client' ]; $user_id = wp_insert_user($user_data); if (is_wp_error($user_id)) { $this->log(sprintf('Failed to create user for client with email %s: %s', $client_data['email'], $user_id->get_error_message()), 'error'); return 0; } // Add user meta $this->update_user_meta($user_id, [ 'business_client_name' => $client_data['name'] ?? '', // Full name as business name 'first_name' => $name_data['first_name'], 'last_name' => $name_data['last_name'], 'company' => $client_data['company'] ?? '', 'address' => $client_data['address'] ?? '', 'phone' => $client_data['phone'] ?? '', 'vat' => $client_data['vat'] ?? '', 'website' => $client_data['website'] ?? '', 'additional_info' => $client_data['additional_info'] ?? '' ]); $this->log(sprintf('Created new user %d for client with email %s', $user_id, $client_data['email'])); return $user_id; } public function get_user_id_for_client(int $client_id): int { if (empty($client_id)) { return 0; } $user_id = get_option('easy_invoice_client_user_mapping_' . $client_id); return !empty($user_id) ? (int)$user_id : 0; } /** * Get client meta for user. * * @since 2.0.0 * @param int $user_id User ID * @return array Client meta */ public function get_client_meta_for_user(int $user_id): array { if (empty($user_id)) { return []; } $user = get_user_by('id', $user_id); if (!$user) { return []; } return [ 'first_name' => get_user_meta($user_id, '_easy_invoice_client_first_name', true) ?: $user->first_name, 'last_name' => get_user_meta($user_id, '_easy_invoice_client_last_name', true) ?: $user->last_name, 'business_client_name' => get_user_meta($user_id, '_easy_invoice_client_business_client_name', true), 'email' => get_user_meta($user_id, '_easy_invoice_client_email', true) ?: $user->user_email, 'username' => get_user_meta($user_id, '_easy_invoice_client_username', true) ?: $user->user_login, 'address' => get_user_meta($user_id, '_easy_invoice_client_address', true), 'extra_info' => get_user_meta($user_id, '_easy_invoice_client_extra_info', true), 'website' => get_user_meta($user_id, '_easy_invoice_client_website', true) ?: $user->user_url, 'display_name' => $user->display_name, 'company' => get_user_meta($user_id, '_easy_invoice_client_company', true), 'phone' => get_user_meta($user_id, '_easy_invoice_client_phone', true), 'vat' => get_user_meta($user_id, '_easy_invoice_client_vat', true) ]; } }