KBoard 플러그인 훅 filter 가이드

1. 설정 및 기본 필터

1.1 KBoard 기본 설정 제어

kboard_settings

내용: 자바스크립트 설정을 동적으로 변경할 수 있습니다.

코드:

add_filter('kboard_settings', 'customize_kboard_settings');
function customize_kboard_settings($settings) {
    // AJAX 로딩 텍스트 커스터마이징
    $settings['loading_text'] = '잠시만 기다려주세요...';
    
    // 파일 업로드 제한 설정
    $settings['max_file_size'] = 10 * 1024 * 1024; // 10MB
    
    // 게시판별 다른 설정 적용
    if (isset($_GET['id']) && $_GET['id'] == 3) {
        $settings['auto_refresh'] = 30000; // 30초마다 자동 새로고침
    }
    
    return $settings;
}

kboard_localize_strings

내용: 다국어 문자열을 커스터마이징합니다.

코드:

add_filter('kboard_localize_strings', 'custom_localize_strings');
function custom_localize_strings($strings) {
    // 기본 메시지 커스터마이징
    $strings['confirm_delete'] = '정말로 삭제하시겠습니까?';
    $strings['loading'] = '로딩 중...';
    $strings['error_occurred'] = '오류가 발생했습니다. 다시 시도해주세요.';
    
    // 브랜드에 맞는 메시지로 변경
    $strings['write_post'] = '새 글쓰기';
    $strings['reply'] = '답글달기';
    
    return $strings;
}

kboard_per_rss

내용: RSS 피드의 게시글 수를 조절합니다.

코드:

add_filter('kboard_per_rss', 'customize_rss_per_page', 10, 2);
function customize_rss_per_page($per_page, $board_id) {
    // 공지사항 게시판은 모든 게시글 포함
    if ($board_id == 1) {
        return -1; // 전체
    }
    
    // 일반 게시판은 최신 50개
    return 50;
}

2. 스킨 시스템 필터

2.1 스킨 관리 및 커스터마이징

kboard_skin_list

내용: 사용 가능한 스킨 목록을 확장합니다.

코드:

add_filter('kboard_skin_list', 'add_custom_skins');
function add_custom_skins($skins) {
    // 커스텀 스킨 추가
    $skins['premium_gallery'] = 'Premium Gallery Skin';
    $skins['corporate_board'] = 'Corporate Board Skin';
    $skins['mobile_optimized'] = 'Mobile Optimized Skin';
    
    return $skins;
}

kboard_skin_file_path

내용: 스킨 파일 경로를 동적으로 변경합니다.

코드:

add_filter('kboard_skin_file_path', 'custom_skin_file_path', 10, 5);
function custom_skin_file_path($file_path, $skin_name, $file, $vars, $skin_obj) {
    // 테마 디렉토리에서 스킨 파일 우선 로드
    $theme_skin_path = get_template_directory() . '/kboard-skins/' . $skin_name . '/' . $file;
    
    if (file_exists($theme_skin_path)) {
        return $theme_skin_path;
    }
    
    // A/B 테스트용 스킨 분기
    if ($skin_name === 'default' && isset($_GET['variant']) && $_GET['variant'] === 'b') {
        $ab_test_path = str_replace('/default/', '/default-variant-b/', $file_path);
        if (file_exists($ab_test_path)) {
            return $ab_test_path;
        }
    }
    
    return $file_path;
}

kboard_skin_category_type

내용: 카테고리 표시 방식을 커스터마이징합니다.

코드:

add_filter('kboard_skin_category_type', 'dynamic_category_type', 10, 3);
function dynamic_category_type($type, $board, $boardBuilder) {
    // 모바일에서는 드롭다운으로 표시
    if (wp_is_mobile()) {
        return 'dropdown';
    }
    
    // 카테고리가 많은 게시판은 검색 가능한 드롭다운
    if (count($board->categories) > 10) {
        return 'searchable_dropdown';
    }
    
    // 기본은 탭 형태
    return 'tabs';
}

