Site Builder
Editing:
edit.php
writable 0666
<?php /************************************************************************** * EDIT SOCIAL PROFILE – /social/edit.php * ---------------------------------------------------------------------- * © 2025 BestDealOn – Free to modify. Requires PHP 8.1+ *************************************************************************/ declare(strict_types=1); /* ---------- CONFIG ---------------------------------------------------- */ $rootDir = $_SERVER['DOCUMENT_ROOT']; $socialDir = $rootDir . '/social/'; $boundsFile = $rootDir . '/geo/json-data/states-bounds.json'; date_default_timezone_set('UTC'); /* ---------- helper: US state list ------------------------------------ */ $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']); } } } /* ---------- helper: normalise channel 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, '@/ '); // strip @ or / if typed 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; $filePrem = "$dir/social.json"; $fileFree = "$dir/new-social.json"; $jsonFile = null; $isPremium = false; if (is_file($filePrem)) { $jsonFile = $filePrem; $isPremium = true; } elseif (is_file($fileFree)) { $jsonFile = $fileFree; } $error = (!$jsonFile && $slug) ? "No profile for @$slug was found." : ''; /* ---------- load JSON ------------------------------------------------- */ $profile = []; if (!$error) { $profile = json_decode(file_get_contents($jsonFile), true, 512, JSON_THROW_ON_ERROR); if (!$profile) $error = 'Profile data corrupted.'; } /* ---------- 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'] ?? '')); if ($display === '' || $email === '') { $error = 'Display name and email are required.'; } elseif (!in_array($state, $usStates, true)) { $error = 'Invalid U.S. state.'; } else { // verify city belongs in state list $cityFile = $rootDir . "/geo/json-data/$state.json"; $cities = is_file($cityFile) ? json_decode(file_get_contents($cityFile), true) : []; $valid = false; foreach ($cities as $c) { if (strcasecmp($c['city'], trim($_POST['city'] ?? '')) === 0) { $valid = true; break; } } if (!$valid) $error = 'City not recognised for that state.'; } if (!$error) { /* ------------ build record ---------------------------------- */ $profile = array_merge($profile, [ 'display_name' => $display, 'email' => $email, 'slogan' => mb_substr(trim($_POST['slogan'] ?? ''), 0, 160), 'description' => mb_substr(trim($_POST['description'] ?? ''), 0, 240), /* 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'] ?? ''), /* channels – normalise to full URLs */ '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 */ $wantPrem = $isPremium; if ($action === 'upgrade') $wantPrem = true; if ($action === 'downgrade') $wantPrem = false; $profile['premium'] = $wantPrem; $target = $dir . ($wantPrem ? '/social.json' : '/new-social.json'); if ($target !== $jsonFile) { @rename($jsonFile, $target); $jsonFile = $target; } file_put_contents($jsonFile, json_encode($profile, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX ); header("Location: /social/view.php?user=$slug"); exit; } } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Edit <?= htmlspecialchars($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:#0077ff;--del:#d60000} body{background:var(--bg);margin:0;font-family:system-ui,Arial,sans-serif;color:var(--fg)} h1{font-size:1.55rem;margin:.7em 0} .lab{display:block;margin:.4em 0 .15em;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.25em;border:0;border-radius:4px;background:var(--accent);color:#fff;font-size:1.04em;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:1em 0} .collapsed{display:none} .arrow{display:inline-block;transition:.18s transform} #cityMsg{font-size:.88em;margin-left:.6em} </style> </head> <script src="/geo/geofence-guard.js" defer></script> <body> <div style="max-width:700px;margin:1.5em auto;padding:1.25em 1.7em;background:#fff;border-radius:8px;box-shadow:0 2px 7px rgba(0,0,0,.06)"> <?php if ($error): ?> <h1>Profile not found</h1> <p><?= htmlspecialchars($error) ?></p> <?php if ($slug): ?><p><a href="/social/add.php">Create it now »</a></p><?php endif; ?> </div></body></html><?php exit; ?> <?php endif; ?> <h1>Edit Profile – <?= htmlspecialchars($profile['display_name'] ?: "@$slug") ?></h1> <?php if ($isPremium): ?><p style="color:#d48c00;font-weight:600"><i class="fas fa-star"></i> Premium listing</p><?php endif; ?> <?php if ($error): ?><p style="color:#c00;font-weight:600"><?= htmlspecialchars($error) ?></p><?php endif; ?> <form id="editForm" method="post" autocomplete="off"> <input type="hidden" name="user" value="<?= htmlspecialchars($slug) ?>"> <input type="hidden" id="lat" name="lat" value="<?= htmlspecialchars($profile['lat'] ?? '') ?>"> <input type="hidden" id="lon" name="lon" value="<?= htmlspecialchars($profile['lon'] ?? '') ?>"> <!-- handle --> <label class="lab">Handle</label><input class="locked-input" readonly value="<?= htmlspecialchars($profile['handle']) ?>"> <!-- display / email --> <label class="lab">Display Name</label><input name="display_name" required maxlength="80" value="<?= htmlspecialchars($profile['display_name']) ?>"> <label class="lab">Email</label><input name="email" type="email" required maxlength="120" value="<?= htmlspecialchars($profile['email']) ?>"> <!-- phone / website --> <label class="lab">Phone (optional)</label><input name="phone" maxlength="16" value="<?= htmlspecialchars($profile['phone'] ?? '') ?>"> <label class="lab">Website (optional)</label><input name="website" maxlength="120" value="<?= htmlspecialchars($profile['website'] ?? '') ?>"> <!-- slogan / description --> <label class="lab">Slogan</label><input name="slogan" maxlength="160" value="<?= htmlspecialchars($profile['slogan'] ?? '') ?>"> <label class="lab">Short Description</label><textarea name="description" rows="3" maxlength="240"><?= htmlspecialchars($profile['description'] ?? '') ?></textarea> <!-- address --> <label class="lab">Street Address (optional)</label><input name="address" maxlength="100" value="<?= htmlspecialchars($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="<?= htmlspecialchars($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="<?= htmlspecialchars($profile['state']) ?>"> </div> <div style="flex:1.3"> <label class="lab">ZIP</label> <input id="zip" name="zip" required maxlength="12" value="<?= htmlspecialchars($profile['zip']) ?>"> </div> </div> <p id="cityMsg"></p> <hr style="margin:1.5em 0 .9em"> <!-- channels --> <?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="<?= htmlspecialchars($ch['video']['youtube'] ?? '') ?>"> <label class="lab">Rumble</label><input name="rumble" maxlength="120" value="<?= htmlspecialchars($ch['video']['rumble'] ?? '') ?>"> <label class="lab">Odysee</label><input name="odysee" maxlength="120" value="<?= htmlspecialchars($ch['video']['odysee'] ?? '') ?>"> <h3 style="margin:1em 0 .3em">Podcast</h3> <label class="lab">Spotify</label><input name="spotify" maxlength="120" value="<?= htmlspecialchars($ch['podcast']['spotify'] ?? '') ?>"> <label class="lab">Apple Podcasts</label><input name="apple" maxlength="120" value="<?= htmlspecialchars($ch['podcast']['apple'] ?? '') ?>"> <label class="lab">Podcast RSS</label><input name="podcast_rss" maxlength="120" value="<?= htmlspecialchars($ch['podcast']['rss'] ?? '') ?>"> <h3 style="margin:1em 0 .3em">Social</h3> <label class="lab">Twitter/X</label><input name="twitter" maxlength="120" value="<?= htmlspecialchars($ch['social']['twitter'] ?? '') ?>"> <label class="lab">TikTok</label><input name="tiktok" maxlength="120" value="<?= htmlspecialchars($ch['social']['tiktok'] ?? '') ?>"> <label class="lab">Instagram</label><input name="instagram" maxlength="120" value="<?= htmlspecialchars($ch['social']['instagram'] ?? '') ?>"> <label class="lab">Facebook</label><input name="facebook" maxlength="120" value="<?= htmlspecialchars($ch['social']['facebook'] ?? '') ?>"> <label class="lab">LinkedIn</label><input name="linkedin" maxlength="120" value="<?= htmlspecialchars($ch['social']['linkedin'] ?? '') ?>"> <label class="lab">Patreon</label><input name="patreon" maxlength="120" value="<?= htmlspecialchars($ch['social']['patreon'] ?? '') ?>"> </div> <hr style="margin:1.5em 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="<?= htmlspecialchars(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="<?= htmlspecialchars(implode(', ', $profile['location_tags'] ?? [])) ?>"> <input type="text" id="locInput" placeholder="Add location tag & press Enter"> <hr style="margin:1.5em 0 .9em"> <p style="margin-top:1.4em"> <button id="saveBtn" type="submit" name="action" value="save">Save changes</button> <?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; ?> <a href="/social/view.php?user=<?= urlencode($slug) ?>">cancel</a> </p> </form> </div> <script> /* -------- toggle influencer list -------- */ 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 --------------- */ function Chips(holderId, hiddenId, inputId){ const h=document.getElementById(holderId), hid=document.getElementById(hiddenId), inp=document.getElementById(inputId); let arr=hid.value?hid.value.split(',').map(t=>t.trim()).filter(Boolean):[]; const 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); }); }; const 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); }); const 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'), saveBtn=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(''); validateCity(); }); } function validateCity(){ 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=''; saveBtn.disabled=false; }else{ msg.textContent='❌ City not in database'; msg.style.color='#d60000'; saveBtn.disabled=true; } } stateEl.addEventListener('blur',()=>loadCities(stateEl.value)); cityEl.addEventListener('blur',validateCity); loadCities(stateEl.value); // initial </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel