회원가입 | 고객센터 |
DESIGNONEX
디자인원엑스
About
Service
Q&A
PR리그
자유게시판N
갤러리
포인트게임
공지사항
통계
로그인 회원가입
고객센터
3.8 Extend 구조

코어 수정 없이 CMS를 확장하는 방법

A Administrator
2026.05.02 02:54(수정됨) 12 0

1. Extend 구조란 무엇인가?

DXCMS v8.1.0은 "코어 파일을 절대 수정하지 않아도 된다"는 철학을 중심으로 설계된 확장 아키텍처를 제공합니다. 이 구조를 Extend 구조라고 부르며, 크게 세 가지 레이어로 구성됩니다.


1.1 왜 코어를 수정하면 안 되는가?

많은 개발자가 기능 추가를 위해 CMS 코어 파일을 직접 수정하는 유혹에 빠집니다. 그러나 이 방식은 다음과 같은 치명적인 문제를 낳습니다.
  • 업데이트 불가: 코어 파일을 수정하면 DXCMS 새 버전이 나와도 덮어쓸 수 없게 됩니다.
  • 충돌 위험: 여러 개발자가 같은 파일을 수정하면 병합 충돌이 발생합니다.
  • 추적 불가: 어떤 기능이 어디서 추가됐는지 파악하기 어려워집니다.
  • 롤백 불가: 문제 발생 시 원래 상태로 되돌리는 것이 매우 어렵습니다.
  • 보안 위험: 핵심 보안 로직이 의도치 않게 변경될 수 있습니다.

DXCMS의 원칙
"코어는 건드리지 않는다. 모든 커스터마이징은 extend/, plugins/, 훅을 통해서만 한다."
이 원칙을 지키면 CMS를 업데이트해도 커스텀 기능이 그대로 유지됩니다.


1.2 Extend 3계층 구조

DXCMS의 확장 메커니즘은 사용 난이도와 목적에 따라 3계층으로 나뉩니다.
 
계층 수단 위치 대상 특징
1계층 (가장 쉬움) extend/ 폴더 extend/top•middle•bottom/ 누구나 파일을 폴더에 넣으면 자동 실행
2계층 (중간) Hook 시스템 plugins/ 또는 extend/ 개발자 특정 시점에 콜백 등록•실행
3계층 (심화) Plugin 시스템 plugins/{plugin-name}/ 플러그인 개발자 에디터•결제 등 교체 가능한 기능 모듈


1.3 실행 흐름 다이어그램

DXCMS 요청 처리의 전체 흐름에서 Extend 구조가 어느 시점에 실행되는지 확인하세요.
 
[ 브라우저 요청 ] → index.php
    │
    ├─ ① CMS 초기화 (DB • 세션 • 인증 • DxSite • DxTheme • 플러그인 로드)
    │
    ├─ ② extend/top/ 실행  ← 점검모드, IP 차단, 전역 변수 주입 등
    │         └─ dx_extend_top 훅도 여기서 발화
    │
    ├─ ③ Router: 라우트 확정 ($GLOBALS['dx_route'] 세팅)
    │
    ├─ ④ extend/middle/ 실행  ← 접근 로그, A/B 테스트, 라우트별 처리
    │         └─ dx_extend_middle 훅도 여기서 발화
    │
    ├─ ⑤ Dispatcher: 핸들러 실행 (페이지 렌더링)
    │
    ├─ ⑥ ob_end_flush() 직전
    │
    └─ ⑦ extend/bottom/ 실행  ← 캐시 저장, 성능 로그, 정리 작업
              └─ dx_extend_bottom 훅도 여기서 발화

[ 브라우저 응답 전송 ]


2. extend/ 폴더 자동 실행 시스템 (DxExtend)

extend/ 폴더 시스템은 "파일만 넣으면 실행되는" 가장 단순한 확장 방법입니다. DxExtend 엔진이 지정된 폴더의 PHP 파일을 파일명 오름차순으로 자동 실행합니다.


2.1 폴더 구조

extend/
├── top/
│   ├── 01_maintenance.php      ← 점검 모드
│   ├── 02_ip_block.php         ← IP 차단
│   └── 03_global_vars.php      ← 전역 변수 주입
│
├── middle/
│   ├── 01_visit_tracker.php    ← 방문자 통계 (내장)
│   ├── 02_analytics.php        ← GA 연동 등
│   └── 03_ab_test.php          ← A/B 테스트
│
└── bottom/
    ├── 01_cache_write.php       ← 페이지 캐시 저장
    ├── 02_darkmode_engine.php   ← 다크모드 JS 주입 (내장)
    └── 03_perf_log.php         ← 성능 로그

