애드온 구조

애드온 구조

라이믹스 애드온의 파일 구조와 핵심 구성 요소를 학습합니다.

기본 파일 구조

애드온 디렉토리 구성

addons/
└── my_addon/
    ├── my_addon.addon.php          # 메인 애드온 파일 (필수)
    ├── conf/
    │   └── info.xml               # 애드온 정보 파일 (필수)
    ├── schemas/
    │   └── install.xml            # 데이터베이스 스키마
    ├── queries/
    │   ├── insertData.xml         # XML 쿼리 파일들
    │   ├── updateData.xml
    │   └── selectData.xml
    ├── lang/
    │   ├── ko.lang.php           # 한국어 언어팩
    │   └── en.lang.php           # 영어 언어팩
    ├── tpl/
    │   ├── config.html           # 설정 템플릿
    │   └── admin.html            # 관리 템플릿
    ├── js/
    │   └── addon.js              # JavaScript 파일
    ├── css/
    │   └── addon.css             # CSS 파일
    └── README.md                 # 문서

필수 파일 구성

1. info.xml - 애드온 정보 파일

<?xml version="1.0" encoding="UTF-8"?>
<addon version="0.2">
    <!-- 기본 정보 -->
    <title xml:lang="ko">나의 애드온</title>
    <title xml:lang="en">My Addon</title>

    <description xml:lang="ko">애드온에 대한 상세 설명입니다.</description>
    <description xml:lang="en">Detailed description of the addon.</description>

    <!-- 버전 및 날짜 -->
    <version>1.0.0</version>
    <date>2024-01-01</date>

    <!-- 제작자 정보 -->
    <author email_address="developer@example.com" link="https://example.com">
        <name xml:lang="ko">개발자</name>
        <name xml:lang="en">Developer</name>
    </author>

    <!-- 의존성 정의 -->
    <dependency>
        <!-- 필수 모듈 -->
        <module name="member" />
        <module name="board" version="1.0.0" />

        <!-- 필수 애드온 -->
        <addon name="mobile" />

        <!-- 라이믹스 버전 요구사항 -->
        <rhymix version="2.0.0" />
    </dependency>

    <!-- 설정 변수 정의 -->
    <extra_vars>
        <!-- 텍스트 입력 -->
        <var name="site_name" type="text" default="My Site">
            <title xml:lang="ko">사이트명</title>
            <title xml:lang="en">Site Name</title>
            <description xml:lang="ko">사이트 이름을 입력하세요.</description>
        </var>

        <!-- 선택 박스 -->
        <var name="enable_cache" type="select" default="Y">
            <title xml:lang="ko">캐시 사용</title>
            <title xml:lang="en">Enable Cache</title>
            <options value="Y">Yes</options>
            <options value="N">No</options>
        </var>

        <!-- 체크박스 -->
        <var name="enabled_features" type="checkbox">
            <title xml:lang="ko">활성화할 기능</title>
            <title xml:lang="en">Features to Enable</title>
            <options value="feature1">기능 1</options>
            <options value="feature2">기능 2</options>
            <options value="feature3">기능 3</options>
        </var>

        <!-- 텍스트 영역 -->
        <var name="custom_css" type="textarea">
            <title xml:lang="ko">커스텀 CSS</title>
            <title xml:lang="en">Custom CSS</title>
            <description xml:lang="ko">추가 CSS 코드를 입력하세요.</description>
        </var>
    </extra_vars>
</addon>

2. 메인 애드온 파일 구조

<?php
/**
 * 애드온명: My Addon
 * 설명: 라이믹스용 커스텀 애드온
 * 버전: 1.0.0
 * 제작자: Developer
 */

// 보안 검사
if(!defined("__XE__")) exit();

// 애드온 클래스 정의
class MyAddon
{
    private $config;
    private $addon_path;

    public function __construct()
    {
        $this->addon_path = __XE_PATH__ . 'addons/my_addon/';
        $this->loadConfig();
    }

