DXCMS Direct 개발 매뉴얼

그누보드 개발자를 위한 DXCMS 직접 개발 가이드 — 이 페이지 하나로 바로 시작할 수 있습니다

① 시작/경로② 로그인/회원③ DB 쿼리 ④ URL/경로⑤ 설정/공용함수⑥ CSRF/보안 ⑦ 포인트⑧ 훅(Hook)⑨ 라우터 연결 ⑩ extend 활용⑪ 알림⑫ 주의사항 ⑬ 실행 예제 ⑭ 테마 레이아웃 ⑮ 게시판 불러오기 ⑯ 페이지네이션
1
시작 — 어느 폴더에서든 자동 탐색
_common.php 대체
그누보드
// 경로 자동 탐색
$path = __DIR__;
while (!file_exists($path.'/common.php')) {
    $p = dirname($path);
    if ($p === $path) break;
    $path = $p;
}
include_once $path.'/common.php';
DXCMS — 완전 동일한 방식
// 방법 1: dx_load.php가 같은 폴더
require_once 'dx_load.php';

// 방법 2: 경로 자동 탐색 (어느 폴더에서든)
$p = __DIR__;
while (!file_exists($p.'/dx_load.php')) {
    $pp = dirname($p);
    if ($pp === $p) break;
    $p = $pp;
}
require_once $p.'/dx_load.php';
방법 2를 권장합니다. 어느 폴더에서든, 몇 단계 깊이든 ../../ 없이 동작합니다. dx_load.php 는 DXCMS 루트에 한 번만 놓으면 됩니다.
2
로그인 / 회원 정보
is_login() / $member 대체
그누보드
is_login()             // 로그인 여부
is_admin()             // 관리자 여부
$member['mb_id']
$member['mb_name']
$member['mb_email']
$member['mb_point']
$member['mb_level']
DXCMS
dx_is_login()          // 로그인 여부
dx_is_admin()          // 관리자 여부
$auth = Auth::getInstance();
$user = $auth->user();
$user['id']    // BIGINT 문자열!
$user['name']
$user['email']
$user['point']
$user['level']
// 로그인 필수 페이지 패턴
if (!dx_is_login()) {
    header('Location: ' . dx_base_url('auth/login') . '?redirect=' . urlencode(dx_current_url()));
    exit;
}

// 관리자 필수 페이지 패턴
if (!dx_is_admin()) {
    http_response_code(403);
    exit('접근 권한이 없습니다.');
}
로그인 상태
❌ 비로그인
관리자
일반 회원
⚠ $user['id'] 는 BIGINT(밀리초 타임스탬프)입니다. (int) 캐스팅 절대 금지. 문자열 그대로 사용하세요.
3
DB 쿼리
sql_query / sql_fetch 대체
그누보드
// 여러 행
$res = sql_query("SELECT * FROM ...");
while($row=sql_fetch_array($res)){}

// 단일 행
$row = sql_fetch("SELECT * FROM ...");

// 값 변경
sql_query("INSERT INTO ...");
DXCMS
// 여러 행
$db = Database::getInstance();
$rows = $db->rows("SELECT * FROM `{$db->table('posts')}`");

// 단일 행 (파라미터 바인딩)
$row = $db->row(
    "SELECT * FROM `{$db->table('posts')}` WHERE id=?",
    array($id)
);

// INSERT / UPDATE / DELETE
$db->insertRow('my_table', array(...));
$db->updateRow('my_table', array(...), array(...));
$db->deleteRow('my_table', array('id'=>$id));
// ─── 전체 메서드 목록 ───────────────────────────────────────────────
$db->rows($sql, $params)             // 여러 행 → 2차원 배열 (없으면 빈 배열)
$db->row($sql, $params)              // 단일 행 → 1차원 배열 (없으면 null)
$db->value($sql, $params)            // 단일 값 (COUNT 등)
$db->query($sql, $params)            // INSERT/UPDATE/DELETE 직접 실행
$db->insertRow('테이블명', $data)     // 편의 INSERT → 새 ID 반환
$db->updateRow('테이블명', $set, $where) // 편의 UPDATE
$db->deleteRow('테이블명', $where)   // 편의 DELETE
$db->table('posts')                  // → 'dx_posts' (prefix 자동)
$db->pdo()                            // PDO 직접 접근 (DDL, 트랜잭션 등)

