Site Builder
Editing:
index.php
writable 0666
<?php /************************************************************************** * UNIVERSAL COUPON BUILDER (business + social) © 2025 BestDealOn * ---------------------------------------------------------------------- * • Works with /ph/<10‑digit>/coupon.json (?ph=##########) * and /social/<slug>/coupon.json (?user=<slug>) * • Drop this file in /coupon/ — it looks **one level up** for the * data folders. Adjust DATA_ROOT below if your structure differs. **************************************************************************/ const DATA_ROOT = __DIR__ . '/..'; // <-- change if needed /* ---------- locate profile & security -------------------------------- */ $phone = preg_replace('/\D/', '', $_GET['ph'] ?? ''); $slug = preg_replace('/[^a-z0-9_]/i', '', $_GET['user'] ?? ''); if ($phone !== '') { // business if (strlen($phone) !== 10) { http_response_code(400); exit('Bad phone'); } $mode = 'biz'; $dir = DATA_ROOT . "/ph/$phone"; $paid = is_file("$dir/business.json"); // premium flag $niceId = $phone; } elseif ($slug !== '') { // social $mode = 'social'; $dir = DATA_ROOT . "/social/$slug"; $paid = is_file("$dir/social.json"); if (!$paid && !is_file("$dir/new-social.json")) { http_response_code(404); exit; } $niceId = '@'.$slug; } else { http_response_code(400); exit('Missing ?ph= or ?user='); } if (!is_dir($dir)) { http_response_code(404); exit('Profile not found'); } $cpFile = "$dir/coupon.json"; /* ---------- helpers -------------------------------------------------- */ function esc(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); } function normalise(array $a): array { foreach ($a as &$c) { $c = [ 'title' => $c['title'] ?? '', 'desc' => $c['desc'] ?? '', 'code' => $c['code'] ?? '', 'link' => $c['link'] ?? '', 'expiry' => $c['expiry'] ?? '', 'active' => ($c['active'] ?? true) ? true : false ]; } return $a; } function loadCoupons(string $f): array { if (!is_file($f)) return []; $j = json_decode(file_get_contents($f), true, 512, JSON_THROW_ON_ERROR); if (array_is_list($j)) return normalise($j); /* legacy object {0:{},1:{}} */ $tmp = []; foreach ($j as $k => $v) if (is_numeric($k)) $tmp[(int)$k] = $v; if (!$tmp) $tmp[] = $j; ksort($tmp); return normalise(array_values($tmp)); } function saveCoupons(string $f, array $a): void { file_put_contents($f, json_encode($a, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX); } /* ---------- REST‑ish API --------------------------------------------- */ header('X-Robots-Tag: noindex'); // builder pages shouldn’t be indexed if (isset($_GET['fetch'])) { // GET ?…&fetch=1 header('Content-Type: application/json'); echo json_encode(['success' => true, 'coupons' => loadCoupons($cpFile)]); exit; } if (isset($_GET['download']) && $paid) { // raw JSON download for owner header('Content-Type: application/json'); header('Content-Disposition: attachment; filename="coupons-'.$niceId.'.json"'); echo json_encode(loadCoupons($cpFile), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST' && $paid) { header('Content-Type: application/json'); $in = json_decode(file_get_contents('php://input'), true); $act = $in['action'] ?? 'save'; $idx = intval($in['index'] ?? 0); $arr = loadCoupons($cpFile); /* -------- bulk upload ------------------------------------------- */ if ($act === 'upload') { $raw = $in['blob'] ?? ''; $test = json_decode($raw, true); if (!is_array($test)) { echo '{"success":false}'; exit; } saveCoupons($cpFile, normalise(array_values($test))); echo json_encode(['success' => true, 'coupons' => loadCoupons($cpFile), 'index' => 0]); exit; } /* -------- delete one ------------------------------------------- */ if ($act === 'delete') { if (isset($arr[$idx])) { array_splice($arr, $idx, 1); saveCoupons($cpFile, $arr); } echo json_encode(['success' => true, 'coupons' => loadCoupons($cpFile), 'index' => 0]); exit; } /* -------- save / new ------------------------------------------- */ $new = [ 'title' => trim($in['title'] ?? ''), 'desc' => trim($in['desc'] ?? ''), 'code' => trim($in['code'] ?? ''), 'link' => trim($in['link'] ?? ''), 'expiry' => trim($in['expiry']?? ''), 'active' => true ]; foreach ($arr as $k => $c) { // duplicate title check if (strcasecmp($c['title'], $new['title']) === 0 && ($act === 'new' || $k !== $idx)) { echo json_encode(['success' => false, 'msg' => 'Duplicate title']); exit; } } if ($act === 'new') { $arr[] = $new; $idx = array_key_last($arr); } else { $arr[$idx] = $new; } saveCoupons($cpFile, $arr); echo json_encode(['success' => true, 'coupons' => loadCoupons($cpFile), 'index' => $idx]); exit; } /* ---------- page ----------------------------------------------------- */ ?><!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Coupon Builder – <?= esc($niceId) ?></title> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="preconnect" href="https://fonts.gstatic.com"> <style> /* -------------------------------------------------------------------- EXACT ORIGINAL STYLING (plus small toast block at bottom) ------------------------------------------------------------------ */ :root{--bg:#f6f8fb;--fg:#233;--accent:#2357d7;--accent-dk:#0f47c6;--gold:#ef8f13;--danger:#c00} *{box-sizing:border-box} body{margin:0;font-family:system-ui,Arial,sans-serif;background:var(--bg);color:var(--fg)} .top{background:#eee;padding:.8em 1.2em;font-weight:900} h1{font-size:1.45rem;margin:0} .container{max-width:760px;margin:2rem auto;padding:0 1rem} /* form + preview shell */ .form-card{background:#fffbe6;border:2px solid #ffd973;padding:2rem;border-radius:18px;box-shadow:0 4px 20px #f5db9c3c} label{font-weight:600;display:block;margin:.75rem 0 .35rem} input,textarea,select{width:100%;padding:.5rem;font-size:1rem;border:1px solid #b7c2df;border-radius:7px} textarea{resize:vertical} button{padding:.55rem 1.2rem;font-size:1rem;border:none;border-radius:8px;font-weight:700;cursor:pointer} .primary{background:var(--gold);color:#fff}.primary:hover{background:#d97706} .alt{background:#aac8ff;color:#103e9d}.alt:hover{background:#8ab4ff} .delete{background:#ffe4e4;color:var(--danger)}.delete:hover{background:#ffd0d0} .btn-row{display:flex;flex-wrap:wrap;gap:.8rem;margin-top:1.2rem}.btn-row .delete{margin-left:auto} /* selector row */ #selectWrap{margin:1.3rem 0}#selectWrap select{max-width:480px} .badge{display:inline-block;padding:.35rem .75rem;border-radius:12px;font-size:.78rem;font-weight:700;color:#fff;background:#2e7d32;margin-left:.5rem} /* preview */ .preview{background:#fff;border:3px solid #ffb63b;border-radius:26px;padding:2rem;max-width:500px;margin:auto;box-shadow:0 8px 28px #c59e6933} .preview h2{margin:.1rem 0 .55rem;color:#df6200;font-size:1.4rem} .code{display:inline-block;padding:.35rem 2rem;border:2px dashed #ffbe4e;border-radius:9px;font-weight:700;background:#ffefc1;color:#a95600;font-family:monospace} .desc{color:#56411e;margin-bottom:1.05rem;line-height:1.45} .desc.collapsed{max-height:4.6rem;overflow:hidden;position:relative} .desc.collapsed:after{content:'';position:absolute;right:0;bottom:0;width:90%;height:1.9rem;background:linear-gradient(to top,#fff 0%,rgba(255,255,255,0) 100%)} .read-more{color:#b0640d;text-decoration:underline;cursor:pointer;font-weight:600} .get-btn{display:inline-block;margin:.9rem 0 0;background:var(--accent);color:#fff;padding:.55rem 1.6rem;border-radius:8px;font-weight:700;text-decoration:none} .get-btn:hover{background:var(--accent-dk)} .toolrow{display:flex;justify-content:flex-end;gap:.9rem;margin:.6rem 0} .btn-mini{background:#ffeebb;box-shadow:0 1px 5px #ffeebb50}.btn-mini:hover{background:#ffd670} .notice{margin-top:.9rem;font-weight:600}.notice.ok{color:#0a7b38}.notice.err{color:var(--danger)} @media(max-width:600px){.container{padding:0 .6rem}.preview{max-width:95vw;padding:1.4rem}} /* toast (small addition for Saved ✓ pop‑ups) */ .toast{position:fixed;bottom:1.2rem;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:.6rem 1.2rem;border-radius:9px;font-weight:600;font-size:.9rem;opacity:0;transition:opacity .25s} .toast.show{opacity:1} </style> </head> <body> <div class="top"><a href="/" style="text-decoration:none;color:#2a3ca5">BestDealOn</a> » Coupon Builder</div> <div class="container"> <h1>Coupon Creator for <?= esc($niceId) ?></h1> <div id="selectWrap"><label>Select coupon to edit<br> <select id="couponSelect"></select></label></div> <form id="couponForm" class="form-card" autocomplete="off"> <input type="hidden" name="index" value="0"> <label>Coupon Headline / Title<input name="title" maxlength="100" required></label> <label>Details / Description<textarea name="desc" rows="4" required></textarea></label> <label>Coupon Code<input name="code" maxlength="40" required></label> <label>Referral / Deal Link<input name="link" placeholder="https://example.com/deal"></label> <label>Expires<input type="date" name="expiry" id="expiryField"></label> <div class="btn-row"> <button class="primary" type="submit" data-act="save">Save changes</button> <button class="alt" type="submit" data-act="new" id="newBtn" style="display:none">Save as new</button> <button class="delete" type="button" id="delBtn">✖</button> </div> </form> <div style="margin-top:2.3rem"> <div class="toolrow"><button class="btn-mini" id="printBtn">🖨️ Print</button><button class="btn-mini" id="shareBtn">📤 Share</button></div> <div class="preview" id="preview"> <h2 id="pvTitle">Your Coupon Headline Here</h2> <div class="desc collapsed" id="pvDesc"></div><span id="readMore" class="read-more" style="display:none">read more</span> <div style="display:flex;gap:.5rem;margin:.85rem 0"><strong>Coupon Code:</strong><span class="code" id="pvCode">BESTDEAL</span></div> <a id="pvLink" class="get-btn" style="display:none" target="_blank" rel="noopener">Get Deal Now »</a> <p style="margin-top:1rem"><strong>Expires:</strong> <span id="pvExp">No Expiry</span></p> </div> </div> <div style="margin:2.5rem auto 0;max-width:500px;text-align:center"> <a class="btn-mini" style="text-decoration:none;padding:.55rem 1.3rem" id="downloadBtn">📥 Download JSON</a><br> <label style="display:block;margin-top:1rem;font-weight:600">Restore from file (.json): <input type="file" id="uploadInput" accept=".json,application/json"> </label> <div id="notice" class="notice"></div> </div> </div> <div id="toast" class="toast"></div> <script> /* ---------- helpers ---------- */ const p = new URLSearchParams(location.search); const ph = p.get('ph') || ''; const usr = p.get('user') || ''; const qs = ph ? 'ph='+encodeURIComponent(ph) : 'user='+encodeURIComponent(usr); const storeKey = 'couponData_' + (ph || usr || 'default'); const $ = q => document.querySelector(q); const toast = msg => { const t=$('#toast'); t.textContent=msg; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),2800); }; /* ---------- server helper (returns null on network / auth error) ----- */ async function talk(body){ try{ const url = '?'+qs + (body ? '' : '&fetch=1'); const opt = body? {method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)} : undefined; const r = await fetch(url, opt); if(!r.ok) throw 0; return await r.json(); }catch(e){ return null; }} /* ---------- data ---------- */ let coupons=[], cur=0; async function load(){ const srv = await talk(null); if(srv && Array.isArray(srv.coupons)) coupons=srv.coupons; else try{ coupons=JSON.parse(localStorage.getItem(storeKey))||[]; }catch{ coupons=[]; } if(!coupons.length) coupons=[{title:'',desc:'',code:'',link:'',expiry:''}]; buildSelect(); select(0); } function saveLocal(){ localStorage.setItem(storeKey, JSON.stringify(coupons)); } function buildSelect(){ const sel=$('#couponSelect'); sel.innerHTML=''; coupons.forEach((c,i)=>{ const o=document.createElement('option'); o.value=i; o.textContent=c.title||`Coupon ${i+1}`; sel.appendChild(o); }); $('#selectWrap').style.display = coupons.length>1 ? 'block' : 'none'; } /* ---------- select & preview ---------- */ function select(i){ cur=i; $('[name=index]').value=i; const c=coupons[i]||{}; ['title','desc','code','link','expiry'].forEach(k=> $('[name='+k+']').value=c[k]||''); preview(); } $('#couponSelect').onchange=e=>select(+e.target.value); function preview(){ const v=n=> $('[name='+n+']').value.trim(); $('#pvTitle').textContent = v('title') || 'Your Coupon Headline Here'; $('#pvCode').textContent = v('code') || 'BESTDEAL'; $('#pvExp').textContent = v('expiry')|| 'No Expiry'; const d=v('desc'), pd=$('#pvDesc'), lim=170; const rm=$('#readMore'); if(d.length>lim){ pd.textContent=d.slice(0,lim)+'…'; pd.classList.add('collapsed'); rm.style.display=''; rm.onclick=()=>{pd.textContent=d; pd.classList.remove('collapsed'); rm.style.display='none';}; }else{ pd.textContent=d||'Your best deal description will appear here.'; pd.classList.remove('collapsed'); rm.style.display='none'; } const l=v('link'); if(l){ $('#pvLink').href=l.startsWith('http')?l:`https://${l}`; $('#pvLink').style.display='inline-block'; } else $('#pvLink').style.display='none'; const dup=coupons.some((c,i)=>i!==cur&&c.title.toLowerCase()===v('title').toLowerCase()); $('#newBtn').style.display=v('title')&&!dup&&v('title')!==coupons[cur].title?'inline-block':'none'; } document.querySelectorAll('.form-card input,.form-card textarea') .forEach(el=>el.addEventListener('input',preview)); /* ---------- save / new ---------- */ $('#couponForm').onsubmit=async e=>{ e.preventDefault(); const act = document.activeElement.dataset.act || 'save'; const obj = {}; new FormData(e.target).forEach((v,k)=>obj[k]=v.trim()); obj.action = act; let srv = await talk(obj); if(srv && srv.success){ coupons=srv.coupons; cur=srv.index; toast('Saved ✓'); }else{ /* offline fallback */ if(act==='save') coupons[cur]=obj; else { delete obj.action; coupons.push(obj); cur=coupons.length-1; } saveLocal(); toast('Saved ‑ offline ✓'); } buildSelect(); select(cur); $('#couponSelect').value=cur; }; /* ---------- delete ---------- */ $('#delBtn').onclick=async ()=>{ if(!confirm('Delete this coupon?')) return; let srv = await talk({action:'delete',index:cur}); if(srv && srv.success) coupons=srv.coupons; else { coupons.splice(cur,1); if(!coupons.length) coupons=[{title:'',desc:'',code:'',link:'',expiry:''}]; saveLocal(); } toast('Deleted ✓'); buildSelect(); select(0); }; /* ---------- download / upload ---------- */ $('#downloadBtn').onclick=()=>{ const blob=new Blob([JSON.stringify({coupons},null,2)],{type:'application/json'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=(ph||usr||'coupons')+'.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); }; $('#uploadInput').onchange=async e=>{ const f=e.target.files[0]; if(!f) return; try{ const raw=await f.text(); const test=JSON.parse(raw); if(!test.coupons||!Array.isArray(test.coupons)) throw 0; let srv=await talk({action:'upload',blob:raw}); coupons = (srv && srv.success)? srv.coupons : test.coupons; if(!(srv&&srv.success)) saveLocal(); toast('Imported ✓'); buildSelect(); select(0); }catch{ $('#notice').textContent='Invalid JSON'; $('#notice').className='notice err'; } }; /* ---------- share / print ---------- */ $('#printBtn').onclick=()=>{ const w=open('','','width=650,height=800'); w.document.write(` <html><head><title>Print</title><style>${document.querySelector('style').innerHTML}</style></head> <body>${$('#preview').outerHTML}</body></html>`); w.document.close(); setTimeout(()=>{w.print();w.close();},300); }; $('#shareBtn').onclick=async e=>{ const url=location.href; try{ if(navigator.share) await navigator.share({title:'My Coupon',url}); else await navigator.clipboard.writeText(url); e.target.textContent='✅ Copied!'; setTimeout(()=>e.target.textContent='📤 Share',1400); }catch{} }; load(); </script> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel