3546 lines
144 KiB
PHP
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();
|
|
}
|
|
}
|
|
|