[ 'Stoelen' => ['chair'], 'Tafels' => ['table'], 'Kasten' => ['shelf'], 'Bedden' => ['bed'], 'Banken' => [], 'Vloerkleden' => ['rug'], 'Verlichting' => ['lighting'], 'Vloeren' => ['floor'], 'Wanddecoratie' => ['wall_decoration', 'window'], 'Ruimteverdelers' => ['divider'], ], 'Spellen' => [ 'Spellen' => ['games'], 'Verkoopautomaten' => ['vending_machine'], 'Fortuna' => ['fortuna'], ], 'Huisdieren' => [ 'Huisdieren' => ['pets'], ], 'Muziek' => [ 'Muziek' => ['music'], 'Geluidseffecten' => ['sound_fx'], ], 'Teleporters' => [ 'Teleporters' => ['teleport'], 'Poorten' => ['gate'], 'Rollers' => ['roller'], ], 'Wired' => [ 'Wired' => ['wired'], 'Wired Effecten' => ['wired_effect'], 'Wired Condities' => ['wired_condition'], 'Wired Triggers' => ['wired_trigger'], 'Wired Add-ons' => ['wired_add_on'], ], 'Diverse' => [ 'Voedsel' => ['food'], 'Prijzen' => ['trophy', 'leaderboards'], 'Cadeaus' => ['present'], 'Credits' => ['credit'], 'Tentjes' => ['tent'], 'Overige' => ['other', 'extras', 'unknown'], ], 'Muur & Vloer' => [ 'Muren' => ['wallpaper'], 'Vloeren' => ['floor_pattern'], 'Landschappen' => ['landscape'], 'Dimmers' => ['dimmer'], ], ]; public function createCatalogPages(): array { $created = 0; $mainPages = [ 'Meubels' => 1, 'Spellen' => 2, 'Huisdieren' => 3, 'Muziek' => 4, 'Teleporters' => 5, 'Wired' => 6, 'Diverse' => 7, 'Muur & Vloer' => 8, ]; foreach ($mainPages as $mainName => $order) { $this->createMainPage($mainName, $order); $created++; } $structure = [ 'Meubels' => [ 'Stoelen' => 'chair', 'Tafels' => 'table', 'Kasten' => 'shelf', 'Bedden' => 'bed', 'Vloerkleden' => 'rug', 'Verlichting' => 'lighting', 'Vloeren' => 'floor', 'Wanddecoratie' => 'wall_decoration', 'Ruimteverdelers' => 'divider', ], 'Spellen' => [ 'Spellen' => 'games', 'Verkoopautomaten' => 'vending_machine', 'Fortuna' => 'fortuna', ], 'Huisdieren' => [ 'Huisdieren' => 'pets', ], 'Muziek' => [ 'Muziek' => 'music', 'Geluidseffecten' => 'sound_fx', ], 'Teleporters' => [ 'Teleporters' => 'teleport', 'Poorten' => 'gate', 'Rollers' => 'roller', ], 'Wired' => [ 'Wired' => 'wired', 'Wired Effecten' => 'wired_effect', 'Wired Condities' => 'wired_condition', 'Wired Triggers' => 'wired_trigger', 'Wired Add-ons' => 'wired_add_on', ], 'Diverse' => [ 'Voedsel' => 'food', 'Prijzen' => 'trophy', 'Cadeaus' => 'present', 'Credits' => 'credit', 'Tentjes' => 'tent', 'Overige' => 'other', ], 'Muur & Vloer' => [ 'Muren' => 'wallpaper', 'Vloeren' => 'floor_pattern', 'Landschappen' => 'landscape', 'Dimmers' => 'dimmer', ], ]; $created = 0; $parentPages = [ 'Meubels' => 2, 'Spellen' => 3, 'Huisdieren' => 4, 'Muziek' => 5, 'Teleporters' => 6, 'Wired' => 7, 'Diverse' => 8, 'Muur & Vloer' => 9, ]; foreach ($structure as $mainName => $subPages) { $parentId = $parentPages[$mainName] ?? 1; $order = 1; foreach (array_keys($subPages) as $subName) { $id = $this->createSubPage($subName, $parentId, $order++); $created++; } } return ['pages_created' => $created]; } public function getFurnitureData(): array { $path = base_path('../Gamedata/config/FurnitureData.json'); if (! file_exists($path)) { $path = '/var/www/Gamedata/config/FurnitureData.json'; } if (! file_exists($path)) { $path = base_path('public/gamedata/config/FurnitureData.json'); } $data = json_decode(file_get_contents($path), true); return $data['roomitemtypes']['furnitype'] ?? []; } private function createMainPage(string $name, int $order): int { $exists = DB::table('catalog_pages')->where('caption', $name)->first(); if ($exists) { return $exists->id; } return DB::table('catalog_pages')->insertGetId([ 'caption' => $name, 'caption_save' => $name, 'parent_id' => -1, 'visible' => 1, 'min_rank' => 1, 'icon_image' => 0, 'order_num' => $order, 'page_layout' => 'default_3x3', ]); } private function createSubPage(string $name, int $parentId, int $order): int { $exists = DB::table('catalog_pages')->where('caption', $name)->where('parent_id', $parentId)->first(); if ($exists) { return $exists->id; } return DB::table('catalog_pages')->insertGetId([ 'caption' => $name, 'caption_save' => $name, 'parent_id' => $parentId, 'visible' => 1, 'min_rank' => 1, 'icon_image' => 0, 'order_num' => $order, 'page_layout' => 'default_3x3', ]); } public function rebuildCatalog(): array { Log::info('[CatalogService] Starting full catalog rebuild'); DB::table('catalog_pages')->where('parent_id', '>', 0)->delete(); DB::table('catalog_pages')->whereIn('parent_id', [-1, 0])->where('id', '>', 10)->delete(); $furnitureData = $this->getFurnitureData(); $itemIdToCategory = []; foreach ($furnitureData as $item) { $id = $item['id']; if (is_numeric($id)) { $itemIdToCategory[$id] = $item['category'] ?? 'unknown'; } } $mainOrder = 1; $categoryToPageId = []; foreach (self::CATALOG_STRUCTURE as $mainName => $subCategories) { $mainPageId = $this->createMainPage($mainName, $mainOrder++); $subOrder = 1; foreach ($subCategories as $subName => $categories) { $subPageId = $this->createSubPage($subName, $mainPageId, $subOrder++); foreach ($categories as $cat) { $categoryToPageId[$cat] = $subPageId; } } } $updated = 0; foreach ($itemIdToCategory as $itemId => $category) { if (isset($categoryToPageId[$category])) { DB::table('catalog_items') ->where('item_ids', (string) $itemId) ->update(['page_id' => $categoryToPageId[$category]]); $updated++; } } Log::info('[CatalogService] Rebuild complete', ['items' => $updated]); return [ 'items_organized' => $updated, 'pages_created' => DB::table('catalog_pages')->count(), ]; } public function organizeCatalog(): array { Log::info('[CatalogService] Starting fast catalog organization'); $categoryToPageId = [ 'chair' => 2221, 'table' => 2222, 'shelf' => 2223, 'bed' => 2224, 'rug' => 2226, 'floor' => 2228, 'lighting' => 2227, 'wall_decoration' => 2229, 'divider' => 2230, 'teleport' => 2240, 'gate' => 2241, 'roller' => 2242, 'wired' => 2244, 'wired_effect' => 2245, 'wired_condition' => 2246, 'wired_trigger' => 2247, 'wired_add_on' => 2248, 'music' => 2237, 'sound_fx' => 2238, 'vending_machine' => 2233, 'games' => 2232, 'fortuna' => 2234, 'pets' => 2235, 'food' => 2250, 'trophy' => 2251, 'present' => 2252, 'credit' => 2253, 'tent' => 2254, 'other' => 'other', 'leaderboards' => 2251, 'extras' => 'other', 'window' => 2229, 'wallpaper' => 'wallpaper', 'floor_pattern' => 'floor_pattern', 'landscape' => 'landscape', 'dimmer' => 'dimmer', ]; $furnitureData = $this->getFurnitureData(); $updates = []; $categoryCounts = []; foreach ($furnitureData as $item) { $id = $item['id']; $category = $item['category'] ?? 'unknown'; if (! is_numeric($id)) { continue; } $pageId = $categoryToPageId[$category] ?? null; if ($pageId && is_numeric($pageId)) { $updates[$pageId][] = (int) $id; $categoryCounts[$category] = ($categoryCounts[$category] ?? 0) + 1; } } $totalUpdated = 0; foreach ($updates as $pageId => $itemIds) { if ($itemIds === []) { continue; } $chunks = array_chunk($itemIds, 500); foreach ($chunks as $chunk) { $chunkStrings = array_map(fn ($id) => (string) $id, $chunk); DB::table('catalog_items') ->whereIn('item_ids', $chunkStrings) ->update(['page_id' => $pageId]); $totalUpdated += count($chunk); } } Log::info('[CatalogService] Fast organization complete', ['items' => $totalUpdated]); return [ 'items_organized' => $totalUpdated, ]; } public function importFromUrl(string $baseUrl): array { Log::info('[CatalogService] Starting import from URL: ' . $baseUrl); $results = [ 'furniture' => 0, 'product' => 0, 'icons' => 0, 'catalog_images' => 0, 'errors' => [], ]; // 1. Download FurnitureData.json $furnitureUrl = $baseUrl . '/gamedata/config/FurnitureData.json'; $localPath = '/var/www/Gamedata/config/FurnitureData.json'; try { $content = @file_get_contents($furnitureUrl); if ($content && json_decode($content, true)) { file_put_contents($localPath, $content); $data = json_decode($content, true); $results['furniture'] = count($data['roomitemtypes']['furnitype'] ?? []); Log::info('[CatalogService] Downloaded FurnitureData.json'); } else { $results['errors'][] = 'FurnitureData.json niet gevonden of ongeldig'; } } catch (Exception $e) { $results['errors'][] = 'FurnitureData: ' . $e->getMessage(); } // 2. Download ProductData.json $productUrl = $baseUrl . '/gamedata/config/ProductData.json'; try { $content = @file_get_contents($productUrl); if ($content && json_decode($content, true)) { file_put_contents('/var/www/Gamedata/config/ProductData.json', $content); $data = json_decode($content, true); $results['product'] = count($data['productdata'] ?? []); Log::info('[CatalogService] Downloaded ProductData.json'); } else { $results['errors'][] = 'ProductData.json niet gevonden of ongeldig'; } } catch (Exception $e) { $results['errors'][] = 'ProductData: ' . $e->getMessage(); } // 3. Download icons $results['icons'] = $this->downloadIconsFromUrl($baseUrl); // 4. Download catalogue images $results['catalog_images'] = $this->downloadCatalogImagesFromUrl($baseUrl); return $results; } private function downloadIconsFromUrl(string $baseUrl): int { $downloaded = 0; $iconsPath = '/var/www/Gamedata/icons'; $url = $baseUrl . '/gamedata/icons'; // Try to list files via HTTP (may not work) // For now, we'll try common icon names from FurnitureData $furnitureData = $this->getFurnitureData(); $iconNames = []; foreach ($furnitureData as $item) { if (isset($item['assets']['asset'])) { foreach ($item['assets']['asset'] as $asset) { if (isset($asset['name']) && str_contains($asset['name'], '_icon')) { $iconNames[] = $asset['name'] . '.png'; } } } // Also check for furni icon if (isset($item['furni']['id'])) { $iconNames[] = $item['classname'] . '_icon.png'; } } $iconNames = array_unique($iconNames); foreach (array_slice($iconNames, 0, 500) as $iconName) { $iconUrl = $url . '/' . $iconName; $localFile = $iconsPath . '/' . $iconName; if (! file_exists($localFile)) { try { $content = @file_get_contents($iconUrl); if ($content) { file_put_contents($localFile, $content); $downloaded++; } } catch (Exception) { // Skip failed downloads } } } Log::info('[CatalogService] Downloaded ' . $downloaded . ' icons'); return $downloaded; } private function downloadCatalogImagesFromUrl(string $baseUrl): int { $downloaded = 0; $catalogPath = '/var/www/Gamedata/catalogue'; $url = $baseUrl . '/gamedata/catalogue'; // Download common catalogue images $commonImages = [ 'header.png', 'footer.png', 'info.png', 'past.png', 'club_header.png', 'club_past.png', 'vip_header.png', ]; foreach ($commonImages as $image) { $imageUrl = $url . '/' . $image; $localFile = $catalogPath . '/' . $image; if (! file_exists($localFile)) { try { $content = @file_get_contents($imageUrl); if ($content) { file_put_contents($localFile, $content); $downloaded++; } } catch (Exception) { // Skip failed downloads } } } Log::info('[CatalogService] Downloaded ' . $downloaded . ' catalog images'); return $downloaded; } public function generateCatalogSql(): array { $furnitureData = $this->getFurnitureData(); // Get current catalog item IDs $existingIds = DB::table('catalog_items') ->pluck('item_ids') ->map(fn ($id) => (int) $id) ->toArray(); $newItems = []; foreach ($furnitureData as $item) { $id = (int) $item['id']; if (! in_array($id, $existingIds)) { $newItems[] = [ 'id' => $id, 'classname' => $item['classname'] ?? 'unknown_' . $id, 'name' => $item['name'] ?? '', 'category' => $item['category'] ?? 'other', ]; } } // Generate SQL $sql = "-- Catalog Update SQL\n"; $sql .= '-- Generated: ' . date('Y-m-d H:i:s') . "\n"; $sql .= '-- New items: ' . count($newItems) . "\n\n"; foreach ($newItems as $item) { $sql .= 'INSERT INTO catalog_items (item_ids, page_id, catalog_name, cost_credits, cost_points, points_type, amount, limited_stack, limited_sells, order_number, have_offer, club_only) '; $sql .= 'VALUES (' . $item['id'] . ", 2, '" . $item['classname'] . "', 10, 0, 0, 1, 0, 0, 0, '1', '0');\n"; } // Save to file $sqlPath = '/var/www/atomcms/storage/logs/catalog_update_' . date('Ymd_His') . '.sql'; file_put_contents($sqlPath, $sql); return [ 'new_items' => count($newItems), 'sql_file' => $sqlPath, 'items' => array_slice($newItems, 0, 50), ]; } public function findMissingItems(): array { $furnitureData = $this->getFurnitureData(); $catalogItemIds = DB::table('catalog_items') ->pluck('item_ids') ->map(fn ($id) => (int) $id) ->toArray(); $validCategories = [ 'chair', 'table', 'shelf', 'bed', 'rug', 'floor', 'lighting', 'wall_decoration', 'divider', 'teleport', 'gate', 'roller', 'wired', 'wired_effect', 'wired_condition', 'wired_trigger', 'wired_add_on', 'music', 'sound_fx', 'vending_machine', 'games', 'fortuna', 'pets', 'food', 'trophy', 'present', 'credit', 'tent', 'extras', 'leaderboards', 'other', 'window', ]; $missing = []; foreach ($furnitureData as $item) { $id = $item['id']; $category = $item['category'] ?? 'unknown'; $classname = $item['classname'] ?? ''; if (in_array($id, $catalogItemIds)) { continue; } if ($category === 'unknown' || $category === '') { continue; } if (str_starts_with($classname, 'avatar_effect')) { continue; } if (! in_array($category, $validCategories)) { continue; } $missing[] = [ 'id' => $id, 'classname' => $classname, 'name' => $item['name'] ?? '', 'category' => $category, ]; } return [ 'total_missing' => count($missing), 'items' => array_slice($missing, 0, 100), ]; } public function addMissingItemsToCatalog(?int $pageId = null): array { $furnitureData = $this->getFurnitureData(); $catalogItemIds = DB::table('catalog_items') ->pluck('item_ids') ->map(fn ($id) => (int) $id) ->toArray(); if ($pageId === null) { $overigePage = DB::table('catalog_pages') ->where('caption', 'Overige') ->where('parent_id', '>', 0) ->first(); if ($overigePage) { $pageId = $overigePage->id; } else { $meubelsPageId = DB::table('catalog_pages') ->where('caption', 'Meubels') ->first()?->id ?? 2; $pageId = DB::table('catalog_pages')->insertGetId([ 'parent_id' => $meubelsPageId, 'caption' => 'Overige', 'caption_save' => 'Overige', 'order_num' => 99, 'visible' => 1, 'enabled' => '1', 'club_only' => '0', 'vip_only' => '0', 'page_layout' => 'default_3x3', 'page_headline' => 'Overige', 'page_teaser' => '', 'icon_color' => 1, 'icon_image' => 1, 'min_rank' => 1, ]); } } $order = DB::table('catalog_items')->max('order_number') ?? 0; $validCategories = [ 'chair', 'table', 'shelf', 'bed', 'rug', 'floor', 'lighting', 'wall_decoration', 'divider', 'teleport', 'gate', 'roller', 'wired', 'wired_effect', 'wired_condition', 'wired_trigger', 'wired_add_on', 'music', 'sound_fx', 'vending_machine', 'games', 'fortuna', 'pets', 'food', 'trophy', 'present', 'credit', 'tent', 'extras', 'leaderboards', 'other', 'window', ]; $inserts = []; foreach ($furnitureData as $item) { $id = $item['id']; if (in_array($id, $catalogItemIds)) { continue; } $classname = $item['classname'] ?? ''; $category = $item['category'] ?? 'unknown'; if (str_starts_with($classname, 'avatar_effect')) { continue; } if ($category === 'unknown' || $category === '') { continue; } if (! in_array($category, $validCategories)) { continue; } $inserts[] = [ 'item_ids' => $id, 'page_id' => $pageId, 'catalog_name' => $classname, 'cost_credits' => $this->estimatePrice($category), 'cost_points' => 0, 'points_type' => 0, 'amount' => 1, 'limited_stack' => 0, 'limited_sells' => 0, 'order_number' => ++$order, 'offer_id' => $item['offerid'] ?? 0, 'song_id' => 0, 'extradata' => '', 'have_offer' => '1', 'club_only' => '0', ]; } $added = 0; if ($inserts !== []) { foreach (array_chunk($inserts, 500) as $chunk) { DB::table('catalog_items')->insert($chunk); $added += count($chunk); } } return [ 'added' => $added, 'page_id' => $pageId, ]; } public function fullSync(string $baseUrl): array { Log::info('[CatalogService] Starting full sync from: ' . $baseUrl); $results = [ 'furniture' => 0, 'product' => 0, 'icons' => 0, 'catalog_images' => 0, 'items_base_inserted' => 0, 'items_base_updated' => 0, 'catalog_inserted' => 0, 'catalog_organized' => 0, 'errors' => [], ]; // Step 1: Download FurnitureData.json $furnitureUrl = rtrim($baseUrl, '/') . '/gamedata/config/FurnitureData.json'; $localFurniturePath = '/var/www/Gamedata/config/FurnitureData.json'; $furnitureContent = @file_get_contents($furnitureUrl); if ($furnitureContent && $furnitureData = json_decode($furnitureContent, true)) { if (file_exists($localFurniturePath)) { copy($localFurniturePath, $localFurniturePath . '.backup'); } file_put_contents($localFurniturePath, $furnitureContent); $results['furniture'] = count($furnitureData['roomitemtypes']['furnitype'] ?? []); } else { $results['errors'][] = 'FurnitureData.json niet bereikbaar'; return $results; } // Step 2: Download ProductData.json $productUrl = rtrim($baseUrl, '/') . '/gamedata/config/ProductData.json'; $productContent = @file_get_contents($productUrl); if ($productContent && json_decode($productContent, true)) { file_put_contents('/var/www/Gamedata/config/ProductData.json', $productContent); $results['product'] = 1; } // Step 3: Sync items_base $sync = $this->syncItemsBase($furnitureData['roomitemtypes']['furnitype'] ?? []); $results['items_base_inserted'] = $sync['inserted']; $results['items_base_updated'] = $sync['updated']; // Step 4: Sync catalog_items $sync = $this->syncCatalogItems($furnitureData['roomitemtypes']['furnitype'] ?? []); $results['catalog_inserted'] = $sync['inserted']; // Step 5: Organize catalog $organize = $this->organizeCatalog(); $results['catalog_organized'] = $organize['items_organized']; // Step 6: Download icons $results['icons'] = $this->syncIcons($furnitureData['roomitemtypes']['furnitype'] ?? [], $baseUrl); // Step 7: Download catalogue images $results['catalog_images'] = $this->syncCatalogImages($baseUrl); Log::info('[CatalogService] Full sync completed', $results); return $results; } private function syncItemsBase(array $furnitureData): array { $inserted = 0; $updated = 0; foreach ($furnitureData as $item) { $id = (int) $item['id']; $classname = $item['classname'] ?? 'unknown_' . $id; $exists = DB::table('items_base')->where('id', $id)->exists(); if ($exists) { DB::table('items_base')->where('id', $id)->update([ 'item_name' => $classname, 'public_name' => $item['name'] ?? $classname, 'interaction_type' => $item['interactiontype'] ?? 'default', ]); $updated++; } else { DB::table('items_base')->insert([ 'id' => $id, 'sprite_id' => $id, 'item_name' => $classname, 'public_name' => $item['name'] ?? $classname, 'type' => 's', 'width' => 1, 'length' => 1, 'stack_height' => 0, 'allow_stack' => 1, 'allow_sit' => 0, 'allow_lay' => 0, 'allow_walk' => 0, 'allow_gift' => 1, 'allow_trade' => 1, 'allow_recycle' => 0, 'allow_marketplace_sell' => 1, 'allow_inventory_stack' => 1, 'interaction_type' => $item['interactiontype'] ?? 'default', 'interaction_modes_count' => 1, ]); $inserted++; } } return ['inserted' => $inserted, 'updated' => $updated]; } private function syncCatalogItems(array $furnitureData): array { $inserted = 0; $existingIds = DB::table('catalog_items')->pluck('item_ids')->map(fn ($id) => (int) $id)->toArray(); $validCategories = [ 'chair', 'table', 'shelf', 'bed', 'rug', 'floor', 'lighting', 'wall_decoration', 'divider', 'teleport', 'gate', 'roller', 'wired', 'wired_effect', 'wired_condition', 'wired_trigger', 'wired_add_on', 'music', 'sound_fx', 'vending_machine', 'games', 'fortuna', 'pets', 'food', 'trophy', 'present', 'credit', 'tent', 'extras', 'leaderboards', 'other', 'window', ]; $overigePage = DB::table('catalog_pages') ->where('caption', 'Overige') ->where('parent_id', '>', 0) ->first(); $defaultPageId = $overigePage?->id ?? 2; $order = DB::table('catalog_items')->max('order_number') ?? 0; $batch = []; foreach ($furnitureData as $item) { $id = (int) $item['id']; if (in_array($id, $existingIds)) { continue; } $classname = $item['classname'] ?? ''; $category = $item['category'] ?? 'unknown'; if (str_starts_with($classname, 'avatar_effect')) { continue; } if ($category === 'unknown' || $category === '' || ! in_array($category, $validCategories)) { continue; } $batch[] = [ 'item_ids' => $id, 'page_id' => $defaultPageId, 'catalog_name' => $classname, 'cost_credits' => $this->estimatePrice($category), 'cost_points' => 0, 'points_type' => 0, 'amount' => 1, 'limited_stack' => 0, 'limited_sells' => 0, 'order_number' => ++$order, 'offer_id' => $item['offerid'] ?? 0, 'song_id' => 0, 'extradata' => '', 'have_offer' => '1', 'club_only' => '0', ]; } if ($batch !== []) { foreach (array_chunk($batch, 500) as $chunk) { DB::table('catalog_items')->insert($chunk); $inserted += count($chunk); } } return ['inserted' => $inserted]; } private function syncIcons(array $furnitureData, string $baseUrl): int { $downloaded = 0; $iconsPath = '/var/www/Gamedata/icons'; $iconsUrl = rtrim($baseUrl, '/') . '/gamedata/icons'; $iconNames = []; foreach ($furnitureData as $item) { $classname = $item['classname'] ?? ''; if ($classname && ! str_starts_with((string) $classname, 'avatar_effect')) { $iconNames[] = $classname . '_icon.png'; } } $iconNames = array_unique($iconNames); foreach (array_slice($iconNames, 0, 2000) as $iconName) { $localFile = $iconsPath . '/' . $iconName; if (! file_exists($localFile)) { $iconUrl = $iconsUrl . '/' . $iconName; $content = @file_get_contents($iconUrl); if ($content && strlen($content) > 100) { file_put_contents($localFile, $content); $downloaded++; } } } return $downloaded; } private function syncCatalogImages(string $baseUrl): int { $downloaded = 0; $catalogPath = '/var/www/Gamedata/catalogue'; $catalogUrl = rtrim($baseUrl, '/') . '/gamedata/catalogue'; $commonImages = [ 'header.png', 'footer.png', 'info.png', 'past.png', 'club_header.png', 'club_past.png', 'vip_header.png', ]; foreach ($commonImages as $image) { $localFile = $catalogPath . '/' . $image; if (! file_exists($localFile)) { $imageUrl = $catalogUrl . '/' . $image; $content = @file_get_contents($imageUrl); if ($content && strlen($content) > 100) { file_put_contents($localFile, $content); $downloaded++; } } } return $downloaded; } public function getStats(): array { return [ 'total_catalog_items' => DB::table('catalog_items')->count(), 'total_pages' => DB::table('catalog_pages')->count(), 'total_items_base' => DB::table('items_base')->count(), 'items_with_pages' => DB::table('catalog_items')->where('page_id', '>', 0)->count(), 'items_without_pages' => DB::table('catalog_items')->where(function ($q) { $q->whereNull('page_id')->orWhere('page_id', 0); })->count(), ]; } public function syncCatalogClothing(): array { Log::info('[CatalogService] Starting catalog_clothing sync'); $figureMapPath = '/var/www/Gamedata/config/FigureMap.json'; if (! file_exists($figureMapPath)) { throw new \Exception('FigureMap.json niet gevonden'); } $figureMap = json_decode(file_get_contents($figureMapPath), true); // Clear table first DB::table('catalog_clothing')->truncate(); $inserted = 0; $batch = []; foreach ($figureMap['libraries'] ?? [] as $library) { $libId = $library['id']; $partIds = []; foreach ($library['parts'] as $part) { $partIds[] = (string) $part['id']; } // Get unique part IDs $uniquePartIds = array_unique($partIds); $setIds = implode(',', $uniquePartIds); // Skip if setid is too long for database (varchar 75) if (strlen($setIds) > 75) { $truncated = []; $length = 0; foreach ($uniquePartIds as $id) { if ($length + strlen($id) + 1 > 75) { break; } $truncated[] = $id; $length += strlen($id) + 1; } $setIds = implode(',', $truncated); } // Convert library name to clothing name format $clothingName = 'clothing_' . $this->libraryToClothingName($libId); // Clean and validate set IDs $cleanSetIds = trim($setIds, ','); if (strlen($cleanSetIds) < 1) { continue; } // Filter out IDs that are too large (>= 2^31, overflows 32-bit integer) $validIds = []; foreach (explode(',', $cleanSetIds) as $id) { $id = (int) trim($id); if ($id > 0 && $id < 2147483647) { $validIds[] = $id; } } if ($validIds === []) { continue; } $cleanSetIds = implode(',', $validIds); $batch[] = [ 'name' => $clothingName, 'setid' => $cleanSetIds, ]; } // Insert in batches foreach (array_chunk($batch, 500) as $chunk) { DB::table('catalog_clothing')->insert($chunk); $inserted += count($chunk); } Log::info('[CatalogService] Catalog clothing sync complete', [ 'inserted' => $inserted, ]); return [ 'inserted' => $inserted, 'total' => DB::table('catalog_clothing')->count(), ]; } private function libraryToClothingName(string $libId): string { // Convert library name like "shirt_F_yogatop" to "yogatop" // Remove gender suffix (_F, _M, _U) $name = preg_replace('/_[FMU]$/', '', $libId); // Remove prefix (shirt_, jacket_, hat_, etc.) $name = preg_replace('/^(shirt|jacket|hat|trousers|acc_eye|acc_face|acc_chest|acc_waist|shoes|hair|eye|head|face|chest|waist)_?/', '', (string) $name); return strtolower((string) $name); } public function syncCatalogClothingFromFurnitureData(): array { Log::info('[CatalogService] Starting catalog_clothing sync from FurnitureData'); $furnitureData = $this->getFurnitureData(); // Get existing clothing $existingClothing = DB::table('catalog_clothing')->pluck('setid', 'name')->toArray(); $inserted = 0; $updated = 0; foreach ($furnitureData as $item) { $classname = $item['classname'] ?? ''; // Check if this is a clothing item (usually starts with 'clothing_') if (! str_starts_with($classname, 'clothing_')) { continue; } // Get set ID from extradata $setid = $item['extradata'] ?? $item['params'] ?? ''; if (empty($setid)) { continue; } // Truncate if too long (column is varchar 75) $setid = substr((string) $setid, 0, 75); if (isset($existingClothing[$classname])) { if ($existingClothing[$classname] !== $setid) { DB::table('catalog_clothing') ->where('name', $classname) ->update(['setid' => $setid]); $updated++; } } else { DB::table('catalog_clothing')->insert([ 'name' => $classname, 'setid' => $setid, ]); $inserted++; } } return [ 'inserted' => $inserted, 'updated' => $updated, 'total' => DB::table('catalog_clothing')->count(), ]; } }