Siteβ―Builder
Editing:
edit-social.php
writable 0666
<?php /************************************************************************** * EDIT SOCIAL PROFILE β /social/edit.php (profile.json edition) * ---------------------------------------------------------------------- * Premium is now a simple flag "premium":"on" | "off" * Upgrade / Downgrade buttons are visible **only to admins** * © 2025Β BestDealOn β free to modify. PHPΒ 8.1+ *************************************************************************/ declare(strict_types=1); /* βββββ authentication βββββββββββββββββββββββββββββββββββββββββββββββ */ require_once __DIR__ . '/../lib/auth.php'; require_login(); $me = current_user(); // you already store role here $isAdmin = ($me['role'] ?? '') === 'admin'; /* βββββ paths & helpers ββββββββββββββββββββββββββββββββββββββββββββββ */ $rootDir = $_SERVER['DOCUMENT_ROOT']; $socialDir = $rootDir . '/social/'; // one directory per profile $boundsFile= $rootDir . '/geo/json-data/states-bounds.json'; date_default_timezone_set('UTC'); /* read list of US states (twoβletter codes) */ $usStates = []; if (is_file($boundsFile)) { foreach (json_decode(file_get_contents($boundsFile), true, 512, JSON_THROW_ON_ERROR) as $row) { if (!empty($row['state']) && strlen($row['state']) === 2) { $usStates[] = strtoupper($row['state']); } } } /* canonicalise social / video URLs */ function canonical_url(string $platform, string $val): string { $val = trim($val); if ($val === '') return ''; if (str_starts_with($val, 'http://') || str_starts_with($val, 'https://')) return $val; $slug = ltrim($val, '@/ '); return match ($platform) { 'twitter' => "https://twitter.com/$slug", 'tiktok' => "https://www.tiktok.com/@$slug", 'youtube' => "https://youtube.com/@$slug", 'rumble' => "https://rumble.com/user/$slug", 'odysee' => "https://odysee.com/@$slug", 'instagram' => "https://instagram.com/$slug", 'facebook' => "https://facebook.com/$slug", 'linkedin' => "https://linkedin.com/in/$slug", 'patreon' => "https://patreon.com/$slug", 'spotify' => "https://open.spotify.com/show/$slug", 'apple' => "https://podcasts.apple.com/podcast/$slug", default => $val }; } /* βββββ which profile? βββββββββββββββββββββββββββββββββββββββββββββββ */ $slug = preg_replace('/[^a-z0-9_]/i', '', ($_REQUEST['user'] ?? '')); $dir = $socialDir . $slug; $file = "$dir/profile.json"; $error = ''; if (!$slug || !is_dir($dir) || !is_file($file)) { $error = "No profile for @$slug was found."; } /* βββββ load JSON ββββββββββββββββββββββββββββββββββββββββββββββββββββ */ $profile = []; if (!$error) { $profile = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR); if (!$profile) $error = 'Profile data corrupted.'; } /* Current premium status */ $isPremium = ($profile['premium'] ?? '') === 'on'; /* βββββ POST handler ββββββββββββββββββββββββββββββββββββββββββββββββ */ if (!$error && $_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? 'save'; // save | upgrade | downgrade $display = trim($_POST['display_name'] ?? ''); $email = trim($_POST['email'] ?? ''); $state = strtoupper(trim($_POST['state'] ?? '')); /* ββ validation βββββββββββββββββββββββββββββββββββββββββββββββ */ if ($display === '' || $email === '') { $error = 'Display name and email are required.'; } elseif (!in_array($state, $usStates, true)) { $error = 'Invalid U.S. state.'; } else { /* validate βcity belongs to stateβ */ $cityFile = "$rootDir/geo/json-data/$state.json"; $cities = is_file($cityFile) ? json_decode(file_get_contents($cityFile), true) : []; $validCity= false; foreach ($cities as $c) { if (strcasecmp($c['city'], trim($_POST['city'] ?? '')) === 0) { $validCity = true; break; } } if (!$validCity) $error = 'City not recognised for that state.'; } /* ββ persist βββββββββββββββββββββββββββββββββββββββββββββββββ */ if (!$error) { /* core editable fields */ $profile = array_merge($profile, [ 'display_name' => $display, 'email' => $email, 'slogan' => mb_substr(trim($_POST['slogan'] ?? ''), 0, 400), 'description' => mb_substr(trim($_POST['description'] ?? ''), 0, 800), /* location */ 'address' => trim($_POST['address'] ?? ''), 'city' => trim($_POST['city'] ?? ''), 'state' => $state, 'zip' => trim($_POST['zip'] ?? ''), 'lat' => trim($_POST['lat'] ?? ''), 'lon' => trim($_POST['lon'] ?? ''), /* contact */ 'phone' => preg_replace('/\D/', '', $_POST['phone'] ?? ''), 'website' => trim($_POST['website'] ?? ''), 'website_rss' => trim($_POST['website_rss'] ?? ''), /* channels (normalised) */ 'channels' => [ 'video' => [ 'youtube' => canonical_url('youtube', $_POST['youtube'] ?? ''), 'rumble' => canonical_url('rumble', $_POST['rumble'] ?? ''), 'odysee' => canonical_url('odysee', $_POST['odysee'] ?? '') ], 'podcast' => [ 'spotify' => canonical_url('spotify', $_POST['spotify'] ?? ''), 'apple' => canonical_url('apple', $_POST['apple'] ?? ''), 'rss' => trim($_POST['podcast_rss'] ?? '') ], 'social' => [ 'twitter' => canonical_url('twitter', $_POST['twitter'] ?? ''), 'tiktok' => canonical_url('tiktok', $_POST['tiktok'] ?? ''), 'instagram' => canonical_url('instagram', $_POST['instagram'] ?? ''), 'facebook' => canonical_url('facebook', $_POST['facebook'] ?? ''), 'linkedin' => canonical_url('linkedin', $_POST['linkedin'] ?? ''), 'patreon' => canonical_url('patreon', $_POST['patreon'] ?? '') ] ], /* tags */ 'tags' => array_filter(array_map('trim', explode(',', $_POST['tags_data'] ?? ''))), 'location_tags' => array_filter(array_map('trim', explode(',', $_POST['location_tags_data'] ?? ''))), 'updated' => date(DATE_ATOM) ]); /* premium flip β allowed only for admins */ if ($isAdmin) { if ($action === 'upgrade') $profile['premium'] = 'on'; if ($action === 'downgrade') $profile['premium'] = 'off'; } file_put_contents( $file, json_encode($profile, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX ); /* redirect back to public profile */ header("Location: /social/$slug/"); exit; } } /* βββββ small helper for chips (tags) βββββββββββββββββββββββββββββββ */ function esc(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); } /* βββββ HTML βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Edit <?= esc($slug ? "@$slug" : 'Profile') ?></title> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" referrerpolicy="no-referrer"> <style> :root{--fg:#222;--bg:#f5f8fb;--accent:#0066ff;--del:#c00000} body{background:var(--bg);margin:0;font-family:system-ui,Arial,sans-serif;color:var(--fg)} .top{background:#eee;padding:.8em 1.2em;font-weight:900} h1{font-size:1.6rem;margin:.7em 0} .lab{display:block;margin:.5em 0 .18em;font-weight:600} input,textarea{width:100%;box-sizing:border-box;padding:.46em .55em;font-size:1em;border:1px solid #bbb;border-radius:4px} button{padding:.58em 1.3em;border:0;border-radius:4px;background:var(--accent);color:#fff;font-size:1.05em;cursor:pointer} button:disabled{opacity:.55} .locked-input{background:#f4f4f4} .tag{display:inline-block;margin:.17em .28em;padding:.24em .55em .18em;border-radius:14px;background:#e4ecff;font-size:.88em} .tag .x{margin-left:.34em;cursor:pointer;font-weight:700;color:var(--del)} .social-toggle{cursor:pointer;font-weight:600;margin:1.1em 0} .collapsed{display:none} .arrow{display:inline-block;transition:.18s transform} .msg{padding:1em;background:#fff1e5;border:1px solid #f3c08c;border-radius:8px;font-weight:600;margin-top:1em} .delete-profile-btn{background:#fff;color:var(--del);border:1px solid var(--del);margin-top:2.2em} </style> </head> <body> <div class="top"><a href="/">BestDealOn</a> » <a href="/members/dashboard.php">Dashboard</a> » Edit Social <a href="<?= $safeUrl ?>" style="float: right;">ViewΒ Page</a></div> <div style="max-width:700px;margin:1.6em auto;padding:1.35em 1.8em;background:#fff;border-radius:10px;box-shadow:0 2px 8px #0001"> <?php if ($error): ?> <h1>Profile not found</h1> <p><?= esc($error) ?></p> </div></body></html><?php exit; ?> <?php endif; ?> <h1>Edit Profile β <?= esc($profile['display_name'] ?: "@$slug") ?></h1> <p style="font-weight:600;color:<?= $isPremium ? '#d48c00':'#555' ?>"> <?php if ($isPremium): ?> <i class="fas fa-star"></i> Premium member <?php else: ?> Free member <?php endif; ?> </p> <?php if ($error): ?><p style="color:#c00;font-weight:600"><?= esc($error) ?></p><?php endif; ?> <form id="editForm" method="post" autocomplete="off"> <input type="hidden" name="user" value="<?= esc($slug) ?>"> <input type="hidden" id="lat" name="lat" value="<?= esc($profile['lat'] ?? '') ?>"> <input type="hidden" id="lon" name="lon" value="<?= esc($profile['lon'] ?? '') ?>"> <!-- handle --> <label class="lab">Handle</label> <input class="locked-input" readonly value="<?= esc($profile['handle']) ?>"> <!-- display / email --> <label class="lab">Display Name</label><input name="display_name" required maxlength="80" value="<?= esc($profile['display_name']) ?>"> <label class="lab">Email</label><input name="email" type="email" required maxlength="120" value="<?= esc($profile['email']) ?>"> <label class="lab">PhoneΒ (optional)</label><input name="phone" maxlength="16" value="<?= esc($profile['phone'] ?? '') ?>"> <!-- slogan / description --> <label class="lab">Slogan</label><input name="slogan" maxlength="400" value="<?= esc($profile['slogan'] ?? '') ?>"> <label class="lab">ShortΒ Description</label><textarea name="description" rows="5" maxlength="800"><?= esc($profile['description'] ?? '') ?></textarea> <!-- website --> <label class="lab">WebsiteΒ (optional)</label><input name="website" maxlength="120" value="<?= esc($profile['website'] ?? '') ?>"> <label class="lab">WebsiteΒ RSS (optional)</label><input name="website_rss" maxlength="120" value="<?= esc($profile['website_rss'] ?? '') ?>"> <!-- address --> <label class="lab">StreetΒ AddressΒ (optional)</label><input name="address" maxlength="100" value="<?= esc($profile['address'] ?? '') ?>"> <div style="display:flex;gap:1em"> <div style="flex:2"> <label class="lab">City</label> <input list="cityList" id="city" name="city" required maxlength="40" value="<?= esc($profile['city']) ?>"> <datalist id="cityList"></datalist> </div> <div style="flex:1"> <label class="lab">State</label> <input id="state" name="state" required maxlength="2" value="<?= esc($profile['state']) ?>"> </div> <div style="flex:1.3"> <label class="lab">ZIP</label> <input id="zip" name="zip" maxlength="12" value="<?= esc($profile['zip']) ?>"> </div> </div> <p id="cityMsg"></p> <hr style="margin:1.6em 0 .9em"> <!-- channels (collapsed) --> <?php $ch = $profile['channels'] ?? ['video'=>[],'podcast'=>[],'social'=>[]]; ?> <div id="toggle" class="social-toggle"><span class="arrow">βΈ</span> Influencer links</div> <div id="channels" class="collapsed"> <h3 style="margin:.6em 0 .3em">Video</h3> <label class="lab">YouTube</label><input name="youtube" maxlength="120" value="<?= esc($ch['video']['youtube'] ?? '') ?>"> <label class="lab">Rumble</label><input name="rumble" maxlength="120" value="<?= esc($ch['video']['rumble'] ?? '') ?>"> <label class="lab">Odysee</label><input name="odysee" maxlength="120" value="<?= esc($ch['video']['odysee'] ?? '') ?>"> <h3 style="margin:1em 0 .3em">Podcast</h3> <label class="lab">Spotify</label><input name="spotify" maxlength="120" value="<?= esc($ch['podcast']['spotify'] ?? '') ?>"> <label class="lab">AppleΒ Podcasts</label><input name="apple" maxlength="120" value="<?= esc($ch['podcast']['apple'] ?? '') ?>"> <label class="lab">PodcastΒ RSS</label><input name="podcast_rss" maxlength="120" value="<?= esc($ch['podcast']['rss'] ?? '') ?>"> <h3 style="margin:1em 0 .3em">Social</h3> <label class="lab">TwitterΒ /Β X</label><input name="twitter" maxlength="120" value="<?= esc($ch['social']['twitter'] ?? '') ?>"> <label class="lab">TikTok</label><input name="tiktok" maxlength="120" value="<?= esc($ch['social']['tiktok'] ?? '') ?>"> <label class="lab">Instagram</label><input name="instagram" maxlength="120" value="<?= esc($ch['social']['instagram'] ?? '') ?>"> <label class="lab">Facebook</label><input name="facebook" maxlength="120" value="<?= esc($ch['social']['facebook'] ?? '') ?>"> <label class="lab">LinkedIn</label><input name="linkedin" maxlength="120" value="<?= esc($ch['social']['linkedin'] ?? '') ?>"> <label class="lab">Patreon</label><input name="patreon" maxlength="120" value="<?= esc($ch['social']['patreon'] ?? '') ?>"> </div> <hr style="margin:1.6em 0 .9em"> <!-- Tag chips --> <label class="lab">ServiceΒ /Β ProductΒ Tags</label> <div id="tagsHolder"></div> <input type="hidden" id="tags_data" name="tags_data" value="<?= esc(implode(', ', $profile['tags'] ?? [])) ?>"> <input type="text" id="tagInput" placeholder="Add tag & pressΒ Enter"> <label class="lab" style="margin-top:1em">Location Tags</label> <div id="locHolder"></div> <input type="hidden" id="location_tags_data" name="location_tags_data" value="<?= esc(implode(', ', $profile['location_tags'] ?? [])) ?>"> <input type="text" id="locInput" placeholder="Add location tag & pressΒ Enter"> <hr style="margin:1.6em 0 .9em"> <!-- Action buttons --> <p style="margin-top:1.4em"> <button id="saveBtn" type="submit" name="action" value="save">Save changes</button> <?php if ($isAdmin): ?> <?php if ($isPremium): ?> <button type="submit" name="action" value="downgrade">Downgrade to Free</button> <?php else: ?> <button type="submit" name="action" value="upgrade">Upgrade to Premium</button> <?php endif; ?> <?php endif; ?> </p> </form> <?php if (isset($msg)): ?> <div class="msg"><?= esc($msg) ?></div> <?php endif; ?> <?php if ($isAdmin): ?> <form method="post" onsubmit="return confirm('β οΈ Delete ENTIRE social profile β@<?= esc($slug) ?>β ?');"> <input type="hidden" name="delete_social_profile" value="1"> <button type="submit" class="delete-profile-btn">Delete Entire Social Profile</button> </form> <?php endif; ?> </div> <!-- ---------- JS (same helpers as before) --------------------------- --> <script> /* --- influencer toggle --- */ document.getElementById('toggle').addEventListener('click',()=>{ const box=document.getElementById('channels'); box.classList.toggle('collapsed'); document.querySelector('#toggle .arrow').style.transform= box.classList.contains('collapsed')?'':'rotate(90deg)'; }); /* --- chip component (tags) --- */ function Chips(holder, hidden, input){ const h=document.getElementById(holder), hid=document.getElementById(hidden), inp=document.getElementById(input); let arr=hid.value?hid.value.split(',').map(t=>t.trim()).filter(Boolean):[]; function render(){ h.innerHTML=''; arr.forEach((t,i)=>{ const s=document.createElement('span'); s.className='tag'; s.textContent=t; const x=document.createElement('span'); x.className='x'; x.innerHTML='×'; x.onclick=()=>{arr.splice(i,1);update();}; s.appendChild(x);h.appendChild(s); }); } function update(){hid.value=arr.join(', ');render();} inp.addEventListener('keydown',e=>{ if(e.key==='Enter'||e.key===','){e.preventDefault();add(inp.value);} if(e.key==='Backspace'&&!inp.value&&arr.length){arr.pop();update();} }); inp.addEventListener('blur',()=>add(inp.value)); function add(v){ v.split(',').map(t=>t.trim()).filter(Boolean).forEach(x=>{if(!arr.includes(x))arr.push(x);}); inp.value='';update(); } update(); } document.addEventListener('DOMContentLoaded',()=>{ Chips('tagsHolder','tags_data','tagInput'); Chips('locHolder','location_tags_data','locInput'); }); /* --- city / state autocompletion --- */ const usStates = <?= json_encode($usStates) ?>; let cityList=[]; const cityEl=document.getElementById('city'), stateEl=document.getElementById('state'), zipEl=document.getElementById('zip'), latH=document.getElementById('lat'), lonH=document.getElementById('lon'), dlist=document.getElementById('cityList'), msg=document.getElementById('cityMsg'), save=document.getElementById('saveBtn'); function loadCities(st){ if(st.length!==2){dlist.innerHTML='';cityList=[];return;} fetch(`/geo/json-data/${st.toUpperCase()}.json`) .then(r=>r.json()).then(js=>{cityList=js; dlist.innerHTML=js.map(c=>`<option value="${c.city}">`).join('');validate();}); } function validate(){ const found=cityList.find(c=>c.city.toLowerCase()===cityEl.value.trim().toLowerCase()); if(found){ zipEl.value=found.zip||''; latH.value=found.lat; lonH.value=found.lon; msg.textContent=''; save.disabled=false; }else{ msg.textContent='β City not in database'; msg.style.color='#c00000'; save.disabled=true; } } stateEl.addEventListener('blur',()=>loadCities(stateEl.value)); cityEl.addEventListener('blur',validate); loadCities(stateEl.value); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel