콘텐츠로 이동

6. 커스텀 모듈

BreezeBio 테마는 4개의 커스텀 포스트 타입(CPT) 모듈과 추가 기능 모듈을 포함합니다.

모듈네임스페이스CPT역할
ContactUsGenedit\ContactUscontact_us문의 폼 처리
TeamMemberGenedit\TeamMemberteam_member팀 멤버 관리
LegalGenedit\Legallegal_document법무 문서
NewsGenedit\News-뉴스 REST API
LlmsTxtGenedit\LlmsTxt-LLMs.txt 생성

src/ContactUs/
├── ContactUs.php # 모듈 초기화
├── ContactUsPostType.php # CPT 등록
├── ContactUsRestApi.php # REST API 엔드포인트
├── ContactUsMailer.php # 이메일 발송
└── ContactUsMetadata.php # 메타데이터 정의
ContactUsPostType.php
class ContactUsPostType
{
public const POST_TYPE = 'contact_us';
public function register(): void
{
register_post_type(self::POST_TYPE, [
'labels' => [
'name' => 'Contact Us',
'singular_name' => 'Contact',
],
'public' => false,
'show_ui' => true,
'show_in_menu' => true,
'supports' => ['title'],
'menu_icon' => 'dashicons-email',
]);
}
}

엔드포인트: POST /wp-json/genedit/v1/contact

ContactUsRestApi.php
class ContactUsRestApi
{
public function register(): void
{
register_rest_route('genedit/v1', '/contact', [
'methods' => 'POST',
'callback' => [$this, 'handleSubmit'],
'permission_callback' => '__return_true',
]);
}
public function handleSubmit(WP_REST_Request $request): WP_REST_Response
{
// 1. 데이터 검증
$data = $this->validateData($request);
// 2. CPT로 저장
$postId = $this->saveContact($data);
// 3. 이메일 발송
(new ContactUsMailer())->send($data);
return new WP_REST_Response([
'success' => true,
'post_id' => $postId,
]);
}
}
필드메타 키타입
First Name_first_namestring
Last Name_last_namestring
Email_emailstring
Phone_phonestring
Company_companystring
Job Title_job_titlestring
Inquiry Type_inquiry_typestring
Message_messagetext
Country_countrystring
Source Page_source_pagestring

src/TeamMember/
├── TeamMember.php # 모듈 초기화
├── TeamMemberPostType.php # CPT 등록
├── TeamMemberTaxonomy.php # Team Group 택소노미
├── TeamMemberQuery.php # 쿼리 헬퍼
├── TeamMemberCommand.php # WP-CLI 명령어
└── TeamMemberMigration.php # 데이터 마이그레이션
TeamMemberPostType.php
class TeamMemberPostType
{
public const POST_TYPE = 'team_member';
public function register(): void
{
register_post_type(self::POST_TYPE, [
'labels' => [
'name' => 'Team Members',
'singular_name' => 'Team Member',
],
'public' => true,
'show_in_rest' => true,
'supports' => ['title', 'thumbnail'],
'menu_icon' => 'dashicons-groups',
'has_archive' => false,
]);
}
}
TeamMemberTaxonomy.php
class TeamMemberTaxonomy
{
public const TAXONOMY = 'team_group';
public function register(): void
{
register_taxonomy(self::TAXONOMY, TeamMemberPostType::POST_TYPE, [
'labels' => [
'name' => 'Team Groups',
'singular_name' => 'Team Group',
],
'public' => true,
'hierarchical' => true,
'show_in_rest' => true,
]);
}
}
슬러그이름프론트엔드 탭
leadershipLeadershipLeadership
advisorsAdvisorsAdvisors
boardBoardBoard of Directors
TeamMemberQuery.php
class TeamMemberQuery
{
public static function getTeamTabs(): array
{
$groups = get_terms([
'taxonomy' => TeamMemberTaxonomy::TAXONOMY,
'hide_empty' => true,
]);
$tabs = [];
foreach ($groups as $group) {
$members = Timber::get_posts([
'post_type' => TeamMemberPostType::POST_TYPE,
'tax_query' => [
['taxonomy' => TeamMemberTaxonomy::TAXONOMY, 'terms' => $group->term_id],
],
'posts_per_page' => -1,
]);
$tabs[] = [
'id' => $group->slug,
'label' => $group->name,
'members' => $members,
];
}
return $tabs;
}
}
필드타입
PositionpositionText
BiobioWYSIWYG
LinkedIn URLlinkedin_urlURL
Profile Imageprofile_imageImage
Terminal window
# 팀 멤버 목록
$ ddev wp genedit team list
# CSV에서 가져오기
$ ddev wp genedit team import /path/to/file.csv
# CSV로 내보내기
$ ddev wp genedit team export /path/to/output.csv