3. 함수 및 매개변수 필터

3.1 사용자 관련 필터

kboard_current_user_roles

내용: 현재 사용자의 역할을 확장합니다.

코드:

add_filter('kboard_current_user_roles', 'extend_user_roles');
function extend_user_roles($roles) {
    $current_user = wp_get_current_user();
    
    // VIP 회원 역할 추가
    if (get_user_meta($current_user->ID, 'is_vip_member', true)) {
        $roles[] = 'vip_member';
    }
    
    // 활동 점수 기반 역할 추가
    $activity_score = get_user_meta($current_user->ID, 'activity_score', true);
    if ($activity_score > 1000) {
        $roles[] = 'power_user';
    }
    
    // 게시판별 모더레이터 역할
    if (current_user_can('moderate_board_' . kboard_id())) {
        $roles[] = 'board_moderator';
    }
    
    return $roles;
}

kboard_user_ip

내용: 사용자 IP 주소를 정확하게 감지합니다.

코드:

add_filter('kboard_user_ip', 'get_real_user_ip');
function get_real_user_ip($ip) {
    // 프록시 서버 환경에서 실제 IP 획득
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        // 여러 IP가 있는 경우 첫 번째 IP 사용
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($ips[0]);
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED'])) {
        return $_SERVER['HTTP_X_FORWARDED'];
    } elseif (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP'])) {
        return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_FORWARDED_FOR'])) {
        return $_SERVER['HTTP_FORWARDED_FOR'];
    } elseif (!empty($_SERVER['HTTP_FORWARDED'])) {
        return $_SERVER['HTTP_FORWARDED'];
    }
    
    return $ip;
}

3.2 콘텐츠 에디터 커스터마이징

kboard_content_editor_list

내용: 사용 가능한 에디터 목록을 확장합니다.

코드:

add_filter('kboard_content_editor_list', 'add_custom_editors');
function add_custom_editors($editors) {
    // 마크다운 에디터 추가
    $editors['markdown'] = 'Markdown Editor';
    
    // 코드 에디터 추가 (개발자 게시판용)
    $editors['code_editor'] = 'Code Editor (Syntax Highlighting)';
    
    // 위지윅 에디터 업그레이드
    $editors['tinymce_advanced'] = 'Advanced WYSIWYG Editor';
    
    return $editors;
}

kboard_content_editor

내용: 에디터 HTML을 완전히 커스터마이징합니다.

코드:

add_filter('kboard_content_editor', 'customize_content_editor', 10, 2);
function customize_content_editor($editor_html, $vars) {
    $editor_type = $vars['editor'] ?? 'default';
    
    switch ($editor_type) {
        case 'markdown':
            $editor_html = '<div class="markdown-editor-container">';
            $editor_html .= '<textarea name="content" class="markdown-editor">' . esc_textarea($vars['content']) . '</textarea>';
            $editor_html .= '<div class="markdown-preview"></div>';
            $editor_html .= '</div>';
            $editor_html .= '<script>initMarkdownEditor();</script>';
            break;
            
        case 'code_editor':
            $editor_html = '<div class="code-editor-container">';
            $editor_html .= '<select name="code_language" class="code-language-selector">';
            $editor_html .= '<option value="php">PHP</option>';
            $editor_html .= '<option value="javascript">JavaScript</option>';
            $editor_html .= '<option value="python">Python</option>';
            $editor_html .= '</select>';
            $editor_html .= '<textarea name="content" class="code-editor">' . esc_textarea($vars['content']) . '</textarea>';
            $editor_html .= '</div>';
            $editor_html .= '<script>initCodeEditor();</script>';
            break;
    }
    
    return $editor_html;
}

4. 리스트 및 쿼리 필터

4.1 게시글 목록 커스터마이징

kboard_list_rpp

내용: 페이지당 게시글 수를 동적으로 조절합니다.

