payfrit-api/api/setup/uploadSavedPage.php
John Mizerek 5761ed3e88 Standardize UUID format: generateUUID() now returns unhyphenated 32-char hex
- Remove vsprintf hyphenation from generateUUID() in helpers.php
- Remove redundant str_replace('-', '', ...) wrappers in callers
- Fix grants/create, tabs/open, orders/getOrCreateCart which were storing hyphenated UUIDs
- Cast prices to float in getForBuilder.php
- Uppercase auth response keys (TOKEN, USERID, FIRSTNAME)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 16:43:02 -07:00

175 lines
5.4 KiB
PHP

<?php
require_once __DIR__ . '/../helpers.php';
runAuth();
/**
* Upload Saved Page (ZIP)
*
* Accepts a ZIP file of a saved web page, extracts it,
* sanitizes unsafe files, and returns the URL to the main HTML file.
*/
$response = ['OK' => 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 = 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);