1장. 테마 시스템 개요
DXCMS 테마는 사이트 전체의 레이아웃•디자인•기능을 정의하는 파일 모음입니다. themes/{테마명}/ 폴더 하나가 하나의 테마이며, 현재 활성 테마에 파일이 없으면 자동으로 default 테마 파일을 사용하는 2단계 폴백 체인이 핵심입니다. 이 구조 덕분에 원하는 파일만 오버라이드하는 최소한의 커스텀 테마를 만들 수 있습니다.
1.1 테마 vs 스킨 vs 플러그인
| 구분 | 테마 | 스킨 | 플러그인 |
| 위치 | themes/{이름}/ | boards/skins/{이름}/ | plugins/{이름}/ |
| 역할 | 전체 레이아웃·디자인 | 게시판 화면만 | 기능 확장 |
| 적용 범위 | 사이트 전체 | 게시판 단위 선택 | 사이트 전체 |
| 필수 파일 | theme.json + layout/main.php | skin.json + list.php + view.php | manifest.php + plugin.php |
| 변경 방법 | 관리자 → 테마 관리 | 게시판 설정 → 스킨 선택 | 관리자 → 플러그인 관리 |
1.2 2단계 폴백 체인
DxTheme::resolve()는 항상 2단계로 파일을 탐색합니다:
1단계: themes/{현재테마}/{파일경로} → 파일이 있으면 즉시 사용
2단계: themes/default/{파일경로} → 없으면 기본 테마에서 폴백
결과: 둘 다 없으면 null 반환 → 화면에 404 또는 오류 박스
예시: 현재 테마 = "my-theme"
resolve("board/list.php") 탐색 순서:
1. themes/my-theme/board/list.php → 없음
2. themes/default/board/list.php → 있음! ✅ 이 파일 사용
💡 핵심 전략
커스텀 테마를 만들 때 바꾸고 싶은 파일만 만들면 됩니다.
layout/main.php만 바꿔도 전체 레이아웃이 바뀝니다.
나머지(게시판, 검색, 홈페이지 등)는 default 테마가 자동으로 처리합니다.
2장. 테마 디렉토리 구조
2.1 전체 파일 트리
themes/
default/ ← 기본 테마 (항상 존재, 최종 폴백 기준)
│ theme.json ← 테마 메타 정보 (필수)
│ style.css ← 테마 공통 스타일시트
│
│ layout/
│ main.php ← 전체 레이아웃 (헤더+사이드바+본문+푸터) ★필수
│ header.php ← 헤더만 (선택)
│ footer.php ← 푸터만 (선택)
│
│ board/ ← 게시판 스킨 (기본 테마 폴백용)
│ list.php ← 목록 (★필수)
│ view.php ← 상세 (★필수)
│ write.php ← 글쓰기/수정
│ _list_rows.php ← 목록 행 파셜
│ style.css ← 게시판 전용 CSS
│ {스킨명}/ ← 게시판 스킨별 오버라이드
│ list.php
│ view.php
│
│ board_latest/ ← 최신글 위젯 스킨
│ list.php ← 기본 목록형 ★필수
│ simple.php ← 심플형
│ card.php ← 카드형
│ {커스텀스킨}.php ← 직접 추가 가능
│
│ page/ ← 정적 페이지
│ home.php ← 홈페이지 (is_home=1인 페이지)
│ 404.php ← 404 오류 페이지
│ 403.php ← 403 오류 페이지
│
│ parts/ ← 재사용 파셜
│ pagination.php
│ breadcrumb.php
│
│ search/
│ list.php ← 통합 검색 결과
│
│ icons/ ← SVG 아이콘
│
└─ (assets/) ← 선택: CSS/JS/이미지
my-theme/ ← 커스텀 테마 (바꾸고 싶은 파일만)
theme.json ← 필수
layout/main.php ← 필수 (레이아웃 전체 교체)
board/list.php ← 선택 (게시판 목록만 오버라이드)
page/home.php ← 선택 (홈페이지만 오버라이드)
2.2 파일별 역할과 필수 여부
| 파일/폴더 | 필수 여부 | 역할 |
| theme.json | ★ 필수 | 테마 메타 정보. 없으면 폴더명이 이름으로 표시됨 |
| layout/main.php | ★ 필수 | 전체 레이아웃. $dx_content 변수가 본문 콘텐츠를 받음 |
| board/list.php | ★ 필수 | 게시판 목록 기본 뷰. 없으면 사이트 동작 불가 |
| board/view.php | ★ 필수 | 게시글 상세 기본 뷰 |
| board/write.php | 권장 | 글쓰기/수정 폼. 없으면 default 테마 폴백 |
| board_latest/list.php | 권장 | dx_board_latest() 함수의 기본 위젯 스킨 |
| page/home.php | 권장 | 홈페이지. 없으면 default 테마 home.php 사용 |
| search/list.php | 선택 | 통합 검색 결과. 없으면 default 폴백 |
| parts/*.php | 선택 | 파셜. dx_include_part()로 호출 |
| style.css | 선택 | 테마 공통 CSS |
| assets/ | 선택 | CSS/JS/이미지. dx_theme_asset()으로 URL 획득 |
3장. theme.json 완전 레퍼런스
3.1 theme.json 전체 형식
{
"name": "My Theme",
"version": "1.0.0",
"author": "개발자명",
"description": "테마 설명",
"preview": "assets/preview.png",
"supports": ["board", "page", "gallery", "qa"],
"options": {
"primary_color": {
"type": "color",
"label": "기본 색상",
"default": "#1a73e8"
},
"logo_text": {
"type": "text",
"label": "로고 텍스트",
"default": ""
},
"footer_desc": {
"type": "text",
"label": "푸터 설명",
"default": ""
}
}
}
3.2 각 필드 상세
| 필드 | 설명 |
| name | 관리자 테마 목록에 표시될 테마 이름 |
| version | 테마 버전 (예: 1.0.0) |
| author | 테마 제작자 이름 |
| description | 테마 설명 (관리자 화면에 표시) |
| preview | 미리보기 이미지 경로 (테마 폴더 기준 상대 경로) |
| supports | 테마가 지원하는 기능 배열. 예: ['board','page','gallery','qa'] |
| options | 관리자에서 설정 가능한 테마 옵션 정의. 키-값 객체 |
3.3 options 필드 타입
options의 각 항목은 관리자 → 테마 관리 → 옵션 편집 화면에서 입력 UI로 표시됩니다.| type 값 | 입력 UI | 설명 |
| color | 색상 피커 | 16진수 색상 코드 (#RRGGBB). 예: primary_color |
| text | 텍스트 입력란 | 단일 줄 문자열. 로고 텍스트, 푸터 문구 등 |
| textarea | 여러 줄 텍스트 | 긴 텍스트 입력. 푸터 설명, 저작권 문구 등 |
| select | 드롭다운 | 여러 선택지 중 하나. choices 배열로 선택지 정의 |
| checkbox | 체크박스 | 참/거짓 값 (on/off 설정) |
3.4 options 값 읽기
theme.json의 options에 정의된 값은 관리자가 저장하면 settings 테이블에 theme_{테마명}_{키} 형식으로 저장됩니다. 테마 파일에서 아래 함수로 읽습니다:
// layout/main.php 등 테마 파일에서 사용
$primaryColor = dx_theme_option("primary_color", "#1a73e8");
$logoText = dx_theme_option("logo_text", "");
$footerDesc = dx_theme_option("footer_desc", "");
// 내부 저장 키 형식 (settings 테이블)
// theme_{테마명}_{옵션키}
// 예: theme_my-theme_primary_color → "#FF5733"
💡 default 테마 options 목록
primary_color — 기본 색상 (CSS --p 변수에 적용)
logo_text — 로고 텍스트 (비우면 site_name 사용)
footer_desc — 푸터 설명 문구
company_name — 상호명 (푸터 사업자 정보)
ceo_name — 대표자명
biz_no — 사업자번호
contact_email — 연락처 이메일
contact_phone — 연락처 전화번호
sns_instagram — 인스타그램 URL
sns_github — GitHub URL
footer_terms_url — 이용약관 URL
footer_privacy_url — 개인정보처리방침 URL
4장. layout/main.php 구조 상세
layout/main.php는 테마의 핵심 파일입니다. HTML 전체 골격을 담당하며, 헤더•내비게이션•사이드바•본문($dx_content)•푸터를 모두 포함합니다.
4.1 $dx_content — 본문 콘텐츠 주입
Dispatcher가 요청된 페이지(게시판, 홈, 정적 페이지 등)의 HTML을 ob_start()로 캡처한 후 $dx_content 변수에 담아 layout/main.php로 전달합니다. main.php는 원하는 위치에 이 변수를 출력합니다.
<!-- layout/main.php 본문 위치 (예시) -->
<main>
<?php echo isset($dx_content) ? $dx_content : ""; ?>
</main>
<!-- 사이드바가 있을 때 (default 테마 방식) -->
<div style="display:flex;gap:22px">
<div style="flex:1">
<?php echo isset($dx_content) ? $dx_content : ""; ?>
</div>
<?php if ($_showSidebar): ?>
<aside style="width:220px"><!-- 사이드바 --></aside>
<?php endif; ?>
</div>
4.2 $context — 현재 페이지 컨텍스트
main.php에는 $context 배열도 함께 전달됩니다. 현재 페이지 유형과 게시판 정보에 따라 레이아웃을 동적으로 조정할 수 있습니다.| $context 키 | 값 및 설명 |
| $context['type'] | 'board' | 'home' | 'page' | 'search' | 'auth' | 'mypage' 등 |
| $context['slug'] | 게시판 키 또는 페이지 슬러그 (board_key 등) |
| $context['board'] | type='board'일 때 게시판 설정 배열 (dx_boards 레코드) |
| $context['page'] | type='page'일 때 페이지 설정 배열 |
| $context['categories'] | 게시판의 카테고리 배열 (사이드바 카테고리 탭용) |
| $context['currentCategory'] | 현재 선택된 카테고리 이름 |
// layout/main.php에서 컨텍스트 활용 예시
$ctx = isset($context) ? $context : array();
$isBoard = isset($ctx["type"]) && $ctx["type"] === "board";
$bk = isset($ctx["slug"]) ? $ctx["slug"] : "";
// 게시판 페이지에서만 사이드바 표시
if ($isBoard && !empty($ctx["categories"])) {
// 카테고리 사이드바 렌더링...
}
4.3 CSS 변수 시스템 (다크모드 대응)
default 테마는 CSS Custom Properties(변수)로 색상과 배경을 관리합니다. body.dark 클래스가 추가되면 다크모드 변수로 자동 전환됩니다.| CSS 변수 | 라이트 기본값 | 다크 기본값 | 용도 |
| --p | #1a73e8 | (동일) | 주요 강조색. dx_theme_option("primary_color")로 변경 |
| --bg-body | #f1f3f5 | #0f172a | 페이지 전체 배경 |
| --bg-card | #ffffff | #1e293b | 카드·패널 배경 |
| --bg-header | #ffffff | #1e293b | 헤더 배경 |
| --text-main | #1e293b | #f1f5f9 | 주요 텍스트 색 |
| --text-muted | #64748b | #94a3b8 | 보조 텍스트 색 |
| --border | #e2e8f0 | #334155 | 테두리·구분선 |
| --hover-row | #f8faff | #243044 | 호버 시 배경 |
/* 커스텀 테마 CSS에서 활용 예시 */ .my-card {
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-main);
}
.my-title { color: var(--p); }
/* 다크모드 추가 스타일 */ body.dark .my-special { opacity: 0.8; }
4.4 훅 포인트
layout/main.php는 세 곳에 훅 포인트를 제공합니다. 플러그인과 테마가 이 훅으로 <head>•스크립트•푸터 영역에 코드를 삽입할 수 있습니다.| 훅 이름 | 위치 및 용도 |
| dx_head | <head> 태그 안 — CSS, meta 태그, JSON-LD 스크립트 삽입용. $context 배열 전달 |
| dx_footer_scripts | </body> 직전 — 추가 JS 파일 삽입용 |
| dx_body_bottom | </body> 최하단 — 분석 스크립트, 채팅 위젯 등 |
// 플러그인에서 <head>에 CSS 삽입 예시
dx_add_hook("dx_head", function($context) {
echo '<link rel="stylesheet" href="' . dx_base_url("plugins/my-plugin/style.css") . '">';
});
// 플러그인에서 </body> 직전에 JS 삽입
dx_add_hook("dx_body_bottom", function() {
echo '<script src="' . dx_base_url("plugins/my-plugin/script.js") . '"></script>';
});
4.5 사이드바 자동 표시 조건
default 테마의 사이드바는 아래 두 조건 중 하나를 만족하면 자동으로 표시됩니다:
- 서브메뉴 존재 — 현재 URL이 속한 상위 메뉴에 서브메뉴가 있을 때
- 게시판 카테고리 — 현재 게시판에 카테고리가 설정되어 있을 때 카테고리 탭 표시
4.6 메뉴 시스템
main.php는 DB에서 메뉴를 로드하여 헤더 내비게이션을 렌더링합니다. 메뉴는 120초 캐시가 적용됩니다.| 변수 | 설명 |
| $_nms | 최상위 메뉴 배열 (parent_id=0) |
| $_allSubs | 전체 서브메뉴 배열 |
| $_subMap[부모ID] | 각 최상위 메뉴의 하위 메뉴 배열 (사이드바용) |
| $_newBkSet | 신규 글이 있는 board_key 집합 (메뉴 N 배지용) |
5장. 폴백 체인 전체 상세
5.1 DxTheme::resolve() — 기본 파일 탐색
// 사용 예
$file = DxTheme::getInstance()->resolve("board/list.php");
// 또는 헬퍼 함수
$file = dx_theme_file("board/list.php");
// 탐색 순서:
// 1. themes/{현재테마}/board/list.php
// 2. themes/default/board/list.php
// 3. 없으면 null 반환
5.2 resolveBoardSkin() — 게시판 스킨 탐색
게시판 스킨(boards/skins/)에 파일이 없을 때 테마에서 탐색하는 경로입니다.| 순위 | 경로 | 설명 |
| 1 | themes/{현재테마}/board/{스킨명}/{액션}.php | 현재 테마 스킨 전용 뷰 |
| 2 | themes/{현재테마}/board/{액션}.php | 현재 테마 공통 뷰 |
| 3 | themes/default/board/{스킨명}/{액션}.php | default 테마 스킨 전용 뷰 |
| 4 | themes/default/board/{액션}.php | default 테마 공통 뷰 (최종 폴백) |
5.3 resolveLatestSkin() — board_latest 위젯 탐색
// dx_board_latest("free", 5, "card") 호출 시 탐색 순서:
1. themes/{현재테마}/board_latest/card.php
2. themes/default/board_latest/card.php
3. themes/default/board_latest/list.php ← 최종 폴백 (항상 존재)
5.4 resolvePart() — 파셜 탐색
// dx_include_part("pagination", [...]) 호출 시 탐색 순서:
1. themes/{현재테마}/parts/pagination.php
2. themes/default/parts/pagination.php
3. 없으면 아무것도 출력 안 함 (조용히 실패)
6장. 테마 헬퍼 함수 완전 레퍼런스
6.1 DxTheme 전용 헬퍼
| 함수 시그니처 | 설명 및 반환값 |
| dx_theme_file('board/list.php') | 테마 파일 절대 경로 반환 (폴백 체인 적용). null 가능 |
| dx_theme_asset('css/style.css') | themes/{현재테마}/assets/ 기준 URL 반환 |
| dx_theme_option('primary_color', '#1a73e8') | theme.json options에 정의된 값 반환. 기본값 지정 가능 |
| dx_include_part('pagination', ['total'=>100]) | parts/ 파셜 파일 include. 두 번째 인자 변수가 파셜에서 사용 가능 |
| DxTheme::getInstance()->getTheme() | 현재 활성 테마명 반환 (예: 'default', 'my-theme') |
| DxTheme::getInstance()->meta() | 현재 테마의 theme.json 배열 반환 |
| DxTheme::getInstance()->allThemes() | 설치된 모든 테마 목록 배열 반환 (관리자용) |
| DxTheme::reset() | 싱글턴 리셋 (테마 변경 후 재초기화 시 사용) |
6.2 board_latest 위젯 함수
dx_board_latest()는 특정 게시판의 최신글을 board_latest 스킨으로 렌더링합니다. 테마의 홈페이지나 사이드바에서 자주 사용합니다.
// 함수 시그니처
dx_board_latest(
$boardKey, // 게시판 키 (필수)
$limit = 5, // 표시할 글 수
$skin = "list", // 스킨명 (list|simple|card|커스텀)
$title = "", // 섹션 제목 (비우면 게시판 이름 자동)
$icon = "", // 이모지 아이콘 (예: "📋")
$titleLen = 0, // 제목 최대 길이 (0=무제한)
$showExcerpt = false // 내용 발췌 표시 여부
);
// 사용 예
<?php dx_board_latest("notice", 5, "simple", "공지사항", "📢"); ?>
<?php dx_board_latest("free", 10, "card", "자유게시판", "💬", 30, true); ?>
<?php dx_board_latest("gallery", 8, "card"); ?>
6.3 board_latest 스킨 파일 변수
board_latest/{스킨}.php 파일에서 사용 가능한 변수:| 변수 | 설명 |
| $board_key | 게시판 키 문자열 |
| $board_name | 게시판 이름 |
| $title | 표시 제목 (지정 없으면 board_name) |
| $icon | 이모지 아이콘 문자열 |
| $more_url | 더보기 URL (게시판 목록 URL) |
| $posts | 최신글 배열 (아래 필드 포함) |
| $show_excerpt | 내용 발췌 표시 여부 (bool) |
6.4 $posts 배열 각 요소 필드
| 필드명 | 설명 |
| id | 게시글 ID |
| title | 제목 (titleLen 적용 후) |
| author | 작성자 이름 |
| date | 날짜 (Y-m-d 형식) |
| date_short | 날짜 단축 (m.d 형식) |
| url | 게시글 view URL |
| board_key | 게시판 키 |
| view_count | 조회수 (int) |
| comment_count | 댓글 수 (int) |
| excerpt | 내용 발췌 (show_excerpt=true일 때, 최대 100자) |
6.5 dx_board_posts() — 원시 데이터 조회
스킨 없이 최신글 배열만 필요할 때 사용합니다
$posts = dx_board_posts(
"notice", // 게시판 키
5, // 가져올 글 수
false, // 공지글 포함 여부
30, // 제목 최대 길이 (0=무제한)
100 // 내용 발췌 길이 (0=발췌 없음)
);
foreach ($posts as $post) {
echo $post["title"]; // 제목
echo $post["url"]; // 게시글 URL
echo $post["date"]; // 날짜
echo $post["excerpt"]; // 내용 발췌
}
6.6 기타 테마 관련 전역 헬퍼
| 함수 | 설명 |
| dx_is_login() | 로그인 여부 확인 (bool). Auth::getInstance()->isLoggedIn() 단축 |
| dx_is_admin() | 관리자 여부 확인 (bool) |
| dx_base_url('path') | 사이트 절대 URL 생성 |
| dx_config('site_name', '기본값') | 사이트 설정값 읽기 |
| dx_csrf_token() | CSRF 토큰 값 반환 (meta 태그용) |
| dx_run_hook('훅명', $args) | 훅 실행 — 플러그인 연동 |
7장. board_latest 위젯 스킨 제작
7.1 기본 제공 스킨
| 스킨 파일 | 특징 |
| list.php | 기본 목록형. 제목·날짜·조회수를 한 줄로 표시. 더보기 링크 포함 |
| simple.php | 심플 목록형. 제목만 한 줄. 가장 가볍고 깔끔한 디자인 |
| card.php | 카드형. 썸네일 이미지 + 제목 + 메타 정보. 갤러리 게시판에 적합 |
7.2 커스텀 board_latest 스킨 만들기
themes/{현재테마}/board_latest/ 또는 themes/default/board_latest/ 폴더에 PHP 파일을 추가하면 새 스킨이 됩니다.
<?php
// themes/my-theme/board_latest/timeline.php
// 타임라인형 위젯 스킨 예시
if (!defined("DX_CMS")) exit;
?>
<div class="my-timeline">
<div class="timeline-header">
<?php if ($icon): ?><span><?php echo htmlspecialchars($icon, ENT_QUOTES); ?></span><?php endif; ?>
<strong><?php echo htmlspecialchars($title, ENT_QUOTES, "UTF-8"); ?></strong>
<a href="<?php echo htmlspecialchars($more_url, ENT_QUOTES); ?>">더보기</a>
</div>
<?php if (empty($posts)): ?>
<p>글이 없습니다.</p>
<?php else: ?>
<ul>
<?php foreach ($posts as $post): ?>
<li>
<span class="date"><?php echo $post["date_short"]; ?></span>
<a href="<?php echo htmlspecialchars($post["url"], ENT_QUOTES); ?>">
<?php echo htmlspecialchars($post["title"], ENT_QUOTES, "UTF-8"); ?>
</a>
<?php if ($post["comment_count"] > 0): ?>
<span>[<?php echo $post["comment_count"]; ?>]</span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<?php /* 호출: dx_board_latest("free", 5, "timeline", "최신글", "🕐"); */ ?>
8장. extend/ 폴더 시스템
8.1 extend/란
extend/ 폴더는 PHP 파일을 넣기만 하면 CMS가 자동으로 실행하는 "파일 기반 훅" 시스템입니다. 플러그인을 별도로 등록하지 않아도 되므로 빠른 커스텀 코드 삽입에 유용합니다.| 폴더 | 실행 시점 | 사용 예 |
| extend/top/ | CMS 초기화 완료 직후 (DB·세션·인증 완료, 라우팅 전) | 점검모드, IP 차단, 다크모드 FOUC 제거 |
| extend/middle/ | 라우트 확정 직후 (핸들러 실행 전) | 권한 체크 추가, 요청 로깅 |
| extend/bottom/ | 렌더링 완료 후 (ob_end_flush 직전) | 성능 로그, 분석 코드 |
파일명 오름차순으로 실행됩니다. 예: 01_darkmode.php → 02_maintenance.php → 03_custom.php
에러가 발생해도 다른 파일 실행에 영향을 주지 않습니다.
8.2 extend/ 실전 예시
점검 모드 (extend/top/99_maintenance.php)
<?php
// extend/top/99_maintenance.php
// 파일명을 .disabled 로 끝내면 무시됨
if (!defined("DX_CMS")) exit;
$isAdmin = function_exists("dx_is_admin") ? dx_is_admin() : false;
if (!$isAdmin) {
http_response_code(503);
echo "<!DOCTYPE html><html><body><h1>점검 중입니다</h1><p>잠시 후 다시 방문해 주세요.</p></body></html>";
exit;
}
성능 로그 (extend/bottom/99_perf_log.php)
<?php
// extend/bottom/99_perf_log.php
if (!defined("DX_CMS")) exit;
$elapsed = microtime(true) - (defined("DX_START") ? DX_START : microtime(true));
dx_log("페이지 생성: " . round($elapsed * 1000) . "ms — " . $_SERVER["REQUEST_URI"], "warning");
9장. 커스텀 테마 제작 완전 가이드
9.1 최소 구조 (레이아웃만 교체)
가장 단순한 커스텀 테마는 theme.json + layout/main.php 두 파일만으로 구성됩니다. 나머지 파일(게시판, 검색, 홈페이지 등)은 default 테마가 자동으로 처리합니다.
themes/my-theme/
├── theme.json ← 테마 메타 정보
└── layout/main.php ← 전체 레이아웃 교체
9.2 theme.json 작성
{
"name": "나의 테마",
"version": "1.0.0",
"author": "나의 이름",
"description": "미니멀 커스텀 테마",
"supports": ["board", "page"],
"options": {
"primary_color": {
"type": "color",
"label": "주 색상",
"default": "#6366f1"
},
"site_slogan": {
"type": "text",
"label": "사이트 슬로건",
"default": "DXCMS로 만든 사이트"
}
}
}
9.3 layout/main.php 최소 구현
<?php if (!defined("DX_CMS")) exit; ?>
<?php
$siteName = dx_config("site_name", "내 사이트");
$primaryColor = dx_theme_option("primary_color", "#6366f1");
$slogan = dx_theme_option("site_slogan", "");
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($siteName, ENT_QUOTES); ?></title>
<style>
:root { --p: <?php echo htmlspecialchars($primaryColor, ENT_QUOTES); ?>; }
body { margin: 0; font-family: sans-serif; background: #f8fafc; }
header { background: var(--p); color: #fff; padding: 16px 24px; }
main { max-width: 960px; margin: 24px auto; padding: 0 16px; }
footer { text-align: center; padding: 24px; color: #64748b; font-size: .85rem; }
</style>
<meta name="csrf-token" content="<?php echo dx_csrf_token(); ?>">
<?php dx_run_hook("dx_head", isset($context) ? $context : array()); ?>
</head>
<body>
<header>
<a href="<?php echo dx_base_url(); ?>" style="color:#fff;text-decoration:none;font-size:1.2rem;font-weight:700">
<?php echo htmlspecialchars($siteName, ENT_QUOTES); ?>
</a>
<?php if ($slogan): ?>
<p style="margin:4px 0 0;font-size:.8rem;opacity:.8"><?php echo htmlspecialchars($slogan, ENT_QUOTES); ?></p>
<?php endif; ?>
</header>
<main>
<?php echo isset($dx_content) ? $dx_content : ""; ?>
</main>
<footer>
<p>© <?php echo date("Y"); ?> <?php echo htmlspecialchars($siteName, ENT_QUOTES); ?></p>
</footer>
<?php dx_run_hook("dx_footer_scripts", array()); ?>
<?php dx_run_hook("dx_body_bottom"); ?>
</body>
</html>
9.4 홈페이지 커스텀 (page/home.php)
is_home=1인 페이지가 요청될 때 themes/{테마}/page/home.php가 사용됩니다. dx_board_latest()로 최신글 위젯을 배치할 수 있습니다.
<?php if (!defined("DX_CMS")) exit; ?>
<!-- 히어로 섹션 -->
<div style="background:var(--p);color:#fff;padding:60px 24px;text-align:center;border-radius:16px;margin-bottom:32px">
<h1 style="font-size:2rem;margin:0 0 12px">환영합니다!</h1>
<p style="opacity:.85;margin:0">우리 사이트에 오신 것을 환영합니다.</p>
</div>
<!-- 최신글 위젯 2컬럼 -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
<div>
<?php dx_board_latest("notice", 5, "simple", "공지사항", "📢"); ?>
</div>
<div>
<?php dx_board_latest("free", 10, "simple", "자유게시판", "💬"); ?>
</div>
</div>
<!-- 갤러리 최신글 -->
<div style="margin-top:32px">
<?php dx_board_latest("gallery", 8, "card", "갤러리", "🖼️"); ?>
</div>
9.5 부분 오버라이드 전략
전체 레이아웃을 유지하면서 특정 페이지만 바꾸는 방법입니다:| 목표 | 파일 추가 위치 |
| 홈페이지 디자인만 교체 | themes/my-theme/page/home.php |
| 게시판 목록 레이아웃 교체 | themes/my-theme/board/list.php |
| 게시글 상세 레이아웃 교체 | themes/my-theme/board/view.php |
| 검색 결과 페이지 교체 | themes/my-theme/search/list.php |
| 최신글 위젯 스킨 추가 | themes/my-theme/board_latest/{스킨}.php |
| 파셜(페이지네이션 등) 교체 | themes/my-theme/parts/{파셜명}.php |
| 갤러리 스킨 오버라이드 | themes/my-theme/board/gallery/list.php |
10장. 관리자에서 테마 관리
10.1 테마 활성화
- themes/ 폴더에 테마 폴더 업로드 (theme.json + layout/main.php 필수)
- 관리자 → 테마 관리 메뉴 접속
- 설치된 테마 목록 확인 (themes/ 폴더를 자동 스캔)
- 원하는 테마의 "활성화" 버튼 클릭
- settings 테이블의 theme 키가 해당 테마 폴더명으로 저장됨
- DxTheme::reset() 호출로 캐시 초기화 및 즉시 적용
10.2 테마 옵션 편집
- 관리자 → 테마 관리 → 해당 테마의 "옵션 편집" 클릭
- theme.json의 options에 정의된 항목이 입력 UI로 표시됨
- 값 입력 후 "저장" 클릭
- settings 테이블에 theme_{테마명}_{키} 형식으로 저장
- 테마 파일에서 dx_theme_option()으로 즉시 읽기 가능
10.3 테마 저장 구조 (DB)
-- 활성 테마 저장
settings.setting_key = "theme"
settings.setting_value = "my-theme"
-- 테마 옵션 저장 형식
settings.setting_key = "theme_my-theme_primary_color"
settings.setting_value = "#FF5733"
settings.setting_key = "theme_my-theme_logo_text"
settings.setting_value = "나의 사이트"
10.4 테마 변경 후 주의사항
- 캐시 초기화 — 테마 변경 시 DxTheme::reset() + DxCache 초기화가 자동 실행됨
- 옵션 초기화 — 새 테마로 변경 후 이전 테마의 옵션은 DB에 남아있지만 미사용 상태
- 게시판 스킨 유지 — 테마를 변경해도 각 게시판의 스킨 설정은 변경되지 않음
- default 테마 주의 — default 테마 파일 수정 시 CMS 업데이트로 덮어씌워질 수 있음. 커스텀 테마 폴더를 따로 만들 것을 권장
11장. 테마 제작 자주 묻는 질문
Q1. 테마를 만들 때 반드시 필요한 파일은?
theme.json과 layout/main.php 두 파일이 필수입니다. 이 두 파일만 있어도 완전히 동작하는 테마가 됩니다. 게시판, 검색, 홈페이지 등 나머지는 default 테마가 자동으로 처리합니다.
Q2. default 테마 파일을 직접 수정해도 되나요?
동작은 하지만 권장하지 않습니다. DXCMS 업데이트 시 default 테마 파일이 덮어씌워질 수 있습니다. 새 폴더에 커스텀 테마를 만들고 바꾸고 싶은 파일만 추가하는 방식을 권장합니다.
Q3. 다크모드를 지원하는 테마를 만들려면?
CSS 변수(--bg-card, --text-main 등)를 사용하고 body.dark 클래스에 대한 다크 색상을 정의하면 됩니다. extend/top/01_darkmode_early.php의 FOUC 제거 스크립트도 함께 사용하면 깜빡임 없는 다크모드가 구현됩니다.
Q4. 게시판마다 다른 레이아웃을 쓰고 싶습니다.
테마는 사이트 전체에 적용됩니다. 게시판마다 다른 화면 구성이 필요하면 게시판 스킨(boards/skins/)을 사용하세요. 스킨은 게시판 단위로 설정 가능하며, 목록•뷰•글쓰기 화면을 완전히 교체할 수 있습니다.
Q5. board_latest 위젯에 내 스킨을 추가하려면?
themes/{현재테마}/board_latest/{스킨명}.php 또는 themes/default/board_latest/{스킨명}.php 파일을 만들면 즉시 사용 가능합니다. dx_board_latest("게시판키", 5, "{스킨명}") 형태로 호출합니다.
Q6. 테마 옵션(theme.json options)에 select나 checkbox도 되나요?
type: "select"와 type: "checkbox"를 사용할 수 있습니다. select는 choices 배열로 선택지를 정의하고, checkbox는 저장 값이 "on"/"off" 문자열입니다. 관리자 테마 옵션 편집 화면에서 자동으로 해당 UI가 표시됩니다.
Q7. 플러그인이 로드한 CSS/JS가 테마보다 먼저 실행되나요?
dx_head 훅은 layout/main.php의 <head> 내부에서 실행됩니다. 플러그인이 dx_add_hook("dx_head")에 CSS/JS를 등록하면 테마의 <head> 안에 삽입됩니다. 우선순위(세 번째 인자)로 실행 순서를 조정할 수 있습니다.