false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE); exit; } $contentId = trim((string)($_GET['content_id'] ?? '')); if ($contentId === '') { http_response_code(400); echo json_encode(['success' => false, 'message' => 'content_id_required'], JSON_UNESCAPED_UNICODE); exit; } try { $pdo = db_conn(); $normCode = static function (string $code): string { return preg_replace('/[^A-Z0-9]/', '', strtoupper(trim($code))); }; $looksLikeCode = static function (string $value): bool { return preg_match('/^[A-Z]{2}[A-Z0-9]{3,}$/', strtoupper(trim($value))) === 1; }; $codeMap = []; try { $stmtCodeMap = $pdo->query("SELECT base_code, code_name FROM edu_codes"); foreach ($stmtCodeMap->fetchAll(PDO::FETCH_ASSOC) as $cr) { $key = $normCode((string)($cr['base_code'] ?? '')); if ($key === '') continue; $codeMap[$key] = (string)($cr['code_name'] ?? ''); } } catch (Throwable $ignore) { $codeMap = []; } // 1) 현재 영상의 키워드 코드 조회 $keywordCodes = []; try { $stmtKw = $pdo->prepare('SELECT keyword_code FROM edu_content_keywords WHERE content_id = ?'); $stmtKw->execute([$contentId]); $keywordCodes = $stmtKw->fetchAll(PDO::FETCH_COLUMN) ?: []; } catch (Throwable $kwErr) { // edu_content_keywords 테이블이 없는 경우 무시 → fallback $keywordCodes = []; } // 키워드 없으면 같은 카테고리 영상으로 fallback if (empty($keywordCodes)) { // 현재 영상의 카테고리 조회 $stmtCat = $pdo->prepare('SELECT category_code FROM edu_contents WHERE content_id = ? LIMIT 1'); $stmtCat->execute([$contentId]); $curCatCode = $stmtCat->fetchColumn(); if (!$curCatCode) { echo json_encode([ 'success' => true, 'keywords' => [], 'videos' => [], ], JSON_UNESCAPED_UNICODE); exit; } $stmtFallback = $pdo->prepare( "SELECT c.content_id, c.title, c.content_url, c.thumbnail_url, c.category_code, c.category_group, c.description, COALESCE(ec.code_name, c.category_code) AS category_name, COALESCE(ec_grp.code_name, c.category_group) AS category_group_name FROM edu_contents c LEFT JOIN edu_codes ec ON TRIM(UPPER(ec.base_code)) = TRIM(UPPER(c.category_code)) LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group)) WHERE c.category_code = ? AND c.content_id != ? AND c.is_active = 1 ORDER BY c.start_date DESC LIMIT 8" ); $stmtFallback->execute([$curCatCode, $contentId]); $rows = $stmtFallback->fetchAll(PDO::FETCH_ASSOC) ?: []; $videos = []; foreach ($rows as $row) { $raw = trim($row['content_url'] ?? ''); $videoId = ''; if (preg_match('/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/', $raw, $m)) { $videoId = $m[1]; } elseif (preg_match('/^[A-Za-z0-9_-]{11}$/', $raw)) { $videoId = $raw; } $thumbFromDb = trim($row['thumbnail_url'] ?? ''); $thumbnail = $thumbFromDb !== '' ? $thumbFromDb : ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : ''); $catCode = $row['category_code'] ?? ''; $subcate = trim((string)($row['category_group_name'] ?? $row['category_group'] ?? '')); $catName = trim((string)($row['category_name'] ?? $catCode)); if (($catName === '' || $looksLikeCode($catName)) && $catCode !== '') { $mapped = $codeMap[$normCode($catCode)] ?? ''; if ($mapped !== '') $catName = $mapped; } $groupRaw = trim((string)($row['category_group'] ?? '')); if (($subcate === '' || $looksLikeCode($subcate)) && $groupRaw !== '') { $mapped = $codeMap[$normCode($groupRaw)] ?? ''; if ($mapped !== '') $subcate = $mapped; } $videos[] = [ 'content_id' => $row['content_id'], 'title' => $row['title'] ?? '', 'content_url' => $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : $raw, 'thumbnail' => $thumbnail, 'category_code' => $catCode, 'category' => $catName, 'category_name' => $catName, 'subcate' => $subcate, 'description' => $row['description'] ?? '', ]; } echo json_encode([ 'success' => true, 'keywords' => [], 'videos' => $videos, ], JSON_UNESCAPED_UNICODE); exit; } // 2) 키워드명 조회 (표시용) $kwPlaceholders = implode(',', array_fill(0, count($keywordCodes), '?')); $keywordNames = []; try { $stmtKwNames = $pdo->prepare( "SELECT keyword_code, keyword_name FROM edu_keywords WHERE keyword_code IN ({$kwPlaceholders})" ); $stmtKwNames->execute($keywordCodes); while ($row = $stmtKwNames->fetch(PDO::FETCH_ASSOC)) { $keywordNames[] = $row['keyword_name']; } } catch (Throwable $kwNameErr) { $keywordNames = []; } // 3) 동일 키워드를 가진 다른 영상 조회 (현재 영상 제외, 랜덤 8개) $rows = []; try { $stmtVideos = $pdo->prepare( "SELECT DISTINCT c.content_id, c.title, c.content_url, c.thumbnail_url, c.category_code, c.category_group, c.description, COALESCE(ec.code_name, c.category_code) AS category_name, COALESCE(ec_grp.code_name, c.category_group) AS category_group_name FROM edu_contents c LEFT JOIN edu_codes ec ON TRIM(UPPER(ec.base_code)) = TRIM(UPPER(c.category_code)) LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group)) INNER JOIN edu_content_keywords ck ON ck.content_id = c.content_id WHERE ck.keyword_code IN ({$kwPlaceholders}) AND c.content_id != ? AND c.is_active = 1 ORDER BY RAND() LIMIT 8" ); $params = array_merge($keywordCodes, [$contentId]); $stmtVideos->execute($params); $rows = $stmtVideos->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $kwVideoErr) { $rows = []; } // 키워드 매칭 결과 없으면 같은 카테고리 fallback if (empty($rows)) { $stmtCat = $pdo->prepare('SELECT category_code FROM edu_contents WHERE content_id = ? LIMIT 1'); $stmtCat->execute([$contentId]); $curCatCode = $stmtCat->fetchColumn(); if ($curCatCode) { $stmtFb = $pdo->prepare( "SELECT c.content_id, c.title, c.content_url, c.thumbnail_url, c.category_code, c.category_group, c.description, COALESCE(ec.code_name, c.category_code) AS category_name, COALESCE(ec_grp.code_name, c.category_group) AS category_group_name FROM edu_contents c LEFT JOIN edu_codes ec ON TRIM(UPPER(ec.base_code)) = TRIM(UPPER(c.category_code)) LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group)) WHERE c.category_code = ? AND c.content_id != ? AND c.is_active = 1 ORDER BY c.start_date DESC LIMIT 8" ); $stmtFb->execute([$curCatCode, $contentId]); $rows = $stmtFb->fetchAll(PDO::FETCH_ASSOC) ?: []; } } $videos = []; foreach ($rows as $row) { $raw = trim($row['content_url'] ?? ''); $videoId = ''; if (preg_match('/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/', $raw, $m)) { $videoId = $m[1]; } elseif (preg_match('/^[A-Za-z0-9_-]{11}$/', $raw)) { $videoId = $raw; } $thumbFromDb = trim($row['thumbnail_url'] ?? ''); $thumbnail = $thumbFromDb !== '' ? $thumbFromDb : ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : ''); $catCode = $row['category_code'] ?? ''; $subcate = trim((string)($row['category_group_name'] ?? $row['category_group'] ?? '')); $catName = trim((string)($row['category_name'] ?? $catCode)); if (($catName === '' || $looksLikeCode($catName)) && $catCode !== '') { $mapped = $codeMap[$normCode($catCode)] ?? ''; if ($mapped !== '') $catName = $mapped; } $groupRaw = trim((string)($row['category_group'] ?? '')); if (($subcate === '' || $looksLikeCode($subcate)) && $groupRaw !== '') { $mapped = $codeMap[$normCode($groupRaw)] ?? ''; if ($mapped !== '') $subcate = $mapped; } $videos[] = [ 'content_id' => $row['content_id'], 'title' => $row['title'] ?? '', 'content_url' => $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : $raw, 'thumbnail' => $thumbnail, 'category_code' => $catCode, 'category' => $catName, 'category_name' => $catName, 'subcate' => $subcate, 'description' => $row['description'] ?? '', ]; } echo json_encode([ 'success' => true, 'keywords' => $keywordNames, 'videos' => $videos, ], JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { error_log('[get_recommend_videos] ' . $e->getMessage()); http_response_code(500); echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE); }