'리더십', ]; $SUBCATE_MAP = [ 'CA200L01' => '리더십 시작하기', 'CA200L02' => '셀프 리더십', 'CA200L03' => '팀 리더십', 'CA200L04' => '실전조직 리더십', 'CA200L05' => '리더케이스탐구', ]; // --------------------------------------------------------- // 공통 함수 // --------------------------------------------------------- function h(?string $str): string { return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function mapContentRow(array $row, array $catMap = [], array $subcateMap = []): array { $kwStr = $row['keywords'] ?? ''; $keywords = $kwStr !== '' ? explode(',', $kwStr) : []; $watchTm = (float)($row['watch_tm'] ?? 0); $contentTm = (float)($row['content_tm'] ?? 0); $gauge = ($contentTm > 0) ? (int)round(($watchTm / $contentTm) * 100) : 0; $gauge = min(100, max(0, $gauge)); $raw = trim($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($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" : ''); $categoryCode = $row['category_code'] ?? ''; $groupCode = $row['category_group'] ?? ''; return [ 'id' => $row['content_id'] ?? '', 'url' => $url, 'thumbnail' => $thumbnail, 'category' => $catMap[$categoryCode] ?? $categoryCode, 'category_code' => $categoryCode, 'subcate' => $subcateMap[$groupCode] ?? $groupCode, 'bookmark' => !empty($row['is_bookmarked']), 'title' => $row['title'] ?? '', 'keywords' => $keywords, 'gauge' => $gauge, 'watch_tm' => (int)($row['watch_tm'] ?? 0), 'content_tm' => (int)($row['content_tm'] ?? 0), 'view_cnt' => (int)($row['view_cnt'] ?? 0), ]; } /** * 디버그용: 바인딩값을 SQL에 치환한 문자열 생성 * 주의: 실제 실행 SQL을 PDO가 제공하는 것은 아니고, 보기 쉽게 만든 디버그용 문자열임 */ function buildDebugSql(string $sql, array $params): string { // :member_id 와 :member_id2 같은 이름 충돌 방지 uksort($params, function ($a, $b) { return strlen((string)$b) <=> strlen((string)$a); }); foreach ($params as $key => $value) { if ($value === null) { $replace = 'NULL'; } elseif (is_int($value) || is_float($value)) { $replace = (string)$value; } else { $replace = "'" . str_replace("'", "''", (string)$value) . "'"; } $sql = str_replace($key, $replace, $sql); } return $sql; } try { $pdo = db_conn(); $pdo->exec("SET NAMES 'utf8mb4'"); // --------------------------------------------------------- // edu_codes 에서 실제 코드맵 조회 // --------------------------------------------------------- $stmtCode = $pdo->query(" SELECT group_code, code, code_name FROM edu_codes WHERE is_active = 1 AND group_code IN ('CA100', 'CA200') "); if ($stmtCode) { foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $cr) { $key = $cr['group_code'] . $cr['code']; if ($cr['group_code'] === 'CA100') { $CATEGORY_MAP[$key] = $cr['code_name']; } elseif ($cr['group_code'] === 'CA200') { $SUBCATE_MAP[$key] = $cr['code_name']; } } } // --------------------------------------------------------- // 이 페이지는 리더십 대분류 전용 // 탭은 category_group 으로 필터링 // category 파라미터는 CA200L01 형태를 권장 // --------------------------------------------------------- $categoryGroup = ''; if ($category !== '' && strtolower($category) !== 'all') { if (preg_match('/^L\d{2}$/', $category)) { $categoryGroup = 'CA200' . $category; // L01 -> CA200L01 } else { $categoryGroup = $category; // 이미 CA200L01이면 그대로 } } // --------------------------------------------------------- // 정렬 조건 // --------------------------------------------------------- switch ($sort) { case 'latest': $orderBy = " COALESCE(c.updated_at, c.created_at) DESC, c.sort_order ASC, c.content_id DESC "; break; case 'seen': $orderBy = " CASE WHEN lh.content_id IS NOT NULL THEN 0 ELSE 1 END ASC, lh.last_viewed_at DESC, c.content_id DESC "; break; case 'unseen': $orderBy = " CASE WHEN lh.content_id IS NULL THEN 0 ELSE 1 END ASC, COALESCE(c.updated_at, c.created_at) DESC, c.content_id DESC "; break; case 'view': default: $orderBy = " COALESCE(vs.view_cnt, 0) DESC, COALESCE(vs.total_all_tm, 0) DESC, COALESCE(c.updated_at, c.created_at) DESC, c.content_id DESC "; break; } // --------------------------------------------------------- // 기본 조건 // --------------------------------------------------------- $where = []; $bind = []; $where[] = "(c.is_active = '1' OR c.is_active = 'Y')"; $where[] = "(c.start_date IS NULL OR c.start_date <= CURDATE())"; $where[] = "(c.end_date IS NULL OR c.end_date >= CURDATE())"; // 리더십 대분류 고정 $where[] = "c.category_code = 'CA10004'"; // 탭 필터 if ($categoryGroup !== '') { $where[] = "c.category_group = :category_group"; $bind[':category_group'] = $categoryGroup; } $whereSql = implode("\n AND ", $where); // --------------------------------------------------------- // TOTAL COUNT // --------------------------------------------------------- $countSql = " SELECT COUNT(*) FROM edu_contents c WHERE {$whereSql} "; $stmtCount = $pdo->prepare($countSql); foreach ($bind as $key => $value) { $stmtCount->bindValue($key, $value, PDO::PARAM_STR); } if ($DEBUG_SQL) { $countDebugSql = buildDebugSql($countSql, $bind); echo '
';
echo "[COUNT SQL]\n\n";
// echo htmlspecialchars($countDebugSql, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo $countDebugSql;
echo "\n\n------------------------------\n\n";
}
$stmtCount->execute();
$totalCount = (int)$stmtCount->fetchColumn();
// ---------------------------------------------------------
// 목록 조회
// ---------------------------------------------------------
$sql = "
SELECT
c.content_id,
c.category_code,
c.category_group,
c.title,
c.content_url,
c.thumbnail_url,
c.sort_order,
c.created_at,
c.updated_at,
lh.content_id AS lh_content_id,
lh.watch_tm,
lh.content_tm,
lh.last_viewed_at,
CASE
WHEN cw.content_id IS NOT NULL THEN 1
ELSE 0
END AS is_bookmarked,
COALESCE(vs.view_cnt, 0) AS view_cnt,
COALESCE(vs.total_all_tm, 0) AS total_all_tm,
GROUP_CONCAT(
DISTINCT ck.keyword_code
ORDER BY ck.keyword_code
SEPARATOR ','
) AS keywords
FROM edu_contents c
LEFT JOIN edu_learning_histories lh
ON lh.content_id = c.content_id
AND lh.member_id = :member_id
AND lh.sys_comp_code = :sys_comp_code
LEFT JOIN edu_content_wishlist cw
ON cw.content_id = c.content_id
AND cw.member_id = :member_id2
AND cw.sys_comp_code = :sys_comp_code2
AND (cw.is_active = '1' OR cw.is_active = 'Y')
LEFT JOIN (
SELECT
content_id,
COUNT(*) AS view_cnt,
COALESCE(SUM(all_tm), 0) AS total_all_tm
FROM edu_learning_histories
GROUP BY content_id
) vs
ON vs.content_id = c.content_id
LEFT JOIN edu_content_keywords ck
ON ck.content_id = c.content_id
WHERE {$whereSql}
GROUP BY c.content_id
ORDER BY {$orderBy}
LIMIT :limit OFFSET :offset
";
/*LIMIT :limit OFFSET :offset*/
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':member_id', $memberId, PDO::PARAM_STR);
$stmt->bindValue(':sys_comp_code', $sysCompCode, PDO::PARAM_STR);
$stmt->bindValue(':member_id2', $memberId, PDO::PARAM_STR);
$stmt->bindValue(':sys_comp_code2', $sysCompCode, PDO::PARAM_STR);
foreach ($bind as $key => $value) {
$stmt->bindValue($key, $value, PDO::PARAM_STR);
}
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
if ($DEBUG_SQL) {
$debugParams = [
':member_id' => $memberId,
':sys_comp_code' => $sysCompCode,
':member_id2' => $memberId,
':sys_comp_code2' => $sysCompCode,
':limit' => $limit,
':offset' => $offset,
];
if (!empty($bind)) {
$debugParams = array_merge($debugParams, $bind);
}
$debugSql = buildDebugSql($sql, $debugParams);
echo "[LIST SQL]\n\n";
//echo htmlspecialchars($debugSql, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo $debugSql;
echo '';
}
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($DEBUG_SQL) {
print_r($rows);
exit;
}
// ---------------------------------------------------------
// HTML 조각 생성
// ---------------------------------------------------------
ob_start();
foreach ($rows as $row) {
$item = mapContentRow($row, $CATEGORY_MAP, $SUBCATE_MAP);
$contentId = h($item['id']);
$title = h($item['title']);
$thumb = h($item['thumbnail']);
$categoryNm= h($item['category']);
$subcateNm = h($item['subcate']);
$checked = $item['bookmark'] ? ' checked' : '';
$viewUrl = "/edu/video_view.php?content_id=" . rawurlencode($item['id']);
?>