migrate/app/Services/MagentoCategoryMigrationSer...

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();
}
}