t('acp.menu.vis_all'), 'guest' => t('acp.menu.vis_guest'), 'user' => t('acp.menu.vis_user'), 'admin' => t('acp.menu.vis_admin'), ); } $locations = theme_menu_locations(); $location = (string)($_GET['loc'] ?? array_key_first($locations)); if (!isset($locations[$location])) { $location = (string)array_key_first($locations); } $hasTable = znote_table_exists('znote_menu'); // --------------------------------------------------------------------------- // Mutations // --------------------------------------------------------------------------- if ($_SERVER['REQUEST_METHOD'] === 'POST') { $do = (string)($_POST['do'] ?? ''); $id = intv($_POST['id'] ?? 0); $loc = preg_replace('/[^a-z0-9_-]/', '', strtolower((string)($_POST['location'] ?? $location))); if (!isset($locations[$loc])) { $loc = $location; } if ($do === 'delete' && $id > 0) { $entry = db()->fetchOne("SELECT `parent_id`, `label` FROM `znote_menu` WHERE `id` = ? LIMIT 1;", [$id]); if (is_array($entry) && (int)$entry['parent_id'] === 0) { db()->execute("DELETE FROM `znote_menu` WHERE `id` = ? OR `parent_id` = ?;", [$id, $id]); acp_log('menu.delete_category', (string)$entry['label']); acp_flash_success(t('acp.menu.category_deleted')); acp_redirect('menus', array('loc' => $loc)); } db()->execute("DELETE FROM `znote_menu` WHERE `id` = ? LIMIT 1;", [$id]); acp_log('menu.delete', is_array($entry) ? (string)$entry['label'] : ('#' . $id)); acp_flash_success(t('acp.menu.deleted')); acp_redirect('menus', array('loc' => $loc)); } if ($do === 'toggle' && $id > 0) { db()->execute("UPDATE `znote_menu` SET `active` = CASE WHEN `active` = 1 THEN 0 ELSE 1 END WHERE `id` = ? LIMIT 1;", [$id]); acp_redirect('menus', array('loc' => $loc)); } if ($do === 'move' && $id > 0) { $item = db()->fetchOne(" SELECT `id`, `parent_id`, `location` FROM `znote_menu` WHERE `id` = ? LIMIT 1; ", [$id]); if (is_array($item)) { $siblings = db()->fetchAll(" SELECT `id` FROM `znote_menu` WHERE `location` = ? AND `parent_id` = ? ORDER BY `sort_order` ASC, `id` ASC; ", [(string)$item['location'], (int)$item['parent_id']]); if (is_array($siblings)) { $ids = array_map('intval', array_column($siblings, 'id')); $pos = array_search($id, $ids, true); $swap = ((string)($_POST['dir'] ?? '') === 'up') ? ((int)$pos - 1) : ((int)$pos + 1); if ($pos !== false && isset($ids[$swap])) { $ids[$pos] = $ids[$swap]; $ids[$swap] = $id; foreach ($ids as $index => $rowId) { db()->execute("UPDATE `znote_menu` SET `sort_order` = ? WHERE `id` = ? LIMIT 1;", [($index + 1) * 10, $rowId]); } } } } acp_redirect('menus', array('loc' => $loc)); } if ($do === 'save') { $label = substr(trim((string)($_POST['label'] ?? '')), 0, 64); $url = substr(trim((string)($_POST['url'] ?? '')), 0, 255); $icon = substr(trim((string)($_POST['icon'] ?? '')), 0, 48); $target = ((string)($_POST['target'] ?? '') === '_blank') ? '_blank' : ''; $visibility = (string)($_POST['visibility'] ?? 'all'); $parent = intv($_POST['parent_id'] ?? 0); $order = intv($_POST['sort_order'] ?? 0); $existing = $id > 0 ? db()->fetchOne(" SELECT `id`, `parent_id`, `location` FROM `znote_menu` WHERE `id` = ? LIMIT 1; ", [$id]) : false; $editingCategory = is_array($existing) && (int)$existing['parent_id'] === 0; if (!isset(acp_menu_visibility()[$visibility])) { $visibility = 'all'; } // A top-level entry is a category: a heading that opens its children, // not a link of its own. Everything nested under one still needs a URL. $isCategory = ($parent === 0); if ($label === '') { acp_flash_error(t('acp.menu.label_required')); acp_redirect('menus', array('loc' => $loc)); } if (!$isCategory && $url === '') { acp_flash_error(t('acp.menu.url_required')); acp_redirect('menus', array('loc' => $loc)); } if ($id > 0 && !is_array($existing)) { acp_flash_error(t('acp.menu.entry_gone')); acp_redirect('menus', array('loc' => $loc)); } if ($editingCategory) { $parent = 0; } elseif ($parent > 0) { $category = $parent > 0 ? db()->fetchOne(" SELECT `id` FROM `znote_menu` WHERE `id` = ? AND `location` = ? AND `parent_id` = 0 LIMIT 1; ", [$parent, $loc]) : false; if (!is_array($category)) { acp_flash_error(t('acp.menu.choose_category')); acp_redirect('menus', array('loc' => $loc)); } } $fieldsSql = "`label` = ?, `url` = ?, `icon` = ?, `target` = ?, `visibility` = ?, `parent_id` = ?, `sort_order` = ?"; $fieldsParams = [$label, $url, $icon, $target, $visibility, $parent, $order]; if ($id > 0) { db()->execute("UPDATE `znote_menu` SET {$fieldsSql} WHERE `id` = ? LIMIT 1;", [...$fieldsParams, $id]); acp_log('menu.update', $label, ['url' => $url, 'location' => $loc]); acp_flash_success(t('acp.menu.updated')); } else { db()->execute("INSERT INTO `znote_menu` SET `location` = ?, `active` = 1, {$fieldsSql};", [$loc, ...$fieldsParams]); acp_log('menu.create', $label, ['url' => $url, 'location' => $loc]); acp_flash_success(t('acp.menu.added')); } acp_redirect('menus', array('loc' => $loc)); } acp_flash_error(t('acp.menu.unknown_action')); acp_redirect('menus', array('loc' => $location)); } // --------------------------------------------------------------------------- // Load // --------------------------------------------------------------------------- $entries = $hasTable ? db()->fetchAll(" SELECT `id`, `parent_id`, `label`, `url`, `icon`, `target`, `visibility`, `sort_order`, `active` FROM `znote_menu` WHERE `location` = ? ORDER BY `sort_order` ASC, `id` ASC; ", [$location]) : false; $entries = is_array($entries) ? $entries : array(); $editing = null; if (($_GET['action'] ?? '') === 'edit') { $editId = intv($_GET['id'] ?? 0); foreach ($entries as $entry) { if ((int)$entry['id'] === $editId) { $editing = $entry; break; } } } $editingCategory = is_array($editing) && (int)$editing['parent_id'] === 0; // Top-level entries, for the parent dropdown. $parents = array(); foreach ($entries as $entry) { if ((int)$entry['parent_id'] === 0 && (int)$entry['id'] !== (int)($editing['id'] ?? 0)) { $parents[(int)$entry['id']] = (string)$entry['label']; } } $topEntries = array(); $childrenByParent = array(); foreach ($entries as $entry) { $parentId = (int)$entry['parent_id']; if ($parentId === 0) { $topEntries[] = $entry; } else { $childrenByParent[$parentId][] = $entry; } } $displayEntries = array(); foreach ($topEntries as $entry) { $displayEntries[] = $entry; foreach ($childrenByParent[(int)$entry['id']] ?? array() as $child) { $displayEntries[] = $child; } } foreach ($childrenByParent as $parentId => $children) { if (!isset($parents[(int)$parentId]) && (int)$parentId !== (int)($editing['id'] ?? 0)) { foreach ($children as $child) { $displayEntries[] = $child; } } } ?>
'znote_menu', 'file' => 'SQL/migrations/2.0.0_menus.sql', ]) ?>
$label): ?>
count($entries), 'location' => '' . h($location) . '']) ?>

h($locations[$location])]) ?>

'theme.json']) ?>

"menus": { "main": "Top navigation" }

<?php foreach (theme_menu_items('main') as $item): ?>
  <a href="<?= $item['url'] ?>"><?= $item['label'] ?></a>
<?php endforeach; ?>

'' . t('acp.menu.admins_only') . '']) ?>

0; $hidden = ((int)$entry['active'] === 0); ?> >
— ' : '' ?> ['fa-arrow-up', t('acp.menu.move_up')], 'down' => ['fa-arrow-down', t('acp.menu.move_down')]) as $dir => $meta): ?>