코드:

add_filter('kboard_list_rpp', 'dynamic_posts_per_page', 10, 3);
function dynamic_posts_per_page($rpp, $board_id, $list) {
    // VIP 회원은 더 많은 게시글 표시
    if (in_array('vip_member', kboard_current_user_roles())) {
        return $rpp * 2;
    }
    
    // 모바일에서는 적게 표시
    if (wp_is_mobile()) {
        return min($rpp, 10);
    }
    
    // 갤러리 게시판은 많이 표시
    if ($board_id == 4) { // 갤러리 게시판
        return 50;
    }
    
    // 사용자 설정 반영
    $user_preference = get_user_meta(get_current_user_id(), 'kboard_posts_per_page', true);
    if ($user_preference) {
        return intval($user_preference);
    }
    
    return $rpp;
}

kboard_list_where

내용: 게시글 목록 쿼리 조건을 수정합니다.

코드:

add_filter('kboard_list_where', 'customize_list_query', 10, 2);
function customize_list_query($where, $board_id) {
    global $wpdb;
    
    // 신고된 게시글 숨기기
    $where .= " AND content.uid NOT IN (
        SELECT content_uid FROM {$wpdb->prefix}kboard_reports 
        WHERE report_count >= 3
    )";
    
    // VIP 전용 게시판 처리
    if ($board_id == 5) { // VIP 게시판
        if (!in_array('vip_member', kboard_current_user_roles())) {
            $where .= " AND 1=0"; // VIP가 아니면 결과 없음
        }
    }
    
    // 지역별 필터링 (사용자 설정 기반)
    $user_region = get_user_meta(get_current_user_id(), 'user_region', true);
    if ($user_region && $board_id == 6) { // 지역 게시판
        $where .= $wpdb->prepare(" AND content.region = %s", $user_region);
    }
    
    return $where;
}

kboard_list_orderby

내용: 정렬 방식을 고급화합니다.

코드:

add_filter('kboard_list_orderby', 'advanced_list_ordering', 10, 2);
function advanced_list_ordering($orderby, $board_id) {
    $sort_option = kboard_search_option();
    
    switch ($sort_option) {
        case 'popular':
            // 인기도 = 조회수 + 댓글수 + 좋아요
            $orderby = "(content.view + content.comment + content.vote_up) DESC, content.date DESC";
            break;
            
        case 'trending':
            // 트렌딩 = 최근 활동 기반 인기도
            $orderby = "((content.view + content.comment + content.vote_up) * EXP(-TIMESTAMPDIFF(HOUR, content.date, NOW()) / 24)) DESC";
            break;
            
        case 'controversial':
            // 논란성 = 좋아요와 싫어요 비율
            $orderby = "ABS(content.vote_up - content.vote_down) DESC, 
                       (content.vote_up + content.vote_down) DESC";
            break;
            
        case 'quality':
            // 품질 = 글자수 + 이미지수 + 댓글수
            $orderby = "(CHAR_LENGTH(content.content) / 100 + 
                       content.comment * 2 + 
                       (CHAR_LENGTH(content.content) - CHAR_LENGTH(REPLACE(content.content, '<img', ''))) * 5) DESC";
            break;
    }
    
    return $orderby;
}

4.2 검색 기능 강화

kboard_list_search_option

내용: 검색 옵션을 확장합니다.

코드:

add_filter('kboard_list_search_option', 'extended_search_options');
function extended_search_options($options) {
    // 기존 옵션에 추가
    $options['tag'] = '태그';
    $options['author_email'] = '작성자 이메일';
    $options['ip_address'] = 'IP 주소';
    $options['date_range'] = '날짜 범위';
    $options['file_name'] = '첨부파일명';
    $options['custom_field'] = '사용자 정의 필드';
    
    return $options;
}

5. URL 및 라우팅 필터

5.1 URL 구조 커스터마이징

kboard_url_file_download

내용: 파일 다운로드 URL을 보안 강화합니다.

