false, 'MESSAGE' => '', 'URL' => '']; try { $tempBaseDir = appRoot() . "/temp/menu-import"; // Create temp directory if needed if (!is_dir($tempBaseDir)) { mkdir($tempBaseDir, 0755, true); } // Cleanup: delete folders older than 1 hour try { $dirs = glob("$tempBaseDir/*", GLOB_ONLYDIR); $oneHourAgo = time() - 3600; foreach ($dirs as $dir) { if (filemtime($dir) < $oneHourAgo) { // Recursively delete $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach ($files as $file) { $file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath()); } rmdir($dir); } } } catch (Exception $e) { // Ignore cleanup errors } // Check if ZIP file was uploaded if (empty($_FILES['zipFile']['tmp_name'])) { $response['MESSAGE'] = 'No ZIP file uploaded'; jsonResponse($response); } // Generate unique folder name $uniqueId = strtolower(str_replace('-', '', generateUUID())); $extractDir = "$tempBaseDir/$uniqueId"; // Validate it's a ZIP file $fileExt = strtolower(pathinfo($_FILES['zipFile']['name'], PATHINFO_EXTENSION)); if ($fileExt !== 'zip') { $response['MESSAGE'] = 'Only ZIP files are accepted'; jsonResponse($response); } // Create extraction directory mkdir($extractDir, 0755, true); // Extract the ZIP file $zip = new ZipArchive(); if ($zip->open($_FILES['zipFile']['tmp_name']) !== true) { rmdir($extractDir); $response['MESSAGE'] = 'Could not open ZIP file'; jsonResponse($response); } $zip->extractTo($extractDir); $zip->close(); // SECURITY: Sanitize extracted files $safeExtensions = ['htm','html','css','js','json','txt','xml','svg','jpg','jpeg','png','gif','webp','ico','woff','woff2','ttf','eot','otf','map']; $deletedCount = 0; $it = new RecursiveDirectoryIterator($extractDir, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($it); foreach ($files as $file) { if ($file->isDir()) continue; $filePath = $file->getRealPath(); // Delete symlinks if (is_link($filePath)) { unlink($filePath); $deletedCount++; continue; } // Check extension whitelist $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); if (!in_array($ext, $safeExtensions)) { unlink($filePath); $deletedCount++; } } $response['SANITIZED_COUNT'] = $deletedCount; // Make extracted files world-readable exec("chmod -R o+rX " . escapeshellarg($extractDir) . " 2>/dev/null"); // Find the main HTML file $htmlFiles = []; // Top-level HTML files foreach (glob("$extractDir/*.htm*") as $f) { $htmlFiles[] = ['name' => basename($f), 'path' => $f, 'depth' => 0]; } // One level deep foreach (glob("$extractDir/*", GLOB_ONLYDIR) as $subDir) { foreach (glob("$subDir/*.htm*") as $f) { $htmlFiles[] = ['name' => basename($f), 'path' => $f, 'depth' => 1]; } } if (empty($htmlFiles)) { // Clean up and error exec("rm -rf " . escapeshellarg($extractDir)); $response['MESSAGE'] = 'No HTML files found in ZIP'; jsonResponse($response); } // Priority: index.html at top level > any index.html > top-level html > first found $htmlFile = null; foreach ($htmlFiles as $hf) { if (strtolower($hf['name']) === 'index.html' && $hf['depth'] === 0) { $htmlFile = $hf; break; } } if (!$htmlFile) { foreach ($htmlFiles as $hf) { if (strtolower($hf['name']) === 'index.html') { $htmlFile = $hf; break; } } } if (!$htmlFile) { foreach ($htmlFiles as $hf) { if ($hf['depth'] === 0) { $htmlFile = $hf; break; } } } if (!$htmlFile) { $htmlFile = $htmlFiles[0]; } // Build URL path $relativePath = str_replace($extractDir, '', $htmlFile['path']); $relativePath = str_replace('\\', '/', $relativePath); if ($relativePath[0] !== '/') $relativePath = '/' . $relativePath; // Determine protocol and host $forwardedProto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? ''; $protocol = ($forwardedProto === 'https' || ($_SERVER['HTTPS'] ?? '') === 'on') ? 'https' : 'http'; $serverHost = $_SERVER['HTTP_HOST'] ?? 'localhost'; $response['OK'] = true; $response['MESSAGE'] = 'ZIP extracted successfully'; $response['URL'] = "$protocol://$serverHost/temp/menu-import/$uniqueId$relativePath"; $response['FOLDER'] = $uniqueId; $response['FILE'] = $htmlFile['name']; $response['FILE_COUNT'] = count($htmlFiles); } catch (Exception $e) { $response['OK'] = false; $response['MESSAGE'] = 'Error: ' . $e->getMessage(); } jsonResponse($response);