// ─── 안전 패턴 (항상 try/catch 감싸기) ─────────────────────────────
$rows = array();
try {
    $rows = $db->rows("SELECT id, title FROM `{$db->table('posts')}` WHERE status=1 LIMIT 10");
} catch (Exception $e) {
    // 테이블 없거나 컬럼명 다른 경우 대비
}
제목날짜
자 오늘도 화이팅하세요. 2026-06-10
모아님 보.고.시.퍼.요 2026-06-10
다 좋았는데 다 웃었는데 | 집에 가는 길이 유난히 허전한 밤 [AI Song] 2026-06-10
CMS관련 문의드립니다 2026-06-09
서비스 장애 복구 완료 안내 2026-06-09
4
URL / 서버 경로
G5_URL / G5_PATH 대체
그누보드
G5_URL       // 사이트 루트 URL
G5_PATH      // 루트 절대경로
G5_DATA_PATH // data 폴더
G5_BBS_URL   // /bbs URL
DXCMS
dx_base_url()              // 사이트 루트 URL
dx_base_url('notice/list') // 내부 URL
dx_current_url()           // 현재 페이지 URL
DX_ROOT    // 루트 절대경로
DX_DATA    // /data 폴더
DX_CORE    // /core 폴더
DX_PLUGINS // /plugins 폴더
dx_base_url()
https://designonex.com/
DX_ROOT
보안상 경로출력안함
DX_VERSION
8.1.0
PHP 버전
5.6.31
5
사이트 설정 / 공용 함수
$config / 내장 함수 대체
// ─── 설정 조회 ($config 대체) ────────────────────────────────────────
dx_config('site_name',  '기본값')   // 사이트명
dx_config('admin_email', '')       // 관리자 이메일
dx_config('theme',       'default') // 현재 테마
dx_config('upload_max_size', '')    // 업로드 최대 크기

// ─── URL / 경로 헬퍼 ────────────────────────────────────────────────
dx_base_url('board/list')    // 내부 URL 생성
dx_current_url()              // 현재 페이지 전체 URL
dx_redirect($url)            // 리다이렉트 후 exit
dx_redirect_back('/')         // 이전 페이지로 (fallback 지정)

// ─── 입력값 처리 ────────────────────────────────────────────────────
dx_get('key', '기본값')       // $_GET 안전하게 가져오기
dx_post('key', '기본값')      // $_POST 안전하게 가져오기
dx_method('POST')            // 요청 메서드 확인

// ─── 파일/이미지 ────────────────────────────────────────────────────
dx_filesize($bytes)           // 파일 크기를 읽기 좋게 (예: "1.2 MB")
dx_base_url('data/uploads/파일.jpg') // 업로드 파일 URL
DxThumb::url('data/uploads/파일.jpg', 300) // 300px 썸네일 URL

// ─── 기타 ────────────────────────────────────────────────────────────
dx_ip()                        // 접속자 IP (프록시 고려)
dx_date($datetime, 'Y.m.d')  // 날짜 형식 변환
dx_is_mobile()                 // 모바일 여부
dx_mb_substr($str, 0, 20)     // 멀티바이트 안전 substr
dx_json(array('success'=>true)) // JSON 응답 + exit
dx_csrf_check()                // CSRF 검증 (실패 시 자동 종료)
dx_csrf_field()                // <input type="hidden" name="_csrf" ...>
dx_csrf_token()                // 토큰 문자열만
site_name
디자인원엑스
총 게시글
891건
총 회원
34명
dx_ip()
216.73.217.62
6
CSRF / POST 처리
check_token / get_token 대체
// ─── POST 처리 전형 패턴 ────────────────────────────────────────────
if (dx_method('POST')) {
    dx_csrf_check();  // ← 반드시 첫 줄! 실패 시 자동 종료

    $title = dx_post('title', '');
    $db->insertRow('my_table', array('title' => $title));
    dx_redirect(dx_base_url('direct/index.php'));
}

// ─── HTML 폼 ────────────────────────────────────────────────────────
// <form method="post">
//   <?php echo dx_csrf_field(); ?>  ← 이 한 줄만 추가
//   <input type="text" name="title">
//   <button type="submit">저장</button>
// </form>