코드:

add_filter('kboard_url_file_download', 'secure_download_url', 10, 4);
function secure_download_url($url, $file, $content, $board) {
    // 토큰 기반 다운로드 URL 생성
    $token = wp_create_nonce('kboard_download_' . $file['uid']);
    $url = add_query_arg([
        'action' => 'kboard_secure_download',
        'file_id' => $file['uid'],
        'token' => $token,
        'expires' => time() + 3600 // 1시간 후 만료
    ], admin_url('admin-ajax.php'));
    
    return $url;
}

kboard_url_document_uid

내용: SEO 친화적 URL 구조를 만듭니다.

코드:

add_filter('kboard_url_document_uid', 'seo_friendly_document_url', 10, 3);
function seo_friendly_document_url($url, $content_uid, $content) {
    if ($content && $content->title) {
        // 제목을 URL 슬러그로 변환
        $slug = sanitize_title($content->title);
        $url = add_query_arg([
            'uid' => $content_uid,
            'slug' => $slug
        ], $url);
    }
    
    return $url;
}

5.2 리다이렉트 제어

kboard_url_document_redirect

내용: 문서 리다이렉트를 조건부로 처리합니다.

코드:

add_filter('kboard_url_document_redirect', 'conditional_redirect', 10, 3);
function conditional_redirect($redirect_url, $content, $board) {
    // 외부 링크인 경우 경고 페이지로 리다이렉트
    if (filter_var($redirect_url, FILTER_VALIDATE_URL) && 
        !str_contains($redirect_url, get_site_url())) {
        
        return add_query_arg([
            'action' => 'external_link_warning',
            'target' => urlencode($redirect_url)
        ], home_url());
    }
    
    // 회원 전용 링크 처리
    if (!is_user_logged_in() && $board->permission_read) {
        return wp_login_url($redirect_url);
    }
    
    return $redirect_url;
}

6. 고급 데이터 처리 필터

6.1 문서 데이터 가공

kboard_insert_data

내용: 문서 등록 시 데이터를 전처리합니다.

코드:

add_filter('kboard_insert_data', 'preprocess_insert_data', 10, 2);
function preprocess_insert_data($data, $board) {
    // 제목 자동 보정
    $data['title'] = trim($data['title']);
    if (empty($data['title'])) {
        $data['title'] = '제목 없음 ' . date('Y-m-d H:i:s');
    }
    
    // 내용 정제
    $data['content'] = wp_kses_post($data['content']);
    
    // 해시태그 자동 추출
    if (preg_match_all('/#([가-힣a-zA-Z0-9_]+)/', $data['content'], $matches)) {
        $data['hashtags'] = implode(',', array_unique($matches[1]));
    }
    
    // 읽기 시간 계산
    $word_count = str_word_count(strip_tags($data['content']));
    $data['reading_time'] = ceil($word_count / 200); // 분당 200단어
    
    // SEO 메타데이터 생성
    $data['meta_description'] = wp_trim_words(strip_tags($data['content']), 20);
    
    // 감정 분석 (긍정/부정)
    $data['sentiment'] = analyze_sentiment($data['content']);
    
    return $data;
}

kboard_update_data

내용: 문서 수정 시 변경 이력을 관리합니다.

코드:

add_filter('kboard_update_data', 'track_update_history', 10, 3);
function track_update_history($data, $content_uid, $board) {
    global $wpdb;
    
    // 기존 데이터 조회
    $original = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}kboard_board_content WHERE uid = %d",
        $content_uid
    ));
    
    if ($original) {
        // 변경 사항 추적
        $changes = [];
        if ($original->title !== $data['title']) {
            $changes['title'] = ['from' => $original->title, 'to' => $data['title']];
        }
        if ($original->content !== $data['content']) {
            $changes['content'] = ['from' => $original->content, 'to' => $data['content']];
        }
        
        // 변경 이력 저장
        if (!empty($changes)) {
            $wpdb->insert(
                $wpdb->prefix . 'kboard_edit_history',
                [
                    'content_uid' => $content_uid,
                    'user_id' => get_current_user_id(),
                    'changes' => json_encode($changes),
                    'date' => current_time('mysql')
                ]
            );
            
            // 수정 횟수 업데이트
            $data['edit_count'] = intval($original->edit_count ?? 0) + 1;
        }
    }
    
    return $data;
}