📌 파일 실행 규칙
• *.php 파일만 실행됩니다 (다른 확장자는 무시)
• 파일명 오름차순으로 실행됩니다 (01_ → 02_ → ... 순서)
• 하위 폴더의 파일도 자동 탐색합니다 (1단계 재귀)
• 한 파일에서 에러가 나도 다른 파일 실행에 영향 없음 (에러 격리)
• .disabled 확장자를 붙이면 비활성화됩니다 (PHP 인식 불가)


2.2 각 슬롯의 실행 시점과 용도


2.2.1 extend/top/ — CMS 초기화 완료 직후

항목 내용
실행 시점 DB 연결, 세션 시작, 인증, DxSite, DxTheme, 플러그인 로드가 모두 완료된 직후
가용 자원 DB 쿼리, 세션, Auth, dx_config(), dx_log(), 모든 CMS 함수 사용 가능
주의 사항 라우트 정보 ($GLOBALS['dx_route'])는 아직 확정되지 않음
권장 용도 점검 모드, IP 차단, 커스텀 인증, 전역 PHP 변수 주입, ob_start 훅 등록


2.2.2 extend/middle/ — 라우트 확정 직후

항목 내용
실행 시점 Router가 요청 URL을 분석하여 라우트를 확정한 직후, 핸들러 실행 전
가용 자원 top/의 모든 자원 + $GLOBALS['dx_route'] (현재 라우트 정보)
주의 사항 핸들러 실행 전이므로 페이지 출력을 가로챌 수 있음
권장 용도 방문자 로그, 방문 통계, 접근 제어, A/B 테스트, 라우트별 분기 처리


$GLOBALS['dx_route'] 구조 예시:

$GLOBALS['dx_route'] = array(
    'type'   => 'board',    // 라우트 타입 (page, board, admin, api 등)
    'slug'   => 'notice',   // 페이지/게시판 슬러그
    'action' => 'list',     // 동작 (list, view, write, edit 등)
    'id'     => 123,        // 게시글 ID (있는 경우)
);


2.2.3 extend/bottom/ — 렌더링 완료 후

항목 내용
실행 시점 ob_end_flush() 직전 (출력 버퍼 플러시 전)
가용 자원 모든 CMS 자원, 단 헤더 변경 불가 (이미 출력 시작)
주의 사항 ob_start가 활성인 경우 추가 출력 가능, 그렇지 않으면 헤더 불가
권장 용도 페이지 캐시 저장, 성능 측정 출력, 임시 파일 정리, JS 인젝션


2.3 파일 작성 방법

extend/ 파일 작성 시 반드시 지켜야 할 규칙이 있습니다.

필수 보안 헤더

<?php
// 직접 접근 차단 (필수)
if (!defined('DX_CMS')) exit;

// 이후에 원하는 코드 작성

⚠️ 경고: if (!defined('DX_CMS')) exit; 를 파일 첫 줄에 반드시 작성하세요.
이 한 줄이 없으면 외부에서 파일을 직접 호출하여 보안 위협이 발생할 수 있습니다.


파일명 네이밍 규칙

패턴 예시 설명
NN_설명.php 01_maintenance.php 숫자 2자리 + 언더스코어 + 설명
비활성화 99_example.php.disabled .disabled 추가 시 PHP가 인식 불가
그룹화 security/01_ip_block.php 하위 폴더로 논리 그룹화 가능


3. extend/ 실전 예제


3.1 점검 모드 구현 (top/)

사이트 점검 시 관리자를 제외한 모든 사용자에게 점검 안내 페이지를 보여줍니다.
파일: extend/top/01_maintenance.php
 
<?php
if (!defined('DX_CMS')) exit;

// ── 점검 모드 설정 ──────────────────────────
$maintenance = array(
    'enabled'  => true,                    // true: 점검 ON
    'message'  => '서비스 점검 중입니다.', // 안내 메시지
    'end_time' => '2026-05-01 03:00',      // 점검 종료 예정 시각
    'allow_ip' => array('127.0.0.1'),      // 허용 IP 목록
);

if (!$maintenance['enabled']) return;   // 점검 OFF면 즉시 종료

// 관리자는 통과
if (function_exists('dx_is_admin') && dx_is_admin()) return;

// 허용 IP는 통과
$clientIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
if (in_array($clientIp, $maintenance['allow_ip'], true)) return;

