Siteβ―Builder
Editing:
modules-perfect.php
writable 0666
<?php /************************************************************************* * USER MODULE CUSTOMISER (frontβend) β v1.4.1 *************************************************************************/ require_once __DIR__.'/lib/auth.php'; session_start(); // login cookie only β no extra session data require_login(); /*βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ * Helpers *βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ*/ function jsonc_load(string $file): array { if (!is_readable($file)) return []; $txt = file_get_contents($file); $txt = preg_replace('!//.*?$!m','',$txt); // remove // comments $txt = preg_replace('!/\\*.*?\\*/!s','',$txt); // remove /* β¦ */ return json_decode($txt, true) ?: []; } function safe_write(string $file, string $txt): void { $tmp = $file.'.tmp'; if (file_put_contents($tmp,$txt,LOCK_EX) === false) { throw new RuntimeException("Cannot write $tmp"); } if (!rename($tmp,$file)) { @unlink($tmp); throw new RuntimeException("Cannot rename $tmp β $file"); } } /*βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ * Paths *βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ*/ $user = current_user(); $slug = strtolower($user['site_slug'] ?: $user['username']); $isBiz = preg_match('/^\d{10}$/',$slug); $root = rtrim($_SERVER['DOCUMENT_ROOT'],'/'); // edit if needed $profileDir = $isBiz ? "$root/ph/$slug/" : "$root/social/$slug/"; $cfgFile = $profileDir.'config.json'; $profFile = $profileDir.'profile.json'; $masterFile = $root.'/pages/modules/modules.json'; // admin master if ($isBiz) { $linkUrl = "https://bestdealon.com/$slug/"; } else { $linkUrl = "https://bestdealon.com/social/$slug/"; } /*βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ * Load JSON data *βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ*/ $cfg = jsonc_load($cfgFile); /* manual profile loader so we get raw values */ $profile = is_file($profFile) ? json_decode(file_get_contents($profFile), true) : []; if (!is_array($profile)) $profile = []; /* master list + admin locks */ $catalog = []; $adminActive = []; // true = allowed for users if (is_readable($masterFile)) { foreach (json_decode(file_get_contents($masterFile), true) as $m) { $name = $m['name']; $catalog[] = $name; $adminActive[$name] = !empty($m['active']); // admin flag } } sort($catalog); /* hero heading/sub never editable here */ $heroHeading = $profile['display_name'] ?? $profile['name'] ?? 'Welcome!'; $heroSub = $profile['slogan'] ?? 'Powered by BestDealOn'; $cfg['hero']['heading'] = $heroHeading; $cfg['hero']['sub'] = $heroSub; $cfg['hero']['bg'] = $cfg['hero']['bg'] ?? '#0066ff'; $cfg['hero']['fg'] = $cfg['hero']['fg'] ?? '#ffffff'; $cfg['site_name'] ??= ($profile['display_name'] ?? 'BestDealOn microsite'); $cfg['tier'] ??= 'free'; /* build current ON/OFF map (visitor copy) */ $currentON = array_fill_keys($catalog,false); foreach ($cfg['modules'] ?? [] as $m) { $currentON[$m['name']] = !empty($m['active']); } $csrf = $_SESSION['csrf'] ??= bin2hex(random_bytes(16)); /*βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ * Ajax save *βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ*/ if ($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['csrf']??'')===$csrf) { try { $in = json_decode($_POST['json'] ?? '', true, 512, JSON_THROW_ON_ERROR); if (!isset($in['modules'])) throw new RuntimeException('Bad JSON'); /* hero colours */ foreach (['bg','fg'] as $k) { if (isset($in['hero'][$k])) $cfg['hero'][$k] = $in['hero'][$k]; } /* rebuild modules respecting admin locks */ $cfg['modules'] = []; foreach ($catalog as $mod) { $userWants = !empty($in['modules'][$mod]); $allowed = $adminActive[$mod] ?? true; $cfg['modules'][] = [ 'name' => $mod, 'active' => $allowed && $userWants ]; } /* prettyβprint + keep oneβliner layout */ $json = json_encode($cfg, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); $lines = array_map(fn($m)=> sprintf(' { "name": "%s", "active": %s }', $m['name'], $m['active'] ? 'true ' : 'false'), $cfg['modules'] ); $block = "\"modules\": [\n\n".implode(",\n",$lines)."\n ]"; if (preg_match('/"modules":/',$json)) { $json = preg_replace('/"modules":\s*\[[^\]]*\]/s',$block,$json,1); } else { $json = rtrim($json,"}\n").",\n $block\n}\n"; } /* save */ if (!is_dir($profileDir) && !@mkdir($profileDir,0775,true)) { throw new RuntimeException("Cannot create $profileDir"); } safe_write($cfgFile,$json); echo '{"ok":1}'; } catch (Throwable $e) { http_response_code(400); echo json_encode(['error'=>$e->getMessage()]); } exit; } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Module Customiser</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> :root{--off:#f8d3d3;--on:#c8f2d9;--dis:#dedede;--gold:#ffb63b; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif} body{margin:0;background:#f5f8fb;color:#123;text-align:center;font-size:18px} h1{margin:1.6rem 0 1.1rem;font-size:1.9rem;font-weight:800;color:#0d305d} .top{background:#eee;padding:.8em 1.2em;font-weight:900} .pickers{display:flex;flex-wrap:wrap;justify-content:center;gap:1.2rem;margin-bottom:1.8rem} .pickers p{margin:0;font-size:1rem;font-weight:600} .pickers label{font-size:.95rem} .pickers input[type=color]{appearance:none;border:none;width:46px;height:28px;border-radius:6px;cursor:pointer} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:1.2rem;max-width:960px;margin:0 auto;padding:0 1rem} .tile{border:3px solid var(--gold);border-radius:22px;padding:1.6rem .8rem 1.2rem;box-shadow:0 4px 18px #dde3fa66;cursor:pointer;transition:.14s} .tile:hover:not(.disabled){transform:translateY(-2px)} .tile h3{margin:0 0 .9rem;font-size:1.05rem;font-weight:700;color:#0d305d} .pill{display:inline-block;padding:.18em 1.2em;border-radius:999px;font-size:.83rem;font-weight:700} .off{background:var(--off);color:#a10000} .on {background:var(--on); color:#00640f} .disabled{background:var(--dis);color:#666;cursor:not-allowed} .debug{font-size:.78rem;color:#555;margin:1.6rem auto;word-break:break-all;max-width:90%} </style> </head> <body> <div class="top"><a href="/" style="text-decoration:none;color:#2a3ca5">BestDealOn</a> » <a href="https://bestdealon.com/members/dashboard.php">Dashboard</a> » <a href="<?=$linkUrl ?>" style="float: right;">ViewΒ Page</a> </div> <h1>Module Customiser</h1> <form id="form"> <div class="pickers"> <p><strong>Heading:</strong> <?= htmlspecialchars($heroHeading) ?></p> <p><strong>Subβtext:</strong> <?= htmlspecialchars($heroSub) ?></p> <label>BG Colour: <input type="color" id="bg" value="<?= htmlspecialchars($cfg['hero']['bg']) ?>"> </label> <label>Text Colour: <input type="color" id="fg" value="<?= htmlspecialchars($cfg['hero']['fg']) ?>"> </label> </div> <div class="grid" id="grid"> <?php foreach ($catalog as $mod): $nice = ucwords(str_replace('-',' ',$mod)); $allowed = $adminActive[$mod] ?? true; $on = $allowed && $currentON[$mod]; $pillCls = $allowed ? ($on ? 'on' : 'off') : 'disabled'; $tileCls = $allowed ? '' : 'disabled'; $txt = $allowed ? ($on ? 'ON' : 'OFF') : 'DISABLED'; ?> <div class="tile <?= $tileCls ?>" data-mod="<?= htmlspecialchars($mod) ?>"> <h3><?= htmlspecialchars($nice) ?></h3> <span class="pill <?= $pillCls ?>"><?= $txt ?></span> </div> <?php endforeach; ?> </div> <input type="hidden" id="csrf" value="<?= htmlspecialchars($csrf) ?>"> </form> <script> const $ = s => document.querySelector(s); let tID; const debounce = (fn,ms) => { clearTimeout(tID); tID = setTimeout(fn,ms); }; ['#bg','#fg'].forEach(sel => $(sel).addEventListener('input', () => debounce(save,200)) ); document.querySelectorAll('.tile').forEach(tile => { if (tile.classList.contains('disabled')) return; // adminβlocked tile.addEventListener('click', () => { const pill = tile.querySelector('.pill'); pill.classList.toggle('on'); pill.classList.toggle('off'); pill.textContent = pill.classList.contains('on') ? 'ON' : 'OFF'; debounce(save,200); }); }); function save(){ const mods = {}; document.querySelectorAll('.tile').forEach(t => { const mod = t.dataset.mod; const pill = t.querySelector('.pill'); mods[mod] = pill.classList.contains('on'); }); fetch(location.href,{ method:'POST', body:new URLSearchParams({ csrf: $('#csrf').value, json: JSON.stringify({ hero:{ bg:$('#bg').value, fg:$('#fg').value }, modules: mods }) }) }) .then(r => r.json()) .then(j => { if(!j.ok) alert('Save failed: '+(j.error||'')); }); } </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel