+
-
-
-
-
=$ic?>
-
=$lbl?>
-
=$val?>
+
+
+
+
+
+
=strtoupper(date('M',strtotime($ev['event_date'])))?>
+
=date('j',strtotime($ev['event_date']))?>
+
=date('Y',strtotime($ev['event_date']))?>
+
+
+
=e($ev['title'])?> FEATURED
+
+ =e($ev['venue'])?>
+ =e($ev['event_time'])?>
+ =e($ev['cost'])?>
+
+
=e(substr($ev['description'],0,160)).(strlen($ev['description'])>160?'...':'')?>
+
-
-
-
-
-
-
-
๐
WHAT'S HAPPENING
-
Upcoming Events
-
-
-
-
-
-
=strtoupper(date('M',strtotime($ev['event_date'])))?>
-
=date('j',strtotime($ev['event_date']))?>
-
=date('Y',strtotime($ev['event_date']))?>
-
-
-
=e($ev['title'])?> โ
FEATURED
-
- ๐ =e($ev['venue'])?>
- ๐ =e($ev['event_time'])?>
- ๐ฐ =e($ev['cost'])?>
-
-
=e(substr($ev['description'],0,180)).(strlen($ev['description'])>180?'โฆ':'')?>
-
-
-
-
-
-
-
-
-
JOIN THE COMMUNITY
-
Own a Business in Keyser?
-
Create an account to manage your listing, post alerts, add menus, and connect with the Keyser community.
-
+
+
+
+
ABOUT KEYSER
+
Railroad Roots, River Views, Mountain Pace
+
+
Keyser began as Paddy Town, then New Creek, before taking its current name in 1874 in honor of Baltimore & Ohio Railroad leader William Keyser. The rail line shaped the cityโs downtown, its work ethic, and its role as Mineral Countyโs county seat.
+
Today the city sits where New Creek meets the North Branch Potomac River, with Potomac State College of WVU, local restaurants, small shops, healthcare, civic life, and outdoor access all close by.
+
+
Learn More About Keyser
+
+
+
+
+
=e($lbl)?>
+
=e($val)?>
+
+
+
+
+
+
+
+
+
RANDOM LOCAL PICKS ยท =e(strtoupper($rotationLabel))?>
+
Explore Something Different
+
+
View All Businesses
+
+
+
+
+
+
+
+
BUSINESS OWNERS
+
Claim your Keyser business page
+
Create a business-owner account, choose the listing you manage, add a verification phone number, and submit first edits or a YouTube/Vimeo video for admin review.
+
+
+
Start a Claim
+
+
Open Dashboard
+
+
+
diff --git a/lone-embed.php b/lone-embed.php
new file mode 100644
index 0000000..5ff6ab1
--- /dev/null
+++ b/lone-embed.php
@@ -0,0 +1,1322 @@
+ 'video/mp4',
+ 'm4v' => 'video/mp4',
+ 'webm' => 'video/webm',
+ 'ogv' => 'video/ogg',
+ 'ogg' => 'video/ogg',
+ 'mov' => 'video/quicktime',
+ 'mkv' => 'video/x-matroska',
+ 'avi' => 'video/x-msvideo',
+ 'wmv' => 'video/x-ms-wmv',
+ 'flv' => 'video/x-flv',
+ 'ts' => 'video/mp2t',
+ '3gp' => 'video/3gpp',
+ '3g2' => 'video/3gpp2',
+ 'm3u8' => 'application/vnd.apple.mpegurl',
+ 'mpd' => 'application/dash+xml',
+ 'mp3' => 'audio/mpeg',
+ 'm4a' => 'audio/mp4',
+ 'aac' => 'audio/aac',
+ 'wav' => 'audio/wav',
+ 'flac' => 'audio/flac',
+ 'oga' => 'audio/ogg',
+ 'opus' => 'audio/ogg',
+ ];
+
+ $ext = extension_from_url($url);
+ return $map[$ext] ?? '';
+}
+
+function classify_kind(string $url, string $mime, string $forced_mode): string
+{
+ $forced = strtolower($forced_mode);
+ if (in_array($forced, ['video', 'audio', 'hls', 'dash', 'iframe'], true)) {
+ return $forced;
+ }
+
+ $ext = extension_from_url($url);
+ $mime_lc = strtolower($mime);
+
+ if ($ext === 'm3u8' || in_array($mime_lc, ['application/x-mpegurl', 'application/vnd.apple.mpegurl'], true)) {
+ return 'hls';
+ }
+
+ if ($ext === 'mpd' || $mime_lc === 'application/dash+xml') {
+ return 'dash';
+ }
+
+ if (starts_with($mime_lc, 'audio/')) {
+ return 'audio';
+ }
+
+ if (starts_with($mime_lc, 'video/')) {
+ return 'video';
+ }
+
+ if (in_array($ext, ['mp3', 'm4a', 'aac', 'wav', 'flac', 'oga', 'opus'], true)) {
+ return 'audio';
+ }
+
+ if (in_array($ext, ['mp4', 'm4v', 'webm', 'ogv', 'ogg', 'mov', 'mkv', 'avi', 'wmv', 'flv', 'ts', '3gp', '3g2'], true)) {
+ return 'video';
+ }
+
+ return 'unknown';
+}
+
+function normalize_url(string $url): string
+{
+ $url = trim(html_entity_decode($url, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
+ if ($url === '') {
+ return '';
+ }
+
+ if (starts_with($url, '//')) {
+ $url = 'https:' . $url;
+ }
+
+ $scheme = parse_url($url, PHP_URL_SCHEME);
+ if (!is_string($scheme) || $scheme === '') {
+ return $url;
+ }
+
+ $scheme = strtolower($scheme);
+ if (!in_array($scheme, ['http', 'https'], true)) {
+ return '';
+ }
+
+ return $url;
+}
+
+function append_query(string $base, array $params): string
+{
+ $query = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
+ if ($query === '') {
+ return $base;
+ }
+
+ return $base . (strpos($base, '?') === false ? '?' : '&') . $query;
+}
+
+function parse_youtube_time(string $value): int
+{
+ $value = trim($value);
+ if ($value === '') {
+ return 0;
+ }
+
+ if (ctype_digit($value)) {
+ return max(0, (int) $value);
+ }
+
+ $seconds = 0;
+ if (preg_match_all('/(\d+)\s*([hms])/i', $value, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $amount = (int) $match[1];
+ $unit = strtolower($match[2]);
+ if ($unit === 'h') {
+ $seconds += $amount * 3600;
+ } elseif ($unit === 'm') {
+ $seconds += $amount * 60;
+ } else {
+ $seconds += $amount;
+ }
+ }
+ }
+
+ return max(0, $seconds);
+}
+
+function clean_token(string $value, string $pattern = '/[^A-Za-z0-9_-]/'): string
+{
+ $clean = preg_replace($pattern, '', trim($value));
+ return is_string($clean) ? $clean : '';
+}
+
+function twitch_parent_host(): string
+{
+ $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
+ $host = strtolower(trim((string) $host));
+ $host = preg_replace('/:\d+$/', '', $host);
+ $host = is_string($host) ? $host : 'localhost';
+ return $host !== '' ? $host : 'localhost';
+}
+
+function provider_embed(string $url): ?array
+{
+ $parts = @parse_url($url);
+ if (!is_array($parts) || empty($parts['host'])) {
+ return null;
+ }
+
+ $host = strtolower((string) $parts['host']);
+ $path = isset($parts['path']) ? (string) $parts['path'] : '';
+ $segments = path_segments($url);
+ $query = get_query_values($url);
+
+ if (host_matches($host, 'youtu.be') || host_matches($host, 'youtube.com') || host_matches($host, 'youtube-nocookie.com')) {
+ $id = '';
+ if (host_matches($host, 'youtu.be') && isset($segments[0])) {
+ $id = $segments[0];
+ }
+ if ($id === '' && isset($query['v'])) {
+ $id = scalar_string($query['v']);
+ }
+ if ($id === '' && preg_match('#/(?:embed|shorts|live|v)/([^/?#]+)#', $path, $m)) {
+ $id = $m[1];
+ }
+
+ $id = clean_token($id);
+ if ($id !== '') {
+ $params = ['rel' => '0', 'modestbranding' => '1', 'playsinline' => '1'];
+ $start = 0;
+ if (isset($query['start'])) {
+ $start = parse_youtube_time(scalar_string($query['start']));
+ } elseif (isset($query['t'])) {
+ $start = parse_youtube_time(scalar_string($query['t']));
+ }
+ if ($start > 0) {
+ $params['start'] = (string) $start;
+ }
+
+ return [
+ 'provider' => 'youtube',
+ 'embed' => append_query('https://www.youtube-nocookie.com/embed/' . rawurlencode($id), $params),
+ ];
+ }
+
+ if (isset($query['list'])) {
+ $list = clean_token(scalar_string($query['list']));
+ if ($list !== '') {
+ return [
+ 'provider' => 'youtube',
+ 'embed' => append_query('https://www.youtube-nocookie.com/embed/videoseries', [
+ 'list' => $list,
+ 'rel' => '0',
+ 'modestbranding' => '1',
+ 'playsinline' => '1',
+ ]),
+ ];
+ }
+ }
+ }
+
+ if (host_matches($host, 'vimeo.com')) {
+ $id = '';
+ if (preg_match('#/(?:video/)?(\d+)#', $path, $m)) {
+ $id = $m[1];
+ }
+
+ if ($id !== '') {
+ return [
+ 'provider' => 'vimeo',
+ 'embed' => append_query('https://player.vimeo.com/video/' . rawurlencode($id), [
+ 'title' => '0',
+ 'byline' => '0',
+ 'portrait' => '0',
+ ]),
+ ];
+ }
+ }
+
+ if (host_matches($host, 'dailymotion.com') || host_matches($host, 'dai.ly')) {
+ $id = '';
+ if (host_matches($host, 'dai.ly') && isset($segments[0])) {
+ $id = $segments[0];
+ }
+ if ($id === '' && preg_match('#/(?:embed/)?video/([^/?#_]+)#', $path, $m)) {
+ $id = $m[1];
+ }
+
+ $id = clean_token($id);
+ if ($id !== '') {
+ return [
+ 'provider' => 'dailymotion',
+ 'embed' => 'https://www.dailymotion.com/embed/video/' . rawurlencode($id),
+ ];
+ }
+ }
+
+ if (host_matches($host, 'twitch.tv')) {
+ $parent = twitch_parent_host();
+
+ if (preg_match('#/videos/(\d+)#', $path, $m)) {
+ return [
+ 'provider' => 'twitch',
+ 'embed' => append_query('https://player.twitch.tv/', [
+ 'video' => $m[1],
+ 'parent' => $parent,
+ 'autoplay' => 'false',
+ ]),
+ ];
+ }
+
+ if (preg_match('#/([^/]+)/clip/([^/?#]+)#', $path, $m)) {
+ return [
+ 'provider' => 'twitch',
+ 'embed' => append_query('https://clips.twitch.tv/embed', [
+ 'clip' => clean_token($m[2]),
+ 'parent' => $parent,
+ 'autoplay' => 'false',
+ ]),
+ ];
+ }
+
+ if (isset($segments[0]) && $segments[0] !== '') {
+ return [
+ 'provider' => 'twitch',
+ 'embed' => append_query('https://player.twitch.tv/', [
+ 'channel' => clean_token($segments[0]),
+ 'parent' => $parent,
+ 'autoplay' => 'false',
+ ]),
+ ];
+ }
+ }
+
+ if (host_matches($host, 'clips.twitch.tv') && isset($segments[0])) {
+ return [
+ 'provider' => 'twitch',
+ 'embed' => append_query('https://clips.twitch.tv/embed', [
+ 'clip' => clean_token($segments[0]),
+ 'parent' => twitch_parent_host(),
+ 'autoplay' => 'false',
+ ]),
+ ];
+ }
+
+ if (host_matches($host, 'facebook.com') || host_matches($host, 'fb.watch')) {
+ return [
+ 'provider' => 'facebook',
+ 'embed' => append_query('https://www.facebook.com/plugins/video.php', [
+ 'href' => $url,
+ 'show_text' => 'false',
+ 'width' => '1280',
+ ]),
+ ];
+ }
+
+ if (host_matches($host, 'tiktok.com') && preg_match('#/video/(\d+)#', $path, $m)) {
+ return [
+ 'provider' => 'tiktok',
+ 'embed' => 'https://www.tiktok.com/embed/v2/' . rawurlencode($m[1]),
+ ];
+ }
+
+ if (host_matches($host, 'instagram.com') && preg_match('#/(p|reel|tv)/([^/?#]+)#', $path, $m)) {
+ return [
+ 'provider' => 'instagram',
+ 'embed' => 'https://www.instagram.com/' . rawurlencode($m[1]) . '/' . rawurlencode($m[2]) . '/embed',
+ ];
+ }
+
+ if ((host_matches($host, 'twitter.com') || host_matches($host, 'x.com')) && preg_match('#/status/(\d+)#', $path, $m)) {
+ return [
+ 'provider' => 'twitter',
+ 'embed' => append_query('https://platform.twitter.com/embed/Tweet.html', ['id' => $m[1]]),
+ ];
+ }
+
+ if (host_matches($host, 'soundcloud.com')) {
+ return [
+ 'provider' => 'soundcloud',
+ 'embed' => append_query('https://w.soundcloud.com/player/', [
+ 'url' => $url,
+ 'auto_play' => 'false',
+ 'show_teaser' => 'true',
+ ]),
+ ];
+ }
+
+ if (host_matches($host, 'open.spotify.com') && isset($segments[0], $segments[1])) {
+ $type = clean_token($segments[0]);
+ $id = clean_token($segments[1]);
+ if (in_array($type, ['album', 'artist', 'episode', 'playlist', 'show', 'track'], true) && $id !== '') {
+ return [
+ 'provider' => 'spotify',
+ 'embed' => 'https://open.spotify.com/embed/' . rawurlencode($type) . '/' . rawurlencode($id),
+ ];
+ }
+ }
+
+ if (host_matches($host, 'drive.google.com')) {
+ $id = '';
+ if (preg_match('#/file/d/([^/]+)#', $path, $m)) {
+ $id = $m[1];
+ } elseif (isset($query['id'])) {
+ $id = scalar_string($query['id']);
+ }
+
+ $id = clean_token($id);
+ if ($id !== '') {
+ return [
+ 'provider' => 'google-drive',
+ 'embed' => 'https://drive.google.com/file/d/' . rawurlencode($id) . '/preview',
+ ];
+ }
+ }
+
+ if ((host_matches($host, 'wistia.com') || host_matches($host, 'wi.st')) && preg_match('#/(?:medias/)?([A-Za-z0-9]+)#', $path, $m)) {
+ return [
+ 'provider' => 'wistia',
+ 'embed' => 'https://fast.wistia.net/embed/iframe/' . rawurlencode($m[1]),
+ ];
+ }
+
+ if (host_matches($host, 'streamable.com') && isset($segments[0])) {
+ return [
+ 'provider' => 'streamable',
+ 'embed' => 'https://streamable.com/e/' . rawurlencode(clean_token($segments[0])),
+ ];
+ }
+
+ if (host_matches($host, 'loom.com') && isset($segments[0], $segments[1]) && $segments[0] === 'share') {
+ return [
+ 'provider' => 'loom',
+ 'embed' => 'https://www.loom.com/embed/' . rawurlencode(clean_token($segments[1])),
+ ];
+ }
+
+ return null;
+}
+
+function rewrite_direct_url(string $url): string
+{
+ $host = strtolower((string) (parse_url($url, PHP_URL_HOST) ?? ''));
+ $path = (string) (parse_url($url, PHP_URL_PATH) ?? '');
+
+ if (host_matches($host, 'dropbox.com')) {
+ $parts = @parse_url($url);
+ if (is_array($parts)) {
+ $query = [];
+ if (isset($parts['query'])) {
+ parse_str((string) $parts['query'], $query);
+ }
+ unset($query['dl'], $query['raw']);
+ $query['raw'] = '1';
+
+ $rebuilt = ($parts['scheme'] ?? 'https') . '://' . ($parts['host'] ?? 'www.dropbox.com');
+ $rebuilt .= $parts['path'] ?? '';
+ $rebuilt = append_query($rebuilt, $query);
+ if (isset($parts['fragment'])) {
+ $rebuilt .= '#' . $parts['fragment'];
+ }
+ return $rebuilt;
+ }
+ }
+
+ if (host_matches($host, 'github.com') && preg_match('#^/([^/]+)/([^/]+)/blob/([^/]+)/(.+)$#', $path, $m)) {
+ return 'https://raw.githubusercontent.com/' . rawurlencode($m[1]) . '/' . rawurlencode($m[2]) . '/' . rawurlencode($m[3]) . '/' . str_replace('%2F', '/', rawurlencode($m[4]));
+ }
+
+ return $url;
+}
+
+function source_from_raw(string $raw, string $forced_mode): ?array
+{
+ $raw = trim($raw);
+ if ($raw === '') {
+ return null;
+ }
+
+ $mime = '';
+ if (strpos($raw, '|') !== false) {
+ [$raw, $mime] = explode('|', $raw, 2);
+ $mime = trim($mime);
+ }
+
+ $original_url = normalize_url($raw);
+ if ($original_url === '') {
+ return null;
+ }
+
+ $provider = provider_embed($original_url);
+ $url = $provider ? $original_url : rewrite_direct_url($original_url);
+ if ($mime === '') {
+ $mime = guess_mime($url);
+ }
+
+ $kind = classify_kind($url, $mime, $forced_mode);
+ if ($provider && strtolower($forced_mode) === 'auto') {
+ $kind = 'iframe';
+ }
+
+ if (strtolower($forced_mode) === 'iframe') {
+ $kind = 'iframe';
+ }
+
+ $ext = extension_from_url($url);
+ $label = $provider['provider'] ?? ($ext !== '' ? $ext : ($kind === 'unknown' ? 'url' : $kind));
+
+ return [
+ 'url' => $url,
+ 'originalUrl' => $original_url,
+ 'embed' => $provider['embed'] ?? $url,
+ 'provider' => $provider['provider'] ?? '',
+ 'mime' => $mime,
+ 'kind' => $kind,
+ 'label' => $label,
+ ];
+}
+
+function query_bool(string $name, bool $default = false): bool
+{
+ if (!array_key_exists($name, $_GET)) {
+ return $default;
+ }
+
+ $value = strtolower(trim((string) $_GET[$name]));
+ if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
+ return true;
+ }
+ if (in_array($value, ['0', 'false', 'no', 'off'], true)) {
+ return false;
+ }
+
+ return $default;
+}
+
+function media_type_attr(array $source): string
+{
+ $mime = isset($source['mime']) ? trim((string) $source['mime']) : '';
+ return $mime !== '' ? ' type="' . h($mime) . '"' : '';
+}
+
+function media_common_attrs(array $config): string
+{
+ $attrs = [];
+
+ if (($config['controls'] ?? true) !== false) {
+ $attrs[] = 'controls';
+ }
+ if (($config['autoplay'] ?? false) === true) {
+ $attrs[] = 'autoplay';
+ }
+ if (($config['muted'] ?? false) === true || ($config['autoplay'] ?? false) === true) {
+ $attrs[] = 'muted';
+ }
+ if (($config['loop'] ?? false) === true) {
+ $attrs[] = 'loop';
+ }
+
+ $attrs[] = 'preload="metadata"';
+
+ $crossorigin = trim((string) ($config['crossorigin'] ?? ''));
+ if ($crossorigin !== '') {
+ $attrs[] = 'crossorigin="' . h($crossorigin) . '"';
+ }
+
+ return implode(' ', $attrs);
+}
+
+$title = trim((string) ($_GET['title'] ?? ''));
+$poster = normalize_url((string) ($_GET['poster'] ?? ''));
+$mode = strtolower(trim((string) ($_GET['mode'] ?? 'auto')));
+if (!in_array($mode, ['auto', 'video', 'audio', 'hls', 'dash', 'iframe'], true)) {
+ $mode = 'auto';
+}
+
+$primary_raw = '';
+foreach (['video', 'url', 'src', 'v', 'slug'] as $key) {
+ if (isset($_GET[$key]) && trim((string) $_GET[$key]) !== '') {
+ $primary_raw = trim((string) $_GET[$key]);
+ break;
+ }
+}
+
+$sources = [];
+if ($primary_raw !== '') {
+ $source = source_from_raw($primary_raw, $mode);
+ if ($source !== null) {
+ $sources[] = $source;
+ }
+}
+
+for ($i = 2; $i <= 10; $i++) {
+ foreach (["video{$i}", "url{$i}", "src{$i}"] as $key) {
+ if (isset($_GET[$key]) && trim((string) $_GET[$key]) !== '') {
+ $source = source_from_raw(trim((string) $_GET[$key]), $mode);
+ if ($source !== null) {
+ $sources[] = $source;
+ }
+ break;
+ }
+ }
+}
+
+$crossorigin = strtolower(trim((string) ($_GET['crossorigin'] ?? '')));
+if (!in_array($crossorigin, ['anonymous', 'use-credentials'], true)) {
+ $crossorigin = '';
+}
+
+$config = [
+ 'sources' => $sources,
+ 'title' => $title,
+ 'poster' => $poster,
+ 'autoplay' => query_bool('autoplay', false),
+ 'muted' => query_bool('muted', false),
+ 'loop' => query_bool('loop', false),
+ 'controls' => query_bool('controls', true),
+ 'playsinline' => true,
+ 'iframeFallback' => query_bool('iframe_fallback', true),
+ 'crossorigin' => $crossorigin,
+ 'withCredentials' => query_bool('with_credentials', false),
+];
+
+$primary_source = $sources[0] ?? null;
+
+$json_config = json_encode(
+ $config,
+ JSON_UNESCAPED_SLASHES
+ | JSON_UNESCAPED_UNICODE
+ | JSON_HEX_TAG
+ | JSON_HEX_AMP
+ | JSON_HEX_APOS
+ | JSON_HEX_QUOT
+);
+if (!is_string($json_config)) {
+ $json_config = '{"sources":[]}';
+}
+
+header_remove('X-Frame-Options');
+header_remove('Content-Security-Policy');
+header_remove('X-Content-Security-Policy');
+header('Content-Type: text/html; charset=utf-8');
+header("Content-Security-Policy: default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'; img-src * data: blob:; media-src * data: blob:; frame-src *; connect-src *");
+header('Referrer-Policy: no-referrer-when-downgrade');
+header('Cross-Origin-Resource-Policy: cross-origin');
+header('X-Content-Type-Options: nosniff');
+?>
+
+
+
+
+
+
= $title !== '' ? h($title) . ' - Player' : 'Player' ?>
+
+
+
+
+
+
+
+
+ 0): ?>
+
+
= $title !== '' ? h($title) : '' ?>
+
+
+ = h((string) $source['label']) ?>
+
+
+
+
+
+
+
+ JavaScript is disabled. Direct files and supported provider embeds can still load; HLS and DASH may need JavaScript.
+
+
+
+
+
+
+
+
+
No media URL supplied
+
Use ?video=https%3A%2F%2Fexample.com%2Fclip.mp4 or ?url=....
+
+
+
+
+
+ 0): ?>
+
+
+
+
diff --git a/register.php b/register.php
index c677d49..6890d42 100644
--- a/register.php
+++ b/register.php
@@ -8,286 +8,262 @@ if (setting('registration_enabled','1') !== '1') {
$pageTitle = 'Create Account โ Discover Keyser WV';
$errors = [];
+$businessOptions = qall("SELECT id,name,address FROM businesses WHERE is_active=1 ORDER BY name");
+$tier = p('member_type', 'member') === 'business_owner' ? 'business_owner' : 'member';
if (isPost()) {
csrfCheck();
- // โโ Honeypot check โ bots fill hidden fields, humans don't โโ
- // Field named "website_url" is visually hidden; any value = bot
- if (ps('website_url') !== '') {
- // Silently succeed (don't tell the bot it failed)
+ if (ps('website_url') !== '' || ps('confirm_email') !== '') {
flash('Account created! Welcome to Discover Keyser WV.','success');
go('/index.php');
}
- // โโ Timing check โ real humans take at least 3 seconds โโ
$formTime = (int)p('_form_time', 0);
if ($formTime && (time() - $formTime) < 3) {
- // Also silent โ pretend success
flash('Account created! Welcome to Discover Keyser WV.','success');
go('/index.php');
}
+ $tier = p('member_type', 'member') === 'business_owner' ? 'business_owner' : 'member';
$un = ps('username');
$em = ps('email');
$pw = ps('password');
$pw2 = ps('password2');
$tos = p('lawful_use', '');
- if (strlen($un) < 3)
- $errors[] = 'Username must be at least 3 characters.';
- if (!preg_match('/^[a-zA-Z0-9_]+$/', $un))
- $errors[] = 'Username may only contain letters, numbers, and underscores.';
- if ($em && !filter_var($em, FILTER_VALIDATE_EMAIL))
- $errors[] = 'Please enter a valid email address.';
- if (strlen($pw) < 6)
- $errors[] = 'Password must be at least 6 characters.';
- if ($pw !== $pw2)
- $errors[] = 'Passwords do not match.';
- if ($tos !== '1')
- $errors[] = 'You must agree to use this service lawfully to create an account.';
- if (!$errors && qval("SELECT id FROM users WHERE username=?", [$un]))
- $errors[] = 'That username is already taken. Please choose another.';
- if (!$errors && $em && qval("SELECT id FROM users WHERE email=?", [$em]))
- $errors[] = 'That email address is already registered. Try signing in instead.';
+ $claimBizId = (int)p('claim_business_id');
+ $contactPhone = ps('contact_phone');
+ $claimBiz = null;
+
+ if (strlen($un) < 3) $errors[] = 'Username must be at least 3 characters.';
+ if (!preg_match('/^[a-zA-Z0-9_]+$/', $un)) $errors[] = 'Username may only contain letters, numbers, and underscores.';
+ if ($em && !filter_var($em, FILTER_VALIDATE_EMAIL)) $errors[] = 'Please enter a valid email address.';
+ if (strlen($pw) < 6) $errors[] = 'Password must be at least 6 characters.';
+ if ($pw !== $pw2) $errors[] = 'Passwords do not match.';
+ if ($tos !== '1') $errors[] = 'You must agree to use this service lawfully to create an account.';
+ if (!$errors && qval("SELECT id FROM users WHERE username=?", [$un])) $errors[] = 'That username is already taken. Please choose another.';
+ if (!$errors && $em && qval("SELECT id FROM users WHERE email=?", [$em])) $errors[] = 'That email address is already registered. Try signing in instead.';
+
+ $claimEdits = [
+ 'description' => ps('claim_description'),
+ 'address' => ps('claim_address'),
+ 'phone' => ps('claim_public_phone'),
+ 'email' => ps('claim_public_email'),
+ 'website' => ps('claim_website'),
+ 'video_url' => ps('claim_video_url'),
+ ];
+
+ if ($tier === 'business_owner') {
+ if (!$claimBizId) {
+ $errors[] = 'Please select the business page you want to claim.';
+ } else {
+ $claimBiz = qone("SELECT * FROM businesses WHERE id=? AND is_active=1", [$claimBizId]);
+ if (!$claimBiz) $errors[] = 'Please select a valid listed business.';
+ }
+ if ($contactPhone === '' || strlen(preg_replace('/\D+/', '', $contactPhone)) < 7) {
+ $errors[] = 'Please enter a contact phone number for verification.';
+ }
+ if ($claimEdits['email'] !== '' && !filter_var($claimEdits['email'], FILTER_VALIDATE_EMAIL)) {
+ $errors[] = 'Please enter a valid public email edit.';
+ }
+ if (!validBusinessVideoUrl($claimEdits['video_url'])) {
+ $errors[] = 'Business videos must be a YouTube or Vimeo URL.';
+ }
+ }
if (!$errors) {
$id = qrun(
"INSERT INTO users(username,email,password)VALUES(?,?,?)",
[$un, $em ?: null, password_hash($pw, PASSWORD_BCRYPT)]
);
+
+ $submittedEdits = 0;
+ if ($tier === 'business_owner' && $claimBiz) {
+ qrun(
+ "INSERT INTO business_claim_requests(business_id,user_id,contact_phone)VALUES(?,?,?)",
+ [$claimBizId, $id, $contactPhone]
+ );
+
+ foreach ($claimEdits as $field => $newValue) {
+ if ($newValue === '') continue;
+ $oldValue = (string)($claimBiz[$field] ?? '');
+ if ($newValue === $oldValue) continue;
+ qrun(
+ "INSERT INTO business_edit_requests(business_id,user_id,field,old_value,new_value)VALUES(?,?,?,?,?)",
+ [$claimBizId, $id, $field, $oldValue, $newValue]
+ );
+ $submittedEdits++;
+ }
+ }
+
loginUser(qone("SELECT * FROM users WHERE id=?", [$id]));
- flash('Welcome to Discover Keyser WV, '.e($un).'!', 'success');
+
+ if ($tier === 'business_owner' && $claimBiz) {
+ $msg = 'Your account is ready. Your claim for '.$claimBiz['name'].' is queued, and we will contact you at '.$contactPhone.' within a few business days for verification.';
+ if ($submittedEdits) $msg .= ' '.$submittedEdits.' proposed edit'.($submittedEdits !== 1 ? 's were' : ' was').' sent to admin review.';
+ flash($msg, 'success');
+ go('/dashboard.php');
+ }
+
+ flash('Welcome to Discover Keyser WV, '.$un.'!', 'success');
go('/index.php');
}
}
+$ownerSelected = $tier === 'business_owner';
+
+$footExtra = <<
+(() => {
+ const radios = document.querySelectorAll('input[name="member_type"]');
+ const ownerFields = document.getElementById('ownerClaimFields');
+ const sync = () => {
+ const owner = document.querySelector('input[name="member_type"]:checked')?.value === 'business_owner';
+ ownerFields?.classList.toggle('is-open', owner);
+ ownerFields?.querySelectorAll('[data-owner-required]').forEach(el => {
+ if (owner) el.setAttribute('required', 'required');
+ else el.removeAttribute('required');
+ });
+ };
+ radios.forEach(r => r.addEventListener('change', sync));
+ sync();
+})();
+
+HTML;
+
include __DIR__.'/includes/header.php';
?>
-
-
-
-
-
-
-
JOIN THE COMMUNITY
-
Create Your Account
-
- Discover Keyser WV is your community hub for local businesses, events, and information.
- Takes less than a minute.
-
+
+
+
JOIN KEYSER ONLINE
+
Create Your Account
+
Choose a community membership or start a verified business-owner claim for a page already listed in the directory.
-
-
-
-
๐ค COMMUNITY MEMBER BENEFITS
-
- โญ Rate and review local Keyser businesses
- ๐
Submit community events to the public calendar
- ๐ฉ Report incorrect or outdated business listings
- ๐ฌ Share your experiences with the Keyser community
- ๐ Access your personal dashboard with your submissions
- ๐ Stay connected with what's happening in Mineral County
-
+
+
=implode(' ', array_map('e', $errors))?>
+
+
+