- 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>
175 lines
5.4 KiB
PHP
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);
|