📡 Database Connections
+📊 Statistics
+🔄 Store Mapping
+Map each Magento 1 store to its corresponding Magento 2 store:
+ +No Magento 1 stores found. Please check your database connection.
+⚡ Actions
+Migration in progress...
+diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 857af5c..b2322d3 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -10,6 +10,7 @@ additional_fqdns: database: type: mariadb version: "10.11" +host_db_port: "3004" use_dns_when_possible: true composer_version: "2" web_environment: [] diff --git a/DATABASE_SETUP.md b/DATABASE_SETUP.md new file mode 100644 index 0000000..4f40883 --- /dev/null +++ b/DATABASE_SETUP.md @@ -0,0 +1,126 @@ +# Database Connection Setup for DDEV + +## Problem: Connection Refused Error + +If you're seeing `SQLSTATE[HY000] [2002] Connection refused`, it means the Laravel application running inside DDEV cannot reach the Magento databases. + +## Solution + +The databases `magento1` and `magento2` are likely running on your **host machine**, but the Laravel app is running inside a **DDEV container**. Containers need a special IP address to reach the host machine. + +### Step 1: Find Your Host IP from DDEV + +Run this command to find the correct IP address: + +```bash +ddev exec "ip route show default | awk '/default/ {print \$3}'" +``` + +This will output something like `172.22.0.1` (your gateway IP). + +### Step 2: Update Your .env File + +Add these lines to your `.env` file with the correct host IP: + +```env +# Magento 1 Database +# Use the gateway IP from Step 1 (e.g., 172.22.0.1) +# OR use 127.0.0.1 if databases are in another DDEV project +MAGENTO1_DB_HOST=172.22.0.1 +MAGENTO1_DB_PORT=3306 +MAGENTO1_DB_DATABASE=magento1 +MAGENTO1_DB_USERNAME=root +MAGENTO1_DB_PASSWORD=your_password +MAGENTO1_DB_PREFIX= + +# Magento 2 Database +MAGENTO2_DB_HOST=172.22.0.1 +MAGENTO2_DB_PORT=3306 +MAGENTO2_DB_DATABASE=magento2 +MAGENTO2_DB_USERNAME=root +MAGENTO2_DB_PASSWORD=your_password +MAGENTO2_DB_PREFIX= +``` + +### Step 3: Verify MySQL is Accessible from Host + +Make sure your MySQL server on the host is configured to accept connections. Check: + +1. **MySQL is running:** + ```bash + sudo systemctl status mysql + # or + sudo systemctl status mariadb + ``` + +2. **MySQL binds to the correct interface:** + Check `/etc/mysql/my.cnf` or `/etc/mysql/mariadb.conf.d/50-server.cnf`: + ```ini + bind-address = 0.0.0.0 # Allows connections from any IP + # OR + bind-address = 127.0.0.1 # Only localhost (won't work from DDEV) + ``` + +3. **Firewall allows connections:** + ```bash + sudo ufw status + # If needed, allow MySQL: + sudo ufw allow 3306/tcp + ``` + +### Step 4: Test Connection from DDEV Container + +Test if you can connect from inside the DDEV container: + +```bash +ddev exec "mysql -h 172.22.0.1 -u root -pyour_password -e 'SHOW DATABASES;'" +``` + +Replace `172.22.0.1` with your gateway IP from Step 1, and `your_password` with your actual MySQL root password. + +### Alternative: If Databases are in Separate DDEV Projects + +If `magento1` and `magento2` are in separate DDEV projects, you can: + +1. **Use the DDEV database service name:** + - If magento1 is in a DDEV project called `magento1`, use: `MAGENTO1_DB_HOST=magento1-db` + - This only works if both projects are in the same Docker network + +2. **Use the host port mapping:** + - Find the mapped port: `ddev describe` (in the magento1 project) + - Use `127.0.0.1` as host and the mapped port + +### Quick Fix Script + +Run this to automatically detect and test the connection: + +```bash +# Get the gateway IP +GATEWAY_IP=$(ddev exec "ip route show default | awk '/default/ {print \$3}'" | tr -d '\n') +echo "Gateway IP: $GATEWAY_IP" + +# Test connection +ddev exec "mysql -h $GATEWAY_IP -u root -proot -e 'SHOW DATABASES LIKE \"magento%\"'" +``` + +### Still Having Issues? + +1. **Check if databases exist:** + ```bash + mysql -u root -p -e "SHOW DATABASES LIKE 'magento%';" + ``` + +2. **Check MySQL user permissions:** + ```bash + mysql -u root -p -e "SELECT User, Host FROM mysql.user WHERE User='root';" + ``` + Make sure root can connect from `%` (any host) or from the gateway IP. + +3. **Check MySQL error log:** + ```bash + sudo tail -f /var/log/mysql/error.log + ``` + +4. **Test from web interface:** + Visit `/migration` and click "Test Connections" to see detailed error messages. + diff --git a/ENV_SETUP_INSTRUCTIONS.md b/ENV_SETUP_INSTRUCTIONS.md new file mode 100644 index 0000000..d8be080 --- /dev/null +++ b/ENV_SETUP_INSTRUCTIONS.md @@ -0,0 +1,76 @@ +# Environment Configuration Instructions + +## Database Password Setup + +The Magento database configuration has been added to your `.env` file, but you need to set the MySQL password. + +### Step 1: Edit .env File + +Open your `.env` file and update these lines with your actual MySQL root password: + +```env +MAGENTO1_DB_PASSWORD=your_actual_mysql_password +MAGENTO2_DB_PASSWORD=your_actual_mysql_password +``` + +### Step 2: Clear Config Cache + +After updating the password, clear Laravel's config cache: + +```bash +ddev exec "php artisan config:clear" +``` + +Or if you're not using DDEV: + +```bash +php artisan config:clear +``` + +### Step 3: Test the Connection + +1. Visit the migration page: `https://migrate.ddev.site/migration` +2. Click the "Test Connections" button +3. You should see green checkmarks if the connections are working + +## Troubleshooting + +### If you don't know your MySQL password: + +1. **Try connecting from the host:** + ```bash + mysql -u root -p + ``` + (Press Enter when prompted for password if there's no password) + +2. **Or check if MySQL has no password:** + - Leave `MAGENTO1_DB_PASSWORD=` empty (no value after the `=`) + - Leave `MAGENTO2_DB_PASSWORD=` empty + +### If connection still fails: + +1. **Verify MySQL is accessible from DDEV:** + ```bash + ddev exec "mysql -h 172.22.0.1 -u root -pyour_password -e 'SHOW DATABASES LIKE \"magento%\"'" + ``` + +2. **Check MySQL bind address:** + ```bash + sudo grep bind-address /etc/mysql/mariadb.conf.d/50-server.cnf + ``` + Should be `bind-address = 0.0.0.0` (not `127.0.0.1`) + +3. **Check if databases exist:** + ```bash + mysql -u root -p -e "SHOW DATABASES LIKE 'magento%';" + ``` + +## Current Configuration + +Your current `.env` settings: +- **Host:** 172.22.0.1 (DDEV gateway IP) +- **Port:** 3306 +- **Database:** magento1 and magento2 +- **Username:** root +- **Password:** (needs to be set) + diff --git a/MAGENTO_MIGRATION_README.md b/MAGENTO_MIGRATION_README.md new file mode 100644 index 0000000..7e33dbf --- /dev/null +++ b/MAGENTO_MIGRATION_README.md @@ -0,0 +1,138 @@ +# Magento Category Migration Tool + +This Laravel application provides a web-based interface to migrate categories from Magento 1 to Magento 2 with multi-store support. + +## Features + +- ✅ Multi-store category migration +- ✅ Web-based user interface +- ✅ Real-time migration progress +- ✅ Database connection testing +- ✅ Category preview before migration +- ✅ Detailed migration logs + +## Setup + +### 1. Database Configuration + +Add the following environment variables to your `.env` file: + +```env +# Magento 1 Database +MAGENTO1_DB_HOST=127.0.0.1 +MAGENTO1_DB_PORT=3306 +MAGENTO1_DB_DATABASE=magento1 +MAGENTO1_DB_USERNAME=root +MAGENTO1_DB_PASSWORD=your_password +MAGENTO1_DB_PREFIX= + +# Magento 2 Database +MAGENTO2_DB_HOST=127.0.0.1 +MAGENTO2_DB_PORT=3306 +MAGENTO2_DB_DATABASE=magento2 +MAGENTO2_DB_USERNAME=root +MAGENTO2_DB_PASSWORD=your_password +MAGENTO2_DB_PREFIX= +``` + +### 2. Access the Migration Interface + +Once your Laravel application is running, navigate to: + +``` +http://your-domain/migration +``` + +Or if using DDEV: + +``` +https://migrate.ddev.site/migration +``` + +## Usage + +### 1. Test Database Connections + +Click the "Test Connections" button to verify that both Magento 1 and Magento 2 databases are accessible. + +### 2. Map Stores + +For each Magento 1 store, select the corresponding Magento 2 store from the dropdown menu. This mapping determines which store view the category attributes will be migrated to. + +### 3. Preview Categories (Optional) + +Click "Preview Categories" to see a list of categories that will be migrated from Magento 1. + +### 4. Start Migration + +Click "Start Migration" to begin the migration process. The tool will: + +- Migrate category structure (parent-child relationships) +- Migrate category attributes (name, URL key, is_active) for each mapped store +- Preserve category hierarchy and positions +- Generate detailed migration logs + +## How It Works + +### Category Migration Process + +1. **Category Structure**: The tool reads all categories from Magento 1 and builds a tree structure +2. **Level-by-Level Migration**: Categories are migrated level by level, ensuring parent categories exist before child categories +3. **Attribute Migration**: For each store mapping, category attributes (name, URL key, is_active) are migrated to the corresponding Magento 2 store view +4. **Path Building**: Category paths are automatically built to maintain the correct hierarchy + +### Database Tables Used + +**Magento 1:** +- `catalog_category_entity` - Category entities +- `catalog_category_entity_varchar` - Category text attributes +- `catalog_category_entity_int` - Category integer attributes +- `core_store` - Store information +- `eav_attribute` - Attribute definitions +- `eav_entity_type` - Entity type definitions + +**Magento 2:** +- `catalog_category_entity` - Category entities +- `catalog_category_entity_varchar` - Category text attributes +- `catalog_category_entity_int` - Category integer attributes +- `store` - Store information +- `eav_attribute` - Attribute definitions +- `eav_entity_type` - Entity type definitions + +## Important Notes + +⚠️ **Backup First**: Always backup your Magento 2 database before running the migration. + +⚠️ **Test Environment**: It's recommended to test the migration on a development/staging environment first. + +⚠️ **Store Mapping**: Ensure that store mappings are correct. Incorrect mappings may result in categories being assigned to the wrong store views. + +⚠️ **Root Category**: The tool assumes Magento 2's root category ID is 2 (default). If your setup uses a different root category ID, you may need to adjust the code. + +## Troubleshooting + +### Connection Errors + +If you see connection errors: + +1. Verify database credentials in `.env` +2. Ensure both databases are accessible from your Laravel application +3. Check database table prefixes if your Magento installations use them +4. Verify network connectivity and firewall settings + +### Migration Errors + +If migration fails: + +1. Check the migration logs in the web interface +2. Review Laravel logs: `storage/logs/laravel.log` +3. Ensure Magento 2 database has proper permissions +4. Verify that required Magento 2 tables exist + +## Support + +For issues or questions, please check: +- Laravel logs: `storage/logs/laravel.log` +- Migration logs displayed in the web interface +- Database connection status in the web interface + diff --git a/app/Http/Controllers/MagentoMigrationController.php b/app/Http/Controllers/MagentoMigrationController.php new file mode 100644 index 0000000..5aa212e --- /dev/null +++ b/app/Http/Controllers/MagentoMigrationController.php @@ -0,0 +1,104 @@ +migrationService = $migrationService; + } + + /** + * Show the migration interface + */ + public function index() + { + $m1Stores = $this->migrationService->getMagento1Stores(); + $m2Stores = $this->migrationService->getMagento2Stores(); + $connectionTest = $this->migrationService->testConnections(); + $m1Categories = $this->migrationService->getMagento1Categories(); + + return view('migration.index', [ + 'm1Stores' => $m1Stores, + 'm2Stores' => $m2Stores, + 'connectionTest' => $connectionTest, + 'm1CategoriesCount' => $m1Categories->count(), + ]); + } + + /** + * Test database connections + */ + public function testConnections() + { + $results = $this->migrationService->testConnections(); + + return response()->json([ + 'success' => $results['magento1'] && $results['magento2'], + 'results' => $results, + ]); + } + + /** + * Get Magento 1 categories preview + */ + public function getMagento1Categories() + { + $categories = $this->migrationService->getMagento1Categories(); + + return response()->json([ + 'success' => true, + 'count' => $categories->count(), + 'categories' => $categories->take(50)->map(function($cat) { + return [ + 'id' => $cat->entity_id, + 'name' => $cat->name ?? 'N/A', + 'level' => $cat->level, + 'parent_id' => $cat->parent_id, + 'is_active' => $cat->is_active ?? 0, + ]; + }), + ]); + } + + /** + * Execute the migration + */ + public function migrate(Request $request) + { + $request->validate([ + 'store_mapping' => 'required|array', + 'store_mapping.*' => 'required|integer', + ]); + + try { + $storeMapping = $request->input('store_mapping'); + + // Convert array format: ['m1_store_id' => 'm2_store_id'] + $mapping = []; + foreach ($storeMapping as $m1StoreId => $m2StoreId) { + $mapping[(int)$m1StoreId] = (int)$m2StoreId; + } + + $result = $this->migrationService->migrateCategories($mapping); + + return response()->json($result); + + } catch (\Exception $e) { + Log::error('Migration error: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Migration failed: ' . $e->getMessage(), + ], 500); + } + } +} + diff --git a/app/Services/MagentoCategoryMigrationService.php b/app/Services/MagentoCategoryMigrationService.php new file mode 100644 index 0000000..1d92355 --- /dev/null +++ b/app/Services/MagentoCategoryMigrationService.php @@ -0,0 +1,465 @@ +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([]); + } + } + + /** + * Migrate categories from Magento 1 to Magento 2 + */ + public function migrateCategories($storeMapping = []) + { + $this->storeMapping = $storeMapping; + $this->migrationLog = []; + $this->categoryMapping = []; + + 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); + } + } + + // 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), + '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; + } + + /** + * Migrate a single category + */ + protected function migrateCategory($m1Category, $parentId = null) + { + try { + // Check if category already exists + 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; + } + + // Insert 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]); + + // Store mapping + $this->categoryMapping[$m1Category->entity_id] = $m2EntityId; + + $this->migrationLog[] = "Migrated category ID {$m1Category->entity_id} -> {$m2EntityId} ({$m1Category->name})"; + + 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; + } + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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; + } +} + diff --git a/config/database.php b/config/database.php index 53dcae0..acbecda 100644 --- a/config/database.php +++ b/config/database.php @@ -113,6 +113,42 @@ // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), ], + 'magento1' => [ + 'driver' => 'mysql', + 'host' => env('MAGENTO1_DB_HOST', 'db'), + 'port' => env('MAGENTO1_DB_PORT', '3306'), + 'database' => env('MAGENTO1_DB_DATABASE', 'magento1'), + 'username' => env('MAGENTO1_DB_USERNAME', 'root'), + 'password' => env('MAGENTO1_DB_PASSWORD', 'root'), + 'charset' => 'utf8', + 'collation' => 'utf8_general_ci', + 'prefix' => env('MAGENTO1_DB_PREFIX', ''), + 'prefix_indexes' => true, + 'strict' => false, + 'engine' => null, + 'options' => [ + PDO::ATTR_TIMEOUT => 5, + ], + ], + + 'magento2' => [ + 'driver' => 'mysql', + 'host' => env('MAGENTO2_DB_HOST', 'db'), + 'port' => env('MAGENTO2_DB_PORT', '3306'), + 'database' => env('MAGENTO2_DB_DATABASE', 'magento2'), + 'username' => env('MAGENTO2_DB_USERNAME', 'root'), + 'password' => env('MAGENTO2_DB_PASSWORD', 'root'), + 'charset' => 'utf8', + 'collation' => 'utf8_general_ci', + 'prefix' => env('MAGENTO2_DB_PREFIX', ''), + 'prefix_indexes' => true, + 'strict' => false, + 'engine' => null, + 'options' => [ + PDO::ATTR_TIMEOUT => 5, + ], + ], + ], /* diff --git a/resources/views/migration/index.blade.php b/resources/views/migration/index.blade.php new file mode 100644 index 0000000..19da52e --- /dev/null +++ b/resources/views/migration/index.blade.php @@ -0,0 +1,480 @@ + + +
+ + +Migrate categories from Magento 1 to Magento 2 with multi-store support
+Map each Magento 1 store to its corresponding Magento 2 store:
+ +No Magento 1 stores found. Please check your database connection.
+Migration in progress...
+