1장. 플러그인 시스템 개요
DXCMS의 플러그인 시스템은 두 개의 핵심 컴포넌트로 구성됩니다. 첫째, HookManager — 액션(Action)과 필터(Filter) 기반의 이벤트 훅 시스템으로, 플러그인이 CMS의 특정 시점에 코드를 삽입합니다. 둘째, PluginRegistry — 에디터•결제•소켓 등 "타입"별 플러그인 등록소로, 관리자가 활성 플러그인을 선택하면 해당 기능이 자동으로 교체됩니다.
1.1 플러그인 시스템 핵심 설계 원칙
- 파일 기반 자동 로딩 — plugins/ 폴더에 폴더를 만들고 plugin.php를 넣으면 자동으로 로드됨 (DB 활성화 불필요)
- 훅 기반 삽입 — 플러그인은 CMS 코드를 직접 수정하지 않고 훅 포인트에 코드를 등록함
- 타입 교체 구조 — 에디터•결제 등 타입은 관리자가 선택한 플러그인으로 언제든 교체 가능
- 우선순위 제어 — 동일 훅에 여러 플러그인이 등록되면 priority 값으로 실행 순서 제어
- PHP 5.6+ 완전 호환 — 클로저, 함수명 문자열, 배열 콜백 모두 지원
- 안전한 오류 격리 — DX_DEBUG=false 시 플러그인 오류가 다른 플러그인에 영향 안 줌
1.2 플러그인 로딩 흐름
index.php의 STEP 3에서 load_plugins() 함수가 호출됩니다.
// index.php — STEP 3 (라우팅 전, 세션 시작 후)
load_plugins();
// core/functions.php — load_plugins() 구현
function load_plugins() {
$dirs = glob(DX_PLUGINS . "/*", GLOB_ONLYDIR);
foreach ($dirs as $dir) {
$f = $dir . "/plugin.php";
if (file_exists($f)) {
require_once $f; // 모든 폴더의 plugin.php 무조건 로드
}
}
}
⚠️ 중요: 모든 plugin.php는 항상 로드됩니다
plugins/ 폴더에 있는 모든 폴더의 plugin.php는 관리자 활성화 여부와 무관하게 항상 실행됩니다.
다만, 훅 등록•타입 등록 자체는 부하가 미미하므로 실용상 문제 없습니다.
무거운 초기화 작업은 훅 콜백 내부에서 처리해야 합니다 (조건부 실행).
실제 기능 실행은 해당 훅이 발동될 때만 동작합니다.
2장. 플러그인 파일 구조
2.1 전체 디렉토리 구조
plugins/
my-plugin/ ← 플러그인 폴더 (폴더명 = 플러그인 식별자)
│ plugin.php ← 메인 실행 파일 ★필수
│ manifest.php ← 메타 정보 (마켓 공유용) ★필수
│
│ admin/ ← 관리자 전용 파일 (선택)
│ settings.php ← 플러그인 설정 페이지
│
│ assets/ ← CSS/JS/이미지 (선택)
│ plugin.css
│ plugin.js
│
│ views/ ← 렌더링 뷰 파일 (선택)
│ widget.php
│
└─ README.md ← 사용 설명서 (선택, 권장)
2.2 파일별 역할
| 파일 | 필수 여부 | 역할 |
| plugin.php | ★ 필수 | 플러그인 진입점. dx_register_plugin() 호출 + 훅 등록. index.php가 자동 로드 |
| manifest.php | ★ 필수 | 메타 정보 배열 return. 관리자 목록 표시 + 마켓 공유에 사용 |
| admin/settings.php | 선택 | 관리자 → 플러그인 설정 페이지 커스텀 UI |
| assets/ | 선택 | CSS/JS/이미지. dx_base_url("plugins/{폴더}/assets/") 로 URL 획득 |
| README.md | 권장 | 사용자 설명서. 관리자 화면에서 표시될 수 있음 |
2.3 manifest.php 전체 형식
<?php
// plugins/my-plugin/manifest.php
return array(
"name" => "My Plugin", // 관리자 목록 표시 이름
"version" => "1.0.0", // 버전
"description" => "플러그인 기능 설명", // 관리자 목록 설명
"author" => "개발자명",
"author_url" => "https://mysite.com",
"type" => "editor", // editor|payment|socket|captcha|utility 등
"min_version" => "8.0.0", // 최소 DXCMS 버전
"tags" => "에디터,WYSIWYG", // 쉼표 구분 태그
"category" => "utility", // 카테고리
"homepage" => "https://...",
"license" => "MIT",
);
2.4 plugin.php 기본 구조
<?php
// plugins/my-plugin/plugin.php
// ⚠️ DX_CMS 상수가 없으면 직접 접근 차단 (보안 필수)
if (!defined("DX_CMS")) exit("Direct access not allowed.");
// 1. PluginRegistry에 타입 등록 (선택)
dx_register_plugin(array(
"id" => "my-plugin", // 고유 ID (폴더명과 일치 권장)
"type" => "editor", // 타입
"name" => "My Plugin", // 관리자 표시 이름
"version" => "1.0.0",
"description" => "설명",
"author" => "개발자명",
"priority" => 10, // select 박스 정렬 순서
"settings" => array( // 플러그인 전용 설정 필드
"api_key" => array(
"label" => "API 키",
"type" => "text",
"default" => "",
),
),
));
// 2. 훅 등록 — 실제 기능 구현
dx_add_hook("dx_head", function($ctx) {
echo '<link rel="stylesheet" href="' . dx_base_url("plugins/my-plugin/assets/plugin.css") . '">';
}, 10);
dx_add_hook("dx_body_bottom", function() {
echo '<script src="' . dx_base_url("plugins/my-plugin/assets/plugin.js") . '"></script>';
});
3장. PluginRegistry — 타입 등록 시스템
PluginRegistry는 "에디터를 교체하고 싶다", "결제 모듈을 바꾸고 싶다" 처럼 관리자가 특정 기능의 구현체를 선택할 수 있게 해주는 시스템입니다. 플러그인이 자신을 특정 타입으로 등록하면, 관리자 설정 화면의 드롭다운에 자동으로 나타납니다.
3.1 기본 제공 타입
| 타입 ID | 설정 키 (DB) | 설명 및 용도 |
| editor | active_editor | 게시글·댓글 작성 WYSIWYG 에디터. 예: CKEditor4, TinyMCE |
| payment | active_payment | 결제 PG사 모듈. 예: 토스페이먼츠, KG이니시스, 카카오페이 |
| captcha | active_captcha | 스팸 방지 CAPTCHA. 예: reCAPTCHA v3, hCaptcha |
| sms | active_sms | SMS 발송. 예: 알리고, 네이버 클라우드 SMS |
| social_login | active_social_login | 소셜 로그인. 카카오·네이버·구글·GitHub |
| socket | active_socket | WebSocket 실시간 기능. 접속자 추적·채팅·알림 |
| (커스텀) | active_{타입명} | 개발자가 임의 타입 추가 가능. 관리자 설정 자동 표시 |
3.2 dx_register_plugin() 파라미터 상세
| 파라미터 | 필수 | 설명 |
| id | ★ 필수 | 플러그인 고유 ID. 영문·숫자·하이픈만. 폴더명과 일치 권장 |
| type | ★ 필수 | 등록할 타입. editor|payment|captcha|sms|socket 또는 커스텀 |
| name | ★ 필수 | 관리자 select 박스에 표시되는 이름 |
| version | 선택 | 버전 표시 (기본 "1.0.0") |
| description | 선택 | 플러그인 기능 설명 |
| author | 선택 | 제작자 이름 |
| priority | 선택 | select 박스 정렬 순서. 낮을수록 위에 표시 (기본 10) |
| settings | 선택 | 관리자 설정 폼에 자동 생성될 입력 필드 정의 배열 |
| usage_guide | 선택 | 관리자 화면에 표시될 사용 방법 안내 배열 |
3.3 settings 필드 타입 완전 가이드
settings 배열에 정의한 필드는 관리자 → 플러그인 → 해당 플러그인 설정 UI에 자동으로 표시됩니다.| type 값 | 관리자 UI | 설명 및 저장 키 |
| "text" | 텍스트 입력란 | plugin_{id}_{key} 형식으로 settings 테이블에 저장 |
| "password" | 비밀번호 입력란 | 값이 마스킹됨. API 키, 시크릿 키 입력용 |
| "select" | 드롭다운 | "options" 배열로 선택지 정의. 예: {"1":"사용","0":"사용 안 함"} |
| "textarea" | 여러 줄 텍스트 | 긴 설정값, JSON 형식 데이터 입력용 |
| "checkbox_group" | 체크박스 그룹 | "options" 배열. 선택된 값이 쉼표 구분 문자열로 저장 |
settings 정의 예시
"settings" => array(
"api_key" => array(
"label" => "API 키",
"type" => "text",
"default" => "",
),
"secret_key" => array(
"label" => "시크릿 키",
"type" => "password",
"default" => "",
),
"use_sandbox" => array(
"label" => "테스트 모드",
"type" => "select",
"options" => array("1" => "테스트", "0" => "실제 결제"),
"default" => "1",
),
"support_methods" => array(
"label" => "지원 결제 수단",
"type" => "checkbox_group",
"options" => array("card" => "카드", "bank" => "계좌이체", "phone" => "휴대폰"),
"default" => "card,bank",
),
),
3.4 settings 값 읽기
저장된 settings 값은 dx_config()로 읽습니다. 키 형식은 plugin_{플러그인ID}_{설정키} 입니다.
// plugin.php 또는 훅 콜백 내에서
$apiKey = dx_config("plugin_my-plugin_api_key", "");
$secretKey = dx_config("plugin_my-plugin_secret_key", "");
$sandbox = dx_config("plugin_my-plugin_use_sandbox", "1") === "1";
// 체크박스 그룹은 쉼표 구분 문자열로 저장됨
$methods = explode(",", dx_config("plugin_my-plugin_support_methods", "card"));
// 결과: ["card", "bank"]
3.5 활성 플러그인 확인
// 현재 활성 에디터 플러그인 ID 반환
$editorId = dx_active_plugin("editor"); // 예: "ckeditor4-editor"
// 현재 활성 결제 플러그인 정보 반환
$paymentInfo = dx_active_plugin_info("payment");
// 결과: ["id"=>"tosspay","name"=>"토스페이먼츠","version"=>"1.0.0",...]
// 특정 타입에 플러그인이 등록되어 있는지 확인
$hasEditor = PluginRegistry::getInstance()->hasPlugins("editor"); // bool
4장. DB 스키마 — dx_plugins 테이블
설치된 플러그인 목록과 공유 상태를 관리하는 테이블입니다. plugin.php는 자동 로드되지만, 관리자 화면에 표시되려면 DB에 등록되어야 합니다.| 컬럼명 | 타입 | 설명 |
| id | INT UNSIGNED | PK (AUTO_INCREMENT) |
| directory | VARCHAR(100) | 플러그인 폴더명. UNIQUE. 예: ckeditor4-editor |
| name | VARCHAR(191) | 표시 이름 (manifest.php에서 로드) |
| version | VARCHAR(50) | 버전 |
| description | TEXT | 설명 |
| author | VARCHAR(191) | 제작자 이름 |
| author_url | VARCHAR(500) | 제작자 URL |
| status | TINYINT(1) | 1=활성, 0=비활성 (toggle_plugin 액션으로 변경) |
| is_shared | TINYINT(1) | 마켓 공유 여부. 1=공개, 0=비공개 |
| share_key | VARCHAR(64) | 공유 고유 키 (32바이트 랜덤 hex). UNIQUE |
| share_desc | TEXT | 공유 시 표시할 설명 (마켓 카드용) |
| share_tags | VARCHAR(500) | 공유 태그 (쉼표 구분) |
| download_count | INT | 공유 다운로드 횟수 |
| settings | TEXT | 플러그인 자체 설정 JSON (미사용 시 NULL) |
| sort_order | SMALLINT | 목록 정렬 순서 |
| installed_at | DATETIME | 최초 등록일시 |
| shared_at | DATETIME | 최초 공유 일시 |
5장. HookManager — 훅 시스템 완전 가이드
HookManager는 액션(Action)과 필터(Filter) 두 가지 훅 방식을 지원합니다. 액션은 특정 시점에 코드를 실행하는 것이고, 필터는 값을 받아 변형 후 반환하는 것입니다.
5.1 Action 훅 — 코드 삽입
// 훅 등록 — plugin.php에서
dx_add_hook(
"훅이름", // 훅 포인트 이름
function($args) { // 콜백 (PHP 5.6+: 클로저, 함수명, 배열 모두 가능)
// 실행할 코드
echo "훅에서 출력";
},
10 // priority (낮을수록 먼저 실행, 기본 10)
);
// 훅 실행 — CMS 또는 테마에서
dx_run_hook("훅이름", $contextData);
// 훅이 등록되어 있는지 확인
$exists = dx_has_hook("훅이름"); // bool
// 특정 콜백만 제거
dx_remove_hook("훅이름", $specificCallback);
// 훅 전체 제거
dx_remove_hook("훅이름");
5.2 Filter 훅 — 값 변형
// 필터 등록 — 값을 받아 변형 후 반환
dx_add_filter("post_content", function($content, $args) {
// 내용에 광고 삽입 예시
return $content . "<div class=\"ad\">광고</div>";
}, 10);
// 필터 실행 — 변형된 값 반환
$filteredContent = dx_apply_filter("post_content", $originalContent, $extraArgs);
5.3 표준 훅 포인트 — 레이아웃 훅
layout/main.php에서 자동으로 실행되는 레이아웃 관련 훅 포인트입니다. 모든 페이지에서 발동됩니다| 훅 이름 | 발동 위치 및 전달 인자 |
| dx_head | <head> 안쪽 — CSS, meta 태그, JSON-LD 삽입. $context 배열 전달 |
| dx_top | 레이아웃 body 최상단 (dx_hook_top() 내부). $context 전달 |
| dx_{type}_top | 페이지 타입별 top 훅. 예: dx_board_top, dx_home_top |
| dx_middle | 콘텐츠 영역 중간 (dx_hook_middle() 내부). $context 전달 |
| dx_{type}_middle | 페이지 타입별 middle 훅. 예: dx_board_middle |
| dx_bottom | 레이아웃 body 하단 (dx_hook_bottom() 내부). $context 전달 |
| dx_{type}_bottom | 페이지 타입별 bottom 훅 |
| dx_footer_scripts | </body> 직전 — JS 파일 삽입용 |
| dx_body_bottom | </body> 최하단 — 분석 코드, 채팅 위젯 등 |
5.4 표준 훅 포인트 — 게시판 훅
| 훅 이름 | 발동 시점 및 전달 인자 |
| dx_board_before | 핸들러 진입 직후. board, action, skin, id 전달 |
| dx_board_list_context | 목록 $ctx 확정 직전 — 참조(&)로 전달, 변경 가능 |
| 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 | 글 삭제 완료 후 |
| dx_after_comment | 댓글 등록 완료 후 — comment_id, post_id, board |
| dx_board_after | 핸들러 처리 완료 후 |
5.5 표준 훅 포인트 — 에디터•결제 훅
| 훅 이름 | 발동 시점 및 용도 |
| dx_editor_render | 에디터 HTML 출력 시점. name, value, options, editor 전달 |
| dx_editor_init | dx_render_editor() 호출 시 내부 브릿지 훅. 에디터 플러그인 실행 |
| dx_payment_request | dx_request_payment() 호출 시. 결제창 HTML/JS 출력 |
5.6 표준 훅 포인트 — 회원 훅
| 훅 이름 | 발동 시점 |
| dx_after_login | 로그인 성공 후. user 배열 전달 |
| dx_after_logout | 로그아웃 후. user 배열 전달 |
| dx_after_register | 회원가입 완료 후. user_id, data 전달 |
5.7 priority(우선순위) 활용
같은 훅에 여러 플러그인이 등록된 경우 priority 값(낮을수록 먼저 실행)으로 순서를 제어합니다.
// 플러그인 A: priority=5 → 먼저 실행
dx_add_hook("dx_head", function($ctx) {
echo '<link rel="stylesheet" href="plugin-a.css">';
}, 5);
// 플러그인 B: priority=10 (기본) → 나중에 실행
dx_add_hook("dx_head", function($ctx) {
echo '<link rel="stylesheet" href="plugin-b.css">';
}, 10);
// 결과: plugin-a.css → plugin-b.css 순서로 출력
// 특별 priority 관례:
// 1~5: 코어 기능 (예약)
// 10 : 기본값 (대부분의 플러그인)
// 100: 나중에 실행 (다른 플러그인 출력 후)
// 999: 가장 마지막 (디버그, 성능 측정 등)
6장. 기본 제공 플러그인 목록
6.1 에디터 플러그인
| 플러그인 | 타입 | 주요 기능 |
| ckeditor4-editor | editor | CKEditor 4 WYSIWYG. 자동번역·드래그테이블·테이블간격·셀배경색. 댓글 에디터 지원 |
6.2 결제 플러그인
| 플러그인 | 타입 | 주요 기능 |
| tosspay-payment | payment | 토스페이먼츠 SDK v1. 카드·계좌이체·가상계좌·휴대폰결제 |
| kakaopay-payment | payment | 카카오페이 단건결제·정기결제 |
| nicepay-payment | payment | 나이스페이먼츠 |
| kg-inicis-payment | payment | KG이니시스 |
| kcp-payment | payment | NHN KCP |
| payletter-payment | payment | 페이레터 |
| naverpay-payment | payment | 네이버페이 |
| danal-payment | payment | 다날 |
| paypal-payment | payment | PayPal (해외결제) |
| stripe-payment | payment | Stripe (해외결제) |
| custom-payment-template | payment | 커스텀 결제 모듈 개발 템플릿 |
6.3 실시간•유틸리티 플러그인
| 플러그인 | 타입 | 주요 기능 |
| dx-socket | socket | 실시간 접속자 추적·알림·채팅·쪽지·댓글 live. WebSocket + Node.js |
| example-plugin | example | 플러그인 개발 예제. 실행 시간·쿼리 수 표시 (DX_DEBUG 시만) |
7장. 관리자 사용방법
7.1 플러그인 목록 화면
관리자 → 플러그인 메뉴를 클릭하면 plugins/ 폴더를 자동 스캔하여 목록을 표시합니다.| UI 요소 | 기능 |
| 플러그인 카드 | 폴더명, 이름, 버전, 제작자, 설명 표시 (manifest.php에서 로드) |
| 활성화 / 비활성화 토글 | dx_plugins.status 컬럼 변경. 토글 후 사용 방법 안내 표시 |
| 모듈 설정 섹션 | PluginRegistry에 등록된 타입별 드롭다운. 활성 플러그인 선택 |
| 플러그인 설정 (⚙️) | 각 플러그인의 settings 필드가 입력 UI로 자동 생성 |
| 마켓 공유 버튼 | 플러그인을 DX마켓에 공유 (is_shared=1) |
7.2 에디터 플러그인 설정 방법
- 관리자 → 플러그인 메뉴 클릭
- "모듈 설정" 섹션에서 "에디터" 드롭다운 확인
- "CKEditor 4" 선택 후 저장
- settings.active_editor = "ckeditor4-editor" 로 저장됨
- 글쓰기 페이지에서 CKEditor 에디터 자동 표시 확인
💡 에디터 없음으로 설정하는 방법
에디터 드롭다운에서 "기본 텍스트에어리어 (에디터 없음)" 선택 → 저장
또는 게시판 설정에서 해당 게시판만 editor = "none" 으로 개별 지정 가능
7.3 플러그인별 설정 저장 방식
플러그인의 settings에 정의된 필드는 관리자 "플러그인 설정" 아코디언 패널에 자동 표시되며, 저장 시 settings 테이블에 아래 형식으로 저장됩니다.
// 저장 키 형식
plugin_{플러그인ID}_{설정키}
// 예시: ckeditor4-editor 플러그인의 height 설정
plugin_ckeditor4-editor_height = "400"
// 예시: tosspay-payment 플러그인의 api_key 설정
plugin_tosspay-payment_api_key = "test_..."
// 읽기: plugin.php 내에서
$height = dx_config("plugin_ckeditor4-editor_height", "400");
7.4 활성 플러그인 선택 저장 방식
모듈 설정 드롭다운에서 선택 후 저장하면 settings 테이블에 타입별 활성 키가 저장됩니다.
// settings 테이블 저장 형식
active_editor = "ckeditor4-editor"
active_payment = "tosspay-payment"
active_captcha = "" (선택 안 함)
active_socket = "dx-socket"
// 코드에서 확인
$editorId = dx_active_plugin("editor"); // "ckeditor4-editor"
$paymentId = dx_active_plugin("payment"); // "tosspay-payment"
8장. 실전 플러그인 제작 예제
8.1 공지사항 배너 플러그인
모든 페이지 상단에 공지 배너를 표시하는 간단한 플러그인 예시입니다.manifest.php
<?php
return array(
"name" => "공지 배너",
"version" => "1.0.0",
"description" => "모든 페이지 상단에 공지 배너 표시",
"author" => "내 이름",
"type" => "utility",
"category" => "utility",
);
plugin.php
<?php
if (!defined("DX_CMS")) exit("Direct access not allowed.");
// 타입 등록 (관리자 드롭다운에는 표시 안 됨 — utility 타입)
dx_register_plugin(array(
"id" => "notice-banner",
"type" => "utility",
"name" => "공지 배너",
"version" => "1.0.0",
"settings" => array(
"banner_text" => array(
"label" => "배너 문구",
"type" => "text",
"default" => "🔔 공지사항: 서버 점검 중입니다.",
),
"banner_color" => array(
"label" => "배경색",
"type" => "select",
"options" => array("blue"=>"파랑","red"=>"빨강","green"=>"초록"),
"default" => "blue",
),
"banner_enabled" => array(
"label" => "배너 표시",
"type" => "select",
"options" => array("1"=>"표시","0"=>"숨김"),
"default" => "1",
),
),
));
// 배너가 활성화된 경우에만 훅 등록
if (dx_config("plugin_notice-banner_banner_enabled", "1") === "1") {
$bannerText = dx_config("plugin_notice-banner_banner_text", "");
$bannerColor = dx_config("plugin_notice-banner_banner_color", "blue");
if ($bannerText) {
$colors = array(
"blue" => "background:#1a73e8;color:#fff",
"red" => "background:#dc2626;color:#fff",
"green" => "background:#16a34a;color:#fff",
);
$style = isset($colors[$bannerColor]) ? $colors[$bannerColor] : $colors["blue"];
dx_add_hook("dx_top", function($ctx) use ($bannerText, $style) {
echo '<div style="' . $style . ';padding:10px 20px;text-align:center;font-size:.875rem;font-weight:600">'
. htmlspecialchars($bannerText, ENT_QUOTES, "UTF-8")
. '</div>';
}, 1); // priority=1: 가장 먼저 출력
}
}
8.2 글 저장 시 외부 알림 플러그인
게시글 등록 시 슬랙 웹훅으로 알림을 보내는 플러그인 예시입니다.
<?php
if (!defined("DX_CMS")) exit;
dx_register_plugin(array(
"id" => "slack-notify",
"type" => "utility",
"name" => "Slack 알림",
"version" => "1.0.0",
"settings" => array(
"webhook_url" => array(
"label" => "Slack 웹훅 URL",
"type" => "text",
"default" => "",
),
"board_keys" => array(
"label" => "알림받을 게시판 키 (쉼표 구분, 비우면 전체)",
"type" => "text",
"default" => "",
),
),
));
// 글 저장 완료 훅 등록
dx_add_hook("dx_after_write", function($args) {
$webhookUrl = dx_config("plugin_slack-notify_webhook_url", "");
if (!$webhookUrl) return;
$boardKeys = dx_config("plugin_slack-notify_board_keys", "");
$board = isset($args["board"]) ? $args["board"] : array();
$boardKey = isset($board["board_key"]) ? $board["board_key"] : "";
// 특정 게시판만 알림 (설정된 경우)
if ($boardKeys) {
$keys = array_map("trim", explode(",", $boardKeys));
if (!in_array($boardKey, $keys)) return;
}
$postId = isset($args["post_id"]) ? $args["post_id"] : "";
$postUrl = dx_base_url($boardKey . "/view/" . $postId);
$data = isset($args["data"]) ? $args["data"] : array();
$title = isset($data["title"]) ? $data["title"] : "(제목 없음)";
// 슬랙 웹훅 전송
$payload = json_encode(array(
"text" => "📝 새 글이 등록되었습니다: *" . $title . "*\n" . $postUrl
));
$ch = curl_init($webhookUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_exec($ch);
curl_close($ch);
}, 10);
8.3 커스텀 에디터 플러그인 최소 구현
새 에디터 플러그인을 만들어 기존 에디터와 교체하는 예시입니다.
<?php
if (!defined("DX_CMS")) exit;
// 1. editor 타입으로 등록
dx_register_plugin(array(
"id" => "my-editor",
"type" => "editor", // ← editor 타입이어야 드롭다운에 표시
"name" => "My Editor",
"version" => "1.0.0",
"settings" => array(
"height" => array(
"label" => "에디터 높이 (px)",
"type" => "text",
"default" => "300",
),
),
));
// 2. dx_editor_render 훅으로 에디터 HTML 출력
// 이 훅은 dx_render_editor()가 내부적으로 호출함
dx_add_hook("dx_editor_render", function($args) {
// 이 플러그인이 활성화된 경우만 실행
if (dx_active_plugin("editor") !== "my-editor") return;
$name = isset($args["name"]) ? $args["name"] : "content";
$value = isset($args["value"]) ? $args["value"] : "";
$height = (int)dx_config("plugin_my-editor_height", "300");
// 에디터 초기화 CSS/JS 로드 (중복 방지)
static $loaded = false;
if (!$loaded) {
$loaded = true;
echo '<script src="https://cdn.example.com/my-editor.js"></script>';
}
// textarea + 에디터 초기화 스크립트
echo '<textarea id="editor_' . htmlspecialchars($name, ENT_QUOTES) . '"'
. ' name="' . htmlspecialchars($name, ENT_QUOTES) . '"'
. ' style="height:' . $height . 'px">' . htmlspecialchars($value, ENT_QUOTES, "UTF-8") . '</textarea>';
echo '<script>MyEditor.init("editor_' . htmlspecialchars($name, ENT_QUOTES) . '");</script>';
}, 10);
9장. 자주 묻는 질문 (FAQ)
Q1. plugin.php가 자동으로 로드된다면 비활성화는 의미가 없나요?
맞습니다. plugin.php는 항상 로드됩니다. 관리자의 "활성화/비활성화" 토글(dx_plugins.status)은 관리자 UI 표시 여부와 공유 기능에 영향을 줄 뿐입니다. 실제로 플러그인을 비활성화하려면 plugin.php에서 dx_config("plugin_{id}_enabled", "1") 값을 체크하는 코드를 직접 작성해야 합니다.
Q2. 동일한 훅에 두 플러그인이 등록되면 어떻게 되나요?
priority 순서대로 모두 실행됩니다. 에디터처럼 "하나만 실행해야 하는" 경우에는 훅 콜백 내부에서 dx_active_plugin("editor") === "my-plugin-id" 조건을 확인하여 현재 활성화된 플러그인만 동작하도록 구현해야 합니다.
Q3. 플러그인에서 DB를 사용하려면?
Database::getInstance()로 글로벌 DB 인스턴스를 얻어 사용합니다. 플러그인 전용 테이블이 필요하면 설치 시 CREATE TABLE을 실행하거나, settings 테이블을 활용하는 것이 가장 단순합니다.
$db = Database::getInstance();
$rows = $db->rows("SELECT * FROM `{$db->table("posts")}` WHERE board_id=? LIMIT 5", array(1));
// 플러그인 전용 설정 저장
$db->query(
"INSERT INTO `{$db->table("settings")}` (setting_key, setting_value, updated_at)
VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW()",
array("plugin_my-plugin_custom_data", json_encode($data))
);
Q4. 플러그인의 CSS/JS 파일 URL을 얻으려면?
// 플러그인 assets 폴더 URL
$cssUrl = dx_base_url("plugins/my-plugin/assets/style.css");
$jsUrl = dx_base_url("plugins/my-plugin/assets/script.js");
// 훅에서 사용
dx_add_hook("dx_head", function() {
echo '<link rel="stylesheet" href="' . dx_base_url("plugins/my-plugin/assets/style.css") . '?v=1.0.0">';
});
Q5. PluginRegistry에 등록하지 않은 플러그인도 동작하나요?
네. dx_register_plugin() 호출 없이 훅만 등록해도 완전히 동작합니다. PluginRegistry는 "에디터를 선택한다", "결제 모듈을 고른다"처럼 관리자가 여러 구현체 중 하나를 선택해야 하는 경우에만 필요합니다. 단순히 기능을 추가하는 플러그인은 훅만 등록하면 됩니다.
Q6. 플러그인이 여러 타입을 동시에 등록할 수 있나요?
네. 예를 들어 소셜 로그인 플러그인이 sms 타입과 social_login 타입을 모두 등록할 수 있습니다. dx_register_plugin()을 여러 번 호출하되, id를 각각 다르게 지정하면 됩니다.
Q7. 플러그인을 삭제하려면?
plugins/ 폴더에서 해당 플러그인 폴더를 삭제하면 됩니다. DB의 dx_plugins 테이블에는 레코드가 남지만 기능상 문제는 없습니다. 깔끔하게 정리하려면 관리자 화면에서 비활성화 후 폴더를 삭제하거나, DB에서 해당 레코드도 직접 삭제하세요.