Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,528 @@
|
||||
<?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);
|
||||
}
|
||||
Reference in New Issue
Block a user