0) ? (int)round(($watchTm / $contentTm) * 100) : 0; $gauge = max(0, min(100, $gauge)); $raw = trim((string)($row['content_url'] ?? '')); 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; } else { $videoId = ''; } $thumbFromDb = trim((string)($row['thumbnail_url'] ?? '')); $url = $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : $raw; $thumbnail = $thumbFromDb !== '' ? $thumbFromDb : ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : '/img/video/img_thumb_01.png'); return [ 'id' => $row['content_id'] ?? '', 'content_id' => $row['content_id'] ?? '', 'category_code' => $row['category_code'] ?? '', 'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''), 'subcate' => (string)($row['category_group'] ?? ''), 'title' => $row['title'] ?? '', 'description' => trim((string)($row['description'] ?? '')), 'description2' => trim((string)($row['description2'] ?? '')), 'url' => $raw, 'content_url' => $raw, 'video_url' => $url, 'video_id' => $videoId, 'thumbnail' => $thumbnail, 'watch_tm' => (int)($row['watch_tm'] ?? 0), 'content_tm' => (int)($row['content_tm'] ?? 0), 'all_tm' => (int)$allTm, 'gauge' => $gauge, 'is_bookmarked' => !empty($row['is_bookmarked']), 'bookmark' => !empty($row['is_bookmarked']), 'keywords' => $keywords, ]; } function category_tag_class(string $categoryCode): string { $map = [ 'CA10001' => 'myclass', 'CA10002' => 'onboarding', 'CA10003' => 'legal', 'CA10004' => 'leader', 'CA10005' => 'insight', 'CA10006' => 'biz', ]; return $map[$categoryCode] ?? 'etc'; } try { $pdo = db_conn(); $pdo->exec("SET NAMES 'utf8mb4'"); $tab = trim((string)($_GET['tab'] ?? 'watching')); $page = max(1, (int)($_GET['page'] ?? 1)); $limit = max(1, (int)($_GET['limit'] ?? 12)); $offset = ($page - 1) * $limit; // ★ 선택년도 수집 $selectedYear = trim((string)($_GET['selected_year'] ?? date('Y'))); if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) { $selectedYear = date('Y'); } $CATEGORY_MAP = [ 'CA10001' => '마이클래스', 'CA10002' => '온보딩', 'CA10003' => '법정교육', 'CA10004' => '리더십', 'CA10005' => '인사이트', 'CA10006' => '비즈트렌드', ]; $stmtCode = $pdo->prepare(" SELECT base_code, code_name FROM edu_codes WHERE group_code = 'CA100' AND is_active = '1' ORDER BY code "); $stmtCode->execute(); foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) { $CATEGORY_MAP[$row['base_code']] = $row['code_name']; } // ========================= // 탭 카운트 (선택년도 기준) // ========================= // 시청중인 콘텐츠 카운트 /* * $stmtCountWatching = $pdo->prepare(" SELECT COUNT(*) FROM edu_learning_histories WHERE member_id = :member_id AND sys_comp_code = :sys_comp_code AND watch_tm < content_tm AND YEAR(last_viewed_at) = :selected_year "); */ $stmtCountWatching = $pdo->prepare(" SELECT COUNT(DISTINCT lh.content_id) FROM edu_learning_histories lh WHERE lh.member_id = :member_id AND lh.sys_comp_code = :sys_comp_code AND lh.watch_tm < lh.content_tm AND YEAR(lh.last_viewed_at) = :selected_year AND NOT EXISTS ( SELECT 1 FROM edu_learning_histories lh2 WHERE lh2.content_id = lh.content_id AND lh2.member_id = lh.member_id AND lh2.sys_comp_code = lh.sys_comp_code AND lh2.completed_at IS NOT NULL ) "); $stmtCountWatching->execute([ ':member_id' => $memberId, ':sys_comp_code' => $sysCompCode, ':selected_year' => (int)$selectedYear, ]); $countWatching = (int)$stmtCountWatching->fetchColumn(); // 시청완료 // 기준: // 1) completed_at 이 있는 이력만 완료로 인정 // 2) completed_at 기준 연도 필터 // 3) 같은 콘텐츠는 최신 완료 1건만 카운트 $stmtCountCompleted = $pdo->prepare(" SELECT COUNT(*) FROM ( SELECT content_id, MAX(completed_at) AS latest_completed_at FROM edu_learning_histories WHERE member_id = :member_id AND sys_comp_code = :sys_comp_code AND completed_at IS NOT NULL AND YEAR(completed_at) = :selected_year GROUP BY content_id ) t "); $stmtCountCompleted->execute([ ':member_id' => $memberId, ':sys_comp_code' => $sysCompCode, ':selected_year' => (int)$selectedYear, ]); $countCompleted = (int)$stmtCountCompleted->fetchColumn(); // 저장한 콘텐츠 // ★ 저장한 시점 기준 년도(favorited_at 우선, 없으면 updated_at, created_at fallback) $stmtCountSaved = $pdo->prepare(" SELECT COUNT(DISTINCT content_id) FROM edu_content_wishlist WHERE member_id = :member_id AND sys_comp_code = :sys_comp_code AND is_active = '1' AND YEAR(COALESCE(favorited_at, updated_at, created_at)) = :selected_year "); $stmtCountSaved->execute([ ':member_id' => $memberId, ':sys_comp_code' => $sysCompCode, ':selected_year' => (int)$selectedYear, ]); $countSaved = (int)$stmtCountSaved->fetchColumn(); // ========================= // 탭별 메인 쿼리 // ========================= if ($tab === 'saved') { // 저장한 콘텐츠 : wishlist 기준 $countTotal = $countSaved; $sql = " SELECT c.content_id, c.category_code, c.category_group, c.title, c.description, c.description2, c.content_url, c.thumbnail_url, COALESCE(MAX(lh.watch_tm), 0) AS watch_tm, COALESCE(MAX(lh.content_tm), 0) AS content_tm, COALESCE(MAX(lh.all_tm), 0) AS all_tm, 1 AS is_bookmarked, GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords, MAX(lh.last_viewed_at) AS sort_last_viewed_at, MAX(COALESCE(cw.favorited_at, cw.updated_at, cw.created_at)) AS sort_saved_at FROM edu_content_wishlist cw INNER JOIN edu_contents c ON c.content_id = cw.content_id LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id AND lh.member_id = cw.member_id AND lh.sys_comp_code = cw.sys_comp_code AND YEAR(lh.last_viewed_at) = :selected_year_lh LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id WHERE cw.member_id = :member_id AND cw.sys_comp_code = :sys_comp_code AND cw.is_active = '1' AND YEAR(COALESCE(cw.favorited_at, cw.updated_at, cw.created_at)) = :selected_year_saved GROUP BY c.content_id ORDER BY CASE WHEN MAX(lh.last_viewed_at) IS NULL THEN 1 ELSE 0 END ASC, MAX(lh.last_viewed_at) DESC, sort_saved_at DESC LIMIT :limit OFFSET :offset "; } elseif ($tab === 'completed') { // 시청완료 콘텐츠 completed_at IS NOT NULL (최신 1건) // 기준: // 1) completed_at 이 있는 이력만 완료로 인정 // 2) 선택년도는 completed_at 기준 // 3) 같은 콘텐츠가 여러 번 완료된 경우 최신 완료 1건만 반영 $countTotal = $countCompleted; $sql = " SELECT c.content_id, c.category_code, c.category_group, c.title, c.description, c.description2, c.content_url, c.thumbnail_url, lh.watch_tm, lh.content_tm, lh.all_tm, CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked, GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords, lh.completed_at AS sort_completed_at FROM ( SELECT t1.content_id, t1.member_id, t1.sys_comp_code, t1.watch_tm, t1.content_tm, t1.all_tm, t1.completed_at FROM edu_learning_histories t1 INNER JOIN ( SELECT content_id, MAX(completed_at) AS latest_completed_at FROM edu_learning_histories WHERE member_id = :completed_member_id_1 AND sys_comp_code = :completed_sys_comp_code_1 AND completed_at IS NOT NULL AND YEAR(completed_at) = :completed_selected_year_1 GROUP BY content_id ) t2 ON t1.content_id = t2.content_id AND t1.completed_at = t2.latest_completed_at AND t1.member_id = :completed_member_id_2 AND t1.sys_comp_code = :completed_sys_comp_code_2 ) lh INNER JOIN edu_contents c ON c.content_id = lh.content_id LEFT JOIN edu_content_wishlist cw ON cw.content_id = c.content_id AND cw.member_id = lh.member_id AND cw.sys_comp_code = lh.sys_comp_code AND cw.is_active = '1' LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id GROUP BY c.content_id, c.category_code, c.category_group, c.title, c.description, c.description2, c.content_url, c.thumbnail_url, lh.watch_tm, lh.content_tm, lh.all_tm, cw.content_id, lh.completed_at ORDER BY lh.completed_at DESC LIMIT :limit OFFSET :offset "; } else { // 시청중인 콘텐츠 : watch_tm < content_tm $tab = 'watching'; $countTotal = $countWatching; $sql = " SELECT c.content_id, c.category_code, c.category_group, c.title, c.description, c.description2, c.content_url, c.thumbnail_url, lh.watch_tm, lh.content_tm, lh.all_tm, CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked, -- 기존 코드 리스트 GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords, -- 추가된 코드명 리스트 GROUP_CONCAT(DISTINCT ec.code_name ORDER BY ck.keyword_code SEPARATOR ',') AS keywords_code_name FROM edu_learning_histories lh INNER JOIN edu_contents c ON c.content_id = lh.content_id LEFT JOIN edu_content_wishlist cw ON cw.content_id = c.content_id AND cw.member_id = lh.member_id AND cw.sys_comp_code = lh.sys_comp_code AND cw.is_active = '1' LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id -- [추가] 코드 테이블 조인 LEFT JOIN edu_codes ec ON ec.base_code = ck.keyword_code AND ec.group_code = 'KW100' WHERE lh.member_id = :member_id AND lh.sys_comp_code = :sys_comp_code AND lh.watch_tm < lh.content_tm AND YEAR(lh.last_viewed_at) = :selected_year AND NOT EXISTS ( SELECT 1 FROM edu_learning_histories lh2 WHERE lh2.content_id = lh.content_id AND lh2.member_id = lh.member_id AND lh2.sys_comp_code = lh.sys_comp_code AND lh2.completed_at IS NOT NULL ) GROUP BY c.content_id ORDER BY lh.last_viewed_at DESC LIMIT :limit OFFSET :offset "; /* WHERE lh.member_id = :member_id AND lh.sys_comp_code = :sys_comp_code AND lh.watch_tm < lh.content_tm AND YEAR(lh.last_viewed_at) = :selected_year GROUP BY c.content_id ORDER BY lh.last_viewed_at DESC LIMIT :limit OFFSET :offset ";*/ // $sql = " // SELECT // c.content_id, // c.category_code, // c.title, // c.content_url, // c.thumbnail_url, // lh.watch_tm, // lh.content_tm, // CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked, // GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords // FROM edu_learning_histories lh // INNER JOIN edu_contents c // ON c.content_id = lh.content_id // LEFT JOIN edu_content_wishlist cw // ON cw.content_id = c.content_id // AND cw.member_id = lh.member_id // AND cw.sys_comp_code = lh.sys_comp_code // AND cw.is_active = '1' // LEFT JOIN edu_content_keywords ck // ON ck.content_id = c.content_id // WHERE lh.member_id = :member_id // AND lh.sys_comp_code = :sys_comp_code // AND lh.watch_tm < lh.content_tm // AND YEAR(lh.last_viewed_at) = :selected_year // GROUP BY c.content_id // ORDER BY lh.last_viewed_at DESC // LIMIT :limit OFFSET :offset // "; } $stmt = $pdo->prepare($sql); if ($tab === 'saved') { $stmt->bindValue(':member_id', $memberId, PDO::PARAM_STR); $stmt->bindValue(':sys_comp_code', $sysCompCode, PDO::PARAM_STR); $stmt->bindValue(':selected_year_lh', (int)$selectedYear, PDO::PARAM_INT); $stmt->bindValue(':selected_year_saved', (int)$selectedYear, PDO::PARAM_INT); } elseif ($tab === 'completed') { $stmt->bindValue(':completed_member_id_1', $memberId, PDO::PARAM_STR); $stmt->bindValue(':completed_sys_comp_code_1', $sysCompCode, PDO::PARAM_STR); $stmt->bindValue(':completed_selected_year_1', (int)$selectedYear, PDO::PARAM_INT); $stmt->bindValue(':completed_member_id_2', $memberId, PDO::PARAM_STR); $stmt->bindValue(':completed_sys_comp_code_2', $sysCompCode, PDO::PARAM_STR); } else { $stmt->bindValue(':member_id', $memberId, PDO::PARAM_STR); $stmt->bindValue(':sys_comp_code', $sysCompCode, PDO::PARAM_STR); $stmt->bindValue(':selected_year', (int)$selectedYear, PDO::PARAM_INT); } $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); ob_start(); $videos = []; foreach ($rows as $row) { $item = map_content_row($row, $CATEGORY_MAP); $tagClass = category_tag_class($item['category_code']); $videos[] = $item; $item['url']="#"; //마이페이지용 모달 개발전까지 링크삭제 ?>
  • <?= mypage_h($item['title']) ?>
  • true, 'tab' => $tab, 'page' => $page, 'limit' => $limit, 'selected_year' => $selectedYear, 'total_count' => $countTotal, 'counts' => [ 'watching' => $countWatching, 'completed' => $countCompleted, 'saved' => $countSaved, ], 'videos' => $videos, 'html' => $html, 'is_last' => (($offset + $limit) >= $countTotal), ]); } catch (Throwable $e) { mypage_json([ 'success' => false, 'message' => '콘텐츠 리스트를 불러오지 못했습니다.', 'error' => $e->getMessage(), ]); }