// ─── API (Ajax JSON) 처리 ───────────────────────────────────────────
if (dx_method('POST')) {
    dx_csrf_check();
    // 처리 후
    dx_json(array('success' => true, 'message' => '저장됨'));
    // dx_json은 내부적으로 exit 호출
}
7
포인트
insert_point / use_point 대체
그누보드
insert_point(
    $mb_id, $point, '사유',
    'board', $board_id
);
DXCMS
DxPoint::add($userId, $amount, '사유');
DxPoint::subtract($userId, $amount, '사유');
$userId$user['id'] 를 그대로 전달하세요. (int) 캐스팅 금지.
현재 비로그인 상태입니다. 로그인 후 사용하세요.
8
훅 (Hook) — 이벤트 기반 확장
// ─── 훅 등록 + 콜백 ─────────────────────────────────────────────────
// ⚠ 클로저(익명함수) 금지! 반드시 일반 함수 사용 (PHP 5.6 호환)

dx_add_hook('dx_after_login', 'my_login_hook');

function my_login_hook($args) {
    $userId = $args['user']['id'];  // BIGINT 문자열
    DxPoint::add($userId, 10, '로그인 포인트');
}

// ─── 내장 훅 목록 ───────────────────────────────────────────────────
// dx_after_login          로그인 후   → $args['user']
// dx_after_logout         로그아웃 후 → $args['user']
// dx_after_register       가입 후     → $args['user_id']
// dx_board_after_save     글 저장 후  → $args['post_id'], $args['board_key']
// dx_board_after_delete   글 삭제 후  → $args['post_id']

// ─── 커스텀 훅 실행 ─────────────────────────────────────────────────
dx_run_hook('my_custom_event', array('data' => '값'));
9
라우터(DxRouter) 연결
DXCMS URL 라우팅에 등록
// ─── routes/myapp.php 파일 생성 ─────────────────────────────────────
// DXCMS 루트/routes/ 폴더에 파일을 추가하면 자동 로드됩니다.
// URL: 도메인/myapp/... 형태로 접근 가능해집니다.

// routes/myapp.php
if (!defined('DX_CMS')) exit();

require_once DX_ROOT . '/myapp/controllers/MyController.php';

DxRouter::group(array('prefix' => '/myapp'), function() {

    DxRouter::get('/',          'MyController@index');
    DxRouter::get('/list',      'MyController@list');
    DxRouter::get('/view/{id}', 'MyController@view');

    // 미들웨어 체이닝
    DxRouter::get('/mypage', 'MyController@mypage')->middleware('auth');
    DxRouter::post('/save',  'MyController@save')->middleware(array('auth', 'csrf'));
    DxRouter::any('/admin', 'MyController@admin')->middleware('admin');

});

// ─── 미들웨어 종류 ──────────────────────────────────────────────────
// 'auth'  → 로그인 필수 (비로그인 시 로그인 페이지로)
// 'admin' → 관리자 필수 (아니면 403)
// 'csrf'  → POST CSRF 토큰 검증
// 'guest' → 비로그인 전용 (로그인 상태면 메인으로)
// 'json'  → Content-Type: application/json 자동 설정
// ─── 컨트롤러 구조 (myapp/controllers/MyController.php) ─────────────
if (!defined('DX_CMS')) exit();

class MyController
{
    private $_db;
    private $_auth;

    public function __construct() {
        $this->_db   = Database::getInstance();
        $this->_auth = Auth::getInstance();
    }

    public function index($params) {
        $data = array('title' => '메인');
        $this->render('main', $data);
    }

    public function view($params) {
        $id = isset($params['id']) ? $params['id'] : '';
        // URL: /myapp/view/123 → $id = '123'
        $this->render('view', array('id' => $id));
    }

    private function render($view, $data = array()) {
        extract($data);
        require DX_ROOT . '/myapp/views/' . $view . '.php';
    }
}
10
extend/ 활용 — 자동 실행 미들웨어
// ─── extend/top/ 폴더에 파일 추가 → 모든 요청에서 자동 실행 ─────────
// 파일명 숫자 순으로 실행됩니다 (10_xxx.php → 20_xxx.php → ...)

