« Back to History
menu_item_insert.php
|
20260722_120325.php
Initial Domain Snapshot
Copy Code
<?php /* ===================================================================== File: /erp/menu_item_insert.php Purpose: Minimal “Menu Item Inserter” (single link add at a time) Scope : Page-only (no global/base edits) DB : nav_menu_sections, nav_menu_items (same as menu_builder.php) Rules : - Company scoped, Owner/Admin only (same guard as builder) - Choose existing Section (dropdown) OR add a New Section inline - Insert exactly ONE menu item at a time (clean UI) - URL normalize: if not starting with "/" or http(s):// → prefix "/" ===================================================================== */ header('X-Frame-Options: SAMEORIGIN'); error_reporting(E_ALL); ini_set('display_errors', 1); /* ---------- Auth + company scope (same as builder) ---------- */ require __DIR__ . '/modules/auth/auth.php'; require_login(); $u = auth_user(); $role = strtolower($u['role'] ?? ''); $company_id = (int)($u['company_id'] ?? 0); $user_id = (int)($u['id'] ?? 0); if (!in_array($role, ['owner','admin'], true)) { http_response_code(403); exit('Forbidden'); } $pdo = $GLOBALS['pdo'] ?? null; if (!$pdo) { require __DIR__ . '/core/db.php'; } /* ---------- Helpers ---------- */ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function cols(PDO $pdo,string $t): array { $c=[]; try{ foreach($pdo->query("DESCRIBE `$t`") as $r){ $c[$r['Field']]=true; } }catch(Throwable $e){} return $c; } function addcol(PDO $pdo,string $t,string $c,string $ddl){ $C=cols($pdo,$t); if(!isset($C[$c])){ $pdo->exec("ALTER TABLE `$t` ADD $ddl"); } } /* ===================================================================== SECTION 1 — Ensure SAME tables as menu_builder.php ===================================================================== */ $pdo->exec(" CREATE TABLE IF NOT EXISTS nav_menu_sections ( id BIGINT PRIMARY KEY AUTO_INCREMENT, company_id BIGINT NOT NULL, name VARCHAR(120) NOT NULL, sort_order INT NOT NULL DEFAULT 0, is_enabled TINYINT(1) NOT NULL DEFAULT 1, created_by BIGINT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); $pdo->exec(" CREATE TABLE IF NOT EXISTS nav_menu_items ( id BIGINT PRIMARY KEY AUTO_INCREMENT, company_id BIGINT NOT NULL, section_id BIGINT NOT NULL, title VARCHAR(160) NOT NULL, url VARCHAR(255) NOT NULL, slug VARCHAR(160) NULL, sort_order INT NOT NULL DEFAULT 0, is_enabled TINYINT(1) NOT NULL DEFAULT 1, created_by BIGINT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_menu_section FOREIGN KEY(section_id) REFERENCES nav_menu_sections(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); /* ===================================================================== SECTION 2 — Fetch Sections for dropdown ===================================================================== */ $sections = []; $st = $pdo->prepare("SELECT id, name FROM nav_menu_sections WHERE company_id=? ORDER BY sort_order, id"); $st->execute([$company_id]); $sections = $st->fetchAll(PDO::FETCH_ASSOC); /* ===================================================================== SECTION 3 — Handle POST (Single Item Insert) ===================================================================== */ if (!ob_get_level()) { ob_start(); } $errors = []; $saved = isset($_GET['saved']) && $_GET['saved']=='1'; $sticky = [ 'section_id' => '', 'new_section' => '', 'title' => '', 'url' => '', 'slug' => '', 'sort_order' => '0', 'active' => '1', ]; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $section_id = (int)($_POST['section_id'] ?? 0); $new_section = trim($_POST['new_section'] ?? ''); $title = trim($_POST['title'] ?? ''); $url = trim($_POST['url'] ?? ''); $slug = trim($_POST['slug'] ?? ''); $sort = trim($_POST['sort_order'] ?? '0'); $active = isset($_POST['active']) ? 1 : 0; // sticky $sticky = [ 'section_id' => (string)$section_id, 'new_section' => $new_section, 'title' => $title, 'url' => $url, 'slug' => $slug, 'sort_order' => $sort, 'active' => $active ? '1' : '0', ]; // validate if ($title==='') $errors[] = 'Title is required.'; if ($url==='') $errors[] = 'URL is required.'; if ($new_section==='' && $section_id<=0) $errors[]='Choose a section or add a new one.'; // normalize URL like builder if ($url!=='' && !preg_match('~^(https?://|/)~i',$url)) { $url = '/'.ltrim($url,'/'); } // create section if needed if (!$errors && $new_section!=='') { try { $pdo->beginTransaction(); $chk = $pdo->prepare("SELECT id FROM nav_menu_sections WHERE company_id=? AND name=? LIMIT 1"); $chk->execute([$company_id,$new_section]); $row = $chk->fetch(PDO::FETCH_ASSOC); if ($row) { $section_id = (int)$row['id']; } else { // next sort = max+10 $mx = $pdo->prepare("SELECT COALESCE(MAX(sort_order),0) FROM nav_menu_sections WHERE company_id=?"); $mx->execute([$company_id]); $next = ((int)$mx->fetchColumn()) + 10; $ins = $pdo->prepare("INSERT INTO nav_menu_sections(company_id,name,sort_order,is_enabled,created_by) VALUES(?,?,?,?,?)"); $ins->execute([$company_id,$new_section,$next,1,$user_id]); $section_id = (int)$pdo->lastInsertId(); } $pdo->commit(); // refresh list for immediate dropdown presence $st = $pdo->prepare("SELECT id, name FROM nav_menu_sections WHERE company_id=? ORDER BY sort_order, id"); $st->execute([$company_id]); $sections = $st->fetchAll(PDO::FETCH_ASSOC); } catch(Throwable $e){ if ($pdo->inTransaction()) $pdo->rollBack(); $errors[] = 'Failed creating section: '.$e->getMessage(); } } // insert single nav_menu_items row if (!$errors) { try{ // default sort = last + 10 inside the chosen section $mx=$pdo->prepare("SELECT COALESCE(MAX(sort_order),0) FROM nav_menu_items WHERE company_id=? AND section_id=?"); $mx->execute([$company_id,$section_id]); $suggested=((int)$mx->fetchColumn())+10; $sort_i = is_numeric($sort) ? (int)$sort : $suggested; if ($sort_i === 0) $sort_i = $suggested; $ins=$pdo->prepare("INSERT INTO nav_menu_items (company_id, section_id, title, url, slug, sort_order, is_enabled, created_by) VALUES (:c,:sid,:t,:u,:s,:o,:on,:by)"); $ins->execute([ ':c'=>$company_id, ':sid'=>$section_id, ':t'=>$title, ':u'=>$url, ':s'=>($slug!=='' ? $slug : null), ':o'=>$sort_i, ':on'=>($active?1:0), ':by'=>$user_id ]); $redir = '/erp/menu_item_insert.php?saved=1'; if (!headers_sent()) { if (ob_get_level()) { ob_end_clean(); } header("Location: $redir", true, 303); exit; } else { echo "<script>location.href='".h($redir)."';</script>"; echo '<noscript><meta http-equiv="refresh" content="0;url='.h($redir).'"></noscript>'; exit; } }catch(Throwable $e){ $errors[] = 'Insert failed: '.$e->getMessage(); } } } /* ---------- Optional legacy header needs ---------- */ if (!isset($company) || !is_array($company)) { $company = ['id'=>$company_id, 'name'=>($u['company_name'] ?? '')]; } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Menu Item Inserter • Mister Manager</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> :root{ --primary:#34A853; --bg:#F5FFF7; --text:#202124; --muted:#5F6368; --card:#FFFFFF; --shadow:0 6px 18px rgba(0,0,0,.06); --radius:16px; --danger:#b00020; } body{margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Arial; background:var(--bg); color:var(--text);} .wrap{max-width:880px; margin:20px auto; padding:16px;} .card{background:var(--card); border-radius:var(--radius); box-shadow:var(--shadow); padding:18px;} h1{margin:0 0 12px;} label{font-size:12px; color:var(--muted); display:block; margin-bottom:6px;} input, select{width:100%; padding:10px; border:1px solid #e5e7eb; border-radius:10px; outline:none} input:focus, select:focus{border-color:#c7ead1; box-shadow:0 0 0 3px rgba(52,168,83,.12)} .row{display:grid; grid-template-columns:1fr 1fr; gap:12px;} .row-1{display:grid; grid-template-columns:1fr; gap:12px;} .toolbar{display:flex; gap:10px; justify-content:space-between; align-items:center; margin-top:12px} .btn{padding:10px 14px; border-radius:12px; border:0; cursor:pointer} .btn-primary{background:var(--primary); color:#fff} .btn-ghost{background:transparent; border:1px dashed #cdd9e3} .pill{display:inline-block; padding:8px 12px; border-radius:999px; background:#e9f6ee; color:#1e8e3e; font-size:12px; margin-bottom:10px} .error{background:#ffebee; color:#b00020; padding:10px 12px; border-radius:10px; margin-bottom:12px} .success{background:#e8f5e9; color:#1e8e3e; padding:10px 12px; border-radius:10px; margin-bottom:12px} .hint{font-size:11px; color:#6b7280; margin-top:4px} </style> </head> <body> <?php require __DIR__ . '/partials/header.php'; ?> <div class="wrap"> <div class="card"> <div class="toolbar"> <h1>Menu Item Inserter</h1> <div><a class="btn btn-ghost" href="/erp/menu_builder.php">↗ Open Menu Builder</a></div> </div> <div class="pill">Company ID: <?= (int)$company_id ?></div> <?php if ($saved): ?> <div class="success">Menu item added successfully.</div> <?php endif; ?> <?php if ($errors): ?> <div class="error"> <strong>Please fix:</strong> <ul style="margin:6px 0 0 18px"> <?php foreach($errors as $e){ echo '<li>'.h($e).'</li>'; } ?> </ul> </div> <?php endif; ?> <form method="post" autocomplete="off" id="menuForm"> <div class="row"> <div> <label>Section (choose existing)</label> <select name="section_id" id="section_id"> <option value="0">— Select Section —</option> <?php foreach($sections as $s): ?> <option value="<?=$s['id']?>" <?= ($sticky['section_id']==(string)$s['id']?'selected':'') ?>> <?= h($s['name']) ?> </option> <?php endforeach; ?> <option value="-1" <?= ($sticky['section_id']=='-1'?'selected':'') ?>>+ Add New Section…</option> </select> <div class="hint">Pick existing section OR choose “Add New Section…” below.</div> </div> <div id="newSecWrap" style="display:none;"> <label>New Section Name</label> <input type="text" name="new_section" id="new_section" placeholder="e.g., Production, Accounts" value="<?= h($sticky['new_section']) ?>"> </div> </div> <div class="row"> <div> <label>Link Title</label> <input type="text" name="title" placeholder="e.g., Beam Entry" required value="<?= h($sticky['title']) ?>"> </div> <div> <label>URL / Path</label> <input type="text" name="url" placeholder="/erp/beam_entry.php or https://example.com" required value="<?= h($sticky['url']) ?>"> <div class="hint">Internal: start with <code>/</code> (e.g., /erp/page.php) • External: full URL</div> </div> </div> <div class="row"> <div> <label>Slug (optional)</label> <input type="text" name="slug" placeholder="e.g., beam_entry" value="<?= h($sticky['slug']) ?>"> <div class="hint">Use for ACL/tag if needed.</div> </div> <div> <label>Sort Order</label> <input type="number" name="sort_order" step="1" min="0" value="<?= h($sticky['sort_order']) ?>"> <div class="hint">Leave 0 to auto (last+10 inside the section).</div> </div> </div> <div class="row"> <div class="row-1"> <label>Status</label> <div style="padding:9px 10px; border:1px solid #e5e7eb; border-radius:10px;"> <label style="display:inline-flex; align-items:center; gap:8px; cursor:pointer;"> <input type="checkbox" name="active" value="1" <?= ($sticky['active']=='1'?'checked':'') ?>> Active (show in menu) </label> </div> </div> <div></div> </div> <div class="toolbar"> <button type="reset" class="btn btn-ghost">Reset</button> <button type="submit" class="btn btn-primary">Insert Menu Item</button> </div> </form> </div> </div> <script> const secSel = document.getElementById('section_id'); const newWrap = document.getElementById('newSecWrap'); const newSec = document.getElementById('new_section'); function toggleNewSec(){ if (secSel.value === '-1') { newWrap.style.display = 'block'; if (newSec) newSec.focus(); } else { newWrap.style.display = 'none'; } } toggleNewSec(); secSel.addEventListener('change', toggleNewSec); // Guard: at least one of (existing section OR new name) must be provided document.getElementById('menuForm').addEventListener('submit', function(ev){ const existing = parseInt(secSel.value || '0', 10); const newName = (newSec.value || '').trim(); if ((existing <= 0) && newName === '') { ev.preventDefault(); alert('Please choose a section or type a new section name.'); } }); </script> </body> </html>