Site Builder
Editing:
modules-admin-upload.php
writable 0666
<?php /***************************************************************** * modules-admin.php – Global module manager (v4) * ------------------------------------------------------------- * Location: /pages/modules/modules-admin.php * JSON file: /pages/modules/modules.json * * + Drag‑drop ordering * + Active / tier toggle (free • premium • admin) * + Upload new module (modules-<name>.php) * + Delete module (removes folder + JSON row) *****************************************************************/ require_once __DIR__.'/../../members/lib/auth.php'; require_login(); if (current_user()['role']!=='admin') forbidden_page(); /* ───────── paths ───────── */ const MOD_ROOT = __DIR__; // /pages/modules const JSON_FILE = MOD_ROOT.'/modules.json'; /* ───────── helpers ─────── */ function load_json(): array { if (!is_file(JSON_FILE)) return []; return json_decode(file_get_contents(JSON_FILE), true) ?: []; } function save_json(array $rows): void { file_put_contents( JSON_FILE, json_encode($rows, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOCK_EX ); } function fs_scan(array $current): array { $found = []; foreach (glob(MOD_ROOT.'/*', GLOB_ONLYDIR) as $dir) { $name = basename($dir); // qr / coupon / … $file = "modules-$name.php"; if (!is_file("$dir/$file")) continue; $rel = "$name/$file"; $http = "/pages/modules/$rel"; $prev = $current[$name] ?? []; $found[$name] = [ 'name' => $name, 'file' => $file, 'relative_path' => $rel, 'http_path' => $http, 'active' => $prev['active'] ?? false, 'tier' => $prev['tier'] ?? 'free' ]; } /* keep stored order first, new items appended */ $ordered = []; foreach (array_keys($current) as $n) if (isset($found[$n])) $ordered[$n] = $found[$n]; foreach ($found as $n => $r) if (!isset($ordered[$n])) $ordered[$n] = $r; return $ordered; } /* current list ------------------------------------------------*/ $mods = []; foreach (load_json() as $row) $mods[$row['name']] = $row; /* ───────── POST (save / rescan / delete / upload) ─────────── */ $msg = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { /* delete row --------------------------------------------------*/ if (isset($_POST['del']) && isset($mods[$_POST['del']])) { $target = basename($_POST['del']); // folder == module name $folder = MOD_ROOT . "/$target"; /* delete folder (very small, PHP file only) */ if (is_file("$folder/modules-$target.php")) unlink("$folder/modules-$target.php"); @rmdir($folder); unset($mods[$target]); save_json(array_values($mods)); header('Location: modules-admin.php?msg=deleted'); exit; } /* upload ------------------------------------------------------*/ if (isset($_POST['upload'])) { if (!isset($_FILES['modfile']) || $_FILES['modfile']['error']!==UPLOAD_ERR_OK) { $msg = 'Upload failed.'; } else { $name = $_FILES['modfile']['name']; if (!preg_match('/^modules-([a-z0-9_]+)\.php$/i', $name, $m)) { $msg = 'File must be named modules-<name>.php'; } else { $mod = strtolower($m[1]); $destDir = MOD_ROOT."/$mod"; $destFile = "$destDir/modules-$mod.php"; /* simple allow‑list: only PHP text files */ $mime = mime_content_type($_FILES['modfile']['tmp_name']); $body = file_get_contents($_FILES['modfile']['tmp_name'], false, null, 0, 2048); if (strpos($mime,'php')===false && strpos($body,'<?php')===false) { $msg = 'Refused – not a PHP module.'; } else { if (!is_dir($destDir)) mkdir($destDir, 0755, true); if (is_file($destFile) && empty($_POST['replace'])) { $msg = 'Module exists – tick “Replace” to overwrite.'; } else { move_uploaded_file($_FILES['modfile']['tmp_name'], $destFile); /* ensure record */ $mods[$mod] = $mods[$mod] ?? [ 'name'=>$mod,'file'=>"modules-$mod.php", 'relative_path'=>"$mod/modules-$mod.php", 'http_path'=>"/pages/modules/$mod/modules-$mod.php", 'active'=>false,'tier'=>'free' ]; save_json(array_values(fs_scan($mods))); // re‑scan for safety header('Location: modules-admin.php?msg=uploaded'); exit; } } } } } /* rescan ------------------------------------------------------*/ if (isset($_POST['rescan'])) { $mods = fs_scan($mods); save_json(array_values($mods)); header('Location: modules-admin.php?msg=scanned'); exit; } /* save order / flags -----------------------------------------*/ if (isset($_POST['order'])) { $order = explode(',', $_POST['order']); $reordered = []; foreach ($order as $n) if (isset($mods[$n])) $reordered[$n] = $mods[$n]; foreach ($mods as $n=>$r) if (!isset($reordered[$n])) $reordered[$n]=$r; $mods = $reordered; } foreach ($mods as $n=>&$r) { $r['active'] = isset($_POST['act'][$n]); $r['tier'] = in_array($_POST['tier'][$n]??'free',['free','premium','admin'],true) ? $_POST['tier'][$n] : 'free'; } save_json(array_values($mods)); header('Location: modules-admin.php?msg=saved'); exit; } /* keep file‑system + JSON in sync on every load ---------------*/ $mods = fs_scan($mods); save_json(array_values($mods)); /* feedback banner ---------------------------------------------*/ $banners = [ 'saved' => 'Changes saved ✓', 'scanned' => 'Rescan complete – new modules added as inactive ✓', 'deleted' => 'Module removed ✓', 'uploaded' => 'Module uploaded ✓' ]; ?> <!doctype html> <title>Module Manager – BestDealOn</title> <meta name=viewport content="width=device-width,initial-scale=1"> <style> :root{--brand:#0066ff;--bg:#f6f9ff;--fg:#111;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif} body{margin:0;background:var(--bg);color:var(--fg)} main{max-width:900px;margin:2.5rem auto;padding:0 1rem} h1{font-size:1.7rem;margin-top:0} table{width:100%;border-collapse:collapse;margin-top:1.4rem;font-size:.92rem} th,td{padding:.55rem .7rem;border-bottom:1px solid #e2e8f3} th{background:#edf3ff;font-weight:600;text-align:left} .drag{cursor:grab;color:#888;text-align:center;width:24px} .center{text-align:center} button{padding:.55rem 1.3rem;border:none;border-radius:6px;font-weight:600;cursor:pointer;margin-right:.6rem} .save{background:var(--brand);color:#fff} .scan{background:#ffb31c;color:#fff} .del{background:#ff5a5a;color:#fff} select{padding:.35rem .5rem;border:1px solid #ccd2e2;border-radius:6px;background:#fff} .msg{padding:.8rem 1rem;border-radius:8px;margin-top:.9rem;font-weight:600} .ok{background:#e7f9eb;color:#0b8133} @media(prefers-color-scheme:dark){ :root{--bg:#0d1117;--fg:#e6edf3;--brand:#2f81f7} table,th,td{border-color:#30363d} th{background:#1d2b55} select{background:#0d1117;color:var(--fg);border-color:#444c5e} } </style> <main> <h1>Global Module Manager</h1> <?php echo isset($_GET['msg'],$banners[$_GET['msg']]) ? '<div class="msg ok">'.$banners[$_GET['msg']].'</div>' : ''; echo $msg ? '<div class="msg" style="background:#ffecec;color:#c00">'.$msg.'</div>' : ''; ?> <form id="frm" method="post" enctype="multipart/form-data"> <input type="hidden" name="order" id="orderField"> <table id="modTable"> <tr> <th class=drag>≡</th><th>Name</th><th>PHP file</th> <th class=center>Active?</th><th class=center>Tier</th><th class=center>Del</th> </tr> <?php foreach($mods as $row): $n=htmlspecialchars($row['name']); ?> <tr data-name="<?= $n ?>"> <td class="drag">≡</td> <td><?= $n ?></td> <td><code><?= htmlspecialchars($row['relative_path']) ?></code></td> <td class=center> <input type=checkbox name="act[<?= $n ?>]" <?= $row['active']?'checked':'' ?>> </td> <td class=center> <select name="tier[<?= $n ?>]"> <?php foreach(['free','premium','admin'] as $t): ?> <option value="<?= $t ?>" <?= $row['tier']===$t?'selected':'' ?>><?= $t ?></option> <?php endforeach; ?> </select> </td> <td class=center> <button class="del" name="del" value="<?= $n ?>" onclick="return confirm('Delete module <?= $n ?> ?')">❌</button> </td> </tr> <?php endforeach; ?> </table> <p style="margin-top:1.4rem"> <button class=save>💾 Save changes</button> <button class=scan name=rescan value=1>🔍 Scan for new modules</button> </p> <fieldset style="border:1px dashed #aac;margin-top:2.2rem;padding:1rem 1.3rem"> <legend><strong>Add / replace module</strong></legend> <input type="file" name="modfile" accept="application/x-httpd-php,.php" required> <label style="margin-left:.6rem"><input type=checkbox name=replace> Replace if exists</label> <button name="upload" value="1" style="margin-left:1rem">Upload</button> <p class="small" style="font-size:.8rem;margin:.6rem 0 0"> File must be named <code>modules‑name.php</code>. “name” becomes the folder. </p> </fieldset> </form> <p style="margin-top:2rem"><a href="/members/dashboard.php">← Back to dashboard</a></p> </main> <script> /* drag‑n‑drop order ------------------------------------------------- */ const table=document.getElementById('modTable');let drag; table.addEventListener('pointerdown',e=>{ const h=e.target.closest('.drag'); if(!h) return; drag=h.parentElement; drag.classList.add('dragging'); e.preventDefault();}); table.addEventListener('pointermove',e=>{ if(!drag) return; const rows=[...table.querySelectorAll('tr[data-name]:not(.dragging)')]; const after=rows.find(r=>e.clientY<r.getBoundingClientRect().top+r.offsetHeight/2); table.tBodies[0].insertBefore(drag,after||null); }); table.addEventListener('pointerup',()=>{if(drag){drag.classList.remove('dragging');drag=null}}); document.getElementById('frm').onsubmit=()=>{ const names=[...table.querySelectorAll('tr[data-name]')].map(r=>r.dataset.name); document.getElementById('orderField').value=names.join(','); }; </script> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel