Complete port of all 163 API endpoints from Lucee/CFML to PHP 8.3. Shared helpers in api/helpers.php (DB, auth, request/response, security). PDO prepared statements throughout. Same JSON response shapes as CFML.
258 lines
11 KiB
PHP
258 lines
11 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../helpers.php';
|
|
runAuth();
|
|
|
|
/**
|
|
* Save menu data from the builder UI
|
|
*
|
|
* POST body: { "BusinessID": 37, "Menu": { "categories": [...] } }
|
|
*/
|
|
|
|
// Track which templates we've already saved options for
|
|
$savedTemplates = [];
|
|
|
|
/**
|
|
* Recursively save options/modifiers at any depth.
|
|
*/
|
|
function saveOptionsRecursive(array $options, int $parentID, int $businessID): void {
|
|
$optSortOrder = 0;
|
|
foreach ($options as $opt) {
|
|
$optDbId = (int) ($opt['dbId'] ?? 0);
|
|
$requiresSelection = (!empty($opt['requiresSelection'])) ? 1 : 0;
|
|
$maxSelections = (int) ($opt['maxSelections'] ?? 0);
|
|
$isDefault = (!empty($opt['isDefault'])) ? 1 : 0;
|
|
$optionID = 0;
|
|
|
|
if ($optDbId > 0) {
|
|
$optionID = $optDbId;
|
|
queryTimed("
|
|
UPDATE Items
|
|
SET Name = ?, Price = ?, IsCheckedByDefault = ?, SortOrder = ?,
|
|
RequiresChildSelection = ?, MaxNumSelectionReq = ?, ParentItemID = ?
|
|
WHERE ID = ?
|
|
", [
|
|
$opt['name'], (float) ($opt['price'] ?? 0), $isDefault, $optSortOrder,
|
|
$requiresSelection, $maxSelections, $parentID, $optDbId,
|
|
]);
|
|
} else {
|
|
queryTimed("
|
|
INSERT INTO Items (
|
|
BusinessID, ParentItemID, Name, Price,
|
|
IsCheckedByDefault, SortOrder, IsActive, AddedOn,
|
|
RequiresChildSelection, MaxNumSelectionReq, CategoryID
|
|
) VALUES (?, ?, ?, ?, ?, ?, 1, NOW(), ?, ?, 0)
|
|
", [
|
|
$businessID, $parentID, $opt['name'], (float) ($opt['price'] ?? 0),
|
|
$isDefault, $optSortOrder, $requiresSelection, $maxSelections,
|
|
]);
|
|
$optionID = (int) lastInsertId();
|
|
}
|
|
|
|
if (!empty($opt['options']) && is_array($opt['options'])) {
|
|
saveOptionsRecursive($opt['options'], $optionID, $businessID);
|
|
}
|
|
|
|
$optSortOrder++;
|
|
}
|
|
}
|
|
|
|
$data = readJsonBody();
|
|
$businessID = (int) ($data['BusinessID'] ?? 0);
|
|
$menu = $data['Menu'] ?? [];
|
|
|
|
if ($businessID === 0) {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'BusinessID is required']);
|
|
}
|
|
|
|
if (!isset($menu['categories']) || !is_array($menu['categories'])) {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'Menu categories are required']);
|
|
}
|
|
|
|
try {
|
|
// Determine schema
|
|
$hasCategoriesData = false;
|
|
try {
|
|
$qCatCheck = queryOne("SELECT 1 as x FROM Categories WHERE BusinessID = ? LIMIT 1", [$businessID]);
|
|
$hasCategoriesData = $qCatCheck !== null;
|
|
} catch (Exception $e) {}
|
|
$newSchemaActive = !$hasCategoriesData;
|
|
|
|
$db = getDb();
|
|
$db->beginTransaction();
|
|
|
|
// Track JS category id -> DB categoryID for subcategory parent resolution
|
|
$jsCatIdToDbId = [];
|
|
|
|
$catSortOrder = 0;
|
|
foreach ($menu['categories'] as $cat) {
|
|
$categoryID = 0;
|
|
$categoryDbId = (int) ($cat['dbId'] ?? 0);
|
|
$categoryMenuId = (int) ($cat['menuId'] ?? 0);
|
|
|
|
// Resolve parentCategoryID
|
|
$parentCategoryID = 0;
|
|
if (!empty($cat['parentCategoryDbId']) && (int) $cat['parentCategoryDbId'] > 0) {
|
|
$parentCategoryID = (int) $cat['parentCategoryDbId'];
|
|
} elseif (!empty($cat['parentCategoryId']) && $cat['parentCategoryId'] !== '0') {
|
|
if (isset($jsCatIdToDbId[$cat['parentCategoryId']])) {
|
|
$parentCategoryID = $jsCatIdToDbId[$cat['parentCategoryId']];
|
|
}
|
|
}
|
|
|
|
if ($newSchemaActive) {
|
|
if ($categoryDbId > 0) {
|
|
$categoryID = $categoryDbId;
|
|
queryTimed("
|
|
UPDATE Items SET Name = ?, SortOrder = ?
|
|
WHERE ID = ? AND BusinessID = ?
|
|
", [$cat['name'], $catSortOrder, $categoryID, $businessID]);
|
|
} else {
|
|
queryTimed("
|
|
INSERT INTO Items (BusinessID, Name, Description, ParentItemID, Price, IsActive, SortOrder, AddedOn, CategoryID)
|
|
VALUES (?, ?, '', 0, 0, 1, ?, NOW(), 0)
|
|
", [$businessID, $cat['name'], $catSortOrder]);
|
|
$categoryID = (int) lastInsertId();
|
|
}
|
|
} else {
|
|
if ($categoryDbId > 0) {
|
|
$categoryID = $categoryDbId;
|
|
queryTimed("
|
|
UPDATE Categories
|
|
SET Name = ?, SortOrder = ?, MenuID = NULLIF(?, 0), ParentCategoryID = ?
|
|
WHERE ID = ?
|
|
", [$cat['name'], $catSortOrder, $categoryMenuId, $parentCategoryID, $categoryID]);
|
|
} else {
|
|
queryTimed("
|
|
INSERT INTO Categories (BusinessID, MenuID, Name, SortOrder, ParentCategoryID, AddedOn)
|
|
VALUES (?, NULLIF(?, 0), ?, ?, ?, NOW())
|
|
", [$businessID, $categoryMenuId, $cat['name'], $catSortOrder, $parentCategoryID]);
|
|
$categoryID = (int) lastInsertId();
|
|
}
|
|
}
|
|
|
|
// Track JS id -> DB id
|
|
if (!empty($cat['id'])) {
|
|
$jsCatIdToDbId[$cat['id']] = $categoryID;
|
|
}
|
|
|
|
// Process items
|
|
if (!empty($cat['items']) && is_array($cat['items'])) {
|
|
$itemSortOrder = 0;
|
|
foreach ($cat['items'] as $item) {
|
|
$itemID = 0;
|
|
$itemDbId = (int) ($item['dbId'] ?? 0);
|
|
|
|
if ($itemDbId > 0) {
|
|
$itemID = $itemDbId;
|
|
if ($newSchemaActive) {
|
|
queryTimed("
|
|
UPDATE Items SET Name = ?, Description = ?, Price = ?, ParentItemID = ?, SortOrder = ?
|
|
WHERE ID = ?
|
|
", [$item['name'], $item['description'] ?? '', (float) ($item['price'] ?? 0), $categoryID, $itemSortOrder, $itemID]);
|
|
} else {
|
|
queryTimed("
|
|
UPDATE Items SET Name = ?, Description = ?, Price = ?, CategoryID = ?, SortOrder = ?
|
|
WHERE ID = ?
|
|
", [$item['name'], $item['description'] ?? '', (float) ($item['price'] ?? 0), $categoryID, $itemSortOrder, $itemID]);
|
|
}
|
|
} else {
|
|
if ($newSchemaActive) {
|
|
queryTimed("
|
|
INSERT INTO Items (BusinessID, ParentItemID, Name, Description, Price, SortOrder, IsActive, AddedOn, CategoryID)
|
|
VALUES (?, ?, ?, ?, ?, ?, 1, NOW(), 0)
|
|
", [$businessID, $categoryID, $item['name'], $item['description'] ?? '', (float) ($item['price'] ?? 0), $itemSortOrder]);
|
|
} else {
|
|
queryTimed("
|
|
INSERT INTO Items (BusinessID, CategoryID, Name, Description, Price, SortOrder, IsActive, ParentItemID, AddedOn)
|
|
VALUES (?, ?, ?, ?, ?, ?, 1, 0, NOW())
|
|
", [$businessID, $categoryID, $item['name'], $item['description'] ?? '', (float) ($item['price'] ?? 0), $itemSortOrder]);
|
|
}
|
|
$itemID = (int) lastInsertId();
|
|
}
|
|
|
|
// Handle modifiers
|
|
if (!empty($item['modifiers']) && is_array($item['modifiers'])) {
|
|
// Clear existing template links for this item
|
|
queryTimed("DELETE FROM lt_ItemID_TemplateItemID WHERE ItemID = ?", [$itemID]);
|
|
|
|
$modSortOrder = 0;
|
|
foreach ($item['modifiers'] as $mod) {
|
|
$modDbId = (int) ($mod['dbId'] ?? 0);
|
|
$requiresSelection = (!empty($mod['requiresSelection'])) ? 1 : 0;
|
|
$maxSelections = (int) ($mod['maxSelections'] ?? 0);
|
|
|
|
if (!empty($mod['isTemplate']) && $modDbId > 0) {
|
|
// Template reference — create link
|
|
queryTimed("
|
|
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE SortOrder = ?
|
|
", [$itemID, $modDbId, $modSortOrder, $modSortOrder]);
|
|
|
|
// Update template name and selection rules
|
|
queryTimed("
|
|
UPDATE Items SET Name = ?, RequiresChildSelection = ?, MaxNumSelectionReq = ?
|
|
WHERE ID = ?
|
|
", [$mod['name'], $requiresSelection, $maxSelections, $modDbId]);
|
|
|
|
// Save template options only once
|
|
if (!isset($savedTemplates[$modDbId])) {
|
|
$savedTemplates[$modDbId] = true;
|
|
if (!empty($mod['options']) && is_array($mod['options'])) {
|
|
saveOptionsRecursive($mod['options'], $modDbId, $businessID);
|
|
}
|
|
}
|
|
|
|
} elseif ($modDbId > 0) {
|
|
// Direct modifier — update
|
|
$isDefault = (!empty($mod['isDefault'])) ? 1 : 0;
|
|
queryTimed("
|
|
UPDATE Items
|
|
SET Name = ?, Price = ?, IsCheckedByDefault = ?, SortOrder = ?,
|
|
RequiresChildSelection = ?, MaxNumSelectionReq = ?, ParentItemID = ?
|
|
WHERE ID = ?
|
|
", [
|
|
$mod['name'], (float) ($mod['price'] ?? 0), $isDefault, $modSortOrder,
|
|
$requiresSelection, $maxSelections, $itemID, $modDbId,
|
|
]);
|
|
|
|
if (!empty($mod['options']) && is_array($mod['options'])) {
|
|
saveOptionsRecursive($mod['options'], $modDbId, $businessID);
|
|
}
|
|
|
|
} else {
|
|
// New direct modifier — insert
|
|
$isDefault = (!empty($mod['isDefault'])) ? 1 : 0;
|
|
queryTimed("
|
|
INSERT INTO Items (
|
|
BusinessID, ParentItemID, Name, Price,
|
|
IsCheckedByDefault, SortOrder, IsActive, AddedOn,
|
|
RequiresChildSelection, MaxNumSelectionReq, CategoryID
|
|
) VALUES (?, ?, ?, ?, ?, ?, 1, NOW(), ?, ?, 0)
|
|
", [
|
|
$businessID, $itemID, $mod['name'], (float) ($mod['price'] ?? 0),
|
|
$isDefault, $modSortOrder, $requiresSelection, $maxSelections,
|
|
]);
|
|
|
|
$newModID = (int) lastInsertId();
|
|
if (!empty($mod['options']) && is_array($mod['options'])) {
|
|
saveOptionsRecursive($mod['options'], $newModID, $businessID);
|
|
}
|
|
}
|
|
$modSortOrder++;
|
|
}
|
|
}
|
|
$itemSortOrder++;
|
|
}
|
|
}
|
|
$catSortOrder++;
|
|
}
|
|
|
|
$db->commit();
|
|
|
|
jsonResponse(['OK' => true, 'SCHEMA' => $newSchemaActive ? 'unified' : 'legacy']);
|
|
|
|
} catch (Exception $e) {
|
|
try { $db->rollBack(); } catch (Exception $re) {}
|
|
jsonResponse(['OK' => false, 'ERROR' => $e->getMessage(), 'DETAIL' => '', 'TYPE' => '']);
|
|
}
|