menuModel = model(MenuModel::class); $this->typeModel = model(MenuTypeModel::class); } /** * 메뉴 관리 화면 (목록 + 등록/수정 폼). 지자체별 메뉴만 조회·관리. */ public function index() { helper('admin'); $lgIdx = admin_effective_lg_idx(); if ($lgIdx === null) { return redirect()->to(base_url('admin/select-local-government')) ->with('error', '메뉴를 관리하려면 먼저 지자체를 선택하세요.'); } $types = $this->typeModel->orderBy('mt_sort', 'ASC')->findAll(); $mtIdx = (int) ($this->request->getGet('mt_idx') ?? 0); if ($mtIdx <= 0 && ! empty($types)) { // 기본 선택: 사이트 메뉴(mt_code=site), 없으면 첫 번째 타입 $siteType = $this->typeModel->where('mt_code', 'site')->first(); $mtIdx = $siteType ? (int) $siteType->mt_idx : (int) $types[0]->mt_idx; } $list = $mtIdx > 0 ? $this->menuModel->getAllByType($mtIdx, $lgIdx) : []; // 현재 지자체에 메뉴가 없으면, mt_idx별로 기본 지자체(lg_idx=1)의 메뉴를 한 번 복사한다. if ($mtIdx > 0 && empty($list)) { $this->menuModel->copyDefaultsFromLg($mtIdx, 1, $lgIdx); $list = $this->menuModel->getAllByType($mtIdx, $lgIdx); } // 트리 순서대로 상위 메뉴 바로 아래에 하위 메뉴가 오도록 평탄화 if (! empty($list)) { $tree = build_menu_tree($list); $list = flatten_menu_tree($tree); } $currentType = $mtIdx > 0 ? $this->typeModel->find($mtIdx) : null; return view('admin/layout', [ 'title' => '메뉴 관리', 'content' => view('admin/menu/index', [ 'types' => $types, 'mtIdx' => $mtIdx, 'mtCode' => $currentType->mt_code ?? '', 'list' => $list, 'levelNames' => config('Roles')->levelNames, ]), ]); } /** * 메뉴 목록 JSON (트리 정렬된 평면 배열). 현재 지자체만. */ public function list() { $lgIdx = admin_effective_lg_idx(); if ($lgIdx === null) { return $this->response->setJSON(['status' => 0, 'msg' => '지자체를 선택하세요.']); } $mtIdx = (int) $this->request->getGet('mt_idx'); if ($mtIdx <= 0) { return $this->response->setJSON(['status' => 0, 'msg' => 'mt_idx required']); } $list = $this->menuModel->getAllByType($mtIdx, $lgIdx); return $this->response->setJSON(['status' => 1, 'data' => $list]); } /** * 메뉴 등록 (현재 지자체 소속으로 저장) */ public function store() { $lgIdx = admin_effective_lg_idx(); if ($lgIdx === null) { return redirect()->to(base_url('admin/select-local-government')) ->with('error', '메뉴를 등록하려면 먼저 지자체를 선택하세요.'); } $mtIdx = (int) $this->request->getPost('mt_idx'); $mmPidx = (int) $this->request->getPost('mm_pidx'); $mmDep = (int) $this->request->getPost('mm_dep'); $mmName = trim((string) $this->request->getPost('mm_name')); if ($mtIdx <= 0) { return redirect()->back()->with('error', '메뉴 종류를 선택하세요.'); } if ($mmName === '') { return redirect()->back()->with('error', '메뉴명을 입력하세요.'); } $mmNum = $this->menuModel->getNextNum($mtIdx, $lgIdx, $mmPidx, $mmDep); $data = [ 'mt_idx' => $mtIdx, 'lg_idx' => $lgIdx, 'mm_name' => $mmName, 'mm_link' => (string) $this->request->getPost('mm_link'), 'mm_pidx' => $mmPidx, 'mm_dep' => $mmDep, 'mm_num' => $mmNum, 'mm_cnode' => 0, 'mm_level' => $this->normalizeMmLevel($mtIdx), 'mm_is_view' => $this->request->getPost('mm_is_view') ? 'Y' : 'N', ]; $this->menuModel->insert($data); if ($mmPidx > 0) { $this->menuModel->updateCnode($mmPidx, 1); } return redirect()->back()->with('success', '메뉴가 등록되었습니다.'); } /** * 메뉴 수정 (현재 지자체 소속 메뉴만 허용) */ public function update(int $id) { $lgIdx = admin_effective_lg_idx(); if ($lgIdx === null) { return redirect()->to(base_url('admin/select-local-government')) ->with('error', '지자체를 선택하세요.'); } $row = $this->menuModel->find($id); if (! $row) { return redirect()->back()->with('error', '메뉴를 찾을 수 없습니다.'); } if ((int) $row->lg_idx !== $lgIdx) { return redirect()->back()->with('error', '해당 지자체의 메뉴만 수정할 수 있습니다.'); } $data = [ 'mm_name' => (string) $this->request->getPost('mm_name'), 'mm_link' => (string) $this->request->getPost('mm_link'), 'mm_level' => $this->normalizeMmLevel((int) $row->mt_idx), 'mm_is_view' => $this->request->getPost('mm_is_view') ? 'Y' : 'N', ]; $this->menuModel->update($id, $data); return redirect()->back()->with('success', '메뉴가 수정되었습니다.'); } /** * 메뉴 삭제 (현재 지자체 소속만 허용, 하위 있으면 불가) */ public function delete(int $id) { $lgIdx = admin_effective_lg_idx(); if ($lgIdx === null) { return redirect()->to(base_url('admin/select-local-government')) ->with('error', '지자체를 선택하세요.'); } $row = $this->menuModel->find($id); if (! $row || (int) $row->lg_idx !== $lgIdx) { return redirect()->back()->with('error', '해당 지자체의 메뉴만 삭제할 수 있습니다.'); } $result = $this->menuModel->deleteSafe($id); if ($result['ok']) { return redirect()->back()->with('success', '메뉴가 삭제되었습니다.'); } return redirect()->back()->with('error', $result['msg']); } /** * 순서 변경 (mm_idx[] 순서대로 mm_num 부여). 현재 지자체 메뉴만. */ public function move() { $lgIdx = admin_effective_lg_idx(); if ($lgIdx === null) { return redirect()->to(base_url('admin/select-local-government')) ->with('error', '지자체를 선택하세요.'); } $ids = $this->request->getPost('mm_idx'); if (! is_array($ids) || empty($ids)) { return redirect()->back()->with('error', '순서를 적용할 메뉴가 없습니다.'); } $this->menuModel->setOrder($ids, $lgIdx); return redirect()->back()->with('success', '순서가 적용되었습니다.'); } /** * 노출 대상: 전체(mm_level_all)이면 빈 문자열, 아니면 선택한 레벨을 쉼표 구분 문자열로 */ private function normalizeMmLevel(int $mtIdx): string { // 관리자 메뉴(admin)는 시민/판매소 노출을 허용하지 않음 → 지자체관리자(3)로 고정 $type = $this->typeModel->find($mtIdx); if ($type && (string) $type->mt_code === 'admin') { return (string) Roles::LEVEL_LOCAL_ADMIN; } if ($this->request->getPost('mm_level_all')) { return ''; } $levels = $this->request->getPost('mm_level'); if (! is_array($levels) || empty($levels)) { return ''; } $levels = array_map('intval', $levels); // super/본부(4·5)는 mm_level 저장 대상 아님. 1,2,3은 그대로 저장 $levels = array_filter($levels, static fn ($v) => $v > 0 && ! \Config\Roles::isSuperAdminEquivalent($v)); return implode(',', array_values($levels)); } }