회원가입 | 고객센터 |
DESIGNONEX
DesignOneX CMS
쇼핑몰
자유게시판
로그인 회원가입
고객센터

언어 자동 체크 기능 코드 블럭

A Administrator
2026.08.11 01:27 2 0
자동으로 코드를  체크하는 기능
 
<?php
/**
 * DXCMS
 * Copyright (C) 2026 designonex [디자인원엑스]
 * License: GNU Lesser General Public License v3.0 (LGPL-3.0)
 * https://designonex.com/dxcms-manual/view/1776622530771477
 */
/**
 * DesignOneX CMS v1.5.0 — 단일 진입점
 * =============================================================
 * PHP 5.6~8.x / IIS·Apache·Nginx·저가형 웹호스팅 완전 호환
 * Windows·Linux 공통
 *
 * ▶ 보안 패치: core/Secure.php 한 파일만 수정하면 됩니다.
 *   다른 파일은 보안과 무관합니다.
 * =============================================================
 */

// ── 출력 버퍼링 ─────────────────────────────────────────────
// IIS/CGI/웹호스팅에서 header()가 "headers already sent" 없이 동작하도록 보장
ob_start();

// ── 공유호스팅 호환 설정 ────────────────────────────────────
@ini_set('pcre.backtrack_limit', '10000000');
@ini_set('pcre.recursion_limit', '10000000');
@ini_set('memory_limit', '256M');

// ── REQUEST_URI 이중 슬래시 정규화 (301 리다이렉트) ──────────
// dx_request_uri()가 PHP 레벨에서 정규화하지만,
// 브라우저 주소창에 //가 보이는 경우를 위해 원본 REQUEST_URI 기준으로 리다이렉트
if (!empty($_SERVER['REQUEST_URI'])) {
    $_dxRawPath  = strtok($_SERVER['REQUEST_URI'], '?');
    $_dxRawQuery = strpos($_SERVER['REQUEST_URI'], '?') !== false
                 ? substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], '?')) : '';
    if (strpos($_dxRawPath, '//') !== false) {
        if (!headers_sent()) {
            header('Location: ' . preg_replace('#/+#', '/', $_dxRawPath) . $_dxRawQuery, true, 301);
        }
        exit;
    }
}

// ── PHP 버전 체크 ────────────────────────────────────────────
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
    header('Content-Type: text/html; charset=UTF-8');
    die('<h2 style="font-family:sans-serif;color:#dc2626">PHP 5.6 이상이 필요합니다. 현재: ' . PHP_VERSION . '</h2>');
}

// ── 핵심 상수 ────────────────────────────────────────────────
define('DX_CMS',     true);
define('DX_VERSION', '8.8.0');
define('DX_ROOT',    str_replace('\\', '/', dirname(__FILE__)));
define('DX_CORE',    DX_ROOT . '/core');
define('DX_DATA',    DX_ROOT . '/data');
define('DX_THEMES',  DX_ROOT . '/themes');
define('DX_PLUGINS', DX_ROOT . '/plugins');
define('DX_PAGES',   DX_ROOT . '/pages');
define('DX_BOARDS',  DX_ROOT . '/boards');
define('DX_EXTEND',  DX_ROOT . '/extend');
define('DX_START',   microtime(true));
define('DX_START_TIME', DX_START);

// ── 설치 여부 확인 ───────────────────────────────────────────
$_dxConfigFile = DX_ROOT . '/data/config.php';
$_dxInstalled  = file_exists($_dxConfigFile);

// ── 에러 설정 ─────────────────────────────────────────────────


if ($_dxInstalled) {
    // 운영: 화면 숨김, 파일 로그
    error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
    @ini_set('display_errors', 0);
    @ini_set('log_errors', 1);
    @ini_set('error_log', DX_ROOT . '/data/error.log');
} else {
    // 미설치: 화면 표시 (설치 디버깅용)
    error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
    @ini_set('display_errors', 1);
}

