Files
MyKeyser/includes/functions.php
T
Ty Clifford 354bb4255d - Rebuilt the CLI importer to use the official Facebook Graph API instead of HTML scraping.
Main changes:
Replaced 
[scripts/facebook-events-import.php](/Users/tyemeclifford/Documents/GH/MyKeyser/scripts/facebook-events-import.php) 
with a Graph API client.
It now supports --page-id, --pages-file, --event-id, --events-file, and 
optional area discovery via --discover-pages --center=LAT,LNG 
--query=....
Uses FACEBOOK_GRAPH_ACCESS_TOKEN or --access-token.
Pulls Graph fields for title, description, start/end time, 
place/location, ticket URI, and cover.source for the main photo.
Keeps the existing staged review/import workflow in 
/admin/event_imports.php.

- Implemented event detail pages and clickable event cards.
What changed:
Added [event.php](/Users/tyemeclifford/Documents/GH/MyKeyser/event.php) 
for individual event pages.
Made event cards clickable from 
[events.php](/Users/tyemeclifford/Documents/GH/MyKeyser/events.php), 
[index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/index.php), and 
legacy 
[2index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/2index.php).
Added large event photo display plus lightbox enlargement in 
[assets/css/style.css](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/css/style.css) 
and 
[assets/js/app.js](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/js/app.js).
Added eventUrl() and externalHref() helpers in 
[includes/functions.php](/Users/tyemeclifford/Documents/GH/MyKeyser/includes/functions.php).
Added copy-event-link support on event detail pages.
Updated README to list the new route.
2026-06-19 18:07:45 -04:00

