magento1Connection = 'magento1'; $this->magento2Connection = 'magento2'; $this->magento1Prefix = config('database.connections.magento1.prefix', ''); $this->magento2Prefix = config('database.connections.magento2.prefix', ''); } /** * Get all stores from Magento 1 */ public function getMagento1Stores() { try { $stores = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'core_store') ->select('store_id', 'code', 'name', 'website_id', 'group_id') ->where('store_id', '>', 0) ->get(); return $stores; } catch (Exception $e) { Log::error('Error fetching Magento 1 stores: ' . $e->getMessage()); return collect([]); } } /** * Get all stores from Magento 2 */ public function getMagento2Stores() { try { $stores = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'store') ->select('store_id', 'code', 'name', 'website_id') ->where('store_id', '>', 0) ->get(); return $stores; } catch (Exception $e) { Log::error('Error fetching Magento 2 stores: ' . $e->getMessage()); return collect([]); } } /** * Get all categories from Magento 1 for a specific store */ public function getMagento1Categories($storeId = null) { try { // Get entity type ID $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get attribute IDs $nameAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); $isActiveAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'is_active') ->value('attribute_id'); $urlKeyAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'url_key') ->value('attribute_id'); $targetStoreId = $storeId ?? 0; // Get base category data $categories = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_category_entity') ->select('entity_id', 'parent_id', 'level', 'path', 'position') ->orderBy('level') ->orderBy('position') ->get(); // Get attribute values $nameValues = []; $isActiveValues = []; $urlKeyValues = []; if ($nameAttributeId) { $nameValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_category_entity_varchar') ->where('attribute_id', $nameAttributeId) ->where('store_id', $targetStoreId) ->pluck('value', 'entity_id') ->toArray(); } if ($isActiveAttributeId) { $isActiveValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_category_entity_int') ->where('attribute_id', $isActiveAttributeId) ->where('store_id', $targetStoreId) ->pluck('value', 'entity_id') ->toArray(); } if ($urlKeyAttributeId) { $urlKeyValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_category_entity_varchar') ->where('attribute_id', $urlKeyAttributeId) ->where('store_id', $targetStoreId) ->pluck('value', 'entity_id') ->toArray(); } // Combine data return $categories->map(function($category) use ($nameValues, $isActiveValues, $urlKeyValues) { $category->name = $nameValues[$category->entity_id] ?? null; $category->is_active = $isActiveValues[$category->entity_id] ?? null; $category->url_key = $urlKeyValues[$category->entity_id] ?? null; return $category; }); } catch (Exception $e) { Log::error('Error fetching Magento 1 categories: ' . $e->getMessage()); return collect([]); } } /** * Get all categories from Magento 2 for a specific store */ public function getMagento2Categories($storeId = null) { try { // Get entity type ID $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get attribute IDs $nameAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); $isActiveAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'is_active') ->value('attribute_id'); $urlKeyAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'url_key') ->value('attribute_id'); $targetStoreId = $storeId ?? 0; // Get base category data $categories = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->select('entity_id', 'parent_id', 'level', 'path', 'position') ->orderBy('level') ->orderBy('position') ->get(); // Get attribute values $nameValues = []; $isActiveValues = []; $urlKeyValues = []; if ($nameAttributeId) { $nameValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity_varchar') ->where('attribute_id', $nameAttributeId) ->where('store_id', $targetStoreId) ->pluck('value', 'entity_id') ->toArray(); } if ($isActiveAttributeId) { $isActiveValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity_int') ->where('attribute_id', $isActiveAttributeId) ->where('store_id', $targetStoreId) ->pluck('value', 'entity_id') ->toArray(); } if ($urlKeyAttributeId) { $urlKeyValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity_varchar') ->where('attribute_id', $urlKeyAttributeId) ->where('store_id', $targetStoreId) ->pluck('value', 'entity_id') ->toArray(); } // Combine data return $categories->map(function($category) use ($nameValues, $isActiveValues, $urlKeyValues) { $category->name = $nameValues[$category->entity_id] ?? null; $category->is_active = $isActiveValues[$category->entity_id] ?? null; $category->url_key = $urlKeyValues[$category->entity_id] ?? null; return $category; }); } catch (Exception $e) { Log::error('Error fetching Magento 2 categories: ' . $e->getMessage()); return collect([]); } } /** * Find categories in Magento 2 that don't exist in Magento 1 * Compares by category name (case-insensitive) */ public function getM2CategoriesNotInM1() { try { $m1Categories = $this->getMagento1Categories(); $m2Categories = $this->getMagento2Categories(); // Create a set of M1 category names (case-insensitive, normalized) $m1CategoryNames = $m1Categories->map(function($cat) { return strtolower(trim($cat->name ?? '')); })->filter(function($name) { return !empty($name); })->unique()->toArray(); // Get all M2 categories to build a map for root category lookup $m2CategoryMap = []; foreach ($m2Categories as $cat) { $m2CategoryMap[$cat->entity_id] = $cat; } // Find M2 categories that don't have a matching name in M1 $missingCategories = $m2Categories->filter(function($m2Cat) use ($m1CategoryNames) { $m2Name = strtolower(trim($m2Cat->name ?? '')); return !empty($m2Name) && !in_array($m2Name, $m1CategoryNames); })->map(function($m2Cat) use ($m2CategoryMap) { // Extract root category ID from path (typically second element: 1/2/3/4 -> 2) $rootCategoryId = null; $rootCategoryName = 'N/A'; if (!empty($m2Cat->path)) { $pathParts = explode('/', $m2Cat->path); // Root category is typically at index 1 (second element) if (count($pathParts) > 1 && isset($pathParts[1])) { $rootCategoryId = (int)$pathParts[1]; // Get root category name if it exists in our map if (isset($m2CategoryMap[$rootCategoryId])) { $rootCategoryName = $m2CategoryMap[$rootCategoryId]->name ?? "ID: {$rootCategoryId}"; } else { $rootCategoryName = "ID: {$rootCategoryId}"; } } } // Add root category info to the category object $m2Cat->root_category_id = $rootCategoryId; $m2Cat->root_category_name = $rootCategoryName; return $m2Cat; })->values(); return $missingCategories; } catch (Exception $e) { Log::error('Error finding M2 categories not in M1: ' . $e->getMessage()); return collect([]); } } /** * Find categories in Magento 1 that don't exist in Magento 2 * Compares by category name (case-insensitive) */ public function getM1CategoriesNotInM2() { try { $m1Categories = $this->getMagento1Categories(); $m2Categories = $this->getMagento2Categories(); // Create a set of M2 category names (case-insensitive, normalized) $m2CategoryNames = $m2Categories->map(function($cat) { return strtolower(trim($cat->name ?? '')); })->filter(function($name) { return !empty($name); })->unique()->toArray(); // Get all M1 categories to build a map for root category lookup $m1CategoryMap = []; foreach ($m1Categories as $cat) { $m1CategoryMap[$cat->entity_id] = $cat; } // Find M1 categories that don't have a matching name in M2 $missingCategories = $m1Categories->filter(function($m1Cat) use ($m2CategoryNames) { $m1Name = strtolower(trim($m1Cat->name ?? '')); return !empty($m1Name) && !in_array($m1Name, $m2CategoryNames); })->map(function($m1Cat) use ($m1CategoryMap) { // Extract root category ID from path (typically second element: 1/2/3/4 -> 2) $rootCategoryId = null; $rootCategoryName = 'N/A'; if (!empty($m1Cat->path)) { $pathParts = explode('/', $m1Cat->path); // Root category is typically at index 1 (second element) if (count($pathParts) > 1 && isset($pathParts[1])) { $rootCategoryId = (int)$pathParts[1]; // Get root category name if it exists in our map if (isset($m1CategoryMap[$rootCategoryId])) { $rootCategoryName = $m1CategoryMap[$rootCategoryId]->name ?? "ID: {$rootCategoryId}"; } else { $rootCategoryName = "ID: {$rootCategoryId}"; } } } // Add root category info to the category object $m1Cat->root_category_id = $rootCategoryId; $m1Cat->root_category_name = $rootCategoryName; return $m1Cat; })->values(); return $missingCategories; } catch (Exception $e) { Log::error('Error finding M1 categories not in M2: ' . $e->getMessage()); return collect([]); } } protected function getCategoryProductCounts($categoryIds, $source = 'm2') { if (empty($categoryIds)) { return []; } try { $connection = $source === 'm1' ? $this->magento1Connection : $this->magento2Connection; $prefix = $source === 'm1' ? $this->magento1Prefix : $this->magento2Prefix; $tableName = $prefix . 'catalog_category_product'; // Check if table exists try { $productCounts = DB::connection($connection) ->table($tableName) ->whereIn('category_id', $categoryIds) ->select('category_id', DB::raw('COUNT(product_id) as product_count')) ->groupBy('category_id') ->pluck('product_count', 'category_id') ->toArray(); } catch (Exception $e) { // Table might not exist, return empty array Log::warning("Table {$tableName} might not exist: " . $e->getMessage()); return []; } return $productCounts; } catch (Exception $e) { Log::error("Error getting product counts: " . $e->getMessage()); return []; } } /** * Build hierarchical category tree structure */ public function buildCategoryTreeHierarchy($categories, $source = 'm2') { $categoryMap = []; $rootCategories = []; // Get all category IDs $categoryIds = $categories->pluck('entity_id')->toArray(); // Get product counts for all categories $productCounts = $this->getCategoryProductCounts($categoryIds, $source); // First pass: create a map of all categories with children array foreach ($categories as $category) { $categoryId = $category->entity_id; $categoryMap[$categoryId] = [ 'id' => $categoryId, 'name' => $category->name ?? 'Unnamed Category', 'parent_id' => $category->parent_id, 'level' => $category->level ?? 0, 'position' => $category->position ?? 0, 'is_active' => $category->is_active ?? 0, 'path' => $category->path ?? '', 'product_count' => $productCounts[$categoryId] ?? 0, 'children' => [] ]; } // Second pass: build the tree structure by assigning children to parents foreach ($categoryMap as $id => $category) { $parentId = $category['parent_id']; // Skip if parent is self (prevent circular references) if ($parentId == $id) { $rootCategories[] = &$categoryMap[$id]; continue; } // Check if this is a root category (parent is 0, 1, or doesn't exist in map) if ($parentId == 0 || $parentId == 1 || !isset($categoryMap[$parentId])) { $rootCategories[] = &$categoryMap[$id]; } else { // Add as child of parent $categoryMap[$parentId]['children'][] = &$categoryMap[$id]; } } // Sort function for categories $sortCategories = function(&$categories) use (&$sortCategories) { usort($categories, function($a, $b) { return $a['position'] <=> $b['position']; }); // Recursively sort children foreach ($categories as &$category) { if (!empty($category['children'])) { $sortCategories($category['children']); } } }; // Sort root categories and all children recursively $sortCategories($rootCategories); return $rootCategories; } /** * Migrate categories from Magento 1 to Magento 2 */ public function migrateCategories($storeMapping = []) { $this->storeMapping = $storeMapping; $this->migrationLog = []; $this->categoryMapping = []; $this->addedCount = 0; $this->existingCount = 0; try { DB::connection($this->magento2Connection)->beginTransaction(); // Get root category ID for Magento 2 (usually 2) $rootCategoryId = $this->getMagento2RootCategoryId(); // Get all categories from Magento 1 $m1Categories = $this->getMagento1Categories(); // Build category tree $categoryTree = $this->buildCategoryTree($m1Categories); // Migrate categories level by level foreach ($categoryTree as $level => $categories) { foreach ($categories as $m1Category) { $this->migrateCategory($m1Category, $rootCategoryId); } } // Update children_count for all affected parent categories $this->updateChildrenCounts(); // Migrate category attributes for each store foreach ($this->storeMapping as $m1StoreId => $m2StoreId) { $this->migrateCategoryAttributes($m1StoreId, $m2StoreId); } DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => 'Categories migrated successfully', 'migrated_count' => count($this->categoryMapping), 'added_count' => $this->addedCount, 'existing_count' => $this->existingCount, 'log' => $this->migrationLog ]; } catch (Exception $e) { DB::connection($this->magento2Connection)->rollBack(); Log::error('Category migration error: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Migration failed: ' . $e->getMessage(), 'log' => $this->migrationLog ]; } } /** * Build category tree organized by level */ protected function buildCategoryTree($categories) { $tree = []; foreach ($categories as $category) { $level = $category->level ?? 1; if (!isset($tree[$level])) { $tree[$level] = []; } $tree[$level][] = $category; } ksort($tree); return $tree; } /** * Find existing category in Magento 2 by name and parent */ protected function findExistingCategoryInM2($categoryName, $parentId) { if (!$categoryName) { return null; } try { // Get name attribute ID for M2 $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return null; } $nameAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); if (!$nameAttributeId) { return null; } // Find categories with the same name and parent (checking default store view) $existingCategories = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity', 'e') ->join($this->magento2Prefix . 'catalog_category_entity_varchar as v', function($join) use ($nameAttributeId) { $join->on('e.entity_id', '=', 'v.entity_id') ->where('v.attribute_id', '=', $nameAttributeId) ->where('v.store_id', '=', 0); }) ->where('v.value', '=', $categoryName) ->where('e.parent_id', '=', $parentId) ->select('e.entity_id') ->first(); return $existingCategories ? $existingCategories->entity_id : null; } catch (Exception $e) { Log::error("Error finding existing category in M2: " . $e->getMessage()); return null; } } /** * Migrate a single category */ protected function migrateCategory($m1Category, $parentId = null) { try { // Check if category already exists in mapping (already processed in this migration) if (isset($this->categoryMapping[$m1Category->entity_id])) { return $this->categoryMapping[$m1Category->entity_id]; } // Determine parent ID if ($m1Category->parent_id == 1 || $m1Category->parent_id == 0) { $m2ParentId = $parentId ?? $this->getMagento2RootCategoryId(); } else { $m2ParentId = $this->categoryMapping[$m1Category->parent_id] ?? $parentId; } // Check if category already exists in M2 database $existingM2CategoryId = null; if ($m1Category->name) { $existingM2CategoryId = $this->findExistingCategoryInM2($m1Category->name, $m2ParentId); } if ($existingM2CategoryId) { // Category already exists in M2, use existing ID $m2EntityId = $existingM2CategoryId; $this->existingCount++; $this->migrationLog[] = "Found existing category: ID {$m1Category->entity_id} -> {$m2EntityId} ({$m1Category->name})"; } else { // Category doesn't exist, create new one $m2EntityId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->insertGetId([ 'attribute_set_id' => 3, // Default category attribute set 'parent_id' => $m2ParentId, 'created_at' => now(), 'updated_at' => now(), 'path' => '', 'position' => $m1Category->position ?? 0, 'level' => $m1Category->level ?? 1, 'children_count' => 0, ]); // Update path $path = $this->buildCategoryPath($m2EntityId, $m2ParentId); DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('entity_id', $m2EntityId) ->update(['path' => $path]); $this->addedCount++; $this->migrationLog[] = "Added new category: ID {$m1Category->entity_id} -> {$m2EntityId} ({$m1Category->name})"; } // Store mapping $this->categoryMapping[$m1Category->entity_id] = $m2EntityId; return $m2EntityId; } catch (Exception $e) { Log::error("Error migrating category {$m1Category->entity_id}: " . $e->getMessage()); $this->migrationLog[] = "ERROR: Failed to migrate category ID {$m1Category->entity_id}: " . $e->getMessage(); throw $e; } } /** * Update children_count for all parent categories */ protected function updateChildrenCounts() { try { // Get all unique parent IDs from migrated categories $parentIds = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->whereIn('entity_id', array_values($this->categoryMapping)) ->distinct() ->pluck('parent_id') ->toArray(); // Update children_count for each parent foreach ($parentIds as $parentId) { if ($parentId > 0) { $childrenCount = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('parent_id', $parentId) ->count(); DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('entity_id', $parentId) ->update(['children_count' => $childrenCount]); } } } catch (Exception $e) { Log::error("Error updating children counts: " . $e->getMessage()); // Don't throw, just log the error } } /** * Build category path */ protected function buildCategoryPath($entityId, $parentId) { if ($parentId == $this->getMagento2RootCategoryId()) { return "1/{$parentId}/{$entityId}"; } $parent = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('entity_id', $parentId) ->first(); if ($parent && $parent->path) { return $parent->path . '/' . $entityId; } return "1/{$parentId}/{$entityId}"; } /** * Migrate category attributes for a specific store */ protected function migrateCategoryAttributes($m1StoreId, $m2StoreId) { try { // Get attribute IDs for Magento 2 $attributeIds = $this->getMagento2AttributeIds(); // Get categories with their attributes from Magento 1 $m1Categories = $this->getMagento1Categories($m1StoreId); foreach ($m1Categories as $m1Category) { if (!isset($this->categoryMapping[$m1Category->entity_id])) { continue; } $m2EntityId = $this->categoryMapping[$m1Category->entity_id]; // Migrate name if ($m1Category->name) { $this->insertCategoryAttribute( $m2EntityId, $attributeIds['name'], $m2StoreId, $m1Category->name ); } // Migrate is_active if ($m1Category->is_active !== null) { $this->insertCategoryAttribute( $m2EntityId, $attributeIds['is_active'], $m2StoreId, $m1Category->is_active ); } // Migrate url_key if ($m1Category->url_key) { $this->insertCategoryAttribute( $m2EntityId, $attributeIds['url_key'], $m2StoreId, $m1Category->url_key ); } } $this->migrationLog[] = "Migrated attributes for store mapping: M1 Store {$m1StoreId} -> M2 Store {$m2StoreId}"; } catch (Exception $e) { Log::error("Error migrating category attributes: " . $e->getMessage()); $this->migrationLog[] = "ERROR: Failed to migrate attributes for store {$m1StoreId}: " . $e->getMessage(); throw $e; } } /** * Insert category attribute value */ protected function insertCategoryAttribute($entityId, $attributeId, $storeId, $value) { // Determine which table to use based on attribute type $attribute = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('attribute_id', $attributeId) ->first(); if (!$attribute) { return; } $table = $this->magento2Prefix . 'catalog_category_entity_' . $attribute->backend_type; // Check if value already exists $exists = DB::connection($this->magento2Connection) ->table($table) ->where('entity_id', $entityId) ->where('attribute_id', $attributeId) ->where('store_id', $storeId) ->exists(); if ($exists) { DB::connection($this->magento2Connection) ->table($table) ->where('entity_id', $entityId) ->where('attribute_id', $attributeId) ->where('store_id', $storeId) ->update(['value' => $value]); } else { DB::connection($this->magento2Connection) ->table($table) ->insert([ 'attribute_id' => $attributeId, 'store_id' => $storeId, 'entity_id' => $entityId, 'value' => $value, ]); } } /** * Get Magento 2 attribute IDs */ protected function getMagento2AttributeIds() { $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); $attributes = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->whereIn('attribute_code', ['name', 'is_active', 'url_key']) ->pluck('attribute_id', 'attribute_code') ->toArray(); return $attributes; } /** * Migrate a single category from Magento 1 to Magento 2 */ public function migrateSingleCategory($m1CategoryId, $storeId = 0) { try { DB::connection($this->magento2Connection)->beginTransaction(); // Get the M1 category $m1Categories = $this->getMagento1Categories($storeId); $m1Category = $m1Categories->firstWhere('entity_id', $m1CategoryId); if (!$m1Category) { return [ 'success' => false, 'message' => 'Category not found in Magento 1', ]; } // Determine parent ID in M2 $m2ParentId = $this->getMagento2RootCategoryId(); // If category has a parent in M1, try to find it in M2 if ($m1Category->parent_id && $m1Category->parent_id != 0 && $m1Category->parent_id != 1) { // Check if parent was already migrated (check by name) $m1ParentCategories = $this->getMagento1Categories($storeId); $m1Parent = $m1ParentCategories->firstWhere('entity_id', $m1Category->parent_id); if ($m1Parent && $m1Parent->name) { $m2ParentId = $this->findExistingCategoryInM2($m1Parent->name, $this->getMagento2RootCategoryId()); if (!$m2ParentId) { // Parent not found, use root $m2ParentId = $this->getMagento2RootCategoryId(); } } } // Check if category already exists in M2 $existingM2CategoryId = null; if ($m1Category->name) { $existingM2CategoryId = $this->findExistingCategoryInM2($m1Category->name, $m2ParentId); } if ($existingM2CategoryId) { DB::connection($this->magento2Connection)->rollBack(); return [ 'success' => false, 'message' => 'Category already exists in Magento 2 (ID: ' . $existingM2CategoryId . ')', 'm2_category_id' => $existingM2CategoryId, ]; } // Create new category entity $m2EntityId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->insertGetId([ 'attribute_set_id' => 3, // Default category attribute set 'parent_id' => $m2ParentId, 'created_at' => now(), 'updated_at' => now(), 'path' => '', 'position' => $m1Category->position ?? 0, 'level' => $m1Category->level ?? 1, 'children_count' => 0, ]); // Update path $path = $this->buildCategoryPath($m2EntityId, $m2ParentId); DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('entity_id', $m2EntityId) ->update(['path' => $path]); // Migrate attributes for default store (0) $attributeIds = $this->getMagento2AttributeIds(); // Migrate name if ($m1Category->name) { $this->insertCategoryAttribute( $m2EntityId, $attributeIds['name'], $storeId, $m1Category->name ); } // Migrate is_active if ($m1Category->is_active !== null) { $this->insertCategoryAttribute( $m2EntityId, $attributeIds['is_active'], $storeId, $m1Category->is_active ); } // Migrate url_key if ($m1Category->url_key) { $this->insertCategoryAttribute( $m2EntityId, $attributeIds['url_key'], $storeId, $m1Category->url_key ); } // Update children_count for parent if ($m2ParentId > 0) { $childrenCount = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('parent_id', $m2ParentId) ->count(); DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('entity_id', $m2ParentId) ->update(['children_count' => $childrenCount]); } DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => 'Category migrated successfully', 'm1_category_id' => $m1CategoryId, 'm2_category_id' => $m2EntityId, 'category_name' => $m1Category->name ?? 'Unnamed Category', ]; } catch (Exception $e) { DB::connection($this->magento2Connection)->rollBack(); Log::error("Error migrating single category {$m1CategoryId}: " . $e->getMessage()); return [ 'success' => false, 'message' => 'Migration failed: ' . $e->getMessage(), ]; } } /** * Rename a Magento 2 category */ public function renameCategory($categoryId, $newName, $storeId = 0) { try { // Validate category exists $category = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('entity_id', $categoryId) ->first(); if (!$category) { return [ 'success' => false, 'message' => 'Category not found', ]; } // Prevent renaming system root if ($categoryId == 0 || $categoryId == 1) { return [ 'success' => false, 'message' => 'Cannot rename system root category', ]; } // Validate name $newName = trim($newName); if (empty($newName)) { return [ 'success' => false, 'message' => 'Category name cannot be empty', ]; } // Get name attribute ID $attributeIds = $this->getMagento2AttributeIds(); if (!isset($attributeIds['name'])) { return [ 'success' => false, 'message' => 'Name attribute not found', ]; } DB::connection($this->magento2Connection)->beginTransaction(); // Update name attribute for the specified store (default store = 0) $this->insertCategoryAttribute( $categoryId, $attributeIds['name'], $storeId, $newName ); DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => 'Category renamed successfully', 'new_name' => $newName, ]; } catch (Exception $e) { if (isset($this->magento2Connection)) { DB::connection($this->magento2Connection)->rollBack(); } Log::error("Error renaming category {$categoryId}: " . $e->getMessage()); return [ 'success' => false, 'message' => 'Failed to rename category: ' . $e->getMessage(), ]; } } /** * Get Magento 2 root category ID */ protected function getMagento2RootCategoryId() { // Root category is usually ID 2 in Magento 2 $rootId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->where('level', 0) ->where('parent_id', 0) ->value('entity_id'); return $rootId ?: 2; } /** * Delete a category and all its children recursively */ public function deleteCategory($categoryId, $source = 'm2') { try { $connection = $source === 'm1' ? $this->magento1Connection : $this->magento2Connection; $prefix = $source === 'm1' ? $this->magento1Prefix : $this->magento2Prefix; // Get category information $category = DB::connection($connection) ->table($prefix . 'catalog_category_entity') ->where('entity_id', $categoryId) ->first(); if (!$category) { return [ 'success' => false, 'message' => 'Category not found', ]; } // Prevent deletion of system root (ID 0 or 1) if ($categoryId == 0 || $categoryId == 1) { return [ 'success' => false, 'message' => 'Cannot delete system root category', ]; } DB::connection($connection)->beginTransaction(); // Recursively delete all children first $this->deleteCategoryChildren($categoryId, $connection, $prefix); // Now delete the category itself $this->deleteCategoryData($categoryId, $connection, $prefix); // Update parent's children_count if parent exists if ($category->parent_id > 0 && $category->parent_id != 1) { $parentChildrenCount = DB::connection($connection) ->table($prefix . 'catalog_category_entity') ->where('parent_id', $category->parent_id) ->count(); DB::connection($connection) ->table($prefix . 'catalog_category_entity') ->where('entity_id', $category->parent_id) ->update(['children_count' => $parentChildrenCount]); } DB::connection($connection)->commit(); return [ 'success' => true, 'message' => 'Category and all subcategories deleted successfully', ]; } catch (Exception $e) { if (isset($connection)) { DB::connection($connection)->rollBack(); } Log::error("Error deleting category {$categoryId}: " . $e->getMessage()); return [ 'success' => false, 'message' => 'Failed to delete category: ' . $e->getMessage(), ]; } } /** * Test database connections */ public function testConnections() { $results = [ 'magento1' => false, 'magento2' => false, ]; try { DB::connection($this->magento1Connection)->select('SELECT 1'); $results['magento1'] = true; } catch (Exception $e) { $results['magento1_error'] = $e->getMessage(); } try { DB::connection($this->magento2Connection)->select('SELECT 1'); $results['magento2'] = true; } catch (Exception $e) { $results['magento2_error'] = $e->getMessage(); } return $results; } /** * Get all category attributes from Magento 1 */ public function getMagento1Attributes() { try { // Get entity type ID for catalog_category $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get all attributes for catalog_category $attributes = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->select('attribute_id', 'attribute_code', 'backend_type', 'frontend_input', 'frontend_label', 'is_required', 'is_user_defined', 'default_value') ->orderBy('attribute_code') ->get(); return $attributes; } catch (Exception $e) { Log::error('Error fetching Magento 1 attributes: ' . $e->getMessage()); return collect([]); } } /** * Get all category attributes from Magento 2 */ public function getMagento2Attributes() { try { // Get entity type ID for catalog_category $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get all attributes for catalog_category $attributes = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->select('attribute_id', 'attribute_code', 'backend_type', 'frontend_input', 'frontend_label', 'is_required', 'is_user_defined', 'default_value') ->orderBy('attribute_code') ->get(); return $attributes; } catch (Exception $e) { Log::error('Error fetching Magento 2 attributes: ' . $e->getMessage()); return collect([]); } } /** * Get attributes that exist in Magento 1 but are missing in Magento 2 */ public function getM1AttributesMissingInM2() { try { $m1Attributes = $this->getMagento1Attributes(); $m2Attributes = $this->getMagento2Attributes(); // Get all M2 attribute codes $m2AttributeCodes = $m2Attributes->pluck('attribute_code')->toArray(); // Filter M1 attributes that don't exist in M2 $missingAttributes = $m1Attributes->filter(function ($attr) use ($m2AttributeCodes) { return !in_array($attr->attribute_code, $m2AttributeCodes); }); return $missingAttributes->values(); } catch (Exception $e) { Log::error('Error fetching missing attributes: ' . $e->getMessage()); return collect([]); } } /** * Migrate an attribute from Magento 1 to Magento 2 */ public function migrateAttribute($m1AttributeId) { try { // Get M1 entity type ID $m1EntityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$m1EntityTypeId) { return ['success' => false, 'message' => 'Magento 1 entity type not found']; } // Get full attribute data from M1 $m1Attribute = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('attribute_id', $m1AttributeId) ->where('entity_type_id', $m1EntityTypeId) ->first(); if (!$m1Attribute) { return ['success' => false, 'message' => 'Attribute not found in Magento 1']; } // Check if attribute already exists in M2 $m2EntityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$m2EntityTypeId) { return ['success' => false, 'message' => 'Magento 2 entity type not found']; } $existingAttribute = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $m2EntityTypeId) ->where('attribute_code', $m1Attribute->attribute_code) ->first(); if ($existingAttribute) { return ['success' => false, 'message' => 'Attribute already exists in Magento 2']; } DB::connection($this->magento2Connection)->beginTransaction(); // Insert attribute into M2 $m2AttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->insertGetId([ 'entity_type_id' => $m2EntityTypeId, 'attribute_code' => $m1Attribute->attribute_code, 'attribute_model' => $m1Attribute->attribute_model ?? null, 'backend_model' => $m1Attribute->backend_model ?? null, 'backend_type' => $m1Attribute->backend_type, 'backend_table' => $m1Attribute->backend_table ?? null, 'frontend_model' => $m1Attribute->frontend_model ?? null, 'frontend_input' => $m1Attribute->frontend_input ?? null, 'frontend_label' => $m1Attribute->frontend_label ?? null, 'frontend_class' => $m1Attribute->frontend_class ?? null, 'source_model' => $m1Attribute->source_model ?? null, 'is_required' => $m1Attribute->is_required ?? 0, 'is_user_defined' => $m1Attribute->is_user_defined ?? 1, 'default_value' => $m1Attribute->default_value ?? null, 'is_unique' => $m1Attribute->is_unique ?? 0, 'note' => $m1Attribute->note ?? null, ]); // Add attribute to default attribute set if it exists $defaultAttributeSet = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_set') ->where('entity_type_id', $m2EntityTypeId) ->where('attribute_set_name', 'Default') ->first(); if ($defaultAttributeSet) { $defaultGroup = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_group') ->where('attribute_set_id', $defaultAttributeSet->attribute_set_id) ->orderBy('sort_order') ->first(); if ($defaultGroup) { // Get max sort order for this group $maxSortOrder = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_attribute') ->where('attribute_set_id', $defaultAttributeSet->attribute_set_id) ->where('attribute_group_id', $defaultGroup->attribute_group_id) ->max('sort_order') ?? 0; // Add attribute to entity_attribute table DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_attribute') ->insert([ 'entity_type_id' => $m2EntityTypeId, 'attribute_set_id' => $defaultAttributeSet->attribute_set_id, 'attribute_group_id' => $defaultGroup->attribute_group_id, 'attribute_id' => $m2AttributeId, 'sort_order' => $maxSortOrder + 10, ]); } } DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => 'Attribute migrated successfully', 'attribute_code' => $m1Attribute->attribute_code, 'm2_attribute_id' => $m2AttributeId, ]; } catch (Exception $e) { if (isset($this->magento2Connection)) { DB::connection($this->magento2Connection)->rollBack(); } Log::error("Error migrating attribute {$m1AttributeId}: " . $e->getMessage()); return ['success' => false, 'message' => 'Failed to migrate attribute: ' . $e->getMessage()]; } } /** * Get attribute groups from Magento 1 */ public function getMagento1AttributeGroups() { try { // Get entity type ID for catalog_category $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get attribute sets for catalog_category $attributeSets = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute_set') ->where('entity_type_id', $entityTypeId) ->get(); $groups = collect([]); foreach ($attributeSets as $set) { // Get attribute groups for this set $setGroups = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute_group') ->where('attribute_set_id', $set->attribute_set_id) ->select('attribute_group_id', 'attribute_set_id', 'attribute_group_name', 'sort_order') ->orderBy('sort_order') ->get(); foreach ($setGroups as $group) { // Get attributes in this group $attributeIds = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_attribute') ->where('attribute_set_id', $set->attribute_set_id) ->where('attribute_group_id', $group->attribute_group_id) ->pluck('attribute_id') ->toArray(); $group->attribute_set_name = $set->attribute_set_name ?? 'Default'; $group->attribute_count = count($attributeIds); $group->attribute_ids = $attributeIds; $groups->push($group); } } return $groups; } catch (Exception $e) { Log::error('Error fetching Magento 1 attribute groups: ' . $e->getMessage()); return collect([]); } } /** * Get attribute groups from Magento 2 */ public function getMagento2AttributeGroups() { try { // Get entity type ID for catalog_category $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get attribute sets for catalog_category $attributeSets = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_set') ->where('entity_type_id', $entityTypeId) ->get(); $groups = collect([]); foreach ($attributeSets as $set) { // Get attribute groups for this set $setGroups = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_group') ->where('attribute_set_id', $set->attribute_set_id) ->select('attribute_group_id', 'attribute_set_id', 'attribute_group_name', 'sort_order') ->orderBy('sort_order') ->get(); foreach ($setGroups as $group) { // Get attributes in this group $attributeIds = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_attribute') ->where('attribute_set_id', $set->attribute_set_id) ->where('attribute_group_id', $group->attribute_group_id) ->pluck('attribute_id') ->toArray(); $group->attribute_set_name = $set->attribute_set_name ?? 'Default'; $group->attribute_count = count($attributeIds); $group->attribute_ids = $attributeIds; $groups->push($group); } } return $groups; } catch (Exception $e) { Log::error('Error fetching Magento 2 attribute groups: ' . $e->getMessage()); return collect([]); } } /** * Get attribute groups that exist in Magento 1 but are missing in Magento 2 */ public function getM1AttributeGroupsMissingInM2() { try { $m1Groups = $this->getMagento1AttributeGroups(); $m2Groups = $this->getMagento2AttributeGroups(); // Create a map of M2 groups by set name and group name $m2GroupMap = []; foreach ($m2Groups as $m2Group) { $key = ($m2Group->attribute_set_name ?? 'Default') . '|' . ($m2Group->attribute_group_name ?? ''); $m2GroupMap[$key] = true; } // Filter M1 groups that don't exist in M2 $missingGroups = $m1Groups->filter(function ($group) use ($m2GroupMap) { $key = ($group->attribute_set_name ?? 'Default') . '|' . ($group->attribute_group_name ?? ''); return !isset($m2GroupMap[$key]); }); return $missingGroups->values(); } catch (Exception $e) { Log::error('Error fetching missing attribute groups: ' . $e->getMessage()); return collect([]); } } /** * Migrate an attribute group from Magento 1 to Magento 2 */ public function migrateAttributeGroup($m1GroupId, $m1SetId) { try { // Get M1 entity type ID $m1EntityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$m1EntityTypeId) { return ['success' => false, 'message' => 'Magento 1 entity type not found']; } // Get M1 attribute group $m1Group = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute_group') ->where('attribute_group_id', $m1GroupId) ->where('attribute_set_id', $m1SetId) ->first(); if (!$m1Group) { return ['success' => false, 'message' => 'Attribute group not found in Magento 1']; } // Get M1 attribute set $m1Set = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute_set') ->where('attribute_set_id', $m1SetId) ->where('entity_type_id', $m1EntityTypeId) ->first(); if (!$m1Set) { return ['success' => false, 'message' => 'Attribute set not found in Magento 1']; } // Get M2 entity type ID $m2EntityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$m2EntityTypeId) { return ['success' => false, 'message' => 'Magento 2 entity type not found']; } // Find or create the corresponding attribute set in M2 $m2Set = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_set') ->where('entity_type_id', $m2EntityTypeId) ->where('attribute_set_name', $m1Set->attribute_set_name) ->first(); if (!$m2Set) { // Create the attribute set in M2 $m2SetId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_set') ->insertGetId([ 'entity_type_id' => $m2EntityTypeId, 'attribute_set_name' => $m1Set->attribute_set_name, 'sort_order' => $m1Set->sort_order ?? 0, ]); $m2Set = (object)['attribute_set_id' => $m2SetId]; } else { $m2SetId = $m2Set->attribute_set_id; } // Check if group already exists in M2 $existingGroup = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_group') ->where('attribute_set_id', $m2SetId) ->where('attribute_group_name', $m1Group->attribute_group_name) ->first(); if ($existingGroup) { return ['success' => false, 'message' => 'Attribute group already exists in Magento 2']; } DB::connection($this->magento2Connection)->beginTransaction(); // Create the attribute group in M2 $m2GroupId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute_group') ->insertGetId([ 'attribute_set_id' => $m2SetId, 'attribute_group_name' => $m1Group->attribute_group_name, 'sort_order' => $m1Group->sort_order ?? 0, ]); // Get attributes from M1 group $m1Attributes = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_attribute') ->where('attribute_set_id', $m1SetId) ->where('attribute_group_id', $m1GroupId) ->orderBy('sort_order') ->get(); // Migrate attributes to M2 group (if they exist in M2) $migratedCount = 0; foreach ($m1Attributes as $m1EntityAttr) { // Get M1 attribute code $m1Attribute = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('attribute_id', $m1EntityAttr->attribute_id) ->first(); if ($m1Attribute) { // Find corresponding M2 attribute by code $m2Attribute = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $m2EntityTypeId) ->where('attribute_code', $m1Attribute->attribute_code) ->first(); if ($m2Attribute) { // Check if already in entity_attribute $exists = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_attribute') ->where('attribute_set_id', $m2SetId) ->where('attribute_group_id', $m2GroupId) ->where('attribute_id', $m2Attribute->attribute_id) ->exists(); if (!$exists) { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_attribute') ->insert([ 'entity_type_id' => $m2EntityTypeId, 'attribute_set_id' => $m2SetId, 'attribute_group_id' => $m2GroupId, 'attribute_id' => $m2Attribute->attribute_id, 'sort_order' => $m1EntityAttr->sort_order ?? 0, ]); $migratedCount++; } } } } DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => 'Attribute group migrated successfully', 'group_name' => $m1Group->attribute_group_name, 'm2_group_id' => $m2GroupId, 'attributes_migrated' => $migratedCount, ]; } catch (Exception $e) { if (isset($this->magento2Connection)) { DB::connection($this->magento2Connection)->rollBack(); } Log::error("Error migrating attribute group {$m1GroupId}: " . $e->getMessage()); return ['success' => false, 'message' => 'Failed to migrate attribute group: ' . $e->getMessage()]; } } /** * Get all products from Magento 1 */ public function getMagento1Products() { try { // Get entity type ID for catalog_product $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get base product data - check if SKU column exists in entity table $hasSkuColumn = false; try { $testQuery = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity') ->select('sku') ->limit(1) ->first(); $hasSkuColumn = true; } catch (Exception $e) { // SKU column doesn't exist, will use EAV $hasSkuColumn = false; } if ($hasSkuColumn) { // SKU is stored directly in entity table $products = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity') ->select('entity_id', 'sku', 'type_id', 'attribute_set_id', 'created_at', 'updated_at') ->orderBy('entity_id') ->get(); } else { // SKU is stored as EAV attribute $products = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity') ->select('entity_id', 'type_id', 'attribute_set_id', 'created_at', 'updated_at') ->orderBy('entity_id') ->get(); } // Get SKU attribute ID (for EAV storage) $skuAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'sku') ->value('attribute_id'); // Get name attribute ID $nameAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); // Get SKU and name values from EAV if needed $skus = []; $names = []; if (!$hasSkuColumn && $skuAttributeId) { $skuValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $skuAttributeId) ->where('store_id', 0) ->pluck('value', 'entity_id') ->toArray(); $skus = $skuValues; } if ($nameAttributeId) { $nameValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $nameAttributeId) ->where('store_id', 0) ->pluck('value', 'entity_id') ->toArray(); $names = $nameValues; } // Combine data foreach ($products as $product) { if ($hasSkuColumn) { // SKU from entity table $product->sku = !empty($product->sku) ? $product->sku : 'N/A'; } else { // SKU from EAV $product->sku = $skus[$product->entity_id] ?? 'N/A'; } $product->name = $names[$product->entity_id] ?? 'Unnamed Product'; } return $products; } catch (Exception $e) { Log::error('Error fetching Magento 1 products: ' . $e->getMessage()); return collect([]); } } /** * Get all products from Magento 2 */ public function getMagento2Products() { try { // Get entity type ID for catalog_product $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } // Get base product data - check if SKU column exists in entity table $hasSkuColumn = false; try { $testQuery = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->select('sku') ->limit(1) ->first(); $hasSkuColumn = true; } catch (Exception $e) { // SKU column doesn't exist, will use EAV $hasSkuColumn = false; } if ($hasSkuColumn) { // SKU is stored directly in entity table $products = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->select('entity_id', 'sku', 'type_id', 'attribute_set_id', 'created_at', 'updated_at') ->orderBy('entity_id') ->get(); } else { // SKU is stored as EAV attribute $products = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->select('entity_id', 'type_id', 'attribute_set_id', 'created_at', 'updated_at') ->orderBy('entity_id') ->get(); } // Get SKU attribute ID (for EAV storage) $skuAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'sku') ->value('attribute_id'); // Get name attribute ID $nameAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); // Get SKU and name values from EAV if needed $skus = []; $names = []; if (!$hasSkuColumn && $skuAttributeId) { $skuValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $skuAttributeId) ->where('store_id', 0) ->pluck('value', 'entity_id') ->toArray(); $skus = $skuValues; } if ($nameAttributeId) { $nameValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $nameAttributeId) ->where('store_id', 0) ->pluck('value', 'entity_id') ->toArray(); $names = $nameValues; } // Combine data foreach ($products as $product) { if ($hasSkuColumn) { // SKU from entity table $product->sku = !empty($product->sku) ? $product->sku : 'N/A'; } else { // SKU from EAV $product->sku = $skus[$product->entity_id] ?? 'N/A'; } $product->name = $names[$product->entity_id] ?? 'Unnamed Product'; } return $products; } catch (Exception $e) { Log::error('Error fetching Magento 2 products: ' . $e->getMessage()); return collect([]); } } /** * Get Magento 1 products that don't exist in Magento 2 */ public function getM1ProductsNotInM2() { try { $m1Products = $this->getMagento1Products(); $m2Products = $this->getMagento2Products(); // Get all M2 SKUs $m2Skus = $m2Products->pluck('sku')->filter(function($sku) { return $sku !== 'N/A' && !empty($sku); })->toArray(); // Get M2 product IDs (for products without SKU) $m2ProductIds = $m2Products->pluck('entity_id')->toArray(); // Filter M1 products that don't exist in M2 $missingProducts = $m1Products->filter(function ($m1Product) use ($m2Skus, $m2ProductIds) { $m1Sku = $m1Product->sku ?? 'N/A'; $hasSku = ($m1Sku !== 'N/A' && !empty($m1Sku)); if ($hasSku) { // Check by SKU return !in_array($m1Sku, $m2Skus); } else { // Check by product ID return !in_array($m1Product->entity_id, $m2ProductIds); } }); return $missingProducts->values(); } catch (Exception $e) { Log::error('Error fetching missing products: ' . $e->getMessage()); return collect([]); } } /** * Get Magento 2 products that don't exist in Magento 1 */ public function getM2ProductsNotInM1() { try { $m1Products = $this->getMagento1Products(); $m2Products = $this->getMagento2Products(); // Get all M1 SKUs $m1Skus = $m1Products->pluck('sku')->filter(function($sku) { return $sku !== 'N/A' && !empty($sku); })->toArray(); // Get M1 product IDs (for products without SKU) $m1ProductIds = $m1Products->pluck('entity_id')->toArray(); // Filter M2 products that don't exist in M1 $missingProducts = $m2Products->filter(function ($m2Product) use ($m1Skus, $m1ProductIds) { $m2Sku = $m2Product->sku ?? 'N/A'; $hasSku = ($m2Sku !== 'N/A' && !empty($m2Sku)); if ($hasSku) { // Check by SKU return !in_array($m2Sku, $m1Skus); } else { // Check by product ID return !in_array($m2Product->entity_id, $m1ProductIds); } }); return $missingProducts->values(); } catch (Exception $e) { Log::error('Error fetching M2 products not in M1: ' . $e->getMessage()); return collect([]); } } /** * Migrate all products from Magento 1 to Magento 2 */ public function migrateProducts($dryRun = false) { try { $this->migrationLog = []; $addedCount = 0; $updatedCount = 0; $errorCount = 0; $missingAttributes = []; // Get M1 and M2 entity type IDs $m1EntityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); $m2EntityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$m1EntityTypeId || !$m2EntityTypeId) { return [ 'success' => false, 'message' => 'Entity type not found', 'added' => 0, 'updated' => 0, 'errors' => 0, 'log' => [], 'missing_attributes' => [] ]; } // Get all M1 products $m1Products = $this->getMagento1Products(); // Get M2 attribute IDs (common ones) $m2AttributeIds = $this->getMagento2ProductAttributeIds(); // Get M1 attribute IDs for common attributes $m1AttributeIds = $this->getMagento1ProductAttributeIds(); // Get all M1 attributes to check for missing ones $allM1Attributes = $this->getAllMagento1ProductAttributes(); // Also get all M1 attribute IDs (not just common ones) for comprehensive checking $allM1AttributeIds = []; if ($allM1Attributes) { foreach ($allM1Attributes as $attr) { $allM1AttributeIds[$attr->attribute_code] = $attr->attribute_id; } } // Get all M2 attribute IDs for comparison $allM2AttributeIds = $this->getAllMagento2ProductAttributeIds(); // Get category mapping (from previous category migrations) $categoryMapping = $this->getCategoryMapping(); if (!$dryRun) { DB::connection($this->magento2Connection)->beginTransaction(); } // Check if M2 has SKU column in entity table $m2HasSkuColumn = false; try { $testQuery = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->select('sku') ->limit(1) ->first(); $m2HasSkuColumn = true; } catch (Exception $e) { $m2HasSkuColumn = false; } foreach ($m1Products as $m1Product) { try { $m1Sku = $m1Product->sku ?? 'N/A'; $hasSku = ($m1Sku !== 'N/A' && !empty($m1Sku)); // Check if product exists in M2 by SKU (if SKU exists) or by ID (if no SKU) $m2Product = null; $m2ProductId = null; $isNew = false; if ($hasSku) { // Check if SKU exists in M2 - try both entity table and EAV if ($m2HasSkuColumn) { // SKU is stored in entity table $m2Product = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->where('sku', $m1Sku) ->first(); } else { // SKU is stored in EAV table if (isset($m2AttributeIds['sku'])) { $m2Product = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $m2AttributeIds['sku']) ->where('value', $m1Sku) ->where('store_id', 0) ->first(); } } } else { // Product has no SKU, check by entity_id $m2Product = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->where('entity_id', $m1Product->entity_id) ->first(); } if ($m2Product) { // Product exists, get its entity_id $m2ProductId = $m2Product->entity_id; if (!$dryRun) { $this->migrationLog[] = "Updating existing product: " . ($hasSku ? "SKU {$m1Sku}" : "No SKU") . " (ID: {$m2ProductId})"; } else { $this->migrationLog[] = "Would update existing product: " . ($hasSku ? "SKU {$m1Sku}" : "No SKU") . " (ID: {$m2ProductId})"; } $updatedCount++; } else { // Product doesn't exist if ($hasSku) { // Product has SKU, will get new ID if (!$dryRun) { // Create new product - check if we need to set SKU in entity table or EAV if ($m2HasSkuColumn) { // SKU goes in entity table $m2ProductId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->insertGetId([ 'sku' => $m1Sku, 'attribute_set_id' => $m1Product->attribute_set_id ?? 4, 'type_id' => $m1Product->type_id ?? 'simple', 'created_at' => $m1Product->created_at ?? now(), 'updated_at' => now(), ]); } else { // SKU goes in EAV table $m2ProductId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->insertGetId([ 'attribute_set_id' => $m1Product->attribute_set_id ?? 4, 'type_id' => $m1Product->type_id ?? 'simple', 'created_at' => $m1Product->created_at ?? now(), 'updated_at' => now(), ]); // Insert SKU in EAV table if (isset($m2AttributeIds['sku'])) { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_varchar') ->insert([ 'attribute_id' => $m2AttributeIds['sku'], 'store_id' => 0, 'entity_id' => $m2ProductId, 'value' => $m1Sku, ]); } } $this->migrationLog[] = "Created new product: SKU {$m1Sku} (ID: {$m2ProductId})"; } else { $m2ProductId = $m1Product->entity_id; // Use M1 ID for dry run simulation $this->migrationLog[] = "Would create new product: SKU {$m1Sku} (ID: {$m2ProductId})"; } } else { // Product has no SKU, use M1 product ID $m2ProductId = $m1Product->entity_id; if (!$dryRun) { // Check if this ID already exists in M2 $existing = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->where('entity_id', $m2ProductId) ->exists(); if (!$existing) { $insertData = [ 'entity_id' => $m2ProductId, 'attribute_set_id' => $m1Product->attribute_set_id ?? 4, 'type_id' => $m1Product->type_id ?? 'simple', 'created_at' => $m1Product->created_at ?? now(), 'updated_at' => now(), ]; // If SKU column exists, set it to NULL or empty if ($m2HasSkuColumn) { $insertData['sku'] = null; } DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->insert($insertData); $this->migrationLog[] = "Created new product: No SKU (ID: {$m2ProductId}, using M1 ID)"; } else { $this->migrationLog[] = "Updating existing product: No SKU (ID: {$m2ProductId})"; $updatedCount++; $addedCount--; // Adjust counts continue; } } else { $this->migrationLog[] = "Would create new product: No SKU (ID: {$m2ProductId}, using M1 ID)"; } } $isNew = true; $addedCount++; } // Migrate product attributes (with dry run support) // Migrate ALL data from ALL catalog_product_entity_* tables // This includes images, all store views, and all attributes $missingAttrs = $this->migrateAllProductEntityData($m1Product->entity_id, $m2ProductId, $m1EntityTypeId, $m2EntityTypeId, $allM1AttributeIds, $allM2AttributeIds, $dryRun, $allM1Attributes); if ($missingAttrs) { foreach ($missingAttrs as $attr) { // Check if this attribute is already in the list $exists = false; foreach ($missingAttributes as $existingAttr) { if ($existingAttr['code'] === $attr['code']) { $exists = true; break; } } if (!$exists) { $missingAttributes[] = $attr; } } } // Migrate media gallery images (skip in dry run) if (!$dryRun) { $this->migrateProductMediaGallery($m1Product->entity_id, $m2ProductId, $m1EntityTypeId); } // Migrate category associations (skip in dry run) if (!$dryRun) { // Ensure product is enabled and visible (required for products to show in categories after reindex) $this->ensureProductIsEnabledAndVisible($m2ProductId); $this->migrateProductCategories($m1Product->entity_id, $m2ProductId, $categoryMapping); } } catch (Exception $e) { $errorCount++; $errorMsg = $e->getMessage(); Log::error("Error migrating product {$m1Product->entity_id} (SKU: " . ($m1Product->sku ?? 'N/A') . "): " . $errorMsg); $this->migrationLog[] = "ERROR: Failed to migrate product SKU " . ($m1Product->sku ?? 'N/A') . ": " . $errorMsg; // Try to extract missing attribute from error message if (preg_match("/Table.*catalog_product_entity_(\w+).*doesn't exist/", $errorMsg, $matches)) { $tableType = $matches[1] ?? null; if ($tableType && $allM1Attributes) { // Find attributes that use this backend type foreach ($allM1Attributes as $attr) { if ($attr->backend_type === $tableType) { $missingAttr = [ 'code' => $attr->attribute_code, 'label' => $attr->frontend_label ?? $attr->attribute_code, 'type' => $attr->backend_type ]; // Check if already exists $exists = false; foreach ($missingAttributes as $existingAttr) { if ($existingAttr['code'] === $missingAttr['code']) { $exists = true; break; } } if (!$exists) { $missingAttributes[] = $missingAttr; } } } } } } } if (!$dryRun) { DB::connection($this->magento2Connection)->commit(); } return [ 'success' => true, 'message' => $dryRun ? 'Product migration dry run completed' : 'Product migration completed', 'added' => $addedCount, 'updated' => $updatedCount, 'errors' => $errorCount, 'log' => $this->migrationLog, 'missing_attributes' => $missingAttributes, 'dry_run' => $dryRun ]; } catch (Exception $e) { if (!$dryRun && isset($this->magento2Connection)) { DB::connection($this->magento2Connection)->rollBack(); } Log::error('Product migration error: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Product migration failed: ' . $e->getMessage(), 'added' => 0, 'updated' => 0, 'errors' => 0, 'log' => [], 'missing_attributes' => [] ]; } } /** * Get Magento 1 product attribute IDs */ protected function getMagento1ProductAttributeIds() { $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return []; } $attributes = ['sku', 'name', 'description', 'short_description', 'price', 'weight', 'status', 'visibility', 'tax_class_id']; $attributeIds = []; foreach ($attributes as $attrCode) { $attrId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', $attrCode) ->value('attribute_id'); if ($attrId) { $attributeIds[$attrCode] = $attrId; } } return $attributeIds; } /** * Get all Magento 1 product attributes */ protected function getAllMagento1ProductAttributes() { $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return collect([]); } return DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->select('attribute_id', 'attribute_code', 'backend_type', 'frontend_label') ->get(); } /** * Get all Magento 2 product attribute IDs */ protected function getAllMagento2ProductAttributeIds() { $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return []; } $attributes = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->select('attribute_id', 'attribute_code') ->get(); $attributeIds = []; foreach ($attributes as $attr) { $attributeIds[$attr->attribute_code] = $attr->attribute_id; } return $attributeIds; } /** * Get Magento 2 product attribute IDs */ protected function getMagento2ProductAttributeIds() { $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return []; } $attributes = ['sku', 'name', 'description', 'short_description', 'price', 'weight', 'status', 'visibility', 'tax_class_id']; $attributeIds = []; foreach ($attributes as $attrCode) { $attrId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', $attrCode) ->value('attribute_id'); if ($attrId) { $attributeIds[$attrCode] = $attrId; } } return $attributeIds; } /** * Migrate ALL data from ALL catalog_product_entity_* tables for a product * This ensures we migrate images, all store views, and all attributes */ protected function migrateAllProductEntityData($m1ProductId, $m2ProductId, $m1EntityTypeId, $m2EntityTypeId, $m1AttributeIds, $m2AttributeIds, $dryRun = false, $allM1Attributes = null) { $missingAttributes = []; // All possible backend types for EAV attributes $backendTypes = ['varchar', 'int', 'text', 'decimal', 'datetime']; // Get attribute mapping: M1 attribute_id => M2 attribute_id $attributeMapping = []; if ($allM1Attributes) { foreach ($allM1Attributes as $m1Attr) { $attrCode = $m1Attr->attribute_code; if (isset($m2AttributeIds[$attrCode])) { $attributeMapping[$m1Attr->attribute_id] = [ 'm2_attr_id' => $m2AttributeIds[$attrCode], 'backend_type' => $m1Attr->backend_type ?? 'varchar', 'attribute_code' => $attrCode ]; } } } // Migrate data from each backend type table foreach ($backendTypes as $backendType) { $m1Table = $this->magento1Prefix . 'catalog_product_entity_' . $backendType; $m2Table = $this->magento2Prefix . 'catalog_product_entity_' . $backendType; try { // Check if M1 table exists $m1Rows = DB::connection($this->magento1Connection) ->table($m1Table) ->where('entity_id', $m1ProductId) ->get(); if ($m1Rows->isEmpty()) { continue; // No data in this table for this product } // Check if M2 table exists try { DB::connection($this->magento2Connection) ->table($m2Table) ->limit(1) ->first(); } catch (Exception $e) { Log::warning("M2 table {$m2Table} doesn't exist: " . $e->getMessage()); continue; } if (!$dryRun) { // Migrate each row foreach ($m1Rows as $m1Row) { $m1AttrId = $m1Row->attribute_id; // Check if we have a mapping for this attribute if (!isset($attributeMapping[$m1AttrId])) { // Attribute doesn't exist in M2, log it if ($allM1Attributes) { $attr = $allM1Attributes->firstWhere('attribute_id', $m1AttrId); if ($attr) { $missingAttr = [ 'code' => $attr->attribute_code ?? 'unknown', 'label' => $attr->frontend_label ?? 'Unknown', 'type' => $attr->backend_type ?? $backendType ]; // Check if already in list $exists = false; foreach ($missingAttributes as $existingAttr) { if ($existingAttr['code'] === $missingAttr['code']) { $exists = true; break; } } if (!$exists) { $missingAttributes[] = $missingAttr; } } } continue; // Skip this attribute } $m2AttrId = $attributeMapping[$m1AttrId]['m2_attr_id']; $storeId = $m1Row->store_id ?? 0; // Check if this row already exists in M2 $exists = DB::connection($this->magento2Connection) ->table($m2Table) ->where('entity_id', $m2ProductId) ->where('attribute_id', $m2AttrId) ->where('store_id', $storeId) ->exists(); if ($exists) { // Update existing row DB::connection($this->magento2Connection) ->table($m2Table) ->where('entity_id', $m2ProductId) ->where('attribute_id', $m2AttrId) ->where('store_id', $storeId) ->update(['value' => $m1Row->value]); } else { // Insert new row DB::connection($this->magento2Connection) ->table($m2Table) ->insert([ 'attribute_id' => $m2AttrId, 'store_id' => $storeId, 'entity_id' => $m2ProductId, 'value' => $m1Row->value, ]); } } } } catch (Exception $e) { // Table doesn't exist in M1, skip it Log::debug("M1 table {$m1Table} doesn't exist or error: " . $e->getMessage()); continue; } } return $missingAttributes; } /** * Migrate product media gallery images from M1 to M2 * Populates catalog_product_entity_media_gallery, catalog_product_entity_media_gallery_value, * and catalog_product_entity_media_gallery_value_to_entity tables */ protected function migrateProductMediaGallery($m1ProductId, $m2ProductId, $m1EntityTypeId) { try { // Get media_gallery attribute ID from M1 $m1MediaGalleryAttrId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $m1EntityTypeId) ->where('attribute_code', 'media_gallery') ->value('attribute_id'); if (!$m1MediaGalleryAttrId) { // Media gallery attribute doesn't exist in M1, skip return; } // Get media_gallery attribute ID from M2 $m2EntityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$m2EntityTypeId) { return; } $m2MediaGalleryAttrId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $m2EntityTypeId) ->where('attribute_code', 'media_gallery') ->value('attribute_id'); if (!$m2MediaGalleryAttrId) { // Media gallery attribute doesn't exist in M2, skip return; } $images = []; // First, try to get images from M1 media gallery tables (if they exist) try { $m1MediaGalleryTable = $this->magento1Prefix . 'catalog_product_entity_media_gallery'; $m1MediaGalleryValueTable = $this->magento1Prefix . 'catalog_product_entity_media_gallery_value'; // Check if M1 media gallery tables exist $m1GalleryImages = DB::connection($this->magento1Connection) ->table($m1MediaGalleryTable) ->where('entity_id', $m1ProductId) ->get(); if ($m1GalleryImages->isNotEmpty()) { // M1 has media gallery tables, use them foreach ($m1GalleryImages as $m1Image) { $valueId = $m1Image->value_id; // Get value data $m1Value = DB::connection($this->magento1Connection) ->table($m1MediaGalleryValueTable) ->where('value_id', $valueId) ->where('store_id', 0) ->first(); $images[] = [ 'file' => $m1Image->value, 'label' => $m1Value->label ?? null, 'position' => $m1Value->position ?? 0, 'disabled' => $m1Value->disabled ?? 0, ]; } } } catch (Exception $e) { // M1 media gallery tables don't exist, fall back to varchar table Log::debug("M1 media gallery tables not found, using varchar table: " . $e->getMessage()); } // If no images from gallery tables, try varchar table (serialized data) if (empty($images)) { $m1MediaGalleryData = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity_varchar') ->where('entity_id', $m1ProductId) ->where('attribute_id', $m1MediaGalleryAttrId) ->where('store_id', 0) ->value('value'); if (empty($m1MediaGalleryData)) { // No media gallery data in M1, skip return; } // Parse the media gallery data (Magento 1 stores it as serialized PHP) if (is_string($m1MediaGalleryData)) { // Try to unserialize (Magento 1 format) $unserialized = @unserialize($m1MediaGalleryData); if ($unserialized !== false && is_array($unserialized)) { $parsedImages = $unserialized; } else { // Try JSON decode (some versions might use JSON) $jsonDecoded = @json_decode($m1MediaGalleryData, true); if (is_array($jsonDecoded)) { $parsedImages = $jsonDecoded; } else { // If it's a simple string, treat it as a single image path if (!empty(trim($m1MediaGalleryData))) { $parsedImages = [['file' => trim($m1MediaGalleryData)]]; } else { $parsedImages = []; } } } // Convert parsed images to our format if (!empty($parsedImages)) { if (isset($parsedImages['images']) && is_array($parsedImages['images'])) { $parsedImages = $parsedImages['images']; } foreach ($parsedImages as $img) { if (is_string($img)) { $images[] = ['file' => $img]; } elseif (is_array($img)) { $images[] = [ 'file' => $img['file'] ?? $img['value'] ?? null, 'label' => $img['label'] ?? $img['label_default'] ?? null, 'position' => isset($img['position']) ? (int)$img['position'] : 0, 'disabled' => isset($img['disabled']) ? (int)$img['disabled'] : 0, ]; } } } } } if (empty($images)) { return; } // Process each image foreach ($images as $imageData) { // Handle different data structures $imageFile = null; $label = null; $position = 0; $disabled = 0; if (is_string($imageData)) { // Simple string path $imageFile = $imageData; } elseif (is_array($imageData)) { // Array structure $imageFile = $imageData['file'] ?? $imageData['value'] ?? null; $label = $imageData['label'] ?? $imageData['label_default'] ?? null; $position = isset($imageData['position']) ? (int)$imageData['position'] : 0; $disabled = isset($imageData['disabled']) ? (int)$imageData['disabled'] : 0; } if (empty($imageFile)) { continue; } // Clean up the image file path (remove leading slashes, etc.) $imageFile = ltrim($imageFile, '/'); if (empty($imageFile)) { continue; } // Check if this image already exists in M2 for this product $existingValueId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_media_gallery') ->where('attribute_id', $m2MediaGalleryAttrId) ->where('value', $imageFile) ->value('value_id'); if (!$existingValueId) { // Insert into catalog_product_entity_media_gallery $valueId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_media_gallery') ->insertGetId([ 'attribute_id' => $m2MediaGalleryAttrId, 'value' => $imageFile, ]); } else { $valueId = $existingValueId; } // Check if value_to_entity link already exists $linkExists = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_media_gallery_value_to_entity') ->where('value_id', $valueId) ->where('entity_id', $m2ProductId) ->exists(); if (!$linkExists) { // Insert into catalog_product_entity_media_gallery_value_to_entity DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_media_gallery_value_to_entity') ->insert([ 'value_id' => $valueId, 'entity_id' => $m2ProductId, ]); } // Insert/update catalog_product_entity_media_gallery_value for default store (store_id = 0) $valueExists = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_media_gallery_value') ->where('value_id', $valueId) ->where('store_id', 0) ->exists(); if (!$valueExists) { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_media_gallery_value') ->insert([ 'value_id' => $valueId, 'store_id' => 0, 'label' => $label, 'position' => $position, 'disabled' => $disabled, ]); } else { // Update existing value DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_media_gallery_value') ->where('value_id', $valueId) ->where('store_id', 0) ->update([ 'label' => $label, 'position' => $position, 'disabled' => $disabled, ]); } } } catch (Exception $e) { // Log error but don't fail the entire migration Log::warning("Error migrating media gallery for product M1 ID {$m1ProductId} -> M2 ID {$m2ProductId}: " . $e->getMessage()); } } /** * Migrate product attributes from M1 to M2 */ protected function migrateProductAttributes($m1ProductId, $m2ProductId, $m1EntityTypeId, $m2EntityTypeId, $m1AttributeIds, $m2AttributeIds, $isNew, $dryRun = false, $allM1Attributes = null) { $missingAttributes = []; // Get attribute backend types for all M1 attributes $m1AttributeTypes = []; if (!empty($m1AttributeIds)) { $m1AttributeTypes = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $m1EntityTypeId) ->whereIn('attribute_id', array_values($m1AttributeIds)) ->pluck('backend_type', 'attribute_id') ->toArray(); } $m2AttributeTypes = []; if (!empty($m2AttributeIds)) { $m2AttributeTypes = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $m2EntityTypeId) ->whereIn('attribute_id', array_values($m2AttributeIds)) ->pluck('backend_type', 'attribute_id') ->toArray(); } // Migrate each attribute foreach ($m1AttributeIds as $attrCode => $m1AttrId) { // Check if attribute exists in M2 if (!isset($m2AttributeIds[$attrCode])) { // Attribute doesn't exist in M2 if ($allM1Attributes) { $attr = $allM1Attributes->firstWhere('attribute_id', $m1AttrId); if ($attr) { $missingAttributes[] = [ 'code' => $attrCode, 'label' => $attr->frontend_label ?? $attrCode, 'type' => $attr->backend_type ?? 'varchar' ]; } } continue; } $m2AttrId = $m2AttributeIds[$attrCode]; $backendType = $m1AttributeTypes[$m1AttrId] ?? 'varchar'; // Handle static attributes (stored in main entity table) if ($backendType === 'static') { // Static attributes are columns in catalog_product_entity table // Skip for now as they're usually handled during entity creation continue; } // Get M1 attribute value $m1Table = $this->magento1Prefix . 'catalog_product_entity_' . $backendType; try { $m1Value = DB::connection($this->magento1Connection) ->table($m1Table) ->where('entity_id', $m1ProductId) ->where('attribute_id', $m1AttrId) ->where('store_id', 0) ->value('value'); } catch (Exception $e) { // Table doesn't exist - this means the attribute table is missing // Track this as a missing attribute/table if ($allM1Attributes) { $attr = $allM1Attributes->firstWhere('attribute_id', $m1AttrId); if ($attr) { $missingAttr = [ 'code' => $attrCode, 'label' => $attr->frontend_label ?? $attrCode, 'type' => $backendType ]; // Check if already in list $exists = false; foreach ($missingAttributes as $existingAttr) { if ($existingAttr['code'] === $missingAttr['code']) { $exists = true; break; } } if (!$exists) { $missingAttributes[] = $missingAttr; } } } Log::warning("Table {$m1Table} doesn't exist for attribute {$attrCode}: " . $e->getMessage()); continue; } if ($m1Value === null) { continue; // No value in M1 } if (!$dryRun) { // Insert or update in M2 $m2Table = $this->magento2Prefix . 'catalog_product_entity_' . $backendType; $exists = DB::connection($this->magento2Connection) ->table($m2Table) ->where('entity_id', $m2ProductId) ->where('attribute_id', $m2AttrId) ->where('store_id', 0) ->exists(); if ($exists) { DB::connection($this->magento2Connection) ->table($m2Table) ->where('entity_id', $m2ProductId) ->where('attribute_id', $m2AttrId) ->where('store_id', 0) ->update(['value' => $m1Value]); } else { DB::connection($this->magento2Connection) ->table($m2Table) ->insert([ 'attribute_id' => $m2AttrId, 'store_id' => 0, 'entity_id' => $m2ProductId, 'value' => $m1Value, ]); } } } return $missingAttributes; } /** * Delete a single product from Magento 2 */ public function deleteM2Product($productId) { try { // Check if product exists $product = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->where('entity_id', $productId) ->first(); if (!$product) { return [ 'success' => false, 'message' => 'Product not found in Magento 2' ]; } DB::connection($this->magento2Connection)->beginTransaction(); try { // Delete product attributes $attributeTables = ['varchar', 'int', 'text', 'decimal', 'datetime']; foreach ($attributeTables as $tableType) { $table = $this->magento2Prefix . 'catalog_product_entity_' . $tableType; try { DB::connection($this->magento2Connection) ->table($table) ->where('entity_id', $productId) ->delete(); } catch (Exception $e) { // Table might not exist, continue Log::warning("Table {$table} might not exist: " . $e->getMessage()); } } // Delete category associations try { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->where('product_id', $productId) ->delete(); } catch (Exception $e) { Log::warning("Table {$this->magento2Prefix}catalog_category_product might not exist: " . $e->getMessage()); } // Delete stock items if they exist try { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'cataloginventory_stock_item') ->where('product_id', $productId) ->delete(); } catch (Exception $e) { // Table might not exist, continue } // Delete the product entity DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->where('entity_id', $productId) ->delete(); DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => "Product ID {$productId} deleted successfully" ]; } catch (Exception $e) { DB::connection($this->magento2Connection)->rollBack(); throw $e; } } catch (Exception $e) { Log::error('Error deleting M2 product: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Failed to delete product: ' . $e->getMessage() ]; } } /** * Sync product category assignments from M1 to M2 * Updates M2 products to be in the same categories as their M1 counterparts */ public function syncProductCategories() { try { $this->migrationLog = []; $updatedCount = 0; $skippedCount = 0; $errorCount = 0; // Get category mapping $categoryMapping = $this->getCategoryMapping(); if (empty($categoryMapping)) { return [ 'success' => false, 'message' => 'No category mapping found. Please migrate categories first.', 'updated' => 0, 'skipped' => 0, 'errors' => 0, 'log' => [] ]; } // Get all M1 products with their category associations $m1Products = $this->getMagento1Products(); $m2Products = $this->getMagento2Products(); // Create lookup maps for M2 products $m2ProductBySku = []; $m2ProductById = []; foreach ($m2Products as $m2Product) { $sku = $m2Product->sku ?? 'N/A'; if ($sku !== 'N/A' && !empty($sku)) { $m2ProductBySku[$sku] = $m2Product->entity_id; } $m2ProductById[$m2Product->entity_id] = $m2Product->entity_id; } DB::connection($this->magento2Connection)->beginTransaction(); foreach ($m1Products as $m1Product) { try { // Find corresponding M2 product $m2ProductId = null; $m1Sku = $m1Product->sku ?? 'N/A'; if ($m1Sku !== 'N/A' && !empty($m1Sku)) { // Match by SKU if (isset($m2ProductBySku[$m1Sku])) { $m2ProductId = $m2ProductBySku[$m1Sku]; } } else { // Match by product ID if (isset($m2ProductById[$m1Product->entity_id])) { $m2ProductId = $m1Product->entity_id; } } if (!$m2ProductId) { $skippedCount++; $this->migrationLog[] = "SKIPPED: M1 Product ID {$m1Product->entity_id} (SKU: {$m1Sku}) - not found in M2"; continue; } // Ensure product is enabled and visible (required for products to show in categories after reindex) $this->ensureProductIsEnabledAndVisible($m2ProductId); // Get M1 product categories $m1Categories = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_category_product') ->where('product_id', $m1Product->entity_id) ->get(); if ($m1Categories->isEmpty()) { $skippedCount++; $this->migrationLog[] = "SKIPPED: M1 Product ID {$m1Product->entity_id} (SKU: {$m1Sku}) - no categories in M1"; continue; } // Get current M2 product categories $m2CurrentCategories = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->where('product_id', $m2ProductId) ->pluck('category_id') ->toArray(); // Build expected M2 categories from M1 categories using mapping $expectedM2Categories = []; $categoriesToAdd = []; $unmappedCategories = []; foreach ($m1Categories as $m1Category) { if (isset($categoryMapping[$m1Category->category_id])) { $m2CategoryId = $categoryMapping[$m1Category->category_id]; $expectedM2Categories[] = $m2CategoryId; // Check if this category association needs to be added if (!in_array($m2CategoryId, $m2CurrentCategories)) { $categoriesToAdd[] = [ 'category_id' => $m2CategoryId, 'product_id' => $m2ProductId, 'position' => $m1Category->position ?? 0, ]; } } else { // Category not in mapping - try to find it by name $m1CategoryName = $this->getCategoryNameById($m1Category->category_id, 'm1'); if ($m1CategoryName) { $m2CategoryId = $this->findCategoryByName($m1CategoryName, 'm2'); if ($m2CategoryId) { // Found by name, add to mapping for future use $categoryMapping[$m1Category->category_id] = $m2CategoryId; $expectedM2Categories[] = $m2CategoryId; if (!in_array($m2CategoryId, $m2CurrentCategories)) { $categoriesToAdd[] = [ 'category_id' => $m2CategoryId, 'product_id' => $m2ProductId, 'position' => $m1Category->position ?? 0, ]; } $this->migrationLog[] = "FOUND BY NAME: M1 Category ID {$m1Category->category_id} ({$m1CategoryName}) -> M2 Category ID {$m2CategoryId}"; } else { $unmappedCategories[] = $m1CategoryName ?: "ID {$m1Category->category_id}"; } } else { $unmappedCategories[] = "ID {$m1Category->category_id}"; } } } if (!empty($unmappedCategories)) { $this->migrationLog[] = "WARNING: M2 Product ID {$m2ProductId} (SKU: {$m1Sku}) - M1 categories not found in M2: " . implode(', ', $unmappedCategories); } // Remove categories that are in M2 but not in M1 (after mapping) $categoriesToRemove = array_diff($m2CurrentCategories, $expectedM2Categories); // Add missing category associations foreach ($categoriesToAdd as $categoryData) { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->insert($categoryData); } // Remove categories that shouldn't be there if (!empty($categoriesToRemove)) { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->where('product_id', $m2ProductId) ->whereIn('category_id', $categoriesToRemove) ->delete(); } if (!empty($categoriesToAdd) || !empty($categoriesToRemove)) { $updatedCount++; $addedCount = count($categoriesToAdd); $removedCount = count($categoriesToRemove); $this->migrationLog[] = "UPDATED: M2 Product ID {$m2ProductId} (SKU: {$m1Sku}) - Added {$addedCount} category(ies), Removed {$removedCount} category(ies)"; } else { $skippedCount++; $this->migrationLog[] = "SKIPPED: M2 Product ID {$m2ProductId} (SKU: {$m1Sku}) - categories already match"; } } catch (Exception $e) { $errorCount++; $m1Sku = $m1Product->sku ?? 'N/A'; $this->migrationLog[] = "ERROR: Failed to sync categories for M1 Product ID {$m1Product->entity_id} (SKU: {$m1Sku}): " . $e->getMessage(); Log::error("Error syncing product categories for M1 Product ID {$m1Product->entity_id}: " . $e->getMessage()); } } DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => "Synced product categories. Updated: {$updatedCount}, Skipped: {$skippedCount}, Errors: {$errorCount}", 'updated' => $updatedCount, 'skipped' => $skippedCount, 'errors' => $errorCount, 'log' => $this->migrationLog ]; } catch (Exception $e) { if (isset($this->magento2Connection)) { DB::connection($this->magento2Connection)->rollBack(); } Log::error('Error syncing product categories: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Failed to sync product categories: ' . $e->getMessage(), 'updated' => 0, 'skipped' => 0, 'errors' => 0, 'log' => $this->migrationLog ]; } } /** * Delete all products in Magento 2 that have entity_id greater than max M1 product ID */ public function deleteM2ProductsAboveM1Max() { try { // Get max product ID from M1 $maxM1ProductId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity') ->max('entity_id'); if (!$maxM1ProductId) { return [ 'success' => false, 'message' => 'No products found in Magento 1', 'deleted' => 0 ]; } // Get products to delete from M2 $productsToDelete = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->where('entity_id', '>', $maxM1ProductId) ->pluck('entity_id') ->toArray(); if (empty($productsToDelete)) { return [ 'success' => true, 'message' => 'No products found to delete', 'deleted' => 0, 'max_m1_id' => $maxM1ProductId ]; } $deletedCount = 0; $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); DB::connection($this->magento2Connection)->beginTransaction(); foreach ($productsToDelete as $productId) { try { // Delete product attributes $attributeTables = ['varchar', 'int', 'text', 'decimal', 'datetime']; foreach ($attributeTables as $tableType) { $table = $this->magento2Prefix . 'catalog_product_entity_' . $tableType; try { DB::connection($this->magento2Connection) ->table($table) ->where('entity_id', $productId) ->delete(); } catch (Exception $e) { // Table might not exist, continue Log::warning("Table {$table} might not exist: " . $e->getMessage()); } } // Delete category associations try { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->where('product_id', $productId) ->delete(); } catch (Exception $e) { Log::warning("Table {$this->magento2Prefix}catalog_category_product might not exist: " . $e->getMessage()); } // Delete stock items if they exist try { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'cataloginventory_stock_item') ->where('product_id', $productId) ->delete(); } catch (Exception $e) { // Table might not exist, continue } // Delete the product entity DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->where('entity_id', $productId) ->delete(); $deletedCount++; } catch (Exception $e) { Log::error("Error deleting product {$productId}: " . $e->getMessage()); } } DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => "Deleted {$deletedCount} product(s) with ID > {$maxM1ProductId}", 'deleted' => $deletedCount, 'max_m1_id' => $maxM1ProductId ]; } catch (Exception $e) { if (isset($this->magento2Connection)) { DB::connection($this->magento2Connection)->rollBack(); } Log::error('Error deleting M2 products above M1 max: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Failed to delete products: ' . $e->getMessage(), 'deleted' => 0 ]; } } /** * Migrate product category associations */ protected function migrateProductCategories($m1ProductId, $m2ProductId, $categoryMapping) { // Get M1 product categories $m1Categories = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_category_product') ->where('product_id', $m1ProductId) ->get(); foreach ($m1Categories as $m1Category) { // Find corresponding M2 category if (isset($categoryMapping[$m1Category->category_id])) { $m2CategoryId = $categoryMapping[$m1Category->category_id]; // Check if association already exists $exists = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->where('category_id', $m2CategoryId) ->where('product_id', $m2ProductId) ->exists(); if (!$exists) { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->insert([ 'category_id' => $m2CategoryId, 'product_id' => $m2ProductId, 'position' => $m1Category->position ?? 0, ]); } } } } /** * Ensure product is enabled and visible (required for products to appear in categories after reindex) */ protected function ensureProductIsEnabledAndVisible($productId) { try { $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return; } // Get attribute IDs $statusAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'status') ->value('attribute_id'); $visibilityAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'visibility') ->value('attribute_id'); // Set status to 1 (enabled) if not already set if ($statusAttributeId) { $statusTable = $this->magento2Prefix . 'catalog_product_entity_int'; $hasStatus = DB::connection($this->magento2Connection) ->table($statusTable) ->where('entity_id', $productId) ->where('attribute_id', $statusAttributeId) ->where('store_id', 0) ->exists(); if (!$hasStatus) { DB::connection($this->magento2Connection) ->table($statusTable) ->insert([ 'attribute_id' => $statusAttributeId, 'store_id' => 0, 'entity_id' => $productId, 'value' => 1, // Enabled ]); } else { // Update to enabled if it's disabled DB::connection($this->magento2Connection) ->table($statusTable) ->where('entity_id', $productId) ->where('attribute_id', $statusAttributeId) ->where('store_id', 0) ->update(['value' => 1]); } } // Set visibility to 4 (Catalog, Search) if not already set if ($visibilityAttributeId) { $visibilityTable = $this->magento2Prefix . 'catalog_product_entity_int'; $hasVisibility = DB::connection($this->magento2Connection) ->table($visibilityTable) ->where('entity_id', $productId) ->where('attribute_id', $visibilityAttributeId) ->where('store_id', 0) ->exists(); if (!$hasVisibility) { DB::connection($this->magento2Connection) ->table($visibilityTable) ->insert([ 'attribute_id' => $visibilityAttributeId, 'store_id' => 0, 'entity_id' => $productId, 'value' => 4, // Catalog, Search ]); } else { // Update to Catalog, Search if it's Not Visible or Search Only $currentVisibility = DB::connection($this->magento2Connection) ->table($visibilityTable) ->where('entity_id', $productId) ->where('attribute_id', $visibilityAttributeId) ->where('store_id', 0) ->value('value'); // Only update if visibility is 1 (Not Visible) or 2 (Catalog) // 3 = Search, 4 = Catalog, Search (both) if ($currentVisibility == 1 || $currentVisibility == 2) { DB::connection($this->magento2Connection) ->table($visibilityTable) ->where('entity_id', $productId) ->where('attribute_id', $visibilityAttributeId) ->where('store_id', 0) ->update(['value' => 4]); } } } } catch (Exception $e) { Log::warning("Error ensuring product {$productId} is enabled and visible: " . $e->getMessage()); } } /** * Get Magento 1 category tree with products */ public function getMagento1CategoryTreeWithProducts() { try { $categories = $this->getMagento1Categories(); $categoryMap = []; // Build category map foreach ($categories as $category) { $categoryMap[$category->entity_id] = [ 'id' => $category->entity_id, 'name' => $category->name, 'parent_id' => $category->parent_id, 'is_active' => $category->is_active ?? true, 'path' => $category->path ?? '', 'children' => [], 'products' => [] ]; } // Get all products in categories $categoryProducts = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_category_product') ->select('category_id', 'product_id', 'position') ->orderBy('category_id') ->orderBy('position') ->get(); // Get product details $productIds = $categoryProducts->pluck('product_id')->unique()->toArray(); $products = []; if (!empty($productIds)) { // Check if SKU column exists in entity table $hasSkuColumn = false; try { $testQuery = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity') ->select('sku') ->limit(1) ->first(); $hasSkuColumn = true; } catch (Exception $e) { $hasSkuColumn = false; } $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if ($entityTypeId) { // Get SKU attribute ID (for EAV storage) $skuAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'sku') ->value('attribute_id'); // Get name attribute ID $nameAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); // Get SKUs - from entity table or EAV $skus = []; if ($hasSkuColumn) { // SKU is stored in entity table $skuValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity') ->whereIn('entity_id', $productIds) ->pluck('sku', 'entity_id') ->toArray(); $skus = $skuValues; } else if ($skuAttributeId) { // SKU is stored in EAV table $skuValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $skuAttributeId) ->where('store_id', 0) ->whereIn('entity_id', $productIds) ->pluck('value', 'entity_id') ->toArray(); $skus = $skuValues; } // Get names $names = []; if ($nameAttributeId) { $nameValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $nameAttributeId) ->where('store_id', 0) ->whereIn('entity_id', $productIds) ->pluck('value', 'entity_id') ->toArray(); $names = $nameValues; } // Build products array foreach ($productIds as $productId) { $sku = $skus[$productId] ?? 'N/A'; $products[$productId] = [ 'id' => $productId, 'sku' => !empty($sku) ? $sku : 'N/A', 'name' => $names[$productId] ?? 'Unnamed Product' ]; } } } // Assign products to categories foreach ($categoryProducts as $cp) { if (isset($categoryMap[$cp->category_id]) && isset($products[$cp->product_id])) { $categoryMap[$cp->category_id]['products'][] = [ 'id' => $products[$cp->product_id]['id'], 'sku' => $products[$cp->product_id]['sku'], 'name' => $products[$cp->product_id]['name'], 'position' => $cp->position ?? 0 ]; } } // Build tree structure $tree = []; foreach ($categoryMap as $categoryId => $category) { if ($category['parent_id'] == 0 || $category['parent_id'] == 1) { // Root category $tree[] = $this->buildCategoryTreeWithProducts($category, $categoryMap); } } return $tree; } catch (Exception $e) { Log::error('Error fetching M1 category tree with products: ' . $e->getMessage()); return []; } } /** * Get Magento 2 category tree with products */ public function getMagento2CategoryTreeWithProducts() { try { $categories = $this->getMagento2Categories(); $categoryMap = []; // Build category map foreach ($categories as $category) { $categoryMap[$category->entity_id] = [ 'id' => $category->entity_id, 'name' => $category->name, 'parent_id' => $category->parent_id, 'is_active' => $category->is_active ?? true, 'path' => $category->path ?? '', 'children' => [], 'products' => [] ]; } // Get all products in categories $categoryProducts = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->select('category_id', 'product_id', 'position') ->orderBy('category_id') ->orderBy('position') ->get(); // Get product details $productIds = $categoryProducts->pluck('product_id')->unique()->toArray(); $products = []; if (!empty($productIds)) { // Check if SKU column exists in entity table $hasSkuColumn = false; try { $testQuery = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->select('sku') ->limit(1) ->first(); $hasSkuColumn = true; } catch (Exception $e) { $hasSkuColumn = false; } $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if ($entityTypeId) { // Get SKU attribute ID (for EAV storage) $skuAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'sku') ->value('attribute_id'); // Get name attribute ID $nameAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); // Get SKUs - from entity table or EAV $skus = []; if ($hasSkuColumn) { // SKU is stored in entity table $skuValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity') ->whereIn('entity_id', $productIds) ->pluck('sku', 'entity_id') ->toArray(); $skus = $skuValues; } else if ($skuAttributeId) { // SKU is stored in EAV table $skuValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $skuAttributeId) ->where('store_id', 0) ->whereIn('entity_id', $productIds) ->pluck('value', 'entity_id') ->toArray(); $skus = $skuValues; } // Get names $names = []; if ($nameAttributeId) { $nameValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_varchar') ->where('attribute_id', $nameAttributeId) ->where('store_id', 0) ->whereIn('entity_id', $productIds) ->pluck('value', 'entity_id') ->toArray(); $names = $nameValues; } // Build products array foreach ($productIds as $productId) { $sku = $skus[$productId] ?? 'N/A'; $products[$productId] = [ 'id' => $productId, 'sku' => !empty($sku) ? $sku : 'N/A', 'name' => $names[$productId] ?? 'Unnamed Product' ]; } } } // Assign products to categories foreach ($categoryProducts as $cp) { if (isset($categoryMap[$cp->category_id]) && isset($products[$cp->product_id])) { $categoryMap[$cp->category_id]['products'][] = [ 'id' => $products[$cp->product_id]['id'], 'sku' => $products[$cp->product_id]['sku'], 'name' => $products[$cp->product_id]['name'], 'position' => $cp->position ?? 0 ]; } } // Build tree structure $tree = []; foreach ($categoryMap as $categoryId => $category) { if ($category['parent_id'] == 0 || $category['parent_id'] == 1) { // Root category $tree[] = $this->buildCategoryTreeWithProducts($category, $categoryMap); } } return $tree; } catch (Exception $e) { Log::error('Error fetching M2 category tree with products: ' . $e->getMessage()); return []; } } /** * Build category tree structure with products recursively */ protected function buildCategoryTreeWithProducts($category, $categoryMap) { $node = [ 'id' => $category['id'], 'name' => $category['name'], 'is_active' => $category['is_active'], 'products' => $category['products'], 'children' => [] ]; // Find children foreach ($categoryMap as $catId => $cat) { if ($cat['parent_id'] == $category['id']) { $node['children'][] = $this->buildCategoryTreeWithProducts($cat, $categoryMap); } } return $node; } /** * Get category name by ID */ protected function getCategoryNameById($categoryId, $source = 'm1') { try { $connection = $source === 'm1' ? $this->magento1Connection : $this->magento2Connection; $prefix = $source === 'm1' ? $this->magento1Prefix : $this->magento2Prefix; $entityTypeId = DB::connection($connection) ->table($prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return null; } $nameAttributeId = DB::connection($connection) ->table($prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); if (!$nameAttributeId) { return null; } $name = DB::connection($connection) ->table($prefix . 'catalog_category_entity_varchar') ->where('entity_id', $categoryId) ->where('attribute_id', $nameAttributeId) ->where('store_id', 0) ->value('value'); return $name; } catch (Exception $e) { Log::warning("Error getting category name for ID {$categoryId}: " . $e->getMessage()); return null; } } /** * Find category by name (case-insensitive, matches any parent) */ protected function findCategoryByName($categoryName, $source = 'm2') { try { $connection = $source === 'm1' ? $this->magento1Connection : $this->magento2Connection; $prefix = $source === 'm1' ? $this->magento1Prefix : $this->magento2Prefix; $entityTypeId = DB::connection($connection) ->table($prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if (!$entityTypeId) { return null; } $nameAttributeId = DB::connection($connection) ->table($prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'name') ->value('attribute_id'); if (!$nameAttributeId) { return null; } // Find category by name (case-insensitive) $category = DB::connection($connection) ->table($prefix . 'catalog_category_entity_varchar') ->where('attribute_id', $nameAttributeId) ->where('store_id', 0) ->whereRaw('LOWER(value) = ?', [strtolower(trim($categoryName))]) ->first(); return $category ? $category->entity_id : null; } catch (Exception $e) { Log::warning("Error finding category by name '{$categoryName}': " . $e->getMessage()); return null; } } /** * Get category mapping from previous migrations */ protected function getCategoryMapping() { // Try to get mapping from category_mapping table or build it from existing categories // Build it by matching category names (case-insensitive, ignoring parent differences) $mapping = []; try { $m1Categories = $this->getMagento1Categories(); $m2Categories = $this->getMagento2Categories(); // Create a map of M2 categories by normalized name (case-insensitive) // Use the first match if multiple categories have the same name $m2CategoryMap = []; foreach ($m2Categories as $m2Cat) { $normalizedName = strtolower(trim($m2Cat->name ?? '')); if (!empty($normalizedName) && !isset($m2CategoryMap[$normalizedName])) { $m2CategoryMap[$normalizedName] = $m2Cat->entity_id; } } // Match M1 categories to M2 by normalized name foreach ($m1Categories as $m1Cat) { $normalizedName = strtolower(trim($m1Cat->name ?? '')); if (!empty($normalizedName) && isset($m2CategoryMap[$normalizedName])) { $mapping[$m1Cat->entity_id] = $m2CategoryMap[$normalizedName]; } } } catch (Exception $e) { Log::warning('Error building category mapping: ' . $e->getMessage()); } return $mapping; } /** * Recursively delete all children of a category */ protected function deleteCategoryChildren($categoryId, $connection, $prefix) { // Get all children $children = DB::connection($connection) ->table($prefix . 'catalog_category_entity') ->where('parent_id', $categoryId) ->get(); // Recursively delete each child foreach ($children as $child) { $this->deleteCategoryChildren($child->entity_id, $connection, $prefix); $this->deleteCategoryData($child->entity_id, $connection, $prefix); } } /** * Fix category products - ensure products are added to categories if missing from catalog_category_product table * Only looks at M2 products and their category_ids attribute */ public function fixCategoryProducts() { try { $this->migrationLog = []; $addedCount = 0; $skippedCount = 0; $errorCount = 0; // Get entity type ID for catalog_product $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_product') ->value('entity_type_id'); if (!$entityTypeId) { return [ 'success' => false, 'message' => 'Could not find catalog_product entity type in Magento 2', 'added' => 0, 'skipped' => 0, 'errors' => 0, 'log' => [] ]; } // Get category_ids attribute ID $categoryIdsAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'category_ids') ->value('attribute_id'); // Get all M2 products $m2Products = $this->getMagento2Products(); // Get all products that are already in catalog_category_product table $productsInCategories = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->distinct() ->pluck('product_id') ->toArray(); // Get all valid M2 category IDs $validCategoryIds = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_entity') ->pluck('entity_id') ->toArray(); DB::connection($this->magento2Connection)->beginTransaction(); foreach ($m2Products as $m2Product) { try { $m2ProductId = $m2Product->entity_id; $m2Sku = $m2Product->sku ?? 'N/A'; // Check if product is already in catalog_category_product table if (in_array($m2ProductId, $productsInCategories)) { $skippedCount++; continue; } // Get category_ids from product attribute $categoryIdsString = null; if ($categoryIdsAttributeId) { // Try to get from varchar table first $categoryIdsValue = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_varchar') ->where('entity_id', $m2ProductId) ->where('attribute_id', $categoryIdsAttributeId) ->where('store_id', 0) ->value('value'); if ($categoryIdsValue) { $categoryIdsString = $categoryIdsValue; } else { // Try text table $categoryIdsValue = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_entity_text') ->where('entity_id', $m2ProductId) ->where('attribute_id', $categoryIdsAttributeId) ->where('store_id', 0) ->value('value'); if ($categoryIdsValue) { $categoryIdsString = $categoryIdsValue; } } } if (empty($categoryIdsString)) { $skippedCount++; $this->migrationLog[] = "SKIPPED: M2 Product ID {$m2ProductId} (SKU: {$m2Sku}) - no category_ids attribute found"; continue; } // Parse category IDs (comma-separated string) $categoryIds = array_filter( array_map('trim', explode(',', $categoryIdsString)), function($id) use ($validCategoryIds) { return !empty($id) && is_numeric($id) && in_array((int)$id, $validCategoryIds); } ); if (empty($categoryIds)) { $skippedCount++; $this->migrationLog[] = "SKIPPED: M2 Product ID {$m2ProductId} (SKU: {$m2Sku}) - no valid category IDs found in attribute"; continue; } // Ensure product is enabled and visible $this->ensureProductIsEnabledAndVisible($m2ProductId); // Add product to categories $categoriesAdded = 0; $invalidCategories = []; foreach ($categoryIds as $categoryId) { $categoryId = (int)$categoryId; // Check if association already exists $exists = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->where('category_id', $categoryId) ->where('product_id', $m2ProductId) ->exists(); if (!$exists) { DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_category_product') ->insert([ 'category_id' => $categoryId, 'product_id' => $m2ProductId, 'position' => 0, // Default position ]); $categoriesAdded++; } } if ($categoriesAdded > 0) { $addedCount++; $invalidMsg = !empty($invalidCategories) ? " (invalid categories: " . implode(', ', $invalidCategories) . ")" : ""; $this->migrationLog[] = "ADDED: M2 Product ID {$m2ProductId} (SKU: {$m2Sku}) - added to {$categoriesAdded} category(ies){$invalidMsg}"; } else { $skippedCount++; $invalidMsg = !empty($invalidCategories) ? " (invalid categories: " . implode(', ', $invalidCategories) . ")" : ""; $this->migrationLog[] = "SKIPPED: M2 Product ID {$m2ProductId} (SKU: {$m2Sku}) - categories already exist or invalid{$invalidMsg}"; } } catch (Exception $e) { $errorCount++; $m2Sku = $m2Product->sku ?? 'N/A'; $this->migrationLog[] = "ERROR: Failed to fix categories for M2 Product ID {$m2ProductId} (SKU: {$m2Sku}): " . $e->getMessage(); Log::error("Error fixing category products for M2 Product ID {$m2ProductId}: " . $e->getMessage()); } } DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => "Fixed category products. Added: {$addedCount}, Skipped: {$skippedCount}, Errors: {$errorCount}", 'added' => $addedCount, 'skipped' => $skippedCount, 'errors' => $errorCount, 'log' => $this->migrationLog ]; } catch (Exception $e) { if (isset($this->magento2Connection)) { DB::connection($this->magento2Connection)->rollBack(); } Log::error('Error fixing category products: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Failed to fix category products: ' . $e->getMessage(), 'added' => 0, 'skipped' => 0, 'errors' => 0, 'log' => [] ]; } } /** * Delete category data (attributes and entity) */ protected function deleteCategoryData($categoryId, $connection, $prefix) { // Get entity type ID $entityTypeId = DB::connection($connection) ->table($prefix . 'eav_entity_type') ->where('entity_type_code', 'catalog_category') ->value('entity_type_id'); if ($entityTypeId) { // Delete attribute values from all attribute tables $attributeTables = ['varchar', 'int', 'text', 'decimal', 'datetime']; foreach ($attributeTables as $tableType) { $table = $prefix . 'catalog_category_entity_' . $tableType; try { DB::connection($connection) ->table($table) ->where('entity_id', $categoryId) ->delete(); } catch (Exception $e) { // Table might not exist, continue Log::warning("Table {$table} might not exist or has no data: " . $e->getMessage()); } } // Delete category product associations try { DB::connection($connection) ->table($prefix . 'catalog_category_product') ->where('category_id', $categoryId) ->delete(); } catch (Exception $e) { Log::warning("Table {$prefix}catalog_category_product might not exist: " . $e->getMessage()); } } // Delete the category entity DB::connection($connection) ->table($prefix . 'catalog_category_entity') ->where('entity_id', $categoryId) ->delete(); } /** * Get all customers from Magento 1 */ public function getMagento1Customers() { try { // Get entity type ID for customer $entityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'customer') ->value('entity_type_id'); if (!$entityTypeId) { Log::warning('Magento 1 customer entity type not found'); return collect([]); } Log::info("Magento 1 customer entity_type_id: {$entityTypeId}"); // Get base customer data - email is stored directly in customer_entity table $customers = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'customer_entity') ->select('entity_id', 'email', 'website_id', 'group_id', 'created_at', 'updated_at') ->orderBy('entity_id') ->get(); Log::info("Found " . $customers->count() . " customers in Magento 1 customer_entity table"); // Get firstname and lastname attribute IDs (these are in EAV) $firstnameAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'firstname') ->value('attribute_id'); $lastnameAttributeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'lastname') ->value('attribute_id'); // Get firstname and lastname from EAV $firstnames = []; $lastnames = []; if ($firstnameAttributeId) { $firstnameValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'customer_entity_varchar') ->where('attribute_id', $firstnameAttributeId) ->pluck('value', 'entity_id') ->toArray(); $firstnames = $firstnameValues; } if ($lastnameAttributeId) { $lastnameValues = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'customer_entity_varchar') ->where('attribute_id', $lastnameAttributeId) ->pluck('value', 'entity_id') ->toArray(); $lastnames = $lastnameValues; } // Combine data - email is already in customer object from the query foreach ($customers as $customer) { // Email is already in customer object, just normalize it $customer->email = !empty($customer->email) ? trim($customer->email) : null; $customer->firstname = $firstnames[$customer->entity_id] ?? 'N/A'; $customer->lastname = $lastnames[$customer->entity_id] ?? 'N/A'; } Log::info("Returning " . $customers->count() . " Magento 1 customers"); return $customers; } catch (Exception $e) { Log::error('Error fetching Magento 1 customers: ' . $e->getMessage()); Log::error('Stack trace: ' . $e->getTraceAsString()); return collect([]); } } /** * Get all customers from Magento 2 */ public function getMagento2Customers() { try { // Get entity type ID for customer $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'customer') ->value('entity_type_id'); if (!$entityTypeId) { Log::warning('Magento 2 customer entity type not found'); return collect([]); } Log::info("Magento 2 customer entity_type_id: {$entityTypeId}"); // Get base customer data - email is stored directly in customer_entity table $customers = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'customer_entity') ->select('entity_id', 'email', 'website_id', 'group_id', 'created_at', 'updated_at') ->orderBy('entity_id') ->get(); Log::info("Found " . $customers->count() . " customers in Magento 2 customer_entity table"); // Get firstname and lastname attribute IDs (these are in EAV) $firstnameAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'firstname') ->value('attribute_id'); $lastnameAttributeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $entityTypeId) ->where('attribute_code', 'lastname') ->value('attribute_id'); // Get firstname and lastname from EAV $firstnames = []; $lastnames = []; if ($firstnameAttributeId) { $firstnameValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'customer_entity_varchar') ->where('attribute_id', $firstnameAttributeId) ->pluck('value', 'entity_id') ->toArray(); $firstnames = $firstnameValues; } if ($lastnameAttributeId) { $lastnameValues = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'customer_entity_varchar') ->where('attribute_id', $lastnameAttributeId) ->pluck('value', 'entity_id') ->toArray(); $lastnames = $lastnameValues; } // Combine data - email is already in customer object from the query foreach ($customers as $customer) { // Email is already in customer object, just normalize it $customer->email = !empty($customer->email) ? trim($customer->email) : null; $customer->firstname = $firstnames[$customer->entity_id] ?? 'N/A'; $customer->lastname = $lastnames[$customer->entity_id] ?? 'N/A'; } Log::info("Returning " . $customers->count() . " Magento 2 customers"); return $customers; } catch (Exception $e) { Log::error('Error fetching Magento 2 customers: ' . $e->getMessage()); Log::error('Stack trace: ' . $e->getTraceAsString()); return collect([]); } } /** * Get Magento 1 customers that don't exist in Magento 2 */ public function getM1CustomersNotInM2() { try { $m1Customers = $this->getMagento1Customers(); $m2Customers = $this->getMagento2Customers(); Log::info("M1 Customers Not In M2: M1 count = " . $m1Customers->count() . ", M2 count = " . $m2Customers->count()); // Get all M2 emails - create a set for faster lookup $m2EmailsSet = []; foreach ($m2Customers as $m2Customer) { $email = $m2Customer->email ?? null; if (!empty($email) && is_string($email)) { $normalizedEmail = strtolower(trim($email)); if (!empty($normalizedEmail)) { $m2EmailsSet[$normalizedEmail] = true; } } } Log::info("M1 Customers Not In M2: M2 emails count = " . count($m2EmailsSet)); // Get all M1 emails for comparison $m1EmailsWithCustomers = []; foreach ($m1Customers as $m1Customer) { $email = $m1Customer->email ?? null; if (!empty($email) && is_string($email)) { $normalizedEmail = strtolower(trim($email)); if (!empty($normalizedEmail)) { $m1EmailsWithCustomers[$normalizedEmail] = $m1Customer; } } } Log::info("M1 Customers Not In M2: M1 emails count = " . count($m1EmailsWithCustomers)); // Filter M1 customers that don't exist in M2 $missingCustomers = collect(); foreach ($m1EmailsWithCustomers as $normalizedEmail => $m1Customer) { if (!isset($m2EmailsSet[$normalizedEmail])) { $missingCustomers->push($m1Customer); } } Log::info("M1 Customers Not In M2: Missing customers count = " . $missingCustomers->count()); return $missingCustomers->values(); } catch (Exception $e) { Log::error('Error fetching missing customers: ' . $e->getMessage()); Log::error('Stack trace: ' . $e->getTraceAsString()); return collect([]); } } /** * Get Magento 2 customers that don't exist in Magento 1 */ public function getM2CustomersNotInM1() { try { $m1Customers = $this->getMagento1Customers(); $m2Customers = $this->getMagento2Customers(); // Get all M1 emails - create a set for faster lookup $m1EmailsSet = []; foreach ($m1Customers as $m1Customer) { $email = $m1Customer->email ?? null; if (!empty($email) && is_string($email)) { $normalizedEmail = strtolower(trim($email)); if (!empty($normalizedEmail)) { $m1EmailsSet[$normalizedEmail] = true; } } } // Filter M2 customers that don't exist in M1 $missingCustomers = collect(); foreach ($m2Customers as $m2Customer) { $email = $m2Customer->email ?? null; if (!empty($email) && is_string($email)) { $normalizedEmail = strtolower(trim($email)); if (!empty($normalizedEmail) && !isset($m1EmailsSet[$normalizedEmail])) { $missingCustomers->push($m2Customer); } } } return $missingCustomers->values(); } catch (Exception $e) { Log::error('Error fetching M2 customers not in M1: ' . $e->getMessage()); Log::error('Stack trace: ' . $e->getTraceAsString()); return collect([]); } } /** * Migrate all customers from Magento 1 to Magento 2 */ public function migrateCustomers($dryRun = false) { try { $this->migrationLog = []; $addedCount = 0; $updatedCount = 0; $errorCount = 0; // Get M1 and M2 entity type IDs $m1EntityTypeId = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_entity_type') ->where('entity_type_code', 'customer') ->value('entity_type_id'); $m2EntityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'customer') ->value('entity_type_id'); if (!$m1EntityTypeId || !$m2EntityTypeId) { return [ 'success' => false, 'message' => 'Entity type not found', 'added' => 0, 'updated' => 0, 'errors' => 0, 'log' => [] ]; } // Get all M1 customers $m1Customers = $this->getMagento1Customers(); // Get all customer attribute IDs from M1 $m1AttributeIds = DB::connection($this->magento1Connection) ->table($this->magento1Prefix . 'eav_attribute') ->where('entity_type_id', $m1EntityTypeId) ->pluck('attribute_id', 'attribute_code') ->toArray(); // Get all customer attribute IDs from M2 $m2AttributeIds = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_attribute') ->where('entity_type_id', $m2EntityTypeId) ->pluck('attribute_id', 'attribute_code') ->toArray(); if (!$dryRun) { DB::connection($this->magento2Connection)->beginTransaction(); } foreach ($m1Customers as $m1Customer) { try { $m1Email = !empty($m1Customer->email) ? strtolower(trim($m1Customer->email)) : null; if (empty($m1Email)) { $this->migrationLog[] = "SKIPPED: Customer ID {$m1Customer->entity_id} - no email address"; continue; } // Check if customer exists in M2 by email (case-insensitive) $m2Customer = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'customer_entity') ->whereRaw('LOWER(TRIM(email)) = ?', [strtolower(trim($m1Email))]) ->first(); $m2CustomerId = null; $isNew = false; if ($m2Customer) { // Customer exists, update $m2CustomerId = $m2Customer->entity_id; if (!$dryRun) { $this->migrationLog[] = "Updating existing customer: {$m1Email} (ID: {$m2CustomerId})"; } else { $this->migrationLog[] = "Would update existing customer: {$m1Email} (ID: {$m2CustomerId})"; } $updatedCount++; } else { // Customer doesn't exist, create if (!$dryRun) { // Insert customer entity $m2CustomerId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'customer_entity') ->insertGetId([ 'email' => $m1Email, 'website_id' => $m1Customer->website_id ?? 1, 'group_id' => $m1Customer->group_id ?? 1, 'created_at' => $m1Customer->created_at ?? now(), 'updated_at' => $m1Customer->updated_at ?? now(), ]); $this->migrationLog[] = "Added new customer: {$m1Email} (ID: {$m2CustomerId})"; } else { $this->migrationLog[] = "Would add new customer: {$m1Email}"; $m2CustomerId = 0; // Placeholder for dry run } $addedCount++; $isNew = true; } if (!$dryRun && $m2CustomerId) { // Migrate customer attributes $attributeTables = ['varchar', 'int', 'text', 'decimal', 'datetime']; foreach ($attributeTables as $tableType) { $m1Table = $this->magento1Prefix . 'customer_entity_' . $tableType; $m2Table = $this->magento2Prefix . 'customer_entity_' . $tableType; try { // Get all attributes for this customer from M1 $m1Attributes = DB::connection($this->magento1Connection) ->table($m1Table) ->where('entity_id', $m1Customer->entity_id) ->get(); foreach ($m1Attributes as $m1Attr) { // Check if attribute exists in M2 $attributeCode = array_search($m1Attr->attribute_id, $m1AttributeIds); if ($attributeCode && isset($m2AttributeIds[$attributeCode])) { $m2AttributeId = $m2AttributeIds[$attributeCode]; // Check if attribute value already exists (customer attributes don't have store_id) $exists = DB::connection($this->magento2Connection) ->table($m2Table) ->where('entity_id', $m2CustomerId) ->where('attribute_id', $m2AttributeId) ->exists(); if (!$exists) { // Customer attributes don't have store_id column $insertData = [ 'entity_id' => $m2CustomerId, 'attribute_id' => $m2AttributeId, 'value' => $m1Attr->value ?? null, ]; DB::connection($this->magento2Connection) ->table($m2Table) ->insert($insertData); } else { // Update existing attribute DB::connection($this->magento2Connection) ->table($m2Table) ->where('entity_id', $m2CustomerId) ->where('attribute_id', $m2AttributeId) ->update(['value' => $m1Attr->value ?? null]); } } } } catch (Exception $e) { // Table might not exist, continue Log::warning("Table {$m1Table} or {$m2Table} might not exist: " . $e->getMessage()); } } } } catch (Exception $e) { $errorCount++; $m1Email = $m1Customer->email ?? 'N/A'; $this->migrationLog[] = "ERROR: Failed to migrate customer {$m1Email}: " . $e->getMessage(); Log::error("Error migrating customer {$m1Email}: " . $e->getMessage()); } } if (!$dryRun) { DB::connection($this->magento2Connection)->commit(); } return [ 'success' => true, 'message' => $dryRun ? 'Dry run completed' : 'Customer migration completed', 'added' => $addedCount, 'updated' => $updatedCount, 'errors' => $errorCount, 'log' => $this->migrationLog ]; } catch (Exception $e) { if (!$dryRun) { DB::connection($this->magento2Connection)->rollBack(); } Log::error('Error migrating customers: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Migration failed: ' . $e->getMessage(), 'added' => 0, 'updated' => 0, 'errors' => 0, 'log' => [] ]; } } /** * Delete a single customer from Magento 2 */ public function deleteM2Customer($customerId) { try { DB::connection($this->magento2Connection)->beginTransaction(); // Get entity type ID $entityTypeId = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'eav_entity_type') ->where('entity_type_code', 'customer') ->value('entity_type_id'); if ($entityTypeId) { // Delete attribute values from all attribute tables $attributeTables = ['varchar', 'int', 'text', 'decimal', 'datetime']; foreach ($attributeTables as $tableType) { $table = $this->magento2Prefix . 'customer_entity_' . $tableType; try { DB::connection($this->magento2Connection) ->table($table) ->where('entity_id', $customerId) ->delete(); } catch (Exception $e) { Log::warning("Table {$table} might not exist: " . $e->getMessage()); } } } // Delete the customer entity DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'customer_entity') ->where('entity_id', $customerId) ->delete(); DB::connection($this->magento2Connection)->commit(); return [ 'success' => true, 'message' => 'Customer deleted successfully' ]; } catch (Exception $e) { DB::connection($this->magento2Connection)->rollBack(); Log::error("Error deleting customer {$customerId}: " . $e->getMessage()); return [ 'success' => false, 'message' => 'Failed to delete customer: ' . $e->getMessage() ]; } } }