// extend/top/10_my_middleware.php
if (!defined('DX_CMS')) exit();

// 예: 모든 페이지에서 방문자 카운트
function _my_visit_count($args) {
    $db = Database::getInstance();
    try {
        $db->query("UPDATE `{$db->table('settings')}` SET setting_value=setting_value+1 WHERE setting_key='visit_count'");
    } catch (Exception $e) {}
}
dx_add_hook('dx_page_start', '_my_visit_count');

// ─── extend/top/ 용도별 파일 예시 ───────────────────────────────────
// 10_board_fields.php  게시판 여분 필드 자동 로드
// 20_my_plugin.php     커스텀 플러그인
// 50_market_guard.php  마켓 다운로드 보호
// 90_maintenance.php   점검 모드

// ─── DX_DIRECT 플래그 (dx_load.php 로 실행 시 true) ─────────────────
if (defined('DX_DIRECT') && DX_DIRECT) {
    // Direct 방식에서만 실행할 코드
}
extend/top/ 파일들은 dx_load.php 가 자동으로 로드합니다. 별도로 include 하지 않아도 됩니다.
11
알림 발송
DxNotification::send($toMemberId, array(
    'type'    => 'system',   // 알림 타입
    'message' => '내용',
    'url'     => dx_base_url('direct/index.php'),
));

// 타입 종류: 'system', 'comment', 'like', 'mention', 'memo'
12
⚠ 반드시 지켜야 할 규칙 (PHP 5.6 호환)
// ❌ 틀림 (PHP 7+ 전용)
$name = $user['name'] ?? '기본값';       // ?? 연산자
$arr  = ['key' => 'value'];               // 배열 단축 문법
$uid  = (int)$user['id'];                 // BIGINT 캐스팅
dx_add_hook('event', function($a){});      // 클로저 훅

// ✅ 맞음 (PHP 5.6 호환)
$name = isset($user['name']) ? $user['name'] : '기본값';
$arr  = array('key' => 'value');
$uid  = $user['id'];                      // 문자열 그대로
dx_add_hook('event', 'my_function_name'); // 일반 함수

// ✅ 출력은 항상 htmlspecialchars
echo htmlspecialchars($value, ENT_QUOTES, 'UTF-8');

// ✅ DB 쿼리는 try/catch 감싸기
try { $rows = $db->rows($sql); } catch (Exception $e) { $rows = array(); }
13
실행 예제 — POST + CSRF 직접 테스트
키워드 입력
CSRF 토큰: 7f3a71b54fb1936f63ce...
폼 제출 → dx_csrf_check() 검증 → 통과 시 처리
14
테마 레이아웃 — 상단/하단 포함
사이트 헤더·푸터 그대로 사용
// ═══ 방법 A: 테마 레이아웃(헤더+콘텐츠+푸터) 완전 적용 ══════════════
require_once 'dx_load.php';

ob_start();          // ← 출력 캡처 시작
?>
<div>내 HTML 콘텐츠</div>
<?php
$dx_content = ob_get_clean();  // ← 캡처 완료

// $context: main.php 가 사이드바/SEO 판단에 사용
// Direct 방식은 'direct' 로 설정 → 사이드바 없이 전체 폭
$context = array('type' => 'direct');

$_layout = dx_theme_file('layout/main.php');
if ($_layout && file_exists($_layout)) {
    include $_layout;  // ← 테마가 $dx_content 를 감싸서 출력
} else {
    echo $dx_content;
}
exit;
// ═══ 방법 B: 테마 없이 내 HTML만 출력 (Direct 독립 페이지) ══════════
require_once 'dx_load.php';
?>
<!DOCTYPE html>
<html lang="ko">
<head><meta charset="UTF-8"><title>내 페이지</title></head>
<body>
  <?php echo dx_is_login() ? '로그인 중' : '비로그인'; ?>
</body></html>
// ═══ 기타 dx_theme_file 활용 ════════════════════════════════════════
dx_theme_file('layout/main.php')    // 테마 레이아웃 절대경로
dx_theme_file('board/basic/list.php') // 테마 내 특정 파일
dx_theme_asset('css/custom.css')      // 테마 에셋 URL
dx_theme_option('primary_color', '#1a73e8') // 테마 옵션
$dx_content: 테마 layout/main.php 가 이 변수를 감싸서 출력합니다. ob_start() → 내 콘텐츠 출력 → ob_get_clean() → include layout/main.php 순서가 핵심입니다.

