Files
2026-05-09 17:32:17 +02:00

1104 lines
36 KiB
PHP
Executable File

<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CatalogService
{
private const array CATALOG_STRUCTURE = [
'Meubels' => [
'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(),
];
}
}