/_preflight.php once, confirm every // row is OK, then DELETE THIS FILE. // // It exists because three host-level facts are unknowable from the repo and every one of // them fails badly on a stock PHP build: // * no argon2 -> src/Security/Password.php calls password_hash(..., PASSWORD_ARGON2ID) // unguarded, and AuthController::DUMMY_HASH runs verify() on EVERY login // attempt including unknown usernames -> the login page fatals for // everyone, instantly. // * no gd -> ImageProcessor::reencode() returns ok=false and IngestController's // `continue` swallows it: events save with no snapshot, API still 201s. // * no pdo_mysql -> every request 500s at Database::connect(). (mysqli being present does // NOT imply pdo_mysql.) // // Deliberately prints booleans only — no phpinfo(), no paths, no config values — so the // window between upload and deletion leaks nothing useful. declare(strict_types=1); header('Content-Type: text/html; charset=utf-8'); header('X-Robots-Tag: noindex, nofollow'); // Find the app root exactly the way public/index.php does — container first, then the bare // directory, probing each level for any marker. Using ANY marker (not all three) is // deliberate here and differs from index.php on purpose: this page exists to DIAGNOSE a // half-finished upload, so it must still locate a root that is missing vendor/ or config.php // and then report precisely which piece is absent. const APP_PRIVATE_DIR = 'move_detect_app'; $root = null; $hasAnyMarker = static fn (string $d): bool => is_file($d . '/config.php') || is_file($d . '/vendor/autoload.php') || is_file($d . '/src/Config.php'); for ($d = dirname(__DIR__), $i = 0; $i < 4; $i++) { foreach ([$d . '/' . APP_PRIVATE_DIR, $d] as $cand) { if ($hasAnyMarker($cand)) { $root = $cand; break 2; } } if (dirname($d) === $d) break; $d = dirname($d); } // Nothing found anywhere IS the diagnosis: fall back to where the files SHOULD be, so every // row below reports a truthful "missing" instead of crashing on null. $root = $root ?? dirname(dirname(__DIR__)) . '/' . APP_PRIVATE_DIR; $checks = [ 'PHP >= 8.2' => [ PHP_VERSION_ID >= 80200, 'Cafe24 관리자에서 PHP 버전을 8.2 이상으로 올리세요. (주의: 카페24는 PHP 버전 변경 시 데이터와 DB가 초기화된다고 안내합니다. 반드시 업로드 전에 정하세요.)', ], 'pdo_mysql 확장' => [ in_array('mysql', PDO::getAvailableDrivers(), true), 'DB 연결이 전혀 안 됩니다. 카페24에 pdo_mysql 활성화를 요청하세요.', ], 'gd 확장' => [ function_exists('imagecreatefromstring') && function_exists('imagejpeg'), '사진(스냅샷)이 조용히 사라집니다. 이벤트는 저장되는데 사진만 없어집니다. 카페24에 gd 활성화를 요청하세요.', ], 'Argon2id 암호 해시' => [ defined('PASSWORD_ARGON2ID') && in_array('argon2id', password_algos(), true), '로그인 페이지가 즉시 죽습니다. 이게 가장 흔한 배포 실패 원인입니다. 카페24에 Argon2 포함 PHP 빌드를 요청하세요.', ], 'config.php 있음' => [ is_readable($root . '/config.php'), 'FTP 최상위(www 밖)에 config.php 를 올리세요.', ], // Checks the parsed VALUES, not the file text. Grepping the raw file for 'CHANGEME' // would also match the template's explanatory comments, which survive a correct edit — // so a properly-filled config would report 실패 forever. 'config.php 를 수정했음' => (function () use ($root) { $fix = 'config.php 안의 CHANGEME 를 실제 DB 이름/아이디/비밀번호로 바꾸세요.'; if (!is_readable($root . '/config.php')) { return [false, $fix]; } $cfg = @require $root . '/config.php'; if (!is_array($cfg)) { return [false, 'config.php 가 PHP 배열을 return 하지 않습니다. 파일이 깨졌는지 확인하세요.']; } foreach (['db_dsn', 'db_user', 'db_pass'] as $k) { if (str_contains((string)($cfg[$k] ?? ''), 'CHANGEME')) { return [false, $fix]; } } return [true, '']; })(), 'storage/snapshots 쓰기 가능' => [ is_dir($root . '/storage/snapshots') && is_writable($root . '/storage/snapshots'), '사진이 조용히 사라집니다. 폴더를 만들고 권한 750 으로 주세요.', ], 'src/ 있음' => [ is_readable($root . '/src/Config.php'), 'src/ 폴더를 FTP 최상위(www 밖)에 올리세요.', ], 'vendor/ 오토로더 있음' => [ is_readable($root . '/vendor/autoload.php'), 'vendor/ 폴더를 FTP 최상위(www 밖)에 올리세요. 없으면 모든 페이지가 죽습니다.', ], // These probe THIS directory (the one the web serves), not $root. If the secrets turned // up here, they are downloadable. 'src/ 가 웹에 노출되지 않음' => [ !is_readable(__DIR__ . '/src/Config.php'), 'src/ 가 웹에서 접근 가능한 폴더 안에 있습니다. FTP 최상위(www 밖)로 옮기세요.', ], 'config.php 가 웹에 노출되지 않음' => [ !is_readable(__DIR__ . '/config.php'), 'config.php 가 웹에서 접근 가능한 폴더 안에 있습니다. DB 비밀번호가 노출됩니다. FTP 최상위(www 밖)로 옮기세요.', ], // Reports the LIVE mode rather than trusting the build. The package's chmod 600 cannot // survive a zip round-trip or an FTP upload (neither carries unix modes), so the only // honest place to check this is here, on the host. 0077 — NOT 0o077, which is PHP 8.1+ // syntax and would make this file fail to parse on the very old build we want to warn about. 'config.php 권한이 600' => [ is_readable($root . '/config.php') && (fileperms($root . '/config.php') & 0077) === 0, 'FTP 클라이언트에서 config.php 권한을 600 으로 바꾸세요. DB 비밀번호가 들어있습니다.', ], 'HTTPS 로 접속됨' => [ (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['SERVER_PORT'] ?? '') === '443' || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'), '세션 쿠키가 cookie_secure=true 라서 HTTP 로는 로그인이 안 됩니다. 카페24에서 무료 SSL 을 켜세요.', ], ]; $failed = array_keys(array_filter($checks, fn($c) => !$c[0])); ?> move-detect 설치 점검

move-detect 설치 점검

[$ok, $fix]): ?>