familyHub/includes/meal_helpers.php

299 lines
8.3 KiB
PHP

<?php
require_once __DIR__ . '/chore_helpers.php';
require_once __DIR__ . '/grocery_helpers.php';
const MEAL_SLOT_BREAKFAST = 'breakfast';
const MEAL_SLOT_LUNCH = 'lunch';
const MEAL_SLOT_DINNER = 'dinner';
/** @return array<int, string> */
function mealSlotTypes(): array {
return [MEAL_SLOT_BREAKFAST, MEAL_SLOT_LUNCH, MEAL_SLOT_DINNER];
}
function mealCurrentWeekStart(): string {
try {
$d = new DateTimeImmutable('monday this week');
return $d->format('Y-m-d');
} catch (Exception $e) {
return gmdate('Y-m-d');
}
}
/**
* @return array<string, array<string, string|null>>
*/
function mealDefaultEmptySlots(): array {
$slots = [];
for ($i = 0; $i < 7; $i++) {
$key = (string) $i;
$slots[$key] = [
MEAL_SLOT_BREAKFAST => null,
MEAL_SLOT_LUNCH => null,
MEAL_SLOT_DINNER => null,
];
}
return $slots;
}
/**
* @param mixed $raw
* @return array{weekStart: string, slots: array<string, array<string, string|null>>}
*/
function normalizeMealPlan($raw): array {
$base = [
'weekStart' => mealCurrentWeekStart(),
'slots' => mealDefaultEmptySlots(),
];
if (!is_array($raw)) {
return $base;
}
$ws = isset($raw['weekStart']) ? trim((string) $raw['weekStart']) : '';
if ($ws !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $ws)) {
$base['weekStart'] = $ws;
}
$slotsIn = $raw['slots'] ?? [];
if (is_array($slotsIn)) {
for ($i = 0; $i < 7; $i++) {
$key = (string) $i;
$day = $slotsIn[$key] ?? $slotsIn[$i] ?? [];
if (!is_array($day)) {
continue;
}
foreach (mealSlotTypes() as $mt) {
$mid = $day[$mt] ?? null;
if ($mid === null || $mid === '') {
$base['slots'][$key][$mt] = null;
} else {
$base['slots'][$key][$mt] = is_string($mid) ? $mid : null;
}
}
}
}
return $base;
}
function loadMealPlan(): array {
return normalizeMealPlan(readJsonFile('meal_plans.json'));
}
/**
* @param mixed $raw
* @return array<int, array<string, mixed>>
*/
function normalizeMealsList($raw): array {
if (!is_array($raw) || !array_is_list($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (is_array($row) && !empty($row['id']) && is_string($row['id'])) {
$out[] = $row;
}
}
return $out;
}
/**
* @param array<string, mixed> $m
* @return array<string, mixed>
*/
function normalizeMealRow(array $m): array {
if (isset($m['name']) && !isset($m['title'])) {
$m['title'] = trim((string) $m['name']);
}
$m['title'] = trim((string) ($m['title'] ?? ''));
$m['image'] = trim((string) ($m['image'] ?? ''));
$m['description'] = trim((string) ($m['description'] ?? ''));
$m['directions'] = trim((string) ($m['directions'] ?? ''));
if (!isset($m['lists']) || !is_array($m['lists'])) {
$m['lists'] = [];
}
$m['lists'] = normalizeChoreLists($m['lists']);
$tags = $m['tags'] ?? [];
if (!is_array($tags)) {
$tags = [];
}
$cleanTags = [];
foreach ($tags as $t) {
$t = strtolower(trim((string) $t));
if (in_array($t, ['breakfast', 'lunch', 'dinner'], true)) {
$cleanTags[] = $t;
}
}
if (isset($m['type']) && is_string($m['type']) && count($cleanTags) === 0) {
$tt = strtolower(trim($m['type']));
if (in_array($tt, ['breakfast', 'lunch', 'dinner'], true)) {
$cleanTags[] = $tt;
}
}
$m['tags'] = array_values(array_unique($cleanTags));
$ing = $m['ingredients'] ?? [];
if (!is_array($ing)) {
$ing = [];
}
$ingOut = [];
foreach ($ing as $line) {
$s = trim((string) $line);
if ($s !== '') {
$ingOut[] = $s;
}
}
$m['ingredients'] = $ingOut;
$items = $m['items'] ?? [];
if (!is_array($items)) {
$items = [];
}
$itemOut = [];
foreach ($items as $it) {
if (!is_array($it)) {
continue;
}
$nm = trim((string) ($it['name'] ?? ''));
if ($nm === '') {
continue;
}
$itemOut[] = [
'name' => $nm,
'storeId' => trim((string) ($it['storeId'] ?? '')),
'description' => trim((string) ($it['description'] ?? '')),
'size' => trim((string) ($it['size'] ?? '')),
'quantity' => trim((string) ($it['quantity'] ?? '1')) ?: '1',
'price' => trim((string) ($it['price'] ?? '')),
'image' => trim((string) ($it['image'] ?? '')),
];
}
$m['items'] = $itemOut;
if (!isset($m['author_id'])) {
$m['author_id'] = '';
}
return $m;
}
/**
* @param array<int, array<string, mixed>> $meals
*/
function findMealById(array $meals, string $id): ?array {
foreach ($meals as $m) {
if (($m['id'] ?? '') === $id) {
return $m;
}
}
return null;
}
/**
* @param array<int, array<string, mixed>> $meals
*/
function findMealIndexById(array $meals, string $id): ?int {
foreach ($meals as $i => $m) {
if (($m['id'] ?? '') === $id) {
return $i;
}
}
return null;
}
/**
* Migrate legacy meals (name, date, type) into library format.
*
* @param array<int, array<string, mixed>> $meals
* @return array<int, array<string, mixed>>
*/
function migrateLegacyMealsList(array $meals): array {
$out = [];
foreach ($meals as $m) {
if (!is_array($m)) {
continue;
}
if (isset($m['title']) && isset($m['tags'])) {
$out[] = normalizeMealRow($m);
continue;
}
$title = trim((string) ($m['title'] ?? $m['name'] ?? ''));
if ($title === '') {
continue;
}
$tags = [];
if (!empty($m['type'])) {
$tt = strtolower(trim((string) $m['type']));
if (in_array($tt, ['breakfast', 'lunch', 'dinner'], true)) {
$tags[] = $tt;
}
}
$out[] = normalizeMealRow([
'id' => !empty($m['id']) && is_string($m['id']) ? $m['id'] : bin2hex(random_bytes(8)),
'title' => $title,
'tags' => $tags,
'description' => isset($m['date']) ? 'Legacy date: ' . (string) $m['date'] : '',
'image' => '',
'directions' => '',
'lists' => [],
'ingredients' => [],
'items' => [],
'author_id' => '',
]);
}
return $out;
}
/**
* Push meal shopping items to grocery lists as pending_review.
*
* @param array<int, array<string, mixed>> $stores
* @return int number of lines added
*/
function pushMealItemsToGrocery(array $meal, array $stores): int {
$items = $meal['items'] ?? [];
if (!is_array($items) || count($items) === 0) {
return 0;
}
$mealId = (string) ($meal['id'] ?? '');
$mealTitle = (string) ($meal['title'] ?? '');
$defaultStore = groceryFirstStoreId($stores);
if ($defaultStore === '') {
return 0;
}
$added = 0;
foreach ($items as $row) {
if (!is_array($row)) {
continue;
}
$name = trim((string) ($row['name'] ?? ''));
if ($name === '') {
continue;
}
$sid = trim((string) ($row['storeId'] ?? ''));
if ($sid === '' || findStoreById($stores, $sid) === null) {
$sid = $defaultStore;
}
$res = groceryAppendShoppingLine(
$stores,
$sid,
$name,
trim((string) ($row['description'] ?? '')),
trim((string) ($row['size'] ?? '')),
trim((string) ($row['quantity'] ?? '1')) ?: '1',
trim((string) ($row['price'] ?? '')),
trim((string) ($row['image'] ?? '')),
'meal_plan',
0,
$mealId !== '' ? $mealId : null,
$mealTitle !== '' ? $mealTitle : null
);
if ($res['ok']) {
$added++;
}
}
return $added;
}
function mealDayShortLabel(string $weekStart, int $offset): string {
$ts = strtotime($weekStart . ' UTC +' . $offset . ' days');
if ($ts === false) {
return (string) $offset;
}
return gmdate('D n/j', $ts);
}