Files
flixcooks-website/helpers.php
T

345 lines
11 KiB
PHP
Raw Normal View History

2026-03-22 22:16:44 +01:00
<?php
// Shared helper functions for the FlixCooks food blog.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/config.php';
2026-05-22 19:07:03 +02:00
function init_db() {
$pdo = get_db_connection();
if (!$pdo) return;
try {
$pdo->exec("CREATE TABLE IF NOT EXISTS recipes (
slug VARCHAR(255) PRIMARY KEY,
data JSONB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$stmt = $pdo->query("SELECT COUNT(*) FROM recipes");
if ($stmt && $stmt->fetchColumn() == 0) {
$path = __DIR__ . '/data/recipes.json';
if (file_exists($path)) {
$json = file_get_contents($path);
$data = json_decode($json, true);
if (is_array($data) && count($data) > 0) {
$insert = $pdo->prepare("INSERT INTO recipes (slug, data) VALUES (:slug, :data)");
foreach ($data as $recipe) {
if (isset($recipe['slug'])) {
$insert->execute([
'slug' => $recipe['slug'],
'data' => json_encode($recipe, JSON_UNESCAPED_UNICODE)
]);
}
}
}
}
}
2026-05-22 19:25:06 +02:00
} catch (\Exception $e) {
2026-05-22 19:07:03 +02:00
error_log("DB Init Error: " . $e->getMessage());
2026-05-22 16:16:34 +02:00
}
}
function load_recipes_local(): array {
2026-05-22 19:07:03 +02:00
$pdo = get_db_connection();
$data = [];
if (!$pdo) {
$path = __DIR__ . '/data/recipes.json';
if (file_exists($path)) {
$json = file_get_contents($path);
$data = json_decode($json, true) ?: [];
}
} else {
static $initialized = false;
if (!$initialized) {
init_db();
$initialized = true;
}
try {
$stmt = $pdo->query("SELECT data FROM recipes");
if ($stmt) {
while ($row = $stmt->fetch()) {
$recipe = json_decode($row['data'], true);
if (is_array($recipe)) {
$data[] = $recipe;
}
}
}
2026-05-22 19:25:06 +02:00
} catch (\Exception $e) {
2026-05-22 19:07:03 +02:00
error_log("Failed to load recipes from DB: " . $e->getMessage());
}
2026-03-22 22:16:44 +01:00
}
foreach ($data as &$recipe) {
if (!empty($recipe['hero']) && is_string($recipe['hero'])) {
$recipe['hero'] = normalize_asset_path($recipe['hero']);
}
}
unset($recipe);
return $data;
}
2026-05-22 16:16:34 +02:00
function load_recipes(): array {
2026-05-22 19:07:03 +02:00
return load_recipes_local();
2026-05-22 16:16:34 +02:00
}
function load_recipe_by_slug(string $slug): ?array {
2026-05-22 19:07:03 +02:00
return find_recipe_by_slug(load_recipes_local(), $slug);
2026-05-22 16:16:34 +02:00
}
2026-03-22 22:16:44 +01:00
function save_recipes(array $recipes): bool {
2026-05-22 19:07:03 +02:00
$pdo = get_db_connection();
2026-03-22 22:16:44 +01:00
$path = __DIR__ . '/data/recipes.json';
2026-05-22 19:07:03 +02:00
if (!$pdo) {
$json = json_encode($recipes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
return (bool) file_put_contents($path, $json);
}
try {
$pdo->beginTransaction();
$slugs = [];
$insert = $pdo->prepare("INSERT INTO recipes (slug, data) VALUES (:slug, :data) ON CONFLICT (slug) DO UPDATE SET data = EXCLUDED.data, updated_at = CURRENT_TIMESTAMP");
foreach ($recipes as $recipe) {
if (isset($recipe['slug'])) {
$slugs[] = $recipe['slug'];
$insert->execute([
'slug' => $recipe['slug'],
'data' => json_encode($recipe, JSON_UNESCAPED_UNICODE)
]);
}
}
if (!empty($slugs)) {
$placeholders = implode(',', array_fill(0, count($slugs), '?'));
$delete = $pdo->prepare("DELETE FROM recipes WHERE slug NOT IN ($placeholders)");
$delete->execute($slugs);
} else {
$pdo->exec("DELETE FROM recipes");
}
$pdo->commit();
// Also update local JSON as backup
$json = json_encode($recipes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
file_put_contents($path, $json);
return true;
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log("Failed to save recipes to DB: " . $e->getMessage());
return false;
}
2026-03-22 22:16:44 +01:00
}
/**
* Default settings for imprint and privacy pages.
*/
function default_site_settings(): array {
$today = date('Y-m-d');
return [
'imprint' => [
'de' => [
'owner_name' => 'FlixCooks (bitte anpassen)',
'address' => 'Straße Hausnummer, PLZ Ort, Österreich',
'email' => 'contact@flixcooks.at',
'phone' => '+43 660 0000000',
'legal_form' => 'Kleines Impressum; kein Firmenbucheintrag/UID hinterlegt.',
'business_purpose' => 'Foodblog & Rezeptmarketing.',
'wko_membership' => '',
'authority' => '',
'uid' => '',
'odr' => 'https://ec.europa.eu/consumers/odr',
'last_updated' => $today,
],
'en' => [
'owner_name' => 'FlixCooks (please update)',
'address' => 'Street number, ZIP City, Austria',
'email' => 'contact@flixcooks.at',
'phone' => '+43 660 0000000',
'legal_form' => 'Small website notice; no commercial register/UID provided.',
'business_purpose' => 'Food blog & recipe marketing.',
'wko_membership' => '',
'authority' => '',
'uid' => '',
'odr' => 'https://consumer-redress.ec.europa.eu/',
'last_updated' => $today,
],
],
'privacy' => [
'de' => [
'controller' => 'FlixCooks (bitte anpassen)',
'contact_email' => 'contact@flixcooks.at',
'contact_phone' => '+43 660 0000000',
'address' => 'Straße Hausnummer, PLZ Ort, Österreich',
'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Deutschland',
'log_retention_days' => 30,
'cookie_statement' => 'Keine Tracking- oder Marketing-Cookies; nur technisch notwendige.',
'purposes' => 'Betrieb und Bereitstellung der Website, Beantwortung von Kontaktanfragen.',
'last_updated' => $today,
],
'en' => [
'controller' => 'FlixCooks (please update)',
'contact_email' => 'contact@flixcooks.at',
'contact_phone' => '+43 660 0000000',
'address' => 'Street number, ZIP City, Austria',
'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany',
'log_retention_days' => 30,
'cookie_statement' => 'No tracking or marketing cookies; only technically necessary cookies.',
'purposes' => 'Operating and providing the website, responding to contact requests.',
'last_updated' => $today,
],
],
];
}
/**
* Load site-wide settings for legal pages, merging with defaults.
*/
function load_site_settings(): array {
$defaults = default_site_settings();
$path = __DIR__ . '/data/site.json';
if (!file_exists($path)) {
return $defaults;
}
$raw = file_get_contents($path);
$data = json_decode($raw, true);
if (!is_array($data)) {
return $defaults;
}
return array_replace_recursive($defaults, $data);
}
/**
* Persist site-wide settings for legal pages.
*/
function save_site_settings(array $settings): bool {
$path = __DIR__ . '/data/site.json';
$json = json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return (bool) file_put_contents($path, $json);
}
2026-03-22 22:16:44 +01:00
function slugify(string $text): string {
$text = strtolower($text);
$text = preg_replace('~[^a-z0-9]+~', '-', $text);
$text = trim($text, '-');
return $text ?: uniqid('recipe-');
}
function find_recipe_by_slug(array $recipes, string $slug): ?array {
foreach ($recipes as $recipe) {
if (($recipe['slug'] ?? '') === $slug) {
return $recipe;
}
}
return null;
}
function localize_recipe(array $recipe, string $lang): array {
$localized = $recipe;
$i18n = $recipe['i18n'][$lang] ?? ($recipe['i18n']['en'] ?? []);
foreach ($i18n as $key => $value) {
$localized[$key] = $value;
}
return $localized;
}
function localize_recipes(array $recipes, string $lang): array {
return array_map(function ($recipe) use ($lang) {
return localize_recipe($recipe, $lang);
}, $recipes);
}
function filter_recipes(array $recipes, ?string $query = null, ?string $tag = null, string $lang = 'en'): array {
$query = $query ? strtolower($query) : null;
$tag = $tag ? strtolower($tag) : null;
$localized = localize_recipes($recipes, $lang);
return array_values(array_filter($localized, function ($recipe) use ($query, $tag) {
$matchesQuery = true;
if ($query) {
$haystack = strtolower(($recipe['title'] ?? '') . ' ' . ($recipe['description'] ?? ''));
$matchesQuery = strpos($haystack, $query) !== false;
}
$matchesTag = true;
if ($tag) {
$tags = array_map('strtolower', $recipe['tags'] ?? []);
$matchesTag = in_array($tag, $tags, true);
}
return $matchesQuery && $matchesTag;
}));
}
function format_minutes(int $minutes): string {
if ($minutes < 60) {
return $minutes . ' min';
}
$hours = intdiv($minutes, 60);
$mins = $minutes % 60;
return $hours . ' hr' . ($hours > 1 ? 's ' : ' ') . ($mins ? $mins . ' min' : '');
}
function e(?string $value): string {
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function normalize_asset_path(string $path): string {
if (!str_starts_with($path, '/assets/')) {
return $path;
}
$absolute = __DIR__ . $path;
if (file_exists($absolute)) {
return $path;
}
$dir = dirname($absolute);
$target = basename($absolute);
if (!is_dir($dir)) {
return $path;
}
foreach (scandir($dir) ?: [] as $entry) {
if (strcasecmp($entry, $target) === 0) {
return rtrim(dirname($path), '/') . '/' . $entry;
}
}
return $path;
}
/**
* Build a URL to the current path with a specific language parameter, preserving other query params.
*/
function lang_url(string $lang): string {
$params = $_GET;
$params['lang'] = $lang;
$path = strtok($_SERVER['REQUEST_URI'], '?') ?: '/';
return $path . '?' . http_build_query($params);
}
// Fallback polyfills for environments without mbstring extension
if (!function_exists('mb_substr')) {
function mb_substr(string $string, int $start, ?int $length = null, ?string $encoding = null): string {
return substr($string, $start, $length ?? strlen($string));
}
}
if (!function_exists('mb_strlen')) {
function mb_strlen(string $string, ?string $encoding = null): int {
return strlen($string);
}
}