migrate/app/Services/MagentoCategoryMigrationSer...

3546 lines
144 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Exception;
class MagentoCategoryMigrationService
{
protected $magento1Connection;
protected $magento2Connection;
protected $magento1Prefix;
protected $magento2Prefix;
protected $storeMapping = [];
protected $categoryMapping = [];
protected $migrationLog = [];
protected $addedCount = 0;
protected $existingCount = 0;
public function __construct()
{
$this->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)
// Use all M1 attributes for comprehensive checking, not just common ones
$missingAttrs = $this->migrateProductAttributes($m1Product->entity_id, $m2ProductId, $m1EntityTypeId, $m2EntityTypeId, $allM1AttributeIds, $allM2AttributeIds, $isNew, $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 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 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);
}
}
/**
* 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();
}
}