431 lines
14 KiB
PHP
431 lines
14 KiB
PHP
<?php
|
|
|
|
//require_once __DIR__ . '/db_conn.php';
|
|
require_once __DIR__ . '/../bbs/db_conn.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
// ---------------------------------------------------------
|
|
// TODO: 실제 로그인 세션 연동 후 교체
|
|
// ---------------------------------------------------------
|
|
$memberId = 'U001';
|
|
$sysCompCode = 'COMP01';
|
|
|
|
// ---------------------------------------------------------
|
|
// 디버그 설정
|
|
// true : 바인딩 적용된 SQL 출력 후 종료
|
|
// false : 정상 실행
|
|
// ---------------------------------------------------------
|
|
$DEBUG_SQL = false;
|
|
|
|
// ---------------------------------------------------------
|
|
// 요청 파라미터
|
|
// ---------------------------------------------------------
|
|
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
|
|
$limit = 2;
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
$category = trim($_GET['category'] ?? ''); // 예: CA200L01
|
|
$sort = trim($_GET['sort'] ?? 'view'); // view / latest / seen / unseen
|
|
|
|
// ---------------------------------------------------------
|
|
// 기본 코드맵 (DB코드 조회 실패 대비)
|
|
// ---------------------------------------------------------
|
|
$CATEGORY_MAP = [
|
|
'CA10004' => '리더십',
|
|
];
|
|
|
|
$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 '<pre>';
|
|
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 '</pre>';
|
|
|
|
}
|
|
|
|
$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']);
|
|
?>
|
|
<li class="video-item">
|
|
<a href="<?= h($viewUrl) ?>" class="card-link">
|
|
<label class="bookmark" for="like_chk_<?= $contentId ?>" onclick="event.preventDefault(); event.stopPropagation();">
|
|
<input type="checkbox" id="like_chk_<?= $contentId ?>" title="좋아요"<?= $checked ?> />
|
|
</label>
|
|
|
|
<div class="item-thumb">
|
|
<?php if ($thumb !== '') { ?>
|
|
<img src="<?= $thumb ?>" alt="<?= $title ?>" loading="lazy" />
|
|
<?php } else { ?>
|
|
<img src="/edu/img/video/no-image.png" alt="<?= $title ?>" loading="lazy" />
|
|
<?php } ?>
|
|
</div>
|
|
|
|
<div class="item-info">
|
|
<strong class="item-title"><?= $title ?></strong>
|
|
<span class="item-desc"><?= $subcateNm !== '' ? $subcateNm : $categoryNm ?></span>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
<?php
|
|
}
|
|
|
|
$html = ob_get_clean();
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'html' => $html,
|
|
'total_count' => $totalCount
|
|
], JSON_UNESCAPED_UNICODE);
|
|
|
|
} catch (Throwable $e) {
|
|
error_log('[get_video_list.php] ' . $e->getMessage());
|
|
|
|
echo json_encode([
|
|
'success' => false,
|
|
'html' => '',
|
|
'total_count' => 0
|
|
], JSON_UNESCAPED_UNICODE);
|
|
}
|