Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
// 에러 출력 설정
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
set_time_limit(0); // 12,650명 처리를 위해 스크립트 실행 시간 제한 해제
|
||||
|
||||
extract($_REQUEST);
|
||||
|
||||
$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 데이터 양이 많으므로 2분으로 연장
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json"]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("CURL 에러: " . curl_error($ch));
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
die("API 호출 실패 (HTTP 상태 코드: $httpCode)");
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (!isset($data['data']) || !is_array($data['data'])) {
|
||||
die("유효한 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
// DB 연결
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
$conn = db_conn();
|
||||
|
||||
$user_list = $data['data'];
|
||||
$total_count = count($user_list);
|
||||
$success_count = 0;
|
||||
$error_count = 0;
|
||||
|
||||
echo "<h3>인사 정보 동기화 시작 (PDO 방식)</h3>";
|
||||
echo "총 대상 인원: {$total_count}명<br><hr>";
|
||||
|
||||
// 1. PDO Prepared Statement 준비 (위치 보유자 '?' 사용)
|
||||
$sql = "CALL proc_edu_users_in(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
$stmt = $conn->prepare($sql);
|
||||
|
||||
$SET_injection_at = date('Y-m-d H:i:s');
|
||||
|
||||
foreach ($user_list as $row) {
|
||||
// 데이터 정리 (null 처리)
|
||||
$params = [
|
||||
$row['member_id'] ?? '',
|
||||
$row['sys_comp_code'] ?? '',
|
||||
$row['name'] ?? '',
|
||||
$row['dept_name'] ?? '',
|
||||
$row['rank_name'] ?? '',
|
||||
(!empty($row['join_date'])) ? $row['join_date'] : null,
|
||||
$row['belong_comp'] ?? '',
|
||||
$row['working_comp'] ?? '',
|
||||
$row['intra_pw'] ?? '',
|
||||
$row['auth_level'] ?? '',
|
||||
(!empty($row['end_date'])) ? $row['end_date'] : null,
|
||||
$row['intro_flag'] ?? '0',
|
||||
$row['belong_comp_id'] ?? '',
|
||||
$row['working_comp_id'] ?? '',
|
||||
$row['created_at'] ?? date('Y-m-d H:i:s'),
|
||||
$row['updated_at'] ?? date('Y-m-d H:i:s'),
|
||||
$SET_injection_at // injection_at
|
||||
];
|
||||
|
||||
// 2. 실행 (PDO는 execute에 배열을 바로 넣으면 바인딩됩니다)
|
||||
try {
|
||||
if ($stmt->execute($params)) {
|
||||
$success_count++;
|
||||
|
||||
// 대량 데이터 처리 시 진행 상황 출력 (100명 단위)
|
||||
if ($success_count % 1000 == 0) {
|
||||
echo "현재 {$success_count}명 처리 중...<br>";
|
||||
flush(); // 화면에 즉시 반영
|
||||
}
|
||||
} else {
|
||||
$error_count++;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error_count++;
|
||||
// 프로시저 내부 에러 로그는 테이블에 쌓이겠지만 PHP 에러도 확인용 출력
|
||||
echo "에러(ID: " . ($row['member_id'] ?? 'unknown') . "): " . $e->getMessage() . "<br>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 로그 파일 생성 및 저장 로직 (한글 깨짐 방지 적용)
|
||||
// ---------------------------------------------------------
|
||||
$log_dir = $_SERVER['DOCUMENT_ROOT'] . "/edu/log/";
|
||||
$log_file_name = "users_info_batch_" . date('Y') . ".txt";
|
||||
$log_path = $log_dir . $log_file_name;
|
||||
|
||||
// 폴더 생성
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0777, true);
|
||||
}
|
||||
|
||||
// 로그 내용 구성
|
||||
$new_log_entry = "주입일자= {$SET_injection_at} , 성공카운트 = {$success_count} , 실패카운트= {$error_count}" . PHP_EOL;
|
||||
|
||||
// UTF-8 BOM (한글 깨짐 방지용 식별코드)
|
||||
$bom = "\xEF\xBB\xBF";
|
||||
|
||||
if (file_exists($log_path)) {
|
||||
// 기존 내용을 읽어옴
|
||||
$existing_content = file_get_contents($log_path);
|
||||
|
||||
// 기존 내용에 BOM이 이미 있다면 제거 (내용 결합 시 중복 방지)
|
||||
$existing_content = str_replace($bom, '', $existing_content);
|
||||
|
||||
// [BOM] + [새 로그] + [기존 로그] 순서로 저장
|
||||
file_put_contents($log_path, $bom . $new_log_entry . $existing_content);
|
||||
} else {
|
||||
// 파일이 처음 생성될 때 BOM과 함께 저장
|
||||
file_put_contents($log_path, $bom . $new_log_entry);
|
||||
}
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------
|
||||
|
||||
echo "<hr>";
|
||||
echo "<strong>처리 완료!</strong><br>";
|
||||
echo "성공: {$success_count}건 / 실패: {$error_count}건";
|
||||
/*
|
||||
해당파일 처리완료후 로그파일 생성
|
||||
생성위치 /edu/log/ 폴더아래
|
||||
users_info_batch_2026.txt 파일을 생성하여 저장한다
|
||||
파일명 마지막은 현재년도가 들어간 파일명이다.
|
||||
해당폴더에 파일이 없을 경우 생성
|
||||
파일이 존재시
|
||||
파일에 있는 로그내용 위에 새로운 로그내용을 추가한다.
|
||||
|
||||
추가될 내용은 주입일자= $SET_injection_at , 성공카운트 = $success_count , 실패카운트= $error_count
|
||||
|
||||
|
||||
*/
|
||||
?>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
extract($_REQUEST);
|
||||
/* https://baroncs.co.kr/edu/ajax/get_users_info.php
|
||||
* https://baroncs.co.kr/edu/ajax/get_users_info.php?sys_comp_code=30
|
||||
*
|
||||
* $sys_comp_code 필수입력값. EduJsonAPI.php데이터 로직상에는 파라미터가 없을 시,
|
||||
* 가족사 전체 통합인사정보를 가져와서 일정부분 부하가 예상된다.
|
||||
* 때문에
|
||||
* 기본적으로 최소한의 조건 sys_comp_code를 전달받아서
|
||||
* 해당 회사 인원들에 대한 데이터만 업데이트하는 것으로 한다.
|
||||
* ※필요 시, 조건에 따라서 가족사전인원 업데이트도 가능하게 할 수 있음
|
||||
* (예시: 특정 시간대(24:00~05:00)에 접근시에는 전인원 업데이트)
|
||||
* $member_id
|
||||
*/
|
||||
$sys_comp_code = $sys_comp_code==""?"":$sys_comp_code;
|
||||
$member_id = $member_id==""?"":$member_id;
|
||||
|
||||
|
||||
if($sys_comp_code==""){
|
||||
echo "Parameter Fail : sys_comp_code is null";
|
||||
exit;
|
||||
}else{
|
||||
|
||||
if( $sys_comp_code=="10" ||
|
||||
$sys_comp_code=="20" ||
|
||||
$sys_comp_code=="30" ||
|
||||
$sys_comp_code=="40" ||
|
||||
$sys_comp_code=="50"
|
||||
){
|
||||
echo "Success :";
|
||||
echo "<br>";
|
||||
echo "sys_comp_code=".$sys_comp_code;
|
||||
echo "<br>";
|
||||
echo "member_id=".$member_id;
|
||||
//exit;
|
||||
}else{
|
||||
echo "Parameter Fail : This is not allowed sys_comp_code";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php";
|
||||
$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php?sys_comp_code=".$sys_comp_code;
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json"]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("CURL 에러: " . curl_error($ch));
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
die("API 호출 실패 (HTTP 상태 코드: $httpCode)");
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
//print_r($data);
|
||||
|
||||
echo "<br><br>";
|
||||
echo "총인원=".count($data['data'])."명";
|
||||
echo "<br><br>";
|
||||
$TEMP_array_data = $data['data'];
|
||||
echo "<br><br>";
|
||||
print_r($TEMP_array_data[80]);
|
||||
echo "<br><br>";
|
||||
print_r($TEMP_array_data);
|
||||
echo "<br><br>";
|
||||
|
||||
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
//=========================
|
||||
//[마이페이지][시청중인,시청완료,저장한 콘텐츠][북마크] DB update,데이터 동적변환
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching'));
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND is_active = '1'
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(cw.favorited_at, cw.updated_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 콘텐츠리스트 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching'));
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND is_active = '1'
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(cw.favorited_at, cw.updated_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 콘텐츠리스트 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching'));
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND is_active = '1'
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(cw.favorited_at, cw.updated_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
//=========================
|
||||
//나의 학습활동 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$categoryBase = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$categoryBase[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 연도 목록
|
||||
$stmtYear = $pdo->prepare("
|
||||
SELECT DISTINCT YEAR(last_viewed_at) AS view_year
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND last_viewed_at IS NOT NULL
|
||||
ORDER BY view_year DESC
|
||||
");
|
||||
$stmtYear->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$yearList = array_map(
|
||||
static fn($v) => (int)$v,
|
||||
array_filter($stmtYear->fetchAll(PDO::FETCH_COLUMN))
|
||||
);
|
||||
|
||||
$selectedYear = isset($_GET['year']) ? (int)$_GET['year'] : 0;
|
||||
if ($selectedYear <= 0) {
|
||||
$selectedYear = !empty($yearList) ? (int)$yearList[0] : (int)date('Y');
|
||||
}
|
||||
|
||||
|
||||
// 내 총학습시간
|
||||
$stmtMyTotal = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(watch_tm), 0) AS total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :stats_year
|
||||
");
|
||||
|
||||
$stmtMyTotal->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':stats_year' => $selectedYear,
|
||||
]);
|
||||
$myTotalMinutes = (int)$stmtMyTotal->fetchColumn();
|
||||
|
||||
// 전체평균
|
||||
$stmtAvg = $pdo->prepare("
|
||||
SELECT COALESCE(AVG(x.user_total_watch_tm), 0) AS avg_total_watch_tm
|
||||
FROM (
|
||||
SELECT
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
SUM(watch_tm) AS user_total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE YEAR(last_viewed_at) = :stats_year
|
||||
GROUP BY member_id, sys_comp_code
|
||||
) x
|
||||
");
|
||||
$stmtAvg->execute([':stats_year' => $selectedYear]);
|
||||
$avgTotalMinutes = (int)round((float)$stmtAvg->fetchColumn());
|
||||
|
||||
// 온보딩 기준일
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT join_date
|
||||
FROM edu_users
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$joinDate = $stmtUser->fetchColumn();
|
||||
|
||||
$onboardingDueDate = '';
|
||||
$isOnboardingPeriod = 'N';
|
||||
if (!empty($joinDate)) {
|
||||
$onboardingDueDate = date('Y-m-d', strtotime($joinDate . ' +14 days'));
|
||||
$isOnboardingPeriod = (date('Y-m-d') <= $onboardingDueDate) ? 'Y' : 'N';
|
||||
}
|
||||
|
||||
// 선택연도 카테고리별 집계
|
||||
$stmtCategory = $pdo->prepare("
|
||||
SELECT
|
||||
c.category_code,
|
||||
COUNT(DISTINCT lh.content_id) AS learned_content_cnt,
|
||||
COUNT(DISTINCT CASE WHEN lh.watch_tm >= lh.content_tm THEN lh.content_id END) AS completed_content_cnt,
|
||||
COALESCE(SUM(lh.watch_tm), 0) AS total_watch_tm,
|
||||
COALESCE(SUM(lh.content_tm), 0) AS total_content_tm
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND YEAR(lh.last_viewed_at) = :stats_year
|
||||
GROUP BY c.category_code
|
||||
");
|
||||
$stmtCategory->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':stats_year' => $selectedYear,
|
||||
]);
|
||||
|
||||
$aggMap = [];
|
||||
foreach ($stmtCategory->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$aggMap[$row['category_code']] = $row;
|
||||
}
|
||||
|
||||
// 카테고리별 전체 콘텐츠 수
|
||||
$stmtTotalCnt = $pdo->prepare("
|
||||
SELECT
|
||||
category_code,
|
||||
COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
GROUP BY category_code
|
||||
");
|
||||
$stmtTotalCnt->execute();
|
||||
|
||||
$totalCntMap = [];
|
||||
foreach ($stmtTotalCnt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$totalCntMap[$row['category_code']] = (int)$row['total_cnt'];
|
||||
}
|
||||
|
||||
$activityItems = [];
|
||||
foreach ($categoryBase as $categoryCode => $categoryName) {
|
||||
$agg = $aggMap[$categoryCode] ?? null;
|
||||
|
||||
$learnedContentCnt = (int)($agg['learned_content_cnt'] ?? 0);
|
||||
$completedContentCnt = (int)($agg['completed_content_cnt'] ?? 0);
|
||||
$totalWatchTm = (int)($agg['total_watch_tm'] ?? 0);
|
||||
$totalContentTm = (int)($agg['total_content_tm'] ?? 0);
|
||||
$masterTotalCnt = (int)($totalCntMap[$categoryCode] ?? 0);
|
||||
|
||||
// 1차안: gauge는 학습이력 기준 content_tm 합계가 있을 때 그 기준으로 계산
|
||||
// schema상 edu_contents에 표준 duration 컬럼이 없어 완전한 "전체 기준 분모"는 별도 운영정책 확정 필요
|
||||
$percent = ($totalContentTm > 0) ? (int)round(($totalWatchTm / $totalContentTm) * 100) : 0;
|
||||
$percent = max(0, min(100, $percent));
|
||||
|
||||
$activityItems[] = [
|
||||
'category_code' => $categoryCode,
|
||||
'category_name' => $categoryName,
|
||||
'learned_content_cnt' => $learnedContentCnt,
|
||||
'completed_content_cnt' => $completedContentCnt,
|
||||
'master_total_cnt' => $masterTotalCnt,
|
||||
'total_watch_tm' => $totalWatchTm,
|
||||
'total_content_tm' => $totalContentTm,
|
||||
'percent' => $percent,
|
||||
'onboarding_due_date' => ($categoryCode === 'CA10002') ? $onboardingDueDate : '',
|
||||
'is_onboarding_period' => ($categoryCode === 'CA10002') ? $isOnboardingPeriod : 'N',
|
||||
];
|
||||
}
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'year_list' => $yearList,
|
||||
'selected_year' => $selectedYear,
|
||||
'my_total_minutes' => $myTotalMinutes,
|
||||
'avg_total_minutes' => $avgTotalMinutes,
|
||||
'onboarding_due_date'=> $onboardingDueDate,
|
||||
'is_onboarding_period' => $isOnboardingPeriod,
|
||||
'activity_items' => $activityItems,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '학습활동 데이터를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
<?php
|
||||
//=========================
|
||||
//나의 학습활동 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$categoryBase = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$categoryBase[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 연도 목록
|
||||
$stmtYear = $pdo->prepare("
|
||||
SELECT DISTINCT YEAR(last_viewed_at) AS view_year
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND last_viewed_at IS NOT NULL
|
||||
ORDER BY view_year DESC
|
||||
");
|
||||
$stmtYear->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$yearList = array_map(
|
||||
static fn($v) => (int)$v,
|
||||
array_filter($stmtYear->fetchAll(PDO::FETCH_COLUMN))
|
||||
);
|
||||
|
||||
$selectedYear = isset($_GET['year']) ? (int)$_GET['year'] : 0;
|
||||
if ($selectedYear <= 0) {
|
||||
$selectedYear = !empty($yearList) ? (int)$yearList[0] : (int)date('Y');
|
||||
}
|
||||
|
||||
|
||||
// 내 총학습시간
|
||||
$stmtMyTotal = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(watch_tm), 0) AS total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :stats_year
|
||||
");
|
||||
|
||||
$stmtMyTotal->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':stats_year' => $selectedYear,
|
||||
]);
|
||||
$myTotalMinutes = (int)$stmtMyTotal->fetchColumn();
|
||||
|
||||
// 전체평균
|
||||
$stmtAvg = $pdo->prepare("
|
||||
SELECT COALESCE(AVG(x.user_total_watch_tm), 0) AS avg_total_watch_tm
|
||||
FROM (
|
||||
SELECT
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
SUM(watch_tm) AS user_total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE YEAR(last_viewed_at) = :stats_year
|
||||
GROUP BY member_id, sys_comp_code
|
||||
) x
|
||||
");
|
||||
$stmtAvg->execute([':stats_year' => $selectedYear]);
|
||||
$avgTotalMinutes = (int)round((float)$stmtAvg->fetchColumn());
|
||||
|
||||
// 온보딩 기준일
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT join_date
|
||||
FROM edu_users
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$joinDate = $stmtUser->fetchColumn();
|
||||
|
||||
$onboardingDueDate = '';
|
||||
$isOnboardingPeriod = 'N';
|
||||
if (!empty($joinDate)) {
|
||||
$onboardingDueDate = date('Y-m-d', strtotime($joinDate . ' +14 days'));
|
||||
$isOnboardingPeriod = (date('Y-m-d') <= $onboardingDueDate) ? 'Y' : 'N';
|
||||
}
|
||||
|
||||
// 선택연도 카테고리별 집계
|
||||
$stmtCategory = $pdo->prepare("
|
||||
SELECT
|
||||
c.category_code,
|
||||
COUNT(DISTINCT lh.content_id) AS learned_content_cnt,
|
||||
COUNT(DISTINCT CASE WHEN lh.watch_tm >= lh.content_tm THEN lh.content_id END) AS completed_content_cnt,
|
||||
COALESCE(SUM(lh.watch_tm), 0) AS total_watch_tm,
|
||||
COALESCE(SUM(lh.content_tm), 0) AS total_content_tm
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND YEAR(lh.last_viewed_at) = :stats_year
|
||||
GROUP BY c.category_code
|
||||
");
|
||||
$stmtCategory->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':stats_year' => $selectedYear,
|
||||
]);
|
||||
|
||||
$aggMap = [];
|
||||
foreach ($stmtCategory->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$aggMap[$row['category_code']] = $row;
|
||||
}
|
||||
|
||||
/*
|
||||
// 카테고리별 전체 콘텐츠 수
|
||||
$stmtTotalCnt = $pdo->prepare("
|
||||
SELECT
|
||||
category_code,
|
||||
COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
GROUP BY category_code
|
||||
");
|
||||
$stmtTotalCnt->execute();
|
||||
|
||||
$totalCntMap = [];
|
||||
foreach ($stmtTotalCnt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$totalCntMap[$row['category_code']] = (int)$row['total_cnt'];
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 카테고리별 전체 콘텐츠 수 산정 : Start
|
||||
// - 카테고리마다 total_cnt 산정 조건이 다를 수 있으므로
|
||||
// 공통 GROUP BY 방식 대신, 카테고리별 분기 구조로 처리한다.
|
||||
// ---------------------------------------------------------
|
||||
|
||||
$totalCntMap = [
|
||||
'CA10001' => 0, // 마이클래스
|
||||
'CA10002' => 0, // 온보딩
|
||||
'CA10003' => 0, // 법정교육
|
||||
'CA10004' => 0, // 리더십
|
||||
'CA10005' => 0, // 인사이트
|
||||
'CA10006' => 0, // 비즈트렌드
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 현재 월을 기준으로 분기 코드 계산
|
||||
* - 1~3월 : CA200Q01
|
||||
* - 4~6월 : CA200Q02
|
||||
* - 7~9월 : CA200Q03
|
||||
* - 10~12월 : CA200Q04
|
||||
*/
|
||||
/*
|
||||
$currentMonth = (int)date('n');
|
||||
if ($currentMonth >= 1 && $currentMonth <= 3) {
|
||||
$currentQuarterBaseCode = 'CA200Q01';
|
||||
} elseif ($currentMonth >= 4 && $currentMonth <= 6) {
|
||||
$currentQuarterBaseCode = 'CA200Q02';
|
||||
} elseif ($currentMonth >= 7 && $currentMonth <= 9) {
|
||||
$currentQuarterBaseCode = 'CA200Q03';
|
||||
} else {
|
||||
$currentQuarterBaseCode = 'CA200Q04';
|
||||
}
|
||||
*/
|
||||
// ---------------------------------------------------------
|
||||
// 1. 마이클래스(CA10001)
|
||||
// - 현재 분기 기준 + 선택년도 기준으로 콘텐츠 수 산정
|
||||
// - 조건:
|
||||
// category_code = CA10001
|
||||
// category_group = 현재분기 base_code
|
||||
// goal_code 앞 4자리 = 선택년도
|
||||
// ---------------------------------------------------------
|
||||
$stmtMyClassCnt = $pdo->prepare("
|
||||
SELECT COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code = 'CA10001'
|
||||
AND base_year = :selected_year
|
||||
");
|
||||
$stmtMyClassCnt->execute([
|
||||
':selected_year' => (string)$selectedYear,
|
||||
]);
|
||||
$totalCntMap['CA10001'] = (int)$stmtMyClassCnt->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. 온보딩(CA10002)
|
||||
// - 현재는 기본 조건만 적용
|
||||
// - 추후 카테고리별 별도 조건을 쉽게 추가할 수 있도록
|
||||
// 분리된 쿼리 구조로 작성
|
||||
// ---------------------------------------------------------
|
||||
$stmtOnboardingCnt = $pdo->prepare("
|
||||
SELECT COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code = 'CA10002'
|
||||
");
|
||||
$stmtOnboardingCnt->execute();
|
||||
$totalCntMap['CA10002'] = (int)$stmtOnboardingCnt->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. 법정교육(CA10003)
|
||||
// - 현재는 기본 조건만 적용
|
||||
// - 추후 카테고리별 별도 조건을 쉽게 추가할 수 있도록
|
||||
// 분리된 쿼리 구조로 작성
|
||||
// ---------------------------------------------------------
|
||||
$stmtLegalCnt = $pdo->prepare("
|
||||
SELECT COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code = 'CA10003'
|
||||
AND base_year = :selected_year
|
||||
");
|
||||
$stmtLegalCnt->execute([
|
||||
':selected_year' => (string)$selectedYear,
|
||||
]);
|
||||
$totalCntMap['CA10003'] = (int)$stmtLegalCnt->fetchColumn();
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. 나머지 카테고리 공통 처리
|
||||
// - 현재는 공통 조건으로 콘텐츠 수 산정
|
||||
// - 필요 시 이후 각 카테고리도 개별 쿼리로 분리 가능
|
||||
// ---------------------------------------------------------
|
||||
$stmtEtcCnt = $pdo->prepare("
|
||||
SELECT
|
||||
category_code,
|
||||
COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code IN ('CA10004', 'CA10005', 'CA10006')
|
||||
GROUP BY category_code
|
||||
");
|
||||
$stmtEtcCnt->execute();
|
||||
|
||||
foreach ($stmtEtcCnt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$totalCntMap[$row['category_code']] = (int)$row['total_cnt'];
|
||||
}
|
||||
// ---------------------------------------------------------
|
||||
// 카테고리별 전체 콘텐츠 수 산정 : End
|
||||
// ---------------------------------------------------------
|
||||
|
||||
|
||||
$activityItems = [];
|
||||
foreach ($categoryBase as $categoryCode => $categoryName) {
|
||||
$agg = $aggMap[$categoryCode] ?? null;
|
||||
|
||||
$learnedContentCnt = (int)($agg['learned_content_cnt'] ?? 0);
|
||||
$completedContentCnt = (int)($agg['completed_content_cnt'] ?? 0);
|
||||
$totalWatchTm = (int)($agg['total_watch_tm'] ?? 0);
|
||||
$totalContentTm = (int)($agg['total_content_tm'] ?? 0);
|
||||
$masterTotalCnt = (int)($totalCntMap[$categoryCode] ?? 0);
|
||||
|
||||
// 1차안: gauge는 학습이력 기준 content_tm 합계가 있을 때 그 기준으로 계산
|
||||
// schema상 edu_contents에 표준 duration 컬럼이 없어 완전한 "전체 기준 분모"는 별도 운영정책 확정 필요
|
||||
$percent = ($totalContentTm > 0) ? (int)round(($totalWatchTm / $totalContentTm) * 100) : 0;
|
||||
$percent = max(0, min(100, $percent));
|
||||
|
||||
$activityItems[] = [
|
||||
'category_code' => $categoryCode,
|
||||
'category_name' => $categoryName,
|
||||
'learned_content_cnt' => $learnedContentCnt,
|
||||
'completed_content_cnt' => $completedContentCnt,
|
||||
'master_total_cnt' => $masterTotalCnt,
|
||||
'total_watch_tm' => $totalWatchTm,
|
||||
'total_content_tm' => $totalContentTm,
|
||||
'percent' => $percent,
|
||||
'onboarding_due_date' => ($categoryCode === 'CA10002') ? $onboardingDueDate : '',
|
||||
'is_onboarding_period' => ($categoryCode === 'CA10002') ? $isOnboardingPeriod : 'N',
|
||||
];
|
||||
}
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'year_list' => $yearList,
|
||||
'selected_year' => $selectedYear,
|
||||
'my_total_minutes' => $myTotalMinutes,
|
||||
'avg_total_minutes' => $avgTotalMinutes,
|
||||
'onboarding_due_date'=> $onboardingDueDate,
|
||||
'is_onboarding_period' => $isOnboardingPeriod,
|
||||
'activity_items' => $activityItems,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '학습활동 데이터를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
/**
|
||||
* JSON 응답 출력 함수
|
||||
*/
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 학습레벨 메타 반환 함수
|
||||
*/
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'label' => 'Master',
|
||||
'class' => 'master',
|
||||
'icon' => '/edu/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'label' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'icon' => '/edu/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'label' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'icon' => '/edu/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'label' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'label' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 총 학습시간 기준으로 학습레벨 계산
|
||||
* - 40시간 이상: Master
|
||||
* - 20 ~ 40시간: Elite
|
||||
* - 8 ~ 20시간: Learner
|
||||
* - 0 ~ 8시간: Rookie
|
||||
*/
|
||||
function calc_level_by_minutes(int $totalMinutes): string
|
||||
{
|
||||
if ($totalMinutes >= 2400) {
|
||||
return 'MASTER';
|
||||
}
|
||||
|
||||
if ($totalMinutes >= 1200) {
|
||||
return 'ELITE';
|
||||
}
|
||||
|
||||
if ($totalMinutes >= 480) {
|
||||
return 'LEARNER';
|
||||
}
|
||||
|
||||
return 'ROOKIE';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ★ 선택년도 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
|
||||
$levelCode = 'ROOKIE';
|
||||
$totalMinutes = 0;
|
||||
$statsYear = $selectedYear;
|
||||
|
||||
// ★ 선택년도 통계 우선 조회
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
stats_year,
|
||||
learning_level,
|
||||
total_minutes
|
||||
FROM edu_yearly_learning_stats
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND stats_year = :selected_year
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelCode = (string)($row['learning_level'] ?? 'ROOKIE');
|
||||
$totalMinutes = (int)($row['total_minutes'] ?? 0);
|
||||
$statsYear = (string)($row['stats_year'] ?? $selectedYear);
|
||||
} else {
|
||||
// ★ 선택년도 통계가 없으면 학습이력 합계로 fallback
|
||||
$stmtFallback = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(watch_tm), 0) AS total_minutes
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :selected_year
|
||||
");
|
||||
$stmtFallback->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
|
||||
$totalMinutes = (int)$stmtFallback->fetchColumn();
|
||||
$levelCode = calc_level_by_minutes($totalMinutes);
|
||||
}
|
||||
|
||||
$levelMeta = mypage_level_meta($levelCode);
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'selected_year' => $selectedYear,
|
||||
'stats_year' => $statsYear,
|
||||
'total_minutes' => $totalMinutes,
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '학습레벨 정보를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$offerStatusMap = [
|
||||
'OF10001' => '제안',
|
||||
'OF10002' => '검토중',
|
||||
'OF10003' => '게시완료',
|
||||
'OF10004' => '게시불가',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'OF100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$offerStatusMap[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
co.offer_id,
|
||||
co.status_code,
|
||||
co.created_at
|
||||
FROM edu_content_offer co
|
||||
WHERE co.member_id = :member_id
|
||||
AND co.sys_comp_code = :sys_comp_code
|
||||
ORDER BY
|
||||
CASE co.status_code
|
||||
WHEN 'OF10002' THEN 0
|
||||
WHEN 'OF10001' THEN 1
|
||||
WHEN 'OF10003' THEN 2
|
||||
WHEN 'OF10004' THEN 3
|
||||
ELSE 9
|
||||
END,
|
||||
co.created_at DESC
|
||||
LIMIT 20
|
||||
");
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
if (!empty($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$statusCode = (string)($row['status_code'] ?? '');
|
||||
$statusName = $offerStatusMap[$statusCode] ?? $statusCode;
|
||||
$isConsider = ($statusCode === 'OF10002');
|
||||
$createdAtDot = !empty($row['created_at']) ? date('y.m.d', strtotime($row['created_at'])) : '';
|
||||
?>
|
||||
<li class="<?= $isConsider ? 'consider' : '' ?>" data-offer-id="<?= h($row['offer_id'] ?? '') ?>">
|
||||
<span class="suggest-date"><?= h($createdAtDot) ?></span>
|
||||
<?php if ($isConsider): ?>
|
||||
<span class="suggest-dots"></span>
|
||||
<?php endif; ?>
|
||||
<span class="suggest-state"><?= h($statusName) ?></span>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
<li>
|
||||
<span class="suggest-date">-</span>
|
||||
<span class="suggest-state">등록된 제안이 없습니다.</span>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'html' => $html,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '제안현황을 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
/**
|
||||
* JSON 응답 출력 함수
|
||||
*/
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML escape 함수
|
||||
*/
|
||||
function h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 카테고리 태그 클래스 반환
|
||||
*/
|
||||
function review_category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ★ 선택년도 파라미터 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
|
||||
// ★ 한줄소감 조회
|
||||
// - 선택년도 기준
|
||||
// - comment 값이 있는 이력만
|
||||
// - 마이클래스(CA10001)만
|
||||
// - 같은 content_id는 최신 시청이력 1건만
|
||||
$sql = "
|
||||
SELECT
|
||||
lh.content_id,
|
||||
lh.member_id,
|
||||
lh.sys_comp_code,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
lh.last_viewed_at,
|
||||
lh.completed_at,
|
||||
lh.comment,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN (
|
||||
SELECT
|
||||
content_id,
|
||||
MAX(last_viewed_at) AS max_last_viewed_at
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :sub_member_id
|
||||
AND sys_comp_code = :sub_sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :sub_selected_year
|
||||
AND comment IS NOT NULL
|
||||
AND TRIM(comment) <> ''
|
||||
GROUP BY content_id
|
||||
) latest
|
||||
ON latest.content_id = lh.content_id
|
||||
AND latest.max_last_viewed_at = lh.last_viewed_at
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
WHERE lh.member_id = :main_member_id
|
||||
AND lh.sys_comp_code = :main_sys_comp_code
|
||||
AND c.category_code = 'CA10001'
|
||||
AND lh.comment IS NOT NULL
|
||||
AND TRIM(lh.comment) <> ''
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':sub_member_id' => $memberId,
|
||||
':sub_sys_comp_code' => $sysCompCode,
|
||||
':sub_selected_year' => (int)$selectedYear,
|
||||
':main_member_id' => $memberId,
|
||||
':main_sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$totalCount = count($rows);
|
||||
|
||||
ob_start();
|
||||
|
||||
if (!empty($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$categoryCode = (string)($row['category_code'] ?? '');
|
||||
$tagClass = review_category_tag_class($categoryCode);
|
||||
|
||||
// ★ 실제 한줄소감은 edu_learning_histories.comment 사용
|
||||
$reviewText = trim((string)($row['comment'] ?? ''));
|
||||
?>
|
||||
<li class="review-item" data-content-id="<?= h($row['content_id'] ?? '') ?>">
|
||||
<div class="review-head">
|
||||
<span class="category-tag <?= h($tagClass) ?>">마이클래스</span>
|
||||
<p class="review-text"><?= h($row['title'] ?? '') ?></p>
|
||||
</div>
|
||||
<div class="review-comment">
|
||||
<span class="review-comment-text">
|
||||
<?= h($reviewText) ?>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<?php
|
||||
}//foreach
|
||||
} else {
|
||||
?>
|
||||
<li class="review-empty">한줄소감이 없습니다.</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'selected_year' => $selectedYear,
|
||||
'total_count' => $totalCount,
|
||||
'html' => $html,
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '한줄소감 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
extract($_REQUEST);
|
||||
/* https://baroncs.co.kr/edu/ajax/get_users_info.php
|
||||
* https://baroncs.co.kr/edu/ajax/get_users_info.php?sys_comp_code=30
|
||||
*
|
||||
* $sys_comp_code 필수입력값. EduJsonAPI.php데이터 로직상에는 파라미터가 없을 시,
|
||||
* 가족사 전체 통합인사정보를 가져와서 일정부분 부하가 예상된다.
|
||||
* 때문에
|
||||
* 기본적으로 최소한의 조건 sys_comp_code를 전달받아서
|
||||
* 해당 회사 인원들에 대한 데이터만 업데이트하는 것으로 한다.
|
||||
* ※필요 시, 조건에 따라서 가족사전인원 업데이트도 가능하게 할 수 있음
|
||||
* (예시: 특정 시간대(24:00~05:00)에 접근시에는 전인원 업데이트)
|
||||
* $member_id
|
||||
*/
|
||||
|
||||
//배치작업 진행후 작업결과 로그 남길것
|
||||
|
||||
$sys_comp_code = $sys_comp_code==""?"":$sys_comp_code;
|
||||
$member_id = $member_id==""?"":$member_id;
|
||||
|
||||
/*
|
||||
if($sys_comp_code==""){
|
||||
//echo "Parameter Fail : sys_comp_code is null";
|
||||
//exit;
|
||||
}else{
|
||||
|
||||
if( $sys_comp_code=="10" ||
|
||||
$sys_comp_code=="20" ||
|
||||
$sys_comp_code=="30" ||
|
||||
$sys_comp_code=="40" ||
|
||||
$sys_comp_code=="50"
|
||||
){
|
||||
echo "Success :";
|
||||
echo "<br>";
|
||||
echo "sys_comp_code=".$sys_comp_code;
|
||||
echo "<br>";
|
||||
echo "member_id=".$member_id;
|
||||
//exit;
|
||||
}else{
|
||||
echo "Parameter Fail : This is not allowed sys_comp_code";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php";
|
||||
//$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php?sys_comp_code=".$sys_comp_code;
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json"]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("CURL 에러: " . curl_error($ch));
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
die("API 호출 실패 (HTTP 상태 코드: $httpCode)");
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
//print_r($data);
|
||||
|
||||
echo "<br><br>";
|
||||
echo "총인원=".count($data['data'])."명";
|
||||
echo "<br><br>";
|
||||
$TEMP_array_data = $data['data'];
|
||||
echo "<br><br>";
|
||||
print_r($TEMP_array_data[80]);
|
||||
echo "<br><br>";
|
||||
print_r($TEMP_array_data);
|
||||
echo "<br><br>";
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,431 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
//=========================
|
||||
//콘텐츠리스트 AJAX
|
||||
//기존 참고 파일처럼 html 조각을 반환하는 방식으로 잡았습니다.
|
||||
//현재 참고 구현도 코드맵/진행률/북마크/키워드 배열 구조를 그렇게 처리하고 있습니다.
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching')); // watching / completed / saved
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND (is_active = '1' OR is_active = 'Y')
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
GROUP BY c.content_id
|
||||
ORDER BY cw.favorited_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>">
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
//=========================
|
||||
//콘텐츠리스트 AJAX
|
||||
//기존 참고 파일처럼 html 조각을 반환하는 방식으로 잡았습니다.
|
||||
//현재 참고 구현도 코드맵/진행률/북마크/키워드 배열 구조를 그렇게 처리하고 있습니다.
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching')); // watching / completed / saved
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND (is_active = '1' OR is_active = 'Y')
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
GROUP BY c.content_id
|
||||
ORDER BY cw.favorited_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 콘텐츠리스트 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
//$memberId = 'U004';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching'));
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// ★ 선택년도 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 탭 카운트 (선택년도 기준)
|
||||
// =========================
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
AND YEAR(last_viewed_at) = :selected_year
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
AND YEAR(last_viewed_at) = :selected_year
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
// ★ 저장한 콘텐츠도 선택년도에 따라 변동되도록
|
||||
// 해당 연도 학습이력이 있는 저장 콘텐츠만 count
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(DISTINCT cw.content_id)
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_learning_histories lh
|
||||
ON lh.content_id = cw.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
// =========================
|
||||
// 탭별 메인 쿼리
|
||||
// =========================
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords,
|
||||
MAX(lh.last_viewed_at) AS sort_last_viewed_at
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
INNER JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
GROUP BY c.content_id
|
||||
ORDER BY sort_last_viewed_at DESC, COALESCE(cw.favorited_at, cw.updated_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':selected_year', (int)$selectedYear, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'selected_year' => $selectedYear,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
/**
|
||||
* JSON 응답 출력 함수
|
||||
*/
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$typeCode = trim((string)($_POST['type_code'] ?? ''));
|
||||
$statusCode = trim((string)($_POST['status_code'] ?? ''));
|
||||
$referenceUrl = trim((string)($_POST['reference_url'] ?? ''));
|
||||
$reason = trim((string)($_POST['reason'] ?? ''));
|
||||
|
||||
|
||||
//제안자는 URL(reference_url)과 제안이유(reason)만 입력한다.
|
||||
//나머지 입력이 필요한 컬럼은 관리자가 입력하게 된다.(type_code,title)
|
||||
|
||||
// ★ 기본값 보정
|
||||
if ($typeCode === '') {
|
||||
$typeCode = '';
|
||||
}
|
||||
|
||||
if ($statusCode === '') {
|
||||
$statusCode = 'OF10002';
|
||||
}
|
||||
|
||||
// ★ 필수값 검증
|
||||
if ($referenceUrl === '') {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => 'URL을 입력해주세요.'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($reason === '') {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '추천이유를 입력해주세요.'
|
||||
]);
|
||||
}
|
||||
|
||||
// ★ URL 형식 검증
|
||||
if (!filter_var($referenceUrl, FILTER_VALIDATE_URL)) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '올바른 URL 형식이 아닙니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// ★ 해당 년월(YYYYMM) 기준 offer_id 생성
|
||||
// 예: 202603-001, 202603-002
|
||||
$offerYm = date('Ym');
|
||||
|
||||
// ★ 같은 년월 prefix를 가진 마지막 offer_id 조회
|
||||
$stmtSeq = $pdo->prepare("
|
||||
SELECT offer_id
|
||||
FROM edu_content_offer
|
||||
WHERE offer_id LIKE :offer_prefix
|
||||
ORDER BY offer_id DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtSeq->execute([
|
||||
':offer_prefix' => $offerYm . '-%',
|
||||
]);
|
||||
|
||||
$lastOfferId = (string)$stmtSeq->fetchColumn();
|
||||
$nextSeq = 1;
|
||||
|
||||
// ★ 마지막 번호가 있으면 뒤 3자리 숫자를 추출해서 +1
|
||||
if ($lastOfferId !== '') {
|
||||
$lastSeq = (int)substr($lastOfferId, -3);
|
||||
$nextSeq = $lastSeq + 1;
|
||||
}
|
||||
|
||||
$offerId = $offerYm . '-' . str_pad((string)$nextSeq, 3, '0', STR_PAD_LEFT);
|
||||
|
||||
// ★ 저장
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO edu_content_offer (
|
||||
offer_id,
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
reference_url,
|
||||
reason,
|
||||
status_code,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
:offer_id,
|
||||
:member_id,
|
||||
:sys_comp_code,
|
||||
:reference_url,
|
||||
:reason,
|
||||
:status_code,
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':offer_id' => $offerId,
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':reference_url' => $referenceUrl,
|
||||
':reason' => $reason,
|
||||
':status_code' => $statusCode,
|
||||
]);
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'message' => '제안이 정상적으로 등록되었습니다.',
|
||||
'offer_id' => $offerId,
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '제안 등록 중 오류가 발생했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 콘텐츠리스트 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching'));
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND is_active = '1'
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(cw.favorited_at, cw.updated_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
function response_json($data) {
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 세션 (실제 환경 기준)
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode= $_SESSION['sys_comp_code'] ?? '';
|
||||
|
||||
// 테스트용 (필요시 사용)
|
||||
// $memberId = 'U001';
|
||||
// $sysCompCode = 'COMP01';
|
||||
|
||||
try {
|
||||
|
||||
if (!$memberId || !$sysCompCode) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '로그인 정보가 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
if (!isset($_FILES['profile_image'])) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '파일이 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
$file = $_FILES['profile_image'];
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '업로드 오류 발생'
|
||||
]);
|
||||
}
|
||||
|
||||
// MIME 체크
|
||||
$mime = mime_content_type($file['tmp_name']);
|
||||
if (!in_array($mime, ['image/jpeg', 'image/png'])) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => 'jpg, png 파일만 업로드 가능합니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// 이미지 생성
|
||||
if ($mime === 'image/jpeg') {
|
||||
$srcImage = imagecreatefromjpeg($file['tmp_name']);
|
||||
} else {
|
||||
$srcImage = imagecreatefrompng($file['tmp_name']);
|
||||
}
|
||||
|
||||
if (!$srcImage) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '이미지 처리 실패'
|
||||
]);
|
||||
}
|
||||
|
||||
// 원본 사이즈
|
||||
$width = imagesx($srcImage);
|
||||
$height = imagesy($srcImage);
|
||||
|
||||
// 새 이미지 생성 (png)
|
||||
$newImage = imagecreatetruecolor($width, $height);
|
||||
|
||||
// 투명 배경 처리
|
||||
imagealphablending($newImage, false);
|
||||
imagesavealpha($newImage, true);
|
||||
|
||||
$transparent = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
|
||||
imagefilledrectangle($newImage, 0, 0, $width, $height, $transparent);
|
||||
|
||||
// 복사
|
||||
imagecopy($newImage, $srcImage, 0, 0, 0, 0, $width, $height);
|
||||
|
||||
// 저장 경로
|
||||
$fileName = $memberId . '_' . $sysCompCode . '.png';
|
||||
$savePath = __DIR__ . '/../img/profile/' . $fileName;
|
||||
$saveUrl = '/edu/img/profile/' . $fileName;
|
||||
|
||||
// 저장 (덮어쓰기)
|
||||
if (!imagepng($newImage, $savePath)) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '파일 저장 실패'
|
||||
]);
|
||||
}
|
||||
|
||||
// 메모리 해제
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($newImage);
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'image_url' => $saveUrl
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '프로필 업로드 중 오류가 발생했습니다.',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// JSON 입력 받기
|
||||
$raw = file_get_contents('php://input');
|
||||
$req = json_decode($raw, true);
|
||||
|
||||
$contentId = trim((string)($req['content_id'] ?? ''));
|
||||
$tab = trim((string)($req['tab'] ?? ''));
|
||||
|
||||
if ($contentId === '') {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => 'content_id가 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// 탭별 삭제 처리
|
||||
if ($tab === 'watching') {
|
||||
// 시청중 목록 제거: 학습이력 삭제 대신
|
||||
// "마이페이지에서 숨김" 정책이 아직 없으면 임시로 위시리스트 제거만 처리하거나
|
||||
// 별도 정책 확정 필요
|
||||
$stmt = $pdo->prepare("
|
||||
DELETE FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND content_id = :content_id
|
||||
");
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':content_id' => $contentId,
|
||||
]);
|
||||
|
||||
} elseif ($tab === 'completed') {
|
||||
// 시청완료 목록 제거
|
||||
$stmt = $pdo->prepare("
|
||||
DELETE FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND content_id = :content_id
|
||||
");
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':content_id' => $contentId,
|
||||
]);
|
||||
|
||||
} elseif ($tab === 'saved') {
|
||||
// 저장한 콘텐츠 제거
|
||||
$stmt = $pdo->prepare("
|
||||
DELETE FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND content_id = :content_id
|
||||
");
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':content_id' => $contentId,
|
||||
]);
|
||||
|
||||
} else {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '유효하지 않은 탭입니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'content_id' => $contentId,
|
||||
'tab' => $tab
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '삭제 처리 중 오류가 발생했습니다.',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
//=========================
|
||||
//나의 학습활동 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$categoryBase = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$categoryBase[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 연도 목록
|
||||
$stmtYear = $pdo->prepare("
|
||||
SELECT DISTINCT YEAR(last_viewed_at) AS view_year
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND last_viewed_at IS NOT NULL
|
||||
ORDER BY view_year DESC
|
||||
");
|
||||
$stmtYear->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$yearList = array_map(
|
||||
static fn($v) => (int)$v,
|
||||
array_filter($stmtYear->fetchAll(PDO::FETCH_COLUMN))
|
||||
);
|
||||
|
||||
$currentYear = (int)date('Y');
|
||||
|
||||
if (empty($yearList)) {
|
||||
// 리스트가 완전히 비어있다면 현재 연도 한 개만 추가
|
||||
$yearList = [$currentYear];
|
||||
}
|
||||
|
||||
$selectedYear = isset($_GET['year']) ? (int)$_GET['year'] : 0;
|
||||
if ($selectedYear <= 0) {
|
||||
$selectedYear = !empty($yearList) ? (int)$yearList[0] : (int)date('Y');
|
||||
}
|
||||
|
||||
|
||||
// 내 총학습시간
|
||||
$stmtMyTotal = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(watch_tm), 0) AS total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :stats_year
|
||||
");
|
||||
|
||||
$stmtMyTotal->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':stats_year' => $selectedYear,
|
||||
]);
|
||||
$myTotalMinutes = (int)$stmtMyTotal->fetchColumn();
|
||||
|
||||
// 전체평균
|
||||
$stmtAvg = $pdo->prepare("
|
||||
SELECT COALESCE(AVG(x.user_total_watch_tm), 0) AS avg_total_watch_tm
|
||||
FROM (
|
||||
SELECT
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
SUM(watch_tm) AS user_total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE YEAR(last_viewed_at) = :stats_year
|
||||
GROUP BY member_id, sys_comp_code
|
||||
) x
|
||||
");
|
||||
$stmtAvg->execute([':stats_year' => $selectedYear]);
|
||||
$avgTotalMinutes = (int)round((float)$stmtAvg->fetchColumn());
|
||||
|
||||
// 온보딩 기준일
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT join_date
|
||||
FROM edu_users
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$joinDate = $stmtUser->fetchColumn();
|
||||
|
||||
$onboardingDueDate = '';
|
||||
$isOnboardingPeriod = 'N';
|
||||
if (!empty($joinDate)) {
|
||||
$onboardingDueDate = date('Y-m-d', strtotime($joinDate . ' +14 days'));
|
||||
$isOnboardingPeriod = (date('Y-m-d') <= $onboardingDueDate) ? 'Y' : 'N';
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 현재 월을 기준으로 분기 코드 계산
|
||||
* - 1~3월 : CA200Q01
|
||||
* - 4~6월 : CA200Q02
|
||||
* - 7~9월 : CA200Q03
|
||||
* - 10~12월 : CA200Q04
|
||||
*/
|
||||
|
||||
$currentMonth = (int)date('n');
|
||||
if ($currentMonth >= 1 && $currentMonth <= 3) {
|
||||
$currentQuarterBaseCode = 'CA200Q01';
|
||||
} elseif ($currentMonth >= 4 && $currentMonth <= 6) {
|
||||
$currentQuarterBaseCode = 'CA200Q02';
|
||||
} elseif ($currentMonth >= 7 && $currentMonth <= 9) {
|
||||
$currentQuarterBaseCode = 'CA200Q03';
|
||||
} else {
|
||||
$currentQuarterBaseCode = 'CA200Q04';
|
||||
}
|
||||
|
||||
// --fn_get_progress_rate(:sys_comp_code, :stats_year1, :member_id, c.category_code, '') AS category_percent,
|
||||
if($memberId=="test"){
|
||||
}else{
|
||||
// 선택연도 카테고리별 집계
|
||||
$stmtCategory = $pdo->prepare("
|
||||
SELECT
|
||||
c.category_code,
|
||||
|
||||
CASE
|
||||
WHEN c.category_code = 'CA10001' THEN
|
||||
fn_get_progress_rate(:sys_comp_code_01, :stats_year_01, :member_id_01, c.category_code, :quarter_code_01)
|
||||
ELSE
|
||||
fn_get_progress_rate(:sys_comp_code_02, :stats_year_02, :member_id_02, c.category_code, '')
|
||||
END AS category_percent,
|
||||
|
||||
COUNT(DISTINCT lh.content_id) AS learned_content_cnt,
|
||||
COUNT(DISTINCT CASE WHEN lh.watch_tm >= lh.content_tm THEN lh.content_id END) AS completed_content_cnt,
|
||||
COALESCE(SUM(lh.watch_tm), 0) AS total_watch_tm,
|
||||
COALESCE(SUM(lh.content_tm), 0) AS total_content_tm
|
||||
|
||||
,FLOOR(COALESCE(SUM(lh.watch_tm), 0)/60) AS total_watch_tm_min
|
||||
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c ON c.content_id = lh.content_id
|
||||
WHERE lh.member_id = :member_id_03
|
||||
AND lh.sys_comp_code = :sys_comp_code_03
|
||||
AND YEAR(lh.last_viewed_at) = :stats_year_03
|
||||
GROUP BY c.category_code
|
||||
");
|
||||
|
||||
$stmtCategory->execute([
|
||||
':sys_comp_code_01' => $sysCompCode,
|
||||
':stats_year_01' => $selectedYear,
|
||||
':member_id_01' => $memberId,
|
||||
':quarter_code_01' => $currentQuarterBaseCode,
|
||||
':sys_comp_code_02' => $sysCompCode,
|
||||
':stats_year_02' => $selectedYear,
|
||||
':member_id_02' => $memberId,
|
||||
':member_id_03' => $memberId,
|
||||
':sys_comp_code_03' => $sysCompCode,
|
||||
':stats_year_03' => $selectedYear,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// $stmtCategory = $pdo->prepare("
|
||||
// SELECT
|
||||
// c.category_code,
|
||||
// COUNT(DISTINCT lh.content_id) AS learned_content_cnt,
|
||||
// COUNT(DISTINCT CASE WHEN lh.watch_tm >= lh.content_tm THEN lh.content_id END) AS completed_content_cnt,
|
||||
// COALESCE(SUM(lh.watch_tm), 0) AS total_watch_tm,
|
||||
// COALESCE(SUM(lh.content_tm), 0) AS total_content_tm
|
||||
// FROM edu_learning_histories lh
|
||||
// INNER JOIN edu_contents c
|
||||
// ON c.content_id = lh.content_id
|
||||
// WHERE lh.member_id = :member_id
|
||||
// AND lh.sys_comp_code = :sys_comp_code
|
||||
// AND YEAR(lh.last_viewed_at) = :stats_year
|
||||
// GROUP BY c.category_code
|
||||
// ");
|
||||
// $stmtCategory->execute([
|
||||
// ':member_id' => $memberId,
|
||||
// ':sys_comp_code' => $sysCompCode,
|
||||
// ':stats_year' => $selectedYear,
|
||||
// ]);
|
||||
|
||||
$aggMap = [];
|
||||
foreach ($stmtCategory->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$aggMap[$row['category_code']] = $row;
|
||||
}
|
||||
|
||||
/*
|
||||
// 카테고리별 전체 콘텐츠 수
|
||||
$stmtTotalCnt = $pdo->prepare("
|
||||
SELECT
|
||||
category_code,
|
||||
COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
GROUP BY category_code
|
||||
");
|
||||
$stmtTotalCnt->execute();
|
||||
|
||||
$totalCntMap = [];
|
||||
foreach ($stmtTotalCnt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$totalCntMap[$row['category_code']] = (int)$row['total_cnt'];
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 카테고리별 전체 콘텐츠 수 산정 : Start
|
||||
// - 카테고리마다 total_cnt 산정 조건이 다를 수 있으므로
|
||||
// 공통 GROUP BY 방식 대신, 카테고리별 분기 구조로 처리한다.
|
||||
// ---------------------------------------------------------
|
||||
|
||||
$totalCntMap = [
|
||||
'CA10001' => 0, // 마이클래스
|
||||
'CA10002' => 0, // 온보딩
|
||||
'CA10003' => 0, // 법정교육
|
||||
'CA10004' => 0, // 리더십
|
||||
'CA10005' => 0, // 인사이트
|
||||
'CA10006' => 0, // 비즈트렌드
|
||||
];
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. 마이클래스(CA10001)
|
||||
// - 현재 분기 기준 + 선택년도 기준으로 콘텐츠 수 산정
|
||||
// - 조건:
|
||||
// category_code = CA10001
|
||||
// category_group = 현재분기 base_code
|
||||
// goal_code 앞 4자리 = 선택년도
|
||||
// ---------------------------------------------------------
|
||||
$stmtMyClassCnt = $pdo->prepare("
|
||||
SELECT COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code = 'CA10001'
|
||||
AND base_year = :selected_year
|
||||
");
|
||||
$stmtMyClassCnt->execute([
|
||||
':selected_year' => (string)$selectedYear,
|
||||
]);
|
||||
$totalCntMap['CA10001'] = (int)$stmtMyClassCnt->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. 온보딩(CA10002)
|
||||
// - 현재는 기본 조건만 적용
|
||||
// - 추후 카테고리별 별도 조건을 쉽게 추가할 수 있도록
|
||||
// 분리된 쿼리 구조로 작성
|
||||
// ---------------------------------------------------------
|
||||
$stmtOnboardingCnt = $pdo->prepare("
|
||||
SELECT COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code = 'CA10002'
|
||||
");
|
||||
$stmtOnboardingCnt->execute();
|
||||
$totalCntMap['CA10002'] = (int)$stmtOnboardingCnt->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. 법정교육(CA10003)
|
||||
// - 현재는 기본 조건만 적용
|
||||
// - 추후 카테고리별 별도 조건을 쉽게 추가할 수 있도록
|
||||
// 분리된 쿼리 구조로 작성
|
||||
// ---------------------------------------------------------
|
||||
$stmtLegalCnt = $pdo->prepare("
|
||||
SELECT COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code = 'CA10003'
|
||||
AND base_year = :selected_year
|
||||
");
|
||||
$stmtLegalCnt->execute([
|
||||
':selected_year' => (string)$selectedYear,
|
||||
]);
|
||||
$totalCntMap['CA10003'] = (int)$stmtLegalCnt->fetchColumn();
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. 나머지 카테고리 공통 처리
|
||||
// - 현재는 공통 조건으로 콘텐츠 수 산정
|
||||
// - 필요 시 이후 각 카테고리도 개별 쿼리로 분리 가능
|
||||
// ---------------------------------------------------------
|
||||
$stmtEtcCnt = $pdo->prepare("
|
||||
SELECT
|
||||
category_code,
|
||||
COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
AND category_code IN ('CA10004', 'CA10005', 'CA10006')
|
||||
GROUP BY category_code
|
||||
");
|
||||
$stmtEtcCnt->execute();
|
||||
|
||||
foreach ($stmtEtcCnt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$totalCntMap[$row['category_code']] = (int)$row['total_cnt'];
|
||||
}
|
||||
// ---------------------------------------------------------
|
||||
// 카테고리별 전체 콘텐츠 수 산정 : End
|
||||
// ---------------------------------------------------------
|
||||
|
||||
|
||||
$activityItems = [];
|
||||
foreach ($categoryBase as $categoryCode => $categoryName) {
|
||||
$agg = $aggMap[$categoryCode] ?? null;
|
||||
|
||||
$learnedContentCnt = (int)($agg['learned_content_cnt'] ?? 0);
|
||||
$completedContentCnt = (int)($agg['completed_content_cnt'] ?? 0);
|
||||
$totalWatchTm = (int)($agg['total_watch_tm'] ?? 0);
|
||||
|
||||
$percent = (int)($agg['category_percent'] ?? 0);
|
||||
|
||||
$totalContentTm = (int)($agg['total_content_tm'] ?? 0);
|
||||
$masterTotalCnt = (int)($totalCntMap[$categoryCode] ?? 0);
|
||||
|
||||
// 1차안: gauge는 학습이력 기준 content_tm 합계가 있을 때 그 기준으로 계산
|
||||
// schema상 edu_contents에 표준 duration 컬럼이 없어 완전한 "전체 기준 분모"는 별도 운영정책 확정 필요
|
||||
//$percent = ($totalContentTm > 0) ? (int)round(($totalWatchTm / $totalContentTm) * 100) : 0;
|
||||
//$percent = max(0, min(100, $percent));
|
||||
|
||||
|
||||
$activityItems[] = [
|
||||
'category_code' => $categoryCode,
|
||||
'category_name' => $categoryName,
|
||||
'learned_content_cnt' => $learnedContentCnt,
|
||||
'completed_content_cnt' => $completedContentCnt,
|
||||
'master_total_cnt' => $masterTotalCnt,
|
||||
'total_watch_tm' => $totalWatchTm,
|
||||
'total_content_tm' => $totalContentTm,
|
||||
'percent' => $percent,
|
||||
'onboarding_due_date' => ($categoryCode === 'CA10002') ? $onboardingDueDate : '',
|
||||
'is_onboarding_period' => ($categoryCode === 'CA10002') ? $isOnboardingPeriod : 'N',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'year_list' => $yearList,
|
||||
'selected_year' => $selectedYear,
|
||||
'my_total_minutes' => $myTotalMinutes,
|
||||
'avg_total_minutes' => $avgTotalMinutes,
|
||||
'onboarding_due_date'=> $onboardingDueDate,
|
||||
'is_onboarding_period' => $isOnboardingPeriod,
|
||||
'activity_items' => $activityItems,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '학습활동 데이터를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
//=========================
|
||||
//나의 학습활동 AJAX
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$categoryBase = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$categoryBase[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 연도 목록
|
||||
$stmtYear = $pdo->prepare("
|
||||
SELECT DISTINCT YEAR(last_viewed_at) AS view_year
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND last_viewed_at IS NOT NULL
|
||||
ORDER BY view_year DESC
|
||||
");
|
||||
$stmtYear->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$yearList = array_map(
|
||||
static fn($v) => (int)$v,
|
||||
array_filter($stmtYear->fetchAll(PDO::FETCH_COLUMN))
|
||||
);
|
||||
|
||||
$selectedYear = isset($_GET['year']) ? (int)$_GET['year'] : 0;
|
||||
if ($selectedYear <= 0) {
|
||||
$selectedYear = !empty($yearList) ? (int)$yearList[0] : (int)date('Y');
|
||||
}
|
||||
|
||||
|
||||
// 내 총학습시간
|
||||
$stmtMyTotal = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(watch_tm), 0) AS total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :stats_year
|
||||
");
|
||||
|
||||
$stmtMyTotal->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':stats_year' => $selectedYear,
|
||||
]);
|
||||
$myTotalMinutes = (int)$stmtMyTotal->fetchColumn();
|
||||
|
||||
// 전체평균
|
||||
$stmtAvg = $pdo->prepare("
|
||||
SELECT COALESCE(AVG(x.user_total_watch_tm), 0) AS avg_total_watch_tm
|
||||
FROM (
|
||||
SELECT
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
SUM(watch_tm) AS user_total_watch_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE YEAR(last_viewed_at) = :stats_year
|
||||
GROUP BY member_id, sys_comp_code
|
||||
) x
|
||||
");
|
||||
$stmtAvg->execute([':stats_year' => $selectedYear]);
|
||||
$avgTotalMinutes = (int)round((float)$stmtAvg->fetchColumn());
|
||||
|
||||
// 온보딩 기준일
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT join_date
|
||||
FROM edu_users
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$joinDate = $stmtUser->fetchColumn();
|
||||
|
||||
$onboardingDueDate = '';
|
||||
$isOnboardingPeriod = 'N';
|
||||
if (!empty($joinDate)) {
|
||||
$onboardingDueDate = date('Y-m-d', strtotime($joinDate . ' +14 days'));
|
||||
$isOnboardingPeriod = (date('Y-m-d') <= $onboardingDueDate) ? 'Y' : 'N';
|
||||
}
|
||||
|
||||
// 선택연도 카테고리별 집계
|
||||
$stmtCategory = $pdo->prepare("
|
||||
SELECT
|
||||
c.category_code,
|
||||
COUNT(DISTINCT lh.content_id) AS learned_content_cnt,
|
||||
COUNT(DISTINCT CASE WHEN lh.watch_tm >= lh.content_tm THEN lh.content_id END) AS completed_content_cnt,
|
||||
COALESCE(SUM(lh.watch_tm), 0) AS total_watch_tm,
|
||||
COALESCE(SUM(lh.content_tm), 0) AS total_content_tm
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND YEAR(lh.last_viewed_at) = :stats_year
|
||||
GROUP BY c.category_code
|
||||
");
|
||||
$stmtCategory->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':stats_year' => $selectedYear,
|
||||
]);
|
||||
|
||||
$aggMap = [];
|
||||
foreach ($stmtCategory->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$aggMap[$row['category_code']] = $row;
|
||||
}
|
||||
|
||||
// 카테고리별 전체 콘텐츠 수
|
||||
$stmtTotalCnt = $pdo->prepare("
|
||||
SELECT
|
||||
category_code,
|
||||
COUNT(*) AS total_cnt
|
||||
FROM edu_contents
|
||||
WHERE (is_active = '1' OR is_active = 'Y')
|
||||
GROUP BY category_code
|
||||
");
|
||||
$stmtTotalCnt->execute();
|
||||
|
||||
$totalCntMap = [];
|
||||
foreach ($stmtTotalCnt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$totalCntMap[$row['category_code']] = (int)$row['total_cnt'];
|
||||
}
|
||||
|
||||
$activityItems = [];
|
||||
foreach ($categoryBase as $categoryCode => $categoryName) {
|
||||
$agg = $aggMap[$categoryCode] ?? null;
|
||||
|
||||
$learnedContentCnt = (int)($agg['learned_content_cnt'] ?? 0);
|
||||
$completedContentCnt = (int)($agg['completed_content_cnt'] ?? 0);
|
||||
$totalWatchTm = (int)($agg['total_watch_tm'] ?? 0);
|
||||
$totalContentTm = (int)($agg['total_content_tm'] ?? 0);
|
||||
$masterTotalCnt = (int)($totalCntMap[$categoryCode] ?? 0);
|
||||
|
||||
// 1차안: gauge는 학습이력 기준 content_tm 합계가 있을 때 그 기준으로 계산
|
||||
// schema상 edu_contents에 표준 duration 컬럼이 없어 완전한 "전체 기준 분모"는 별도 운영정책 확정 필요
|
||||
$percent = ($totalContentTm > 0) ? (int)round(($totalWatchTm / $totalContentTm) * 100) : 0;
|
||||
$percent = max(0, min(100, $percent));
|
||||
|
||||
$activityItems[] = [
|
||||
'category_code' => $categoryCode,
|
||||
'category_name' => $categoryName,
|
||||
'learned_content_cnt' => $learnedContentCnt,
|
||||
'completed_content_cnt' => $completedContentCnt,
|
||||
'master_total_cnt' => $masterTotalCnt,
|
||||
'total_watch_tm' => $totalWatchTm,
|
||||
'total_content_tm' => $totalContentTm,
|
||||
'percent' => $percent,
|
||||
'onboarding_due_date' => ($categoryCode === 'CA10002') ? $onboardingDueDate : '',
|
||||
'is_onboarding_period' => ($categoryCode === 'CA10002') ? $isOnboardingPeriod : 'N',
|
||||
];
|
||||
}
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'year_list' => $yearList,
|
||||
'selected_year' => $selectedYear,
|
||||
'my_total_minutes' => $myTotalMinutes,
|
||||
'avg_total_minutes' => $avgTotalMinutes,
|
||||
'onboarding_due_date'=> $onboardingDueDate,
|
||||
'is_onboarding_period' => $isOnboardingPeriod,
|
||||
'activity_items' => $activityItems,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '학습활동 데이터를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
//[마이페이지][학습레벨] 데이터 동적변환
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
/**
|
||||
* JSON 응답 출력 함수
|
||||
*/
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 학습레벨 메타 반환 함수
|
||||
*/
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'label' => 'Master',
|
||||
'class' => 'master',
|
||||
'icon' => '/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'label' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'icon' => '/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'label' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'icon' => '/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'label' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'icon' => '/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'label' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'icon' => '/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 총 학습시간 기준으로 학습레벨 계산
|
||||
* - 40시간 이상: Master
|
||||
* - 20 ~ 40시간: Elite
|
||||
* - 8 ~ 20시간: Learner
|
||||
* - 0 ~ 8시간: Rookie
|
||||
*/
|
||||
function calc_level_by_minutes(int $totalMinutes): string
|
||||
{
|
||||
if ($totalMinutes >= 2400) {
|
||||
return 'MASTER';
|
||||
}
|
||||
|
||||
if ($totalMinutes >= 1200) {
|
||||
return 'ELITE';
|
||||
}
|
||||
|
||||
if ($totalMinutes >= 480) {
|
||||
return 'LEARNER';
|
||||
}
|
||||
|
||||
return 'ROOKIE';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ★ 선택년도 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
|
||||
$levelCode = 'ROOKIE';
|
||||
$totalMinutes = 0;
|
||||
$statsYear = $selectedYear;
|
||||
|
||||
// ★ 선택년도 통계 우선 조회
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
stats_year,
|
||||
learning_level,
|
||||
total_minutes
|
||||
FROM edu_yearly_learning_stats
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND stats_year = :selected_year
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelCode = (string)($row['learning_level'] ?? 'ROOKIE');
|
||||
$totalMinutes = (int)($row['total_minutes'] ?? 0);
|
||||
$statsYear = (string)($row['stats_year'] ?? $selectedYear);
|
||||
} else {
|
||||
// ★ 선택년도 통계가 없으면 학습이력 합계로 fallback
|
||||
$stmtFallback = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(watch_tm), 0) AS total_minutes
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :selected_year
|
||||
");
|
||||
$stmtFallback->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
|
||||
$totalMinutes = (int)$stmtFallback->fetchColumn();
|
||||
$totalMinutes = floor($totalMinutes / 60);//초를 분으로
|
||||
$levelCode = calc_level_by_minutes($totalMinutes);
|
||||
}
|
||||
|
||||
$levelMeta = mypage_level_meta($levelCode);
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'selected_year' => $selectedYear,
|
||||
'stats_year' => $statsYear,
|
||||
'total_minutes' => $totalMinutes,
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '학습레벨 정보를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
//[마이페이지][제안하기][제안이력리스트] 데이터 동적변환
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$offerStatusMap = [
|
||||
'OF10001' => '제안',
|
||||
'OF10002' => '검토중',
|
||||
'OF10003' => '게시완료',
|
||||
'OF10004' => '게시불가',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'OF100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$offerStatusMap[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
co.offer_id,
|
||||
co.status_code,
|
||||
co.reason,
|
||||
co.reason_return,
|
||||
co.created_at
|
||||
FROM edu_content_offer co
|
||||
WHERE co.member_id = :member_id
|
||||
AND co.sys_comp_code = :sys_comp_code
|
||||
ORDER BY
|
||||
CASE co.status_code
|
||||
WHEN 'OF10002' THEN 0
|
||||
WHEN 'OF10001' THEN 1
|
||||
WHEN 'OF10003' THEN 2
|
||||
WHEN 'OF10004' THEN 3
|
||||
ELSE 9
|
||||
END,
|
||||
co.created_at DESC
|
||||
LIMIT 20
|
||||
");
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
if (!empty($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$statusCode = (string)($row['status_code'] ?? '');
|
||||
$reason = (string)($row['reason'] ?? '');
|
||||
$reason_return = (string)($row['reason_return'] ?? '');
|
||||
$statusName = $offerStatusMap[$statusCode] ?? $statusCode;
|
||||
$isConsider = ($statusCode === 'OF10002');
|
||||
$createdAtDot = !empty($row['created_at']) ? date('y.m.d', strtotime($row['created_at'])) : '';
|
||||
?>
|
||||
<li style="cursor: pointer;" title="클릭 시 상세결과를 볼 수 있습니다." data-offer-date="<?= h($createdAtDot) ?>" data-offer-reason="<?= h($row['reason'] ?? '') ?>" data-offer-return="<?= h($row['reason_return'] ?? '') ?>" class="<?= $isConsider ? 'consider' : '' ?>" data-offer-id="<?= h($row['offer_id'] ?? '') ?>">
|
||||
<span class="suggest-date"><?= h($createdAtDot) ?></span>
|
||||
<?php if ($isConsider): ?>
|
||||
<span class="suggest-dots"></span>
|
||||
<?php endif; ?>
|
||||
<span class="suggest-state"><?= h($statusName) ?></span>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
<li>
|
||||
<span class="suggest-date">-</span>
|
||||
<span class="suggest-state">등록된 제안이 없습니다.</span>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'html' => $html,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '제안현황을 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
//[마이페이지][한줄소감] 데이터 동적변환
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
/**
|
||||
* JSON 응답 출력 함수
|
||||
*/
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML escape 함수
|
||||
*/
|
||||
function h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 카테고리 태그 클래스 반환
|
||||
*/
|
||||
function review_category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ★ 선택년도 파라미터 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
|
||||
// ★ 한줄소감 조회
|
||||
// - 선택년도 기준
|
||||
// - comment 값이 있는 이력만
|
||||
// - 마이클래스(CA10001)만
|
||||
// - 같은 content_id는 최신 시청이력 1건만
|
||||
$sql = "
|
||||
SELECT
|
||||
lh.content_id,
|
||||
lh.member_id,
|
||||
lh.sys_comp_code,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
lh.last_viewed_at,
|
||||
lh.completed_at,
|
||||
lh.comment,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN (
|
||||
SELECT
|
||||
content_id,
|
||||
MAX(last_viewed_at) AS max_last_viewed_at
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :sub_member_id
|
||||
AND sys_comp_code = :sub_sys_comp_code
|
||||
AND YEAR(last_viewed_at) = :sub_selected_year
|
||||
AND comment IS NOT NULL
|
||||
AND TRIM(comment) <> ''
|
||||
GROUP BY content_id
|
||||
) latest
|
||||
ON latest.content_id = lh.content_id
|
||||
AND latest.max_last_viewed_at = lh.last_viewed_at
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
WHERE lh.member_id = :main_member_id
|
||||
AND lh.sys_comp_code = :main_sys_comp_code
|
||||
AND c.category_code = 'CA10001'
|
||||
AND lh.comment IS NOT NULL
|
||||
AND TRIM(lh.comment) <> ''
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':sub_member_id' => $memberId,
|
||||
':sub_sys_comp_code' => $sysCompCode,
|
||||
':sub_selected_year' => (int)$selectedYear,
|
||||
':main_member_id' => $memberId,
|
||||
':main_sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$totalCount = count($rows);
|
||||
|
||||
ob_start();
|
||||
|
||||
if (!empty($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$categoryCode = (string)($row['category_code'] ?? '');
|
||||
$tagClass = review_category_tag_class($categoryCode);
|
||||
|
||||
// ★ 실제 한줄소감은 edu_learning_histories.comment 사용
|
||||
$reviewText = trim((string)($row['comment'] ?? ''));
|
||||
?>
|
||||
<li class="review-item" data-content-id="<?= h($row['content_id'] ?? '') ?>">
|
||||
<div class="review-head">
|
||||
<span class="category-tag <?= h($tagClass) ?>">마이클래스</span>
|
||||
<p class="review-text"><?= h($row['title'] ?? '') ?></p>
|
||||
</div>
|
||||
<div class="review-comment">
|
||||
<span class="review-comment-text">
|
||||
<?= h($reviewText) ?>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<?php
|
||||
}//foreach
|
||||
} else {
|
||||
?>
|
||||
<li class="review-empty">한줄소감이 없습니다.</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'selected_year' => $selectedYear,
|
||||
'total_count' => $totalCount,
|
||||
'html' => $html,
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '한줄소감 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
// 에러 출력 설정
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
set_time_limit(0); // 12,650명 처리를 위해 스크립트 실행 시간 제한 해제
|
||||
|
||||
extract($_REQUEST);
|
||||
|
||||
$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 데이터 양이 많으므로 2분으로 연장
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json"]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("CURL 에러: " . curl_error($ch));
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
die("API 호출 실패 (HTTP 상태 코드: $httpCode)");
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (!isset($data['data']) || !is_array($data['data'])) {
|
||||
die("유효한 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
// DB 연결
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
$conn = db_conn();
|
||||
|
||||
$user_list = $data['data'];
|
||||
$total_count = count($user_list);
|
||||
$success_count = 0;
|
||||
$error_count = 0;
|
||||
|
||||
echo "<h3>인사 정보 동기화 시작 (PDO 방식)</h3>";
|
||||
echo "총 대상 인원: {$total_count}명<br><hr>";
|
||||
|
||||
$SET_injection_at = date('Y-m-d H:i:s');
|
||||
|
||||
$backup_log = "";
|
||||
// ---------------------------------------------------------
|
||||
// [추가] 데이터 주입 전 기존 데이터 백업 (삭제 후 재입력 방식)
|
||||
// ---------------------------------------------------------
|
||||
try {
|
||||
$bk_stmt = $conn->prepare("CALL proc_edu_users_backup(?)");
|
||||
$bk_stmt->execute([$SET_injection_at]);
|
||||
echo "기존 데이터 백업 완료 (이전 백업 삭제 후 재생성)...<br><br>";
|
||||
$backup_log .= "기존 데이터 백업 완료 (이전 백업 삭제 후 재생성) : 백업일시".$SET_injection_at;
|
||||
flush();
|
||||
} catch (PDOException $e) {
|
||||
// 백업 실패 시 안전을 위해 스크립트를 중단하거나 경고를 띄울 수 있습니다.
|
||||
echo "<span style='color:red;'>백업 실패: " . $e->getMessage() . "</span><br>작업을 중단합니다.";
|
||||
$backup_log .= "기존 데이터 백업 실패 : 백업일시".$SET_injection_at;
|
||||
exit;
|
||||
|
||||
}
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// 1. PDO Prepared Statement 준비 (위치 보유자 '?' 사용)
|
||||
$sql = "CALL proc_edu_users_in(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
$stmt = $conn->prepare($sql);
|
||||
|
||||
foreach ($user_list as $row) {
|
||||
// 데이터 정리 (null 처리)
|
||||
$params = [
|
||||
$row['member_id'] ?? '',
|
||||
$row['sys_comp_code'] ?? '',
|
||||
$row['name'] ?? '',
|
||||
$row['dept_name'] ?? '',
|
||||
$row['rank_name'] ?? '',
|
||||
(!empty($row['join_date'])) ? $row['join_date'] : null,
|
||||
$row['belong_comp'] ?? '',
|
||||
$row['working_comp'] ?? '',
|
||||
$row['intra_pw'] ?? '',
|
||||
$row['auth_level'] ?? '',
|
||||
(!empty($row['end_date'])) ? $row['end_date'] : null,
|
||||
$row['intro_flag'] ?? '0',
|
||||
$row['belong_comp_id'] ?? '',
|
||||
$row['working_comp_id'] ?? '',
|
||||
$row['created_at'] ?? date('Y-m-d H:i:s'),
|
||||
$row['updated_at'] ?? date('Y-m-d H:i:s'),
|
||||
$SET_injection_at // injection_at
|
||||
];
|
||||
|
||||
// 2. 실행 (PDO는 execute에 배열을 바로 넣으면 바인딩됩니다)
|
||||
try {
|
||||
if ($stmt->execute($params)) {
|
||||
$success_count++;
|
||||
|
||||
// 대량 데이터 처리 시 진행 상황 출력 (100명 단위)
|
||||
if ($success_count % 1000 == 0) {
|
||||
echo "현재 {$success_count}명 처리 중...<br>";
|
||||
flush(); // 화면에 즉시 반영
|
||||
}
|
||||
} else {
|
||||
$error_count++;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error_count++;
|
||||
// 프로시저 내부 에러 로그는 테이블에 쌓이겠지만 PHP 에러도 확인용 출력
|
||||
echo "에러(ID: " . ($row['member_id'] ?? 'unknown') . "): " . $e->getMessage() . "<br>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 로그 파일 생성 및 저장 로직 (한글 깨짐 방지 적용)
|
||||
// ---------------------------------------------------------
|
||||
$log_dir = $_SERVER['DOCUMENT_ROOT'] . "/log/";
|
||||
$log_file_name = "users_info_batch_" . date('Y') . ".txt";
|
||||
$log_path = $log_dir . $log_file_name;
|
||||
|
||||
// 폴더 생성
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0777, true);
|
||||
}
|
||||
|
||||
// 로그 내용 구성
|
||||
|
||||
if($backup_log==""){
|
||||
$new_log_entry = "주입일자= {$SET_injection_at} , 성공카운트 = {$success_count} , 실패카운트= {$error_count}" . PHP_EOL;
|
||||
}else{
|
||||
$new_log_entry = $backup_log."\n";
|
||||
$new_log_entry .= "주입일자= {$SET_injection_at} , 성공카운트 = {$success_count} , 실패카운트= {$error_count}" . PHP_EOL;
|
||||
}
|
||||
|
||||
|
||||
// UTF-8 BOM (한글 깨짐 방지용 식별코드)
|
||||
$bom = "\xEF\xBB\xBF";
|
||||
|
||||
if (file_exists($log_path)) {
|
||||
// 기존 내용을 읽어옴
|
||||
$existing_content = file_get_contents($log_path);
|
||||
|
||||
// 기존 내용에 BOM이 이미 있다면 제거 (내용 결합 시 중복 방지)
|
||||
$existing_content = str_replace($bom, '', $existing_content);
|
||||
|
||||
// [BOM] + [새 로그] + [기존 로그] 순서로 저장
|
||||
file_put_contents($log_path, $bom . $new_log_entry . $existing_content);
|
||||
} else {
|
||||
// 파일이 처음 생성될 때 BOM과 함께 저장
|
||||
file_put_contents($log_path, $bom . $new_log_entry);
|
||||
}
|
||||
// ---------------------------------------------------------
|
||||
|
||||
echo "<hr>";
|
||||
echo "<strong>처리 완료!</strong><br>";
|
||||
echo "성공: {$success_count}건 / 실패: {$error_count}건";
|
||||
?>
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
// // 1. DB 연결 파일 불러오기 (bbs 폴더 안에 있으므로 경로 주의)
|
||||
// require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
// // 2. 프로시저 파일 불러오기
|
||||
// require_once __DIR__ . '/pros_users_inup.php';
|
||||
|
||||
// extract($_REQUEST);
|
||||
// /* https://baroncs.co.kr/ajax/get_users_info.php
|
||||
// * https://baroncs.co.kr/ajax/get_users_info.php?sys_comp_code=30
|
||||
// *
|
||||
// * $sys_comp_code 필수입력값. EduJsonAPI.php데이터 로직상에는 파라미터가 없을 시,
|
||||
// * 가족사 전체 통합인사정보를 가져와서 일정부분 부하가 예상된다.
|
||||
// * 때문에
|
||||
// * 기본적으로 최소한의 조건 sys_comp_code를 전달받아서
|
||||
// * 해당 회사 인원들에 대한 데이터만 업데이트하는 것으로 한다.
|
||||
// * ※필요 시, 조건에 따라서 가족사전인원 업데이트도 가능하게 할 수 있음
|
||||
// * (예시: 특정 시간대(24:00~05:00)에 접근시에는 전인원 업데이트)
|
||||
// * $member_id
|
||||
// */
|
||||
|
||||
// //배치작업 진행후 작업결과 로그 남길것
|
||||
|
||||
// $sys_comp_code = $sys_comp_code==""?"":$sys_comp_code;
|
||||
// $member_id = $member_id==""?"":$member_id;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// /*
|
||||
// if($sys_comp_code==""){
|
||||
// //echo "Parameter Fail : sys_comp_code is null";
|
||||
// //exit;
|
||||
// }else{
|
||||
|
||||
// if( $sys_comp_code=="10" ||
|
||||
// $sys_comp_code=="20" ||
|
||||
// $sys_comp_code=="30" ||
|
||||
// $sys_comp_code=="40" ||
|
||||
// $sys_comp_code=="50"
|
||||
// ){
|
||||
// echo "Success :";
|
||||
// echo "<br>";
|
||||
// echo "sys_comp_code=".$sys_comp_code;
|
||||
// echo "<br>";
|
||||
// echo "member_id=".$member_id;
|
||||
// //exit;
|
||||
// }else{
|
||||
// echo "Parameter Fail : This is not allowed sys_comp_code";
|
||||
// exit;
|
||||
// }
|
||||
|
||||
|
||||
// }
|
||||
|
||||
// */
|
||||
// $url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php";
|
||||
// //$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php?sys_comp_code=".$sys_comp_code;
|
||||
|
||||
// $ch = curl_init();
|
||||
// curl_setopt($ch, CURLOPT_URL, $url);
|
||||
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
// curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json"]);
|
||||
|
||||
// $response = curl_exec($ch);
|
||||
// $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
|
||||
// if (curl_errno($ch)) {
|
||||
// die("CURL 에러: " . curl_error($ch));
|
||||
// }
|
||||
// curl_close($ch);
|
||||
|
||||
// if ($httpCode !== 200) {
|
||||
// die("API 호출 실패 (HTTP 상태 코드: $httpCode)");
|
||||
// }
|
||||
|
||||
// $data = json_decode($response, true);
|
||||
// //print_r($data);
|
||||
|
||||
// // echo "<br><br>";
|
||||
// // echo "총인원=".count($data['data'])."명";
|
||||
// // echo "<br><br>";
|
||||
// // $TEMP_array_data = $data['data'];
|
||||
// // echo "<br><br>";
|
||||
// // print_r($TEMP_array_data[80]);
|
||||
// // echo "<br><br>";
|
||||
// // print_r($TEMP_array_data);
|
||||
// // echo "<br><br>";
|
||||
|
||||
|
||||
|
||||
// if ($httpCode !== 200) {
|
||||
// die("API 호출 실패 (HTTP 상태 코드: $httpCode)");
|
||||
// }
|
||||
|
||||
// $data = json_decode($response, true);
|
||||
|
||||
// // 3. 실행 및 결과 출력
|
||||
// if (function_exists('db_conn')) {
|
||||
// $pdo = db_conn(); // bbs/db_conn.php에 정의된 함수 호출
|
||||
|
||||
// if (!empty($data['data'])) {
|
||||
// // 송 선임님이 검토 요청하신 그 함수 호출!
|
||||
// $result = processUserSync($pdo, $data['data']);
|
||||
|
||||
// echo "──────────────────────────<br>";
|
||||
// echo "동기화 결과 요약 (".date('Y-m-d H:i:s').")<br>";
|
||||
// echo "- 신규 등록: {$result['ins']}건<br>";
|
||||
// echo "- 정보 수정: {$result['upd']}건<br>";
|
||||
// if($result['err'] > 0) echo "- 오류 발생: {$result['err']}건<br>";
|
||||
// echo "──────────────────────────";
|
||||
// }
|
||||
// } else {
|
||||
// die("에러: db_conn() 함수를 찾을 수 없습니다. 경로를 확인해주세요.");
|
||||
// }
|
||||
|
||||
?>
|
||||
<?php
|
||||
// get_users_info2.php
|
||||
ini_set('display_errors', 1); // 에러 확인용
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
require_once __DIR__ . '/pros_users_inup.php';
|
||||
|
||||
extract($_REQUEST);
|
||||
|
||||
// 1. 파라미터 체크 (테스트를 위해 30번 고정하거나 파라미터 우선)
|
||||
$target_comp = isset($sys_comp_code) && $sys_comp_code != "" ? $sys_comp_code : "30";
|
||||
|
||||
// 2. API URL에 파라미터 붙이기
|
||||
$url = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php?sys_comp_code=" . $target_comp;
|
||||
|
||||
echo "Target Company: " . $target_comp . " (데이터 가져오는 중...)<br>";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 60); // 시간을 넉넉히 60초로 늘림
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json"]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("CURL 에러: " . curl_error($ch));
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (empty($data['data'])) {
|
||||
die("해당 회사({$target_comp})의 데이터가 없거나 API 응답이 비어있습니다.");
|
||||
}
|
||||
|
||||
// 3. DB 연결 및 실행
|
||||
if (function_exists('db_conn')) {
|
||||
$pdo = db_conn();
|
||||
|
||||
echo "총 인원: " . count($data['data']) . "명 처리 시작...<br>";
|
||||
|
||||
// pros_users_inup.php의 함수 호출
|
||||
$result = processUserSync($pdo, $data['data']);
|
||||
|
||||
echo "──────────────────────────<br>";
|
||||
echo "동기화 결과 요약<br>";
|
||||
echo "- 대상 회사 코드: {$target_comp}<br>";
|
||||
echo "- 신규 등록: {$result['ins']}건<br>";
|
||||
echo "- 정보 수정: {$result['upd']}건<br>";
|
||||
if($result['err'] > 0) echo "- 오류 발생: {$result['err']}건<br>";
|
||||
echo "──────────────────────────";
|
||||
}
|
||||
?>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 콘텐츠리스트 AJAX
|
||||
// [마이페이지][시청중인, 시청완료, 저장한 콘텐츠] 데이터 동적변환
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
//keywords_code_name
|
||||
$kwStr = (string)($row['keywords_code_name'] ?? $row['keywords'] ?? '');
|
||||
$keywords = ($kwStr !== '') ? explode(',', $kwStr) : [];
|
||||
|
||||
$watchTm = (float)($row['watch_tm'] ?? 0);
|
||||
$contentTm = (float)($row['content_tm'] ?? 0);
|
||||
$allTm = (float)($row['all_tm'] ?? 0);
|
||||
$gauge = ($contentTm > 0) ? (int)round(($watchTm / $contentTm) * 100) : 0;
|
||||
$gauge = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'id' => $row['content_id'] ?? '',
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'subcate' => (string)($row['category_group'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'description' => trim((string)($row['description'] ?? '')),
|
||||
'description2' => trim((string)($row['description2'] ?? '')),
|
||||
'url' => $raw,
|
||||
'content_url' => $raw,
|
||||
'video_url' => $url,
|
||||
'video_id' => $videoId,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'all_tm' => (int)$allTm,
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'bookmark' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching'));
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// ★ 선택년도 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 탭 카운트 (선택년도 기준)
|
||||
// =========================
|
||||
|
||||
// 시청중인 콘텐츠 카운트
|
||||
/*
|
||||
* $stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
AND YEAR(last_viewed_at) = :selected_year
|
||||
");
|
||||
*/
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(DISTINCT lh.content_id)
|
||||
FROM edu_learning_histories lh
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM edu_learning_histories lh2
|
||||
WHERE lh2.content_id = lh.content_id
|
||||
AND lh2.member_id = lh.member_id
|
||||
AND lh2.sys_comp_code = lh.sys_comp_code
|
||||
AND lh2.completed_at IS NOT NULL
|
||||
)
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
// 시청완료
|
||||
// 기준:
|
||||
// 1) completed_at 이 있는 이력만 완료로 인정
|
||||
// 2) completed_at 기준 연도 필터
|
||||
// 3) 같은 콘텐츠는 최신 완료 1건만 카운트
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT
|
||||
content_id,
|
||||
MAX(completed_at) AS latest_completed_at
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND completed_at IS NOT NULL
|
||||
AND YEAR(completed_at) = :selected_year
|
||||
GROUP BY content_id
|
||||
) t
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
// 저장한 콘텐츠
|
||||
// ★ 저장한 시점 기준 년도(favorited_at 우선, 없으면 updated_at, created_at fallback)
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(DISTINCT content_id)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND is_active = '1'
|
||||
AND YEAR(COALESCE(favorited_at, updated_at, created_at)) = :selected_year
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
// =========================
|
||||
// 탭별 메인 쿼리
|
||||
// =========================
|
||||
if ($tab === 'saved') {
|
||||
// 저장한 콘텐츠 : wishlist 기준
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.title,
|
||||
c.description,
|
||||
c.description2,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
COALESCE(MAX(lh.watch_tm), 0) AS watch_tm,
|
||||
COALESCE(MAX(lh.content_tm), 0) AS content_tm,
|
||||
COALESCE(MAX(lh.all_tm), 0) AS all_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords,
|
||||
MAX(lh.last_viewed_at) AS sort_last_viewed_at,
|
||||
MAX(COALESCE(cw.favorited_at, cw.updated_at, cw.created_at)) AS sort_saved_at
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year_lh
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
AND YEAR(COALESCE(cw.favorited_at, cw.updated_at, cw.created_at)) = :selected_year_saved
|
||||
GROUP BY c.content_id
|
||||
ORDER BY
|
||||
CASE WHEN MAX(lh.last_viewed_at) IS NULL THEN 1 ELSE 0 END ASC,
|
||||
MAX(lh.last_viewed_at) DESC,
|
||||
sort_saved_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
// 시청완료 콘텐츠 completed_at IS NOT NULL (최신 1건)
|
||||
// 기준:
|
||||
// 1) completed_at 이 있는 이력만 완료로 인정
|
||||
// 2) 선택년도는 completed_at 기준
|
||||
// 3) 같은 콘텐츠가 여러 번 완료된 경우 최신 완료 1건만 반영
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.title,
|
||||
c.description,
|
||||
c.description2,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
lh.all_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords,
|
||||
lh.completed_at AS sort_completed_at
|
||||
FROM (
|
||||
SELECT
|
||||
t1.content_id,
|
||||
t1.member_id,
|
||||
t1.sys_comp_code,
|
||||
t1.watch_tm,
|
||||
t1.content_tm,
|
||||
t1.all_tm,
|
||||
t1.completed_at
|
||||
FROM edu_learning_histories t1
|
||||
INNER JOIN (
|
||||
SELECT
|
||||
content_id,
|
||||
MAX(completed_at) AS latest_completed_at
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :completed_member_id_1
|
||||
AND sys_comp_code = :completed_sys_comp_code_1
|
||||
AND completed_at IS NOT NULL
|
||||
AND YEAR(completed_at) = :completed_selected_year_1
|
||||
GROUP BY content_id
|
||||
) t2
|
||||
ON t1.content_id = t2.content_id
|
||||
AND t1.completed_at = t2.latest_completed_at
|
||||
AND t1.member_id = :completed_member_id_2
|
||||
AND t1.sys_comp_code = :completed_sys_comp_code_2
|
||||
) lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
GROUP BY
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.title,
|
||||
c.description,
|
||||
c.description2,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
lh.all_tm,
|
||||
cw.content_id,
|
||||
lh.completed_at
|
||||
ORDER BY lh.completed_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
// 시청중인 콘텐츠 : watch_tm < content_tm
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.title,
|
||||
c.description,
|
||||
c.description2,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
lh.all_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
-- 기존 코드 리스트
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords,
|
||||
-- 추가된 코드명 리스트
|
||||
GROUP_CONCAT(DISTINCT ec.code_name ORDER BY ck.keyword_code SEPARATOR ',') AS keywords_code_name
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
-- [추가] 코드 테이블 조인
|
||||
LEFT JOIN edu_codes ec
|
||||
ON ec.base_code = ck.keyword_code
|
||||
AND ec.group_code = 'KW100'
|
||||
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM edu_learning_histories lh2
|
||||
WHERE lh2.content_id = lh.content_id
|
||||
AND lh2.member_id = lh.member_id
|
||||
AND lh2.sys_comp_code = lh.sys_comp_code
|
||||
AND lh2.completed_at IS NOT NULL
|
||||
)
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
|
||||
";
|
||||
|
||||
/*
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";*/
|
||||
|
||||
// $sql = "
|
||||
// SELECT
|
||||
// c.content_id,
|
||||
// c.category_code,
|
||||
// c.title,
|
||||
// c.content_url,
|
||||
// c.thumbnail_url,
|
||||
// lh.watch_tm,
|
||||
// lh.content_tm,
|
||||
// CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
// GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
// FROM edu_learning_histories lh
|
||||
// INNER JOIN edu_contents c
|
||||
// ON c.content_id = lh.content_id
|
||||
// LEFT JOIN edu_content_wishlist cw
|
||||
// ON cw.content_id = c.content_id
|
||||
// AND cw.member_id = lh.member_id
|
||||
// AND cw.sys_comp_code = lh.sys_comp_code
|
||||
// AND cw.is_active = '1'
|
||||
// LEFT JOIN edu_content_keywords ck
|
||||
// ON ck.content_id = c.content_id
|
||||
// WHERE lh.member_id = :member_id
|
||||
// AND lh.sys_comp_code = :sys_comp_code
|
||||
// AND lh.watch_tm < lh.content_tm
|
||||
// AND YEAR(lh.last_viewed_at) = :selected_year
|
||||
// GROUP BY c.content_id
|
||||
// ORDER BY lh.last_viewed_at DESC
|
||||
// LIMIT :limit OFFSET :offset
|
||||
// ";
|
||||
}
|
||||
|
||||
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$stmt->bindValue(':member_id', $memberId, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':sys_comp_code', $sysCompCode, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':selected_year_lh', (int)$selectedYear, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':selected_year_saved', (int)$selectedYear, PDO::PARAM_INT);
|
||||
|
||||
} elseif ($tab === 'completed') {
|
||||
$stmt->bindValue(':completed_member_id_1', $memberId, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':completed_sys_comp_code_1', $sysCompCode, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':completed_selected_year_1', (int)$selectedYear, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':completed_member_id_2', $memberId, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':completed_sys_comp_code_2', $sysCompCode, PDO::PARAM_STR);
|
||||
|
||||
} else {
|
||||
$stmt->bindValue(':member_id', $memberId, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':sys_comp_code', $sysCompCode, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':selected_year', (int)$selectedYear, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
$videos = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
$videos[] = $item;
|
||||
|
||||
|
||||
$item['url']="#"; //마이페이지용 모달 개발전까지 링크삭제
|
||||
|
||||
?>
|
||||
<li title="클릭 시, 모달 연결은 추후 개발예정"
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'selected_year' => $selectedYear,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'videos' => $videos,
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
//=========================
|
||||
//콘텐츠리스트 AJAX
|
||||
//기존 참고 파일처럼 html 조각을 반환하는 방식으로 잡았습니다.
|
||||
//현재 참고 구현도 코드맵/진행률/북마크/키워드 배열 구조를 그렇게 처리하고 있습니다.
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching')); // watching / completed / saved
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND (is_active = '1' OR is_active = 'Y')
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
GROUP BY c.content_id
|
||||
ORDER BY cw.favorited_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>">
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
//=========================
|
||||
//콘텐츠리스트 AJAX
|
||||
//기존 참고 파일처럼 html 조각을 반환하는 방식으로 잡았습니다.
|
||||
//현재 참고 구현도 코드맵/진행률/북마크/키워드 배열 구조를 그렇게 처리하고 있습니다.
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
function mypage_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function map_content_row(array $row, array $catMap = []): array
|
||||
{
|
||||
$kwStr = (string)($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 = max(0, min(100, $gauge));
|
||||
|
||||
$raw = trim((string)($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((string)($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" : '/edu/img/video/img_thumb_01.png');
|
||||
|
||||
return [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'category_code' => $row['category_code'] ?? '',
|
||||
'category_name' => $catMap[$row['category_code'] ?? ''] ?? ($row['category_code'] ?? ''),
|
||||
'title' => $row['title'] ?? '',
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'gauge' => $gauge,
|
||||
'is_bookmarked' => !empty($row['is_bookmarked']),
|
||||
'keywords' => $keywords,
|
||||
];
|
||||
}
|
||||
|
||||
function category_tag_class(string $categoryCode): string
|
||||
{
|
||||
$map = [
|
||||
'CA10001' => 'myclass',
|
||||
'CA10002' => 'onboarding',
|
||||
'CA10003' => 'legal',
|
||||
'CA10004' => 'leader',
|
||||
'CA10005' => 'insight',
|
||||
'CA10006' => 'biz',
|
||||
];
|
||||
|
||||
return $map[$categoryCode] ?? 'etc';
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$tab = trim((string)($_GET['tab'] ?? 'watching')); // watching / completed / saved
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(1, (int)($_GET['limit'] ?? 12));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA100'
|
||||
AND is_active = '1'
|
||||
ORDER BY code
|
||||
");
|
||||
$stmtCode->execute();
|
||||
foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$CATEGORY_MAP[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 탭 카운트
|
||||
$stmtCountWatching = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm < content_tm
|
||||
");
|
||||
$stmtCountWatching->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countWatching = (int)$stmtCountWatching->fetchColumn();
|
||||
|
||||
$stmtCountCompleted = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND watch_tm >= content_tm
|
||||
");
|
||||
$stmtCountCompleted->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countCompleted = (int)$stmtCountCompleted->fetchColumn();
|
||||
|
||||
$stmtCountSaved = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND (is_active = '1' OR is_active = 'Y')
|
||||
");
|
||||
$stmtCountSaved->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
$countSaved = (int)$stmtCountSaved->fetchColumn();
|
||||
|
||||
if ($tab === 'saved') {
|
||||
$countTotal = $countSaved;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
1 AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_content_wishlist cw
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = cw.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = cw.member_id
|
||||
AND lh.sys_comp_code = cw.sys_comp_code
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE cw.member_id = :member_id
|
||||
AND cw.sys_comp_code = :sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
GROUP BY c.content_id
|
||||
ORDER BY cw.favorited_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} elseif ($tab === 'completed') {
|
||||
$countTotal = $countCompleted;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm >= lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY COALESCE(lh.completed_at, lh.last_viewed_at) DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
} else {
|
||||
$tab = 'watching';
|
||||
$countTotal = $countWatching;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.category_code,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(DISTINCT ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
LEFT JOIN edu_content_wishlist cw
|
||||
ON cw.content_id = c.content_id
|
||||
AND cw.member_id = lh.member_id
|
||||
AND cw.sys_comp_code = lh.sys_comp_code
|
||||
AND (cw.is_active = '1' OR cw.is_active = 'Y')
|
||||
LEFT JOIN edu_content_keywords ck
|
||||
ON ck.content_id = c.content_id
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :sys_comp_code
|
||||
AND lh.watch_tm < lh.content_tm
|
||||
GROUP BY c.content_id
|
||||
ORDER BY lh.last_viewed_at DESC
|
||||
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(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
ob_start();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$item = map_content_row($row, $CATEGORY_MAP);
|
||||
$tagClass = category_tag_class($item['category_code']);
|
||||
?>
|
||||
<li
|
||||
class="content-card <?php if ($tab === 'completed') { ?>content-card--completed<?php } ?>"
|
||||
data-content-id="<?= mypage_h($item['content_id']) ?>"
|
||||
>
|
||||
<a href="<?= mypage_h($item['url']) ?>" class="card-link">
|
||||
<label class="bookmark" for="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>" onclick="event.stopPropagation();">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wish_<?= mypage_h($tab . '_' . $item['content_id']) ?>"
|
||||
title="저장"
|
||||
<?php if ($item['is_bookmarked']) { ?>checked<?php } ?>
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="item-thumb">
|
||||
<img src="<?= mypage_h($item['thumbnail']) ?>" alt="<?= mypage_h($item['title']) ?>">
|
||||
<div class="gauge-bar rect">
|
||||
<div class="gauge-fill" style="width: <?= (int)$item['gauge'] ?>%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
<span class="category-tag <?= mypage_h($tagClass) ?>">
|
||||
<?= mypage_h($item['category_name']) ?>
|
||||
</span>
|
||||
<strong class="item-title"><?= mypage_h($item['title']) ?></strong>
|
||||
|
||||
<div class="tag-list">
|
||||
<?php foreach ($item['keywords'] as $kw): ?>
|
||||
<?php if (trim((string)$kw) === '') { continue; } ?>
|
||||
<span class="tag"><?= mypage_h($kw) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-card" title="삭제"></button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
mypage_json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total_count' => $countTotal,
|
||||
'counts' => [
|
||||
'watching' => $countWatching,
|
||||
'completed' => $countCompleted,
|
||||
'saved' => $countSaved,
|
||||
],
|
||||
'html' => $html,
|
||||
'is_last' => (($offset + $limit) >= $countTotal),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mypage_json([
|
||||
'success' => false,
|
||||
'message' => '콘텐츠 리스트를 불러오지 못했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
//[마이페이지][제안하기][제안등록] DB insert
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
/**
|
||||
* JSON 응답 출력 함수
|
||||
*/
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$typeCode = trim((string)($_POST['type_code'] ?? ''));
|
||||
$statusCode = trim((string)($_POST['status_code'] ?? ''));
|
||||
$referenceUrl = trim((string)($_POST['reference_url'] ?? ''));
|
||||
$reason = trim((string)($_POST['reason'] ?? ''));
|
||||
|
||||
|
||||
//제안자는 URL(reference_url)과 제안이유(reason)만 입력한다.
|
||||
//나머지 입력이 필요한 컬럼은 관리자가 입력하게 된다.(type_code,title)
|
||||
|
||||
// ★ 기본값 보정
|
||||
if ($typeCode === '') {
|
||||
$typeCode = '';
|
||||
}
|
||||
|
||||
if ($statusCode === '') {
|
||||
$statusCode = 'OF10002';
|
||||
}
|
||||
|
||||
// ★ 필수값 검증
|
||||
if ($referenceUrl === '') {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => 'URL을 입력해주세요.'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($reason === '') {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '추천이유를 입력해주세요.'
|
||||
]);
|
||||
}
|
||||
|
||||
// ★ URL 형식 검증
|
||||
if (!filter_var($referenceUrl, FILTER_VALIDATE_URL)) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '올바른 URL 형식이 아닙니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// ★ 해당 년월(YYYYMM) 기준 offer_id 생성
|
||||
// 예: 202603-001, 202603-002
|
||||
$offerYm = date('Ym');
|
||||
|
||||
// ★ 같은 년월 prefix를 가진 마지막 offer_id 조회
|
||||
$stmtSeq = $pdo->prepare("
|
||||
SELECT offer_id
|
||||
FROM edu_content_offer
|
||||
WHERE offer_id LIKE :offer_prefix
|
||||
ORDER BY offer_id DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtSeq->execute([
|
||||
':offer_prefix' => $offerYm . '-%',
|
||||
]);
|
||||
|
||||
$lastOfferId = (string)$stmtSeq->fetchColumn();
|
||||
$nextSeq = 1;
|
||||
|
||||
// ★ 마지막 번호가 있으면 뒤 3자리 숫자를 추출해서 +1
|
||||
if ($lastOfferId !== '') {
|
||||
$lastSeq = (int)substr($lastOfferId, -3);
|
||||
$nextSeq = $lastSeq + 1;
|
||||
}
|
||||
|
||||
$offerId = $offerYm . '-' . str_pad((string)$nextSeq, 3, '0', STR_PAD_LEFT);
|
||||
|
||||
// ★ 저장
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO edu_content_offer (
|
||||
offer_id,
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
reference_url,
|
||||
reason,
|
||||
status_code,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
:offer_id,
|
||||
:member_id,
|
||||
:sys_comp_code,
|
||||
:reference_url,
|
||||
:reason,
|
||||
:status_code,
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':offer_id' => $offerId,
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':reference_url' => $referenceUrl,
|
||||
':reason' => $reason,
|
||||
':status_code' => $statusCode,
|
||||
]);
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'message' => '제안이 정상적으로 등록되었습니다.',
|
||||
'offer_id' => $offerId,
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '제안 등록 중 오류가 발생했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// pros_users_inup.php
|
||||
|
||||
/**
|
||||
* 사용자 정보 동기화 및 이력 저장 함수
|
||||
* @param PDO $pdo DB연결 객체
|
||||
* @param array $userData API로부터 받은 사용자 배열 데이터
|
||||
* @return array 처리 결과 카운트
|
||||
*/
|
||||
function processUserSync($pdo, $userData) {
|
||||
$insCount = 0;
|
||||
$updCount = 0;
|
||||
$errCount = 0;
|
||||
|
||||
foreach ($userData as $user) {
|
||||
$mid = $user['member_id'];
|
||||
$scc = $user['sys_comp_code'];
|
||||
|
||||
if (empty($mid) || empty($scc)) continue; // 필수 키 없으면 패스
|
||||
|
||||
try {
|
||||
// 1. 존재 여부 확인 (복합키 기준)
|
||||
$checkSql = "SELECT COUNT(*) FROM edu_users_temp WHERE member_id = ? AND sys_comp_code = ?";
|
||||
$stmt = $pdo->prepare($checkSql);
|
||||
$stmt->execute([$mid, $scc]);
|
||||
$exists = $stmt->fetchColumn() > 0;
|
||||
|
||||
// 날짜 세팅 (비어있거나 잘못된 날짜 처리)
|
||||
$join_date = (!empty($user['join_date']) && $user['join_date'] != "0000-00-00") ? $user['join_date'] : null;
|
||||
$end_date = (!empty($user['end_date']) && $user['end_date'] != "0000-00-00") ? $user['end_date'] : null;
|
||||
|
||||
if ($exists) {
|
||||
// 2-1. UPDATE (기존 정보 갱신)
|
||||
$sql = "UPDATE edu_users_temp SET
|
||||
name = :name, dept_name = :dept_name, rank_name = :rank_name,
|
||||
join_date = :join_date, belong_comp = :belong_comp, working_comp = :working_comp,
|
||||
end_date = :end_date, belong_comp_id = :belong_comp_id, working_comp_id = :working_comp_id,
|
||||
updated_at = NOW()
|
||||
WHERE member_id = :member_id AND sys_comp_code = :sys_comp_code";
|
||||
$action = "UPDATE";
|
||||
} else {
|
||||
// 2-2. INSERT (신규 등록)
|
||||
$sql = "INSERT INTO edu_users_temp (
|
||||
member_id, sys_comp_code, name, dept_name, rank_name,
|
||||
join_date, belong_comp, working_comp, end_date,
|
||||
belong_comp_id, working_comp_id, created_at, updated_at
|
||||
) VALUES (
|
||||
:member_id, :sys_comp_code, :name, :dept_name, :rank_name,
|
||||
:join_date, :belong_comp, :working_comp, :end_date,
|
||||
:belong_comp_id, :working_comp_id, NOW(), NOW()
|
||||
)";
|
||||
$action = "INSERT";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$res = $stmt->execute([
|
||||
':member_id' => $mid,
|
||||
':sys_comp_code' => $scc,
|
||||
':name' => $user['name'] ?? '',
|
||||
':dept_name' => $user['dept_name'] ?? '',
|
||||
':rank_name' => $user['rank_name'] ?? '',
|
||||
':join_date' => $join_date,
|
||||
':belong_comp' => $user['belong_comp'] ?? '',
|
||||
':working_comp' => $user['working_comp'] ?? '',
|
||||
':end_date' => $end_date,
|
||||
':belong_comp_id' => $user['belong_comp_id'] ?? '',
|
||||
':working_comp_id'=> $user['working_comp_id'] ?? ''
|
||||
]);
|
||||
|
||||
if ($res) {
|
||||
if ($action == "INSERT") {
|
||||
$insCount++;
|
||||
// [INSERT 전용 이력] 생성일시만 기록
|
||||
$hisSql = "INSERT INTO edu_userinfo_history (
|
||||
uh_member_id, uh_sys_comp_code, uh_created_at, uh_updated_at, uh_etc_01
|
||||
) VALUES (
|
||||
:mid, :scc, NOW(), NULL, :etc
|
||||
)";
|
||||
} else {
|
||||
$updCount++;
|
||||
// [UPDATE 전용 이력] 수정일시만 기록
|
||||
$hisSql = "INSERT INTO edu_userinfo_history (
|
||||
uh_member_id, uh_sys_comp_code, uh_created_at, uh_updated_at, uh_etc_01
|
||||
) VALUES (
|
||||
:mid, :scc, NULL, NOW(), :etc
|
||||
)";
|
||||
}
|
||||
|
||||
$hisStmt = $pdo->prepare($hisSql);
|
||||
$hisStmt->execute([
|
||||
':mid' => $mid,
|
||||
':scc' => $scc,
|
||||
':etc' => $action . " 수행 완료"
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Sync Error ($mid): " . $e->getMessage());
|
||||
$errCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return ['ins' => $insCount, 'upd' => $updCount, 'err' => $errCount];
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
//=========================
|
||||
// [마이페이지][북마크 토글]
|
||||
// - 저장한 콘텐츠 체크 / 체크해제 처리
|
||||
// - 체크해제 시 실제 delete 하지 않고 is_active 값을 0으로 update
|
||||
// - 체크 시 is_active 값을 1로 update 또는 신규 insert
|
||||
//=========================
|
||||
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 세션 시작
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
// ---------------------------------------------------------
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
|
||||
// 테스트용 임시값
|
||||
// $memberId = 'U001';
|
||||
// $sysCompCode = 'COMP01';
|
||||
|
||||
/**
|
||||
* JSON 응답 출력 후 종료
|
||||
*/
|
||||
function response_json(array $data): void
|
||||
{
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. 요청 JSON 읽기
|
||||
// - 프런트에서 fetch + JSON body로 전달
|
||||
// ---------------------------------------------------------
|
||||
$raw = file_get_contents('php://input');
|
||||
$req = json_decode($raw, true);
|
||||
|
||||
$contentId = trim((string)($req['content_id'] ?? ''));
|
||||
$isChecked = trim((string)($req['is_checked'] ?? ''));
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. 기본 유효성 검사
|
||||
// ---------------------------------------------------------
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '로그인 정보가 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($contentId === '') {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => 'content_id가 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. 체크값 정규화
|
||||
// - 현재 운영 중 프런트는 Y/N을 보낼 수 있고,
|
||||
// 향후 1/0으로 바뀔 가능성도 있으므로 둘 다 허용
|
||||
// - 최종적으로 DB 저장용 is_active 값은 '1' / '0' 으로 맞춘다.
|
||||
// ---------------------------------------------------------
|
||||
if ($isChecked === 'Y' || $isChecked === '1') {
|
||||
$activeValue = '1';
|
||||
} elseif ($isChecked === 'N' || $isChecked === '0') {
|
||||
$activeValue = '0';
|
||||
} else {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '유효하지 않은 요청입니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. 기존 북마크 이력 존재 여부 확인
|
||||
// - member_id + sys_comp_code + content_id 조합 기준
|
||||
// ---------------------------------------------------------
|
||||
$stmtCheck = $pdo->prepare("
|
||||
SELECT COUNT(*) AS cnt
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND content_id = :content_id
|
||||
");
|
||||
$stmtCheck->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':content_id' => $contentId,
|
||||
]);
|
||||
|
||||
$exists = (int)$stmtCheck->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 5. INSERT / UPDATE 처리
|
||||
//
|
||||
// [기존 row 있음]
|
||||
// - is_active만 update
|
||||
// - updated_at 갱신
|
||||
// - 체크(활성화) 시 favorited_at도 NOW()로 갱신
|
||||
//
|
||||
// [기존 row 없음]
|
||||
// - 신규 insert
|
||||
// - created_at / updated_at 저장
|
||||
// - 체크 상태가 1이면 favorited_at도 NOW() 저장
|
||||
//
|
||||
// ※ 실제 delete 하지 않고 soft delete 방식 유지
|
||||
// ---------------------------------------------------------
|
||||
if ($exists > 0) {
|
||||
if ($activeValue === '1') {
|
||||
// 체크 ON
|
||||
$stmtUpdate = $pdo->prepare("
|
||||
UPDATE edu_content_wishlist
|
||||
SET is_active = :is_active,
|
||||
favorited_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND content_id = :content_id
|
||||
");
|
||||
} else {
|
||||
// 체크 OFF
|
||||
$stmtUpdate = $pdo->prepare("
|
||||
UPDATE edu_content_wishlist
|
||||
SET is_active = :is_active,
|
||||
updated_at = NOW()
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
AND content_id = :content_id
|
||||
");
|
||||
}
|
||||
|
||||
$stmtUpdate->execute([
|
||||
':is_active' => $activeValue,
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':content_id' => $contentId,
|
||||
]);
|
||||
|
||||
} else {
|
||||
if ($activeValue === '1') {
|
||||
// 신규 체크 ON insert
|
||||
$stmtInsert = $pdo->prepare("
|
||||
INSERT INTO edu_content_wishlist (
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
content_id,
|
||||
is_active,
|
||||
favorited_at,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
:member_id,
|
||||
:sys_comp_code,
|
||||
:content_id,
|
||||
:is_active,
|
||||
NOW(),
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
");
|
||||
} else {
|
||||
// 신규 체크 OFF 요청이 들어온 경우도 예외 없이 기록은 남김
|
||||
// (운영상 불필요하다면 이 분기는 막아도 됨)
|
||||
$stmtInsert = $pdo->prepare("
|
||||
INSERT INTO edu_content_wishlist (
|
||||
member_id,
|
||||
sys_comp_code,
|
||||
content_id,
|
||||
is_active,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
:member_id,
|
||||
:sys_comp_code,
|
||||
:content_id,
|
||||
:is_active,
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
$stmtInsert->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':content_id' => $contentId,
|
||||
':is_active' => $activeValue,
|
||||
]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 6. saved 탭 카운트만 재계산
|
||||
// - 북마크 토글은 saved 상태만 변경하므로
|
||||
// watching / completed 카운트는 여기서 건드리지 않는다.
|
||||
// ---------------------------------------------------------
|
||||
$stmtSavedCount = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM edu_content_wishlist cw
|
||||
WHERE cw.member_id = :saved_member_id
|
||||
AND cw.sys_comp_code = :saved_sys_comp_code
|
||||
AND cw.is_active = '1'
|
||||
");
|
||||
$stmtSavedCount->execute([
|
||||
':saved_member_id' => $memberId,
|
||||
':saved_sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$savedCount = (int)$stmtSavedCount->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 7. 성공 응답 반환
|
||||
// ---------------------------------------------------------
|
||||
response_json([
|
||||
'success' => true,
|
||||
'content_id' => $contentId,
|
||||
'is_checked' => $activeValue,
|
||||
'counts' => [
|
||||
'saved' => $savedCount,
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '북마크 처리 중 오류가 발생했습니다.',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
function response_json($data) {
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 세션 (실제 환경 기준)
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode= $_SESSION['sys_comp_code'] ?? '';
|
||||
|
||||
// 테스트용 (필요시 사용)
|
||||
// $memberId = 'U001';
|
||||
// $sysCompCode = 'COMP01';
|
||||
|
||||
|
||||
try {
|
||||
|
||||
if (!$memberId || !$sysCompCode) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '로그인 정보가 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
if (!isset($_FILES['profile_image'])) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '파일이 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
$file = $_FILES['profile_image'];
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '업로드 오류 발생'
|
||||
]);
|
||||
}
|
||||
|
||||
// 2차 검사: 서버에서 3MB 제한
|
||||
$maxSize = 2 * 1024 * 1024; // 2MB
|
||||
if ($file['size'] > $maxSize) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '2MB 이하의 이미지 파일만 업로드 가능합니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// MIME 체크
|
||||
$mime = mime_content_type($file['tmp_name']);
|
||||
if (!in_array($mime, ['image/jpeg', 'image/png'])) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => 'jpg, png 파일만 업로드 가능합니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// 이미지 생성
|
||||
if ($mime === 'image/jpeg') {
|
||||
$srcImage = imagecreatefromjpeg($file['tmp_name']);
|
||||
} else {
|
||||
$srcImage = imagecreatefrompng($file['tmp_name']);
|
||||
}
|
||||
|
||||
if (!$srcImage) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '이미지 처리 실패'
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
// 원본 사이즈
|
||||
$width = imagesx($srcImage);
|
||||
$height = imagesy($srcImage);
|
||||
|
||||
// 새 이미지 생성 (png)
|
||||
$newImage = imagecreatetruecolor($width, $height);
|
||||
|
||||
// 투명 배경 처리
|
||||
imagealphablending($newImage, false);
|
||||
imagesavealpha($newImage, true);
|
||||
|
||||
$transparent = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
|
||||
imagefilledrectangle($newImage, 0, 0, $width, $height, $transparent);
|
||||
|
||||
// 복사
|
||||
imagecopy($newImage, $srcImage, 0, 0, 0, 0, $width, $height);
|
||||
*/
|
||||
// 원본 사이즈
|
||||
$srcWidth = imagesx($srcImage);
|
||||
$srcHeight = imagesy($srcImage);
|
||||
|
||||
// 최대 사이즈 (긴 변 기준)
|
||||
$maxSize = 250;
|
||||
|
||||
// 비율 계산
|
||||
if ($srcWidth >= $srcHeight) {
|
||||
$newWidth = $maxSize;
|
||||
$newHeight = intval($srcHeight * ($maxSize / $srcWidth));
|
||||
} else {
|
||||
$newHeight = $maxSize;
|
||||
$newWidth = intval($srcWidth * ($maxSize / $srcHeight));
|
||||
}
|
||||
|
||||
// 새 이미지 생성
|
||||
$newImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
// 투명 배경
|
||||
imagealphablending($newImage, false);
|
||||
imagesavealpha($newImage, true);
|
||||
|
||||
$transparent = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
|
||||
imagefilledrectangle($newImage, 0, 0, $newWidth, $newHeight, $transparent);
|
||||
|
||||
// 리사이즈 복사
|
||||
imagecopyresampled(
|
||||
$newImage,
|
||||
$srcImage,
|
||||
0, 0, 0, 0,
|
||||
$newWidth, $newHeight,
|
||||
$srcWidth, $srcHeight
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
// 저장 경로
|
||||
$fileName = $memberId . '_' . $sysCompCode . '.png';
|
||||
$savePath = __DIR__ . '/../img/profile/' . $fileName;
|
||||
$saveUrl = '/img/profile/' . $fileName;
|
||||
|
||||
// 저장 (덮어쓰기)
|
||||
if (!imagepng($newImage, $savePath)) {
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '파일 저장 실패'
|
||||
]);
|
||||
}
|
||||
|
||||
// 메모리 해제
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($newImage);
|
||||
|
||||
response_json([
|
||||
'success' => true,
|
||||
'image_url' => $saveUrl
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
|
||||
response_json([
|
||||
'success' => false,
|
||||
'message' => '프로필 업로드 중 오류가 발생했습니다.',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user