// 점검 페이지 출력
http_response_code(503);
header('Retry-After: 3600');
?>
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>서비스 점검중</title>
  <style>
    body { font-family: sans-serif; text-align: center; padding: 80px; }
    h1 { color: #1E3A5F; }
    .time { color: #2563EB; font-weight: bold; }
  </style>
</head>
<body>
  <h1>🔧 서비스 점검중</h1>
  <p>더 나은 서비스를 위해 잠시 점검 중입니다.</p>
  <p class="time">점검 종료 예정: <?php echo $maintenance['end_time']; ?></p>
</body>
</html>
<?php exit;


3.2 IP 차단 (top/)

특정 IP 또는 IP 대역을 차단합니다.
파일: extend/top/02_ip_block.php
 
<?php
if (!defined('DX_CMS')) exit;

$blockedIps = array(
    '192.168.1.100',       // 특정 IP
    '10.0.0.',             // 이 문자열로 시작하는 모든 IP
    '203.0.113.',          // 예: 특정 기관 IP 대역
);

$clientIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';

foreach ($blockedIps as $blocked) {
    if (strpos($clientIp, $blocked) === 0) {
        http_response_code(403);
        dx_log('[IP Block] 차단: ' . $clientIp, 'warning');
        exit('403 Forbidden');
    }
}


3.3 전역 변수 주입 (top/)

모든 페이지에서 공통으로 필요한 설정값이나 변수를 전역으로 주입합니다.
파일: extend/top/03_global_vars.php
 
<?php
if (!defined('DX_CMS')) exit;

// 전역 변수 정의
define('MY_APP_VERSION', '2.0.0');
define('MY_CDN_URL', 'https://cdn.example.com');

// PHP $_GLOBALS에 배열로 주입 (테마/플러그인에서 접근 가능)
$GLOBALS['my_app'] = array(
    'version'    => MY_APP_VERSION,
    'cdn'        => MY_CDN_URL,
    'start_time' => microtime(true),
);

// 세션 기반 사용자 커스텀 데이터 초기화
if (!isset($_SESSION['my_prefs'])) {
    $_SESSION['my_prefs'] = array(
        'lang'  => 'ko',
        'theme' => 'light',
    );
}


3.4 방문자 통계 로깅 (middle/)

라우트 확정 후 방문자 정보를 기록합니다. DXCMS 내장 extend/middle/01_visit_tracker.php는 이미 고성능으로 구현되어 있으며, 아래는 간단한 커스텀 접근 로그 예제입니다.
파일: extend/middle/02_custom_access_log.php
 
<?php
if (!defined('DX_CMS')) exit;

// 라우트 정보 가져오기 (middle에서만 사용 가능)
$route = isset($GLOBALS['dx_route']) ? $GLOBALS['dx_route'] : array();
$type  = isset($route['type']) ? $route['type'] : 'unknown';

// admin, api 요청은 로그에서 제외
if (in_array($type, array('admin', 'api'), true)) return;

// 봇 제외
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
if (preg_match('/bot|crawl|spider/i', $ua)) return;

// 로그 기록 (응답 후 처리로 성능 영향 최소화)
register_shutdown_function(function() use ($route, $ua) {
    $log = date('Y-m-d H:i:s')
         . ' | ' . dx_ip()
         . ' | ' . json_encode($route)
         . ' | ' . substr($ua, 0, 100);
    dx_log($log, 'info');
});


3.5 HTML 버퍼 조작으로 JS 주입 (top/ + bottom/ 조합)

다크모드 FOUC 방지와 같이 최종 HTML에 스크립트를 삽입해야 하는 경우, ob_start를 top/에서 등록하고 bottom/에서 JS를 출력합니다.
파일: extend/top/01_darkmode_early.php (내장 예제 요약)
 
<?php
if (!defined('DX_CMS')) exit;

// ob_start 콜백으로 최종 HTML 버퍼를 가로챔
ob_start(function($buffer) {
    // <head> 바로 뒤에 인라인 스크립트 삽입
    $script = '<script>
        (function(){
            var t = localStorage.getItem("theme");
            if (t === "dark") document.body.classList.add("dark");
        })();
    </script>';
    $buffer = preg_replace('/<head([^>]*)>/i', '<head$1>' . $script, $buffer, 1);
    return $buffer;
});


4. Hook 시스템 (HookManager)

Hook 시스템은 "특정 시점에 콜백 함수를 등록하고 실행"하는 방식입니다. extend/ 폴더보다 더 세밀하게 실행 시점과 우선순위를 제어할 수 있습니다.


4.1 Hook의 종류

종류 함수 역할 반환값
Action Hook dx_add_hook() / dx_run_hook() 특정 시점에 코드 실행 없음 (side effect)
Filter Hook dx_add_filter() / dx_apply_filter() 값을 가공하여 반환 가공된 값


4.2 전역 헬퍼 함수

함수 매개변수 설명
dx_add_hook($name, $callback, $priority) name: 훅 이름 / callback: 실행함수 / priority: 우선순위(기본 10) Action 훅 등록. 낮은 priority가 먼저 실행
dx_run_hook($name, $args) name: 훅 이름 / args: 전달 인자 배열 등록된 Action 훅 실행
dx_add_filter($name, $callback, $priority) 위와 동일 Filter 훅 등록 (내부적으로 add와 동일)
dx_apply_filter($name, $value, $args) name: 훅 이름 / value: 원본값 / args: 추가 인자 등록된 Filter 실행 후 최종값 반환
dx_remove_hook($name, $callback) name: 훅 이름 / callback: 제거할 콜백(null=전체) 등록된 훅 제거
dx_has_hook($name) name: 훅 이름 해당 훅이 등록되어 있는지 확인


4.3 내장 훅 포인트

DXCMS는 페이지 렌더링 시 자동으로 다음 훅을 발화합니다.


4.3.1 전역 훅 (모든 페이지)

훅 이름 발화 시점 주요 용도
dx_top 모든 페이지 상단 공통 헤더 요소 삽입, 스크립트 주입
dx_middle 모든 페이지 중간 콘텐츠 영역 내 삽입
dx_bottom 모든 페이지 하단 </body> 직전 JS 로드, 분석 코드, 채팅 위젯 등

 

4.3.2 타입별 훅 (자동 생성)

현재 라우트 타입이 board라면 dx_board_top, dx_board_middle, dx_board_bottom 훅이 자동으로 발화됩니다.
 
// 라우트 타입: 'board', 'page', 'admin', 'api' 등
dx_add_hook('dx_board_top', function($context) {
    // 게시판 페이지 상단에만 실행되는 코드
    echo '<div class="board-notice">게시판 공지</div>';
});

dx_add_hook('dx_page_top', function($context) {
    // 일반 페이지(type=page) 상단에만 실행
});


4.3.3 슬러그별 훅 (자동 생성)

라우트 slug가 notice라면 dx_page_notice_top 등 슬러그 전용 훅이 발화됩니다.
 
// slug='notice'인 페이지 상단에만 실행
dx_add_hook('dx_page_notice_top', function($context) {
    echo '<p>공지사항 전용 배너</p>';
});


4.3.4 extend/ 슬롯 훅

훅 이름 발화 위치
dx_extend_top extend/top/ 파일 실행 완료 직후
dx_extend_middle extend/middle/ 파일 실행 완료 직후
dx_extend_bottom extend/bottom/ 파일 실행 완료 직후


4.4 우선순위(Priority) 제어

여러 훅 콜백이 동일한 훅에 등록된 경우, priority 값이 낮을수록 먼저 실행됩니다.
 
// 1번 먼저 실행 (priority 1)
dx_add_hook('dx_bottom', function() {
    echo '<!-- 첫 번째 -->';
}, 1);

// 2번 나중에 실행 (priority 10, 기본값)
dx_add_hook('dx_bottom', function() {
    echo '<!-- 두 번째 -->';
}, 10);

// 3번 맨 마지막 실행 (priority 999)
dx_add_hook('dx_bottom', function() {
    echo '<!-- 맨 마지막 -->';
}, 999);


4.5 Filter Hook 실용 예제

Filter 훅은 값을 받아서 변환한 후 반환합니다. Action 훅과 달리 반환값이 중요합니다.
 
// 게시글 내용 필터: 특정 단어 마스킹
dx_add_filter('dx_post_content', function($content, $args) {
    // 금칙어 필터링
    $content = str_replace('금칙어1', '***', $content);
    $content = str_replace('금칙어2', '***', $content);
    return $content;  // 반드시 반환!
}, 10);

// 코어에서 필터 적용 (코어가 이미 이렇게 구현되어 있음)
$content = dx_apply_filter('dx_post_content', $rawContent, array('post_id' => $id));


5. Plugin 시스템 (PluginRegistry)

플러그인 시스템은 에디터, 결제 모듈, CAPTCHA, SMS 등 "교체 가능한 기능 모듈"을 위한 고급 확장 방법입니다. PluginRegistry에 등록된 플러그인은 관리자 페이지에서 선택하고 활성화할 수 있습니다.


5.1 지원 플러그인 타입

타입 키워드 설명 설정 키
에디터 editor 게시글 작성 에디터 (TinyMCE, CKEditor 등) active_editor
결제 payment PG사 결제 모듈 (KG이니시스, 토스 등) active_payment
CAPTCHA captcha 스팸 방지 (reCAPTCHA, hCaptcha 등) active_captcha
SMS sms 문자 발송 (NCP, 알리고 등) active_sms
소셜 로그인 social_login 카카오, 네이버, 구글 로그인 active_social_login
소켓 socket WebSocket 실시간 기능 active_socket
커스텀 자유 지정 개발자가 임의 타입 추가 가능 active_{타입}


5.2 플러그인 폴더 구조

plugins/
├── my-plugin/              ← 플러그인 폴더명 = 플러그인 ID
│   ├── plugin.php          ← 플러그인 메인 파일 (훅 등록 등)
│   ├── manifest.php        ← 플러그인 메타 정보
│   └── admin/              ← 관리자 설정 페이지 (선택)
│       └── settings.php
│
├── example-plugin/
│   ├── plugin.php
│   └── manifest.php
│
└── dx-payment-helper.php   ← 공용 결제 헬퍼 (내장)


5.3 플러그인 개발 3단계


Step 1: manifest.php 작성

manifest.php는 플러그인의 메타 정보를 담습니다. 마켓 등록 시 이 파일이 사용됩니다.
 
<?php
// plugins/my-editor/manifest.php
return array(
    'name'        => 'My Custom Editor',
    'version'     => '1.0.0',
    'description' => '커스텀 마크다운 에디터입니다.',
    'author'      => '홍길동',
    'author_url'  => 'https://example.com',
    'type'        => 'editor',          // 플러그인 타입
    'min_version' => '8.0.0',           // 최소 CMS 버전
    'tags'        => '에디터,마크다운',
    'license'     => 'MIT',
);


Step 2: plugin.php 작성

plugin.php는 플러그인의 핵심 파일입니다. 플러그인을 PluginRegistry에 등록하고, 훅을 통해 동작을 구현합니다.
 
<?php
if (!defined('DX_CMS')) exit('Direct access not allowed.');

// ── 1. 플러그인 등록 (PluginRegistry에 등록) ─────
dx_register_plugin(array(
    'id'          => 'my-editor',
    'type'        => 'editor',
    'name'        => 'My Custom Editor',
    'version'     => '1.0.0',
    'description' => '커스텀 마크다운 에디터',
    'author'      => '홍길동',
    'priority'    => 20,               // 낮을수록 목록 상단
    'settings'    => array(           // 플러그인 전용 설정 필드
        'theme'     => array('label' => '테마', 'type' => 'select',
                             'options' => array('light'=>'라이트', 'dark'=>'다크')),
        'height'    => array('label' => '에디터 높이(px)', 'type' => 'number'),
    ),
));

// ── 2. 에디터 렌더링 훅 구현 ──────────────────────
dx_add_hook('dx_editor_render', function($args) {
    // 이 플러그인이 활성화된 경우에만 실행
    if ($args['editor'] !== 'my-editor') return;

    $name  = htmlspecialchars($args['name'], ENT_QUOTES);
    $value = htmlspecialchars($args['value'], ENT_QUOTES, 'UTF-8');

    echo '<div class="my-editor-wrap">';
    echo '<textarea id="my-editor-' . $name . '" name="' . $name . '">'
       . $value . '</textarea>';
    echo '<script src="/plugins/my-editor/editor.js"></script>';
    echo '</div>';
}, 10);


Step 3: 활성 플러그인 확인 및 호출

테마나 다른 코드에서 현재 활성화된 플러그인을 확인하고 사용합니다.
 
// 현재 활성 에디터 ID 확인
$editorId = dx_active_plugin('editor');  // 예: 'my-editor'

// 에디터 렌더링 (모든 에디터 공통 인터페이스)
dx_render_editor('content', $existingContent, array(
    'height' => 400,
    'board'  => $board,   // 게시판별 에디터 오버라이드 가능
));

// 결제 요청
dx_request_payment(array(
    'order_id'     => 'ORD-2026-001',
    'amount'       => 29000,
    'product_name' => 'DXCMS Pro',
    'buyer_name'   => '홍길동',
    'buyer_email'  => 'hong@example.com',
    'return_url'   => 'https://example.com/payment/result',
));


6. Hook 활용 실전 패턴


6.1 디버그 패널 삽입 (dx_bottom)

DX_DEBUG 모드에서 실행 시간과 쿼리 수를 페이지 우하단에 표시합니다.
 
// extend/top/01_debug_panel.php 또는 plugin.php 내에서
dx_add_hook('dx_bottom', function($context) {
    if (!defined('DX_DEBUG') || !DX_DEBUG) return;

    $time    = round((microtime(true) - DX_START_TIME) * 1000, 2);
    $queries = Database::getInstance()->getQueryCount();
    $route   = isset($GLOBALS['dx_route']) ? json_encode($GLOBALS['dx_route']) : '-';

    echo '<div style="position:fixed;bottom:10px;right:10px;
               background:rgba(0,0,0,.85);color:#fff;
               padding:8px 14px;border-radius:8px;font-size:11px;
               font-family:monospace;z-index:9999;line-height:1.8">';
    echo '⚡ ' . $time . 'ms &nbsp;|&nbsp; 🗄 ' . $queries . ' queries';
    echo '<br>📍 ' . htmlspecialchars($route, ENT_QUOTES);
    echo '</div>';
}, 999);


6.2 특정 페이지에만 CSS/JS 삽입

특정 슬러그의 페이지에만 에셋을 추가합니다.
 
// 'gallery' 슬러그 페이지 상단에만 CSS 삽입
dx_add_hook('dx_page_gallery_top', function($context) {
    echo '<link rel="stylesheet" href="/assets/css/gallery-custom.css">';
});

// 'gallery' 슬러그 페이지 하단에만 JS 삽입
dx_add_hook('dx_page_gallery_bottom', function($context) {
    echo '<script src="/assets/js/gallery-lightbox.js"></script>';
});


6.3 관리자 전용 사이드바 메뉴 추가

dx_add_hook('dx_admin_sidebar', function($context) {
    echo '<li class="nav-item">';
    echo '  <a href="/admin/my-module" class="nav-link">';
    echo '    <i class="icon">📊</i> 내 모듈';
    echo '  </a>';
    echo '</li>';
});


6.4 훅 우선순위를 이용한 실행 순서 보장

// ① 가장 먼저: 보안 체크 (priority 1)
dx_add_hook('dx_top', 'my_security_check', 1);

// ② 다음: 사용자 권한 확인 (priority 5)
dx_add_hook('dx_top', 'my_permission_check', 5);

// ③ 기본: 일반 처리 (priority 10, 기본값)
dx_add_hook('dx_top', 'my_normal_process');

// ④ 맨 마지막: 로깅 (priority 999)
dx_add_hook('dx_top', 'my_access_log', 999);

function my_security_check($ctx) { /* ... */ }
function my_permission_check($ctx) { /* ... */ }
function my_normal_process($ctx) { /* ... */ }
function my_access_log($ctx) { /* ... */ }


7. extend/ vs Hook vs Plugin 선택 가이드

세 가지 확장 방법 중 어떤 것을 선택해야 하는지 기준을 정리합니다.
 
비교 항목 extend/ 폴더 Hook 시스템 Plugin 시스템
Plugin 시스템 ⭐ (쉬움) ⭐⭐ (중간) ⭐⭐⭐ (어려움)
PHP 코드 필요 최소 (파일 생성만) 필요 (콜백 함수) 필요 (등록+구현)
실행 시점 제어 슬롯 3개로 제한 훅 이름으로 정밀 제어 훅 기반으로 구현
우선순위 제어 파일명으로만 제어 priority 매개변수로 제어 priority 매개변수 지원
에러 격리 자동 (파일 단위) 수동 try-catch 필요 수동 try-catch 필요
관리자 UI 없음 없음 관리자 설정 화면 자동 생성
교체 가능성 없음 없음 동일 타입 중 선택/전환 가능
권장 사용 사례 점검모드, IP차단, 로그 콘텐츠 삽입, 값 필터링 에디터, 결제, SMS 교체


7.1 의사결정 트리

커스터마이징이 필요한가?
│
├─ 특정 시점에 코드만 실행하면 되는가?
│   ├─ YES, 복잡한 제어 불필요 → extend/ 폴더 사용
│   └─ YES, 실행 시점/순서를 정밀 제어해야 함 → Hook 사용
│
├─ 에디터/결제/SMS 같은 기능을 교체/추가하려는가?
│   └─ YES → Plugin 시스템 사용
│
└─ 코어 파일을 수정하고 싶은 유혹이 드는가?
    └─ 절대 NO! → 위 세 가지 중 하나로 반드시 해결 가능


7.2 혼합 사용 예제

실제 운영 환경에서는 세 가지를 혼합하여 사용합니다.
 
// plugins/my-plugin/plugin.php

// ① Plugin Registry에 등록 (Plugin 시스템)
dx_register_plugin(array(
    'id' => 'my-plugin', 'type' => 'editor', 'name' => 'My Plugin'
));

// ② Hook으로 전역 동작 추가 (Hook 시스템)
dx_add_hook('dx_bottom', function() {
    echo '<script src="/plugins/my-plugin/main.js"></script>';
}, 10);

// ③ extend/top/에 전역 변수 주입 (extend/ 폴더)
// → extend/top/01_my_plugin_vars.php 파일로 분리


8. 보안 및 주의사항


8.1 DxExtend의 보안 메커니즘

DxExtend 엔진은 다음과 같은 보안 장치를 내장하고 있습니다.

🔒 DxExtend 내장 보안 장치
1. realpath() 검증: 심볼릭 링크 등 경로 조작을 통해 extend/ 외부 파일을 실행하는 것을 차단합니다.
2. 에러 격리: set_error_handler()로 각 파일의 에러를 가로채어 로그에만 기록하고 다음 파일을 계속 실행합니다.
3. 직접 접근 차단: DX_CMS 상수 미정의 시 즉시 종료됩니다.
4. EXTR_SKIP: extract($context, EXTR_SKIP)으로 기존 변수를 덮어쓰지 않습니다.


8.2 개발 시 주의사항

⚠️ 주의 1: extend/ 파일에서 exit/die를 무분별하게 호출하면 안 됩니다.
   점검 모드처럼 의도적인 경우는 OK이지만, 일반 로직에서 exit하면 이후 파일이 실행되지 않습니다.

⚠️ 주의 2: Filter 훅에서 반드시 값을 return 해야 합니다.
   dx_apply_filter()를 통해 값이 전달되는데, 콜백에서 return을 빠뜨리면 null이 반환되어
   데이터가 사라집니다.

⚠️ 주의 3: bottom/ 슬롯에서 헤더를 변경할 수 없습니다.
   bottom은 렌더링 완료 후 실행되므로 header() 함수 호출이 무시됩니다.
   헤더 변경이 필요하면 top/ 슬롯을 사용하세요.

⚠️ 주의 4: DB 집중 작업은 register_shutdown_function()으로 처리하세요.
   방문자 로그, 통계 집계 등은 응답 후 처리해야 사용자 체감 속도가 향상됩니다.
   내장 visit_tracker는 이 패턴을 사용합니다.


8.3 파일 권한 설정

대상 권한 설명
extend/ 폴더 755 읽기/실행 가능, 웹서버 쓰기 불필요
extend/*.php 644 웹서버 실행 가능, 타인 쓰기 불가
plugins/ 폴더 755 읽기/실행 가능
plugins/**/plugin.php 644 안전한 권한


9. 빠른 참조 (Quick Reference)


9.1 가장 자주 쓰는 패턴

// ① 모든 페이지 하단에 스크립트 삽입
dx_add_hook('dx_bottom', function() {
    echo '<script src="/my-script.js"></script>';
}, 10);

// ② 점검 모드 (extend/top/01_maint.php)
if (!dx_is_admin()) { http_response_code(503); exit('점검중'); }

// ③ 현재 라우트 타입 확인 (middle/)
$type = isset($GLOBALS['dx_route']['type']) ? $GLOBALS['dx_route']['type'] : '';

// ④ 에디터 렌더링
dx_render_editor('content', $value, array('height' => 400));

// ⑤ 현재 활성 결제 플러그인 ID
$paymentId = dx_active_plugin('payment');

// ⑥ 훅 등록 확인 후 실행
if (dx_has_hook('my_custom_hook')) {
    dx_run_hook('my_custom_hook', array('key' => 'value'));
}

// ⑦ 훅 제거
dx_remove_hook('dx_bottom', 'my_callback_function');


9.2 내장 전역 함수 참조

함수 설명
dx_is_admin() 현재 사용자가 관리자인지 확인
dx_is_logged_in() 로그인 여부 확인
dx_ip() 클라이언트 IP 반환 (Cloudflare 우선)
dx_config($key, $default) CMS 설정값 조회
dx_log($message, $level) 로그 기록 (info/warning/error)
dx_base_url($path) 기본 URL + 경로 조합
dx_request_uri() 현재 요청 URI 반환
dx_error($message, $code) 에러 페이지 출력 후 종료


9.3 체크리스트: extend 파일 작성 시

1. if (!defined('DX_CMS')) exit; 를 파일 첫 줄에 작성
2. 파일명은 숫자 접두사로 실행 순서 지정 (예: 01_my_feature.php)
3. 에러가 발생해도 안전하게 처리 (try-catch 또는 @ 연산자)
4. 불필요한 전역 변수는 unset()으로 정리
5. DB 작업은 register_shutdown_function()으로 응답 후 처리
6. 비활성화 시 .disabled 확장자 추가 (삭제보다 안전)
7. 테스트: DX_DEBUG 모드에서 에러 로그 확인

댓글0

로그인 후 댓글을 작성할 수 있습니다.
번호 제목 작성자 날짜 조회
74
5. 관리자 기능 사용법 통계
Administrator
04.21 57
5. 관리자 기능 사용법
통계
Administrator 04.21 조회 57
73
5. 관리자 기능 사용법 회원 랭킹
Administrator
04.21 57
5. 관리자 기능 사용법
회원 랭킹
Administrator 04.21 조회 57
72
5. 관리자 기능 사용법 포인트샵
Administrator
04.21 58
5. 관리자 기능 사용법
포인트샵
Administrator 04.21 조회 58
71
5. 관리자 기능 사용법 레벨 관리
Administrator
04.21 59
5. 관리자 기능 사용법
레벨 관리
Administrator 04.21 조회 59
70
5. 관리자 기능 사용법 포인트 관리
Administrator
04.21 59
5. 관리자 기능 사용법
포인트 관리
Administrator 04.21 조회 59
69
5. 관리자 기능 사용법 문자 서비스
Administrator
04.21 56
5. 관리자 기능 사용법
문자 서비스
Administrator 04.21 조회 56
68
5. 관리자 기능 사용법 메일 보내기
Administrator
04.21 61
5. 관리자 기능 사용법
메일 보내기
Administrator 04.21 조회 61
67
5. 관리자 기능 사용법 회원 관리
Administrator
04.21 58
5. 관리자 기능 사용법
회원 관리
Administrator 04.21 조회 58
66
5. 관리자 기능 사용법 메뉴 관리
Administrator
04.21 59
5. 관리자 기능 사용법
메뉴 관리
Administrator 04.21 조회 59
65
5. 관리자 기능 사용법 인기글
Administrator
04.21 60
5. 관리자 기능 사용법
인기글
Administrator 04.21 조회 60
64
5. 관리자 기능 사용법 카테고리
Administrator
04.21 57
5. 관리자 기능 사용법
카테고리
Administrator 04.21 조회 57
63
5. 관리자 기능 사용법 게시판 그룹
Administrator
04.21 60
5. 관리자 기능 사용법
게시판 그룹
Administrator 04.21 조회 60
62
5. 관리자 기능 사용법 게시판 관리
Administrator
04.21 52
5. 관리자 기능 사용법
게시판 관리
Administrator 04.21 조회 52
61
5. 관리자 기능 사용법 전체 공지
Administrator
04.21 52
5. 관리자 기능 사용법
전체 공지
Administrator 04.21 조회 52
60
5. 관리자 기능 사용법 팝업 관리
Administrator
04.21 53
5. 관리자 기능 사용법
팝업 관리
Administrator 04.21 조회 53
59
5. 관리자 기능 사용법 페이지 관리
Administrator
04.21 52
5. 관리자 기능 사용법
페이지 관리
Administrator 04.21 조회 52
58
5. 관리자 기능 사용법 대시보드
Administrator
04.21 53
5. 관리자 기능 사용법
대시보드
Administrator 04.21 조회 53
57
4.5 UI / 출력 구조 템플릿 구조
Administrator
04.21 50
4.5 UI / 출력 구조
템플릿 구조
Administrator 04.21 조회 50
56
4.5 UI / 출력 구조 View 처리 방식
Administrator
04.21 53
4.5 UI / 출력 구조
View 처리 방식
Administrator 04.21 조회 53
55
4.4 게시판 시스템 구조 파일 업로드 구조
Administrator
04.21 48
4.4 게시판 시스템 구조
파일 업로드 구조
Administrator 04.21 조회 48
27
전체 회원
248
전체 게시글
126
전체 댓글
402
오늘 방문
25,749
전체 방문
7
현재 접속
인기글 7일 이내
최신글
최신댓글
목록