    /**
     * 설정 로드
     */
    private function loadConfig()
    {
        // 애드온 설정 가져오기
        $oAddonModel = getModel('addon');
        $this->config = $oAddonModel->getAddonConfig('my_addon');

        // 기본값 설정
        if(!$this->config) {
            $this->config = new stdClass();
        }

        $this->setDefaultConfig();
    }

    /**
     * 기본 설정값 설정
     */
    private function setDefaultConfig()
    {
        $defaults = array(
            'enabled' => 'Y',
            'cache_time' => 3600,
            'max_items' => 10
        );

        foreach($defaults as $key => $value) {
            if(!isset($this->config->{$key})) {
                $this->config->{$key} = $value;
            }
        }
    }

    /**
     * 애드온이 실행되어야 하는지 확인
     */
    public function shouldExecute()
    {
        // 비활성화된 경우
        if($this->config->enabled !== 'Y') {
            return false;
        }

        // 관리자 페이지에서는 실행하지 않음
        if(Context::get('module') === 'admin') {
            return false;
        }

        return true;
    }

    /**
     * 모듈 실행 전 처리
     */
    public function beforeModuleProc()
    {
        if(!$this->shouldExecute()) return;

        // 요청 데이터 검증
        $this->validateRequest();

        // 권한 확인
        $this->checkPermissions();
    }

    /**
     * 모듈 실행 후 처리
     */
    public function afterModuleProc()
    {
        if(!$this->shouldExecute()) return;

        // 결과 데이터 처리
        $this->processResults();

        // 추가 데이터 설정
        $this->setAdditionalData();
    }

    /**
     * 화면 출력 전 처리
     */
    public function beforeDisplayContent()
    {
        if(!$this->shouldExecute()) return;

        // CSS/JavaScript 파일 추가
        $this->addAssets();

        // 메타태그 추가
        $this->addMetaTags();
    }

    /**
     * 요청 데이터 검증
     */
    private function validateRequest()
    {
        // 입력값 검증 로직
    }

    /**
     * 권한 확인
     */
    private function checkPermissions()
    {
        // 권한 확인 로직
    }

    /**
     * 결과 데이터 처리
     */
    private function processResults()
    {
        // 결과 처리 로직
    }

    /**
     * 추가 데이터 설정
     */
    private function setAdditionalData()
    {
        // 추가 데이터 설정 로직
    }

    /**
     * CSS/JS 파일 추가
     */
    private function addAssets()
    {
        // CSS 파일 추가
        if(file_exists($this->addon_path . 'css/addon.css')) {
            Context::addCSSFile($this->addon_path . 'css/addon.css');
        }

        // JavaScript 파일 추가
        if(file_exists($this->addon_path . 'js/addon.js')) {
            Context::addJSFile($this->addon_path . 'js/addon.js');
        }
    }

    /**
     * 메타태그 추가
     */
    private function addMetaTags()
    {
        // 메타태그 추가 로직
    }
}

// 애드온 인스턴스 생성
$my_addon = new MyAddon();

// 트리거 포인트별 실행
switch($called_position) {
    case 'before_module_proc':
        $my_addon->beforeModuleProc();
        break;

    case 'after_module_proc':
        $my_addon->afterModuleProc();
        break;

    case 'before_display_content':
        $my_addon->beforeDisplayContent();
        break;
}
?>

데이터베이스 스키마

schemas/install.xml

