1209 lines
44 KiB
PHP
1209 lines
44 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([]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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 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([]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
|