// ── 미설치 → install/ 이동 ───────────────────────────────────
if (!$_dxInstalled) {
    $_dxScheme = (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off')
              || (!empty($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443)
        ? 'https' : 'http';
    $_dxHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
    $_dxDir  = '';
    if (isset($_SERVER['SCRIPT_NAME'])) {
        $_d = dirname(str_replace('\\', '/', $_SERVER['SCRIPT_NAME']));
        if ($_d !== '.' && $_d !== '/') $_dxDir = rtrim($_d, '/');
    }
    if (!headers_sent()) {
        header('Location: ' . $_dxScheme . '://' . $_dxHost . $_dxDir . '/install/');
    }
    exit;
}

// ── HTTPS 감지 (IIS·리버스프록시·Cloudflare 포함) ───────────
$_dxIsHttps = (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off')
           || (!empty($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443)
           || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https')
           || (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && strtolower($_SERVER['HTTP_X_FORWARDED_SSL']) === 'on')
           || (!empty($_SERVER['HTTP_CF_VISITOR']) && strpos($_SERVER['HTTP_CF_VISITOR'], 'https') !== false);

// ── DX_SECURITY_PATH 선취 파싱 ──────────────────────────────
// config.php는 STEP3에서 정식 로드되지만,
// Secure.php 로드에 필요한 DX_SECURITY_PATH 상수만 먼저 확보
// config.php 텍스트에서 define('DX_SECURITY_PATH', ...) 라인만 정규식으로 파싱
$_dxSecurityPathValue = '';
if ($_dxInstalled) {
    $_dxCfgRaw = @file_get_contents($_dxConfigFile);
    if ($_dxCfgRaw !== false) {
        $_dxSpPat = "/define\\s*\\(\\s*[\\x22\\x27]DX_SECURITY_PATH[\\x22\\x27]\\s*,\\s*[\\x22\\x27]([a-f0-9]{16})[\\x22\\x27]\\s*\\)/";
        if (preg_match($_dxSpPat, $_dxCfgRaw, $_dxSpM)) {
            $_dxSecurityPathValue = $_dxSpM[1];
        }
    }
    unset($_dxCfgRaw, $_dxSpM);
}

// ═══════════════════════════════════════════════════════════
// [STEP 1] 클래스/함수 정의 파일 로드 — 실행 없이 정의만
// ═══════════════════════════════════════════════════════════
require_once DX_CORE . '/functions.php';
require_once DX_CORE . '/DxCache.php';        // ← 성능 최적화: 최우선 로드 (config.php 캐싱에 필요)

// ── Secure.php: 설치 시 생성된 고유 경로에서 로드 ──────────
// DX_SECURITY_PATH = 설치 시 생성된 16자리 해시 (data/config.php에 정의됨)
// 경로: core/security/{hash}/Secure.php
// 폴백: 경로 미정의 또는 파일 없으면 원본 core/Secure.php 사용
if ($_dxSecurityPathValue) {
    $_dxSecurePath = DX_CORE . '/security/' . $_dxSecurityPathValue . '/Secure.php';
    if (!file_exists($_dxSecurePath)) {
        $_dxSecurePath = DX_CORE . '/Secure.php';  // 파일 손실 시 폴백
    }
} else {
    $_dxSecurePath = DX_CORE . '/Secure.php';  // 신규 설치 전 또는 구버전 호환
}
require_once $_dxSecurePath;  // ◀ 보안 전담 클래스
require_once DX_CORE . '/DxSanitizer.php';
require_once DX_CORE . '/db/Database.php';
require_once DX_CORE . '/hook/HookManager.php';
require_once DX_CORE . '/PluginRegistry.php';
require_once DX_CORE . '/auth/Auth.php';
require_once DX_CORE . '/DxPoint.php';
require_once DX_CORE . '/DxFriend.php';
require_once DX_CORE . '/DxShop.php';
require_once DX_CORE . '/DxSite.php';
require_once DX_CORE . '/DxTheme.php';
// DxCache는 STEP1 최상단에서 로드됨 (중복 제거)
require_once DX_CORE . '/DxSeo.php';
require_once DX_CORE . '/DxCategory.php';
require_once DX_CORE . '/DxThumb.php';
require_once DX_CORE . '/DxNotification.php';
require_once DX_CORE . '/DxMemberMonitor.php';
require_once DX_CORE . '/DxPopup.php';
require_once DX_CORE . '/DxBoardSkin.php';
require_once DX_CORE . '/DxMarket.php';
if (file_exists(DX_CORE . '/DxSocialAuth.php')) require_once DX_CORE . '/DxSocialAuth.php';
require_once DX_CORE . '/DxExtend.php';
require_once DX_CORE . '/router/Router.php';
require_once DX_CORE . '/router/Dispatcher.php';
// ── v6.2.0: 라라벨 스타일 확장 ──────────────────────────────
require_once DX_CORE . '/db/QueryBuilder.php';   // QueryBuilder + dx_db()
require_once DX_CORE . '/DxContainer.php';        // DI 컨테이너
require_once DX_CORE . '/DxRouter.php';           // 클래스 기반 라우터

// ═══════════════════════════════════════════════════════════
// [STEP 2] 보안 초기화 — Secure.php가 전담
//          세션 설정 → 세션 시작 → 보안 헤더
// ═══════════════════════════════════════════════════════════
$_dxSecure = Secure::getInstance();

// 세션 설정 (session_start() 전)
$_dxSecure->initSession($_dxIsHttps);

// 세션 시작 — 읽기 전용 요청 최적화
// GET 요청이고 세션 쿠키 없으면 세션 시작 안 함 → 파일락 제거, 성능 향상
// view 페이지는 조회수 중복 방지를 위해 세션 필요 → 스킵 대상 제외
// api/ 요청은 로그인 여부 확인을 위해 세션 필요 → 스킵 대상 제외
$_dxNeedSession = true;
if (
    isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET' &&
    empty($_COOKIE[session_name()]) &&
    !isset($_SERVER['HTTP_X_REQUESTED_WITH'])
) {
    $_dxUri = dx_request_uri();
    // strpos() 가 0 을 반환하면 PHP 에서 false 로 처리되는 버그 방지
    // 반드시 !== false 로 비교해야 함 (루트 설치 시 /admin/ → strpos = 0)
    if (strpos($_dxUri, '/admin') !== false
        || strpos($_dxUri, '/auth')  !== false
        || strpos($_dxUri, '/view/') !== false
        || strpos($_dxUri, '/api/')  !== false
        || strpos($_dxUri, '/write') !== false
        || strpos($_dxUri, '/edit')  !== false
        || strpos($_dxUri, '/reply') !== false
    ) {
        // 세션 필요한 경로 → $_dxNeedSession = true 유지 (기본값)
    } else {
        $_dxNeedSession = false;
    }
}
if ($_dxNeedSession) {
    $_dxSecure->startSession();
}

// 보안 헤더 발행 (.htaccess/web.config 없는 저가형 호스팅도 커버)
$_dxSecure->sendSecurityHeaders();

// CSRF 토큰 선제 발급 (세션 있을 때만)
if ($_dxNeedSession) {
    $_dxSecure->csrfToken();
}

// 전역 참조용 (functions.php의 dx_csrf_token(), dx_csrf_field(), dx_csrf_check() 가 사용)
// Secure::getInstance()를 통해 위임 — 기존 코드 호환 유지

// ═══════════════════════════════════════════════════════════
// [STEP 3] DB 연결 + 설정 로드
//          config.php 내에서 $db->connect() 실행
// ═══════════════════════════════════════════════════════════
require_once $_dxConfigFile;
// config.php에 정의되지 않은 경우 폴백 (config.php가 먼저 define하므로 중복 경고 없음)
if (!defined('DX_DEBUG'))         define('DX_DEBUG', false);
if (!defined('DX_SECURITY_PATH')) define('DX_SECURITY_PATH', '');

// 디버그 바 (DX_DEBUG=true + 관리자 전용) — DX_DEBUG 확정 이후 로드
require_once DX_CORE . '/DxDebugBar.php';

// ═══════════════════════════════════════════════════════════
// [STEP 3-B] 시크릿 키 주입 — DB 설정 로드 직후
// secret_key (사이트마다 다른 64자리 랜덤) 기반으로
// 세션/CSRF/RateLimit 키 이름을 동적으로 도출
// → 소스코드 공개해도 키 이름 예측 불가
// ═══════════════════════════════════════════════════════════
$_dxSecretVal = dx_config('secret_key', '');

// ── 기존 설치 호환: config.php에 DX_SECRET_KEY 없으면 자동 추가 ──
// 성능 최적화: define 여부를 먼저 확인하여 불필요한 file_get_contents/file_put_contents 방지
if (!defined('DX_SECRET_KEY')) {
    if ($_dxSecretVal) {
        define('DX_SECRET_KEY', $_dxSecretVal);
        // config.php에 영구 추가 (다음 요청부터 define으로 즉시 사용)
        // strpos로 텍스트를 먼저 확인하되, file_get_contents는 1회만 실행
        $_dxCfgPath = DX_ROOT . '/data/config.php';
        if (is_writable($_dxCfgPath)) {
            $_dxCfgContent = file_get_contents($_dxCfgPath);
            if ($_dxCfgContent !== false && strpos($_dxCfgContent, 'DX_SECRET_KEY') === false) {
                $appendLine = PHP_EOL . "define('DX_SECRET_KEY', '" . addslashes($_dxSecretVal) . "');" . PHP_EOL;
                file_put_contents($_dxCfgPath, $_dxCfgContent . $appendLine);
            }
            unset($_dxCfgContent);
        }
    }
}

$_dxSecure->initSecretKeys($_dxSecretVal);

// ═══════════════════════════════════════════════════════════
// [STEP 4] DB 연결 후 초기화
// ═══════════════════════════════════════════════════════════
HookManager::getInstance();
PluginRegistry::getInstance();
load_plugins();
_dx_register_point_hooks();

// ── 보안 초기화: WAF + IP 차단 + 봇 탐지 ──────────────────
// Secure::initSecurity() 는 load_plugins() 이후 실행해야
// 플러그인이 wafExcludeFields 등을 훅으로 추가할 수 있음.
Secure::getInstance()->initSecurity();

// 멀티사이트: DxSite가 도메인별 설정을 $dx_config에 덮어씀
DxSite::getInstance();

// 테마 엔진: DxSite 이후 (테마 확정 뒤)
DxTheme::getInstance();

// 인증: DB 연결 후 세션 검증
Auth::getInstance();
// ── v6.2.0: DI 컨테이너 초기화 ─────────────────────────────
DxContainer::getInstance()->registerCoreServices();

// extend/ 폴더 자동 생성
DxExtend::getInstance()->ensureDirs();

// 팝업 자동 출력 훅 등록
dx_add_hook('dx_body_bottom', function() {
    DxPopup::render();
}, 50);

// ── 회원 모니터링 훅 등록 ──────────────────────────────────
// 로그인 → onLogin
dx_add_hook('dx_after_login', function($args) {
    $user = isset($args['user']) ? $args['user'] : null;
    if ($user && isset($user['id'])) {
        DxMemberMonitor::onLogin((int)$user['id']);
    }
}, 20);

// 로그아웃 → onLogout
dx_add_hook('dx_after_logout', function($args) {
    $user = isset($args['user']) ? $args['user'] : null;
    if ($user && isset($user['id'])) {
        DxMemberMonitor::onLogout((int)$user['id']);
    }
}, 20);

// 매 요청 → last_seen 갱신 (로그인 상태일 때만)
if (Auth::getInstance()->isLoggedIn()) {
    DxMemberMonitor::onRequest((int)Auth::getInstance()->get('id', 0));
}

// extend/top/ 실행 (모든 초기화 완료 직후)
DxExtend::getInstance()->runTop(array(
    'version' => DX_VERSION,
    'path'    => dx_request_uri(),
));

// ═══════════════════════════════════════════════════════════
// [STEP 5] 라우팅 + 디스패치
//          모든 초기화 완료 후 실행
// ═══════════════════════════════════════════════════════════
// ── v6.2.0: routes/ 폴더 자동 로드 ─────────────────────────
$_dxRouteDir = DX_ROOT . '/routes';
if (is_dir($_dxRouteDir)) {
    $_dxRouteFiles = glob($_dxRouteDir . '/*.php');
    if ($_dxRouteFiles) {
        sort($_dxRouteFiles);
        foreach ($_dxRouteFiles as $_dxRouteFile) {
            require_once $_dxRouteFile;
        }
    }
}
unset($_dxRouteDir, $_dxRouteFiles, $_dxRouteFile);

// DxRouter에 매칭된 라우트가 없으면 기존 파일 기반 라우팅으로 폴백
if (!DxRouter::dispatch()) {
    $_dxDispatcher = new Dispatcher(new Router());
    $_dxDispatcher->dispatch();
}

// extend/bottom/ 실행 (렌더링 완료 후)
DxExtend::getInstance()->runBottom(array(
    'elapsed' => round((microtime(true) - DX_START) * 1000, 2),
));

// 출력 버퍼 최종 flush (IIS/CGI 환경 보장)
if (ob_get_level() > 0) {
    ob_end_flush();
}

 

댓글0

로그인 후 댓글을 작성할 수 있습니다.
번호 제목 작성자 날짜 조회
6
Administrator
08.11 2
Administrator · 2
2
Administrator
08.11 2
Administrator · 2
1
Administrator
08.11 2
Administrator · 2
3
전체 회원
35
전체 게시글
6
전체 댓글
3
오늘 방문
163
전체 방문
0
현재 접속
인기글 7일 이내
최신글
최신댓글
목록