<?xml version="1.0" encoding="UTF-8"?>
<schema version="0.3">
    <!-- 메인 테이블 -->
    <table name="addon_my_addon_data">
        <column name="data_srl" type="number" size="11" notnull="notnull" primary_key="primary_key" />
        <column name="member_srl" type="number" size="11" index="idx_member" />
        <column name="title" type="varchar" size="250" index="idx_title" />
        <column name="content" type="text" />
        <column name="status" type="varchar" size="10" default="active" index="idx_status" />
        <column name="view_count" type="number" size="11" default="0" />
        <column name="regdate" type="date" index="idx_regdate" />
        <column name="last_update" type="date" />
    </table>

    <!-- 설정 테이블 -->
    <table name="addon_my_addon_config">
        <column name="config_srl" type="number" size="11" notnull="notnull" primary_key="primary_key" />
        <column name="config_key" type="varchar" size="100" unique="unique" />
        <column name="config_value" type="text" />
        <column name="regdate" type="date" />
    </table>

    <!-- 로그 테이블 -->
    <table name="addon_my_addon_logs">
        <column name="log_srl" type="number" size="11" notnull="notnull" primary_key="primary_key" />
        <column name="member_srl" type="number" size="11" index="idx_member" />
        <column name="action" type="varchar" size="50" index="idx_action" />
        <column name="target_srl" type="number" size="11" />
        <column name="ip_address" type="varchar" size="45" />
        <column name="user_agent" type="text" />
        <column name="data" type="text" />
        <column name="regdate" type="date" index="idx_regdate" />
    </table>
</schema>

XML 쿼리 파일

queries/insertData.xml

<?xml version="1.0" encoding="UTF-8"?>
<query id="insertData" action="insert">
    <tables>
        <table name="addon_my_addon_data" />
    </tables>
    <columns>
        <column name="member_srl" var="member_srl" />
        <column name="title" var="title" />
        <column name="content" var="content" />
        <column name="status" var="status" default="active" />
        <column name="regdate" var="regdate" />
        <column name="last_update" var="last_update" />
    </columns>
</query>

queries/selectDataList.xml

<?xml version="1.0" encoding="UTF-8"?>
<query id="selectDataList" action="select">
    <tables>
        <table name="addon_my_addon_data" alias="d" />
        <table name="member" alias="m" />
    </tables>
    <columns>
        <column name="d.*" />
        <column name="m.nick_name" />
        <column name="m.email_address" />
    </columns>
    <conditions>
        <condition operation="equal" column="d.status" var="status" default="active" />
        <condition operation="like" column="d.title" var="search_keyword" />
        <condition operation="more" column="d.regdate" var="start_date" filter="date" />
        <condition operation="less" column="d.regdate" var="end_date" filter="date" />
    </conditions>
    <navigation>
        <index var="sort_index" default="d.regdate" order="order_type" />
        <list_count var="list_count" default="20" />
        <page_count var="page_count" default="10" />
        <page var="page" default="1" />
    </navigation>
    <joins>
        <join type="left" table="member" alias="m" on="d.member_srl = m.member_srl" />
    </joins>
</query>

언어팩 파일

lang/ko.lang.php

<?php
/**
 * 한국어 언어팩
 */

// 기본 문구
$lang->my_addon = '나의 애드온';
$lang->my_addon_description = '애드온 설명';

// 설정 관련
$lang->my_addon_config = '애드온 설정';
$lang->my_addon_enabled = '애드온 활성화';
$lang->my_addon_cache_time = '캐시 시간';
$lang->my_addon_max_items = '최대 항목 수';

// 메시지
$lang->msg_my_addon_success = '작업이 성공적으로 완료되었습니다.';
$lang->msg_my_addon_error = '오류가 발생했습니다.';
$lang->msg_my_addon_no_permission = '권한이 없습니다.';
$lang->msg_my_addon_invalid_data = '잘못된 데이터입니다.';

// 버튼
$lang->cmd_my_addon_save = '저장';
$lang->cmd_my_addon_cancel = '취소';
$lang->cmd_my_addon_delete = '삭제';
$lang->cmd_my_addon_modify = '수정';

// 필드명
$lang->my_addon_title = '제목';
$lang->my_addon_content = '내용';
$lang->my_addon_author = '작성자';
$lang->my_addon_regdate = '등록일';
$lang->my_addon_status = '상태';