src/Legal/
├── LegalDocument.php # 모듈 초기화
├── LegalDocumentPostType.php # CPT 등록
├── LegalTypeTaxonomy.php # Legal Type 택소노미
└── LegalDocumentQuery.php # 쿼리 헬퍼
LegalDocumentPostType.php
class LegalDocumentPostType
{
public const POST_TYPE = 'legal_document';
public function register(): void
{
register_post_type(self::POST_TYPE, [
'labels' => [
'name' => 'Legal Documents',
'singular_name' => 'Legal Document',
],
'public' => true,
'show_in_rest' => true,
'supports' => ['title', 'editor'],
'menu_icon' => 'dashicons-media-document',
'has_archive' => false,
'rewrite' => ['slug' => 'legal-document'],
]);
}
}
슬러그이름URL
termsTerms of Service/legal/terms
privacyPrivacy Policy/legal/privacy
cookiesCookie Policy/legal/cookies

기본 WordPress Posts에 추가 REST 엔드포인트를 제공합니다.

NewsRestApi.php
class NewsRestApi
{
public function register(): void
{
register_rest_route('genedit/v1', '/news', [
'methods' => 'GET',
'callback' => [$this, 'getNews'],
'permission_callback' => '__return_true',
'args' => [
'count' => ['default' => 10],
'category' => ['default' => ''],
'lang' => ['default' => ''],
],
]);
}
public function getNews(WP_REST_Request $request): WP_REST_Response
{
$args = [
'post_type' => 'post',
'posts_per_page' => $request->get_param('count'),
'post_status' => 'publish',
];
// 카테고리 필터
if ($category = $request->get_param('category')) {
$args['category_name'] = $category;
}
// 다국어 필터 (Polylang)
if ($lang = $request->get_param('lang')) {
$args['lang'] = $lang;
}
$posts = Timber::get_posts($args);
return new WP_REST_Response(
array_map([$this, 'formatPost'], $posts)
);
}
}
{
"id": 123,
"title": "News Title",
"excerpt": "News excerpt...",
"date": "2026-02-05",
"category": "Press Releases",
"thumbnail": "https://...",
"url": "/news/news-title"
}

LLMs.txt는 AI 언어 모델이 웹사이트 정보를 이해할 수 있도록 하는 표준 형식입니다.

GET /llms.txt
LlmsTxtGenerator.php
class LlmsTxtGenerator
{
public function register(): void
{
add_action('init', [$this, 'addRewriteRule']);
add_filter('query_vars', [$this, 'addQueryVar']);
add_action('template_redirect', [$this, 'handleRequest']);
}
public function generate(): string
{
$output = "# BreezeBio\n\n";
$output .= "> Genetic Medicine Delivered\n\n";
$output .= "## About\n";
$output .= "BreezeBio develops innovative genetic medicines...\n\n";
$output .= "## Pages\n";
// 페이지 목록 생성
return $output;
}
}

GeneditSite.php
public function __construct()
{
// 1. 블록 시스템
(new BlockRegistrar())->init();
// 2. CPT 모듈들
(new ContactUs())->init(); // contact_us CPT + REST API
(new TeamMember())->init(); // team_member CPT + CLI
(new LegalDocument())->init(); // legal_document CPT
// 3. 추가 기능
(new NewsRestApi())->register(); // 뉴스 REST API
(new LlmsTxtGenerator())->register(); // LLMs.txt
parent::__construct();
}

← 이전: 5. ACF 블록 명세 | 다음: 7. 부록 →