505 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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((string)($_SERVER['REQUEST_METHOD'] ?? 'GET')) === '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) !== '';
}
/* ── Uploads / Images ─────────────────────────────────── */
function uploadRoot(): string { return dirname(__DIR__).'/uploads'; }
function uploadPublicPath(string $subdir, string $filename): string {
return '/uploads/'.trim($subdir, '/').'/'.$filename;
}
function cleanUploadSubdir(string $subdir): string {
$subdir = trim(str_replace('\\', '/', $subdir), '/');
return preg_replace('/[^a-zA-Z0-9_\/-]+/', '', $subdir) ?: 'misc';
}
function ensureUploadDir(string $subdir): string {
$subdir = cleanUploadSubdir($subdir);
$dir = uploadRoot().'/'.$subdir;
if (!is_dir($dir)) mkdir($dir, 0775, true);
return $dir;
}
function mbToBytes(mixed $mb, float $default = 5): int {
$n = (float)$mb;
if ($n <= 0) $n = $default;
return max(1, (int)round($n * 1024 * 1024));
}
function uploadAllowedExts(string $csv, array $fallback): array {
$raw = array_filter(array_map('trim', explode(',', strtolower($csv))));
$exts = $raw ?: $fallback;
return array_values(array_unique(array_map(fn($e) => ltrim($e, '.'), $exts)));
}
function imageExts(): array { return ['jpg','jpeg','png','gif','webp']; }
function isImageExt(string $ext): bool { return in_array(strtolower($ext), imageExts(), true); }
function isImagePath(string $path): bool {
return isImageExt(strtolower(pathinfo(parse_url($path, PHP_URL_PATH) ?: $path, PATHINFO_EXTENSION)));
}
function uploadErrorText(int $code): string {
return match($code) {
UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'The uploaded file is too large.',
UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially received.',
UPLOAD_ERR_NO_TMP_DIR => 'The server is missing a temporary upload directory.',
UPLOAD_ERR_CANT_WRITE => 'The server could not write the uploaded file.',
UPLOAD_ERR_EXTENSION => 'A server extension stopped the upload.',
default => 'The file could not be uploaded.',
};
}
function resizeImageFile(string $path, int $thresholdBytes, int $maxWidth = 1600, int $quality = 82): bool {
if ($thresholdBytes <= 0 || !file_exists($path) || filesize($path) <= $thresholdBytes) return false;
if (!function_exists('getimagesize') || !function_exists('imagecreatetruecolor')) return false;
$info = @getimagesize($path);
if (!$info || empty($info[0]) || empty($info[1]) || empty($info['mime'])) return false;
[$w, $h] = $info;
$mime = $info['mime'];
$loader = match($mime) {
'image/jpeg' => 'imagecreatefromjpeg',
'image/png' => 'imagecreatefrompng',
'image/gif' => 'imagecreatefromgif',
'image/webp' => 'imagecreatefromwebp',
default => '',
};
if ($loader === '' || !function_exists($loader)) return false;
$src = @$loader($path);
if (!$src) return false;
$scale = min(1, $maxWidth / max($w, $h));
$nw = max(1, (int)round($w * $scale));
$nh = max(1, (int)round($h * $scale));
$dst = imagecreatetruecolor($nw, $nh);
if ($mime === 'image/png' || $mime === 'image/webp') {
imagealphablending($dst, false);
imagesavealpha($dst, true);
$transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
imagefilledrectangle($dst, 0, 0, $nw, $nh, $transparent);
}
imagecopyresampled($dst, $src, 0, 0, 0, 0, $nw, $nh, $w, $h);
$quality = max(40, min(95, $quality));
$ok = match($mime) {
'image/jpeg' => imagejpeg($dst, $path, $quality),
'image/png' => imagepng($dst, $path, 6),
'image/gif' => imagegif($dst, $path),
'image/webp' => function_exists('imagewebp') ? imagewebp($dst, $path, $quality) : false,
default => false,
};
imagedestroy($src);
imagedestroy($dst);
if ($ok) @chmod($path, 0664);
return (bool)$ok;
}
function normalizeUploadFiles(string $field): array {
if (empty($_FILES[$field])) return [];
$file = $_FILES[$field];
if (!is_array($file['name'])) return [$file];
$out = [];
foreach ($file['name'] as $i => $name) {
$out[] = [
'name' => $name,
'type' => $file['type'][$i] ?? '',
'tmp_name' => $file['tmp_name'][$i] ?? '',
'error' => $file['error'][$i] ?? UPLOAD_ERR_NO_FILE,
'size' => $file['size'][$i] ?? 0,
];
}
return $out;
}
function storeUploadedFile(array $file, string $subdir, array $allowedExts, int $maxBytes, array $opts = []): array {
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) return ['ok'=>true, 'path'=>''];
if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) return ['ok'=>false, 'error'=>uploadErrorText((int)$file['error'])];
if ((int)($file['size'] ?? 0) > $maxBytes) return ['ok'=>false, 'error'=>'The uploaded file exceeds the configured size limit.'];
$original = (string)($file['name'] ?? 'upload');
$ext = strtolower(pathinfo($original, PATHINFO_EXTENSION));
$allowedExts = array_map('strtolower', $allowedExts);
if ($ext === '' || !in_array($ext, $allowedExts, true)) {
return ['ok'=>false, 'error'=>'This file type is not allowed.'];
}
if (!empty($opts['image_only']) && !isImageExt($ext)) return ['ok'=>false, 'error'=>'Please upload an image file.'];
if (!empty($opts['image_only']) && function_exists('getimagesize') && !@getimagesize((string)$file['tmp_name'])) {
return ['ok'=>false, 'error'=>'The selected file is not a valid image.'];
}
$dir = ensureUploadDir($subdir);
$filename = date('YmdHis').'-'.bin2hex(random_bytes(5)).'.'.$ext;
$dest = $dir.'/'.$filename;
$tmp = (string)$file['tmp_name'];
$moved = is_uploaded_file($tmp) ? move_uploaded_file($tmp, $dest) : @rename($tmp, $dest);
if (!$moved) return ['ok'=>false, 'error'=>'The uploaded file could not be saved.'];
@chmod($dest, 0664);
if (isImageExt($ext)) {
resizeImageFile(
$dest,
(int)($opts['resize_over_bytes'] ?? 0),
(int)($opts['image_max_width'] ?? 1600),
(int)($opts['image_quality'] ?? 82)
);
}
$mime = '';
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if ($finfo) {
$mime = (string)finfo_file($finfo, $dest);
finfo_close($finfo);
}
}
return [
'ok' => true,
'path' => uploadPublicPath($subdir, $filename),
'name' => $original,
'mime' => $mime,
'size' => file_exists($dest) ? filesize($dest) : (int)($file['size'] ?? 0),
];
}
function storeSingleUpload(string $field, string $subdir, array $allowedExts, int $maxBytes, array $opts = []): array {
$files = normalizeUploadFiles($field);
return $files ? storeUploadedFile($files[0], $subdir, $allowedExts, $maxBytes, $opts) : ['ok'=>true, 'path'=>''];
}
function storeMultipleUploads(string $field, string $subdir, array $allowedExts, int $maxBytes, array $opts = []): array {
$saved = [];
foreach (normalizeUploadFiles($field) as $file) {
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) continue;
$res = storeUploadedFile($file, $subdir, $allowedExts, $maxBytes, $opts);
if (!$res['ok']) return ['ok'=>false, 'error'=>$res['error'] ?? 'Upload failed.', 'files'=>$saved];
if (!empty($res['path'])) $saved[] = $res;
}
return ['ok'=>true, 'files'=>$saved];
}
function formatBytes(int $bytes): string {
if ($bytes >= 1048576) return number_format($bytes / 1048576, 1).' MB';
if ($bytes >= 1024) return number_format($bytes / 1024, 1).' KB';
return $bytes.' B';
}
function uniqueForumSlug(string $title, int $id): string {
$base = makeSlug($title) ?: 'topic';
return $base.'-'.$id;
}
function forumTopicUrl(array $topic): string {
return '/forum/'.rawurlencode((string)$topic['slug']).'/';
}
function eventUrl(array $event): string {
return '/event.php?id='.(int)($event['id'] ?? 0);
}
function externalHref(string $url): string {
$url = trim($url);
if ($url === '') return '';
if (preg_match('~^https?://~i', $url)) return $url;
return 'https://'.$url;
}
/* ── Email verification / Mailgun ─────────────────────── */
function requestBaseUrl(): string {
$configured = trim(setting('site_base_url', ''));
if ($configured !== '') return rtrim($configured, '/');
$host = (string)($_SERVER['HTTP_HOST'] ?? '');
if ($host === '') return '';
$https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (($_SERVER['SERVER_PORT'] ?? '') === '443');
return ($https ? 'https' : 'http').'://'.$host;
}
function absoluteUrl(string $path): string {
$base = requestBaseUrl();
if ($base === '') return $path;
return rtrim($base, '/').'/'.ltrim($path, '/');
}
function mailgunEndpoint(): string {
$region = strtolower(setting('mailgun_region', 'us'));
$base = $region === 'eu' ? 'https://api.eu.mailgun.net' : 'https://api.mailgun.net';
return $base.'/v3/'.rawurlencode(setting('mailgun_domain', '')).'/messages';
}
function mailgunConfigured(): bool {
return setting('mailgun_domain', '') !== '' && setting('mailgun_api_key', '') !== '';
}
function emailFromAddress(): string {
$from = trim(setting('mailgun_from_email', ''));
if ($from !== '') return $from;
$domain = setting('mailgun_domain', '');
return $domain !== '' ? 'postmaster@'.$domain : '';
}
function formattedFromAddress(): string {
$name = trim(setting('mailgun_from_name', 'My Keyser'));
$email = emailFromAddress();
return $name !== '' ? "$name <$email>" : $email;
}
function createEmailVerificationToken(int $userId): string {
qrun("UPDATE email_verification_tokens SET used_at=datetime('now') WHERE user_id=? AND purpose='email_verify' AND used_at IS NULL", [$userId]);
$token = bin2hex(random_bytes(32));
qrun(
"INSERT INTO email_verification_tokens(user_id,token_hash,expires_at)VALUES(?,?,datetime('now','+24 hours'))",
[$userId, hash('sha256', $token)]
);
return $token;
}
function mailgunSend(string $to, string $subject, string $text, string $html = ''): array {
if (!mailgunConfigured()) return [false, 'Mailgun domain and API key are required.'];
$from = formattedFromAddress();
if ($from === '' || !filter_var(emailFromAddress(), FILTER_VALIDATE_EMAIL)) {
return [false, 'A valid Mailgun from email is required.'];
}
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) return [false, 'Recipient email is invalid.'];
$fields = [
'from' => $from,
'to' => $to,
'subject' => $subject,
'text' => $text,
];
if (setting('mailgun_send_mode', 'html') === 'html' && $html !== '') {
$fields['html'] = $html;
}
$endpoint = mailgunEndpoint();
$apiKey = setting('mailgun_api_key', '');
if (function_exists('curl_init')) {
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $fields,
CURLOPT_USERPWD => 'api:'.$apiKey,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
]);
$body = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($errno) return [false, $error ?: 'Mailgun request failed.'];
if ($status < 200 || $status >= 300) return [false, 'Mailgun returned HTTP '.$status.': '.substr((string)$body, 0, 180)];
return [true, ''];
}
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Authorization: Basic ".base64_encode('api:'.$apiKey)."\r\n".
"Content-Type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query($fields),
'timeout' => 20,
'ignore_errors' => true,
],
]);
$body = @file_get_contents($endpoint, false, $context);
$statusLine = $http_response_header[0] ?? '';
if (!preg_match('/\s(2\d\d)\s/', $statusLine)) {
return [false, trim(($statusLine ?: 'Mailgun request failed.').' '.substr((string)$body, 0, 180))];
}
return [true, ''];
}
function emailShellHtml(string $title, string $intro, string $bodyHtml, string $buttonText = '', string $buttonUrl = ''): string {
$site = e(setting('site_name', 'My Keyser'));
$titleEsc = e($title);
$introEsc = e($intro);
$button = '';
if ($buttonText !== '' && $buttonUrl !== '') {
$button = '<tr><td style="padding:22px 0 6px"><a href="'.e($buttonUrl).'" style="display:inline-block;background:linear-gradient(135deg,#ffd166,#26f4ff);color:#03050b;text-decoration:none;font-family:Arial,sans-serif;font-size:14px;font-weight:800;letter-spacing:1px;text-transform:uppercase;padding:13px 20px;border-radius:6px">'.e($buttonText).'</a></td></tr>';
}
return '<!doctype html><html><body style="margin:0;background:#03050b;color:#f2f4ff;font-family:Arial,sans-serif">'.
'<div style="display:none;max-height:0;overflow:hidden;color:transparent">'.$introEsc.'</div>'.
'<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:radial-gradient(circle at 15% 0%,rgba(38,244,255,.18),transparent 340px),radial-gradient(circle at 85% 10%,rgba(255,79,216,.14),transparent 360px),#03050b;padding:34px 14px">'.
'<tr><td align="center"><table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width:620px;background:#0e172b;border:1px solid rgba(38,244,255,.28);border-radius:8px;overflow:hidden;box-shadow:0 18px 44px rgba(0,0,0,.55)">'.
'<tr><td style="padding:24px 26px;border-bottom:1px solid rgba(38,244,255,.18);background:linear-gradient(135deg,rgba(38,244,255,.12),rgba(255,79,216,.08),rgba(255,209,102,.08))">'.
'<div style="font-size:12px;letter-spacing:4px;color:#26f4ff;text-transform:uppercase;font-weight:700">'.$site.'</div>'.
'<h1 style="margin:10px 0 0;font-family:Georgia,serif;font-size:32px;line-height:1.1;color:#ffffff">'.$titleEsc.'</h1>'.
'</td></tr><tr><td style="padding:26px;color:#aab7cf;font-size:16px;line-height:1.75">'.
$bodyHtml.
'<table role="presentation" cellspacing="0" cellpadding="0">'.$button.'</table>'.
'<p style="margin:24px 0 0;color:#65768f;font-size:13px">Keyser, West Virginia - local businesses, events, and community information.</p>'.
'</td></tr></table></td></tr></table></body></html>';
}
function verificationEmailContent(array $user, string $verifyUrl): array {
$name = (string)($user['username'] ?? 'there');
$isOwner = ($user['member_type'] ?? 'member') === 'business_owner';
$title = $isOwner ? 'Verify Your Business Owner Account' : 'Verify Your Account';
$subject = $isOwner ? 'Verify your My Keyser business owner account' : 'Verify your My Keyser account';
$text = "Hi $name,\n\n".
"Thanks for registering with My Keyser. Please verify your email address before signing in:\n\n".
"$verifyUrl\n\n".
"This link expires in 24 hours.\n\n".
($isOwner ? "After verification, your business claim will remain queued and we will contact you within a couple of business days.\n\n" : '').
"My Keyser";
$htmlBody = '<p style="margin:0 0 16px;color:#f2f4ff">Hi '.e($name).',</p>'.
'<p style="margin:0 0 16px">Thanks for registering with My Keyser. Please verify your email address before signing in.</p>'.
($isOwner ? '<p style="margin:0 0 16px">After verification, your business claim will stay queued for review and our team will contact you within a couple of business days.</p>' : '').
'<p style="margin:0;color:#65768f;font-size:13px">This link expires in 24 hours.</p>';
return [$subject, $text, emailShellHtml($title, 'Verify your My Keyser account.', $htmlBody, 'Verify Email', $verifyUrl)];
}
function registrationCompleteEmailContent(array $user): array {
$name = (string)($user['username'] ?? 'there');
$isOwner = ($user['member_type'] ?? 'member') === 'business_owner';
if ($isOwner) {
$subject = 'Thank you for claiming your My Keyser business page';
$text = "Hi $name,\n\nThank you for registering as a business owner with My Keyser.\n\nYour email is verified and you can now log in. Our team will contact you within a couple of business days for a verification call before assigning your business page.\n\nThank you for helping keep Keyser's directory accurate.\n\nMy Keyser";
$body = '<p style="margin:0 0 16px;color:#f2f4ff">Hi '.e($name).',</p>'.
'<p style="margin:0 0 16px">Thank you for registering as a business owner with My Keyser. Your email is verified and you can now log in.</p>'.
'<p style="margin:0 0 16px">Our team will contact you within a couple of business days for a verification call before assigning your business page.</p>'.
'<p style="margin:0">Thank you for helping keep Keyser&rsquo;s directory accurate.</p>';
return [$subject, $text, emailShellHtml('Thanks, Business Owner', 'Your business owner account is verified.', $body, 'Log In', absoluteUrl('/login.php'))];
}
$subject = 'Thank you for registering with My Keyser';
$text = "Hi $name,\n\nThank you for registering with My Keyser. Your email is verified and you can now log in.\n\nWe are glad to have you in the Keyser community.\n\nMy Keyser";
$body = '<p style="margin:0 0 16px;color:#f2f4ff">Hi '.e($name).',</p>'.
'<p style="margin:0 0 16px">Thank you for registering with My Keyser. Your email is verified and you can now log in.</p>'.
'<p style="margin:0">We are glad to have you in the Keyser community.</p>';
return [$subject, $text, emailShellHtml('Thanks For Registering', 'Your My Keyser account is verified.', $body, 'Log In', absoluteUrl('/login.php'))];
}
function sendVerificationEmail(array $user, string $token): array {
$url = absoluteUrl('/verify-email.php?token='.rawurlencode($token));
[$subject, $text, $html] = verificationEmailContent($user, $url);
return mailgunSend((string)$user['email'], $subject, $text, $html);
}
function sendRegistrationCompleteEmail(array $user): array {
[$subject, $text, $html] = registrationCompleteEmailContent($user);
return mailgunSend((string)$user['email'], $subject, $text, $html);
}