// 상태값
$lang->my_addon_status_active = '활성';
$lang->my_addon_status_inactive = '비활성';
$lang->my_addon_status_pending = '대기';
?>

설정 템플릿

tpl/config.html

<!-- 애드온 설정 페이지 -->
<form action="{getUrl()}" method="post" class="addon-config-form">
    <input type="hidden" name="addon" value="my_addon" />
    <input type="hidden" name="act" value="procAddonAdminSetup" />

    <div class="config-section">
        <h3>기본 설정</h3>

        <!-- 활성화 여부 -->
        <div class="form-group">
            <label for="enabled">애드온 활성화</label>
            <select name="enabled" id="enabled">
                <option value="Y" selected="selected"|cond="$addon_config->enabled=='Y'">예</option>
                <option value="N" selected="selected"|cond="$addon_config->enabled=='N'">아니오</option>
            </select>
        </div>

        <!-- 캐시 시간 -->
        <div class="form-group">
            <label for="cache_time">캐시 시간 (초)</label>
            <input type="number" name="cache_time" id="cache_time" 
                   value="{$addon_config->cache_time}" min="0" max="86400" />
            <p class="help-text">0으로 설정하면 캐시를 사용하지 않습니다.</p>
        </div>

        <!-- 최대 항목 수 -->
        <div class="form-group">
            <label for="max_items">최대 표시 항목 수</label>
            <input type="number" name="max_items" id="max_items" 
                   value="{$addon_config->max_items}" min="1" max="100" />
        </div>
    </div>

    <div class="config-section">
        <h3>고급 설정</h3>

        <!-- 제외할 페이지 -->
        <div class="form-group">
            <label for="excluded_pages">제외할 페이지 (mid, 콤마로 구분)</label>
            <textarea name="excluded_pages" id="excluded_pages" rows="3">{$addon_config->excluded_pages}</textarea>
            <p class="help-text">예: admin, private, test</p>
        </div>

        <!-- 커스텀 CSS -->
        <div class="form-group">
            <label for="custom_css">커스텀 CSS</label>
            <textarea name="custom_css" id="custom_css" rows="10">{$addon_config->custom_css}</textarea>
        </div>
    </div>

    <div class="form-actions">
        <button type="submit" class="btn btn-primary">설정 저장</button>
        <button type="button" class="btn btn-secondary" onclick="history.back()">취소</button>
    </div>
</form>

<style>
.addon-config-form {
    max-width: 800px;
    margin: 0 auto;
}

.config-section {
    background: #f8f9fa;
    padding: 20px;
    margin-bottom: 20px;
    border-radius: 8px;
}

.config-section h3 {
    margin-top: 0;
    margin-bottom: 20px;
    color: #333;
}

.form-group {
    margin-bottom: 15px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
}

.form-group input[type="text"],
.form-group input[type="number"],
.form-group select,
.form-group textarea {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
}

.form-group textarea {
    resize: vertical;
    font-family: 'Consolas', 'Monaco', monospace;
}

.help-text {
    margin-top: 5px;
    font-size: 12px;
    color: #666;
}

.form-actions {
    text-align: center;
    padding-top: 20px;
    border-top: 1px solid #eee;
}

.btn {
    padding: 10px 20px;
    margin: 0 5px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
}

.btn-primary {
    background: #007bff;
    color: white;
}

.btn-secondary {
    background: #6c757d;
    color: white;
}
</style>

모범 사례

  1. 파일 구조: 명확한 디렉토리 구조로 파일 관리
  2. 네이밍: 일관된 명명 규칙 사용
  3. 설정 관리: 유연한 설정 시스템 구축
  4. 에러 처리: 적절한 예외 처리 구현
  5. 문서화: 상세한 주석과 README 작성

다음 단계

애드온 구조를 이해했다면, 애드온 기초를 학습하세요.