6.2 투표 및 평가 시스템

kboard_vote_filter

내용: 투표 시스템을 고도화합니다.

코드:

add_filter('kboard_vote_filter', 'advanced_voting_system', 10, 3);
function advanced_voting_system($vote_data, $content_uid, $vote_type) {
    // 중복 투표 방지 (IP + 사용자 ID 기반)
    $user_id = get_current_user_id();
    $user_ip = kboard_user_ip();
    
    global $wpdb;
    $existing_vote = $wpdb->get_var($wpdb->prepare(
        "SELECT vote_type FROM {$wpdb->prefix}kboard_vote_log 
         WHERE content_uid = %d AND (user_id = %d OR user_ip = %s) 
         ORDER BY date DESC LIMIT 1",
        $content_uid, $user_id, $user_ip
    ));
    
    if ($existing_vote === $vote_type) {
        // 같은 투표 취소
        $vote_data['action'] = 'cancel';
    } elseif ($existing_vote) {
        // 투표 변경
        $vote_data['action'] = 'change';
        $vote_data['previous_vote'] = $existing_vote;
    } else {
        // 새 투표
        $vote_data['action'] = 'new';
    }
    
    // 투표 가중치 계산 (사용자 신뢰도 기반)
    $user_trust_score = get_user_meta($user_id, 'trust_score', true) ?: 1.0;
    $vote_data['weight'] = min(max($user_trust_score, 0.1), 3.0);
    
    // 투표 이유 저장 (선택사항)
    if (!empty($_POST['vote_reason'])) {
        $vote_data['reason'] = sanitize_textarea_field($_POST['vote_reason']);
    }
    
    return $vote_data;
}

7. 고급 활용 패턴

7.1 조건부 필터 체인

코드:

// 게시판별 다른 필터 적용
add_filter('kboard_list_where', 'board_specific_filters', 10, 2);
function board_specific_filters($where, $board_id) {
    switch ($board_id) {
        case 1: // 공지사항
            return apply_filters('kboard_notice_list_where', $where);
        case 2: // 자유게시판
            return apply_filters('kboard_free_list_where', $where);
        case 3: // 갤러리
            return apply_filters('kboard_gallery_list_where', $where);
        default:
            return $where;
    }
}

// 공지사항 게시판 전용 필터
add_filter('kboard_notice_list_where', 'notice_board_filter');
function notice_board_filter($where) {
    // 중요 공지사항 우선 표시
    global $wpdb;
    $where .= " ORDER BY CASE WHEN content.priority = 'urgent' THEN 0 ELSE 1 END, content.date DESC";
    return $where;
}

7.2 캐싱 활용 필터

코드:

add_filter('kboard_content_list_items', 'cached_content_list', 10, 2);
function cached_content_list($items, $board_id) {
    $cache_key = 'kboard_list_' . $board_id . '_' . md5(serialize($_GET));
    
    // 캐시된 데이터 확인
    $cached_items = wp_cache_get($cache_key, 'kboard');
    if ($cached_items !== false) {
        return $cached_items;
    }
    
    // 아이템 후처리
    foreach ($items as &$item) {
        // 썸네일 생성
        if (empty($item->thumbnail)) {
            $item->thumbnail = extract_first_image($item->content);
        }
        
        // 요약 생성
        $item->summary = wp_trim_words(strip_tags($item->content), 30);
        
        // 읽기 시간 계산
        $word_count = str_word_count(strip_tags($item->content));
        $item->reading_time = ceil($word_count / 200);
    }
    
    // 캐시 저장 (5분)
    wp_cache_set($cache_key, $items, 'kboard', 300);
    
    return $items;
}

