152 lines
7.2 KiB
PHP
152 lines
7.2 KiB
PHP
<?php
|
||
/* ── Auth ─────────────────────────────────────────────── */
|
||
function uid(): int { return (int)($_SESSION['uid'] ?? 0); }
|
||
function uname(): string{ return (string)($_SESSION['uname'] ?? ''); }
|
||
function authed(): bool { return uid() > 0; }
|
||
function isAdmin(): bool{ return !empty($_SESSION['admin']); }
|
||
|
||
function requireLogin(string $back = ''): void {
|
||
if (!authed()) {
|
||
flash('Please log in to continue.', 'warning');
|
||
go('/login.php?next='.urlencode($back ?: $_SERVER['REQUEST_URI']));
|
||
}
|
||
}
|
||
function requireAdmin(): void {
|
||
if (!isAdmin()) { flash('Access denied.', 'error'); go('/index.php'); }
|
||
}
|
||
function loginUser(array $u): void {
|
||
session_regenerate_id(true);
|
||
$_SESSION['uid'] = $u['id'];
|
||
$_SESSION['uname'] = $u['username'];
|
||
$_SESSION['admin'] = (bool)$u['is_admin'];
|
||
qrun("UPDATE users SET last_login=datetime('now') WHERE id=?", [$u['id']]);
|
||
}
|
||
function logoutUser(): void {
|
||
session_unset(); session_destroy();
|
||
session_start(); session_regenerate_id(true);
|
||
}
|
||
|
||
/* ── Flash ────────────────────────────────────────────── */
|
||
function flash(string $msg, string $type = 'info'): void { $_SESSION['_flash'][] = [$type, $msg]; }
|
||
function getFlashes(): array { $f = $_SESSION['_flash'] ?? []; unset($_SESSION['_flash']); return $f; }
|
||
|
||
/* ── Output ───────────────────────────────────────────── */
|
||
function e(mixed $v): string { return htmlspecialchars((string)($v ?? ''), ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'); }
|
||
function go(string $url): never { header('Location:'.$url); exit; }
|
||
|
||
/* ── Input ────────────────────────────────────────────── */
|
||
function p(string $k, mixed $d = ''): mixed { return $_POST[$k] ?? $d; }
|
||
function g(string $k, mixed $d = ''): mixed { return $_GET[$k] ?? $d; }
|
||
function iget(string $k): int { return (int)($_POST[$k] ?? $_GET[$k] ?? 0); }
|
||
function ps(string $k): string { return trim((string)($_POST[$k] ?? '')); }
|
||
function gs(string $k): string { return trim((string)($_GET[$k] ?? '')); }
|
||
function isPost(): bool { return strtoupper($_SERVER['REQUEST_METHOD']) === 'POST'; }
|
||
|
||
/* ── CSRF ─────────────────────────────────────────────── */
|
||
function csrf(): string {
|
||
if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(24));
|
||
return $_SESSION['csrf'];
|
||
}
|
||
function csrfField(): string { return '<input type="hidden" name="_csrf" value="'.e(csrf()).'">'; }
|
||
function csrfCheck(): void {
|
||
if (!hash_equals(csrf(), p('_csrf', ''))) { http_response_code(403); die('Invalid CSRF token.'); }
|
||
}
|
||
|
||
/* ── Settings ─────────────────────────────────────────── */
|
||
function setting(string $k, string $def = ''): string {
|
||
$v = qval("SELECT value FROM settings WHERE key=?", [$k]);
|
||
return ($v !== false && $v !== null) ? (string)$v : $def;
|
||
}
|
||
function setSetting(string $k, string $v): void {
|
||
qrun("INSERT INTO settings(key,value)VALUES(?,?)ON CONFLICT(key)DO UPDATE SET value=excluded.value", [$k,$v]);
|
||
}
|
||
|
||
/* ── Ownership ────────────────────────────────────────── */
|
||
function owns(int $bizId): bool {
|
||
if (isAdmin()) return true;
|
||
if (!authed()) return false;
|
||
return (bool)qval("SELECT 1 FROM business_owners WHERE user_id=? AND business_id=?", [uid(),$bizId]);
|
||
}
|
||
|
||
/* ── Stars ────────────────────────────────────────────── */
|
||
function starDisplay(float $avg): string {
|
||
$r = (int)round($avg);
|
||
$out = '<span class="stars">';
|
||
for ($i = 1; $i <= 5; $i++) $out .= '<span class="star'.($i<=$r?' on':'').'">★</span>';
|
||
return $out.'</span>';
|
||
}
|
||
function starPicker(string $name = 'rating', int $sel = 0): string {
|
||
$out = '<div class="star-picker">';
|
||
for ($i = 5; $i >= 1; $i--) {
|
||
$chk = $sel === $i ? 'checked' : '';
|
||
$out .= '<input type="radio" id="sr'.$i.'" name="'.$name.'" value="'.$i.'" '.$chk.'>';
|
||
$out .= '<label for="sr'.$i.'">★</label>';
|
||
}
|
||
return $out.'</div>';
|
||
}
|
||
|
||
/* ── Date / Money ─────────────────────────────────────── */
|
||
function ago(mixed $dt): string {
|
||
if ($dt === null || $dt === '' || $dt === false) return '';
|
||
$ts = is_int($dt) ? $dt : @strtotime((string)$dt);
|
||
if (!$ts) return '';
|
||
$d = time() - $ts;
|
||
if ($d < 60) return 'Just now';
|
||
if ($d < 3600) return floor($d / 60).'m ago';
|
||
if ($d < 86400) return floor($d / 3600).'h ago';
|
||
if ($d < 604800) return floor($d / 86400).'d ago';
|
||
return date('M j, Y', $ts);
|
||
}
|
||
function fdate(mixed $dt, string $fmt = 'M j, Y'): string {
|
||
if ($dt === null || $dt === '' || $dt === false) return '';
|
||
$ts = is_int($dt) ? $dt : @strtotime((string)$dt);
|
||
return $ts ? date($fmt, $ts) : '';
|
||
}
|
||
function money(float $n): string { return '$'.number_format($n, 2); }
|
||
|
||
/* ── Misc ─────────────────────────────────────────────── */
|
||
function alertIcon(mixed $t): string {
|
||
return match((string)($t ?? '')) {
|
||
'news' => '📰',
|
||
'event' => '🎉',
|
||
'warning' => '⚠️',
|
||
'sale' => '🏷️',
|
||
default => 'ℹ️',
|
||
};
|
||
}
|
||
|
||
function parseHours(mixed $json): array {
|
||
$s = (string)($json ?? '');
|
||
if ($s === '' || $s === '[]' || $s === 'null') return [];
|
||
$decoded = json_decode($s, true);
|
||
if (!is_array($decoded)) return [];
|
||
// Must be an object (associative), not a plain list
|
||
foreach (array_keys($decoded) as $key) {
|
||
if (!is_string($key)) return [];
|
||
}
|
||
return $decoded;
|
||
}
|
||
|
||
function makeSlug(string $s): string {
|
||
return trim(preg_replace('/[^a-z0-9]+/', '-', strtolower($s)), '-');
|
||
}
|
||
function uniqueSlug(string $base): string {
|
||
$slug = makeSlug($base); $i = 0;
|
||
while (qval("SELECT id FROM businesses WHERE slug=?", [$slug.($i ? "-$i" : "")])) $i++;
|
||
return $slug . ($i ? "-$i" : "");
|
||
}
|
||
|
||
function businessVideoProvider(string $url): string {
|
||
$host = strtolower((string)(parse_url(trim($url), PHP_URL_HOST) ?? ''));
|
||
$host = preg_replace('/^www\./', '', $host);
|
||
if ($host === 'youtu.be' || $host === 'youtube.com' || str_ends_with($host, '.youtube.com') || $host === 'youtube-nocookie.com' || str_ends_with($host, '.youtube-nocookie.com')) return 'YouTube';
|
||
if ($host === 'vimeo.com' || str_ends_with($host, '.vimeo.com')) return 'Vimeo';
|
||
return '';
|
||
}
|
||
function validBusinessVideoUrl(string $url): bool {
|
||
$url = trim($url);
|
||
if ($url === '') return true;
|
||
$scheme = strtolower((string)(parse_url($url, PHP_URL_SCHEME) ?? ''));
|
||
return in_array($scheme, ['http','https'], true) && businessVideoProvider($url) !== '';
|
||
}
|