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.
289 lines
12 KiB
PHP
289 lines
12 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../helpers.php';
|
|
runAuth();
|
|
|
|
/**
|
|
* Analyze Menu for Modifier Patterns
|
|
*
|
|
* Parses item descriptions to detect modifier groups (size variants,
|
|
* protein choices, add-ons, etc.) and suggests templates.
|
|
*
|
|
* POST JSON: { categories: [{ name, categoryNote, items: [{ name, description }] }] }
|
|
*/
|
|
|
|
$response = ['OK' => false];
|
|
|
|
try {
|
|
$data = readJsonBody();
|
|
if (empty($data)) throw new Exception('No request body provided');
|
|
|
|
$categories = $data['categories'] ?? [];
|
|
|
|
$detectedTemplates = [];
|
|
$analyzedCategories = [];
|
|
|
|
foreach ($categories as $cat) {
|
|
$catName = $cat['name'] ?? '';
|
|
$catNote = $cat['categoryNote'] ?? '';
|
|
$items = $cat['items'] ?? [];
|
|
|
|
// Check for category-wide patterns in the note
|
|
$categoryTemplates = [];
|
|
|
|
// "Add to any salad/item" pattern
|
|
if (preg_match('/add\s+(?:the\s+following\s+)?to\s+any\s+(salad|item)/i', $catNote)) {
|
|
if (preg_match('/add[^:]+:\s*(.+)/i', $catNote, $addOnMatch)) {
|
|
$addOnText = $addOnMatch[1];
|
|
$addOnItems = array_map('trim', explode(',', $addOnText));
|
|
$templateID = 'cat_' . strtolower(preg_replace('/\W+/', '_', $catName)) . '_addons';
|
|
|
|
if (!isset($detectedTemplates[$templateID])) {
|
|
$options = [['name' => 'None', 'price' => 0, 'isDefault' => true]];
|
|
foreach ($addOnItems as $addon) {
|
|
$addon = trim($addon);
|
|
if (!empty($addon)) {
|
|
$options[] = ['name' => $addon, 'price' => 0, 'isDefault' => false];
|
|
}
|
|
}
|
|
$detectedTemplates[$templateID] = [
|
|
'id' => $templateID,
|
|
'name' => 'Add Protein',
|
|
'options' => $options,
|
|
'required' => false,
|
|
'maxSelections' => 1,
|
|
'detectedFrom' => "Category note: $catName",
|
|
'appliesTo' => [],
|
|
];
|
|
}
|
|
$categoryTemplates[] = $templateID;
|
|
}
|
|
}
|
|
|
|
// Analyze each item
|
|
$analyzedItems = [];
|
|
foreach ($items as $item) {
|
|
$itemName = $item['name'] ?? '';
|
|
$itemDesc = $item['description'] ?? '';
|
|
$itemMods = [];
|
|
|
|
// Apply category-wide templates
|
|
foreach ($categoryTemplates as $catTmpl) {
|
|
$itemMods[] = $catTmpl;
|
|
$detectedTemplates[$catTmpl]['appliesTo'][] = $itemName;
|
|
}
|
|
|
|
// Size patterns (small available)
|
|
if (preg_match('/\bsmall\s*(available|option)?\b/i', $itemDesc) ||
|
|
preg_match('/\bsmall\b/i', $itemName)) {
|
|
$templateID = 'size_regular_small';
|
|
if (!isset($detectedTemplates[$templateID])) {
|
|
$detectedTemplates[$templateID] = [
|
|
'id' => $templateID,
|
|
'name' => 'Size',
|
|
'options' => [
|
|
['name' => 'Regular', 'price' => 0, 'isDefault' => true],
|
|
['name' => 'Small', 'price' => 0, 'isDefault' => false],
|
|
],
|
|
'required' => true,
|
|
'maxSelections' => 1,
|
|
'detectedFrom' => "Pattern: 'Small available'",
|
|
'appliesTo' => [],
|
|
];
|
|
}
|
|
$itemMods[] = $templateID;
|
|
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
|
|
}
|
|
|
|
// Half order pattern
|
|
if (preg_match('/\b(1\/2|half)\s*(order)?\s*(available)?\b/i', $itemDesc)) {
|
|
$templateID = 'size_full_half';
|
|
if (!isset($detectedTemplates[$templateID])) {
|
|
$detectedTemplates[$templateID] = [
|
|
'id' => $templateID,
|
|
'name' => 'Size',
|
|
'options' => [
|
|
['name' => 'Full Order', 'price' => 0, 'isDefault' => true],
|
|
['name' => 'Half Order', 'price' => 0, 'isDefault' => false],
|
|
],
|
|
'required' => true,
|
|
'maxSelections' => 1,
|
|
'detectedFrom' => "Pattern: '1/2 order available'",
|
|
'appliesTo' => [],
|
|
];
|
|
}
|
|
$itemMods[] = $templateID;
|
|
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
|
|
}
|
|
|
|
// Boneless or bone in
|
|
if (preg_match('/\bboneless\s+(or|\/)\s*bone\s*-?\s*in\b/i', $itemDesc)) {
|
|
$templateID = 'wing_style';
|
|
if (!isset($detectedTemplates[$templateID])) {
|
|
$detectedTemplates[$templateID] = [
|
|
'id' => $templateID,
|
|
'name' => 'Style',
|
|
'options' => [
|
|
['name' => 'Bone-In', 'price' => 0, 'isDefault' => true],
|
|
['name' => 'Boneless', 'price' => 0, 'isDefault' => false],
|
|
],
|
|
'required' => true,
|
|
'maxSelections' => 1,
|
|
'detectedFrom' => "Pattern: 'Boneless or bone in'",
|
|
'appliesTo' => [],
|
|
];
|
|
}
|
|
$itemMods[] = $templateID;
|
|
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
|
|
}
|
|
|
|
// Protein add-on (With Chicken / With Steak or Shrimp)
|
|
if (preg_match('/with\s+chicken.*(?:steak|shrimp)/i', $itemDesc)) {
|
|
$templateID = 'protein_addon';
|
|
if (!isset($detectedTemplates[$templateID])) {
|
|
$detectedTemplates[$templateID] = [
|
|
'id' => $templateID,
|
|
'name' => 'Add Protein',
|
|
'options' => [
|
|
['name' => 'Plain', 'price' => 0, 'isDefault' => true],
|
|
['name' => 'With Chicken', 'price' => 0, 'isDefault' => false],
|
|
['name' => 'With Steak or Shrimp', 'price' => 0, 'isDefault' => false],
|
|
],
|
|
'required' => false,
|
|
'maxSelections' => 1,
|
|
'detectedFrom' => "Pattern: 'With Chicken / With Steak or Shrimp'",
|
|
'appliesTo' => [],
|
|
];
|
|
}
|
|
$itemMods[] = $templateID;
|
|
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
|
|
}
|
|
|
|
// "add X +$Y" pattern
|
|
if (preg_match_all('/add\s+(\w+(?:\s+(?:and\s+)?\w+)?)\s*[.\+]?\s*(\d+(?:\.\d{2})?)/i', $itemDesc, $priceMatches)) {
|
|
$templateID = 'addon_' . strtolower(preg_replace('/\W+/', '_', $itemName));
|
|
if (!isset($detectedTemplates[$templateID])) {
|
|
$detectedTemplates[$templateID] = [
|
|
'id' => $templateID,
|
|
'name' => 'Add-ons',
|
|
'options' => [],
|
|
'required' => false,
|
|
'maxSelections' => 0,
|
|
'detectedFrom' => "Pattern: 'Add X +\$Y' in $itemName",
|
|
'appliesTo' => [],
|
|
'needsPricing' => true,
|
|
];
|
|
}
|
|
$itemMods[] = $templateID;
|
|
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
|
|
}
|
|
|
|
// "with guacamole" pattern
|
|
if (preg_match('/\bwith\s+guacamole\b/i', $itemDesc)) {
|
|
$templateID = 'addon_guacamole';
|
|
if (!isset($detectedTemplates[$templateID])) {
|
|
$detectedTemplates[$templateID] = [
|
|
'id' => $templateID,
|
|
'name' => 'Add Guacamole',
|
|
'options' => [
|
|
['name' => 'No Guacamole', 'price' => 0, 'isDefault' => true],
|
|
['name' => 'Add Guacamole', 'price' => 0, 'isDefault' => false],
|
|
],
|
|
'required' => false,
|
|
'maxSelections' => 1,
|
|
'detectedFrom' => "Pattern: 'With guacamole'",
|
|
'appliesTo' => [],
|
|
'needsPricing' => true,
|
|
];
|
|
}
|
|
$itemMods[] = $templateID;
|
|
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
|
|
}
|
|
|
|
$analyzedItems[] = [
|
|
'name' => $itemName,
|
|
'description' => $itemDesc,
|
|
'price' => $item['price'] ?? 0,
|
|
'detectedModifiers' => $itemMods,
|
|
'originalDescription' => $itemDesc,
|
|
];
|
|
}
|
|
|
|
$analyzedCategories[] = [
|
|
'name' => $catName,
|
|
'categoryNote' => $catNote,
|
|
'items' => $analyzedItems,
|
|
'categoryWideTemplates' => $categoryTemplates,
|
|
];
|
|
}
|
|
|
|
// Build template array sorted by usage count
|
|
$templateArray = array_values($detectedTemplates);
|
|
foreach ($templateArray as &$tmpl) {
|
|
$tmpl['usageCount'] = count($tmpl['appliesTo']);
|
|
}
|
|
unset($tmpl);
|
|
usort($templateArray, fn($a, $b) => $b['usageCount'] - $a['usageCount']);
|
|
|
|
// Generate questions
|
|
$questions = [];
|
|
|
|
if (!empty($templateArray)) {
|
|
$questions[] = [
|
|
'type' => 'confirm_templates',
|
|
'question' => 'I detected these modifier groups. Are they correct?',
|
|
'templates' => $templateArray,
|
|
];
|
|
}
|
|
|
|
$itemsNeedingPrices = [];
|
|
foreach ($analyzedCategories as $cat) {
|
|
foreach ($cat['items'] as $item) {
|
|
if (($item['price'] ?? 0) == 0) {
|
|
$itemsNeedingPrices[] = ['category' => $cat['name'], 'item' => $item['name']];
|
|
}
|
|
}
|
|
}
|
|
if (!empty($itemsNeedingPrices)) {
|
|
$questions[] = ['type' => 'pricing_needed', 'question' => 'These items need prices:', 'items' => $itemsNeedingPrices];
|
|
}
|
|
|
|
$templatesNeedingPrices = [];
|
|
foreach ($templateArray as $tmpl) {
|
|
if (!empty($tmpl['needsPricing'])) {
|
|
$templatesNeedingPrices[] = [
|
|
'templateId' => $tmpl['id'],
|
|
'templateName' => $tmpl['name'],
|
|
'appliesTo' => $tmpl['appliesTo'],
|
|
];
|
|
}
|
|
}
|
|
if (!empty($templatesNeedingPrices)) {
|
|
$questions[] = ['type' => 'modifier_pricing_needed', 'question' => 'These add-on options need prices:', 'templates' => $templatesNeedingPrices];
|
|
}
|
|
|
|
// Count totals
|
|
$totalItems = 0;
|
|
$totalLinks = 0;
|
|
foreach ($analyzedCategories as $cat) {
|
|
$totalItems += count($cat['items']);
|
|
foreach ($cat['items'] as $item) {
|
|
$totalLinks += count($item['detectedModifiers']);
|
|
}
|
|
}
|
|
|
|
$response['OK'] = true;
|
|
$response['analyzedMenu'] = [
|
|
'categories' => $analyzedCategories,
|
|
'detectedTemplates' => $templateArray,
|
|
'totalItems' => $totalItems,
|
|
'totalTemplates' => count($templateArray),
|
|
'totalModifierLinks' => $totalLinks,
|
|
];
|
|
$response['questions'] = $questions;
|
|
$response['summary'] = "Analyzed $totalItems items, detected " . count($templateArray) . " modifier templates with $totalLinks links";
|
|
|
|
} catch (Exception $e) {
|
|
$response['error'] = $e->getMessage();
|
|
}
|
|
|
|
jsonResponse($response);
|