Siteβ―Builder
Editing:
index.php
writable 0666
<?php require_once __DIR__.'/../members/lib/auth.php'; require_login(); if (current_user()['role'] !== 'admin') forbidden_page(); /* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ paths/index.php β’ Complete selfβcontained path manager + picker UI Creates/maintains: β paths.json (dynamic list of named paths) β file_paths.php (DEFINEβbased constants for inclusion elsewhere) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ /* ββ AJAX: directory listing ββββββββββββββββββββββββββββββββββββββββββββββββ */ if (isset($_GET['ajax']) && $_GET['ajax'] === 'dirs') { $root = realpath($_SERVER['DOCUMENT_ROOT']); // change if preferred $requested = realpath($_GET['dir'] ?? $root) ?: $root; if (strpos($requested, $root) !== 0) $requested = $root; // sandbox $items = array_filter(scandir($requested), function ($f) use ($requested) { return $f !== '.' && $f !== '..' && is_dir($requested . DIRECTORY_SEPARATOR . $f); }); $response = [ 'cwd' => $requested, 'root' => $root, 'dirs' => array_values($items) ]; header('Content-Type: application/json'); echo json_encode($response); exit; } /* ββ storage files ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ $pathsFile = __DIR__ . '/paths.json'; $phpConstsFile = __DIR__ . '/file_paths.php'; /* ββ load current paths βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ $paths = is_file($pathsFile) ? json_decode(file_get_contents($pathsFile), true) : []; /* ββ actions ---------------------------------------------------------------- */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { /* add / update path */ if (isset($_POST['add_path'])) { $name = trim($_POST['new_path_name'] ?? ''); $value = trim($_POST['new_path_value'] ?? ''); if ($name && $value) { $paths[$name] = $value; file_put_contents($pathsFile, json_encode($paths, JSON_PRETTY_PRINT)); } } /* delete path */ if (isset($_POST['delete_path']) && isset($paths[$_POST['delete_path']])) { unset($paths[$_POST['delete_path']]); file_put_contents($pathsFile, json_encode($paths, JSON_PRETTY_PRINT)); } /* import JSON */ if (isset($_POST['import_json']) && isset($_FILES['json_file']) && $_FILES['json_file']['error'] === UPLOAD_ERR_OK) { $uploaded = json_decode(file_get_contents($_FILES['json_file']['tmp_name']), true); if (is_array($uploaded)) { $paths = $uploaded; file_put_contents($pathsFile, json_encode($paths, JSON_PRETTY_PRINT)); } } /* export PHP constants */ if (isset($_POST['export_php'])) { $content = "<?php\n"; foreach ($paths as $k => $v) { $content .= "define('" . strtoupper($k) . "', '" . addslashes($v) . "');\n"; } file_put_contents($phpConstsFile, $content); } /* export JSON for download */ if (isset($_POST['export_json'])) { header('Content-Type: application/json'); header('Content-Disposition: attachment; filename="paths.json"'); echo json_encode($paths, JSON_PRETTY_PRINT); exit; } header('Location: ' . $_SERVER['REQUEST_URI']); // PRG pattern exit; } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Path Manager</title> <style> body{font-family:sans-serif;margin:2rem} ul{padding-left:1rem} li{margin:.25rem 0} button{margin-left:.5rem} .modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5)} .modal-content{background:#fff;margin:5% auto;padding:1rem;width:70%;max-height:80vh;overflow:auto} .dir-item{cursor:pointer;padding:.2rem .4rem} .dir-item:hover{background:#eee} .dir-path{font-size:.9rem;color:#555} :root{ /* ---------- Color system ---------- */ --hue : 210; --sat : 15%; --light : 18%; --bg : hsl(var(--hue) var(--sat) var(--light)); /* #1e242b */ --surface : hsl(var(--hue) 15% 26%); /* #2a313a */ --surface-alt : hsl(var(--hue) 18% 32%); --stroke : hsl(var(--hue) 12% 38%); --text-primary : hsl(var(--hue) 10% 94%); --text-secondary : hsl(var(--hue) 10% 70%); --accent : hsl(195 100% 46%); /* Azure */ --accent-hover : hsl(195 100% 35%); --danger : hsl(0 75% 60%); --danger-hover : hsl(0 80% 50%); --radius : .55rem; --shadow : 0 6px 18px rgba(0,0,0,.35); --trans : .24s cubic-bezier(.45,.25,.18,1); --font-sans : "Inter",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif; } @media (prefers-color-scheme: light){ :root{ --light : 96%; --bg : hsl(var(--hue) var(--sat) var(--light)); /* #f1f4f8 */ --surface : #fff; --surface-alt : hsl(var(--hue) 20% 92%); --stroke : hsl(var(--hue) 10% 83%); --text-primary : hsl(var(--hue) 25% 10%); --text-secondary:hsl(var(--hue) 15% 38%); --shadow : 0 4px 12px rgba(0,0,0,.08); } } /* ---------- Global reset ---------- */ *, *::before, *::after{box-sizing:border-box;margin:0;padding:0} html{font-size:16px} body{ font-family:var(--font-sans); color:var(--text-primary); background:var(--bg); line-height:1.55; min-height:100vh; display:flex; flex-direction:column; align-items:center; padding:2.5rem 1rem; } /* ---------- Headings ---------- */ h1,h2,h3{font-weight:600;margin-bottom:1rem} h1{font-size:1.95rem} h2{font-size:1.225rem;margin-top:2.25rem} /* ---------- Containers ---------- */ form, .modal-content{ background:var(--surface); border:1px solid var(--stroke); border-radius:var(--radius); padding:1.5rem; width:100%; max-width:760px; box-shadow:var(--shadow); } ul{list-style:none;margin-top:.75rem} li{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:.6rem .4rem;border-bottom:1px solid var(--stroke)} li:last-child{border-bottom:none} strong{flex:1 1 180px;font-weight:600;color:var(--text-secondary)} .dir-path{flex:3 1 300px;font-family:monospace;overflow:auto} /* ---------- Inputs & Buttons ---------- */ label{display:flex;flex-direction:column;gap:.35rem;margin-bottom:1rem;flex:1} input[type=text]{padding:.6rem .8rem;border:1px solid var(--stroke);border-radius:var(--radius);background:var(--surface-alt);color:var(--text-primary);transition:var(--trans)} input[type=text]:focus{outline:none;border-color:var(--accent);background:var(--surface)} button{ cursor:pointer; border:none; border-radius:var(--radius); padding:.55rem 1rem; font-weight:600; font-size:.9rem; transition:var(--trans); color:#fff; background:var(--accent); } button:hover{background:var(--accent-hover)} button:disabled{opacity:.45;cursor:not-allowed} /* danger buttons */ button[name="delete_path"]{background:var(--danger)} button[name="delete_path"]:hover{background:var(--danger-hover)} /* icon buttons (picker / delete) */ button[name="delete_path"], button[onclick^="openPicker"]{padding:.35rem .6rem;font-size:.85rem} /* grouped inline buttons */ form[style*="display:inline"] button{margin-left:.25rem} /* ---------- Modal ---------- */ .modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:50;align-items:center;justify-content:center} .modal.show{display:flex} .modal-content{ width:90%; max-width:640px; max-height:90vh; overflow-y:auto; animation:fadeIn .3s ease; } @keyframes fadeIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}} .dir-item{padding:.45rem .6rem;border-radius:var(--radius)} .dir-item:hover{background:var(--surface-alt)} /* ---------- Breadcrumbs ---------- */ #crumbs{display:flex;flex-wrap:wrap;gap:.25rem;font-size:.875rem;margin-bottom:.75rem} #crumbs span{cursor:pointer;color:var(--accent)} #crumbs span:hover{text-decoration:underline} /* ---------- Responsive tweaks ---------- */ @media (max-width:540px){ li{flex-direction:column;align-items:flex-start} .dir-path{flex:none;width:100%} button{width:100%;margin-top:.4rem} } </style> </head> <body> <h1>Site Path Configuration</h1> <!-- Existing paths ----------------------------------------------------------> <h2>Current Paths</h2> <form method="post"> <ul> <?php foreach ($paths as $n => $p): ?> <li><strong><?=htmlspecialchars($n)?></strong> <span class="dir-path"><?=htmlspecialchars($p)?></span> <button type="submit" name="delete_path" value="<?=htmlspecialchars($n)?>" onclick="return confirm('Delete path "<?=htmlspecialchars($n)?>"?')">ποΈ</button> </li> <?php endforeach;?> </ul> </form> <!-- Add path ---------------------------------------------------------------> <h2>Add / Update Path</h2> <form method="post"> <label>Name <input name="new_path_name" required></label> <label>Value <input id="new_path_value" name="new_path_value" required style="width:50%"> <button type="button" onclick="openPicker('new_path_value')">π Pick</button></label> <button name="add_path">Save</button> </form> <!-- Import / Export --------------------------------------------------------> <h2>Import / Export</h2> <form method="post" enctype="multipart/form-data" style="margin-bottom:.5rem"> <input type="file" name="json_file" accept="application/json" required> <button name="import_json">Import JSON</button> </form> <form method="post" style="display:inline"> <button name="export_json">Download JSON</button> </form> <form method="post" style="display:inline"> <button name="export_php">Create PHP Constants</button> </form> <!-- Modal picker -----------------------------------------------------------> <div id="picker" class="modal" onclick="if(event.target===this)closePicker()"> <div class="modal-content"> <h3>Select Directory</h3> <div id="crumbs"></div> <ul id="dirList"></ul> <button onclick="selectCurrent()">Use This Directory</button> <button onclick="closePicker()">Close</button> </div> </div> <script> let activeInput='', cwd=''; function openPicker(inputId, dir='') { activeInput=inputId; document.getElementById('picker').style.display='block'; loadDirs(dir); } function closePicker(){document.getElementById('picker').style.display='none'} function loadDirs(dir){ fetch('?ajax=dirs&dir='+encodeURIComponent(dir)) .then(r=>r.json()) .then(data=>{ cwd=data.cwd; buildCrumbs(data.cwd, data.root); const list=document.getElementById('dirList'); list.innerHTML=''; if(data.cwd!==data.root){ const up=document.createElement('li'); up.textContent='[..]'; up.className='dir-item'; up.onclick=()=>loadDirs(data.cwd.replace(/\/[^/]+$/,'')||data.root); list.appendChild(up); } data.dirs.forEach(d=>{ const li=document.createElement('li'); li.textContent=d; li.className='dir-item'; li.onclick=()=>loadDirs(data.cwd+'/'+d); list.appendChild(li); }); }); } function buildCrumbs(path, root){ const crumbs=document.getElementById('crumbs'); crumbs.innerHTML=''; let accum=''; path.replace(root,'').split('/').forEach(seg=>{ if(seg){ accum+='/'+seg; const span=document.createElement('span'); span.textContent='/'+seg; span.style.cursor='pointer'; span.onclick=()=>loadDirs(root+accum); crumbs.appendChild(span); } }); } function selectCurrent(){ document.getElementById(activeInput).value=cwd; closePicker(); } </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel