1장. 게시판 시스템 개요
DXCMS의 게시판 시스템은 하나의 공통 핸들러(boards/handler.php)를 중심으로, 스킨•테마 폴백 체인을 통해 다양한 유형의 게시판을 단일 코드베이스로 운영합니다. 일반 게시판부터 갤러리•Q&A•쇼핑몰까지 모두 같은 URL 구조와 데이터 모델을 사용하며, 스킨으로 화면만 교체합니다.
1.1 핵심 설계 원칙
- 단일 진입점 — 모든 게시판 요청은 boards/handler.php 하나로 집결
- 스킨 폴백 체인 — 6단계 우선순위로 뷰 파일 자동 탐색 (없으면 기본 테마로 안전 폴백)
- 막코딩 지원 — 스킨에서 Notice/Warning이 발생해도 CMS 전체는 정상 동작, 에러는 data/error.log에 기록
- DB 독립 — PDO 기반, MySQL/MariaDB 완전 지원, PHP 5.6~8.x 호환
- 멀티사이트 — site_domain 컬럼으로 도메인별 게시판 분리 가능
- 크로스 플랫폼 — Windows IIS, Linux Apache/Nginx 모두 동일하게 동작
1.2 URL 구조
게시판 URL은 /{게시판키}/{액션}/{ID} 형태를 따릅니다.
/{board_key}/list — 게시글 목록
/{board_key}/view/{id} — 게시글 상세보기
/{board_key}/write — 글쓰기
/{board_key}/edit/{id} — 글 수정
/{board_key}/delete/{id} — 글 삭제
/{board_key}/reply/{id} — 답글 작성
/{board_key}/search — 검색
/{board_key}/bulk — 관리자 일괄 처리
/{board_key}/{custom_action} — 스킨 커스텀 액션 (예: /shop/cart)
2장. 디렉토리 및 파일 구조
2.1 게시판 관련 디렉토리 전체 맵
boards/
handler.php ← 공통 핸들러 (진입점)
skins/
SKIN_GUIDE.md ← 스킨 개발 가이드
gallery/ ← 갤러리 스킨 (기본 제공)
shop/ ← 쇼핑몰 스킨 (기본 제공)
{커스텀스킨}/ ← 사용자 정의 스킨
category/
skins/default/ ← 기본 카테고리 탭 스킨
themes/default/board/
list.php ← 기본 목록 뷰
view.php ← 기본 상세 뷰
write.php ← 기본 글쓰기 뷰
style.css ← 게시판 기본 스타일
core/
DxCategory.php ← 카테고리 계층 관리 클래스
DxBoardSkin.php ← 스킨 파일 탐색 클래스
DxThumb.php ← 썸네일 자동 생성
data/uploads/boards/ ← 첨부파일 실제 저장 위치
2.2 스킨 내부 구조
하나의 스킨은 아래 파일/폴더로 구성됩니다. 필수는 skin.json 하나이며, 나머지는 선택 사항입니다.
boards/skins/{스킨이름}/
skin.json ← 스킨 메타 정보 (필수)
list.php ← 목록 뷰
view.php ← 상세 뷰
write.php ← 글쓰기/수정 폼
parts/ ← 재사용 파셜 (row.php 등)
actions/ ← 커스텀 액션
{액션}.php ← /게시판키/{액션} URL 자동 매핑
assets/ ← CSS, JS, 이미지
skin.css
skin.js
2.3 skin.json 상세
스킨 메타 파일로, 관리자 스킨 목록에 표시되는 정보를 담습니다.
{
"name": "갤러리",
"description": "썸네일 그리드 갤러리.",
"version": "5.5.0",
"author": "DesignOneX",
"actions": ["list", "view", "write"],
"config": {
"date_format": "Y-m-d",
"per_page": "20"
}
}
| 키 |
설명 |
| name |
스킨 표시 이름 (관리자 드롭다운에 노출) |
| description |
스킨 설명 |
| version |
스킨 버전 |
| author |
스킨 제작자 |
| actions |
이 스킨이 처리할 액션 배열 (나머지는 기본 테마 폴백) |
| config |
스킨 자체 설정 (dx_skin_config() 함수로 읽기) |
3장. 스킨 폴백(탐색) 체인
핸들러는 요청된 액션(list, view, write 등)에 맞는 뷰 파일을 아래 6단계 순서로 탐색합니다. 앞 단계에서 파일을 찾으면 뒤 단계는 탐색하지 않습니다.
| 우선순위 |
경로 |
설명 |
| 1 |
boards/skins/{스킨}/{액션}/handler.php |
완전 독립 핸들러 — DB쿼리부터 렌더까지 직접 처리 |
| 2 |
boards/skins/{스킨}/{액션}.php |
스킨 독립 뷰 |
| 3 |
themes/{현재테마}/board/{스킨}/{액션}.php |
테마 스킨 전용 뷰 |
| 4 |
themes/{현재테마}/board/{액션}.php |
테마 공통 뷰 |
| 5 |
themes/default/board/{스킨}/{액션}.php |
기본 테마 스킨 전용 뷰 |
| 6 |
themes/default/board/{액션}.php |
최종 폴백 (항상 존재, 이것도 없으면 500 오류) |
실전 팁
list.php 하나만 만들어도 됩니다. view, write는 themes/default/board/로 자동 폴백됩니다.
완전히 새로운 로직이 필요한 경우: boards/skins/{스킨}/list/handler.php 를 만들면
DB 쿼리부터 렌더링까지 모두 그 파일에서 처리합니다 — 공통 핸들러를 전혀 타지 않습니다.
3.1 커스텀 액션 (표준 외 URL)
boards/skins/{스킨}/actions/ 폴더에 PHP 파일을 넣으면 /{게시판키}/{파일명} URL이 자동 생성됩니다.
// boards/skins/shop/actions/cart.php
// URL: /shop-board/cart
$db = $GLOBALS['dx_handler_context']['db'];
$auth = $GLOBALS['dx_handler_context']['auth'];
$board = $GLOBALS['dx_handler_context']['board'];
// ... 원하는 로직 작성
4장. DB 스키마 상세
게시판 시스템과 관련된 핵심 테이블은 8개입니다. 모두 dx_ 접두사를 기본으로 사용하며, 설치 시 config.php에서 변경 가능합니다.
4.1 dx_boards — 게시판 설정
게시판 하나당 레코드 하나. 글쓰기 권한, 스킨, 기능 ON/OFF 등 모든 게시판 설정이 이 테이블에 저장됩니다.
| 컬럼명 |
타입 |
기본값 |
설명 |
| id |
INT UNSIGNED |
AUTO_INCREMENT |
PK |
| board_key |
VARCHAR(100) |
- |
URL에 사용되는 고유 키 (영문+숫자+-_) |
| board_name |
VARCHAR(191) |
- |
게시판 표시 이름 |
| description |
TEXT |
NULL |
게시판 설명 |
| board_type |
ENUM |
normal |
normal / gallery / qa / faq / news |
| skin |
VARCHAR(100) |
default |
사용할 스킨 이름 (boards/skins/ 폴더명) |
| group_id |
INT |
0 |
게시판 그룹 ID (메뉴 묶음 용도) |
| read_level |
TINYINT |
0 |
읽기 권한: 0=전체, 1=회원, 9=관리자 |
| write_level |
TINYINT |
1 |
쓰기 권한: 0=전체, 1=회원, 9=관리자 |
| comment_level |
TINYINT |
1 |
댓글 권한: 0=전체, 1=회원, 9=관리자 |
| per_page |
TINYINT |
20 |
목록 페이지당 글 수 |
| use_comment |
TINYINT |
1 |
댓글 사용: 1=사용, 0=미사용 |
| use_file |
TINYINT |
1 |
첨부파일 사용: 1=사용, 0=미사용 |
| use_notice |
TINYINT |
1 |
공지글 사용 여부 |
| use_category |
TINYINT |
0 |
카테고리 사용: 1=사용, 0=미사용 |
| use_tag |
TINYINT |
0 |
태그 사용 여부 |
| use_anonymous |
TINYINT |
0 |
비회원 글쓰기 허용 여부 |
| use_survey |
TINYINT |
0 |
설문 기능 사용 여부 |
| file_count |
TINYINT |
5 |
글당 최대 첨부파일 수 |
| show_list_in_view |
TINYINT |
0 |
뷰 페이지 하단에 목록 표시 여부 |
| use_breadcrumb |
TINYINT |
1 |
상단 브레드크럼 표시 여부 |
| thumb_type |
VARCHAR(20) |
crop |
썸네일 유형: fit|crop|width|height |
| thumb_w |
SMALLINT |
300 |
썸네일 가로 픽셀 |
| thumb_h |
SMALLINT |
200 |
썸네일 세로 픽셀 |
| thumb_crop |
VARCHAR(20) |
center |
크롭 기준: center|top|left |
| cat_skin |
VARCHAR(50) |
default |
카테고리 탭 스킨 이름 |
| site_domain |
VARCHAR(191) |
(빈값) |
멀티사이트 도메인 (빈값=전체 공통) |
| status |
TINYINT |
1 |
1=활성, 0=비활성 |
| sort_order |
SMALLINT |
0 |
메뉴 정렬 순서 (낮을수록 위) |
4.2 dx_posts — 게시글
모든 게시판의 글이 이 테이블에 저장됩니다. ID는 밀리초 타임스탬프 기반으로 자동 생성되어 순서와 시간이 동일합니다.
| 컬럼명 |
타입 |
기본값 |
설명 |
| id |
BIGINT UNSIGNED |
밀리초ID |
PK — 밀리초 타임스탬프로 자동 생성 |
| board_id |
INT UNSIGNED |
- |
소속 게시판 ID (dx_boards.id 참조) |
| parent_id |
BIGINT |
0 |
답글 시 원글 ID (0=최상위 글) |
| member_id |
INT |
0 |
작성 회원 ID (0=비회원) |
| author_name |
VARCHAR(100) |
NULL |
비회원 작성자명 |
| author_pass |
VARCHAR(255) |
NULL |
비회원 비밀번호 (bcrypt 해시) |
| title |
VARCHAR(191) |
- |
제목 |
| content |
LONGTEXT |
- |
본문 (HTML 허용, XSS 필터링 후 저장) |
| category |
VARCHAR(100) |
NULL |
선택된 카테고리 이름 |
| tags |
VARCHAR(191) |
NULL |
쉼표 구분 태그 문자열 |
| link |
VARCHAR(500) |
NULL |
단일 링크 URL (레거시, 다중링크는 post_links) |
| thumbnail |
VARCHAR(191) |
NULL |
썸네일 이미지 경로 |
| view_count |
INT |
0 |
조회수 (세션 기반 중복 방지) |
| like_count |
INT |
0 |
좋아요 수 |
| comment_count |
INT |
0 |
댓글 수 |
| is_notice |
TINYINT |
0 |
공지글 여부 (1=공지) |
| is_secret |
TINYINT |
0 |
비밀글 여부 (1=비밀글) |
| dl_level |
TINYINT |
0 |
첨부파일 다운 권한: 0=전체, 1=회원, 2=관리자 |
| dl_limit |
INT |
0 |
1인당 최대 다운로드 수 (0=무제한) |
| dl_point |
INT |
0 |
다운로드 시 차감 포인트 |
| ip |
VARCHAR(45) |
NULL |
작성자 IP (IPv6 포함) |
| status |
TINYINT |
1 |
1=정상, 0=삭제됨 |
| popular_score |
INT |
0 |
인기점수 = 조회×1 + 좋아요×5 + 댓글×3 (7일 시간감쇠) |
| created_at |
DATETIME |
- |
작성일시 |
| updated_at |
DATETIME |
NULL |
수정일시 |
4.3 dx_categories — 카테고리 (무한 계층)
게시판별 분류 체계를 저장합니다. parent_id=0이 최상위이며, depth와 path 컬럼으로 계층 깊이와 조상 경로를 추적합니다.
| 컬럼명 |
타입 |
기본값 |
설명 |
| id |
INT UNSIGNED |
AUTO_INCREMENT |
PK |
| board_id |
INT |
0 |
소속 게시판 ID (0=공통 분류) |
| parent_id |
INT |
0 |
상위 카테고리 ID (0=최상위) |
| name |
VARCHAR(191) |
- |
카테고리 이름 |
| slug |
VARCHAR(100) |
(빈값) |
URL 슬러그 (미사용 시 name으로 필터) |
| depth |
TINYINT |
0 |
계층 깊이 (0=최상위, 1=2단계, ...) |
| path |
VARCHAR(500) |
(빈값) |
조상 ID 경로 (예: 0/5/12 — 빠른 트리 탐색용) |
| color |
VARCHAR(20) |
blue |
카테고리 색상 키 (blue|green|red|orange|purple|slate) |
| show_in_list |
TINYINT |
1 |
목록 페이지 탭에 표시 여부 |
| show_in_view |
TINYINT |
1 |
뷰 페이지 배지에 표시 여부 |
| sort_order |
SMALLINT |
0 |
같은 계층 내 정렬 순서 |
| status |
TINYINT |
1 |
1=활성, 0=비활성 |
| created_at |
DATETIME |
- |
생성일시 |
4.4 dx_comments — 댓글
게시글에 달리는 댓글과 대댓글을 저장합니다. parent_id=0이 최상위 댓글이며, depth로 대댓글 깊이를 표현합니다.
| 컬럼명 |
타입 |
기본값 |
설명 |
| id |
BIGINT UNSIGNED |
밀리초ID |
PK |
| post_id |
BIGINT |
- |
소속 게시글 ID |
| parent_id |
BIGINT |
0 |
상위 댓글 ID (0=최상위) |
| member_id |
INT |
0 |
작성 회원 ID (0=비회원) |
| author_name |
VARCHAR(100) |
NULL |
비회원 작성자명 |
| content |
TEXT |
- |
댓글 내용 |
| depth |
TINYINT |
0 |
댓글 깊이 (0=댓글, 1=대댓글) |
| status |
TINYINT |
1 |
1=정상, 0=삭제 |
| created_at |
DATETIME |
- |
작성일시 |
4.5 dx_post_files — 첨부파일
게시글 첨부파일 정보를 저장합니다. 실제 파일은 data/uploads/boards/{게시판키}/ 에 저장됩니다.
| 컬럼명 |
타입 |
기본값 |
설명 |
| id |
INT UNSIGNED |
AUTO_INCREMENT |
PK |
| post_id |
BIGINT |
- |
소속 게시글 ID |
| orig_name |
VARCHAR(191) |
- |
원본 파일명 (사용자 업로드 이름) |
| save_name |
VARCHAR(191) |
- |
저장 파일명 (날짜_랜덤8자리.확장자) |
| save_path |
VARCHAR(191) |
- |
저장 경로 (boards/{key}/{save_name}) |
| file_size |
INT |
0 |
파일 크기 (바이트) |
| file_ext |
VARCHAR(20) |
- |
확장자 (소문자) |
| download_count |
INT |
0 |
다운로드 횟수 |
| is_thumb |
TINYINT |
0 |
썸네일 파일 여부 |
| thumb_path |
VARCHAR(191) |
NULL |
썸네일 경로 (갤러리 스킨용) |
| created_at |
DATETIME |
- |
업로드 일시 |
4.6 dx_post_links — 다중 링크
게시글 하나에 여러 링크를 첨부할 수 있습니다. 클릭 수는 dx_link_clicks 테이블에 IP+날짜 기준으로 중복 방지 후 집계됩니다.
| 컬럼명 |
타입 |
설명 |
| post_id |
BIGINT |
소속 게시글 ID |
| url |
VARCHAR(500) |
링크 URL (http 없으면 https:// 자동 보정) |
| label |
VARCHAR(100) |
링크 제목 (선택, 비워두면 URL 표시) |
| sort_order |
SMALLINT |
정렬 순서 |
| click_count |
INT |
클릭 수 (편집 시 기존 URL과 동일하면 유지) |
4.7 dx_global_notices — 전체 공지
모든 게시판 상단에 공통 표시되는 공지입니다. 게시 기간(start_at~end_at)을 설정하여 자동으로 노출/숨김 처리됩니다.
| 컬럼명 |
타입 |
설명 |
| title |
VARCHAR(191) |
공지 제목 |
| title_color |
VARCHAR(20) |
제목 색상 (hex, 비우면 기본색) |
| link_url |
VARCHAR(500) |
클릭 시 이동 URL (선택) |
| link_target |
VARCHAR(10) |
_self 또는 _blank |
| start_at |
DATETIME |
게시 시작일 (NULL=즉시) |
| end_at |
DATETIME |
게시 종료일 (NULL=무기한) |
| sort_order |
SMALLINT |
정렬 순서 (낮을수록 위) |
| status |
TINYINT |
1=활성, 0=비활성 |
5장. 카테고리 시스템 (DxCategory)
카테고리는 무한 계층(parent_id 트리) 구조로, DxCategory 클래스가 DB 조회•트리 빌드•평면 변환을 담당합니다.
5.1 DxCategory 주요 메서드
| 메서드 |
설명 |
| getByBoard($boardId, $showIn) |
게시판의 카테고리 행 배열 반환. showIn=list|view|"" (전체) |
| buildTree($rows, $parentId) |
평면 배열 → 계층 트리 변환 (재귀). sort_order → id 순 |
| flattenTree($tree, $depth) |
트리 → 평면 select 옵션용 배열. label에 들여쓰기 기호 포함 |
| getFlatByBoard($boardId, $showIn) |
getByBoard + buildTree + flattenTree 한번에 처리 |
| getDescendantNames($boardId, $catName) |
선택한 카테고리 + 모든 하위 카테고리 이름 배열 반환 (목록 필터용) |
| renderList($board, $currentCat, $theme) |
목록 페이지 카테고리 탭 자동 렌더링 (2단 구조) |
| renderView($board, $catName, $theme) |
뷰 페이지 카테고리 배지 렌더링 |
5.2 계층 구조 예시
depth 0이 최상위, depth 1이 그 하위입니다. parent_id가 상위 id를 참조합니다.
분류 (depth=0, parent_id=0)
└ 서브분류1 (depth=1, parent_id=분류.id)
└ 서브서브분류 (depth=2, parent_id=서브분류1.id)
└ 서브분류2 (depth=1, parent_id=분류.id)
💡 상위 분류 클릭 시 동작
상위 분류(depth=0)를 클릭하면 해당 분류의 모든 하위 분류 글까지 함께 표시됩니다.
getDescendantNames()가 선택 카테고리와 모든 자손의 이름을 수집하여 WHERE IN (?) 쿼리를 생성합니다.
5.3 카테고리 탭 렌더링 (2단 구조)
renderList()는 아래 2단 탭 구조를 자동 렌더링합니다:
- 1단 탭 — 전체 + 최상위 분류들 (depth=0)
- 2단 탭 — 선택된 최상위 분류의 하위 분류들 (depth=1+)
- 하위 분류가 없는 최상위 분류는 1단만 표시
- 스킨 파일 없으면 _renderListDefault() 내장 탭 사용 (Tailwind CSS)
6장. 목록(list) 기능 완전 분석
게시판 목록은 단순한 리스트 출력 외에도 검색•필터•정렬•일괄처리•캐시•SEO까지 담당합니다.
6.1 목록 데이터 처리 흐름
- 캐시 확인 — 비로그인+검색없음+기본페이지 조건이면 DxCache에서 60초 캐시 데이터 반환
- 파라미터 수집 — page, s(검색어), sf(검색필드), cat(카테고리), tag(태그)
- 쿼리 조립 — board_id + status=1 + is_notice=0 조건에 검색/카테고리/태그 필터 추가
- 공지글 쿼리 — use_notice=1인 게시판은 is_notice=1 글 최대 5개 별도 조회
- 전체 공지 쿼리 — dx_global_notices에서 게시 기간이 현재인 공지 조회
- 카테고리 조회 — use_category=1이면 DxCategory::getFlatByBoard()로 탭 데이터 조회
- 캐시 저장 — 읽기 전용 조건이면 60초 캐시 저장
- SEO 빌드 — DxSeo::build('board_list') 호출
- 렌더링 — _brd_render('list', $ctx) 호출 → 스킨 폴백 체인
6.2 검색 파라미터
| 파라미터 |
설명 |
| ?s=검색어 |
검색어 (2자 이상이면 popular_keywords 테이블에 자동 기록) |
| ?sf=both |
검색 필드: both(제목+내용), title, content, author, category |
| ?sf=like |
정렬: 좋아요 많은 순 (like_count DESC) |
| ?sf=popular |
정렬: 인기점수 순 (popular_score DESC) |
| ?cat=분류명 |
카테고리 필터 (하위 분류 포함 자동 필터) |
| ?tag=태그명 |
태그 필터 (LIKE %태그%) |
| ?page=N |
페이지 번호 (1부터 시작) |
6.3 관리자 일괄 처리 (Bulk)
관리자 로그인 시 목록에 체크박스가 표시되며, 아래 일괄 작업이 가능합니다.
- 일괄 삭제 — 선택한 글 + 댓글 + 첨부파일 + 좋아요 + 스크랩 완전 삭제
- 일괄 이동 — 선택한 글을 다른 게시판으로 이동 (board_id 변경)
- 일괄 복사 — 선택한 글을 다른 게시판으로 복사 (첨부파일 링크도 복사)
⚠️ IIS 주의사항
IIS 환경에서는 /board/bulk URL 라우팅이 불가한 경우가 있습니다.
이를 위해 bulk 처리는 /board/list 로 POST 요청하여 bulk_act 파라미터로 구분합니다.
7장. 상세보기(view) 기능 완전 분석
7.1 view 컨텍스트 변수 전체 목록
$ctx 배열로 스킨 파일에 전달되는 변수입니다.
| 변수 |
설명 |
| $board |
게시판 설정 배열 (dx_boards 레코드) |
| $post |
게시글 데이터 + 작성자 이름(member_name), 프로필(member_profile_img) 포함 |
| $files |
첨부파일 배열 (dx_post_files, id 오름차순) |
| $comments |
댓글 배열 (dx_comments + 작성자 정보, id 오름차순) |
| $postLinks |
다중 링크 배열 (dx_post_links, sort_order 오름차순) |
| $prevPost |
이전 글 (id, title, created_at) |
| $nextPost |
다음 글 (id, title, created_at) |
| $viewList |
뷰 하단 목록 배열 (show_list_in_view=1일 때만) |
| $survey |
설문 데이터 (use_survey=1이고 설문이 있을 때) |
| $surveyQuestions |
설문 문항 배열 |
| $surveyVoted |
현재 사용자의 설문 참여 여부 (bool) |
| $surveyResults |
설문 결과 집계 배열 (공개 조건 충족 시) |
| $surveyEnded |
설문 마감 여부 (bool) |
| $categories |
카테고리 배열 (사이드바·모바일 탭용) |
7.2 조회수 중복 방지
세션 기반으로 같은 사용자의 중복 조회를 방지합니다.
- 같은 세션에서 이미 본 글이면 조회수 증가 없음 (세션 키: viewed_post_{id})
- 세션이 없는 환경(비로그인 GET 최적화)에서도 안전하게 처리
- 조회 시 popular_score 자동 갱신: 조회×1 + 좋아요×5 + 댓글×3 × 시간감쇠
- 시간감쇠: 7일마다 10%씩 감소 (최소 10% 유지)
7.3 비밀글 보호
is_secret=1인 글은 아래 조건을 만족해야 내용을 볼 수 있습니다:
- 관리자 — 항상 열람 가능
- 작성자 본인 — member_id가 일치하는 로그인 회원
- 그 외 — 403 오류 반환
8장. 글쓰기 및 수정(write/edit)
8.1 권한 체계
write_level 컬럼 값에 따라 접근이 제어됩니다:
- 0 — 비회원 포함 누구나 작성 가능
- 1 — 로그인 회원만 (미로그인 시 로그인 페이지로 리다이렉트)
- 9 — 관리자만 작성 가능
8.2 저장 데이터 처리
POST 요청 수신 시 아래 순서로 처리합니다:
- 비회원 글쓰기 캡챠 검증 (use_captcha=1이고 비로그인 상태일 때)
- 입력값 XSS 필터링 — DxSanitizer::text() / editorContent() 사용
- dl_ 컬럼 존재 여부 체크 (migrate 미실행 환경 대응, 없으면 자동 제외)
- 신규: insertWithMicrotimeId() 로 밀리초 타임스탬프 ID 생성
- 수정: title/content/category/tags/is_notice/is_secret/updated_at 만 UPDATE
- 첨부파일 업로드 처리 (dx_board_upload_files())
- 삭제할 파일 처리 (delete_files[] 체크박스)
- 다중 링크 저장 (dx_board_save_links())
- 설문 저장 (dx_board_save_survey(), use_survey=1일 때만)
- 갤러리 타입이면 DxThumb::autoFromPost() 썸네일 자동 생성
- 게시판 목록 캐시 무효화 (DxCache::deletePrefix)
- 실시간 알림 세션 저장 (dx_post_live_broadcast)
8.3 첨부파일 보안
파일 업로드 시 아래 보안 정책이 적용됩니다:
- 블랙리스트 방식 — php, asp, jsp, sh 등 서버 실행 확장자만 차단, 나머지는 허용
- 이중 확장자 차단 — image.php.jpg 처럼 중간에 위험 확장자가 있으면 차단
- .htaccess 자동 생성 — 업로드 폴더에 PHP 실행 차단 htaccess 자동 생성 (Apache)
- web.config 자동 생성 — IIS 환경용 PHP 실행 차단 web.config 자동 생성
- 파일명 랜덤화 — 날짜_랜덤8자리.확장자 형식으로 저장 (원본명은 DB에 보존)
- 크기 제한 — upload_max_size 설정 + php.ini upload_max_filesize 중 더 작은 값 적용
9장. 설문 기능 (use_survey)
게시판 설정에서 use_survey=1 로 활성화하면, 글쓰기 폼에 설문 섹션이 추가됩니다. 하나의 게시글에 설문 하나를 첨부할 수 있습니다.
9.1 설문 관련 테이블
- dx_surveys — 설문 메타 (제목, 마감일, 비회원 허용, 결과 공개 조건)
- dx_survey_questions — 문항 (단일선택/복수선택/단답형, 선택지 JSON, 필수 여부)
- dx_survey_answers — 응답 데이터 (회원ID 또는 IP 기반)
- dx_survey_votes — 참여 로그 (중복 참여 방지, 회원=UNIQUE, 비회원=IP 기반)
9.2 결과 공개 조건
| show_result 값 |
공개 조건 |
| always |
항상 공개 (참여 여부 무관) |
| after_vote |
참여 후 공개 (기본값) |
| after_end |
마감일 이후에만 공개 |
| 관리자 |
조건 무관 항상 열람 가능 |
10장. 인기점수(popular_score) 시스템
조회, 좋아요, 댓글 수를 가중 합산하고, 시간이 지날수록 점수를 낮추는 감쇠 공식을 적용합니다.
10.1 점수 계산 공식
기본 점수 = 조회수 × 1 + 좋아요 × 5 + 댓글수 × 3
시간 감쇠 = max(0.1, 1.0 - floor(경과일수 / 7) × 0.1)
→ 7일마다 10% 감소, 최소 10% 유지
최종 popular_score = round(기본점수 × 시간감쇠)
10.2 인기순 정렬 사용
목록 URL에 sf=popular 파라미터를 추가하면 인기점수 내림차순으로 정렬됩니다.
예시: /{board_key}/list?sf=popular
11장. 스킨 개발 가이드
11.1 스킨에서 사용 가능한 헬퍼 함수
| 함수 |
설명 |
| dx_skin_asset('skin.css') |
스킨 assets/ 폴더의 파일 URL 반환 |
| dx_skin_config('key', '기본값') |
skin.json의 config 값 읽기 |
| dx_skin_part('row', ['post'=>$post]) |
스킨 parts/ 폴더 파셜 include |
| $GLOBALS['dx_board_skin'] |
현재 스킨 이름 읽기 |
| dx_base_url($path) |
사이트 기준 절대 URL 생성 |
| dx_method('POST') |
HTTP 메서드 확인 |
| dx_get('key', '기본값') |
GET 파라미터 안전 읽기 |
| dx_post('key', '기본값') |
POST 파라미터 안전 읽기 |
| dx_csrf_field() |
CSRF 토큰 hidden 필드 HTML 반환 |
11.2 list.php 기본 템플릿
아래는 최소한의 list.php 스킨 예시입니다. $board, $posts, $total, $page, $perPage 등이 자동으로 주입됩니다.
<?php if (!defined('DX_CMS')) exit; ?>
<h1><?php echo htmlspecialchars($board['board_name'], ENT_QUOTES); ?></h1>
<?php foreach ($posts as $p): ?>
<div>
<a href="<?php echo dx_base_url($board['board_key']'/view/'.$p['id']); ?>">
<?php echo htmlspecialchars($p['title'], ENT_QUOTES); ?>
</a>
<span><?php echo $p['created_at']; ?></span>
</div>
<?php endforeach; ?>
11.3 제공 스킨 목록
| 스킨명 |
타입 |
특징 |
| default |
기본 게시판 |
테이블 형 목록, 검색, 공지, 카테고리 탭, 관리자 bulk 지원 |
| gallery |
갤러리 |
썸네일 그리드, 라이트박스, 좋아요, 실시간 소켓 지원 |
| shop |
쇼핑몰 |
상품 목록, 장바구니(/cart), 주문/결제 커스텀 액션 포함 |
12장. 관리자 사용방법
12.1 게시판 생성
- 관리자 → 게시판 관리 → 게시판 추가 버튼 클릭
- 게시판 키 입력 — URL에 사용될 영문자/숫자/하이픈/언더스코어 (예: notice, free-board)
- 게시판 이름 입력 — 화면에 표시될 이름
- 게시판 유형 선택 — normal / gallery / qa / faq / news
- 스킨 선택 — boards/skins/ 폴더에 있는 스킨이 자동으로 드롭다운에 표시됨
- 권한 설정 — 읽기/쓰기/댓글 각각 0=전체/1=회원/9=관리자
- 기능 설정 — 댓글•파일•공지•카테고리•태그•설문 ON/OFF
- 저장
12.2 카테고리 관리
- 관리자 → 게시판 관리 → 해당 게시판 → 카테고리 탭 클릭
- 카테고리 추가 — 이름, 상위 카테고리, 색상, 목록/뷰 표시 여부 설정
- 순서 변경 — sort_order 숫자 수정 (낮을수록 먼저)
- 계층 설정 — 상위 카테고리를 선택하면 depth 자동 계산
- 비활성화 — status=0으로 변경하면 프런트에서 숨겨짐
12.3 전체 공지 관리
- 관리자 → 전체 공지 → 공지 추가
- 제목, 색상, 링크 URL, 링크 타겟(_self/_blank) 입력
- 게시 기간 설정 — 시작일과 종료일 (비워두면 즉시~무기한)
- 정렬 순서 설정 — sort_order 낮을수록 위에 표시
- 저장 — 모든 게시판 목록 상단에 자동 표시됨
12.4 게시글 관리 (목록에서)
목록 페이지에서 관리자는 아래 작업을 수행할 수 있습니다:
- 체크박스 선택 — 글 앞의 체크박스를 클릭하여 일괄 처리 대상 선택
- 일괄 삭제 — 선택한 글을 댓글•파일•좋아요•스크랩까지 완전 삭제
- 일괄 이동 — 선택한 글을 다른 게시판으로 이동 (게시판 선택 화면으로 이동)
- 일괄 복사 — 선택한 글을 다른 게시판으로 복사
12.5 설문 게시글 작성
- use_survey=1인 게시판에서 글쓰기 클릭
- 제목•본문 작성
- 설문 추가 체크박스 체크
- 설문 제목, 마감일, 비회원 참여 허용, 결과 공개 조건 설정
- 문항 추가 — 단일선택(radio)/복수선택(checkbox)/단답형(text)
- 선택형 문항은 선택지를 줄바꿈으로 구분하여 입력
- 저장 — 글 저장과 동시에 설문도 저장됨
- 수정 시 설문 체크박스를 해제하면 해당 설문이 비활성화됨
13장. 훅(Hook) 연동 포인트
플러그인이나 테마에서 게시판 동작을 커스터마이징할 때 사용하는 훅입니다.
| 훅 이름 |
발생 시점 및 전달 인자 |
| dx_board_before |
핸들러 최초 진입 시 (board, action, skin, id 전달) |
| dx_board_list_context |
목록 $ctx 배열 확정 직전 (context 참조 전달 — 변경 가능) |
| dx_board_view_context |
상세보기 $ctx 배열 확정 직전 |
| dx_board_write_context |
글쓰기 폼 $ctx 배열 확정 직전 |
| dx_board_before_save |
글 저장 직전 — $data 참조 전달로 추가 필드 처리 가능 |
| dx_after_write |
글 저장 완료 후 (post_id, board, data 전달) |
| dx_board_after_save |
글 저장 후 — redirect URL 변경 가능 |
| dx_board_before_delete |
글 삭제 직전 (post, board 전달) |
| dx_board_after_delete |
글 삭제 완료 후 (post_id, board 전달) |
| dx_board_after |
핸들러 처리 완료 후 |
💡 훅 사용 예시
// 글 저장 전 커스텀 필드 추가
dx_add_hook('dx_board_before_save', function($args) {
$args['data']['custom_field'] = '값';
});
14장. 캐시 및 성능 최적화
14.1 게시판 목록 캐시
아래 조건을 모두 만족할 때 목록 데이터를 60초 캐시합니다:
- 비로그인 상태
- 검색어 없음 (?s= 파라미터 없음)
- 카테고리/태그 필터 없음
- action이 list (search 액션은 캐시 안함)
캐시 무효화는 글 작성/수정/삭제 시 자동으로 처리됩니다:
DxCache::deletePrefix('board_list_' . $boardKey . '_');
카테고리 변경 시에는 아래 캐시도 함께 삭제해야 합니다:
DxCache::deletePrefix('cat_board_'); // 카테고리 목록 캐시
14.2 DB 인덱스
게시글 목록 쿼리 성능을 위한 복합 인덱스가 포함되어 있습니다:
-- 게시판 목록 (WHERE board_id=? AND status=1 ORDER BY id DESC)
KEY idx_board_status_id (board_id, status, id)
-- 인기글 정렬 (ORDER BY popular_score DESC)
KEY idx_board_status_score (board_id, status, popular_score)
15장. 오류 처리 및 디버깅
15.1 에러 로그
CMS 관련 오류는 data/error.log 에 자동 기록됩니다. 서버 콘솔에는 출력되지 않습니다.
로그 형식: [날짜시간][레벨][파일:라인] 내용
[2026-05-10 14:23:01][ERROR][boards/handler.php:45] ...
15.2 DX_DEBUG 모드
config.php에서 DX_DEBUG를 true로 설정하면 스킨에서 발생한 Notice/Warning 목록이 화면 하단에 표시됩니다. 프로덕션 환경에서는 반드시 false로 유지하세요.
// config.php
define('DX_DEBUG', true); // 개발 시에만 사용
15.3 스킨 파일 없음 오류
6단계 폴백 체인을 모두 탐색했음에도 뷰 파일이 없으면 500 오류 박스를 표시하며, 탐색한 경로 목록을 보여줍니다. 이 오류가 발생하면:
- boards/skins/{스킨}/ 폴더에 list.php, view.php, write.php가 있는지 확인
- skin.json의 actions 배열에 해당 액션이 포함되어 있는지 확인
- themes/default/board/ 에 기본 파일이 존재하는지 확인