Files
edu/ajax/get_video_list.php

528 lines
17 KiB
PHP

<?php
//require_once __DIR__ . '/db_conn.php';
require_once __DIR__ . '/../bbs/db_conn.php';
header('Content-Type: application/json; charset=utf-8');
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
extract($_REQUEST);
// ---------------------------------------------------------
// 로그인 세션에서 사용자 정보 가져오기
// ---------------------------------------------------------
$memberId = trim((string)($_SESSION['member_id'] ?? $_SESSION['user_id'] ?? $_SESSION['ss_mb_id'] ?? ''));
$sysCompCode = trim((string)($_SESSION['sys_comp_code'] ?? $_SESSION['company'] ?? ''));
// sysCompCode 세션 미설정 시 DB에서 보완
if ($memberId !== '' && $sysCompCode === '') {
try {
$__pdo = db_conn();
$__st = $__pdo->prepare('SELECT sys_comp_code FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1');
$__st->execute([$memberId]);
$sysCompCode = (string)($__st->fetchColumn() ?: '');
} catch (Throwable $__e) { /* ignore */ }
}
// ---------------------------------------------------------
// 디버그 설정
// true : 바인딩 적용된 SQL 출력 후 종료
// false : 정상 실행
// ---------------------------------------------------------
//https://baroncs.co.kr/ajax/get_video_list.php?SET_PREFIX=I
$DEBUG_SQL = false;//정상진행
//$DEBUG_SQL = true; //디버깅
$prefixCondition = isset($SET_PREFIX) ? $SET_PREFIX : 'L'; //L=리더십(기본값), I=인사이트
// ---------------------------------------------------------
// 요청 파라미터
// ---------------------------------------------------------
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$limitParam = trim((string)($_GET['limit'] ?? ''));
$limit = 8;
$noLimit = false;
if ($limitParam !== '') {
if (strtolower($limitParam) === 'all') {
$noLimit = true;
} else {
$parsedLimit = (int)$limitParam;
if ($parsedLimit > 0) {
$limit = $parsedLimit;
}
}
}
$offset = ($page - 1) * $limit;
$category = trim($_GET['category'] ?? ''); // 예: CA200L01
$sort = trim($_GET['sort'] ?? 'view'); // view / latest / seen / unseen
// ---------------------------------------------------------
// 기본 코드맵 (DB코드 조회 실패 대비)
// ---------------------------------------------------------
if($prefixCondition=="L"){
$SET_category_code = "CA10004";//리더십
$CATEGORY_MAP = [
'CA10004' => '리더십',
];
$SUBCATE_MAP = [
'CA200L01' => '리더십 시작하기',
'CA200L02' => '셀프 리더십',
'CA200L03' => '팀 리더십',
'CA200L04' => '실전 리더십',
'CA200L05' => '인물탐구',
];
}else if($prefixCondition=="I"){
$SET_category_code = "CA10005";//인사이트
$CATEGORY_MAP = [
'CA10005' => '인사이트',
];
$SUBCATE_MAP = [
'CA200I01' => '경제와 사회',
'CA200I02' => '기술과 미래',
'CA200I03' => '트렌드 업',
'CA200I04' => '행복과 건강',
'CA200I05' => '피플스토리',
'CA200I06' => '라이프',
];
}
// ---------------------------------------------------------
// 공통 함수
// ---------------------------------------------------------
function FN_change_entity(?string $str): string
{
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function mapContentRow(array $row, array $catMap = [], array $subcateMap = []): array
{
$kwStr = $row['keyword_names'] ?? ($row['keywords'] ?? '');
$keywords = $kwStr !== '' ? explode(',', $kwStr) : [];
$keywords = array_values(array_filter(array_map('trim', $keywords), static function ($v) {
return $v !== '';
}));
$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'] ?? '',
'description' => $row['description'] ?? '',
'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 = 'CA100'
OR
( group_code = 'CA200' AND code LIKE '".$prefixCondition."%' )
)
");
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 = "
c.created_at DESC,
c.content_id DESC
";
break;
case 'seen':
$orderBy = "
c.created_at DESC,
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 = "
c.created_at DESC,
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,
c.created_at DESC,
c.content_id DESC
";
break;
}
// ---------------------------------------------------------
// 기본 조건
// ---------------------------------------------------------
$where = [];
$bind = [];
//배열추가
$where[] = "c.is_active = '1'";
$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 = '{$SET_category_code}'"; //edu_codes.base_code == edu_contents.category_code
// 탭 필터
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();
// ---------------------------------------------------------
// 목록 조회
// ---------------------------------------------------------
$limitSql = $noLimit ? '' : "\n LIMIT :limit OFFSET :offset\n";
$sql = "
SELECT
c.content_id,
c.category_code,
c.category_group,
c.title,
c.content_url,
c.thumbnail_url,
c.description,
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,
GROUP_CONCAT(
DISTINCT COALESCE(kc.code_name, ck.keyword_code)
ORDER BY ck.keyword_code
SEPARATOR ','
) AS keyword_names
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'
LEFT JOIN (
SELECT
content_id,
COUNT(DISTINCT member_id) 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
AND ck.is_active = '1'
LEFT JOIN edu_codes kc
ON kc.base_code = ck.keyword_code
AND kc.is_active = 1
WHERE {$whereSql}
GROUP BY c.content_id
ORDER BY {$orderBy}
{$limitSql}
";
/*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);
}
if (!$noLimit) {
$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,
];
if (!$noLimit) {
$debugParams[':limit'] = $limit;
$debugParams[':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 = FN_change_entity($item['id']);
$title = FN_change_entity($item['title']);
$thumb = FN_change_entity($item['thumbnail']);
$categoryNm= FN_change_entity($item['category']);
$subcateNm = FN_change_entity($item['subcate']);
$descRaw = $item['description'] ?? '';
$keywords = $item['keywords'] ?? [];
$keywordsCsv = implode(',', $keywords);
$keywordTags = '';
if (!empty($keywords)) {
$keywordTags = implode(' ', array_map(static function ($kw) {
return '#' . $kw;
}, $keywords));
}
$itemDesc = $keywordTags !== ''
? FN_change_entity($keywordTags)
: ($subcateNm !== '' ? $subcateNm : $categoryNm);
$checked = $item['bookmark'] ? ' checked' : '';
$viewUrl = "/video_view.php?content_id=" . rawurlencode($item['id']);
?>
<li class="video-item" data-content-id="<?= $contentId ?>" data-content-url="<?= FN_change_entity($item['url']) ?>" data-title="<?= $title ?>" data-category-name="<?= $categoryNm ?>" data-sub-category="<?= $subcateNm ?>" data-watch-tm="<?= (int)($item['watch_tm'] ?? 0) ?>" data-content-tm="<?= (int)($item['content_tm'] ?? 0) ?>" data-description="<?= FN_change_entity($descRaw) ?>" data-keywords="<?= FN_change_entity($keywordsCsv) ?>">
<a href="<?= FN_change_entity($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="/img/video/no-image.png" alt="<?= $title ?>" loading="lazy" />
<?php } ?>
</div>
<div class="item-info">
<strong class="item-title"><?= $title ?></strong>
<div class="item-desc-wrapper">
<span class="item-desc"><?= $itemDesc ?></span>
<span class="item-view-count">시청수 <?= (int)($item['view_cnt'] ?? 0) ?> 회</span>
</div>
</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);
}