7.3 A/B 테스트 필터

코드:

add_filter('kboard_skin_file_path', 'ab_test_skin_variant', 10, 5);
function ab_test_skin_variant($file_path, $skin_name, $file, $vars, $skin_obj) {
    // 사용자를 A/B 그룹으로 분할
    $user_id = get_current_user_id() ?: ip2long(kboard_user_ip());
    $ab_group = ($user_id % 2 === 0) ? 'A' : 'B';
    
    // B 그룹용 스킨 파일이 존재하면 사용
    if ($ab_group === 'B') {
        $variant_path = str_replace($file, 'variant-b-' . $file, $file_path);
        if (file_exists($variant_path)) {
            // A/B 테스트 로깅
            error_log("AB Test: User $user_id using variant B for $file");
            return $variant_path;
        }
    }
    
    return $file_path;
}

8. 성능 최적화 필터

8.1 쿼리 최적화

코드:

add_filter('kboard_list_select', 'optimize_list_query');
function optimize_list_query($select) {
    // 불필요한 필드 제외하고 필요한 필드만 선택
    $select = "content.uid, content.title, content.date, content.member_display, 
               content.view, content.comment, content.vote_up, content.vote_down,
               SUBSTRING(content.content, 1, 200) as content_preview";
    
    return $select;
}

add_filter('kboard_list_from', 'add_query_joins');
function add_query_joins($from) {
    global $wpdb;
    
    // 필요한 경우에만 조인 추가
    if (kboard_search_option() === 'author_info') {
        $from .= " LEFT JOIN {$wpdb->users} u ON content.member_uid = u.ID";
    }
    
    return $from;
}

8.2 이미지 최적화

코드:

add_filter('kboard_content_editor', 'optimize_image_upload', 10, 2);
function optimize_image_upload($editor_html, $vars) {
    // 이미지 업로드 시 자동 압축 설정 추가
    $editor_html .= '<script>
        document.addEventListener("DOMContentLoaded", function() {
            const imageInputs = document.querySelectorAll("input[type=file][accept*=image]");
            imageInputs.forEach(function(input) {
                input.addEventListener("change", function(e) {
                    const files = Array.from(e.target.files);
                    const compressedFiles = [];
                    
                    files.forEach(function(file) {
                        if (file.type.startsWith("image/")) {
                            compressImage(file, 0.8, 1920, 1080).then(function(compressed) {
                                compressedFiles.push(compressed);
                            });
                        }
                    });
                });
            });
        });
    </script>';
    
    return $editor_html;
}

실무 베스트 프랙티스

1. 필터 체인 관리

코드:

// 필터 우선순위를 명확하게 설정
add_filter('kboard_list_where', 'security_filter', 5);    // 보안 필터 우선
add_filter('kboard_list_where', 'permission_filter', 10); // 권한 필터
add_filter('kboard_list_where', 'custom_filter',  15);    // 커스텀 필터

2. 데이터 검증

코드:

add_filter('kboard_insert_data', 'validate_and_sanitize_data', 1, 2);
function validate_and_sanitize_data($data, $board) {
    // 모든 입력 데이터 검증
    $data['title'] = sanitize_text_field($data['title'] ?? '');
    $data['content'] = wp_kses_post($data['content'] ?? '');
    
    // 필수 필드 검증
    if (empty($data['title'])) {
        wp_die('제목을 입력해주세요.');
    }
    
    return $data;
}

3. 에러 처리

코드:

add_filter('kboard_content_list_items', 'handle_list_errors', 999, 2);
function handle_list_errors($items, $board_id) {
    if (is_wp_error($items)) {
        error_log('KBoard 리스트 오류: ' . $items->get_error_message());
        return []; // 빈 배열 반환
    }
    
    return $items;
}