→ 실행 예제: theme_example.php (테마+게시판+페이지네이션)

15
게시판 불러오기
dx_board_posts / dx_board_latest
// ─── 방법 1: 배열로 받아서 직접 출력 ────────────────────────────────
// dx_board_posts(게시판키, 개수, 공지포함, 제목길이, 요약길이)
$posts = dx_board_posts('free', 5);
// 반환 배열 구조: id(문자열), title, created_at, view_count,
//                board_key, author, thumbnail, excerpt 등

foreach ($posts as $p) {
    $postId = $p['id'];          // BIGINT 문자열 — (int) 금지!
    $title  = $p['title'];
    $date   = substr($p['created_at'], 0, 10);
    $viewUrl = dx_base_url('free/view/' . $postId);
    echo '<a href="' . $viewUrl . '">' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '</a>';
}

// ─── 방법 2: 위젯 HTML 바로 출력 (스킨 적용) ───────────────────────
// dx_board_latest(게시판키, 개수, 스킨, 제목텍스트, 아이콘)
dx_board_latest('notice', 5, 'list', '공지사항', '');
dx_board_latest('free',   5, 'list', '자유게시판', '');

// ─── 방법 3: DB 직접 조회 (커스텀 조건) ────────────────────────────
$db = Database::getInstance();
$boardRow = $db->row(
    "SELECT id FROM `{$db->table('boards')}` WHERE board_key=? AND status=1",
    array('free')
);
if ($boardRow) {
    $posts = $db->rows(
        "SELECT id, title, created_at FROM `{$db->table('posts')}`
         WHERE board_id=? AND status=1 AND is_notice=0
         ORDER BY created_at DESC LIMIT 10",
        array($boardRow['id'])
    );
}
게시판 key: 관리자 → 게시판 관리에서 확인하세요. 예) free, notice, qa 등
id 는 BIGINT 문자열: $p['id'] 그대로 URL에 넣으세요. (int) 캐스팅 금지.
16
페이지네이션
dx_pagination()
// ─── dx_pagination 기본 사용법 ──────────────────────────────────────
// dx_pagination($total, $perPage, $current, $urlPattern, $range=5)

$page    = max(1, (int)dx_get('page', 1));
$perPage = 10;

// 전체 건수 조회
$total = (int)$db->value(
    "SELECT COUNT(*) FROM `{$db->table('posts')}` WHERE status=1"
);

// {page} 가 실제 페이지 번호로 자동 치환됩니다
$pagingHtml = dx_pagination(
    $total,                  // 전체 건수
    $perPage,                // 페이지당 행 수
    $page,                   // 현재 페이지
    '?page={page}',          // URL 패턴
    5                        // 표시할 버튼 수 (기본 5)
);

// 검색 파라미터가 있는 경우
$keyword = dx_get('keyword', '');
$pagingHtml = dx_pagination(
    $total, $perPage, $page,
    '?keyword=' . urlencode($keyword) . '&page={page}'
);

// ─── 출력 ────────────────────────────────────────────────────────────
echo '<div class="my-page-wrap">' . $pagingHtml . '</div>';

// ─── CSS (페이지 버튼 스타일) ────────────────────────────────────────
// dx_pagination 이 생성하는 클래스: .dx-page-btn / .dx-page-active
// ─── OFFSET 계산 (LIMIT 쿼리와 세트로 사용) ──────────────────────────
$offset = ($page - 1) * $perPage;

$rows = $db->rows(
    "SELECT id, title FROM `{$db->table('posts')}`
     WHERE status=1
     ORDER BY created_at DESC
     LIMIT {$perPage} OFFSET {$offset}"
);
// posts.id 는 BIGINT — (int) 캐스팅 금지!
foreach ($rows as $r) {
    $id    = $r['id'];    // 문자열 그대로
    $title = $r['title'];
}

→ 실행 예제: theme_example.php (테마+게시판+페이지네이션 모두 포함)

샘플 파일