Files
dnssash/main_add.php
T
2026-06-11 18:47:38 +09:00

581 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* install_journal_theme.php
* - GNUBOARD5 테마( journal ) + 공용 게시판 스킨 생성
* - ZIP까지 생성
*
* 사용 후 반드시 삭제하세요.
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
function ensure_dir($dir) {
if (!is_dir($dir)) {
if (!mkdir($dir, 0755, true)) {
throw new Exception("mkdir 실패: {$dir}");
}
}
}
function put_file($path, $content) {
$dir = dirname($path);
ensure_dir($dir);
if (file_put_contents($path, $content) === false) {
throw new Exception("file_put_contents 실패: {$path}");
}
}
function zip_dir($sourceDir, $zipPath) {
if (!extension_loaded('zip')) {
throw new Exception("PHP zip 확장(ZipArchive)이 없습니다.");
}
$sourceDir = rtrim($sourceDir, '/\\');
$zip = new ZipArchive();
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new Exception("ZIP 생성 실패: {$zipPath}");
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
$filePath = $file->getPathname();
$localPath = substr($filePath, strlen($sourceDir) + 1);
if ($file->isDir()) {
$zip->addEmptyDir($localPath);
} else {
$zip->addFile($filePath, $localPath);
}
}
$zip->close();
}
try {
$root = __DIR__;
$themeRoot = $root . '/theme/journal';
// ---------------------------
// 1) 테마 기본 파일
// ---------------------------
put_file($themeRoot.'/theme.config.php', <<<'PHP'
<?php
if (!defined('_GNUBOARD_')) exit;
$theme_config = array();
$theme_config['name'] = 'journal';
$theme_config['version'] = '1.0.0';
PHP
);
put_file($themeRoot.'/head.php', <<<'PHP'
<?php
if (!defined('_GNUBOARD_')) exit;
add_stylesheet('<link rel="stylesheet" href="'.G5_THEME_URL.'/assets/css/journal.base.css?v=1">', 0);
add_stylesheet('<link rel="stylesheet" href="'.G5_THEME_URL.'/assets/css/journal.layout.css?v=1">', 1);
add_javascript('<script src="'.G5_THEME_URL.'/assets/js/journal.base.js?v=1"></script>', 0);
include_once(G5_THEME_PATH.'/parts/header/header.php');
PHP
);
put_file($themeRoot.'/tail.php', <<<'PHP'
<?php
if (!defined('_GNUBOARD_')) exit;
include_once(G5_THEME_PATH.'/parts/footer/footer.php');
PHP
);
put_file($themeRoot.'/index.php', <<<'PHP'
<?php
define('_INDEX_', true);
include_once('./_common.php');
include_once(G5_THEME_PATH.'/head.php');
include_once(G5_THEME_PATH.'/parts/main/main.php');
include_once(G5_THEME_PATH.'/tail.php');
PHP
);
// ---------------------------
// 2) parts: header/main/footer
// ---------------------------
put_file($themeRoot.'/parts/header/header.php', <<<'PHP'
<?php
if (!defined('_GNUBOARD_')) exit;
?>
<header class="jrnl" id="jrnl-header">
<div class="jrnl__topbar">
<div class="jrnl__container">
<nav class="jrnl__utility">
<?php if ($is_member) { ?>
<a href="<?php echo G5_BBS_URL ?>/logout.php">로그아웃</a>
<?php } else { ?>
<a href="<?php echo G5_BBS_URL ?>/login.php">로그인</a>
<a href="<?php echo G5_BBS_URL ?>/register.php">회원가입</a>
<?php } ?>
<a href="<?php echo G5_BBS_URL ?>/faq.php">고객센터</a>
</nav>
</div>
</div>
<div class="jrnl__gnb">
<div class="jrnl__container jrnl__gnbRow">
<a class="jrnl__logo" href="<?php echo G5_URL ?>/">
<img src="<?php echo G5_THEME_URL ?>/assets/img/logo.svg" alt="LaserWorld">
</a>
<button class="jrnl__menuBtn" type="button" data-jrnl="menu-toggle">메뉴</button>
<nav class="jrnl__nav" data-jrnl="nav">
<a href="#">기업소개</a>
<a href="#">레이저 뉴스</a>
<a href="#">신기술&신제품</a>
<a href="#">커뮤니티</a>
<a href="#">과월호</a>
</nav>
<div class="jrnl__search">
<form action="<?php echo G5_BBS_URL ?>/search.php" method="get">
<input type="text" name="stx" placeholder="검색" />
<button type="submit">검색</button>
</form>
</div>
</div>
</div>
</header>
PHP
);
put_file($themeRoot.'/parts/main/main.php', <<<'PHP'
<?php
if (!defined('_GNUBOARD_')) exit;
?>
<main class="jrnl" id="jrnl-main">
<!-- HERO + LATEST -->
<section class="jrnl__hero">
<div class="jrnl__container jrnl__heroGrid">
<article class="jrnl__heroCard" data-slot="hero">
<div class="jrnl__heroThumb">
<img src="<?php echo G5_THEME_URL ?>/assets/img/hero-laser.svg" alt="Hero" />
</div>
<div class="jrnl__heroBody">
<p class="jrnl__kicker">HEADLINE</p>
<h1 class="jrnl__heroTitle">메인 헤드라인 영역(연동 전)</h1>
<p class="jrnl__heroLead">이 영역은 ‘가장 최신/중요 기사 1건’을 연결하세요.</p>
<div class="jrnl__meta">
<span>YYYY-MM-DD</span>
<span>Category</span>
</div>
<a class="jrnl__btn" href="#">전체 기사 보기</a>
</div>
</article>
<aside class="jrnl__latest" data-slot="latest">
<div class="jrnl__sectionHead">
<h2>Latest News</h2>
<a href="#">더보기</a>
</div>
<ul class="jrnl__latestList">
<?php for($i=0;$i<8;$i++){ ?>
<li class="jrnl__latestItem">
<a href="#">
<span class="jrnl__latestTitle">최신 기사 제목 자리 <?php echo $i+1; ?></span>
<span class="jrnl__latestDate">YYYY-MM-DD</span>
</a>
</li>
<?php } ?>
</ul>
</aside>
</div>
</section>
<!-- THIS ISSUE -->
<section class="jrnl__issue">
<div class="jrnl__container">
<div class="jrnl__sectionHead">
<h2>VOL. 384 | Monthly Laser Technology</h2>
<a href="#">과월호 보기</a>
</div>
<div class="jrnl__cardGrid" data-slot="issue-picks">
<?php for($i=0;$i<6;$i++){ ?>
<article class="jrnl__card">
<div class="jrnl__thumb">
<img src="<?php echo G5_THEME_URL ?>/assets/img/card-placeholder.svg" alt="thumb" />
</div>
<div class="jrnl__body">
<p class="jrnl__tag">Editors Pick</p>
<h3 class="jrnl__title">이번 호 추천 기사 <?php echo $i+1; ?></h3>
<p class="jrnl__excerpt">요약문 자리(2~3줄)</p>
<div class="jrnl__meta"><span>YYYY-MM-DD</span><span>Category</span></div>
</div>
</article>
<?php } ?>
</div>
</div>
</section>
<!-- SECTIONS -->
<section class="jrnl__sections">
<div class="jrnl__container">
<?php
$sections = array(
array('id'=>'focus', 'title'=>'포커스', 'sub'=>'Industry Focus'),
array('id'=>'interview', 'title'=>'인터뷰', 'sub'=>'Interviews'),
array('id'=>'market', 'title'=>'레이저시장', 'sub'=>'Laser Market'),
array('id'=>'trend', 'title'=>'관련산업동향', 'sub'=>'Industry Trend'),
array('id'=>'photo', 'title'=>'포토이슈', 'sub'=>'Photo Issue'),
array('id'=>'tech', 'title'=>'신기술', 'sub'=>'New Technology'),
array('id'=>'product', 'title'=>'신제품', 'sub'=>'New Products'),
);
?>
<?php foreach($sections as $sec){ ?>
<div class="jrnl__block" data-slot="section-<?php echo $sec['id']; ?>">
<div class="jrnl__sectionHead">
<div>
<h2><?php echo $sec['title']; ?></h2>
<p class="jrnl__sectionSub"><?php echo $sec['sub']; ?></p>
</div>
<a href="#">더보기</a>
</div>
<div class="jrnl__cardGrid jrnl__cardGrid--3">
<?php for($i=0;$i<3;$i++){ ?>
<article class="jrnl__card">
<div class="jrnl__thumb">
<img src="<?php echo G5_THEME_URL ?>/assets/img/card-placeholder.svg" alt="thumb" />
</div>
<div class="jrnl__body">
<p class="jrnl__tag"><?php echo $sec['title']; ?></p>
<h3 class="jrnl__title"><?php echo $sec['title']; ?> 기사 <?php echo $i+1; ?></h3>
<p class="jrnl__excerpt">요약문 자리(2~3줄)</p>
<div class="jrnl__meta"><span>YYYY-MM-DD</span><span><?php echo $sec['sub']; ?></span></div>
</div>
</article>
<?php } ?>
</div>
</div>
<?php } ?>
</div>
</section>
</main>
PHP
);
put_file($themeRoot.'/parts/footer/footer.php', <<<'PHP'
<?php
if (!defined('_GNUBOARD_')) exit;
?>
<footer class="jrnl" id="jrnl-footer">
<div class="jrnl__container">
<section class="jrnl__cta">
<h3>정기 구독 신청 & 광고 게재 문의</h3>
<p>월간 레이저 기술 / 레이저 뉴스 / 신기술&신제품</p>
<a class="jrnl__btn" href="#">문의하기</a>
</section>
<div class="jrnl__footerMeta">
<a href="<?php echo G5_BBS_URL ?>/content.php?co_id=privacy">개인정보처리방침</a>
<a href="<?php echo G5_BBS_URL ?>/content.php?co_id=provision">이용약관</a>
<p class="jrnl__copy">© <?php echo date('Y'); ?> LaserWorld</p>
</div>
</div>
</footer>
PHP
);
// ---------------------------
// 3) assets: css/js/img(svg)
// ---------------------------
put_file($themeRoot.'/assets/css/journal.base.css', <<<'CSS'
.jrnl { color:#111; font-family: system-ui, -apple-system, Segoe UI, Roboto, "Noto Sans KR", sans-serif; }
.jrnl * { box-sizing: border-box; }
.jrnl a { color: inherit; text-decoration: none; }
.jrnl img { max-width:100%; display:block; }
.jrnl__container { width: min(1200px, calc(100% - 32px)); margin:0 auto; }
.jrnl__btn { display:inline-block; padding:10px 14px; border:1px solid #111; background:#111; color:#fff; border-radius:10px; }
CSS
);
put_file($themeRoot.'/assets/css/journal.layout.css', <<<'CSS'
#jrnl-header .jrnl__topbar { background:#f6f6f6; font-size:14px; }
#jrnl-header .jrnl__utility { display:flex; gap:12px; justify-content:flex-end; padding:8px 0; }
#jrnl-header .jrnl__gnb { border-bottom:1px solid #eee; background:#fff; }
#jrnl-header .jrnl__gnbRow { display:flex; align-items:center; gap:16px; padding:14px 0; }
#jrnl-header .jrnl__nav { display:flex; gap:14px; margin-left:auto; }
#jrnl-header .jrnl__menuBtn { display:none; margin-left:auto; }
#jrnl-main .jrnl__hero { padding:22px 0; background:#0b0f17; border-bottom:1px solid #111; }
#jrnl-main .jrnl__heroGrid { display:grid; grid-template-columns: 1.6fr 1fr; gap:18px; }
#jrnl-main .jrnl__heroCard { background:#101827; border:1px solid rgba(255,255,255,.08); border-radius:12px; overflow:hidden; color:#fff; }
#jrnl-main .jrnl__heroThumb { background:#05070c; }
#jrnl-main .jrnl__heroBody { padding:16px; }
#jrnl-main .jrnl__kicker { margin:0 0 6px; font-size:12px; letter-spacing:.08em; color:rgba(255,255,255,.7); }
#jrnl-main .jrnl__heroTitle { margin:0 0 8px; font-size:28px; line-height:1.2; }
#jrnl-main .jrnl__heroLead { margin:0 0 10px; color:rgba(255,255,255,.85); }
#jrnl-main .jrnl__meta { display:flex; gap:10px; font-size:12px; color:rgba(255,255,255,.7); }
#jrnl-main .jrnl__btn { background:#fff; color:#111; border-color:#fff; }
#jrnl-main .jrnl__latest { background:#0f172a; border:1px solid rgba(255,255,255,.08); border-radius:12px; padding:16px; color:#fff; }
#jrnl-main .jrnl__latestList { list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:10px; }
#jrnl-main .jrnl__latestItem a { display:flex; justify-content:space-between; gap:12px; color:#fff; }
#jrnl-main .jrnl__latestTitle { color:rgba(255,255,255,.9); }
#jrnl-main .jrnl__latestDate { color:rgba(255,255,255,.6); font-size:12px; }
#jrnl-main .jrnl__issue { padding:28px 0; background:#fff; }
#jrnl-main .jrnl__sections { padding:10px 0 40px; background:#fff; }
#jrnl-main .jrnl__block { padding:22px 0; border-top:1px solid #eee; }
#jrnl-main .jrnl__sectionHead { display:flex; align-items:flex-end; justify-content:space-between; gap:12px; margin-bottom:12px; }
#jrnl-main .jrnl__sectionSub { margin:0; color:#666; font-size:13px; }
#jrnl-main .jrnl__cardGrid { display:grid; grid-template-columns: repeat(3, 1fr); gap:16px; }
#jrnl-main .jrnl__card { border:1px solid #eee; border-radius:12px; overflow:hidden; background:#fff; }
#jrnl-main .jrnl__thumb { background:#f2f2f2; }
#jrnl-main .jrnl__body { padding:14px; }
#jrnl-main .jrnl__tag { margin:0 0 6px; font-size:12px; color:#666; }
#jrnl-main .jrnl__title { margin:0 0 8px; font-size:18px; line-height:1.3; }
#jrnl-main .jrnl__excerpt { margin:0 0 10px; color:#444; font-size:14px; line-height:1.5; }
#jrnl-main .jrnl__meta { display:flex; gap:10px; font-size:12px; color:#777; }
#jrnl-footer { border-top:1px solid #eee; background:#fff; padding:26px 0; }
#jrnl-footer .jrnl__cta { border:1px solid #eee; border-radius:12px; padding:16px; background:#fafafa; margin-bottom:14px; }
#jrnl-footer .jrnl__footerMeta { display:flex; flex-wrap:wrap; gap:12px; align-items:center; justify-content:space-between; }
#jrnl-footer .jrnl__copy { margin:0; color:#666; font-size:13px; }
@media (max-width: 1024px) {
#jrnl-main .jrnl__heroGrid { grid-template-columns: 1fr; }
#jrnl-main .jrnl__cardGrid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
#jrnl-header .jrnl__nav { display:none; }
#jrnl-header .jrnl__menuBtn { display:inline-block; }
#jrnl-main .jrnl__cardGrid { grid-template-columns: 1fr; }
}
CSS
);
put_file($themeRoot.'/assets/js/journal.base.js', <<<'JS'
(function () {
const btn = document.querySelector('[data-jrnl="menu-toggle"]');
const nav = document.querySelector('[data-jrnl="nav"]');
if (!btn || !nav) return;
btn.addEventListener('click', () => {
const isOpen = nav.style.display === 'flex';
nav.style.display = isOpen ? 'none' : 'flex';
nav.style.flexDirection = 'column';
nav.style.gap = '10px';
});
})();
JS
);
// 로고 SVG
put_file($themeRoot.'/assets/img/logo.svg', <<<'SVG'
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="32" viewBox="0 0 160 32">
<rect width="160" height="32" rx="8" fill="#111"/>
<text x="12" y="21" fill="#fff" font-size="16" font-family="Arial">LaserWorld</text>
</svg>
SVG
);
// 메인 히어로 이미지 (SVG)
put_file($themeRoot.'/assets/img/hero-laser.svg', <<<'SVG'
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="675" viewBox="0 0 1200 675">
<defs>
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#0b0f17"/>
<stop offset="1" stop-color="#111c33"/>
</linearGradient>
<radialGradient id="g2" cx="70%" cy="35%" r="60%">
<stop offset="0" stop-color="#66e3ff" stop-opacity="0.55"/>
<stop offset="0.55" stop-color="#2b5cff" stop-opacity="0.18"/>
<stop offset="1" stop-color="#0b0f17" stop-opacity="0"/>
</radialGradient>
<filter id="glow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="6" result="b"/>
<feMerge>
<feMergeNode in="b"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<rect width="1200" height="675" fill="url(#g1)"/>
<rect width="1200" height="675" fill="url(#g2)"/>
<!-- grid -->
<g opacity="0.22">
<path d="M0 560 H1200" stroke="#2a3b5f"/>
<path d="M0 470 H1200" stroke="#2a3b5f"/>
<path d="M0 380 H1200" stroke="#2a3b5f"/>
<path d="M0 290 H1200" stroke="#2a3b5f"/>
<path d="M0 200 H1200" stroke="#2a3b5f"/>
<path d="M150 0 V675" stroke="#2a3b5f"/>
<path d="M330 0 V675" stroke="#2a3b5f"/>
<path d="M510 0 V675" stroke="#2a3b5f"/>
<path d="M690 0 V675" stroke="#2a3b5f"/>
<path d="M870 0 V675" stroke="#2a3b5f"/>
<path d="M1050 0 V675" stroke="#2a3b5f"/>
</g>
<!-- laser line -->
<g filter="url(#glow)">
<path d="M120 470 C 380 300, 540 420, 760 270 C 900 175, 1030 190, 1120 140"
stroke="#7bf4ff" stroke-width="6" fill="none" opacity="0.95"/>
<path d="M120 470 C 380 300, 540 420, 760 270 C 900 175, 1030 190, 1120 140"
stroke="#2b5cff" stroke-width="2" fill="none" opacity="0.8"/>
</g>
<!-- sparks -->
<g opacity="0.9">
<circle cx="760" cy="270" r="4" fill="#7bf4ff"/>
<circle cx="770" cy="280" r="2" fill="#7bf4ff"/>
<circle cx="748" cy="262" r="2" fill="#7bf4ff"/>
</g>
<text x="70" y="90" fill="rgba(255,255,255,0.92)" font-size="34" font-family="Arial" font-weight="700">
Laser Industry Journal
</text>
<text x="70" y="132" fill="rgba(255,255,255,0.7)" font-size="18" font-family="Arial">
Latest news + in-depth features, optimized for responsive layouts.
</text>
</svg>
SVG
);
// 카드용 플레이스홀더 이미지 (SVG)
put_file($themeRoot.'/assets/img/card-placeholder.svg', <<<'SVG'
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f3f4f6"/>
<stop offset="1" stop-color="#e5e7eb"/>
</linearGradient>
</defs>
<rect width="960" height="540" fill="url(#bg)"/>
<path d="M0 420 L260 260 L420 370 L630 180 L960 420 V540 H0 Z" fill="#d1d5db"/>
<circle cx="720" cy="160" r="48" fill="#cbd5e1"/>
<text x="40" y="70" fill="#6b7280" font-size="28" font-family="Arial" font-weight="700">Thumbnail</text>
<text x="40" y="110" fill="#6b7280" font-size="16" font-family="Arial">16:9 placeholder</text>
</svg>
SVG
);
// ---------------------------
// 4) 게시판 공용 스킨
// ---------------------------
$skinRoot = $themeRoot.'/skin/board/journal_board';
put_file($skinRoot.'/style.css', <<<'CSS'
.jrnl-board .jrnl-board__head { display:flex; justify-content:space-between; align-items:flex-end; gap:12px; margin:18px 0; }
.jrnl-board .jrnl-board__title { margin:0; font-size:26px; }
.jrnl-board .jrnl-board__list { display:grid; grid-template-columns:repeat(3, 1fr); gap:16px; }
.jrnl-board .jrnl-board__item { border:1px solid #eee; border-radius:12px; overflow:hidden; background:#fff; }
.jrnl-board .jrnl-board__thumb { aspect-ratio:16/9; background:#ddd; overflow:hidden; }
.jrnl-board .jrnl-board__thumb img { width:100%; height:100%; object-fit:cover; }
.jrnl-board .jrnl-board__body { padding:14px; }
.jrnl-board .jrnl-board__meta { font-size:12px; color:#777; display:flex; gap:10px; margin-bottom:8px; }
.jrnl-board .jrnl-board__subject { margin:0 0 8px; font-size:18px; line-height:1.3; }
.jrnl-board .jrnl-board__excerpt { margin:0; color:#444; font-size:14px; line-height:1.5; }
@media (max-width:1024px){
.jrnl-board .jrnl-board__list { grid-template-columns:repeat(2, 1fr); }
}
@media (max-width:640px){
.jrnl-board .jrnl-board__list { grid-template-columns:1fr; }
}
CSS
);
put_file($skinRoot.'/list.skin.php', <<<'PHP'
<?php
if (!defined('_GNUBOARD_')) exit;
add_stylesheet('<link rel="stylesheet" href="'.$board_skin_url.'/style.css?v=1">', 50);
?>
<div class="jrnl jrnl-board" id="jrnl-board-<?php echo $bo_table; ?>">
<div class="jrnl__container">
<div class="jrnl-board__head">
<div>
<h1 class="jrnl-board__title"><?php echo $board['bo_subject']; ?></h1>
</div>
<div class="jrnl-board__actions">
<?php if ($write_href) { ?>
<a class="jrnl__btn" href="<?php echo $write_href; ?>">글쓰기</a>
<?php } ?>
</div>
</div>
<div class="jrnl-board__list">
<?php for ($i=0; $i<count($list); $i++) {
$excerpt = strip_tags($list[$i]['wr_content']);
$excerpt = preg_replace('/\s+/', ' ', $excerpt);
$excerpt = mb_substr($excerpt, 0, 120);
if (mb_strlen($excerpt) >= 120) $excerpt .= '...';
// 썸네일은 연동 시 get_list_thumbnail()로 교체 권장
$fallback = G5_THEME_URL.'/assets/img/card-placeholder.svg';
?>
<article class="jrnl-board__item">
<a href="<?php echo $list[$i]['href']; ?>">
<div class="jrnl-board__thumb">
<img src="<?php echo $fallback; ?>" alt="thumb" />
</div>
<div class="jrnl-board__body">
<div class="jrnl-board__meta">
<span><?php echo date('Y-m-d', strtotime($list[$i]['wr_datetime'])); ?></span>
<span><?php echo $list[$i]['name']; ?></span>
</div>
<h2 class="jrnl-board__subject"><?php echo $list[$i]['subject']; ?></h2>
<p class="jrnl-board__excerpt"><?php echo $excerpt; ?></p>
</div>
</a>
</article>
<?php } ?>
</div>
<div style="margin:18px 0;">
<?php echo $write_pages; ?>
</div>
</div>
</div>
PHP
);
// ---------------------------
// 5) ZIP 생성
// ---------------------------
zip_dir($themeRoot, $root.'/theme-journal.zip');
zip_dir($skinRoot, $root.'/skin-journal_board.zip');
header('Content-Type: text/plain; charset=utf-8');
echo "완료!\n";
echo "- 생성: theme/journal\n";
echo "- 생성: theme-journal.zip\n";
echo "- 생성: skin-journal_board.zip\n";
echo "\n주의: install_journal_theme.php 파일은 실행 후 삭제하세요.\n";
} catch (Exception $e) {
header('Content-Type: text/plain; charset=utf-8');
echo "오류: ".$e->getMessage()."\n";
}