528 lines
17 KiB
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);
|
|
}
|