Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
//=========================
|
||||
//개인정보 + 최신연도 학습레벨 + 배지
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
if (!function_exists('mypage_h')) {
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_level_meta')) {
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'code' => 'Master',
|
||||
'class' => 'master',
|
||||
'label' => 'Master',
|
||||
'icon' => '/edu/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'code' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'label' => 'Elite',
|
||||
'icon' => '/edu/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'code' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'label' => 'Learner',
|
||||
'icon' => '/edu/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_badge_map')) {
|
||||
function mypage_badge_map(string $badgeCode): array
|
||||
{
|
||||
$badgeCode = strtoupper(trim($badgeCode));
|
||||
|
||||
$BADGE_ICON_MAP = [
|
||||
// 실제 운영 시 코드값 확정되면 여기만 교체
|
||||
'BG001' => [
|
||||
'slot' => 'hat',
|
||||
'img' => '/edu/img/mypage/ico_school.png',
|
||||
'name' => '학습 배지',
|
||||
],
|
||||
'BG002' => [
|
||||
'slot' => 'pencil',
|
||||
'img' => '/edu/img/mypage/ico_pencil.png',
|
||||
'name' => '작성 배지',
|
||||
],
|
||||
'BG003' => [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => 'Pick 배지',
|
||||
],
|
||||
];
|
||||
|
||||
return $BADGE_ICON_MAP[$badgeCode] ?? [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => '기본 배지',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'name' => '사용자',
|
||||
'rank_name' => '',
|
||||
'working_comp' => '',
|
||||
'join_date' => '',
|
||||
'latest_stats_year' => '',
|
||||
'learning_level' => 'Rookie',
|
||||
'level_class' => 'rookie',
|
||||
'level_icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
'total_minutes' => 0,
|
||||
'profile_image' => '/edu/img/insight/profile.png', // 현재는 기본 이미지 고정
|
||||
];
|
||||
|
||||
$profileBadges = [
|
||||
'hat' => null,
|
||||
'pencil' => null,
|
||||
'pick' => null,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 최신년도 학습통계 우선
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.working_comp,
|
||||
u.join_date,
|
||||
ys.stats_year,
|
||||
ys.learning_level,
|
||||
ys.total_minutes
|
||||
FROM edu_users u
|
||||
LEFT JOIN edu_yearly_learning_stats ys
|
||||
ON ys.member_id = u.member_id
|
||||
AND ys.sys_comp_code = u.sys_comp_code
|
||||
AND ys.stats_year = (
|
||||
SELECT MAX(stats_year)
|
||||
FROM edu_yearly_learning_stats
|
||||
WHERE member_id = u.member_id
|
||||
AND sys_comp_code = u.sys_comp_code
|
||||
)
|
||||
WHERE u.member_id = :member_id
|
||||
AND u.sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelMeta = mypage_level_meta($row['learning_level'] ?? '');
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $row['member_id'],
|
||||
'sys_comp_code' => $row['sys_comp_code'],
|
||||
'name' => $row['name'] ?? '사용자',
|
||||
'rank_name' => $row['rank_name'] ?? '',
|
||||
'working_comp' => $row['working_comp'] ?? '',
|
||||
'join_date' => $row['join_date'] ?? '',
|
||||
'latest_stats_year' => $row['stats_year'] ?? '',
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
'total_minutes' => (int)($row['total_minutes'] ?? 0),
|
||||
'profile_image' => '/edu/img/insight/profile.png',
|
||||
];
|
||||
}
|
||||
|
||||
// 최신통계가 없으면 누적 이력 합계 fallback
|
||||
if ((int)$profileData['total_minutes'] === 0) {
|
||||
$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
|
||||
");
|
||||
$stmtFallback->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$fallbackMin = (int)$stmtFallback->fetchColumn();
|
||||
if ($fallbackMin > 0) {
|
||||
$profileData['total_minutes'] = $fallbackMin;
|
||||
}
|
||||
}
|
||||
|
||||
// 배지 조회
|
||||
$stmtBadge = $pdo->prepare("
|
||||
SELECT badge_code, issued_at
|
||||
FROM edu_user_badges
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
ORDER BY issued_at DESC, seq DESC
|
||||
");
|
||||
$stmtBadge->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmtBadge->fetchAll(PDO::FETCH_ASSOC) as $badgeRow) {
|
||||
$badgeInfo = mypage_badge_map($badgeRow['badge_code'] ?? '');
|
||||
$slot = $badgeInfo['slot'];
|
||||
|
||||
if (!isset($profileBadges[$slot]) || $profileBadges[$slot] !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profileBadges[$slot] = [
|
||||
'badge_code' => $badgeRow['badge_code'],
|
||||
'slot' => $slot,
|
||||
'img' => $badgeInfo['img'],
|
||||
'name' => $badgeInfo['name'],
|
||||
'issued_at' => $badgeRow['issued_at'],
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
//=========================
|
||||
//개인정보 + 최신연도 학습레벨 + 배지
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
if (!function_exists('mypage_h')) {
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_level_meta')) {
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'code' => 'Master',
|
||||
'class' => 'master',
|
||||
'label' => 'Master',
|
||||
'icon' => '/edu/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'code' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'label' => 'Elite',
|
||||
'icon' => '/edu/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'code' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'label' => 'Learner',
|
||||
'icon' => '/edu/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_badge_map')) {
|
||||
function mypage_badge_map(string $badgeCode): array
|
||||
{
|
||||
$badgeCode = strtoupper(trim($badgeCode));
|
||||
|
||||
$BADGE_ICON_MAP = [
|
||||
// 실제 운영 시 코드값 확정되면 여기만 교체
|
||||
'BG001' => [
|
||||
'slot' => 'hat',
|
||||
'img' => '/edu/img/mypage/ico_school.png',
|
||||
'name' => '학습 배지',
|
||||
],
|
||||
'BG002' => [
|
||||
'slot' => 'pencil',
|
||||
'img' => '/edu/img/mypage/ico_pencil.png',
|
||||
'name' => '작성 배지',
|
||||
],
|
||||
'BG003' => [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => 'Pick 배지',
|
||||
],
|
||||
];
|
||||
|
||||
return $BADGE_ICON_MAP[$badgeCode] ?? [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => '기본 배지',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'name' => '사용자',
|
||||
'rank_name' => '',
|
||||
'working_comp' => '',
|
||||
'join_date' => '',
|
||||
'latest_stats_year' => '',
|
||||
'learning_level' => 'Rookie',
|
||||
'level_class' => 'rookie',
|
||||
'level_icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
'total_minutes' => 0,
|
||||
'profile_image' => '/edu/img/insight/profile.png', // 현재는 기본 이미지 고정
|
||||
];
|
||||
|
||||
$profileBadges = [
|
||||
'hat' => null,
|
||||
'pencil' => null,
|
||||
'pick' => null,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 최신년도 학습통계 우선
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.working_comp,
|
||||
u.join_date,
|
||||
ys.stats_year,
|
||||
ys.learning_level,
|
||||
ys.total_minutes
|
||||
FROM edu_users u
|
||||
LEFT JOIN edu_yearly_learning_stats ys
|
||||
ON ys.member_id = u.member_id
|
||||
AND ys.sys_comp_code = u.sys_comp_code
|
||||
AND ys.stats_year = (
|
||||
SELECT MAX(stats_year)
|
||||
FROM edu_yearly_learning_stats
|
||||
WHERE member_id = u.member_id
|
||||
AND sys_comp_code = u.sys_comp_code
|
||||
)
|
||||
WHERE u.member_id = :member_id
|
||||
AND u.sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelMeta = mypage_level_meta($row['learning_level'] ?? '');
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $row['member_id'],
|
||||
'sys_comp_code' => $row['sys_comp_code'],
|
||||
'name' => $row['name'] ?? '사용자',
|
||||
'rank_name' => $row['rank_name'] ?? '',
|
||||
'working_comp' => $row['working_comp'] ?? '',
|
||||
'join_date' => $row['join_date'] ?? '',
|
||||
'latest_stats_year' => $row['stats_year'] ?? '',
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
'total_minutes' => (int)($row['total_minutes'] ?? 0),
|
||||
'profile_image' => '/edu/img/insight/profile.png',
|
||||
];
|
||||
}
|
||||
|
||||
// 최신통계가 없으면 누적 이력 합계 fallback
|
||||
if ((int)$profileData['total_minutes'] === 0) {
|
||||
$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
|
||||
");
|
||||
$stmtFallback->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$fallbackMin = (int)$stmtFallback->fetchColumn();
|
||||
if ($fallbackMin > 0) {
|
||||
$profileData['total_minutes'] = $fallbackMin;
|
||||
}
|
||||
}
|
||||
|
||||
// 배지 조회
|
||||
$stmtBadge = $pdo->prepare("
|
||||
SELECT badge_code, issued_at
|
||||
FROM edu_user_badges
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
ORDER BY issued_at DESC, seq DESC
|
||||
");
|
||||
$stmtBadge->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmtBadge->fetchAll(PDO::FETCH_ASSOC) as $badgeRow) {
|
||||
$badgeInfo = mypage_badge_map($badgeRow['badge_code'] ?? '');
|
||||
$slot = $badgeInfo['slot'];
|
||||
|
||||
if (!isset($profileBadges[$slot]) || $profileBadges[$slot] !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profileBadges[$slot] = [
|
||||
'badge_code' => $badgeRow['badge_code'],
|
||||
'slot' => $slot,
|
||||
'img' => $badgeInfo['img'],
|
||||
'name' => $badgeInfo['name'],
|
||||
'issued_at' => $badgeRow['issued_at'],
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 개인정보 + 배지
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
if (!function_exists('mypage_h')) {
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_level_meta')) {
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'code' => 'Master',
|
||||
'class' => 'master',
|
||||
'label' => 'Master',
|
||||
'icon' => '/edu/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'code' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'label' => 'Elite',
|
||||
'icon' => '/edu/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'code' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'label' => 'Learner',
|
||||
'icon' => '/edu/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_badge_map')) {
|
||||
function mypage_badge_map(string $badgeCode): array
|
||||
{
|
||||
$badgeCode = strtoupper(trim($badgeCode));
|
||||
|
||||
$BADGE_ICON_MAP = [
|
||||
// 실제 운영 시 코드값 확정되면 여기만 교체
|
||||
'BG001' => [
|
||||
'slot' => 'hat',
|
||||
'img' => '/edu/img/mypage/ico_school.png',
|
||||
'name' => '학습 배지',
|
||||
],
|
||||
'BG002' => [
|
||||
'slot' => 'pencil',
|
||||
'img' => '/edu/img/mypage/ico_pencil.png',
|
||||
'name' => '작성 배지',
|
||||
],
|
||||
'BG003' => [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => 'Pick 배지',
|
||||
],
|
||||
];
|
||||
|
||||
return $BADGE_ICON_MAP[$badgeCode] ?? [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => '기본 배지',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
//echo "memberId===".$memberId."<br>";
|
||||
//echo "sysCompCode===".$sysCompCode."<br>";exit;
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
|
||||
// ★ 선택년도 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
$profileFileName = $memberId . '_' . $sysCompCode . '.png';
|
||||
$profileFilePath = __DIR__ . '/../img/profile/' . $profileFileName;
|
||||
$profileImageUrl = '/edu/img/profile/' . $profileFileName;
|
||||
|
||||
|
||||
|
||||
if (file_exists($profileFilePath)) {
|
||||
$profileImage = $profileImageUrl;
|
||||
/*
|
||||
if($memberId=="M21420"){
|
||||
echo 11111111;
|
||||
exit;
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
/*
|
||||
if($memberId=="M21420"){
|
||||
echo 222222222222;
|
||||
exit;
|
||||
}*/
|
||||
|
||||
$profileImage = '/edu/img/ico/ico_profile.svg';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
if (file_exists($profileFilePath)) {
|
||||
$profileImage = $profileImageUrl;
|
||||
} else {
|
||||
$profileImage = '/edu/img/ico/ico_profile.svg';
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'name' => '사용자',
|
||||
'rank_name' => '',
|
||||
'working_comp' => '',
|
||||
'join_date' => '',
|
||||
'latest_stats_year' => $selectedYear,
|
||||
'learning_level' => 'Rookie',
|
||||
'level_class' => 'rookie',
|
||||
'level_icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
'total_minutes' => 0,
|
||||
'profile_image' => $profileImage, // 현재는 기본 이미지 고정
|
||||
];
|
||||
|
||||
$profileBadges = [
|
||||
'hat' => null,
|
||||
'pencil' => null,
|
||||
'pick' => null,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ★ 선택년도 학습통계 기준
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.working_comp,
|
||||
u.join_date,
|
||||
|
||||
ec.code,
|
||||
ec.code_name,
|
||||
|
||||
ys.stats_year,
|
||||
ys.learning_level,
|
||||
ys.total_minutes
|
||||
FROM edu_users u
|
||||
LEFT JOIN edu_yearly_learning_stats ys
|
||||
ON ys.member_id = u.member_id
|
||||
AND ys.sys_comp_code = u.sys_comp_code
|
||||
AND ys.stats_year = :selected_year
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT code,code_name FROM edu_codes WHERE group_code = 'CO100'
|
||||
) ec
|
||||
ON ec.code = u.belong_comp
|
||||
WHERE u.member_id = :member_id
|
||||
AND u.sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelMeta = mypage_level_meta($row['learning_level'] ?? '');
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $row['member_id'],
|
||||
'sys_comp_code' => $row['sys_comp_code'],
|
||||
'name' => $row['name'] ?? '사용자',
|
||||
'rank_name' => $row['rank_name'] ?? '',
|
||||
'belong_comp_code' => $row['code'] ?? '',
|
||||
'belong_comp_name' => $row['code_name'] ?? '',
|
||||
'working_comp' => $row['working_comp'] ?? '',
|
||||
'join_date' => $row['join_date'] ?? '',
|
||||
'latest_stats_year' => $row['stats_year'] ?? $selectedYear,
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
'total_minutes' => (int)($row['total_minutes'] ?? 0),
|
||||
'profile_image' => $profileImage, // 현재는 기본 이미지 고정
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
// ★ 선택년도 통계가 없으면 선택년도 학습이력 합계 fallback
|
||||
if ((int)$profileData['total_minutes'] === 0) {
|
||||
$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,
|
||||
]);
|
||||
|
||||
$fallbackMin = (int)$stmtFallback->fetchColumn();
|
||||
if ($fallbackMin > 0) {
|
||||
$profileData['total_minutes'] = $fallbackMin;
|
||||
}
|
||||
}
|
||||
|
||||
// 배지 조회
|
||||
$stmtBadge = $pdo->prepare("
|
||||
SELECT badge_code, issued_at
|
||||
FROM edu_user_badges
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
ORDER BY issued_at DESC, seq DESC
|
||||
");
|
||||
$stmtBadge->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmtBadge->fetchAll(PDO::FETCH_ASSOC) as $badgeRow) {
|
||||
$badgeInfo = mypage_badge_map($badgeRow['badge_code'] ?? '');
|
||||
$slot = $badgeInfo['slot'];
|
||||
|
||||
if (!isset($profileBadges[$slot]) || $profileBadges[$slot] !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profileBadges[$slot] = [
|
||||
'badge_code' => $badgeRow['badge_code'],
|
||||
'slot' => $slot,
|
||||
'img' => $badgeInfo['img'],
|
||||
'name' => $badgeInfo['name'],
|
||||
'issued_at' => $badgeRow['issued_at'],
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 컨텐츠 제안하기 기본값 + 제안현황
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$offerDefaultTypeCode = 'OF10001'; // 제안
|
||||
$offerDefaultStatusCode = 'OF10002'; // 검토중
|
||||
|
||||
$offerStatusMap = [
|
||||
'OF10001' => '제안',
|
||||
'OF10002' => '검토중',
|
||||
'OF10003' => '게시완료',
|
||||
'OF10004' => '게시불가',
|
||||
];
|
||||
|
||||
$offerHistory = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 상태 코드맵 동적 조회 (기본값 fallback 유지)
|
||||
$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 $codeRow) {
|
||||
$offerStatusMap[$codeRow['base_code']] = $codeRow['code_name'];
|
||||
}
|
||||
|
||||
// 제안 이력 조회
|
||||
// 정렬 우선순위:
|
||||
// 1) 검토중(OF10002) 최상단
|
||||
// 2) 그 외 상태는 코드 우선순위대로
|
||||
// 3) 같은 상태 안에서는 최신순
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
co.offer_id,
|
||||
co.type_code,
|
||||
co.title,
|
||||
co.reference_url,
|
||||
co.reason,
|
||||
co.status_code,
|
||||
co.created_at,
|
||||
co.updated_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,
|
||||
co.offer_id DESC
|
||||
LIMIT 20
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$statusCode = trim((string)($row['status_code'] ?? ''));
|
||||
|
||||
$offerHistory[] = [
|
||||
'offer_id' => $row['offer_id'] ?? '',
|
||||
'type_code' => $row['type_code'] ?? '',
|
||||
'title' => $row['title'] ?? '',
|
||||
'reference_url' => $row['reference_url'] ?? '',
|
||||
'reason' => $row['reason'] ?? '',
|
||||
'status_code' => $statusCode,
|
||||
'status_name' => $offerStatusMap[$statusCode] ?? $statusCode,
|
||||
'created_at' => $row['created_at'] ?? '',
|
||||
'updated_at' => $row['updated_at'] ?? '',
|
||||
'created_at_dot' => !empty($row['created_at']) ? date('y.m.d', strtotime($row['created_at'])) : '',
|
||||
'is_consider' => ($statusCode === 'OF10002'),
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
// error_log($e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
//=========================
|
||||
//한줄소감
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$myCommentList = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
lh.content_id,
|
||||
lh.comment,
|
||||
lh.last_viewed_at,
|
||||
c.title,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
u.name
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
INNER JOIN edu_users u
|
||||
ON u.member_id = lh.member_id
|
||||
AND u.sys_comp_code = lh.sys_comp_code
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :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
|
||||
LIMIT 20
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$myCommentList[] = [
|
||||
'content_id' => $row['content_id'],
|
||||
'title' => $row['title'],
|
||||
'comment' => $row['comment'],
|
||||
'member_name' => $row['name'],
|
||||
'last_viewed_at' => $row['last_viewed_at'],
|
||||
'date_dot' => !empty($row['last_viewed_at']) ? date('y.m.d', strtotime($row['last_viewed_at'])) : '',
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../db_conn.php';
|
||||
|
||||
$pdo = db_conn();
|
||||
|
||||
echo "--- SHOW CREATE TABLE ---\n";
|
||||
$row = $pdo->query('SHOW CREATE TABLE edu_learning_histories')->fetch(PDO::FETCH_ASSOC);
|
||||
echo ($row['Create Table'] ?? 'N/A') . "\n\n";
|
||||
|
||||
echo "--- INDEXES ---\n";
|
||||
$stmt = $pdo->query('SHOW INDEX FROM edu_learning_histories');
|
||||
foreach ($stmt as $r) {
|
||||
echo ($r['Key_name'] ?? ''), "\t",
|
||||
($r['Seq_in_index'] ?? ''), "\t",
|
||||
($r['Column_name'] ?? ''), "\t",
|
||||
((int)($r['Non_unique'] ?? 1) === 0 ? 'UNIQUE' : 'NON_UNIQUE'),
|
||||
"\n";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../db_conn.php';
|
||||
|
||||
$pdo = db_conn();
|
||||
$pdo->exec('ALTER TABLE edu_learning_histories DROP PRIMARY KEY, ADD PRIMARY KEY (member_id, sys_comp_code, content_id)');
|
||||
|
||||
echo "ALTER_OK\n";
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
try {
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 1. edu_codes에서 keyword 관련 group_code 확인
|
||||
echo "=== edu_codes: keyword 관련 group_code 목록 ===\n";
|
||||
$stmt = $pdo->query("SELECT group_code, COUNT(*) as cnt FROM edu_codes WHERE group_code LIKE '%KW%' OR base_code LIKE 'KW%' GROUP BY group_code");
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
echo "group_code={$r['group_code']} => {$r['cnt']}건\n";
|
||||
}
|
||||
|
||||
echo "\n=== edu_codes: KW로 시작하는 base_code 전체 ===\n";
|
||||
$stmt = $pdo->query("SELECT group_code, base_code, code_name FROM edu_codes WHERE base_code LIKE 'KW%' ORDER BY group_code, base_code LIMIT 30");
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
echo "[{$r['group_code']}] {$r['base_code']} => {$r['code_name']}\n";
|
||||
}
|
||||
|
||||
echo "\n=== edu_content_keywords: keyword_code 분포 (상위 10개) ===\n";
|
||||
$stmt = $pdo->query("SELECT keyword_code, COUNT(*) as cnt FROM edu_content_keywords GROUP BY keyword_code ORDER BY cnt DESC LIMIT 10");
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
echo "{$r['keyword_code']} => {$r['cnt']}건\n";
|
||||
}
|
||||
|
||||
echo "\n=== edu_contents: 전체 / is_offer=1 / is_offer!=1 건수 ===\n";
|
||||
$total = $pdo->query("SELECT COUNT(*) FROM edu_contents WHERE is_active=1 OR is_active IS NULL")->fetchColumn();
|
||||
$offer = $pdo->query("SELECT COUNT(*) FROM edu_contents WHERE is_offer=1 AND (is_active=1 OR is_active IS NULL)")->fetchColumn();
|
||||
$normal = $pdo->query("SELECT COUNT(*) FROM edu_contents WHERE (is_offer IS NULL OR is_offer!=1) AND (is_active=1 OR is_active IS NULL)")->fetchColumn();
|
||||
echo "전체={$total}, offer(Pick)={$offer}, 일반={$normal}\n";
|
||||
|
||||
echo "\n=== 일반 영상 중 keyword 매핑 있는 건수 ===\n";
|
||||
$cnt = $pdo->query("SELECT COUNT(DISTINCT c.content_id) FROM edu_contents c JOIN edu_content_keywords ck ON ck.content_id=c.content_id WHERE (c.is_offer IS NULL OR c.is_offer!=1) AND (c.is_active=1 OR c.is_active IS NULL)")->fetchColumn();
|
||||
echo "키워드 매핑 있는 일반 영상={$cnt}\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
try {
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
echo "=== 1. edu_codes KW100 키워드 수 ===\n";
|
||||
$cnt = $pdo->query("SELECT COUNT(*) FROM edu_codes WHERE group_code = 'KW100'")->fetchColumn();
|
||||
echo "count={$cnt}\n";
|
||||
|
||||
echo "\n=== 1b. edu_codes 컬럼 목록 ===\n";
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_codes")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($cols as $c) echo $c['Field'] . " (" . $c['Type'] . ")\n";
|
||||
} catch (Exception $e) { echo "에러: " . $e->getMessage() . "\n"; }
|
||||
|
||||
echo "\n=== 1c. main_data.php 실제 쿼리 테스트 ===\n";
|
||||
try {
|
||||
$rows2 = $pdo->query("SELECT base_code AS keyword_code, code_name AS keyword_name FROM edu_codes WHERE group_code = 'KW100' ORDER BY base_code")->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo count($rows2) . "건 반환\n";
|
||||
if ($rows2) echo json_encode($rows2[0], JSON_UNESCAPED_UNICODE) . " ...\n";
|
||||
} catch (Exception $e) { echo "에러: " . $e->getMessage() . "\n"; }
|
||||
|
||||
echo "\n=== 1d. sort_order 포함 쿼리 (이전 방식) ===\n";
|
||||
try {
|
||||
$rows3 = $pdo->query("SELECT base_code AS keyword_code, code_name AS keyword_name FROM edu_codes WHERE group_code = 'KW100' ORDER BY sort_order, base_code")->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo count($rows3) . "건 반환 (sort_order 존재)\n";
|
||||
} catch (Exception $e) { echo "sort_order 없음: " . $e->getMessage() . "\n"; }
|
||||
|
||||
echo "\n=== 2. edu_recommend_keywords 테이블 존재 ===\n";
|
||||
try {
|
||||
$rows = $pdo->query("SELECT * FROM edu_recommend_keywords LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo count($rows) . "건\n";
|
||||
foreach ($rows as $r) echo json_encode($r, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
} catch (Exception $e) { echo "없음: " . $e->getMessage() . "\n"; }
|
||||
|
||||
echo "\n=== 3. edu_user_keywords 테이블 존재 ===\n";
|
||||
try {
|
||||
$cnt2 = $pdo->query("SELECT COUNT(*) FROM edu_user_keywords")->fetchColumn();
|
||||
echo "count={$cnt2}\n";
|
||||
} catch (Exception $e) { echo "없음: " . $e->getMessage() . "\n"; }
|
||||
|
||||
echo "\n=== 4. CREATE TABLE 권한 테스트 ===\n";
|
||||
try {
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS _edu_test_create (id INT) ENGINE=InnoDB");
|
||||
$pdo->exec("DROP TABLE IF EXISTS _edu_test_create");
|
||||
echo "CREATE 권한 있음\n";
|
||||
} catch (Exception $e) { echo "CREATE 권한 없음: " . $e->getMessage() . "\n"; }
|
||||
|
||||
echo "\n=== 5. 세션 memberId/sysCompCode ===\n";
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
$mid = $_SESSION['ss_mb_id'] ?? $_SESSION['member_id'] ?? '(없음)';
|
||||
$sc = $_SESSION['sys_comp_code'] ?? '(없음)';
|
||||
echo "member_id={$mid}, sys_comp_code={$sc}\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$pdo = new PDO(
|
||||
'mysql:host=baroncs.co.kr;port=3306;dbname=baronhomep;charset=utf8mb4',
|
||||
'baronhomep',
|
||||
'baron3840!!',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
]
|
||||
);
|
||||
|
||||
$dbName = 'baronhomep';
|
||||
$table = 'edu_learning_histories';
|
||||
|
||||
$out = [];
|
||||
$out[] = '== TRIGGERS ON edu_learning_histories ==';
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT TRIGGER_NAME, EVENT_MANIPULATION, ACTION_TIMING, EVENT_OBJECT_TABLE, ACTION_STATEMENT
|
||||
FROM information_schema.TRIGGERS
|
||||
WHERE TRIGGER_SCHEMA = ?
|
||||
AND EVENT_OBJECT_TABLE = ?
|
||||
ORDER BY TRIGGER_NAME'
|
||||
);
|
||||
$stmt->execute([$dbName, $table]);
|
||||
$triggers = $stmt->fetchAll();
|
||||
if (!$triggers) {
|
||||
$out[] = '(none)';
|
||||
} else {
|
||||
foreach ($triggers as $t) {
|
||||
$out[] = sprintf('- %s | %s %s ON %s', $t['TRIGGER_NAME'], $t['ACTION_TIMING'], $t['EVENT_MANIPULATION'], $t['EVENT_OBJECT_TABLE']);
|
||||
$out[] = ' ACTION: ' . preg_replace('/\s+/', ' ', (string)$t['ACTION_STATEMENT']);
|
||||
}
|
||||
}
|
||||
|
||||
$out[] = '';
|
||||
$out[] = '== ROUTINES REFERENCING edu_learning_histories ==';
|
||||
$stmt2 = $pdo->prepare(
|
||||
'SELECT ROUTINE_TYPE, ROUTINE_NAME
|
||||
FROM information_schema.ROUTINES
|
||||
WHERE ROUTINE_SCHEMA = ?
|
||||
AND ROUTINE_DEFINITION LIKE ?
|
||||
ORDER BY ROUTINE_TYPE, ROUTINE_NAME'
|
||||
);
|
||||
$stmt2->execute([$dbName, '%edu_learning_histories%']);
|
||||
$routines = $stmt2->fetchAll();
|
||||
if (!$routines) {
|
||||
$out[] = '(none)';
|
||||
} else {
|
||||
foreach ($routines as $r) {
|
||||
$out[] = sprintf('- %s %s', $r['ROUTINE_TYPE'], $r['ROUTINE_NAME']);
|
||||
}
|
||||
}
|
||||
|
||||
$out[] = '';
|
||||
$out[] = '== COLUMNS ==';
|
||||
$stmt3 = $pdo->prepare(
|
||||
'SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
AND TABLE_NAME = ?
|
||||
ORDER BY ORDINAL_POSITION'
|
||||
);
|
||||
$stmt3->execute([$dbName, $table]);
|
||||
$cols = $stmt3->fetchAll();
|
||||
foreach ($cols as $c) {
|
||||
$out[] = sprintf('- %s | %s | NULL=%s | KEY=%s | EXTRA=%s', $c['COLUMN_NAME'], $c['COLUMN_TYPE'], $c['IS_NULLABLE'], $c['COLUMN_KEY'], $c['EXTRA']);
|
||||
}
|
||||
|
||||
echo implode(PHP_EOL, $out) . PHP_EOL;
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* 키워드 맵핑 확인 API
|
||||
* KW10004, KW10005가 무엇으로 맵핑되는지 확인
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$result = [
|
||||
'kw_codes' => ['KW10004', 'KW10005'],
|
||||
'mapping' => [],
|
||||
'all_kw100_sample' => [],
|
||||
'recommend_kw_raw' => [],
|
||||
];
|
||||
|
||||
// 1. KW10004, KW10005의 한글명 확인
|
||||
$stmt = $pdo->prepare("SELECT base_code, code_name FROM edu_codes WHERE base_code IN ('KW10004', 'KW10005')");
|
||||
$stmt->execute();
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$result['mapping'][$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// 2. KW100 그룹의 모든 키워드 샘플
|
||||
$stmt2 = $pdo->query("SELECT base_code, code_name FROM edu_codes WHERE group_code = 'KW100' LIMIT 10");
|
||||
$result['all_kw100_sample'] = $stmt2->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 3. edu_recommend_keywords 원본 데이터
|
||||
$stmt3 = $pdo->query("SELECT * FROM edu_recommend_keywords");
|
||||
$result['recommend_kw_raw'] = $stmt3->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 4. 최종 조인 결과 (main_data.php와 동일한 쿼리)
|
||||
$stmt4 = $pdo->prepare("
|
||||
SELECT rk.keyword_code, ec.code_name AS keyword_name, rk.is_active, rk.sys_comp_code
|
||||
FROM edu_recommend_keywords rk
|
||||
JOIN edu_codes ec ON ec.base_code = rk.keyword_code
|
||||
WHERE rk.is_active = 1
|
||||
");
|
||||
$stmt4->execute();
|
||||
$result['admin_keywords_final'] = $stmt4->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? $_SESSION['user_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? $_SESSION['company'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
$rawBody = file_get_contents('php://input');
|
||||
if (is_string($rawBody) && $rawBody !== '') {
|
||||
$decoded = json_decode($rawBody, true);
|
||||
if (is_array($decoded)) {
|
||||
$payload = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$contentId = trim((string)($payload['content_id'] ?? ''));
|
||||
if ($contentId === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'content_id_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
$stmtUser = $pdo->prepare('SELECT sys_comp_code FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1');
|
||||
$stmtUser->execute([$memberId]);
|
||||
$sysCompCode = (string)($stmtUser->fetchColumn() ?: '');
|
||||
}
|
||||
|
||||
$sql = 'UPDATE edu_learning_histories SET comment = NULL WHERE content_id = ? AND member_id = ?';
|
||||
$params = [$contentId, $memberId];
|
||||
|
||||
if ($sysCompCode !== '') {
|
||||
$sql .= ' AND sys_comp_code = ?';
|
||||
$params[] = $sysCompCode;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
if ($stmt->rowCount() < 1) {
|
||||
echo json_encode(['success' => false, 'message' => '삭제할 소감이 없습니다.'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[clear_comment] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$code = strtoupper(trim((string)($_GET['code'] ?? '')));
|
||||
$code = preg_replace('/[^A-Z0-9]/', '', $code);
|
||||
if ($code === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'code_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$stmt = $pdo->prepare('SELECT code_name FROM edu_codes WHERE REPLACE(REPLACE(UPPER(base_code), "-", ""), " ", "") = ? LIMIT 1');
|
||||
$stmt->execute([$code]);
|
||||
$codeName = (string)($stmt->fetchColumn() ?: '');
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'code' => $code,
|
||||
'code_name' => $codeName,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[code_name] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs 디렉토리 API 공통 함수 모듈
|
||||
* 모든 API 파일에서 공통으로 사용하는 기능 제공
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* API 응답 헤더 설정
|
||||
*/
|
||||
function api_header_json() {
|
||||
if (!headers_sent()) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션 시작 및 로그인 확인
|
||||
*
|
||||
* @return array|null 로그인된 경우 ['member_id' => '...', 'sys_comp_code' => '...'] 반환, 아니면 null
|
||||
*/
|
||||
function api_get_session_user() {
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
if ($memberId === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
if ($sysCompCode === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 체크 후 사용자 정보 반환
|
||||
* 미로그인시 401 에러 응답 후 종료
|
||||
*
|
||||
* @return array ['member_id' => '...', 'sys_comp_code' => '...']
|
||||
*/
|
||||
function api_require_login() {
|
||||
$user = api_get_session_user();
|
||||
if ($user === null) {
|
||||
http_response_code(401);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Unauthorized',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* sys_comp_code 조회 (없으면 DB에서 조회)
|
||||
*
|
||||
* @param PDO $pdo 데이터베이스 연결
|
||||
* @param string $memberId 회원 ID
|
||||
* @param string $preferredCode 우선할 코드 (있으면 사용)
|
||||
* @return string sys_comp_code 또는 빈 문자열
|
||||
*/
|
||||
function api_get_sys_comp_code(PDO $pdo, string $memberId, string $preferredCode = ''): string {
|
||||
if ($preferredCode !== '') {
|
||||
return $preferredCode;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT sys_comp_code FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1');
|
||||
$stmt->execute([$memberId]);
|
||||
return (string)($stmt->fetchColumn() ?: '');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST/JSON 입력 데이터 파싱
|
||||
*
|
||||
* @return array $_POST 또는 JSON decoded 배열
|
||||
*/
|
||||
function api_get_input(): array {
|
||||
$input = json_decode((string)file_get_contents('php://input'), true);
|
||||
if (is_array($input)) {
|
||||
return $input;
|
||||
}
|
||||
return $_GET + $_POST;
|
||||
}
|
||||
|
||||
/**
|
||||
* content_id 유효성 확인
|
||||
*
|
||||
* @param PDO $pdo 데이터베이스 연결
|
||||
* @param string $contentId 확인할 content_id
|
||||
* @return string|null 존재하면 content_id 반환, 없으면 null
|
||||
*/
|
||||
function api_verify_content_id(PDO $pdo, string $contentId): ?string {
|
||||
if ($contentId === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT content_id FROM edu_contents WHERE content_id = ? LIMIT 1');
|
||||
$stmt->execute([$contentId]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
return $row ? (string)($row['content_id']) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 에러 응답
|
||||
*
|
||||
* @param int $httpCode HTTP 응답 코드
|
||||
* @param string $message 에러 메시지
|
||||
* @param array $extra 추가 정보
|
||||
*/
|
||||
function api_error(int $httpCode, string $message, array $extra = []) {
|
||||
http_response_code($httpCode);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
...$extra
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 성공 응답
|
||||
*
|
||||
* @param mixed $data 응답 데이터
|
||||
* @param string|null $message 성공 메시지
|
||||
*/
|
||||
function api_success($data = null, ?string $message = null) {
|
||||
$response = ['success' => true];
|
||||
if ($message !== null) {
|
||||
$response['message'] = $message;
|
||||
}
|
||||
if ($data !== null) {
|
||||
$response['data'] = $data;
|
||||
}
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
$payload = [];
|
||||
$rawBody = file_get_contents('php://input');
|
||||
if (is_string($rawBody) && $rawBody !== '') {
|
||||
$decoded = json_decode($rawBody, true);
|
||||
if (is_array($decoded)) {
|
||||
$payload = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
// id 또는 comment_id 파라미터 지원
|
||||
$commentIdRaw = $payload['id'] ?? $payload['comment_id'] ?? $_POST['id'] ?? $_POST['comment_id'] ?? null;
|
||||
$commentId = is_numeric($commentIdRaw) ? (int)$commentIdRaw : 0;
|
||||
if ($commentId <= 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '삭제할 댓글 ID가 올바르지 않습니다.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'sys_comp_code_missing'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// edu_comments 테이블에서 DELETE (본인 댓글만)
|
||||
$stmt = $pdo->prepare(
|
||||
'DELETE FROM edu_comments WHERE id = ? AND member_id = ? AND sys_comp_code = ?'
|
||||
);
|
||||
$stmt->execute([$commentId, $memberId, $sysCompCode]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '삭제할 댓글이 없거나 권한이 없습니다.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => ['id' => $commentId],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'server_error',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs/api/diagnosis.php — 디버그용 진단 API
|
||||
* 현재 사용자의 세션, 키워드 저장 상태, DB 연결 등을 확인
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
require_once dirname(__DIR__) . '/auth.php';
|
||||
|
||||
edu_start_session();
|
||||
|
||||
$memberId = edu_current_member_id();
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
$diag = [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'session_keys' => array_keys($_SESSION),
|
||||
];
|
||||
|
||||
if ($memberId === '') {
|
||||
echo json_encode(['success' => false, 'error' => 'not_logged_in'] + $diag, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 1. 사용자 정보
|
||||
$userSt = $pdo->prepare('SELECT * FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1');
|
||||
$userSt->execute([$memberId]);
|
||||
$userRow = $userSt->fetch(PDO::FETCH_ASSOC);
|
||||
$diag['user_info'] = $userRow ? [
|
||||
'name' => $userRow['name'] ?? '',
|
||||
'rank_name' => $userRow['rank_name'] ?? '',
|
||||
'sys_comp_code' => $userRow['sys_comp_code'] ?? '',
|
||||
] : null;
|
||||
|
||||
// 2. 저장된 키워드 (sys_comp_code 제약 없이)
|
||||
$kwSt = $pdo->prepare("
|
||||
SELECT uk.keyword_code, ec.code_name AS keyword_name, uk.member_id, uk.sys_comp_code
|
||||
FROM edu_user_keywords uk
|
||||
JOIN edu_codes ec ON ec.base_code = uk.keyword_code
|
||||
WHERE uk.member_id = ?
|
||||
ORDER BY uk.keyword_code
|
||||
");
|
||||
$kwSt->execute([$memberId]);
|
||||
$savedKws = $kwSt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$diag['saved_keywords'] = $savedKws;
|
||||
$diag['saved_keywords_count'] = count($savedKws);
|
||||
|
||||
// 3. 테이블 존재 확인
|
||||
$tblCheck = [];
|
||||
foreach (['edu_user_keywords', 'edu_codes', 'edu_recommend_keywords', 'edu_contents'] as $tbl) {
|
||||
try {
|
||||
$cnt = $pdo->query("SELECT COUNT(*) FROM {$tbl}")->fetchColumn();
|
||||
$tblCheck[$tbl] = $cnt;
|
||||
} catch (Exception $e) {
|
||||
$tblCheck[$tbl] = 'ERROR: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
$diag['table_check'] = $tblCheck;
|
||||
|
||||
// 3b. edu_recommend_keywords 컬럼 확인
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_recommend_keywords")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$diag['edu_recommend_keywords_columns'] = array_column($cols, 'Field');
|
||||
} catch (Exception $e) {
|
||||
$diag['edu_recommend_keywords_columns'] = 'ERROR: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
// 4. 회사 추천 키워드 샘플 (main_data.php와 동일 규칙)
|
||||
try {
|
||||
$admSt = $pdo->prepare("
|
||||
SELECT rk.keyword_code, ec.code_name AS keyword_name, rk.is_active, rk.sys_comp_code
|
||||
FROM edu_recommend_keywords rk
|
||||
JOIN edu_codes ec ON ec.base_code = rk.keyword_code
|
||||
WHERE rk.is_active = 1
|
||||
AND rk.keyword_code IS NOT NULL
|
||||
AND rk.keyword_code <> ''
|
||||
AND ec.group_code = 'KW100'
|
||||
ORDER BY rk.keyword_code
|
||||
LIMIT 2
|
||||
");
|
||||
$admSt->execute();
|
||||
$admKws = $admSt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$diag['admin_keywords_sample'] = $admKws;
|
||||
$diag['admin_keywords_count'] = count($admKws);
|
||||
} catch (Exception $e) {
|
||||
$diag['admin_keywords_sample'] = 'ERROR: ' . $e->getMessage();
|
||||
$diag['admin_keywords_count'] = 0;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true] + $diag, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
] + $diag, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$contentId = trim((string)($_GET['content_id'] ?? ''));
|
||||
if ($contentId === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'content_id_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$stmtCategory = $pdo->prepare('SELECT category_code FROM edu_contents WHERE content_id = ? LIMIT 1');
|
||||
$stmtCategory->execute([$contentId]);
|
||||
$categoryCode = strtoupper((string)($stmtCategory->fetchColumn() ?: ''));
|
||||
|
||||
// ── 마이클래스(CA10001): edu_learning_histories.comment 에서 본인 소감 조회 ──
|
||||
if ($categoryCode === 'CA10001') {
|
||||
$sqlLh = 'SELECT lh.member_id, COALESCE(u.name, lh.member_id) AS member_name,
|
||||
lh.comment, lh.last_viewed_at
|
||||
FROM edu_learning_histories lh
|
||||
LEFT JOIN edu_users u ON u.member_id = lh.member_id AND u.sys_comp_code = lh.sys_comp_code
|
||||
WHERE lh.content_id = ? AND lh.member_id = ?';
|
||||
$paramsLh = [$contentId, $memberId];
|
||||
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
if ($sysCompCode !== '') {
|
||||
$sqlLh .= ' AND lh.sys_comp_code = ?';
|
||||
$paramsLh[] = $sysCompCode;
|
||||
}
|
||||
|
||||
$sqlLh .= ' ORDER BY lh.last_viewed_at DESC LIMIT 1';
|
||||
$stmtLh = $pdo->prepare($sqlLh);
|
||||
$stmtLh->execute($paramsLh);
|
||||
$row = $stmtLh->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$data = [];
|
||||
if ($row && $row['comment'] !== null && trim((string)$row['comment']) !== '') {
|
||||
$authorId = (string)($row['member_id'] ?? '');
|
||||
$profileFilePath = __DIR__ . '/../../img/profile/' . $authorId . '_' . $sysCompCode . '.png';
|
||||
$profileImageUrl = file_exists($profileFilePath)
|
||||
? '/img/profile/' . $authorId . '_' . $sysCompCode . '.png'
|
||||
: '/img/ico/ico_user.svg';
|
||||
$data[] = [
|
||||
'id' => $contentId,
|
||||
'member_id' => $authorId,
|
||||
'member_name' => (string)($row['member_name'] ?? $authorId),
|
||||
'comment' => (string)$row['comment'],
|
||||
'created_at' => (string)($row['last_viewed_at'] ?? ''),
|
||||
'updated_at' => (string)($row['last_viewed_at'] ?? ''),
|
||||
'is_author' => true,
|
||||
'profile_image' => $profileImageUrl,
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'category' => $categoryCode,
|
||||
'data' => $data,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 기타 카테고리: 기존 edu_comments 테이블 조회 ──
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
$params = [$contentId];
|
||||
$sql = 'SELECT c.id, c.member_id, c.sys_comp_code, COALESCE(u.name, c.member_id) AS member_name, c.comment, c.created_at, c.updated_at
|
||||
FROM edu_comments c
|
||||
LEFT JOIN edu_users u ON u.member_id = c.member_id AND u.sys_comp_code = c.sys_comp_code
|
||||
WHERE c.content_id = ?';
|
||||
|
||||
$sql .= ' ORDER BY c.id DESC';
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
$data = array_map(static function (array $row) use ($memberId, $sysCompCode): array {
|
||||
$authorId = (string)($row['member_id'] ?? '');
|
||||
$rowSysCompCode = (string)($row['sys_comp_code'] ?? '');
|
||||
$profileFilePath = __DIR__ . '/../../img/profile/' . $authorId . '_' . $rowSysCompCode . '.png';
|
||||
$profileImageUrl = file_exists($profileFilePath)
|
||||
? '/img/profile/' . $authorId . '_' . $rowSysCompCode . '.png'
|
||||
: '/img/ico/ico_user.svg';
|
||||
return [
|
||||
'id' => (int)($row['id'] ?? 0),
|
||||
'member_id' => $authorId,
|
||||
'member_name' => (string)($row['member_name'] ?? $authorId),
|
||||
'comment' => (string)($row['comment'] ?? ''),
|
||||
'created_at' => (string)($row['created_at'] ?? ''),
|
||||
'updated_at' => (string)($row['updated_at'] ?? ''),
|
||||
'is_author' => ($authorId === $memberId && $rowSysCompCode === $sysCompCode),
|
||||
'profile_image' => $profileImageUrl,
|
||||
];
|
||||
}, $rows);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'category' => $categoryCode,
|
||||
'data' => $data,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[get_comments] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
require_once __DIR__ . '/common.php';
|
||||
|
||||
$sessionUser = api_get_session_user();
|
||||
$memberId = (string)($sessionUser['member_id'] ?? $_SESSION['member_id'] ?? '');
|
||||
$sysCompCode = (string)($sessionUser['sys_comp_code'] ?? $_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$fixedLegalNameMap = [
|
||||
'C01' => '개인정보보호',
|
||||
'C02' => '직장내 괴롭힘 예방',
|
||||
'C03' => '장애인 인식 개선',
|
||||
'C04' => '성희롱 예방 교육',
|
||||
'C05' => '퇴직금 교육',
|
||||
];
|
||||
|
||||
$normalizeLegalGroupCode = static function ($value) {
|
||||
$code = strtoupper(trim((string)$value));
|
||||
if (preg_match('/^CA200(C\d{2})$/', $code, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
if (preg_match('/^C\d{2}$/', $code)) {
|
||||
return $code;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
$legalCodes = array_keys($fixedLegalNameMap);
|
||||
$legalRawCodes = [];
|
||||
foreach ($legalCodes as $code) {
|
||||
$legalRawCodes[] = $code;
|
||||
$legalRawCodes[] = 'CA200' . $code;
|
||||
}
|
||||
$legalRawCodes = array_values(array_unique($legalRawCodes));
|
||||
$phCodes = implode(',', array_fill(0, count($legalRawCodes), '?'));
|
||||
|
||||
// 법정교육(CA10003) 컨텐츠를 category_group 기준으로 조회
|
||||
$sql = "
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.description,
|
||||
c.sort_order,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
lh.all_tm,
|
||||
lh.completed_at
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
WHERE c.category_code = 'CA10003'
|
||||
AND c.is_active = 1
|
||||
AND c.category_group IN ($phCodes)
|
||||
ORDER BY c.category_group ASC, c.sort_order ASC, c.content_id ASC
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute(array_merge([$memberId, $sysCompCode], $legalRawCodes));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
$chapterMap = [];
|
||||
foreach ($legalCodes as $index => $code) {
|
||||
$chapterMap[$code] = [
|
||||
'code' => $code,
|
||||
'name' => $fixedLegalNameMap[$code] ?? $code,
|
||||
'lessons' => [],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$groupCode = $normalizeLegalGroupCode($row['category_group'] ?? '');
|
||||
if ($groupCode === '' || !isset($chapterMap[$groupCode])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// YouTube videoId 추출
|
||||
$raw = trim($row['content_url'] ?? '');
|
||||
$videoId = '';
|
||||
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;
|
||||
}
|
||||
|
||||
$title = $row['title'] ?? '';
|
||||
$watchTm = (int)($row['watch_tm'] ?? 0);
|
||||
$contentTm = (int)($row['content_tm'] ?? 0);
|
||||
$completed = !empty($row['completed_at']);
|
||||
|
||||
$chapterMap[$groupCode]['lessons'][] = [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'title' => $title,
|
||||
'url' => $videoId,
|
||||
'description'=> (string)($row['description'] ?? ''),
|
||||
'sort_order' => (int)($row['sort_order'] ?? 0),
|
||||
'watch_tm' => $watchTm,
|
||||
'content_tm' => $contentTm,
|
||||
'all_tm' => (int)($row['all_tm'] ?? 0),
|
||||
'completed' => $completed,
|
||||
];
|
||||
}
|
||||
|
||||
$chapters = [];
|
||||
foreach ($legalCodes as $code) {
|
||||
$chapter = $chapterMap[$code];
|
||||
usort($chapter['lessons'], static function ($left, $right) {
|
||||
$sortDiff = (int)($left['sort_order'] ?? 0) <=> (int)($right['sort_order'] ?? 0);
|
||||
if ($sortDiff !== 0) {
|
||||
return $sortDiff;
|
||||
}
|
||||
|
||||
return strcmp((string)($left['content_id'] ?? ''), (string)($right['content_id'] ?? ''));
|
||||
});
|
||||
|
||||
$chapters[] = [
|
||||
'code' => $chapter['code'],
|
||||
'name' => $chapter['name'],
|
||||
'lessons' => array_map(static function ($lesson) {
|
||||
unset($lesson['sort_order']);
|
||||
return $lesson;
|
||||
}, $chapter['lessons']),
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'chapters' => $chapters,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'server_error',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
require_once __DIR__ . '/common.php';
|
||||
|
||||
$sessionUser = api_get_session_user();
|
||||
$memberId = (string)($sessionUser['member_id'] ?? $_SESSION['member_id'] ?? '');
|
||||
$sysCompCode = (string)($sessionUser['sys_comp_code'] ?? $_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$onboardingGroups = [
|
||||
'CA200O01', 'CA200O02', 'CA200O03', 'CA200O04', 'CA200O05',
|
||||
'CA200O06', 'CA200O07', 'CA200O08', 'CA200O09', 'CA200O10',
|
||||
];
|
||||
$placeholders = implode(',', array_fill(0, count($onboardingGroups), '?'));
|
||||
|
||||
$categoryMap = [
|
||||
'CA200O01' => ['pieceId' => 1, 'group' => 3, 'area' => 'value'],
|
||||
'CA200O02' => ['pieceId' => 2, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O03' => ['pieceId' => 3, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O04' => ['pieceId' => 4, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O05' => ['pieceId' => 5, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O06' => ['pieceId' => 6, 'group' => 3, 'area' => 'value'],
|
||||
'CA200O07' => ['pieceId' => 7, 'group' => 4, 'area' => 'company'],
|
||||
'CA200O08' => ['pieceId' => 8, 'group' => 4, 'area' => 'company'],
|
||||
'CA200O09' => ['pieceId' => 9, 'group' => 1, 'area' => 'family'],
|
||||
'CA200O10' => ['pieceId' => 10, 'group' => 1, 'area' => 'family'],
|
||||
];
|
||||
|
||||
$chapterNameMap = [];
|
||||
try {
|
||||
$stmtCodes = $pdo->prepare("\n SELECT base_code, code_name\n FROM edu_codes\n WHERE group_code = 'CA200'\n AND base_code IN ('CA200O01','CA200O02','CA200O03','CA200O04','CA200O05','CA200O06','CA200O07','CA200O08','CA200O09','CA200O10')\n ORDER BY base_code\n ");
|
||||
$stmtCodes->execute();
|
||||
foreach (($stmtCodes->fetchAll(PDO::FETCH_ASSOC) ?: []) as $codeRow) {
|
||||
$baseCode = (string)($codeRow['base_code'] ?? '');
|
||||
$codeName = trim((string)($codeRow['code_name'] ?? ''));
|
||||
if ($baseCode !== '' && $codeName !== '') {
|
||||
$chapterNameMap[$baseCode] = $codeName;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 코드명 조회 실패 시 콘텐츠 제목으로 폴백한다.
|
||||
}
|
||||
|
||||
$useContentHistories = true;
|
||||
try {
|
||||
$checkTable = $pdo->query("SHOW TABLES LIKE 'edu_content_histories'");
|
||||
if (!$checkTable || $checkTable->rowCount() === 0) {
|
||||
$useContentHistories = false;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$useContentHistories = false;
|
||||
}
|
||||
|
||||
if ($useContentHistories) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("\n SELECT\n c.*,\n COALESCE(lh.watch_tm, 0) AS watch_tm,\n COALESCE(lh.content_tm, 0) AS content_tm,\n lh.completed_at AS lh_completed_at,\n CASE\n WHEN ch.content_id IS NULL THEN 'none'\n WHEN ch.completed_at IS NOT NULL THEN 'completed'\n ELSE 'in_progress'\n END AS learning_status\n FROM edu_contents c\n LEFT JOIN edu_learning_histories lh\n ON lh.content_id = c.content_id\n AND lh.member_id = ?\n AND lh.sys_comp_code = ?\n LEFT JOIN edu_content_histories ch\n ON ch.content_id = c.content_id\n AND ch.member_id = ?\n AND ch.sys_comp_code = ?\n WHERE c.category_group IN ($placeholders)\n ORDER BY FIELD(c.category_group, 'CA200O01','CA200O02','CA200O03','CA200O04','CA200O05','CA200O06','CA200O07','CA200O08','CA200O09','CA200O10'), COALESCE(NULLIF(c.sort_order, 0), 9999), c.content_id\n ");
|
||||
$stmt->execute(array_merge([$memberId, $sysCompCode, $memberId, $sysCompCode], $onboardingGroups));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
} catch (Throwable $e) {
|
||||
$useContentHistories = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$useContentHistories) {
|
||||
$stmt = $pdo->prepare("\n SELECT\n c.*,\n COALESCE(lh.watch_tm, 0) AS watch_tm,\n COALESCE(lh.content_tm, 0) AS content_tm,\n lh.completed_at AS lh_completed_at,\n CASE\n WHEN lh.content_id IS NULL THEN 'none'\n WHEN lh.completed_at IS NOT NULL THEN 'completed'\n ELSE 'in_progress'\n END AS learning_status\n FROM edu_contents c\n LEFT JOIN edu_learning_histories lh\n ON lh.content_id = c.content_id\n AND lh.member_id = ?\n AND lh.sys_comp_code = ?\n WHERE c.category_group IN ($placeholders)\n ORDER BY FIELD(c.category_group, 'CA200O01','CA200O02','CA200O03','CA200O04','CA200O05','CA200O06','CA200O07','CA200O08','CA200O09','CA200O10'), COALESCE(NULLIF(c.sort_order, 0), 9999), c.content_id\n ");
|
||||
$stmt->execute(array_merge([$memberId, $sysCompCode], $onboardingGroups));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
|
||||
$chapterMap = [];
|
||||
foreach ($rows as $index => $row) {
|
||||
$categoryGroup = (string)($row['category_group'] ?? '');
|
||||
if ($categoryGroup === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapped = $categoryMap[$categoryGroup] ?? null;
|
||||
$description = trim((string)($row['description'] ?? $row['description1'] ?? $row['description_1'] ?? $row['content_desc'] ?? $row['content_desc1'] ?? $row['content_description'] ?? $row['content_description1'] ?? ''));
|
||||
$description2 = trim((string)($row['description2'] ?? $row['description_2'] ?? $row['content_desc2'] ?? $row['content_description2'] ?? ''));
|
||||
|
||||
if (!isset($chapterMap[$categoryGroup])) {
|
||||
$chapterMap[$categoryGroup] = [
|
||||
'id' => 0,
|
||||
'name' => $chapterNameMap[$categoryGroup] ?? str_replace('[온보딩] ', '', (string)($row['title'] ?? '')),
|
||||
'pieceId' => (int)($mapped['pieceId'] ?? ($index + 1)),
|
||||
'type' => 'youtube',
|
||||
'group' => (int)($mapped['group'] ?? 1),
|
||||
'category_group' => $categoryGroup,
|
||||
'area' => (string)($mapped['area'] ?? 'family'),
|
||||
'lessons' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$learningStatus = (string)($row['learning_status'] ?? 'none');
|
||||
|
||||
$chapterMap[$categoryGroup]['lessons'][] = [
|
||||
'content_id' => (string)($row['content_id'] ?? ''),
|
||||
'bookmark_content_id' => (string)($row['content_id'] ?? ''),
|
||||
'comment_content_id' => (string)($row['content_id'] ?? ''),
|
||||
'title' => (string)($row['title'] ?? ''),
|
||||
'label' => str_replace('[온보딩] ', '', (string)($row['title'] ?? '')),
|
||||
'description' => $description,
|
||||
'description2' => $description2,
|
||||
'url' => (string)($row['content_url'] ?? ''),
|
||||
'completed' => ($learningStatus === 'completed'),
|
||||
'learning_status' => $learningStatus,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'sort_order' => (int)($row['sort_order'] ?? 0),
|
||||
'is_bookmarked' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$bookmarkIds = [];
|
||||
foreach ($chapterMap as $chapterItem) {
|
||||
foreach (($chapterItem['lessons'] ?? []) as $lessonItem) {
|
||||
$bookmarkId = trim((string)($lessonItem['bookmark_content_id'] ?? ''));
|
||||
if ($bookmarkId !== '') {
|
||||
$bookmarkIds[] = $bookmarkId;
|
||||
}
|
||||
}
|
||||
}
|
||||
$bookmarkIds = array_values(array_unique($bookmarkIds));
|
||||
|
||||
$wishlistMap = [];
|
||||
if (!empty($bookmarkIds)) {
|
||||
$wishlistPlaceholders = implode(',', array_fill(0, count($bookmarkIds), '?'));
|
||||
$stmtWishlist = $pdo->prepare(
|
||||
"SELECT content_id, is_active\n FROM edu_content_wishlist\n WHERE member_id = ?\n AND sys_comp_code = ?\n AND content_id IN ($wishlistPlaceholders)"
|
||||
);
|
||||
$stmtWishlist->execute(array_merge([$memberId, $sysCompCode], $bookmarkIds));
|
||||
foreach (($stmtWishlist->fetchAll(PDO::FETCH_ASSOC) ?: []) as $wishlistRow) {
|
||||
$wishlistMap[(string)$wishlistRow['content_id']] = ((string)($wishlistRow['is_active'] ?? '0') === '1');
|
||||
}
|
||||
}
|
||||
|
||||
$chapters = [];
|
||||
foreach ($onboardingGroups as $groupCode) {
|
||||
if (!isset($chapterMap[$groupCode])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$chapter = $chapterMap[$groupCode];
|
||||
usort($chapter['lessons'], static function ($left, $right) {
|
||||
$sortDiff = (int)($left['sort_order'] ?? 0) <=> (int)($right['sort_order'] ?? 0);
|
||||
if ($sortDiff !== 0) {
|
||||
return $sortDiff;
|
||||
}
|
||||
|
||||
return strcmp((string)($left['content_id'] ?? ''), (string)($right['content_id'] ?? ''));
|
||||
});
|
||||
|
||||
$chapter['id'] = count($chapters) + 1;
|
||||
$chapter['lessons'] = array_map(static function ($lesson) use ($wishlistMap) {
|
||||
$bookmarkId = trim((string)($lesson['bookmark_content_id'] ?? ''));
|
||||
$lesson['is_bookmarked'] = ($bookmarkId !== '' && !empty($wishlistMap[$bookmarkId]));
|
||||
unset($lesson['sort_order']);
|
||||
return $lesson;
|
||||
}, $chapter['lessons']);
|
||||
|
||||
$chapters[] = $chapter;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'chapters' => $chapters,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'server_error',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$contentId = trim((string)($_GET['content_id'] ?? ''));
|
||||
if ($contentId === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'content_id_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$normCode = static function (string $code): string {
|
||||
return preg_replace('/[^A-Z0-9]/', '', strtoupper(trim($code)));
|
||||
};
|
||||
$looksLikeCode = static function (string $value): bool {
|
||||
return preg_match('/^[A-Z]{2}[A-Z0-9]{3,}$/', strtoupper(trim($value))) === 1;
|
||||
};
|
||||
$codeMap = [];
|
||||
try {
|
||||
$stmtCodeMap = $pdo->query("SELECT base_code, code_name FROM edu_codes");
|
||||
foreach ($stmtCodeMap->fetchAll(PDO::FETCH_ASSOC) as $cr) {
|
||||
$key = $normCode((string)($cr['base_code'] ?? ''));
|
||||
if ($key === '') continue;
|
||||
$codeMap[$key] = (string)($cr['code_name'] ?? '');
|
||||
}
|
||||
} catch (Throwable $ignore) {
|
||||
$codeMap = [];
|
||||
}
|
||||
|
||||
// 1) 현재 영상의 키워드 코드 조회
|
||||
$keywordCodes = [];
|
||||
try {
|
||||
$stmtKw = $pdo->prepare('SELECT keyword_code FROM edu_content_keywords WHERE content_id = ?');
|
||||
$stmtKw->execute([$contentId]);
|
||||
$keywordCodes = $stmtKw->fetchAll(PDO::FETCH_COLUMN) ?: [];
|
||||
} catch (Throwable $kwErr) {
|
||||
// edu_content_keywords 테이블이 없는 경우 무시 → fallback
|
||||
$keywordCodes = [];
|
||||
}
|
||||
|
||||
// 키워드 없으면 같은 카테고리 영상으로 fallback
|
||||
if (empty($keywordCodes)) {
|
||||
// 현재 영상의 카테고리 조회
|
||||
$stmtCat = $pdo->prepare('SELECT category_code FROM edu_contents WHERE content_id = ? LIMIT 1');
|
||||
$stmtCat->execute([$contentId]);
|
||||
$curCatCode = $stmtCat->fetchColumn();
|
||||
|
||||
if (!$curCatCode) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'keywords' => [],
|
||||
'videos' => [],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmtFallback = $pdo->prepare(
|
||||
"SELECT c.content_id, c.title, c.content_url, c.thumbnail_url,
|
||||
c.category_code, c.category_group, c.description,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec ON TRIM(UPPER(ec.base_code)) = TRIM(UPPER(c.category_code))
|
||||
LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
WHERE c.category_code = ?
|
||||
AND c.content_id != ?
|
||||
AND c.is_active = 1
|
||||
ORDER BY c.start_date DESC
|
||||
LIMIT 8"
|
||||
);
|
||||
$stmtFallback->execute([$curCatCode, $contentId]);
|
||||
$rows = $stmtFallback->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
$videos = [];
|
||||
foreach ($rows as $row) {
|
||||
$raw = trim($row['content_url'] ?? '');
|
||||
$videoId = '';
|
||||
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;
|
||||
}
|
||||
$thumbFromDb = trim($row['thumbnail_url'] ?? '');
|
||||
$thumbnail = $thumbFromDb !== ''
|
||||
? $thumbFromDb
|
||||
: ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : '');
|
||||
$catCode = $row['category_code'] ?? '';
|
||||
$subcate = trim((string)($row['category_group_name'] ?? $row['category_group'] ?? ''));
|
||||
$catName = trim((string)($row['category_name'] ?? $catCode));
|
||||
if (($catName === '' || $looksLikeCode($catName)) && $catCode !== '') {
|
||||
$mapped = $codeMap[$normCode($catCode)] ?? '';
|
||||
if ($mapped !== '') $catName = $mapped;
|
||||
}
|
||||
$groupRaw = trim((string)($row['category_group'] ?? ''));
|
||||
if (($subcate === '' || $looksLikeCode($subcate)) && $groupRaw !== '') {
|
||||
$mapped = $codeMap[$normCode($groupRaw)] ?? '';
|
||||
if ($mapped !== '') $subcate = $mapped;
|
||||
}
|
||||
$videos[] = [
|
||||
'content_id' => $row['content_id'],
|
||||
'title' => $row['title'] ?? '',
|
||||
'content_url' => $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : $raw,
|
||||
'thumbnail' => $thumbnail,
|
||||
'category_code' => $catCode,
|
||||
'category' => $catName,
|
||||
'category_name' => $catName,
|
||||
'subcate' => $subcate,
|
||||
'description' => $row['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'keywords' => [],
|
||||
'videos' => $videos,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2) 키워드명 조회 (표시용)
|
||||
$kwPlaceholders = implode(',', array_fill(0, count($keywordCodes), '?'));
|
||||
$keywordNames = [];
|
||||
try {
|
||||
$stmtKwNames = $pdo->prepare(
|
||||
"SELECT keyword_code, keyword_name FROM edu_keywords WHERE keyword_code IN ({$kwPlaceholders})"
|
||||
);
|
||||
$stmtKwNames->execute($keywordCodes);
|
||||
while ($row = $stmtKwNames->fetch(PDO::FETCH_ASSOC)) {
|
||||
$keywordNames[] = $row['keyword_name'];
|
||||
}
|
||||
} catch (Throwable $kwNameErr) {
|
||||
$keywordNames = [];
|
||||
}
|
||||
|
||||
// 3) 동일 키워드를 가진 다른 영상 조회 (현재 영상 제외, 랜덤 8개)
|
||||
$rows = [];
|
||||
try {
|
||||
$stmtVideos = $pdo->prepare(
|
||||
"SELECT DISTINCT
|
||||
c.content_id,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.description,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec ON TRIM(UPPER(ec.base_code)) = TRIM(UPPER(c.category_code))
|
||||
LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
INNER JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
WHERE ck.keyword_code IN ({$kwPlaceholders})
|
||||
AND c.content_id != ?
|
||||
AND c.is_active = 1
|
||||
ORDER BY RAND()
|
||||
LIMIT 8"
|
||||
);
|
||||
$params = array_merge($keywordCodes, [$contentId]);
|
||||
$stmtVideos->execute($params);
|
||||
$rows = $stmtVideos->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
} catch (Throwable $kwVideoErr) {
|
||||
$rows = [];
|
||||
}
|
||||
|
||||
// 키워드 매칭 결과 없으면 같은 카테고리 fallback
|
||||
if (empty($rows)) {
|
||||
$stmtCat = $pdo->prepare('SELECT category_code FROM edu_contents WHERE content_id = ? LIMIT 1');
|
||||
$stmtCat->execute([$contentId]);
|
||||
$curCatCode = $stmtCat->fetchColumn();
|
||||
if ($curCatCode) {
|
||||
$stmtFb = $pdo->prepare(
|
||||
"SELECT c.content_id, c.title, c.content_url, c.thumbnail_url,
|
||||
c.category_code, c.category_group, c.description,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec ON TRIM(UPPER(ec.base_code)) = TRIM(UPPER(c.category_code))
|
||||
LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
WHERE c.category_code = ?
|
||||
AND c.content_id != ?
|
||||
AND c.is_active = 1
|
||||
ORDER BY c.start_date DESC
|
||||
LIMIT 8"
|
||||
);
|
||||
$stmtFb->execute([$curCatCode, $contentId]);
|
||||
$rows = $stmtFb->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
$videos = [];
|
||||
foreach ($rows as $row) {
|
||||
$raw = trim($row['content_url'] ?? '');
|
||||
$videoId = '';
|
||||
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;
|
||||
}
|
||||
|
||||
$thumbFromDb = trim($row['thumbnail_url'] ?? '');
|
||||
$thumbnail = $thumbFromDb !== ''
|
||||
? $thumbFromDb
|
||||
: ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : '');
|
||||
|
||||
$catCode = $row['category_code'] ?? '';
|
||||
$subcate = trim((string)($row['category_group_name'] ?? $row['category_group'] ?? ''));
|
||||
$catName = trim((string)($row['category_name'] ?? $catCode));
|
||||
if (($catName === '' || $looksLikeCode($catName)) && $catCode !== '') {
|
||||
$mapped = $codeMap[$normCode($catCode)] ?? '';
|
||||
if ($mapped !== '') $catName = $mapped;
|
||||
}
|
||||
$groupRaw = trim((string)($row['category_group'] ?? ''));
|
||||
if (($subcate === '' || $looksLikeCode($subcate)) && $groupRaw !== '') {
|
||||
$mapped = $codeMap[$normCode($groupRaw)] ?? '';
|
||||
if ($mapped !== '') $subcate = $mapped;
|
||||
}
|
||||
|
||||
$videos[] = [
|
||||
'content_id' => $row['content_id'],
|
||||
'title' => $row['title'] ?? '',
|
||||
'content_url' => $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : $raw,
|
||||
'thumbnail' => $thumbnail,
|
||||
'category_code' => $catCode,
|
||||
'category' => $catName,
|
||||
'category_name' => $catName,
|
||||
'subcate' => $subcate,
|
||||
'description' => $row['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'keywords' => $keywordNames,
|
||||
'videos' => $videos,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('[get_recommend_videos] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
//마이클래스 영상모달 관련영상 리스트 API - 목표코드(goal_code) 기반
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$contentId = trim((string)($_GET['content_id'] ?? ''));
|
||||
if ($contentId === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'content_id_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$stmtCurrent = $pdo->prepare(
|
||||
"SELECT content_id, goal_code
|
||||
FROM edu_contents
|
||||
WHERE content_id = ?
|
||||
AND is_active = 1
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmtCurrent->execute([$contentId]);
|
||||
$current = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
|
||||
if (!$current) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'mode' => 'goal_code',
|
||||
'goal_code' => null,
|
||||
'keywords' => [],
|
||||
'videos' => [],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$goalCode = trim((string)($current['goal_code'] ?? ''));
|
||||
if ($goalCode === '') {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'mode' => 'goal_code',
|
||||
'goal_code' => '',
|
||||
'keywords' => [],
|
||||
'videos' => [],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmtVideos = $pdo->prepare(
|
||||
"SELECT
|
||||
c.content_id,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.description,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name,
|
||||
c.goal_code
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec
|
||||
ON TRIM(UPPER(ec.base_code)) = TRIM(UPPER(c.category_code))
|
||||
LEFT JOIN edu_codes ec_grp
|
||||
ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
WHERE c.is_active = 1
|
||||
AND c.goal_code = ?
|
||||
AND c.content_id <> ?
|
||||
ORDER BY
|
||||
COALESCE(NULLIF(c.sort_order, 0), 9999) ASC,
|
||||
c.updated_at DESC,
|
||||
c.content_id DESC
|
||||
LIMIT 6"
|
||||
);
|
||||
$stmtVideos->execute([$goalCode, $contentId]);
|
||||
$rows = $stmtVideos->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
$videos = [];
|
||||
foreach ($rows as $row) {
|
||||
$raw = trim((string)($row['content_url'] ?? ''));
|
||||
$videoId = '';
|
||||
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;
|
||||
}
|
||||
|
||||
$thumbFromDb = trim((string)($row['thumbnail_url'] ?? ''));
|
||||
$thumbnail = $thumbFromDb !== ''
|
||||
? $thumbFromDb
|
||||
: ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : '');
|
||||
|
||||
$catCode = (string)($row['category_code'] ?? '');
|
||||
$subcate = trim((string)($row['category_group_name'] ?? $row['category_group'] ?? ''));
|
||||
$catName = trim((string)($row['category_name'] ?? $catCode));
|
||||
|
||||
$videos[] = [
|
||||
'content_id' => $row['content_id'] ?? '',
|
||||
'title' => (string)($row['title'] ?? ''),
|
||||
'content_url' => $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : $raw,
|
||||
'thumbnail' => $thumbnail,
|
||||
'category_code' => $catCode,
|
||||
'category' => $catName,
|
||||
'category_name' => $catName,
|
||||
'subcate' => $subcate,
|
||||
'description' => (string)($row['description'] ?? ''),
|
||||
'goal_code' => (string)($row['goal_code'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'mode' => 'goal_code',
|
||||
'goal_code' => $goalCode,
|
||||
'keywords' => [],
|
||||
'videos' => $videos,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('[get_recommend_videos] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* 영상 시청 시간 조회 API
|
||||
* POST /bbs/api/get_video_time.php
|
||||
*
|
||||
* Parameters:
|
||||
* - content_id (필수): 영상 content ID (string)
|
||||
*
|
||||
* Returns:
|
||||
* - watch_tm : edu_learning_histories.watch_tm (이어보기 기준 시간, 초)
|
||||
* - content_tm : edu_learning_histories.content_tm (영상 전체 길이, 초)
|
||||
*/
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$member_id = trim((string)($_SESSION['member_id'] ?? ''));
|
||||
$sys_comp_code = trim((string)($_SESSION['sys_comp_code'] ?? ''));
|
||||
|
||||
if ($member_id === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($sys_comp_code === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'sys_comp_code_missing']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// content_id: JSON body 또는 POST 폼 데이터 모두 허용
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$jsonBody = (is_string($rawBody) && $rawBody !== '') ? json_decode($rawBody, true) : null;
|
||||
$content_id = trim((string)(
|
||||
(is_array($jsonBody) ? ($jsonBody['content_id'] ?? '') : '') ?:
|
||||
($_POST['content_id'] ?? '') ?:
|
||||
($_GET['content_id'] ?? '')
|
||||
));
|
||||
|
||||
if ($content_id === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid content_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
// edu_learning_histories 에서 watch_tm / content_tm 조회
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT watch_tm, content_tm
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND content_id = ?
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->execute([$member_id, $sys_comp_code, $content_id]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$watch_tm = (int)($row['watch_tm'] ?? 0);
|
||||
$content_tm = (int)($row['content_tm'] ?? 0);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'content_id' => $content_id,
|
||||
'watch_tm' => $watch_tm,
|
||||
'content_tm' => $content_tm,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('[get_video_time.php] Error: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'server_error']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* edu_goal_contents 자동 매핑 스크립트
|
||||
* category_group 기준으로 goal_code와 content_id 연결
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
try {
|
||||
// 1. 모든 goal_code를 category_group별로 그룹화
|
||||
$goals = $pdo->query("
|
||||
SELECT goal_code, quarter
|
||||
FROM edu_learning_goals
|
||||
WHERE is_active = '1'
|
||||
ORDER BY goal_code
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// quarter를 category_group으로 맵핑하는 쿼리
|
||||
// CA200Q01 → CA200Q01, CA200Q02 → CA200Q02 등
|
||||
|
||||
// 2. 각 quarter의 영상들을 조회
|
||||
$inserted = 0;
|
||||
foreach ($goals as $goal) {
|
||||
$goalCode = $goal['goal_code'];
|
||||
$quarter = $goal['quarter']; // 예: CA200Q01
|
||||
|
||||
// 같은 quarter의 영상 6개 조회
|
||||
$contents = $pdo->query("
|
||||
SELECT content_id
|
||||
FROM edu_contents
|
||||
WHERE category_group = ?
|
||||
AND content_id NOT IN (
|
||||
SELECT content_id FROM edu_goal_contents
|
||||
WHERE category_group = ?
|
||||
)
|
||||
ORDER BY content_id ASC
|
||||
LIMIT 6
|
||||
")->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Wait - category_group이 edu_goal_contents에 없네
|
||||
// 대신 이미 다른 goal에 할당된 content를 피해야 함
|
||||
|
||||
$contents = $pdo->query("
|
||||
SELECT content_id
|
||||
FROM edu_contents
|
||||
WHERE category_group = ?
|
||||
AND content_id NOT IN (
|
||||
SELECT content_id FROM edu_goal_contents
|
||||
)
|
||||
ORDER BY content_id ASC
|
||||
LIMIT 6
|
||||
")->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// INSERT
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO edu_goal_contents
|
||||
(goal_code, content_id, is_active, sort_order)
|
||||
VALUES (?, ?, '1', ?)
|
||||
");
|
||||
|
||||
foreach ($contents as $idx => $contentId) {
|
||||
$stmt->execute([$goalCode, $contentId, $idx + 1]);
|
||||
$inserted++;
|
||||
}
|
||||
|
||||
echo "- $goalCode: " . count($contents) . "개 영상 매핑\n";
|
||||
}
|
||||
|
||||
// 1. quarter별로 goal들을 그룹화
|
||||
$quarterGoals = $pdo->query("
|
||||
SELECT quarter, GROUP_CONCAT(goal_code ORDER BY goal_code) as goals
|
||||
FROM edu_learning_goals
|
||||
WHERE is_active = '1'
|
||||
GROUP BY quarter
|
||||
ORDER BY quarter
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$inserted = 0;
|
||||
|
||||
// 2. 각 quarter마다 처리
|
||||
foreach ($quarterGoals as $qg) {
|
||||
$quarter = $qg['quarter'];
|
||||
$goalCodes = explode(',', $qg['goals']);
|
||||
|
||||
// 해당 quarter의 모든 영상 조회
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT content_id
|
||||
FROM edu_contents
|
||||
WHERE category_group = ?
|
||||
ORDER BY content_id ASC
|
||||
");
|
||||
$stmt->execute([$quarter]);
|
||||
$allContents = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// 3. 영상들을 goal별로 6개씩 분배
|
||||
$contentIdx = 0;
|
||||
foreach ($goalCodes as $goalCode) {
|
||||
$goalCode = trim($goalCode);
|
||||
|
||||
// 이 goal을 위해 6개 영상 선택
|
||||
$goalContents = array_slice($allContents, $contentIdx, 6);
|
||||
$contentIdx += 6;
|
||||
|
||||
// INSERT
|
||||
$stmtInsert = $pdo->prepare("
|
||||
INSERT INTO edu_goal_contents
|
||||
(goal_code, content_id, is_active, sort_order)
|
||||
VALUES (?, ?, '1', ?)
|
||||
");
|
||||
|
||||
foreach ($goalContents as $idx => $contentId) {
|
||||
$stmtInsert->execute([$goalCode, $contentId, $idx + 1]);
|
||||
$inserted++;
|
||||
}
|
||||
|
||||
echo "- {$goalCode}: " . count($goalContents) . "개 영상 (quarter: {$quarter})\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "총 {$inserted}개 행 INSERT 완료",
|
||||
'result' => "goal_code와 content_id 매핑 완료"
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
|
||||
function json_exit(array $payload, int $status = 200): void {
|
||||
http_response_code($status);
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function resolve_member_and_company(PDO $pdo): array {
|
||||
$sessionMemberId = trim((string)($_SESSION['ss_mb_id'] ?? $_SESSION['member_id'] ?? ''));
|
||||
$memberId = $sessionMemberId !== '' ? $sessionMemberId : 'U001';
|
||||
|
||||
$stmtUser = $pdo->prepare('SELECT sys_comp_code FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1');
|
||||
$stmtUser->execute([$memberId]);
|
||||
$sysCompCode = (string)($stmtUser->fetchColumn() ?: '');
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
json_exit([
|
||||
'success' => false,
|
||||
'message' => 'edu_users에 회원 정보가 없어 마이클래스 상태를 조회할 수 없습니다.',
|
||||
'data' => ['member_id' => $memberId],
|
||||
], 400);
|
||||
}
|
||||
|
||||
return [$memberId, $sysCompCode];
|
||||
}
|
||||
|
||||
function ensure_goal_contents(PDO $pdo, string $goalCode, string $quarter, string $actor): int {
|
||||
$stmtCnt = $pdo->prepare("SELECT COUNT(*) FROM edu_goal_contents WHERE goal_code = ? AND (is_active = '1' OR is_active IS NULL)");
|
||||
$stmtCnt->execute([$goalCode]);
|
||||
$existing = (int)$stmtCnt->fetchColumn();
|
||||
if ($existing >= 6) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$stmtCandidates = $pdo->prepare(
|
||||
"SELECT c.content_id
|
||||
FROM edu_contents c
|
||||
WHERE (c.is_active = '1' OR c.is_active IS NULL)
|
||||
AND (c.goal_code = :goal_code OR c.category_group = :quarter)
|
||||
ORDER BY
|
||||
CASE WHEN c.sort_order IS NULL THEN 1 ELSE 0 END,
|
||||
c.sort_order ASC,
|
||||
c.content_id ASC
|
||||
LIMIT 6"
|
||||
);
|
||||
$stmtCandidates->execute([
|
||||
':goal_code' => $goalCode,
|
||||
':quarter' => $quarter,
|
||||
]);
|
||||
$candidateIds = $stmtCandidates->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (count($candidateIds) < 6) {
|
||||
$stmtFallback = $pdo->prepare(
|
||||
"SELECT c.content_id
|
||||
FROM edu_contents c
|
||||
WHERE (c.is_active = '1' OR c.is_active IS NULL)
|
||||
ORDER BY
|
||||
CASE WHEN c.sort_order IS NULL THEN 1 ELSE 0 END,
|
||||
c.sort_order ASC,
|
||||
c.content_id ASC
|
||||
LIMIT 6"
|
||||
);
|
||||
$stmtFallback->execute();
|
||||
$candidateIds = $stmtFallback->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
$candidateIds = array_values(array_unique(array_filter(array_map('strval', $candidateIds))));
|
||||
|
||||
$stmtUpsert = $pdo->prepare(
|
||||
"INSERT INTO edu_goal_contents
|
||||
(goal_code, content_id, index_id, is_active, sort_order, created_by, created_at, updated_by, updated_at)
|
||||
VALUES
|
||||
(?, ?, ?, '1', ?, ?, NOW(), ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
is_active = VALUES(is_active),
|
||||
sort_order = VALUES(sort_order),
|
||||
updated_by = VALUES(updated_by),
|
||||
updated_at = NOW()"
|
||||
);
|
||||
|
||||
foreach ($candidateIds as $idx => $contentId) {
|
||||
$order = $idx + 1;
|
||||
$stmtUpsert->execute([$goalCode, $contentId, $order, $order, $actor, $actor]);
|
||||
}
|
||||
|
||||
$stmtCnt->execute([$goalCode]);
|
||||
return (int)$stmtCnt->fetchColumn();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
[$memberId, $sysCompCode] = resolve_member_and_company($pdo);
|
||||
|
||||
$stmtGoals = $pdo->query(
|
||||
"SELECT goal_code, title, quarter, goal_no, sort_order
|
||||
FROM edu_learning_goals
|
||||
WHERE (is_active = '1' OR is_active IS NULL)
|
||||
ORDER BY base_year DESC, quarter ASC, goal_no ASC, goal_code ASC"
|
||||
);
|
||||
$goals = $stmtGoals->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$goals) {
|
||||
json_exit(['success' => true, 'data' => ['goals' => [], 'videos' => []]]);
|
||||
}
|
||||
|
||||
$stmtSelected = $pdo->prepare(
|
||||
"SELECT ug.goal_code, ug.quarter, ug.completed_date, ug.updated_at, lg.title
|
||||
FROM edu_user_learning_goals ug
|
||||
LEFT JOIN edu_learning_goals lg ON lg.goal_code = ug.goal_code
|
||||
WHERE ug.member_id = ?
|
||||
AND ug.sys_comp_code = ?
|
||||
AND (ug.is_active = '1' OR ug.is_active IS NULL)
|
||||
ORDER BY ug.updated_at DESC, ug.goal_code DESC"
|
||||
);
|
||||
$stmtSelected->execute([$memberId, $sysCompCode]);
|
||||
$selectedGoals = $stmtSelected->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$requestedGoalCode = trim((string)($_GET['goal_code'] ?? ''));
|
||||
$activeGoalCode = $requestedGoalCode;
|
||||
if ($activeGoalCode === '' && !empty($selectedGoals)) {
|
||||
$activeGoalCode = (string)$selectedGoals[0]['goal_code'];
|
||||
}
|
||||
if ($activeGoalCode === '') {
|
||||
$activeGoalCode = (string)$goals[0]['goal_code'];
|
||||
}
|
||||
|
||||
$goalIndex = [];
|
||||
foreach ($goals as $g) {
|
||||
$goalIndex[(string)$g['goal_code']] = $g;
|
||||
}
|
||||
|
||||
if (!isset($goalIndex[$activeGoalCode])) {
|
||||
$activeGoalCode = (string)$goals[0]['goal_code'];
|
||||
}
|
||||
|
||||
$activeQuarter = (string)($goalIndex[$activeGoalCode]['quarter'] ?? '');
|
||||
ensure_goal_contents($pdo, $activeGoalCode, $activeQuarter, $memberId);
|
||||
|
||||
$stmtVideos = $pdo->prepare(
|
||||
"SELECT
|
||||
gc.goal_code,
|
||||
gc.content_id,
|
||||
gc.sort_order,
|
||||
gc.index_id,
|
||||
c.title,
|
||||
c.description,
|
||||
c.description2,
|
||||
c.content_url,
|
||||
c.thumbnail_url,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
lh.completed_at
|
||||
FROM edu_goal_contents gc
|
||||
JOIN edu_contents c ON c.content_id = gc.content_id
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
AND lh.content_id = gc.content_id
|
||||
WHERE gc.goal_code = ?
|
||||
AND (gc.is_active = '1' OR gc.is_active IS NULL)
|
||||
ORDER BY
|
||||
CASE WHEN gc.sort_order IS NULL THEN 1 ELSE 0 END,
|
||||
gc.sort_order ASC,
|
||||
CASE WHEN gc.index_id IS NULL THEN 1 ELSE 0 END,
|
||||
gc.index_id ASC,
|
||||
gc.content_id ASC
|
||||
LIMIT 6"
|
||||
);
|
||||
$stmtVideos->execute([$memberId, $sysCompCode, $activeGoalCode]);
|
||||
$videos = $stmtVideos->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$contentIds = array_values(array_unique(array_filter(array_map(static function ($r) {
|
||||
return (string)($r['content_id'] ?? '');
|
||||
}, $videos))));
|
||||
|
||||
$memoMap = [];
|
||||
if (!empty($contentIds)) {
|
||||
$in = implode(',', array_fill(0, count($contentIds), '?'));
|
||||
$stmtMemo = $pdo->prepare(
|
||||
"SELECT content_id, seq, title
|
||||
FROM edu_content_memos
|
||||
WHERE content_id IN ($in)
|
||||
AND (is_active = '1' OR is_active IS NULL)
|
||||
ORDER BY content_id ASC, seq ASC"
|
||||
);
|
||||
$stmtMemo->execute($contentIds);
|
||||
$memoRows = $stmtMemo->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($memoRows as $m) {
|
||||
$cid = (string)$m['content_id'];
|
||||
if (!isset($memoMap[$cid])) {
|
||||
$memoMap[$cid] = [];
|
||||
}
|
||||
$memoTitle = trim((string)($m['title'] ?? ''));
|
||||
if ($memoTitle !== '') {
|
||||
$memoMap[$cid][] = $memoTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($videos as $idx => $video) {
|
||||
$cid = (string)$video['content_id'];
|
||||
$videos[$idx]['memos'] = $memoMap[$cid] ?? [];
|
||||
$videos[$idx]['is_completed'] = !empty($video['completed_at']);
|
||||
$videos[$idx]['slot_no'] = $idx + 1;
|
||||
}
|
||||
|
||||
$stmtProgress = $pdo->prepare(
|
||||
"SELECT
|
||||
ug.goal_code,
|
||||
COUNT(gc.content_id) AS total_count,
|
||||
SUM(CASE WHEN lh.completed_at IS NOT NULL THEN 1 ELSE 0 END) AS completed_count
|
||||
FROM edu_user_learning_goals ug
|
||||
LEFT JOIN edu_goal_contents gc
|
||||
ON gc.goal_code = ug.goal_code
|
||||
AND (gc.is_active = '1' OR gc.is_active IS NULL)
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.member_id = ug.member_id
|
||||
AND lh.sys_comp_code = ug.sys_comp_code
|
||||
AND lh.content_id = gc.content_id
|
||||
WHERE ug.member_id = ?
|
||||
AND ug.sys_comp_code = ?
|
||||
AND (ug.is_active = '1' OR ug.is_active IS NULL)
|
||||
GROUP BY ug.goal_code"
|
||||
);
|
||||
$stmtProgress->execute([$memberId, $sysCompCode]);
|
||||
$progressRows = $stmtProgress->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$goalProgress = [];
|
||||
foreach ($progressRows as $row) {
|
||||
$total = (int)($row['total_count'] ?? 0);
|
||||
$completed = (int)($row['completed_count'] ?? 0);
|
||||
$goalProgress[(string)$row['goal_code']] = [
|
||||
'total_count' => $total,
|
||||
'completed_count' => $completed,
|
||||
'is_completed' => $total > 0 && $completed >= $total,
|
||||
];
|
||||
}
|
||||
|
||||
json_exit([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'active_goal_code' => $activeGoalCode,
|
||||
'goals' => $goals,
|
||||
'selected_goals' => $selectedGoals,
|
||||
'goal_progress' => $goalProgress,
|
||||
'videos' => $videos,
|
||||
],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exit([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$debugLog = dirname(__DIR__) . '/_save_comment_debug.log';
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
file_put_contents($debugLog, "[" . date('Y-m-d H:i:s') . "] REQUEST START\n", FILE_APPEND);
|
||||
file_put_contents($debugLog, "SESSION: member_id={$memberId}, sys_comp_code={$sysCompCode}\n", FILE_APPEND);
|
||||
|
||||
if ($memberId === '') {
|
||||
file_put_contents($debugLog, "ERROR: member_id is empty\n", FILE_APPEND);
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode((string)file_get_contents('php://input'), true);
|
||||
if (!is_array($input)) {
|
||||
$input = $_POST;
|
||||
}
|
||||
|
||||
file_put_contents($debugLog, "INPUT: " . json_encode($input, JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND);
|
||||
|
||||
$contentId = trim((string)($input['content_id'] ?? ''));
|
||||
$comment = trim((string)($input['comment'] ?? ''));
|
||||
$commentId = (int)($input['id'] ?? 0);
|
||||
|
||||
if ($contentId === '') {
|
||||
file_put_contents($debugLog, "ERROR: content_id is empty\n", FILE_APPEND);
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'content_id_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($comment === '') {
|
||||
file_put_contents($debugLog, "ERROR: comment is empty\n", FILE_APPEND);
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'comment_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (function_exists('mb_substr')) {
|
||||
$comment = mb_substr($comment, 0, 255, 'UTF-8');
|
||||
} else {
|
||||
$comment = substr($comment, 0, 255);
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
file_put_contents($debugLog, "ERROR: sys_comp_code is empty\n", FILE_APPEND);
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'sys_comp_code_missing'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmtCategory = $pdo->prepare('SELECT category_code FROM edu_contents WHERE content_id = ? LIMIT 1');
|
||||
$stmtCategory->execute([$contentId]);
|
||||
$categoryCode = strtoupper((string)($stmtCategory->fetchColumn() ?: ''));
|
||||
|
||||
if ($categoryCode === '') {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'content_not_found'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$savedId = 0;
|
||||
$mode = 'insert';
|
||||
|
||||
// ── 마이클래스(CA10001): edu_learning_histories.comment 컬럼에 UPDATE ──
|
||||
if ($categoryCode === 'CA10001') {
|
||||
$sqlLh = 'UPDATE edu_learning_histories SET comment = ? WHERE content_id = ? AND member_id = ? AND sys_comp_code = ?';
|
||||
$paramsLh = [$comment, $contentId, $memberId, $sysCompCode];
|
||||
$stmtLh = $pdo->prepare($sqlLh);
|
||||
$stmtLh->execute($paramsLh);
|
||||
|
||||
if ($stmtLh->rowCount() < 1) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => '학습 이력이 없어 소감을 저장할 수 없습니다.'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'mode' => 'update',
|
||||
'category' => $categoryCode,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 기타 카테고리: 기존 edu_comments 테이블 사용 ──
|
||||
if ($commentId > 0) {
|
||||
$stmtUpdate = $pdo->prepare(
|
||||
'UPDATE edu_comments
|
||||
SET comment = ?, updated_by = ?, updated_at = NOW()
|
||||
WHERE id = ? AND member_id = ? AND sys_comp_code = ?'
|
||||
);
|
||||
$stmtUpdate->execute([$comment, $memberId, $commentId, $memberId, $sysCompCode]);
|
||||
|
||||
if ($stmtUpdate->rowCount() < 1) {
|
||||
$pdo->rollBack();
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'forbidden_or_not_found'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$savedId = $commentId;
|
||||
$mode = 'update';
|
||||
} else {
|
||||
$stmtInsert = $pdo->prepare(
|
||||
'INSERT INTO edu_comments
|
||||
(parent_id, sys_comp_code, member_id, comment, content_id, created_by, created_at, updated_by, updated_at)
|
||||
VALUES (NULL, ?, ?, ?, ?, ?, NOW(), ?, NOW())'
|
||||
);
|
||||
$stmtInsert->execute([$sysCompCode, $memberId, $comment, $contentId, $memberId, $memberId]);
|
||||
$savedId = (int)$pdo->lastInsertId();
|
||||
$mode = 'insert';
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'id' => $savedId,
|
||||
'mode' => $mode,
|
||||
'category' => $categoryCode,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log('[save_comment] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Debug logging
|
||||
$debugLog = __DIR__ . '/../../_save_learning_debug.log';
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] REQUEST START\n";
|
||||
$logEntry .= "POST Data (\$_POST): " . json_encode($_POST, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
$logEntry .= "Raw Body: " . $rawBody . "\n";
|
||||
file_put_contents($debugLog, $logEntry, FILE_APPEND);
|
||||
|
||||
try {
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
$payload = [];
|
||||
if (is_string($rawBody) && $rawBody !== '') {
|
||||
$decoded = json_decode($rawBody, true);
|
||||
if (is_array($decoded)) {
|
||||
$payload = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($payload)) {
|
||||
$payload = $_POST;
|
||||
}
|
||||
|
||||
$logEntry = "Payload: " . json_encode($payload, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
file_put_contents($debugLog, $logEntry, FILE_APPEND);
|
||||
|
||||
$sessionMemberId = trim((string)($_SESSION['member_id'] ?? ''));
|
||||
$memberId = $sessionMemberId;
|
||||
if ($memberId === '') {
|
||||
file_put_contents($debugLog, "ERROR: member_id is empty\n", FILE_APPEND);
|
||||
http_response_code(401);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'not_logged_in',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sessionSysCompCode = trim((string)($_SESSION['sys_comp_code'] ?? ''));
|
||||
if ($sessionSysCompCode === '') {
|
||||
file_put_contents($debugLog, "ERROR: sys_comp_code is empty in session for member_id={$memberId}\n", FILE_APPEND);
|
||||
http_response_code(401);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'sys_comp_code_missing',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$contentIdRaw = trim((string)($payload['content_id'] ?? ''));
|
||||
if ($contentIdRaw === '') {
|
||||
file_put_contents($debugLog, "ERROR: content_id empty\n", FILE_APPEND);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'content_id가 올바르지 않습니다.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$toInt = static function ($value): int {
|
||||
if ($value === null || $value === '') {
|
||||
return 0;
|
||||
}
|
||||
return max(0, (int)$value);
|
||||
};
|
||||
|
||||
$watchTm = $toInt($payload['watch_tm'] ?? 0);
|
||||
$contentTmInput = $toInt($payload['content_tm'] ?? 0);
|
||||
$allTmInput = $toInt($payload['all_tm'] ?? 0);
|
||||
$allTmIncrement = $toInt($payload['all_tm_increment'] ?? 0);
|
||||
|
||||
$rawCompleted = (string)($payload['completed'] ?? '0');
|
||||
$isCompleted = in_array($rawCompleted, ['1', 'true', 'TRUE', 'y', 'Y', 'on', 'ON'], true);
|
||||
|
||||
$rawWatching = (string)($payload['is_watching'] ?? 'N');
|
||||
$isWatching = in_array($rawWatching, ['1', 'true', 'TRUE', 'y', 'Y', 'on', 'ON', 'Y'], true) ? 'Y' : 'N';
|
||||
|
||||
$stmtContent = $pdo->prepare(
|
||||
"SELECT content_id
|
||||
FROM edu_contents
|
||||
WHERE content_id = ?
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmtContent->execute([$contentIdRaw]);
|
||||
$contentRow = $stmtContent->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$resolvedContentId = $contentRow['content_id'] ?? null;
|
||||
$defaultContentTm = 0;
|
||||
|
||||
if (!$resolvedContentId && preg_match('/^[0-9]+$/', $contentIdRaw)) {
|
||||
$stmtLegacy = $pdo->prepare(
|
||||
"SELECT content_id
|
||||
FROM edu_contents
|
||||
WHERE content_id LIKE ?
|
||||
ORDER BY content_id DESC"
|
||||
);
|
||||
$stmtLegacy->execute([$contentIdRaw . '-%']);
|
||||
$legacyRows = $stmtLegacy->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (count($legacyRows) === 1) {
|
||||
$resolvedContentId = $legacyRows[0]['content_id'];
|
||||
$defaultContentTm = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$resolvedContentId) {
|
||||
file_put_contents($debugLog, "ERROR: content_id not found in edu_contents - requested={$contentIdRaw}\n", FILE_APPEND);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'edu_contents에 존재하지 않는 content_id입니다.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$payloadSysCompCode = trim((string)($payload['sys_comp_code'] ?? ''));
|
||||
if ($payloadSysCompCode !== '' && $payloadSysCompCode !== $sessionSysCompCode) {
|
||||
file_put_contents($debugLog, "ERROR: sys_comp_code mismatch (session={$sessionSysCompCode}, payload={$payloadSysCompCode}) for member_id={$memberId}\n", FILE_APPEND);
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'sys_comp_code_mismatch',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sysCompCode = $sessionSysCompCode;
|
||||
$stmtUser = $pdo->prepare('SELECT 1 FROM edu_users WHERE member_id = ? AND sys_comp_code = ? LIMIT 1');
|
||||
$stmtUser->execute([$memberId, $sysCompCode]);
|
||||
$userExists = (bool)$stmtUser->fetchColumn();
|
||||
|
||||
if (!$userExists) {
|
||||
file_put_contents($debugLog, "ERROR: edu_users row not found for member_id={$memberId}, sys_comp_code={$sysCompCode}\n", FILE_APPEND);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'edu_users에 회원 정보가 없어 학습이력을 저장할 수 없습니다.',
|
||||
'data' => [
|
||||
'member_id' => $memberId,
|
||||
'session_member_id' => $sessionMemberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$contentTm = max($contentTmInput, $defaultContentTm);
|
||||
$watchTm = min($watchTm, $contentTm > 0 ? $contentTm : $watchTm);
|
||||
|
||||
$stmtCurrent = $pdo->prepare(
|
||||
"SELECT watch_tm, content_tm, all_tm, completed_at
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND content_id = ?
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmtCurrent->execute([$memberId, $sysCompCode, $resolvedContentId]);
|
||||
$currentRow = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
|
||||
$prevWatchTm = (int)($currentRow['watch_tm'] ?? 0);
|
||||
$prevContentTm = (int)($currentRow['content_tm'] ?? 0);
|
||||
$prevAllTm = (int)($currentRow['all_tm'] ?? 0);
|
||||
$prevCompletedAt = $currentRow['completed_at'] ?? null;
|
||||
|
||||
|
||||
$effectiveContentTm = max($contentTm, $prevContentTm);
|
||||
$effectiveWatchTm = max($watchTm, $prevWatchTm);
|
||||
if ($effectiveContentTm > 0) {
|
||||
$effectiveWatchTm = min($effectiveWatchTm, $effectiveContentTm);
|
||||
}
|
||||
|
||||
$deltaWatchTm = max(0, $effectiveWatchTm - $prevWatchTm);
|
||||
$effectiveAllTm = max($prevAllTm + $allTmIncrement, $allTmInput, $prevAllTm + $deltaWatchTm);
|
||||
|
||||
// 90% 이상 시청 시 완료 처리
|
||||
$completionByTime = ($effectiveContentTm > 0) && ($effectiveWatchTm >= 0.9 * $effectiveContentTm);
|
||||
$completedAt = ($isCompleted || $completionByTime) ? ($prevCompletedAt ?: date('Y-m-d H:i:s')) : null;
|
||||
|
||||
if ($currentRow) {
|
||||
$stmtSave = $pdo->prepare(
|
||||
"UPDATE edu_learning_histories
|
||||
SET watch_tm = ?,
|
||||
content_tm = ?,
|
||||
all_tm = ?,
|
||||
completed_at = ?,
|
||||
is_watching = ?,
|
||||
last_viewed_at = NOW()
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND content_id = ?"
|
||||
);
|
||||
$stmtSave->execute([
|
||||
$effectiveWatchTm,
|
||||
$effectiveContentTm,
|
||||
$effectiveAllTm,
|
||||
$completedAt,
|
||||
$isWatching,
|
||||
$memberId,
|
||||
$sysCompCode,
|
||||
$resolvedContentId,
|
||||
]);
|
||||
} else {
|
||||
$logEntry = "No existing row found. Attempting INSERT with data: member_id=$memberId, sys_comp_code=$sysCompCode, content_id=$resolvedContentId, watch_tm=$effectiveWatchTm, all_tm=$effectiveAllTm\n";
|
||||
file_put_contents($debugLog, $logEntry, FILE_APPEND);
|
||||
|
||||
try {
|
||||
$stmtSave = $pdo->prepare(
|
||||
"INSERT INTO edu_learning_histories
|
||||
(member_id, sys_comp_code, content_id, first_viewed_at, last_viewed_at, watch_tm, content_tm, all_tm, completed_at, is_watching)
|
||||
VALUES
|
||||
(?, ?, ?, NOW(), NOW(), ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$stmtSave->execute([
|
||||
$memberId,
|
||||
$sysCompCode,
|
||||
$resolvedContentId,
|
||||
$effectiveWatchTm,
|
||||
$effectiveContentTm,
|
||||
$effectiveAllTm,
|
||||
$completedAt,
|
||||
$isWatching,
|
||||
]);
|
||||
$logEntry = "INSERT successful\n";
|
||||
file_put_contents($debugLog, $logEntry, FILE_APPEND);
|
||||
} catch (PDOException $e) {
|
||||
$logEntry = "INSERT failed with exception: " . $e->getCode() . " - " . $e->getMessage() . "\n";
|
||||
file_put_contents($debugLog, $logEntry, FILE_APPEND);
|
||||
|
||||
if ((string)$e->getCode() !== '23000') {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// 동시 저장 경합으로 동일 PK insert가 충돌하면 content_id 기준 update로 재시도
|
||||
$logEntry = "Conflict detected. Retrying with UPDATE...\n";
|
||||
file_put_contents($debugLog, $logEntry, FILE_APPEND);
|
||||
|
||||
$stmtRetry = $pdo->prepare(
|
||||
"UPDATE edu_learning_histories
|
||||
SET watch_tm = ?,
|
||||
content_tm = ?,
|
||||
all_tm = ?,
|
||||
completed_at = ?,
|
||||
is_watching = ?,
|
||||
last_viewed_at = NOW()
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND content_id = ?"
|
||||
);
|
||||
$stmtRetry->execute([
|
||||
$effectiveWatchTm,
|
||||
$effectiveContentTm,
|
||||
$effectiveAllTm,
|
||||
$completedAt,
|
||||
$isWatching,
|
||||
$memberId,
|
||||
$sysCompCode,
|
||||
$resolvedContentId,
|
||||
]);
|
||||
|
||||
$logEntry = "UPDATE (retry) completed\n";
|
||||
file_put_contents($debugLog, $logEntry, FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'content_id' => $resolvedContentId,
|
||||
'requested_content_id' => $contentIdRaw,
|
||||
'watch_tm' => $effectiveWatchTm,
|
||||
'content_tm' => $effectiveContentTm,
|
||||
'all_tm' => $effectiveAllTm,
|
||||
'completed' => $completedAt !== null,
|
||||
'completed_at' => $completedAt,
|
||||
'is_watching' => $isWatching,
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
file_put_contents($debugLog, "SUCCESS: saved member_id={$memberId}, sys_comp_code={$sysCompCode}, content_id={$resolvedContentId}, watch_tm={$effectiveWatchTm}, all_tm={$effectiveAllTm}, is_watching={$isWatching}\n", FILE_APPEND);
|
||||
} catch (Throwable $e) {
|
||||
file_put_contents($debugLog, "FATAL: " . $e->getCode() . " - " . $e->getMessage() . "\n", FILE_APPEND);
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? $_SESSION['user_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? $_SESSION['company'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
$stmtComp = $pdo->prepare('SELECT sys_comp_code FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1');
|
||||
$stmtComp->execute([$memberId]);
|
||||
$sysCompCode = (string)($stmtComp->fetchColumn() ?: '');
|
||||
}
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'sys_comp_code_missing'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode((string)file_get_contents('php://input'), true) ?: [];
|
||||
$goalCode = trim((string)($input['goal_code'] ?? ''));
|
||||
$quarter = trim((string)($input['quarter'] ?? ''));
|
||||
|
||||
if ($goalCode === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'goal_code_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($quarter !== '') {
|
||||
$stmtGoal = $pdo->prepare('SELECT goal_code, quarter FROM edu_learning_goals WHERE is_active = \'1\' AND goal_code = ? AND quarter = ? LIMIT 1');
|
||||
$stmtGoal->execute([$goalCode, $quarter]);
|
||||
} else {
|
||||
$stmtGoal = $pdo->prepare('SELECT goal_code, quarter FROM edu_learning_goals WHERE is_active = \'1\' AND goal_code = ? LIMIT 1');
|
||||
$stmtGoal->execute([$goalCode]);
|
||||
}
|
||||
|
||||
$goalRow = $stmtGoal->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$goalRow) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'goal_not_found'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$goalCode = (string)$goalRow['goal_code'];
|
||||
$quarter = trim((string)($goalRow['quarter'] ?? $quarter));
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
if ($quarter !== '') {
|
||||
$stmtDeactivate = $pdo->prepare(
|
||||
'UPDATE edu_user_learning_goals
|
||||
SET is_active = \'0\', updated_by = ?, updated_at = NOW()
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND quarter = ?
|
||||
AND goal_code <> ?
|
||||
AND is_active = \'1\'
|
||||
AND (completed_date IS NULL OR completed_date = \'0000-00-00\')'
|
||||
);
|
||||
$stmtDeactivate->execute([$memberId, $memberId, $sysCompCode, $quarter, $goalCode]);
|
||||
}
|
||||
|
||||
$stmtExists = $pdo->prepare(
|
||||
'SELECT 1
|
||||
FROM edu_user_learning_goals
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND goal_code = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmtExists->execute([$memberId, $sysCompCode, $goalCode]);
|
||||
$exists = (bool)$stmtExists->fetchColumn();
|
||||
|
||||
if ($exists) {
|
||||
$stmtUp = $pdo->prepare(
|
||||
'UPDATE edu_user_learning_goals
|
||||
SET quarter = ?, is_active = \'1\', updated_by = ?, updated_at = NOW()
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND goal_code = ?'
|
||||
);
|
||||
$stmtUp->execute([$quarter, $memberId, $memberId, $sysCompCode, $goalCode]);
|
||||
} else {
|
||||
$stmtIn = $pdo->prepare(
|
||||
'INSERT INTO edu_user_learning_goals
|
||||
(member_id, sys_comp_code, goal_code, quarter, is_active, created_by, created_at, updated_by, updated_at)
|
||||
VALUES (?, ?, ?, ?, \'1\', ?, NOW(), ?, NOW())'
|
||||
);
|
||||
$stmtIn->execute([$memberId, $sysCompCode, $goalCode, $quarter, $memberId, $memberId]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'goal_code' => $goalCode,
|
||||
'quarter' => $quarter,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log('[save_user_learning_goal] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* 영상 시청 시간 저장 API
|
||||
* POST /bbs/api/save_video_time.php
|
||||
*
|
||||
* Parameters:
|
||||
* - content_id (필수): 영상 content ID
|
||||
* - current_seconds (필수): 현재 시청 시간 (초 단위)
|
||||
*/
|
||||
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 세션 체크
|
||||
if (!isset($_SESSION['member_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
require $_SERVER['DOCUMENT_ROOT'] . '/www/baroncs/dbconfig.php';
|
||||
require $_SERVER['DOCUMENT_ROOT'] . '/www/baroncs/head.php';
|
||||
|
||||
$member_id = intval($_SESSION['member_id']);
|
||||
$content_id = intval($_POST['content_id'] ?? 0);
|
||||
$current_seconds = intval($_POST['current_seconds'] ?? 0);
|
||||
|
||||
if ($content_id <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid content_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 마이클래스 영상인지 확인 (CA10001 카테고리)
|
||||
$sql = "SELECT id, category_code FROM edu_contents WHERE id = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("i", $content_id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$content = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$content) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Content not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$is_myclass = ($content['category_code'] === 'CA10001');
|
||||
|
||||
// 시청 시간 저장 (마이클래스만)
|
||||
if ($is_myclass) {
|
||||
// edu_video_playback 테이블에 저장
|
||||
$check_sql = "SELECT id FROM edu_video_playback WHERE member_id = ? AND content_id = ?";
|
||||
$check_stmt = $conn->prepare($check_sql);
|
||||
$check_stmt->bind_param("ii", $member_id, $content_id);
|
||||
$check_stmt->execute();
|
||||
$check_result = $check_stmt->get_result();
|
||||
$exists = $check_result->fetch_assoc();
|
||||
$check_stmt->close();
|
||||
|
||||
if ($exists) {
|
||||
// 기존 레코드 업데이트
|
||||
$update_sql = "UPDATE edu_video_playback SET current_seconds = ?, updated_at = NOW() WHERE member_id = ? AND content_id = ?";
|
||||
$update_stmt = $conn->prepare($update_sql);
|
||||
$update_stmt->bind_param("iii", $current_seconds, $member_id, $content_id);
|
||||
$update_stmt->execute();
|
||||
$update_stmt->close();
|
||||
} else {
|
||||
// 새 레코드 생성
|
||||
$insert_sql = "INSERT INTO edu_video_playback (member_id, content_id, current_seconds) VALUES (?, ?, ?)";
|
||||
$insert_stmt = $conn->prepare($insert_sql);
|
||||
$insert_stmt->bind_param("iii", $member_id, $content_id, $current_seconds);
|
||||
$insert_stmt->execute();
|
||||
$insert_stmt->close();
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'content_id' => $content_id,
|
||||
'current_seconds' => $current_seconds,
|
||||
'is_myclass' => $is_myclass
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[save_video_time.php] Error: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
|
||||
$memberId = trim((string)($_SESSION['member_id'] ?? ''));
|
||||
$sysCompCode = trim((string)($_SESSION['sys_comp_code'] ?? ''));
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => '로그인이 필요합니다.'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'message' => 'POST only'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$contentId = trim((string)($_POST['content_id'] ?? ''));
|
||||
$isActive = trim((string)($_POST['is_active'] ?? ''));
|
||||
|
||||
if ($contentId === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'content_id 필수'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// is_active: '1' → 활성, 그 외 → '0' 비활성
|
||||
$activeValue = ($isActive === '1') ? '1' : '0';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// sys_comp_code가 세션에 없으면 DB에서 조회
|
||||
if ($sysCompCode === '') {
|
||||
$stmtUser = $pdo->prepare('SELECT sys_comp_code FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1');
|
||||
$stmtUser->execute([$memberId]);
|
||||
$row = $stmtUser->fetch();
|
||||
$sysCompCode = (string)($row['sys_comp_code'] ?? '');
|
||||
}
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
echo json_encode(['success' => false, 'message' => '회사코드 확인 불가'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 기존 레코드 확인
|
||||
$stmtCheck = $pdo->prepare(
|
||||
'SELECT content_id, is_active
|
||||
FROM edu_content_wishlist
|
||||
WHERE content_id = ?
|
||||
AND member_id = ?
|
||||
AND sys_comp_code = ?'
|
||||
);
|
||||
$stmtCheck->execute([$contentId, $memberId, $sysCompCode]);
|
||||
$existing = $stmtCheck->fetch();
|
||||
|
||||
if ($existing) {
|
||||
// UPDATE
|
||||
$stmtUpdate = $pdo->prepare(
|
||||
'UPDATE edu_content_wishlist
|
||||
SET is_active = ?, updated_at = NOW()
|
||||
WHERE content_id = ?
|
||||
AND member_id = ?
|
||||
AND sys_comp_code = ?'
|
||||
);
|
||||
$stmtUpdate->execute([$activeValue, $contentId, $memberId, $sysCompCode]);
|
||||
} else {
|
||||
// INSERT
|
||||
$stmtInsert = $pdo->prepare(
|
||||
'INSERT INTO edu_content_wishlist
|
||||
(content_id, member_id, sys_comp_code, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, NOW(), NOW())'
|
||||
);
|
||||
$stmtInsert->execute([$contentId, $memberId, $sysCompCode, $activeValue]);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'is_active' => $activeValue], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => '서버 오류'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/common.php';
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
|
||||
api_header_json();
|
||||
|
||||
$user = api_require_login();
|
||||
$method = strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||
|
||||
/**
|
||||
* Normalize keyword for storage (trim + max 50 chars).
|
||||
*/
|
||||
function normalize_keyword(string $value): string {
|
||||
$keyword = trim($value);
|
||||
if ($keyword === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (function_exists('mb_substr')) {
|
||||
return mb_substr($keyword, 0, 50, 'UTF-8');
|
||||
}
|
||||
|
||||
return substr($keyword, 0, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert one search log row using per-user seq and short retry on PK conflicts.
|
||||
*/
|
||||
function insert_search_log(PDO $pdo, string $memberId, string $sysCompCode, string $keyword): array {
|
||||
$maxAttempts = 3;
|
||||
|
||||
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$stmtSeq = $pdo->prepare(
|
||||
'SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq
|
||||
FROM edu_search_logs
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?'
|
||||
);
|
||||
$stmtSeq->execute([$memberId, $sysCompCode]);
|
||||
$nextSeq = (int)($stmtSeq->fetchColumn() ?: 1);
|
||||
|
||||
$stmtInsert = $pdo->prepare(
|
||||
'INSERT INTO edu_search_logs
|
||||
(member_id, sys_comp_code, seq, keyword, searched_at)
|
||||
VALUES
|
||||
(?, ?, ?, ?, NOW())'
|
||||
);
|
||||
$stmtInsert->execute([$memberId, $sysCompCode, $nextSeq, $keyword]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
return [
|
||||
'seq' => $nextSeq,
|
||||
'keyword' => $keyword,
|
||||
'searched_at' => date('Y-m-d H:i:s'),
|
||||
'retry_count' => $attempt - 1,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
$sqlState = '';
|
||||
if ($e instanceof PDOException && isset($e->errorInfo[0])) {
|
||||
$sqlState = (string)$e->errorInfo[0];
|
||||
}
|
||||
|
||||
$isDuplicateKey = ($sqlState === '23000');
|
||||
if (!$isDuplicateKey || $attempt >= $maxAttempts) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException('search_log_insert_retry_exceeded');
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$windowDays = 7;
|
||||
$limit = (int)($_GET['limit'] ?? 20);
|
||||
if ($limit < 1) {
|
||||
$limit = 1;
|
||||
} elseif ($limit > 50) {
|
||||
$limit = 50;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT seq, keyword, searched_at
|
||||
FROM edu_search_logs
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND searched_at >= (NOW() - INTERVAL 7 DAY)
|
||||
ORDER BY searched_at DESC, seq DESC
|
||||
LIMIT ' . $limit
|
||||
);
|
||||
$stmt->execute([(string)$user['member_id'], (string)$user['sys_comp_code']]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
$logs = array_map(static function (array $row): array {
|
||||
return [
|
||||
'seq' => (int)($row['seq'] ?? 0),
|
||||
'keyword' => (string)($row['keyword'] ?? ''),
|
||||
'searched_at' => (string)($row['searched_at'] ?? ''),
|
||||
];
|
||||
}, $rows);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'logs' => $logs,
|
||||
'window_days' => $windowDays,
|
||||
'limit' => $limit,
|
||||
'member_id' => (string)$user['member_id'],
|
||||
'sys_comp_code' => (string)$user['sys_comp_code'],
|
||||
],
|
||||
'meta' => [
|
||||
'api' => 'search_logs',
|
||||
'version' => 1,
|
||||
'status' => 'ok',
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
error_log('[search_logs][GET] ' . $e->getMessage());
|
||||
api_error(500, 'search_logs_fetch_failed');
|
||||
}
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
$input = api_get_input();
|
||||
$keyword = normalize_keyword((string)($input['keyword'] ?? ''));
|
||||
|
||||
if ($keyword === '') {
|
||||
api_error(400, 'keyword_required');
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$inserted = insert_search_log(
|
||||
$pdo,
|
||||
(string)$user['member_id'],
|
||||
(string)$user['sys_comp_code'],
|
||||
$keyword
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'accepted' => true,
|
||||
'keyword' => $inserted['keyword'],
|
||||
'seq' => (int)$inserted['seq'],
|
||||
'searched_at' => (string)$inserted['searched_at'],
|
||||
'member_id' => (string)$user['member_id'],
|
||||
'sys_comp_code' => (string)$user['sys_comp_code'],
|
||||
],
|
||||
'meta' => [
|
||||
'api' => 'search_logs',
|
||||
'version' => 1,
|
||||
'status' => 'ok',
|
||||
'retry_count' => (int)$inserted['retry_count'],
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
error_log('[search_logs][POST] ' . $e->getMessage());
|
||||
api_error(500, 'search_log_insert_failed');
|
||||
}
|
||||
}
|
||||
|
||||
api_error(405, 'method_not_allowed', [
|
||||
'allowed_methods' => ['GET', 'POST'],
|
||||
]);
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
require_once dirname(__DIR__) . '/auth.php';
|
||||
|
||||
edu_start_session();
|
||||
|
||||
$memberId = edu_current_member_id();
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
echo json_encode(['success' => false, 'error' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// edu_user_keywords 테이블 없으면 생성 (권한 없으면 무시)
|
||||
try {
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS edu_user_keywords (
|
||||
member_id VARCHAR(20) NOT NULL,
|
||||
sys_comp_code VARCHAR(20) NOT NULL DEFAULT '',
|
||||
keyword_code VARCHAR(20) NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (member_id, sys_comp_code, keyword_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
");
|
||||
} catch (Throwable $__ce) { error_log('[user_keywords] CREATE: ' . $__ce->getMessage()); }
|
||||
|
||||
if ($sysCompCode === '') {
|
||||
$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() ?: '');
|
||||
}
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
|
||||
// ── POST: 사용자 키워드 저장 ──────────────────────────
|
||||
if ($method === 'POST') {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$rawKws = array_slice(
|
||||
array_values(array_filter(array_map('trim', $input['keywords'] ?? []))),
|
||||
0, 3
|
||||
);
|
||||
|
||||
// 한글 키워드명 → KW 코드 변환
|
||||
$nameToKw = [];
|
||||
$codeRows = $pdo->query("SELECT base_code, code_name FROM edu_codes WHERE group_code = 'KW100'")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($codeRows as $cr) {
|
||||
$nameToKw[$cr['code_name']] = $cr['base_code'];
|
||||
}
|
||||
|
||||
// 기존 삭제 후 재삽입
|
||||
try {
|
||||
$delStmt = $pdo->prepare('DELETE FROM edu_user_keywords WHERE member_id = ? AND sys_comp_code = ?');
|
||||
$delStmt->execute([$memberId, $sysCompCode]);
|
||||
error_log('[user_keywords] DELETE completed');
|
||||
} catch (Throwable $__de) {
|
||||
error_log('[user_keywords] DELETE failed: ' . $__de->getMessage());
|
||||
}
|
||||
|
||||
$insertCnt = 0;
|
||||
try {
|
||||
$stmtIns = $pdo->prepare(
|
||||
'INSERT INTO edu_user_keywords (member_id, sys_comp_code, keyword_code) VALUES (?, ?, ?)'
|
||||
);
|
||||
foreach ($rawKws as $i => $kw) {
|
||||
$code = $nameToKw[$kw] ?? null;
|
||||
if ($code) {
|
||||
$stmtIns->execute([$memberId, $sysCompCode, $code]);
|
||||
$insertCnt++;
|
||||
error_log('[user_keywords] INSERT: keyword=' . $kw . ' -> code=' . $code);
|
||||
}
|
||||
}
|
||||
error_log('[user_keywords] total INSERT: ' . $insertCnt);
|
||||
} catch (Throwable $__ie) {
|
||||
error_log('[user_keywords] INSERT failed: ' . $__ie->getMessage());
|
||||
echo json_encode(['success' => false, 'error' => 'insert_failed: ' . $__ie->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'inserted' => $insertCnt], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── GET: 사용자 키워드 조회 ── (sys_comp_code 조건 제거, member_id만으로 조회)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT uk.keyword_code, ec.code_name AS keyword_name
|
||||
FROM edu_user_keywords uk
|
||||
JOIN edu_codes ec ON ec.base_code = uk.keyword_code
|
||||
WHERE uk.member_id = ?
|
||||
ORDER BY uk.keyword_code
|
||||
LIMIT 3
|
||||
");
|
||||
$stmt->execute([$memberId]);
|
||||
$keywords = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'keywords' => $keywords], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('[user_keywords] ' . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'error' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs/api/videos_by_keywords.php
|
||||
* ─────────────────────────────────────────────────────────────────────
|
||||
* 활성 키워드 기반 영상 목록을 반환하는 AJAX 엔드포인트
|
||||
*
|
||||
* Request POST application/json
|
||||
* { "my_keywords": ["AI","리더십"], "admin_keywords": ["경제"] }
|
||||
*
|
||||
* Response application/json
|
||||
* { "success": true, "videos": [ ...video objects... ] }
|
||||
*
|
||||
* 슬롯 구성:
|
||||
* [0] Pick 영상 (is_offer=1, 항상 고정)
|
||||
* [1-5] 활성 키워드(my + admin 합집합)에 매칭되는 영상, 랜덤 최대 5개
|
||||
* → 부족하면 랜덤 보충
|
||||
* ─────────────────────────────────────────────────────────────────────
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once dirname(__DIR__) . '/db_conn.php';
|
||||
require_once dirname(__DIR__) . '/auth.php';
|
||||
|
||||
edu_start_session();
|
||||
|
||||
$memberId = edu_current_member_id();
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
echo json_encode(['success' => false, 'error' => 'not_logged_in_or_invalid_session'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 요청 파싱 ──────────────────────────────────────────────────────
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$myKwRaw = array_values(array_filter(array_map('trim', $input['my_keywords'] ?? [])));
|
||||
$adminKwRaw = array_values(array_filter(array_map('trim', $input['admin_keywords'] ?? [])));
|
||||
$allInputKw = array_values(array_unique(array_merge($myKwRaw, $adminKwRaw)));
|
||||
|
||||
function edu_norm_code(string $code): string
|
||||
{
|
||||
return preg_replace('/[^A-Z0-9]/', '', strtoupper(trim($code)));
|
||||
}
|
||||
|
||||
function edu_looks_like_code(string $value): bool
|
||||
{
|
||||
return preg_match('/^[A-Z]{2}[A-Z0-9]{3,}$/', strtoupper(trim($value))) === 1;
|
||||
}
|
||||
|
||||
// ── mapRow (main_data.php mapContentRow 와 동일 로직) ─────────────────
|
||||
function mapRow(array $row, string $picker = ''): array
|
||||
{
|
||||
global $eduCodeNameMap;
|
||||
|
||||
// keyword_code 는 한글 텍스트로 저장돼 있음
|
||||
// GROUP_CONCAT 에서 이미 한글로 변환된 값이 옴
|
||||
$kwStr = $row['keywords'] ?? '';
|
||||
$keywords = $kwStr !== '' ? array_values(array_unique(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=|\.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}" : '';
|
||||
$thumbnail = $thumbFromDb !== ''
|
||||
? $thumbFromDb
|
||||
: ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : '');
|
||||
|
||||
$categoryCode = trim((string)($row['category_code'] ?? ''));
|
||||
// SQL COALESCE(ec.code_name, c.category_code) AS category_name 으로 가져온 한글명 직접 사용
|
||||
$categoryName = trim((string)($row['category_name'] ?? $categoryCode));
|
||||
|
||||
if (($categoryName === '' || edu_looks_like_code($categoryName)) && $categoryCode !== '') {
|
||||
$mappedCategory = $eduCodeNameMap[edu_norm_code($categoryCode)] ?? '';
|
||||
if ($mappedCategory !== '') {
|
||||
$categoryName = $mappedCategory;
|
||||
}
|
||||
}
|
||||
|
||||
$groupRaw = trim((string)($row['category_group'] ?? ''));
|
||||
$subcate = trim((string)($row['category_group_name'] ?? $groupRaw));
|
||||
if (($subcate === '' || edu_looks_like_code($subcate)) && $groupRaw !== '') {
|
||||
$mappedGroup = $eduCodeNameMap[edu_norm_code($groupRaw)] ?? '';
|
||||
if ($mappedGroup !== '') {
|
||||
$subcate = $mappedGroup;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $row['content_id'],
|
||||
'content_id' => $row['content_id'],
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'category' => $categoryName,
|
||||
'category_code' => $categoryCode,
|
||||
'subcate' => $subcate,
|
||||
'bookmark' => (bool)($row['is_bookmarked'] ?? false),
|
||||
'title' => $row['title'] ?? '',
|
||||
'picker' => $picker,
|
||||
'type' => 'main',
|
||||
'keywords' => $keywords,
|
||||
'gauge' => $gauge,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'all_tm' => (int)($row['all_tm'] ?? 0),
|
||||
'watch_min' => (int)floor(((int)($row['watch_tm'] ?? 0)) / 60),
|
||||
'content_min' => (int)floor(((int)($row['content_tm'] ?? 0)) / 60),
|
||||
'all_min' => (int)floor(((int)($row['all_tm'] ?? 0)) / 60),
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$eduCodeNameMap = [];
|
||||
try {
|
||||
$stmtCodeMap = $pdo->query("SELECT base_code, code_name FROM edu_codes");
|
||||
foreach ($stmtCodeMap->fetchAll(PDO::FETCH_ASSOC) as $cr) {
|
||||
$k = edu_norm_code((string)($cr['base_code'] ?? ''));
|
||||
if ($k === '') continue;
|
||||
$eduCodeNameMap[$k] = (string)($cr['code_name'] ?? '');
|
||||
}
|
||||
} catch (Throwable $ignore) {
|
||||
$eduCodeNameMap = [];
|
||||
}
|
||||
|
||||
// ── edu_codes 매핑 로드 (KW↔한글 양방향) ───────────────────────
|
||||
$codeStmt = $pdo->query("SELECT base_code, code_name FROM edu_codes WHERE group_code = 'KW100'");
|
||||
$kwToName = []; // KW10001 => 온보딩
|
||||
$nameToKw = []; // 온보딩 => KW10001
|
||||
foreach ($codeStmt->fetchAll(PDO::FETCH_ASSOC) as $cr) {
|
||||
$kwToName[$cr['base_code']] = $cr['code_name'];
|
||||
$nameToKw[$cr['code_name']] = $cr['base_code'];
|
||||
}
|
||||
|
||||
// 입력 키워드(한글)를 KW코드로 정규화
|
||||
$allActiveKw = [];
|
||||
foreach ($allInputKw as $kw) {
|
||||
if (isset($kwToName[$kw])) {
|
||||
$allActiveKw[] = $kw; // 이미 KW코드
|
||||
} elseif (isset($nameToKw[$kw])) {
|
||||
$allActiveKw[] = $nameToKw[$kw]; // 한글 → KW코드
|
||||
}
|
||||
}
|
||||
$allActiveKw = array_values(array_unique($allActiveKw));
|
||||
|
||||
// 키워드가 비어있으면 전체 KW 코드 사용 (항상 영상 표시)
|
||||
if (empty($allActiveKw)) {
|
||||
$allActiveKw = array_keys($kwToName);
|
||||
}
|
||||
$stmtPick = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name,
|
||||
COALESCE(lh.watch_tm, 0) AS watch_tm,
|
||||
COALESCE(lh.content_tm, 0) AS content_tm,
|
||||
COALESCE(lh.all_tm, 0) AS all_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(COALESCE(kwec.code_name, ck.keyword_code) ORDER BY ck.keyword_code SEPARATOR ',') AS keywords,
|
||||
u.name AS picker_name
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec ON ec.base_code = c.category_code
|
||||
LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.is_active = 1
|
||||
LEFT JOIN edu_codes kwec ON kwec.base_code = ck.keyword_code
|
||||
LEFT JOIN edu_content_offer co ON co.offer_id = c.offer_id
|
||||
LEFT JOIN edu_users u ON u.member_id = co.member_id
|
||||
AND u.sys_comp_code = co.sys_comp_code
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.member_id = :mid_w
|
||||
AND cw.sys_comp_code = :sc_w
|
||||
AND cw.is_active = 1
|
||||
AND cw.content_id = c.content_id
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = :mid_lh
|
||||
AND lh.sys_comp_code = :sc_lh
|
||||
WHERE c.is_offer = 1
|
||||
AND (c.is_active = 1 OR c.is_active IS NULL)
|
||||
AND c.issue_type_code = 'IS10003'
|
||||
GROUP BY c.content_id
|
||||
ORDER BY c.sort_order, RAND()
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtPick->execute([
|
||||
':mid_w' => $memberId,
|
||||
':sc_w' => $sysCompCode,
|
||||
':mid_lh' => $memberId,
|
||||
':sc_lh' => $sysCompCode,
|
||||
]);
|
||||
$pickRow = $stmtPick->fetch();
|
||||
$pickVideo = $pickRow ? mapRow($pickRow, $pickRow['picker_name'] ?? '동료') : null;
|
||||
|
||||
$usedIds = $pickVideo ? [$pickVideo['id']] : [];
|
||||
|
||||
// ── 활성 키워드 매칭 영상 (슬롯 1~5) ─────────────────────────
|
||||
$kwVideos = [];
|
||||
if (!empty($allActiveKw)) {
|
||||
$phKw = implode(',', array_fill(0, count($allActiveKw), '?'));
|
||||
$excClause = !empty($usedIds)
|
||||
? 'AND c.content_id NOT IN (' . implode(',', array_fill(0, count($usedIds), '?')) . ')'
|
||||
: '';
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name,
|
||||
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(COALESCE(kwec.code_name, ck.keyword_code) ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec ON ec.base_code = c.category_code
|
||||
LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.is_active = 1
|
||||
LEFT JOIN edu_codes kwec ON kwec.base_code = ck.keyword_code
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.member_id = ?
|
||||
AND cw.sys_comp_code = ?
|
||||
AND cw.is_active = 1
|
||||
AND cw.content_id = c.content_id
|
||||
WHERE (c.is_active = 1 OR c.is_active IS NULL)
|
||||
AND (c.is_offer IS NULL OR c.is_offer != 1)
|
||||
AND c.category_code IN ('CA10004', 'CA10005')
|
||||
AND c.content_id IN (
|
||||
SELECT DISTINCT content_id
|
||||
FROM edu_content_keywords
|
||||
WHERE keyword_code IN ({$phKw})
|
||||
AND is_active = 1
|
||||
)
|
||||
{$excClause}
|
||||
GROUP BY c.content_id
|
||||
ORDER BY RAND()
|
||||
LIMIT 5
|
||||
");
|
||||
|
||||
$params = [$memberId, $sysCompCode, $memberId, $sysCompCode];
|
||||
$params = array_merge($params, $allActiveKw);
|
||||
if (!empty($usedIds)) {
|
||||
$params = array_merge($params, $usedIds);
|
||||
}
|
||||
$stmt->execute($params);
|
||||
foreach ($stmt->fetchAll() as $r) {
|
||||
$kwVideos[] = mapRow($r);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 최종 조합 ─────────────────────────────────────────────────
|
||||
// 키워드 매칭 영상이 5개 미만이면 랜덤 영상으로 보충 (최대 5개까지)
|
||||
$usedIdsAll = $usedIds;
|
||||
foreach ($kwVideos as $v) { $usedIdsAll[] = $v['id']; }
|
||||
|
||||
$need = 5 - count($kwVideos);
|
||||
if ($need > 0) {
|
||||
$excAll = !empty($usedIdsAll)
|
||||
? 'AND c.content_id NOT IN (' . implode(',', array_fill(0, count($usedIdsAll), '?')) . ')'
|
||||
: '';
|
||||
$stmtFill = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name,
|
||||
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(COALESCE(kwec.code_name, ck.keyword_code) ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec ON ec.base_code = c.category_code
|
||||
LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.is_active = 1
|
||||
LEFT JOIN edu_codes kwec ON kwec.base_code = ck.keyword_code
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.member_id = ?
|
||||
AND cw.sys_comp_code = ?
|
||||
AND cw.is_active = 1
|
||||
AND cw.content_id = c.content_id
|
||||
WHERE (c.is_active = 1 OR c.is_active IS NULL)
|
||||
AND (c.is_offer IS NULL OR c.is_offer != 1)
|
||||
AND c.category_code IN ('CA10004', 'CA10005')
|
||||
AND c.content_id IN (SELECT DISTINCT content_id FROM edu_content_keywords WHERE is_active = 1)
|
||||
{$excAll}
|
||||
GROUP BY c.content_id
|
||||
ORDER BY RAND()
|
||||
LIMIT {$need}
|
||||
");
|
||||
$fillParams = [$memberId, $sysCompCode, $memberId, $sysCompCode];
|
||||
if (!empty($usedIdsAll)) {
|
||||
$fillParams = array_merge($fillParams, $usedIdsAll);
|
||||
}
|
||||
$stmtFill->execute($fillParams);
|
||||
foreach ($stmtFill->fetchAll() as $r) {
|
||||
$kwVideos[] = mapRow($r);
|
||||
}
|
||||
}
|
||||
|
||||
// 항상 6개: [0]=pick영상, [1~5]=키워드 관련영상(중복X), 부족하면 랜덤, 그래도 부족하면 빈카드
|
||||
$videos = [];
|
||||
if ($pickVideo) {
|
||||
$videos[] = $pickVideo;
|
||||
}
|
||||
// pick영상 ID를 제외한 키워드 영상 5개
|
||||
$cnt = 0;
|
||||
foreach ($kwVideos as $v) {
|
||||
if ($pickVideo && $v['id'] == $pickVideo['id']) continue;
|
||||
$videos[] = $v;
|
||||
$cnt++;
|
||||
if ($cnt >= 5) break;
|
||||
}
|
||||
// 키워드 영상이 5개 미만이면 랜덤 영상으로 보충 (pick/키워드 중복 제외)
|
||||
$need = 6 - count($videos);
|
||||
if ($need > 0) {
|
||||
$usedIdsAll = array_map(function($v){return is_array($v)?$v['id']:$v;}, $videos);
|
||||
$excAll = !empty($usedIdsAll)
|
||||
? 'AND c.content_id NOT IN (' . implode(',', array_fill(0, count($usedIdsAll), '?')) . ')'
|
||||
: '';
|
||||
$stmtFill = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
COALESCE(ec.code_name, c.category_code) AS category_name,
|
||||
COALESCE(ec_grp.code_name, c.category_group) AS category_group_name,
|
||||
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(COALESCE(kwec.code_name, ck.keyword_code) ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes ec ON ec.base_code = c.category_code
|
||||
LEFT JOIN edu_codes ec_grp ON TRIM(UPPER(ec_grp.base_code)) = TRIM(UPPER(c.category_group))
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.is_active = 1
|
||||
LEFT JOIN edu_codes kwec ON kwec.base_code = ck.keyword_code
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.member_id = ?
|
||||
AND cw.sys_comp_code = ?
|
||||
AND cw.is_active = 1
|
||||
AND cw.content_id = c.content_id
|
||||
WHERE (c.is_active = 1 OR c.is_active IS NULL)
|
||||
AND (c.is_offer IS NULL OR c.is_offer != 1)
|
||||
AND c.category_code IN ('CA10004', 'CA10005')
|
||||
AND c.content_id IN (SELECT DISTINCT content_id FROM edu_content_keywords WHERE is_active = 1)
|
||||
{$excAll}
|
||||
GROUP BY c.content_id
|
||||
ORDER BY RAND()
|
||||
LIMIT {$need}
|
||||
");
|
||||
$fillParams = [$memberId, $sysCompCode, $memberId, $sysCompCode];
|
||||
if (!empty($usedIdsAll)) {
|
||||
$fillParams = array_merge($fillParams, $usedIdsAll);
|
||||
}
|
||||
$stmtFill->execute($fillParams);
|
||||
foreach ($stmtFill->fetchAll() as $r) {
|
||||
$videos[] = mapRow($r);
|
||||
}
|
||||
}
|
||||
// 그래도 부족하면 빈 카드로 패딩
|
||||
while (count($videos) < 6) {
|
||||
$videos[] = [
|
||||
'id' => 'empty_' . count($videos),
|
||||
'url' => '',
|
||||
'thumbnail' => '',
|
||||
'category' => '',
|
||||
'category_code' => '',
|
||||
'subcate' => '',
|
||||
'bookmark' => false,
|
||||
'title' => '',
|
||||
'picker' => '',
|
||||
'type' => 'main',
|
||||
'keywords' => [],
|
||||
'gauge' => 0,
|
||||
'watch_tm' => 0,
|
||||
'content_tm' => 0,
|
||||
'all_tm' => 0,
|
||||
'watch_min' => 0,
|
||||
'content_min' => 0,
|
||||
'all_min' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
// 메인 게이지용: 회원 누적 시청시간(watch_tm, 초) 합계
|
||||
$stmtTotal = $pdo->prepare("\n SELECT COALESCE(SUM(watch_tm), 0) AS total_watch_tm\n FROM edu_learning_histories\n WHERE member_id = ? AND sys_comp_code = ?\n ");
|
||||
$stmtTotal->execute([$memberId, $sysCompCode]);
|
||||
$totalWatchTm = (int)$stmtTotal->fetchColumn();
|
||||
$totalMin = (int)floor($totalWatchTm / 60);
|
||||
|
||||
// 전체 평균 학습시간 계산: (전사 모든 사용자의 총 watch_tm 합계) / (학습경험이 있는 사용자 수)
|
||||
$avgWatchMin = 50; // 기본값
|
||||
try {
|
||||
$stmtAvg = $pdo->prepare("
|
||||
SELECT
|
||||
COALESCE(SUM(h.watch_tm), 0) as total_watch_tm,
|
||||
COUNT(DISTINCT h.member_id) as unique_members
|
||||
FROM edu_learning_histories h
|
||||
WHERE h.sys_comp_code = ?
|
||||
");
|
||||
$stmtAvg->execute([$sysCompCode]);
|
||||
$avgRow = $stmtAvg->fetch(PDO::FETCH_ASSOC);
|
||||
if ($avgRow) {
|
||||
$totalWatchTmAll = (int)($avgRow['total_watch_tm'] ?? 0);
|
||||
$uniqueMembers = (int)($avgRow['unique_members'] ?? 0);
|
||||
if ($uniqueMembers > 0) {
|
||||
$avgWatchMin = (int)floor($totalWatchTmAll / $uniqueMembers / 60);
|
||||
$avgWatchMin = max(50, $avgWatchMin);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('[videos_by_keywords] avgWatchMin calculation failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
echo json_encode(
|
||||
[
|
||||
'success' => true,
|
||||
'videos' => $videos,
|
||||
'total_watch_tm' => $totalWatchTm,
|
||||
'total_all_tm' => $totalWatchTm,
|
||||
'total_min' => $totalMin,
|
||||
'avg_watch_min' => $avgWatchMin,
|
||||
],
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
error_log('[videos_by_keywords] ' . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'videos' => [], 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs/api_log.php
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
* 공통 로그 기록 헬퍼
|
||||
*
|
||||
* 사용법:
|
||||
* require_once __DIR__ . '/api_log.php';
|
||||
* api_log('save_learning', 'unregistered content', ['content_id' => 'F1']);
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
define('API_LOG_FILE', __DIR__ . '/logs/api.log');
|
||||
define('API_LOG_MAX_BYTES', 512 * 1024); // 512KB 초과 시 rotate
|
||||
|
||||
/**
|
||||
* 로그 한 줄 기록
|
||||
* @param string $tag 태그 (파일명/기능명)
|
||||
* @param string $message 메시지
|
||||
* @param array $context 추가 데이터 (선택)
|
||||
*/
|
||||
function api_log(string $tag, string $message, array $context = []): void
|
||||
{
|
||||
$logDir = dirname(API_LOG_FILE);
|
||||
if (!is_dir($logDir)) {
|
||||
@mkdir($logDir, 0755, true);
|
||||
// 디렉토리 직접 접근 차단
|
||||
@file_put_contents($logDir . '/.htaccess', "Deny from all\n");
|
||||
}
|
||||
|
||||
// 512KB 초과 시 rotate (이전 로그 보존)
|
||||
if (file_exists(API_LOG_FILE) && filesize(API_LOG_FILE) > API_LOG_MAX_BYTES) {
|
||||
@rename(API_LOG_FILE, API_LOG_FILE . '.' . date('YmdHis') . '.bak');
|
||||
}
|
||||
|
||||
$ts = date('Y-m-d H:i:s');
|
||||
$ctx = empty($context) ? '' : ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);
|
||||
$line = "[{$ts}] [{$tag}] {$message}{$ctx}" . PHP_EOL;
|
||||
|
||||
@file_put_contents(API_LOG_FILE, $line, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
if (!function_exists('edu_start_session')) {
|
||||
function edu_start_session(): void
|
||||
{
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_current_member_id')) {
|
||||
function edu_current_member_id(): string
|
||||
{
|
||||
edu_start_session();
|
||||
return trim((string)($_SESSION['ss_mb_id'] ?? $_SESSION['member_id'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_is_logged_in')) {
|
||||
function edu_is_logged_in(): bool
|
||||
{
|
||||
return edu_current_member_id() !== '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_current_user')) {
|
||||
function edu_current_user(): array
|
||||
{
|
||||
edu_start_session();
|
||||
$user = $_SESSION['edu_user'] ?? [];
|
||||
return is_array($user) ? $user : [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_user_field')) {
|
||||
function edu_user_field(string $field, $default = null)
|
||||
{
|
||||
$user = edu_current_user();
|
||||
if (array_key_exists($field, $user)) {
|
||||
return $user[$field];
|
||||
}
|
||||
|
||||
edu_start_session();
|
||||
$sessionKey = 'edu_user_' . $field;
|
||||
if (array_key_exists($sessionKey, $_SESSION)) {
|
||||
return $_SESSION[$sessionKey];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_require_login')) {
|
||||
function edu_require_login(): void
|
||||
{
|
||||
if (edu_is_logged_in()) {
|
||||
return;
|
||||
}
|
||||
|
||||
header('Location: http://erp.baroncs.co.kr/mobile/sys/controller/Study_controller.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
require_once __DIR__ . '/api_log.php';
|
||||
// 외부(호출하는 파일)에서 $SET_PREFIX를 정의하지 않았을 경우를 대비한 기본값 설정
|
||||
$prefixCondition = isset($SET_PREFIX) ? $SET_PREFIX : 'L'; //L=리더십(기본값), I=인사이트
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
if (!function_exists('edu_extract_youtube_id')) {
|
||||
function edu_extract_youtube_id(string $rawUrl): string
|
||||
{
|
||||
$value = trim($rawUrl);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (preg_match('/^[A-Za-z0-9_-]{11}$/', $value) === 1) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (strpos($value, 'youtube.com/watch') !== false) {
|
||||
$query = parse_url($value, PHP_URL_QUERY);
|
||||
if (is_string($query)) {
|
||||
parse_str($query, $params);
|
||||
return trim((string) ($params['v'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($value, 'youtu.be/') !== false) {
|
||||
return trim((string) preg_replace('/[?&#].*$/', '', substr($value, strpos($value, 'youtu.be/') + 9)));
|
||||
}
|
||||
|
||||
if (strpos($value, 'youtube.com/embed/') !== false) {
|
||||
return trim((string) preg_replace('/[?&#].*$/', '', substr($value, strpos($value, 'youtube.com/embed/') + 18)));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_build_youtube_thumbnail')) {
|
||||
function edu_build_youtube_thumbnail(string $rawUrl, string $thumbnailName = 'hqdefault.jpg'): string
|
||||
{
|
||||
$videoId = edu_extract_youtube_id($rawUrl);
|
||||
if ($videoId === '') {
|
||||
return '/img/video/img_thumb_01.png';
|
||||
}
|
||||
|
||||
$thumbnailName = trim($thumbnailName);
|
||||
if ($thumbnailName === '') {
|
||||
$thumbnailName = 'hqdefault.jpg';
|
||||
}
|
||||
|
||||
return 'https://img.youtube.com/vi/' . rawurlencode($videoId) . '/' . rawurlencode($thumbnailName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_normalize_youtube_url')) {
|
||||
function edu_normalize_youtube_url(string $rawUrl): string
|
||||
{
|
||||
$value = trim($rawUrl);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (preg_match('/^https?:\/\//i', $value) === 1) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$videoId = edu_extract_youtube_id($value);
|
||||
if ($videoId === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return 'https://www.youtube.com/watch?v=' . rawurlencode($videoId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('edu_build_profile_image_path')) {
|
||||
function edu_build_profile_image_path(string $memberId, string $sysCompCode = '', string $belongCompId = '', string $workingCompId = ''): string
|
||||
{
|
||||
$memberId = trim($memberId);
|
||||
if ($memberId === '') {
|
||||
return '/img/ico/ico_user.svg';
|
||||
}
|
||||
|
||||
$teamCodeSource = '';
|
||||
foreach ([$workingCompId, $belongCompId, $sysCompCode] as $candidate) {
|
||||
$candidate = preg_replace('/[^A-Za-z0-9]/', '', strtoupper(trim((string) $candidate)));
|
||||
if ($candidate !== '') {
|
||||
$teamCodeSource = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$teamCode = $teamCodeSource === '' ? '' : substr($teamCodeSource, -2);
|
||||
if ($teamCode === '') {
|
||||
return '/img/ico/ico_user.svg';
|
||||
}
|
||||
|
||||
return '/img/profile/' . rawurlencode($memberId . '_' . $teamCode) . '.png';
|
||||
}
|
||||
}
|
||||
|
||||
//Svg
|
||||
if($prefixCondition=='L'){
|
||||
$prefixSvg = "leadership";
|
||||
}else if($prefixCondition=='I'){
|
||||
$prefixSvg = "insight";
|
||||
}
|
||||
|
||||
//배너설정
|
||||
if($prefixCondition=='L'){//L=리더십(기본값), I=인사이트
|
||||
$array_banner_img = [
|
||||
['normal' => '/img/leadership/img_banner_01.png', 'mobile' => '/img/leadership/img_banner_01_m.png', 'title' => '실천으로 완성하는 리더십'],
|
||||
['normal' => '/img/leadership/img_banner_02.png', 'mobile' => '/img/leadership/img_banner_02_m.png', 'title' => '성장하는 리더십 여정'],
|
||||
['normal' => '/img/leadership/img_banner_03.png', 'mobile' => '/img/leadership/img_banner_03_m.png', 'title' => '리더로 성장하는 과정'],
|
||||
];
|
||||
}else if($prefixCondition=='I'){//L=리더십(기본값), I=인사이트
|
||||
$array_banner_img = [
|
||||
['normal' => '/img/insight/img_banner_01.png', 'mobile' => '/img/insight/img_banner_01_m.png', 'title' => '잘되는 일 하세요'],
|
||||
['normal' => '/img/insight/img_banner_02.png', 'mobile' => '/img/insight/img_banner_02_m.png', 'title' => '다시 일하고 싶은 관계'],
|
||||
['normal' => '/img/insight/img_banner_03.png', 'mobile' => '/img/insight/img_banner_03_m.png', 'title' => '3일의 실행'],
|
||||
];
|
||||
}
|
||||
$dbSuccess = false;
|
||||
$array_tab_info = [];
|
||||
$array_leadership_banner = [];
|
||||
$array_insight_banner = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// LIKE 연산자를 안전하게 처리하기 위해 준비된 선언(Prepared Statement) 사용
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT group_code, base_code, code, code_name
|
||||
FROM edu_codes
|
||||
WHERE is_active = 1
|
||||
AND group_code = 'CA200'
|
||||
AND code LIKE :prefix
|
||||
ORDER BY base_code ASC
|
||||
");
|
||||
|
||||
// 'L%' 형태로 바인딩하여 검색
|
||||
$stmtCode->execute([':prefix' => $prefixCondition . '%']);
|
||||
$rows = $stmtCode->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($rows) {
|
||||
$i = 1;
|
||||
foreach ($rows as $cr) {
|
||||
$index = sprintf('%02d', $i);
|
||||
$index_src = '/img/ico/ico_'.$prefixSvg.'_' . $index . '.svg';
|
||||
|
||||
$array_tab_info[] = [
|
||||
'base_code' => $cr['base_code'],
|
||||
'code_name' => $cr['code_name'],
|
||||
'src' => $index_src
|
||||
];
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($prefixCondition === 'L') {
|
||||
$stmtBanner = $pdo->prepare(
|
||||
"
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.title,
|
||||
c.description1,
|
||||
c.description2,
|
||||
c.description3,
|
||||
c.content_url,
|
||||
c.sort_order
|
||||
FROM edu_contents c
|
||||
WHERE c.is_active = 1
|
||||
AND c.issue_type_code = 'LD10010'
|
||||
ORDER BY
|
||||
CASE WHEN COALESCE(c.sort_order, 0) = 0 THEN 1 ELSE 0 END,
|
||||
c.sort_order ASC,
|
||||
c.content_id DESC
|
||||
"
|
||||
);
|
||||
$stmtBanner->execute();
|
||||
|
||||
foreach ($stmtBanner->fetchAll(PDO::FETCH_ASSOC) as $bannerRow) {
|
||||
$contentUrl = trim((string) ($bannerRow['content_url'] ?? ''));
|
||||
$array_leadership_banner[] = [
|
||||
'content_id' => (string) ($bannerRow['content_id'] ?? ''),
|
||||
'title' => (string) ($bannerRow['title'] ?? ''),
|
||||
'description1' => trim((string) ($bannerRow['description1'] ?? '')),
|
||||
'description2' => trim((string) ($bannerRow['description2'] ?? '')),
|
||||
'description3' => trim((string) ($bannerRow['description3'] ?? '')),
|
||||
'content_url' => $contentUrl,
|
||||
'thumbnail' => edu_build_youtube_thumbnail($contentUrl, 'sddefault.jpg'),
|
||||
'banner_title_pc' => '/img/leadership/banner_leadership.svg',
|
||||
'banner_title_mo' => '/img/leadership/banner_leadership_m.svg',
|
||||
];
|
||||
}
|
||||
} elseif ($prefixCondition === 'I') {
|
||||
$stmtBanner = $pdo->prepare(
|
||||
"
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.title,
|
||||
c.description1,
|
||||
c.description2,
|
||||
c.description3,
|
||||
c.content_url,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
c.issue_type_code,
|
||||
c.is_offer,
|
||||
c.offer_id,
|
||||
c.sort_order,
|
||||
cg.code_name AS sub_category_name,
|
||||
o.member_id AS offer_member_id,
|
||||
o.sys_comp_code AS offer_sys_comp_code,
|
||||
u.name AS offer_member_name,
|
||||
u.dept_name AS offer_dept_name,
|
||||
u.rank_name AS offer_rank_name,
|
||||
u.sys_comp_code AS user_sys_comp_code,
|
||||
u.belong_comp_id,
|
||||
u.working_comp_id
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_codes cg
|
||||
ON cg.base_code = c.category_group
|
||||
AND cg.group_code = 'CA200'
|
||||
AND cg.is_active = 1
|
||||
LEFT JOIN edu_content_offer o
|
||||
ON o.offer_id = c.offer_id
|
||||
LEFT JOIN edu_users u
|
||||
ON u.member_id = o.member_id
|
||||
AND (
|
||||
o.sys_comp_code IS NULL
|
||||
OR o.sys_comp_code = ''
|
||||
OR u.sys_comp_code = o.sys_comp_code
|
||||
)
|
||||
WHERE c.is_active = 1
|
||||
AND c.issue_type_code IN ('IS10001', 'IS10002', 'IS10003')
|
||||
ORDER BY
|
||||
CASE c.issue_type_code
|
||||
WHEN 'IS10001' THEN 1
|
||||
WHEN 'IS10002' THEN 2
|
||||
WHEN 'IS10003' THEN 3
|
||||
ELSE 99
|
||||
END ASC,
|
||||
CASE WHEN COALESCE(c.sort_order, 0) = 0 THEN 1 ELSE 0 END,
|
||||
c.sort_order ASC,
|
||||
c.content_id DESC
|
||||
"
|
||||
);
|
||||
$stmtBanner->execute();
|
||||
|
||||
$insightBannerTitleMap = [
|
||||
'IS10001' => [
|
||||
'pc' => '/img/insight/banner_issue.svg',
|
||||
'mo' => '/img/insight/banner_issue_m.svg',
|
||||
'alt' => 'Hot Issue',
|
||||
],
|
||||
'IS10002' => [
|
||||
'pc' => '/img/insight/banner_pick.svg',
|
||||
'mo' => '/img/insight/banner_pick_m.svg',
|
||||
'alt' => "Editor's Pick",
|
||||
],
|
||||
'IS10003' => [
|
||||
'pc' => '/img/insight/banner_choice.svg',
|
||||
'mo' => '/img/insight/banner_choice_m.svg',
|
||||
'alt' => "Top Learner's Choice",
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($stmtBanner->fetchAll(PDO::FETCH_ASSOC) as $bannerRow) {
|
||||
$issueTypeCode = (string) ($bannerRow['issue_type_code'] ?? '');
|
||||
$titleMeta = $insightBannerTitleMap[$issueTypeCode] ?? $insightBannerTitleMap['IS10001'];
|
||||
$contentUrl = edu_normalize_youtube_url((string) ($bannerRow['content_url'] ?? ''));
|
||||
$offerMemberId = trim((string) ($bannerRow['offer_member_id'] ?? ''));
|
||||
$offerMemberName = trim((string) ($bannerRow['offer_member_name'] ?? ''));
|
||||
$offerDeptName = trim((string) ($bannerRow['offer_dept_name'] ?? ''));
|
||||
$offerRankName = trim((string) ($bannerRow['offer_rank_name'] ?? ''));
|
||||
$isTopLearner = $issueTypeCode === 'IS10003' && (string) ($bannerRow['is_offer'] ?? '') === '1';
|
||||
|
||||
$array_insight_banner[] = [
|
||||
'content_id' => (string) ($bannerRow['content_id'] ?? ''),
|
||||
'title' => (string) ($bannerRow['title'] ?? ''),
|
||||
'description1' => trim((string) ($bannerRow['description1'] ?? '')),
|
||||
'description2' => trim((string) ($bannerRow['description2'] ?? '')),
|
||||
'description3' => trim((string) ($bannerRow['description3'] ?? '')),
|
||||
'content_url' => $contentUrl,
|
||||
'thumbnail' => edu_build_youtube_thumbnail($contentUrl, 'sddefault.jpg'),
|
||||
'category_name' => '인사이트',
|
||||
'sub_category_name' => trim((string) ($bannerRow['sub_category_name'] ?? '')),
|
||||
'issue_type_code' => $issueTypeCode,
|
||||
'banner_title_pc' => $titleMeta['pc'],
|
||||
'banner_title_mo' => $titleMeta['mo'],
|
||||
'banner_title_alt' => $titleMeta['alt'],
|
||||
'is_top_learner' => $isTopLearner,
|
||||
'offer_member_id' => $offerMemberId,
|
||||
'offer_member_name' => $offerMemberName,
|
||||
'offer_dept_name' => $offerDeptName,
|
||||
'offer_rank_name' => $offerRankName,
|
||||
'offer_profile_image' => $isTopLearner
|
||||
? edu_build_profile_image_path(
|
||||
$offerMemberId,
|
||||
(string) ($bannerRow['user_sys_comp_code'] ?? $bannerRow['offer_sys_comp_code'] ?? ''),
|
||||
(string) ($bannerRow['belong_comp_id'] ?? ''),
|
||||
(string) ($bannerRow['working_comp_id'] ?? '')
|
||||
)
|
||||
: '/img/ico/ico_user.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$dbSuccess = true;
|
||||
} catch (Exception $e) {
|
||||
$errMsg = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
||||
error_log('[leadership_init_data.php] DB Error: ' . $errMsg);
|
||||
// api_log 함수가 있다면 기록
|
||||
if (function_exists('api_log')) {
|
||||
api_log('leadership_init', 'DB_ERROR', ['error' => $errMsg]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
echo "DB Connected.\n";
|
||||
|
||||
// Check procedure
|
||||
$stmt = $pdo->query("SHOW PROCEDURE STATUS WHERE Db = 'baronhomep' AND Name = 'sp_insert_edu_access_log'");
|
||||
$proc = $stmt->fetchAll();
|
||||
if (empty($proc)) {
|
||||
echo "Procedure sp_insert_edu_access_log DOES NOT EXIST.\n";
|
||||
} else {
|
||||
echo "Procedure sp_insert_edu_access_log exists.\n";
|
||||
}
|
||||
|
||||
// check if u.belong_comp exists
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM edu_users LIKE 'belong_comp'");
|
||||
$col = $stmt->fetchAll();
|
||||
if (empty($col)) {
|
||||
echo "Column belong_comp DOES NOT EXIST in edu_users.\n";
|
||||
} else {
|
||||
echo "Column belong_comp exists.\n";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Basic Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* db_conn.php - MariaDB PDO
|
||||
*/
|
||||
|
||||
// 세션 쿠키 도메인을 서브도메인 공유가 가능하도록 설정
|
||||
session_set_cookie_params(0, '/', '.baroncs.co.kr');
|
||||
|
||||
// 세션이 시작되지 않았다면 시작
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
function db_conn(): PDO
|
||||
{
|
||||
static $pdo = null;
|
||||
if ($pdo !== null) { return $pdo; }
|
||||
|
||||
$host = 'localhost';
|
||||
$port = 3306;
|
||||
$dbname = 'baronhomep';
|
||||
$user = 'baronhomep';
|
||||
$pass = 'baron3840!!';
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
$dsn = "mysql:host={$host};dbname={$dbname};charset={$charset}";
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
} catch (PDOException $e) {
|
||||
error_log('[DB_CONN ERROR] ' . $e->getMessage());
|
||||
throw new RuntimeException('DB error: ' . $e->getMessage());
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
// 직접 실행 시 연결 테스트
|
||||
if (basename(__FILE__) === basename($_SERVER['SCRIPT_FILENAME'] ?? '')) {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo '<h2>DB 연결 테스트</h2>';
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$ver = $pdo->query('SELECT VERSION()')->fetchColumn();
|
||||
echo "<p style='color:green'>연결 성공 - MariaDB {$ver}</p>";
|
||||
$tables = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
|
||||
echo '<p>테이블 (' . count($tables) . '개):</p><ul>';
|
||||
foreach ($tables as $t) {
|
||||
echo '<li>' . htmlspecialchars($t) . '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
} catch (Exception $e) {
|
||||
echo "<p style='color:red'>실패: " . htmlspecialchars($e->getMessage()) . "</p>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs/debug_log.php
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
* API 로그 뷰어 (개발/운영 디버깅용)
|
||||
*
|
||||
* 접근: /bbs/debug_log.php?key=baron_debug_2026
|
||||
* key 파라미터가 틀리면 403 반환
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
define('DEBUG_KEY', 'baron_debug_2026'); // ← 필요 시 변경
|
||||
define('LOG_FILE', __DIR__ . '/logs/api.log');
|
||||
define('LINES_MAX', 300); // 최대 표시 줄 수
|
||||
|
||||
// ── 인증 ──────────────────────────────────────────────────────────
|
||||
if (($_GET['key'] ?? '') !== DEBUG_KEY) {
|
||||
http_response_code(403);
|
||||
exit('403 Forbidden');
|
||||
}
|
||||
|
||||
// ── 로그 파일 읽기 ────────────────────────────────────────────────
|
||||
$action = $_GET['action'] ?? 'view';
|
||||
|
||||
// 로그 삭제
|
||||
if ($action === 'clear') {
|
||||
@file_put_contents(LOG_FILE, '');
|
||||
header('Location: debug_log.php?key=' . DEBUG_KEY . '&cleared=1');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 로그 다운로드
|
||||
if ($action === 'download') {
|
||||
if (file_exists(LOG_FILE)) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="api_' . date('Ymd_His') . '.log"');
|
||||
readfile(LOG_FILE);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// 로그 내용 조회
|
||||
$logLines = [];
|
||||
$fileSize = 0;
|
||||
$fileExist = file_exists(LOG_FILE);
|
||||
|
||||
if ($fileExist) {
|
||||
$fileSize = filesize(LOG_FILE);
|
||||
$raw = file(LOG_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
// 최신 줄이 위로 오도록 역순 + 최대 LINES_MAX 줄
|
||||
$logLines = array_slice(array_reverse($raw), 0, LINES_MAX);
|
||||
}
|
||||
|
||||
// ── 필터 ─────────────────────────────────────────────────────────
|
||||
$filter = trim($_GET['q'] ?? '');
|
||||
if ($filter !== '') {
|
||||
$logLines = array_values(array_filter(
|
||||
$logLines,
|
||||
fn($l) => stripos($l, $filter) !== false
|
||||
));
|
||||
}
|
||||
|
||||
// ── HTML 출력 ─────────────────────────────────────────────────────
|
||||
$cleared = isset($_GET['cleared']);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>API Log Viewer</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: 'Consolas', monospace; background: #0d1117; color: #c9d1d9; padding: 16px; font-size: 13px; }
|
||||
h1 { color: #58a6ff; margin-bottom: 12px; font-size: 18px; }
|
||||
.toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
|
||||
.toolbar input { flex: 1; min-width: 200px; padding: 6px 10px; background: #161b22; border: 1px solid #30363d; color: #c9d1d9; border-radius: 4px; }
|
||||
.toolbar a, .toolbar button { padding: 6px 14px; border-radius: 4px; text-decoration: none; font-size: 12px; cursor: pointer; border: none; }
|
||||
.btn-refresh { background: #1f6feb; color: #fff; }
|
||||
.btn-clear { background: #da3633; color: #fff; }
|
||||
.btn-dl { background: #238636; color: #fff; }
|
||||
.meta { color: #8b949e; font-size: 11px; margin-bottom: 8px; }
|
||||
.notice { background: #1c2128; border: 1px solid #30363d; padding: 24px; border-radius: 6px; color: #8b949e; text-align: center; }
|
||||
.log-table { width: 100%; border-collapse: collapse; }
|
||||
.log-table tr:nth-child(even) { background: #161b22; }
|
||||
.log-table td { padding: 4px 8px; vertical-align: top; border-bottom: 1px solid #21262d; word-break: break-all; }
|
||||
.td-no { color: #8b949e; width: 40px; text-align: right; user-select: none; }
|
||||
.td-line { white-space: pre-wrap; }
|
||||
/* 태그별 색상 */
|
||||
.tag-save_learning { color: #79c0ff; }
|
||||
.tag-user_keywords { color: #56d364; }
|
||||
.tag-error { color: #f85149; }
|
||||
.tag-warn { color: #d29922; }
|
||||
.cleared { background: #1b2a1b; border: 1px solid #238636; color: #56d364; padding: 8px 12px; border-radius: 4px; margin-bottom: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔍 API Log Viewer</h1>
|
||||
|
||||
<?php if ($cleared): ?>
|
||||
<div class="cleared">✅ 로그가 초기화됐습니다.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="toolbar">
|
||||
<form method="get" style="display:contents">
|
||||
<input type="hidden" name="key" value="<?= htmlspecialchars(DEBUG_KEY) ?>">
|
||||
<input type="text" name="q" placeholder="필터 (예: save_learning, unregistered, U001 ...)"
|
||||
value="<?= htmlspecialchars($filter) ?>">
|
||||
<button type="submit" class="btn-refresh">🔍 검색</button>
|
||||
</form>
|
||||
<a href="?key=<?= DEBUG_KEY ?>" class="btn-refresh">↺ 새로고침</a>
|
||||
<a href="?key=<?= DEBUG_KEY ?>&action=download" class="btn-dl">⬇ 다운로드</a>
|
||||
<a href="?key=<?= DEBUG_KEY ?>&action=clear"
|
||||
onclick="return confirm('로그를 모두 삭제하시겠습니까?')" class="btn-clear">🗑 초기화</a>
|
||||
</div>
|
||||
|
||||
<?php if (!$fileExist || $fileSize === 0): ?>
|
||||
<div class="notice">
|
||||
<?= $fileExist ? '로그 파일이 비어 있습니다.' : '아직 로그 파일이 없습니다.<br>API를 한 번 호출하면 자동 생성됩니다.' ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<div class="meta">
|
||||
파일: <?= htmlspecialchars(LOG_FILE) ?> |
|
||||
크기: <?= number_format($fileSize) ?> bytes |
|
||||
표시: <?= count($logLines) ?>줄
|
||||
<?= $filter !== '' ? ' (필터 적용: <strong>' . htmlspecialchars($filter) . '</strong>)' : '' ?>
|
||||
</div>
|
||||
|
||||
<table class="log-table">
|
||||
<?php foreach ($logLines as $i => $line):
|
||||
// 태그 추출 for 색상 ([2026-03-10 12:34:56] [태그] ...)
|
||||
preg_match('/\[([^\]]+)\]\s*\[([^\]]+)\]/', $line, $m);
|
||||
$tagClass = '';
|
||||
if (!empty($m[2])) {
|
||||
$t = strtolower($m[2]);
|
||||
$tagClass = 'tag-' . preg_replace('/[^a-z0-9_]/', '_', $t);
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td class="td-no"><?= count($logLines) - $i ?></td>
|
||||
<td class="td-line <?= htmlspecialchars($tagClass) ?>"><?= htmlspecialchars($line) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
// 30초마다 자동 갱신 (필터 없을 때만)
|
||||
<?php if ($filter === ''): ?>
|
||||
setTimeout(() => location.reload(), 30000);
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
/**
|
||||
* 외부 인트라넷 → 배움터 진입 분기 페이지 (entry_new.php)
|
||||
*
|
||||
* 기존 세션 데이터를 초기화한 후 새로운 사용자 세션을 할당합니다.
|
||||
*/
|
||||
/**
|
||||
* 외부 인트라넷 → 배움터 진입 API
|
||||
*
|
||||
* 인트라넷에서 POST 방식으로 member_id, sys_comp_code를 전달받아
|
||||
* edu_users 테이블에서 사용자를 확인하고, intro_flag 값에 따라
|
||||
* 적절한 페이지로 리다이렉트합니다.
|
||||
*
|
||||
* POST 파라미터:
|
||||
* - member_id (필수) 사용자 ID
|
||||
* - sys_comp_code (필수) 회사 코드
|
||||
*
|
||||
* 사용 예 (인트라넷 HTML form):
|
||||
* <form method="POST" action="https://baroncs.co.kr/bbs/entry.php">
|
||||
* <input type="hidden" name="member_id" value="U001" />
|
||||
* <input type="hidden" name="sys_comp_code" value="COMP01" />
|
||||
* <button type="submit">배움터</button>
|
||||
* </form>
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// 1. 요청 방식 및 파라미터 검증
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
exit('허용되지 않는 요청 방식입니다.');
|
||||
}
|
||||
|
||||
$memberId = trim((string)($_POST['member_id'] ?? ''));
|
||||
$sysCompCode = trim((string)($_POST['sys_comp_code'] ?? ''));
|
||||
|
||||
// 필수 값이 없으면 로그인 페이지로 이동
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
header("Location: /skin/login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// --- [일일 통합 동기화 체크/실행] ---
|
||||
// 동기화 실패가 사용자 로그인 자체를 막지 않도록 분리 처리한다.
|
||||
$lockAcquired = false;
|
||||
try {
|
||||
$stmtLock = $pdo->query("SELECT GET_LOCK('edu_users_daily_sync', 5)");
|
||||
$lockAcquired = ((int)$stmtLock->fetchColumn() === 1);
|
||||
|
||||
if ($lockAcquired) {
|
||||
$stmtCheck = $pdo->query(
|
||||
"SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM edu_users_daily_logs
|
||||
WHERE DATE(injection_at) = CURDATE()
|
||||
)"
|
||||
);
|
||||
$isDone = ((int)$stmtCheck->fetchColumn() === 1);
|
||||
|
||||
if (!$isDone) {
|
||||
set_time_limit(0);
|
||||
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
$syncUrl = $scheme . '://' . $host . '/ajax/get_users_info.php';
|
||||
|
||||
$chSync = curl_init();
|
||||
curl_setopt($chSync, CURLOPT_URL, $syncUrl);
|
||||
curl_setopt($chSync, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($chSync, CURLOPT_TIMEOUT, 1800);
|
||||
curl_setopt($chSync, CURLOPT_HTTPHEADER, ["Accept: text/html"]);
|
||||
|
||||
$syncResponse = curl_exec($chSync);
|
||||
$syncHttpCode = curl_getinfo($chSync, CURLINFO_HTTP_CODE);
|
||||
$syncError = curl_error($chSync);
|
||||
curl_close($chSync);
|
||||
|
||||
if ($syncResponse === false || $syncHttpCode !== 200) {
|
||||
throw new RuntimeException('daily sync API failed: http=' . $syncHttpCode . ', err=' . $syncError);
|
||||
}
|
||||
|
||||
$stmtDaily = $pdo->prepare("CALL proc_edu_users_daily(?)");
|
||||
$stmtDaily->execute([$memberId]);
|
||||
}
|
||||
} else {
|
||||
error_log('[entry.php] daily sync lock not acquired, skip sync this request.');
|
||||
}
|
||||
} catch (Throwable $syncEx) {
|
||||
error_log('[entry.php] daily sync failed: ' . $syncEx->getMessage());
|
||||
} finally {
|
||||
if ($lockAcquired) {
|
||||
try {
|
||||
$pdo->query("SELECT RELEASE_LOCK('edu_users_daily_sync')");
|
||||
} catch (Throwable $unlockEx) {
|
||||
error_log('[entry.php] release lock failed: ' . $unlockEx->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- [일일 통합 동기화 체크/실행 끝] ---
|
||||
|
||||
// 2. 사용자 조회 (member_id, sys_comp_code 기반)
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT member_id, sys_comp_code, name, rank_name, intro_flag
|
||||
FROM edu_users
|
||||
WHERE member_id = ? AND sys_comp_code = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.auth_level,
|
||||
u.intro_flag,
|
||||
|
||||
ec.code,
|
||||
ec.code_name as comp_name
|
||||
|
||||
FROM edu_users u
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT code,code_name FROM edu_codes WHERE group_code = 'CO100'
|
||||
) ec
|
||||
ON ec.code = u.belong_comp
|
||||
WHERE u.member_id = ? AND u.sys_comp_code = ?
|
||||
LIMIT 1
|
||||
"
|
||||
);
|
||||
|
||||
$stmt->execute([$memberId, $sysCompCode]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 사용자가 없으면 로그인 페이지로 이동
|
||||
if (!$user) {
|
||||
header("Location: http://erp.baroncs.co.kr/mobile/sys/controller/Study_controller.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- [세션 초기화 단계] ---
|
||||
// 새로운 사용자의 정보를 담기 전에 기존 세션 변수를 모두 제거합니다.
|
||||
session_unset();
|
||||
// 보안을 위해 세션 ID를 새로 발급합니다. (기존 세션 데이터 삭제 포함)
|
||||
session_regenerate_id(true);
|
||||
// -------------------------
|
||||
|
||||
// 3. 변수 정리 및 세션 저장 (요청하신 규칙 엄격 적용)
|
||||
$U_member_id = $user['member_id'];
|
||||
$U_sys_comp_code = $user['sys_comp_code'];
|
||||
$U_name = $user['name'];
|
||||
$U_rank_name = $user['rank_name'];
|
||||
$U_auth_level = (string)($user['auth_level'] ?? '');
|
||||
$U_comp_name = $user['comp_name'];
|
||||
$U_intro_flag = (string)$user['intro_flag']; // '0' 또는 '1'
|
||||
|
||||
|
||||
$_SESSION['member_id'] = $U_member_id;
|
||||
$_SESSION['comp_name'] = $U_comp_name;
|
||||
$_SESSION['sys_comp_code'] = $U_sys_comp_code;
|
||||
|
||||
$_SESSION['member_name'] = $U_name;
|
||||
$_SESSION['name'] = $U_name;
|
||||
$_SESSION['rank_name'] = $U_rank_name;
|
||||
$_SESSION['auth_level'] = $U_auth_level;
|
||||
$_SESSION['intro_flag'] = $U_intro_flag;
|
||||
|
||||
|
||||
|
||||
// 2026-04-02 권오재 추가.--- [추가: 접속 이력 프로시저 호출] ---
|
||||
// 1. 정보 수집
|
||||
$ip_address = $_SERVER['REMOTE_ADDR'];
|
||||
$user_agent = $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
// OS 및 디바이스 판별 (간단한 예시)
|
||||
$os_name = 'Unknown OS';
|
||||
if (preg_match('/windows|win32/i', $user_agent))
|
||||
$os_name = 'Windows';
|
||||
else if (preg_match('/macintosh|mac os x/i', $user_agent))
|
||||
$os_name = 'Mac OS';
|
||||
else if (preg_match('/android/i', $user_agent))
|
||||
$os_name = 'Android';
|
||||
else if (preg_match('/iphone/i', $user_agent))
|
||||
$os_name = 'iOS';
|
||||
|
||||
$device_type = (preg_match('/mobile|android|iphone|ipad/i', $user_agent)) ? 'Mobile' : 'PC';
|
||||
|
||||
// 브라우저명 추출 (Whale을 Chrome보다 우선 판별)
|
||||
$browser_name = 'Unknown Browser';
|
||||
if (stripos($user_agent, 'Whale') !== false)
|
||||
$browser_name = 'Whale';
|
||||
else if (stripos($user_agent, 'Edg') !== false || stripos($user_agent, 'Edge') !== false)
|
||||
$browser_name = 'Edge';
|
||||
else if (stripos($user_agent, 'MSIE') !== false || stripos($user_agent, 'Trident') !== false)
|
||||
$browser_name = 'IE';
|
||||
else if (stripos($user_agent, 'Chrome') !== false)
|
||||
$browser_name = 'Chrome';
|
||||
else if (stripos($user_agent, 'Firefox') !== false)
|
||||
$browser_name = 'Firefox';
|
||||
else if (stripos($user_agent, 'Safari') !== false)
|
||||
$browser_name = 'Safari';
|
||||
|
||||
// 2. 프로시저 실행
|
||||
$stmtLog = $pdo->prepare("CALL proc_insert_edu_access_log(?, ?, ?, ?, ?, ?)");
|
||||
$stmtLog->execute([
|
||||
$_SESSION['member_id'],
|
||||
$_SESSION['sys_comp_code'],
|
||||
$ip_address,
|
||||
$os_name,
|
||||
$device_type,
|
||||
$browser_name
|
||||
]);
|
||||
// 4. 리다이렉트 분기 및 DB 업데이트
|
||||
if ($U_intro_flag === '1') {
|
||||
// 읽음 완료 상태: 메인으로 이동
|
||||
header("Location: /skin/index.php");
|
||||
exit;
|
||||
} else {
|
||||
// 읽지 않음(0) 상태: DB 업데이트 후 인트로로 이동
|
||||
$stmtUpdate = $pdo->prepare(
|
||||
'UPDATE edu_users
|
||||
SET intro_flag = "1"
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND intro_flag = "0"'
|
||||
);
|
||||
$stmtUpdate->execute([$U_member_id, $U_sys_comp_code]);
|
||||
|
||||
// 업데이트 성공 후 세션 동기화 (인트로 이동 직전)
|
||||
$_SESSION['intro_flag'] = '1';
|
||||
|
||||
header("Location: /skin/intro.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Entry Error: " . $e->getMessage());
|
||||
exit('시스템 오류가 발생했습니다. 잠시 후 다시 시도해주세요.');
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* 외부 인트라넷 → 배움터 진입 API
|
||||
*
|
||||
* 인트라넷에서 POST 방식으로 member_id, sys_comp_code를 전달받아
|
||||
* edu_users 테이블에서 사용자를 확인하고, intro_flag 값에 따라
|
||||
* 적절한 페이지로 리다이렉트합니다.
|
||||
*
|
||||
* POST 파라미터:
|
||||
* - member_id (필수) 사용자 ID
|
||||
* - sys_comp_code (필수) 회사 코드
|
||||
*
|
||||
* 사용 예 (인트라넷 HTML form):
|
||||
* <form method="POST" action="https://baroncs.co.kr/edu/bbs/entry.php">
|
||||
* <input type="hidden" name="member_id" value="U001" />
|
||||
* <input type="hidden" name="sys_comp_code" value="COMP01" />
|
||||
* <button type="submit">배움터</button>
|
||||
* </form>
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
exit('허용되지 않는 요청 방식입니다.');
|
||||
}
|
||||
|
||||
$memberId = trim((string)($_POST['member_id'] ?? ''));
|
||||
$sysCompCode = trim((string)($_POST['sys_comp_code'] ?? ''));
|
||||
|
||||
// 테스트 토글: 아래 true 라인을 주석 해제하면 intro_flag와 무관하게 index.php로 진입
|
||||
// $forceIndexRedirectForTest = false;
|
||||
$forceIndexRedirectForTest = true;
|
||||
|
||||
// 테스트 토글: intro_flag 자동 업데이트(0->1) 실행 여부
|
||||
$enableIntroFlagAutoUpdate = true;
|
||||
// $enableIntroFlagAutoUpdate = false;
|
||||
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
http_response_code(400);
|
||||
exit('필수 파라미터가 누락되었습니다.');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// [1] 사용자 조회
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT name, rank_name, intro_flag
|
||||
FROM edu_users
|
||||
WHERE member_id = ? AND sys_comp_code = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$memberId, $sysCompCode]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// [2] 신규 사용자 자동 등록
|
||||
// if (!$user) {
|
||||
// // 인트라넷에서 이름을 안 보내줄 경우를 대비한 기본값
|
||||
// $newName = trim((string)($_POST['name'] ?? '신규사용자'));
|
||||
// $newRank = trim((string)($_POST['rank_name'] ?? '사원'));
|
||||
|
||||
// $ins = $pdo->prepare(
|
||||
// 'INSERT INTO edu_users (member_id, sys_comp_code, name, rank_name, intro_flag, created_at, is_active)
|
||||
// VALUES (?, ?, ?, ?, "0", NOW(), "Y")'
|
||||
// );
|
||||
// $ins->execute([$memberId, $sysCompCode, $newName, $newRank]);
|
||||
|
||||
// $user = ['name' => $newName, 'rank_name' => $newRank, 'intro_flag' => '0'];
|
||||
// }
|
||||
|
||||
// [3] 세션 저장 (통합 및 정리)
|
||||
// 여러 시스템 호환을 위해 필요한 키값을 모두 채워주되, 이름은 'member_name'으로 통일하는 것이 좋습니다.
|
||||
$_SESSION['member_id'] = $memberId;
|
||||
$_SESSION['user_id'] = $memberId;
|
||||
$_SESSION['ss_mb_id'] = $memberId;
|
||||
$_SESSION['sys_comp_code'] = $sysCompCode;
|
||||
$_SESSION['company'] = $sysCompCode;
|
||||
|
||||
$_SESSION['member_name'] = $user['name']; // 인트로에서 쓸 변수
|
||||
$_SESSION['name'] = $user['name']; // 일반 페이지용
|
||||
$_SESSION['rank_name'] = $user['rank_name']; // 직책
|
||||
|
||||
// 세션 보안 강화
|
||||
session_regenerate_id(true);
|
||||
|
||||
// [4] 리다이렉트
|
||||
$introFlag = (string)($user['intro_flag'] ?? '0');
|
||||
$redirectUrl = ($introFlag === '1') ? '/edu/skin/index.php' : '/edu/skin/intro.php';
|
||||
|
||||
if ($forceIndexRedirectForTest) {
|
||||
$redirectUrl = '/edu/skin/index.php';
|
||||
}
|
||||
|
||||
// index.php 진입 시 intro_flag가 0이면 자동으로 1로 승격 (토글 가능)
|
||||
if ($enableIntroFlagAutoUpdate && $redirectUrl === '/edu/skin/index.php' && $introFlag === '0') {
|
||||
$stmtUpdateIntroFlag = $pdo->prepare(
|
||||
'UPDATE edu_users
|
||||
SET intro_flag = ?
|
||||
WHERE member_id = ? AND sys_comp_code = ? AND intro_flag = 0'
|
||||
);
|
||||
$stmtUpdateIntroFlag->execute(['1', $memberId, $sysCompCode]);
|
||||
$introFlag = '1';
|
||||
}
|
||||
|
||||
header("Location: $redirectUrl");
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
exit('오류: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// function goToEdu(memberId, sysCompCode) {
|
||||
// var form = document.createElement('form');
|
||||
// form.method = 'POST';
|
||||
// form.action = 'https://baroncs.co.kr/edu/bbs/entry.php';
|
||||
|
||||
// var inputId = document.createElement('input');
|
||||
// inputId.type = 'hidden';
|
||||
// inputId.name = 'member_id';
|
||||
// inputId.value = memberId;
|
||||
|
||||
// var inputComp = document.createElement('input');
|
||||
// inputComp.type = 'hidden';
|
||||
// inputComp.name = 'sys_comp_code';
|
||||
// inputComp.value = sysCompCode;
|
||||
|
||||
// form.appendChild(inputId);
|
||||
// form.appendChild(inputComp);
|
||||
// document.body.appendChild(form);
|
||||
// form.submit();
|
||||
// }
|
||||
|
||||
// <a href="#" onclick="goToEdu('U001', 'COMP01'); return false;">배움터</a>
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* /bbs/entry_moon.php
|
||||
* 그누보드 기반 시스템 분기 및 세션 처리 페이지
|
||||
*/
|
||||
|
||||
// 1. 그누보드 환경 설정 로드 (경로에 맞춰 수정 필요)
|
||||
// 보통 그누보드 루트의 common.php를 불러오면 세션 시작 및 DB 연결이 포함됩니다.
|
||||
include_once('./_common.php');
|
||||
|
||||
/**
|
||||
* 만약 common.php를 쓰지 않고 별도 DB 연결을 사용한다면
|
||||
* 아래와 같이 세션과 DB 연결을 직접 처리합니다.
|
||||
*/
|
||||
/*
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
*/
|
||||
|
||||
// 2. POST 파라미터 체크
|
||||
$member_id = isset($_POST['member_id']) ? trim((string)$_POST['member_id']) : '';
|
||||
$sys_comp_code = isset($_POST['sys_comp_code']) ? trim((string)$_POST['sys_comp_code']) : '';
|
||||
|
||||
// 값이 없거나 널이면 로그인 페이지로 이동
|
||||
if (!$member_id || !$sys_comp_code) {
|
||||
header("Location: /skin/login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 3. 사용자 조회 (Prepared Statement)
|
||||
$sql = "SELECT member_id, sys_comp_code, name, rank_name, intro_flag
|
||||
FROM edu_users
|
||||
WHERE member_id = ? AND sys_comp_code = ?
|
||||
LIMIT 1";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$member_id, $sys_comp_code]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 결과값이 없으면 로그인 페이지로 이동
|
||||
if (!$user) {
|
||||
header("Location: /skin/login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. 변수 정리 및 세션 할당
|
||||
$U_member_id = $user['member_id'];
|
||||
$U_sys_comp_code = $user['sys_comp_code'];
|
||||
$U_name = $user['name'];
|
||||
$U_rank_name = $user['rank_name'];
|
||||
$U_intro_flag = (int)$user['intro_flag'];
|
||||
|
||||
// 요청하신 세션 할당 규칙 적용
|
||||
$_SESSION['user_id'] = $U_member_id;
|
||||
$_SESSION['ss_mb_id'] = $U_member_id; // 그누보드 표준 세션 키
|
||||
$_SESSION['sys_comp_code'] = $U_sys_comp_code; // 기획안에 따른 flag 할당
|
||||
$_SESSION['company'] = $U_sys_comp_code; // 기획안에 따른 flag 할당
|
||||
$_SESSION['member_name'] = $U_name;
|
||||
$_SESSION['name'] = $U_name;
|
||||
$_SESSION['rank_name'] = $U_rank_name;
|
||||
$_SESSION['intro_flag'] = $U_intro_flag;
|
||||
|
||||
// 세션 보안을 위한 ID 재생성
|
||||
session_regenerate_id(true);
|
||||
|
||||
// 5. 페이지 이동 분기
|
||||
if ($U_intro_flag === 1) {
|
||||
// 이미 읽음 상태면 메인으로
|
||||
header("Location: /skin/index.php");
|
||||
exit;
|
||||
} else {
|
||||
// 읽지 않음(0) 상태면 DB 업데이트 후 인트로로
|
||||
$update_sql = "UPDATE edu_users
|
||||
SET intro_flag = 1
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND intro_flag = 0";
|
||||
|
||||
$up_stmt = $pdo->prepare($update_sql);
|
||||
$up_stmt->execute([$U_member_id, $U_sys_comp_code]);
|
||||
|
||||
// 업데이트 성공 여부와 상관없이(혹은 성공 직후) 세션 동기화 후 이동
|
||||
$_SESSION['intro_flag'] = 1;
|
||||
$_SESSION['sys_comp_code'] = 1;
|
||||
$_SESSION['company'] = 1;
|
||||
|
||||
header("Location: /skin/intro.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// 운영 환경에서는 에러 로그만 남기고 로그인 페이지로 보내는 것이 안전합니다.
|
||||
error_log($e->getMessage());
|
||||
header("Location: /skin/login.php");
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* 외부 인트라넷 → 배움터 진입 분기 페이지 (entry_new.php)
|
||||
*
|
||||
* 기존 세션 데이터를 초기화한 후 새로운 사용자 세션을 할당합니다.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// 1. 요청 방식 및 파라미터 검증
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
exit('허용되지 않는 요청 방식입니다.');
|
||||
}
|
||||
|
||||
$memberId = trim((string)($_POST['member_id'] ?? ''));
|
||||
$sysCompCode = trim((string)($_POST['sys_comp_code'] ?? ''));
|
||||
|
||||
// 필수 값이 없으면 로그인 페이지로 이동
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
header("Location: /skin/login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// 2. 사용자 조회 (member_id, sys_comp_code 기반)
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT member_id, sys_comp_code, name, rank_name, intro_flag
|
||||
FROM edu_users
|
||||
WHERE member_id = ? AND sys_comp_code = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.intro_flag,
|
||||
|
||||
ec.code,
|
||||
ec.code_name as comp_name
|
||||
|
||||
FROM edu_users u
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT code,code_name FROM edu_codes WHERE group_code = 'CO100'
|
||||
) ec
|
||||
ON ec.code = u.belong_comp
|
||||
WHERE u.member_id = ? AND u.sys_comp_code = ?
|
||||
LIMIT 1
|
||||
"
|
||||
);
|
||||
|
||||
$stmt->execute([$memberId, $sysCompCode]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 사용자가 없으면 로그인 페이지로 이동
|
||||
if (!$user) {
|
||||
header("Location: /skin/login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- [세션 초기화 단계] ---
|
||||
// 새로운 사용자의 정보를 담기 전에 기존 세션 변수를 모두 제거합니다.
|
||||
session_unset();
|
||||
// 보안을 위해 세션 ID를 새로 발급합니다. (기존 세션 데이터 삭제 포함)
|
||||
session_regenerate_id(true);
|
||||
// -------------------------
|
||||
|
||||
// 3. 변수 정리 및 세션 저장 (요청하신 규칙 엄격 적용)
|
||||
$U_member_id = $user['member_id'];
|
||||
$U_sys_comp_code = $user['sys_comp_code'];
|
||||
$U_name = $user['name'];
|
||||
$U_rank_name = $user['rank_name'];
|
||||
$U_comp_name = $user['comp_name'];
|
||||
$U_intro_flag = (string)$user['intro_flag']; // '0' 또는 '1'
|
||||
|
||||
$_SESSION['user_id'] = $U_member_id;
|
||||
$_SESSION['ss_mb_id'] = $U_member_id;
|
||||
$_SESSION['comp_name'] = $U_comp_name;
|
||||
$_SESSION['sys_comp_code'] = $U_sys_comp_code;
|
||||
$_SESSION['company'] = $U_sys_comp_code;
|
||||
|
||||
$_SESSION['member_name'] = $U_name;
|
||||
$_SESSION['name'] = $U_name;
|
||||
$_SESSION['rank_name'] = $U_rank_name;
|
||||
$_SESSION['intro_flag'] = $U_intro_flag;
|
||||
|
||||
// 4. 리다이렉트 분기 및 DB 업데이트
|
||||
if ($U_intro_flag === '1') {
|
||||
// 읽음 완료 상태: 메인으로 이동
|
||||
header("Location: /skin/index.php");
|
||||
exit;
|
||||
} else {
|
||||
// 읽지 않음(0) 상태: DB 업데이트 후 인트로로 이동
|
||||
$stmtUpdate = $pdo->prepare(
|
||||
'UPDATE edu_users
|
||||
SET intro_flag = "1"
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND intro_flag = "0"'
|
||||
);
|
||||
$stmtUpdate->execute([$U_member_id, $U_sys_comp_code]);
|
||||
|
||||
// 업데이트 성공 후 세션 동기화 (인트로 이동 직전)
|
||||
$_SESSION['intro_flag'] = '1';
|
||||
|
||||
header("Location: /skin/intro.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Entry Error: " . $e->getMessage());
|
||||
exit('시스템 오류가 발생했습니다. 잠시 후 다시 시도해주세요.');
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Index page backend entrypoint.
|
||||
* Keep view/backend filename aligned: skin/index.php <-> bbs/index.php
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/main_data.php';
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 헤더 진도율 초기데이터
|
||||
// - 마이클래스 / 온보딩 / 법정교육
|
||||
// - 현재년도 기준
|
||||
// - 마이페이지 "나의 학습 활동"과 동일한 시간기준 진도율 사용
|
||||
//=========================
|
||||
require_once __DIR__ . '/../bbs/db_conn.php';
|
||||
|
||||
// 세션 시작
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
// $memberId = 'U001';
|
||||
// $sysCompCode = 'COMP01';
|
||||
|
||||
|
||||
/*
|
||||
// ---------------------------------------------------------
|
||||
// 헤더에서 바로 사용할 기본 변수
|
||||
// ---------------------------------------------------------
|
||||
$header_myclass_percent = 0;
|
||||
$header_onboarding_percent = 0;
|
||||
$header_legal_percent = 0;
|
||||
*/
|
||||
// ---------------------------------------------------------
|
||||
// 세션값이 없으면 기본값 0으로 종료
|
||||
// ---------------------------------------------------------
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 현재년도
|
||||
$currentYear = (int)date('Y');
|
||||
// 현재 분기코드 (CA200Q01 ~ CA200Q04)
|
||||
$currentQuarterNum = (int)ceil((int)date('m') / 3);
|
||||
$currentQuarterCode = sprintf('CA200Q%02d', $currentQuarterNum);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. 마이클래스(CA10001) 진도율
|
||||
// - 마이페이지와 동일 기준:
|
||||
// percent = SUM(watch_tm) / SUM(content_tm) * 100
|
||||
// - 현재년도 기준: YEAR(last_viewed_at) = 현재년도
|
||||
// ---------------------------------------------------------
|
||||
$stmtMyclass = $pdo->prepare("
|
||||
SELECT fn_get_progress_rate(:sys_comp_code,:current_year,:member_id,'CA10001',:quarter_code) AS percent ;
|
||||
");
|
||||
// $stmtMyclass = $pdo->prepare("
|
||||
// SELECT
|
||||
// CASE
|
||||
// WHEN COALESCE(SUM(lh.content_tm), 0) > 0
|
||||
// THEN ROUND(COALESCE(SUM(lh.watch_tm), 0) / SUM(lh.content_tm) * 100)
|
||||
// ELSE 0
|
||||
// END AS percent
|
||||
// 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 c.category_code = 'CA10001'
|
||||
// AND YEAR(lh.last_viewed_at) = :current_year
|
||||
// ");
|
||||
$stmtMyclass->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':current_year' => $currentYear,
|
||||
':quarter_code' => $currentQuarterCode,
|
||||
]);
|
||||
$header_myclass_percent = (int)$stmtMyclass->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. 온보딩(CA10002) 진도율
|
||||
// - 마이페이지와 동일 기준:
|
||||
// percent = SUM(watch_tm) / SUM(content_tm) * 100
|
||||
// - 현재년도 기준: YEAR(last_viewed_at) = 현재년도
|
||||
// ---------------------------------------------------------
|
||||
$stmtOnboarding = $pdo->prepare("
|
||||
SELECT fn_get_progress_rate(:sys_comp_code,:current_year,:member_id,'CA10002','') AS percent ;
|
||||
");
|
||||
// $stmtOnboarding = $pdo->prepare("
|
||||
// SELECT
|
||||
// CASE
|
||||
// WHEN COALESCE(SUM(lh.content_tm), 0) > 0
|
||||
// THEN ROUND(COALESCE(SUM(lh.watch_tm), 0) / SUM(lh.content_tm) * 100)
|
||||
// ELSE 0
|
||||
// END AS percent
|
||||
// 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 c.category_code = 'CA10002'
|
||||
// AND YEAR(lh.last_viewed_at) = :current_year
|
||||
// ");
|
||||
$stmtOnboarding->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':current_year' => $currentYear,
|
||||
]);
|
||||
$header_onboarding_percent = (int)$stmtOnboarding->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. 법정교육(CA10003) 진도율
|
||||
// - 마이페이지와 동일 기준:
|
||||
// percent = SUM(watch_tm) / SUM(content_tm) * 100
|
||||
// - 현재년도 기준: YEAR(last_viewed_at) = 현재년도
|
||||
// ---------------------------------------------------------
|
||||
$stmtLegal = $pdo->prepare("
|
||||
SELECT fn_get_progress_rate(:sys_comp_code,:current_year,:member_id,'CA10003','') AS percent ;
|
||||
");
|
||||
// $stmtLegal = $pdo->prepare("
|
||||
// SELECT
|
||||
// CASE
|
||||
// WHEN COALESCE(SUM(lh.content_tm), 0) > 0
|
||||
// THEN ROUND(COALESCE(SUM(lh.watch_tm), 0) / SUM(lh.content_tm) * 100)
|
||||
// ELSE 0
|
||||
// END AS percent
|
||||
// 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 c.category_code = 'CA10003'
|
||||
// AND YEAR(lh.last_viewed_at) = :current_year
|
||||
// ");
|
||||
$stmtLegal->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':current_year' => $currentYear,
|
||||
]);
|
||||
$header_legal_percent = (int)$stmtLegal->fetchColumn();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 예외 방어: 0~100 범위 보정
|
||||
// ---------------------------------------------------------
|
||||
$header_myclass_percent = max(0, min(100, $header_myclass_percent));
|
||||
$header_onboarding_percent = max(0, min(100, $header_onboarding_percent));
|
||||
$header_legal_percent = max(0, min(100, $header_legal_percent));
|
||||
|
||||
if($memberId=="M21420" ){
|
||||
//echo "test=".$memberId.":".$header_myclass_percent;
|
||||
//echo "test=".$memberId.":".$header_onboarding_percent;
|
||||
//echo "test=".$memberId.":".$header_legal_percent;
|
||||
|
||||
//exit;
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// 헤더는 페이지 공통영역이므로, 오류가 나더라도 전체 화면이 죽지 않게
|
||||
// 기본값 0 유지
|
||||
$header_myclass_percent = 0;
|
||||
$header_onboarding_percent = 0;
|
||||
$header_legal_percent = 0;
|
||||
|
||||
//echo "test 11=".$memberId.":".$header_myclass_percent;
|
||||
|
||||
// 필요시 개발단계에서만 로그 확인
|
||||
// error_log('[init_data_for_header.php] ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
require_once __DIR__ . '/api_log.php';
|
||||
|
||||
// 외부(호출하는 파일)에서 $SET_PREFIX를 정의하지 않았을 경우를 대비한 기본값 설정
|
||||
$prefixCondition = isset($SET_PREFIX) ? $SET_PREFIX : 'L'; //L=리더십(기본값), I=인사이트
|
||||
|
||||
// $memberId = 'U001';
|
||||
// $sysCompCode = 'COMP01';
|
||||
|
||||
//Svg
|
||||
if($prefixCondition=='L'){
|
||||
$prefixSvg = "leadership";
|
||||
}else if($prefixCondition=='I'){
|
||||
$prefixSvg = "insight";
|
||||
}
|
||||
|
||||
//배너설정
|
||||
if($prefixCondition=='L'){//L=리더십(기본값), I=인사이트
|
||||
$array_banner_img = [
|
||||
['normal' => '/img/leadership/img_banner_01.png', 'mobile' => '/img/leadership/img_banner_01_m.png', 'title' => '실천으로 완성하는 리더십'],
|
||||
['normal' => '/img/leadership/img_banner_02.png', 'mobile' => '/img/leadership/img_banner_02_m.png', 'title' => '성장하는 리더십 여정'],
|
||||
['normal' => '/img/leadership/img_banner_03.png', 'mobile' => '/img/leadership/img_banner_03_m.png', 'title' => '리더로 성장하는 과정'],
|
||||
];
|
||||
}else if($prefixCondition=='I'){//L=리더십(기본값), I=인사이트
|
||||
$array_banner_img = [
|
||||
['normal' => '/img/insight/img_banner_01.png', 'mobile' => '/img/insight/img_banner_01_m.png', 'title' => '배너 1'],
|
||||
['normal' => '/img/insight/img_banner_02.png', 'mobile' => '/img/insight/img_banner_02_m.png', 'title' => '배너 2'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$dbSuccess = false;
|
||||
$array_tab_info = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// LIKE 연산자를 안전하게 처리하기 위해 준비된 선언(Prepared Statement) 사용
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT group_code, base_code, code, code_name
|
||||
FROM edu_codes
|
||||
WHERE is_active = 1
|
||||
AND group_code = 'CA200'
|
||||
AND code LIKE :prefix
|
||||
ORDER BY base_code ASC
|
||||
");
|
||||
|
||||
// 'L%' 형태로 바인딩하여 검색
|
||||
$stmtCode->execute([':prefix' => $prefixCondition . '%']);
|
||||
$rows = $stmtCode->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($rows) {
|
||||
$i = 1;
|
||||
foreach ($rows as $cr) {
|
||||
$index = sprintf('%02d', $i);
|
||||
$index_src = '/img/ico/ico_'.$prefixSvg.'_' . $index . '.svg';
|
||||
|
||||
$array_tab_info[] = [
|
||||
'base_code' => $cr['base_code'],
|
||||
'code_name' => $cr['code_name'],
|
||||
'src' => $index_src
|
||||
];
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
$dbSuccess = true;
|
||||
} catch (Exception $e) {
|
||||
$errMsg = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
||||
error_log('[leadership_init_data.php] DB Error: ' . $errMsg);
|
||||
// api_log 함수가 있다면 기록
|
||||
if (function_exists('api_log')) {
|
||||
api_log('leadership_init', 'DB_ERROR', ['error' => $errMsg]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs/legal_learning_data.php — 법정교육 페이지 데이터
|
||||
*
|
||||
* 제공 변수:
|
||||
* $userName - 사용자 이름
|
||||
* $userRank - 직위/직급
|
||||
* $dDay - D-day 숫자
|
||||
* $deadlineStr - 마감일 문자열 (n월 j일)
|
||||
* $chapters - 챕터 배열 (id, code, name, lessons[])
|
||||
* $chaptersJson - $chapters JSON 문자열 (window.learningChapterData)
|
||||
* $configJson - window.learningConfigData 형식 JSON
|
||||
* $progressRate - 전체 진도율 (0~100 정수)
|
||||
*/
|
||||
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
require_once __DIR__ . '/api/common.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
// ── 1. 로그인 유저 ID (로그인 세션 변수 사용) ────────────────────────────
|
||||
$sessionUser = api_get_session_user();
|
||||
if (!$sessionUser || empty($sessionUser['member_id'])) {
|
||||
// 로그인 세션이 없으면 접근 불가 또는 리다이렉트 처리 (여기선 예시로 403)
|
||||
http_response_code(403);
|
||||
exit('로그인 세션이 필요합니다.');
|
||||
}
|
||||
$memberId = $sessionUser['member_id'];
|
||||
$sysCompCode = $sessionUser['sys_comp_code'];
|
||||
|
||||
// ── 2. 유저 정보 ─────────────────────────────────────────────────────────
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT name, rank_name, sys_comp_code
|
||||
FROM edu_users
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([$memberId, $sysCompCode]);
|
||||
$userRow = $stmtUser->fetch();
|
||||
$userName = $userRow['name'] ?? '사용자';
|
||||
$userRank = $userRow['rank_name'] ?? '';
|
||||
|
||||
// ── 3. 법정교육 카테고리/챕터 고정 매핑 (관리자 코드관리 기준) ───────────────
|
||||
$fixedLegalNameMap = [
|
||||
'C01' => '개인정보보호',
|
||||
'C02' => '직장내 괴롭힘 예방',
|
||||
'C03' => '장애인 인식 개선',
|
||||
'C04' => '성희롱 예방 교육',
|
||||
'C05' => '퇴직금 교육',
|
||||
// 'C06' => '산업안전보건',
|
||||
];
|
||||
|
||||
$normalizeLegalGroupCode = static function ($value) {
|
||||
$code = strtoupper(trim((string)$value));
|
||||
if (preg_match('/^CA200(C\d{2})$/', $code, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
if (preg_match('/^C\d{2}$/', $code)) {
|
||||
return $code;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
$legalCodes = array_keys($fixedLegalNameMap);
|
||||
$legalRawCodes = [];
|
||||
foreach ($legalCodes as $code) {
|
||||
$legalRawCodes[] = $code;
|
||||
$legalRawCodes[] = 'CA200' . $code;
|
||||
}
|
||||
$legalRawCodes = array_values(array_unique($legalRawCodes));
|
||||
|
||||
// 카테고리명 조회 (edu_codes 테이블)
|
||||
$phCodes = implode(',', array_fill(0, count($legalRawCodes), '?'));
|
||||
$stmtCodes = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE base_code IN ($phCodes)
|
||||
ORDER BY base_code
|
||||
");
|
||||
$stmtCodes->execute($legalRawCodes);
|
||||
$codeNameMap = [];
|
||||
foreach ($stmtCodes->fetchAll() as $row) {
|
||||
$codeNameMap[$row['base_code']] = $row['code_name'];
|
||||
}
|
||||
|
||||
// ── 4. 콘텐츠 + 시청 이력 조회 ───────────────────────────────────────────
|
||||
$stmtContents = $pdo->prepare("
|
||||
SELECT
|
||||
c.content_id,
|
||||
c.title,
|
||||
c.content_url,
|
||||
c.description,
|
||||
c.category_group,
|
||||
c.end_date,
|
||||
c.sort_order,
|
||||
COALESCE(lh.content_tm, 0) AS content_tm,
|
||||
COALESCE(lh.watch_tm, 0) AS watch_tm,
|
||||
COALESCE(lh.all_tm, 0) AS all_tm,
|
||||
lh.completed_at
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
WHERE c.is_active = 1
|
||||
AND c.category_code = 'CA10003'
|
||||
AND c.category_group IN ($phCodes)
|
||||
ORDER BY c.category_group, c.sort_order ASC, c.content_id ASC
|
||||
");
|
||||
$stmtContents->execute(array_merge([$memberId, $sysCompCode], $legalRawCodes));
|
||||
$rows = $stmtContents->fetchAll();
|
||||
|
||||
// ── 5. 디버그 정보 준비 ──────────────────────────────────────────────
|
||||
$debugData = [
|
||||
'WHERE_category_code' => 'CA10003',
|
||||
'WHERE_category_group_IN' => $legalRawCodes,
|
||||
'rows_count' => count($rows),
|
||||
'chapter_configs' => [],
|
||||
];
|
||||
foreach ($legalCodes as $code) {
|
||||
$debugData['chapter_configs'][$code] = [
|
||||
'normalize_from' => [$code, 'CA200' . $code],
|
||||
];
|
||||
}
|
||||
|
||||
// ── 6. 챕터별 그룹화 ─────────────────────────────────────────────────────
|
||||
$chapterMap = [];
|
||||
foreach ($legalCodes as $idx => $code) {
|
||||
$chapterMap[$code] = [
|
||||
'id' => $idx + 1,
|
||||
'code' => $code,
|
||||
'name' => $fixedLegalNameMap[$code] ?? ($codeNameMap[$code] ?? $code),
|
||||
'lessons' => [],
|
||||
'completed' => true, // 아래에서 미완료 레슨 있으면 false
|
||||
'min_end_date' => null,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$code = $normalizeLegalGroupCode($row['category_group'] ?? '');
|
||||
if ($code === '' || !isset($chapterMap[$code])) { continue; }
|
||||
|
||||
$contentTm = (int)$row['content_tm'];
|
||||
$watchTm = (int)$row['watch_tm'];
|
||||
$allTm = (int)$row['all_tm'];
|
||||
$completedAt = $row['completed_at'];
|
||||
$hasHistory = ($contentTm > 0 || $watchTm > 0 || !empty($completedAt));
|
||||
$completed = (!empty($completedAt));
|
||||
$status = !$hasHistory ? 'not-started' : ($completed ? 'completed' : 'in-progress');
|
||||
$lessonEndDate = !empty($row['end_date']) ? substr($row['end_date'], 0, 10) : null;
|
||||
$chapterMap[$code]['lessons'][] = [
|
||||
'content_id' => $row['content_id'],
|
||||
'title' => $row['title'],
|
||||
'url' => $row['content_url'],
|
||||
'description' => $row['description'] ?? '',
|
||||
'content_tm' => $contentTm,
|
||||
'watch_tm' => $watchTm,
|
||||
'all_tm' => $allTm,
|
||||
'completed' => $completed,
|
||||
'status' => $status,
|
||||
'end_date' => $lessonEndDate,
|
||||
'sort_order' => (int)($row['sort_order'] ?? 0),
|
||||
];
|
||||
if ($lessonEndDate !== null) {
|
||||
if ($chapterMap[$code]['min_end_date'] === null || $lessonEndDate < $chapterMap[$code]['min_end_date']) {
|
||||
$chapterMap[$code]['min_end_date'] = $lessonEndDate;
|
||||
}
|
||||
}
|
||||
if (!$completed) {
|
||||
$chapterMap[$code]['completed'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 영상이 없는 챕터는 미완료 처리
|
||||
foreach ($chapterMap as &$ch) {
|
||||
if (empty($ch['lessons'])) {
|
||||
$ch['completed'] = false;
|
||||
}
|
||||
}
|
||||
unset($ch);
|
||||
|
||||
$chapters = array_values($chapterMap);
|
||||
|
||||
// lessons를 sort_order 기준으로 재정렬 (동률 시 content_id ASC)
|
||||
foreach ($chapters as &$ch) {
|
||||
usort($ch['lessons'], static function ($a, $b) {
|
||||
$diff = ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0);
|
||||
return $diff !== 0 ? $diff : ($a['content_id'] <=> $b['content_id']);
|
||||
});
|
||||
}
|
||||
unset($ch);
|
||||
|
||||
// ── 6. 전체 진도율 계산 ──────────────────────────────────────────────────
|
||||
$totalLessons = 0;
|
||||
$completedLessons = 0;
|
||||
foreach ($chapters as $ch) {
|
||||
foreach ($ch['lessons'] as $lesson) {
|
||||
$totalLessons++;
|
||||
if ($lesson['completed']) { $completedLessons++; }
|
||||
}
|
||||
}
|
||||
$progressRate = $totalLessons > 0
|
||||
? (int)round($completedLessons / $totalLessons * 100)
|
||||
: 0;
|
||||
|
||||
// ── 7. D-day 계산 (edu_contents.end_date 최솟값 기준, 없으면 당해 12월 31일) ──
|
||||
$minEndDate = null;
|
||||
foreach ($chapters as $ch) {
|
||||
if (!empty($ch['min_end_date'])) {
|
||||
if ($minEndDate === null || $ch['min_end_date'] < $minEndDate) {
|
||||
$minEndDate = $ch['min_end_date'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$deadline = $minEndDate
|
||||
? new DateTime($minEndDate)
|
||||
: new DateTime(date('Y') . '-12-31');
|
||||
$today = new DateTime(date('Y-m-d'));
|
||||
$diff = $today->diff($deadline);
|
||||
$dDay = ($today <= $deadline) ? (int)$diff->days : 0;
|
||||
$deadlineStr = $deadline->format('n월 j일');
|
||||
|
||||
// ── 8. JS 주입용 JSON 생성 ────────────────────────────────────────────────
|
||||
$chaptersJson = json_encode($chapters, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// window.learningConfigData 형식: { chapterId: { completed, lessons:[{completed}] } }
|
||||
$configData = [];
|
||||
foreach ($chapters as $ch) {
|
||||
$chapterConfig = [
|
||||
'completed' => $ch['completed'],
|
||||
'lessons' => array_map(
|
||||
fn($l) => ['completed' => $l['completed']],
|
||||
$ch['lessons']
|
||||
),
|
||||
];
|
||||
|
||||
// 프론트 코드 호환을 위해 code/id 키 모두 제공
|
||||
$configData[$ch['code']] = $chapterConfig;
|
||||
$configData[(string)$ch['id']] = $chapterConfig;
|
||||
}
|
||||
$configJson = json_encode($configData, JSON_UNESCAPED_UNICODE);
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
require_once __DIR__ . '/auth.php';
|
||||
|
||||
edu_start_session();
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
|
||||
header('Location: /skin/login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$redirect = trim((string) ($_POST['redirect'] ?? '/skin/index.php'));
|
||||
if ($redirect === '' || strpos($redirect, '/') !== 0) {
|
||||
$redirect = '/skin/index.php';
|
||||
}
|
||||
|
||||
$memberIdInput = trim((string) ($_POST['member_id'] ?? ''));
|
||||
$passwordInput = trim((string) ($_POST['intra_pw'] ?? ''));
|
||||
$sysCompCode = trim((string) ($_POST['sys_comp_code'] ?? ''));
|
||||
//개발편의 : 임의셋팅 : 오픈후 삭제
|
||||
// if (strtoupper($memberIdInput) === '12') {
|
||||
// $memberIdInput = "U001";
|
||||
// $passwordInput = "baron3840";
|
||||
// }
|
||||
|
||||
if ($memberIdInput === '' || $passwordInput === '' || $sysCompCode === '') {
|
||||
$back = '/skin/login.php?error=' . rawurlencode('회사코드, ID, 비밀번호를 모두 입력해 주세요.')
|
||||
. '&redirect=' . rawurlencode($redirect)
|
||||
. '&member_id=' . rawurlencode($memberIdInput)
|
||||
. '&sys_comp_code=' . rawurlencode($sysCompCode);
|
||||
header('Location: ' . $back);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT *
|
||||
FROM edu_users
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->execute([$memberIdInput, $sysCompCode]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
|
||||
$storedPw = trim((string) ($user['intra_pw'] ?? ''));
|
||||
$verified = $user && ($storedPw !== '') && hash_equals($storedPw, $passwordInput);
|
||||
|
||||
if (!$verified) {
|
||||
$back = '/skin/login.php?error=' . rawurlencode('ID 또는 비밀번호가 올바르지 않습니다.')
|
||||
. '&redirect=' . rawurlencode($redirect)
|
||||
. '&member_id=' . rawurlencode($memberIdInput)
|
||||
. '&sys_comp_code=' . rawurlencode($sysCompCode);
|
||||
header('Location: ' . $back);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 세션 초기화 후 세션 지정
|
||||
$_SESSION = []; // 기존 세션 데이터 전체 초기화
|
||||
session_regenerate_id(true); // 새 세션 ID 발급 (세션 고정 공격 방지)
|
||||
|
||||
|
||||
$_SESSION['member_id'] = (string) $user['member_id'];
|
||||
$_SESSION['sys_comp_code'] = (string) ($user['sys_comp_code'] ?? '');
|
||||
$_SESSION['belong_comp'] = (string) ($user['belong_comp'] ?? '');
|
||||
$_SESSION['working_comp'] = (string) ($user['working_comp'] ?? '');
|
||||
$_SESSION['member_name'] = (string) ($user['name'] ?? '');
|
||||
$_SESSION['auth_level'] = (string) ($user['auth_level'] ?? 0);
|
||||
|
||||
// Keep the full edu_users row in session for cross-page use (password excluded).
|
||||
unset($user['intra_pw']);
|
||||
$_SESSION['edu_user'] = $user;
|
||||
$_SESSION['edu_user_all'] = $user;
|
||||
foreach ($user as $field => $value) {
|
||||
$_SESSION['edu_user_' . $field] = $value;
|
||||
}
|
||||
|
||||
$_SESSION['edu_login'] = '1';
|
||||
$_SESSION['edu_login_at'] = date('Y-m-d H:i:s');
|
||||
|
||||
// 2026-04-02 권오재 추가.--- [추가: 접속 이력 프로시저 호출] ---
|
||||
// 1. 정보 수집
|
||||
$ip_address = $_SERVER['REMOTE_ADDR'];
|
||||
$user_agent = $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
// OS 및 디바이스 판별 (간단한 예시)
|
||||
$os_name = 'Unknown OS';
|
||||
if (preg_match('/windows|win32/i', $user_agent))
|
||||
$os_name = 'Windows';
|
||||
else if (preg_match('/macintosh|mac os x/i', $user_agent))
|
||||
$os_name = 'Mac OS';
|
||||
else if (preg_match('/android/i', $user_agent))
|
||||
$os_name = 'Android';
|
||||
else if (preg_match('/iphone/i', $user_agent))
|
||||
$os_name = 'iOS';
|
||||
|
||||
$device_type = (preg_match('/mobile|android|iphone|ipad/i', $user_agent)) ? 'Mobile' : 'PC';
|
||||
|
||||
// 브라우저명 추출
|
||||
$browser_name = 'Unknown Browser';
|
||||
if (strpos($user_agent, 'MSIE') !== FALSE || strpos($user_agent, 'Trident') !== FALSE)
|
||||
$browser_name = 'IE';
|
||||
else if (strpos($user_agent, 'Edge') !== FALSE)
|
||||
$browser_name = 'Edge';
|
||||
else if (strpos($user_agent, 'Chrome') !== FALSE)
|
||||
$browser_name = 'Chrome';
|
||||
else if (strpos($user_agent, 'Firefox') !== FALSE)
|
||||
$browser_name = 'Firefox';
|
||||
else if (strpos($user_agent, 'Safari') !== FALSE)
|
||||
$browser_name = 'Safari';
|
||||
|
||||
// 2. 프로시저 실행
|
||||
$stmtLog = $pdo->prepare("CALL proc_insert_edu_access_log(?, ?, ?, ?, ?, ?)");
|
||||
$stmtLog->execute([
|
||||
$_SESSION['member_id'],
|
||||
$_SESSION['sys_comp_code'],
|
||||
$ip_address,
|
||||
$os_name,
|
||||
$device_type,
|
||||
$browser_name
|
||||
]);
|
||||
|
||||
header('Location: ' . $redirect);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
$back = '/skin/login.php?error=' . rawurlencode('로그인 처리 중 오류가 발생했습니다.')
|
||||
. '&redirect=' . rawurlencode($redirect)
|
||||
. '&member_id=' . rawurlencode($memberIdInput);
|
||||
header('Location: ' . $back);
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* 로그아웃 API
|
||||
* 모든 세션 데이터를 제거하고 쿠키를 삭제합니다.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
// 세션 시작
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// 로그 기록 (디버깅용)
|
||||
$logMsg = '[logout.php] Logout requested | Session ID: ' . session_id() . ' | Time: ' . date('Y-m-d H:i:s');
|
||||
error_log($logMsg);
|
||||
|
||||
// 현재 세션 데이터 로그 (디버깅용)
|
||||
$sessionKeys = implode(', ', array_keys($_SESSION));
|
||||
error_log('[logout.php] Session keys before clear: ' . ($sessionKeys ?: 'empty'));
|
||||
|
||||
// 모든 세션 변수 제거
|
||||
$_SESSION = [];
|
||||
|
||||
// 세션 쿠키 제거
|
||||
if (ini_get("session.use_cookies")) {
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
$cookiePath = $params['path'] ?: '/';
|
||||
$cookieDomain = $params['domain'] ?: '';
|
||||
$cookieSecure = $params['secure'] ?? false;
|
||||
$cookieHttpOnly = $params['httponly'] ?? true;
|
||||
|
||||
// PHP 7.3+ 배열 문법
|
||||
if (PHP_VERSION_ID >= 70300) {
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
[
|
||||
'expires' => 0,
|
||||
'path' => $cookiePath,
|
||||
'domain' => $cookieDomain,
|
||||
'secure' => $cookieSecure,
|
||||
'httponly' => $cookieHttpOnly,
|
||||
'samesite' => 'Lax'
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// PHP 7.2 이하 호환
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
0,
|
||||
$cookiePath,
|
||||
$cookieDomain,
|
||||
$cookieSecure,
|
||||
$cookieHttpOnly
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 세션 파괴
|
||||
@session_destroy();
|
||||
|
||||
error_log('[logout.php] Session destroyed successfully');
|
||||
|
||||
// 성공 응답
|
||||
http_response_code(200);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '로그아웃 되었습니다.',
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[logout.php] Exception: ' . $e->getMessage());
|
||||
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '로그아웃 중 오류가 발생했습니다.',
|
||||
'error' => $e->getMessage()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Deny from all
|
||||
@@ -0,0 +1,799 @@
|
||||
[2026-03-10 18:55:33] [save_learning] request {"content_id":"F3","watch_tm":140,"content_tm":141,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 18:55:33] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:55:52] [save_learning] request {"content_id":"F3","watch_tm":5,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:55:52] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:55:57] [save_learning] request {"content_id":"F3","watch_tm":10,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:55:57] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:02] [save_learning] request {"content_id":"F3","watch_tm":15,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:02] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:07] [save_learning] request {"content_id":"F3","watch_tm":20,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:07] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:12] [save_learning] request {"content_id":"F3","watch_tm":25,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:12] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:17] [save_learning] request {"content_id":"F3","watch_tm":30,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:17] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:22] [save_learning] request {"content_id":"F3","watch_tm":35,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:22] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:27] [save_learning] request {"content_id":"F3","watch_tm":40,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:27] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:32] [save_learning] request {"content_id":"F3","watch_tm":45,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:32] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:37] [save_learning] request {"content_id":"F3","watch_tm":50,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:37] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:42] [save_learning] request {"content_id":"F3","watch_tm":55,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:42] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:47] [save_learning] request {"content_id":"F3","watch_tm":60,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:47] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:52] [save_learning] request {"content_id":"F3","watch_tm":65,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:52] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:56:57] [save_learning] request {"content_id":"F3","watch_tm":70,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:56:57] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:02] [save_learning] request {"content_id":"F3","watch_tm":75,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:02] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:07] [save_learning] request {"content_id":"F3","watch_tm":80,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:07] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:12] [save_learning] request {"content_id":"F3","watch_tm":85,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:12] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:17] [save_learning] request {"content_id":"F3","watch_tm":90,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:17] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:22] [save_learning] request {"content_id":"F3","watch_tm":95,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:22] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:27] [save_learning] request {"content_id":"F3","watch_tm":100,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:27] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:32] [save_learning] request {"content_id":"F3","watch_tm":105,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:32] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:37] [save_learning] request {"content_id":"F3","watch_tm":110,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:37] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:42] [save_learning] request {"content_id":"F3","watch_tm":115,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:42] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:47] [save_learning] request {"content_id":"F3","watch_tm":120,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:47] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:52] [save_learning] request {"content_id":"F3","watch_tm":125,"content_tm":140,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:57:52] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:57:53] [save_learning] request {"content_id":"F3","watch_tm":125,"content_tm":140,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 18:57:53] [save_learning] unregistered content_id {"content_id":"F3","member_id":"U001"}
|
||||
[2026-03-10 18:58:06] [save_learning] request {"content_id":"F5","watch_tm":4,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:06] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:11] [save_learning] request {"content_id":"F5","watch_tm":9,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:11] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:16] [save_learning] request {"content_id":"F5","watch_tm":14,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:16] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:21] [save_learning] request {"content_id":"F5","watch_tm":19,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:21] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:26] [save_learning] request {"content_id":"F5","watch_tm":24,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:26] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:31] [save_learning] request {"content_id":"F5","watch_tm":29,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:31] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:36] [save_learning] request {"content_id":"F5","watch_tm":34,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:36] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:41] [save_learning] request {"content_id":"F5","watch_tm":39,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:41] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:46] [save_learning] request {"content_id":"F5","watch_tm":44,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:46] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:51] [save_learning] request {"content_id":"F5","watch_tm":49,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:51] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:58:56] [save_learning] request {"content_id":"F5","watch_tm":54,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:58:56] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:01] [save_learning] request {"content_id":"F5","watch_tm":59,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:01] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:06] [save_learning] request {"content_id":"F5","watch_tm":64,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:06] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:11] [save_learning] request {"content_id":"F5","watch_tm":69,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:11] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:16] [save_learning] request {"content_id":"F5","watch_tm":74,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:16] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:21] [save_learning] request {"content_id":"F5","watch_tm":79,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:21] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:26] [save_learning] request {"content_id":"F5","watch_tm":84,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:26] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:31] [save_learning] request {"content_id":"F5","watch_tm":89,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:31] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:36] [save_learning] request {"content_id":"F5","watch_tm":94,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:36] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:41] [save_learning] request {"content_id":"F5","watch_tm":99,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:41] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:46] [save_learning] request {"content_id":"F5","watch_tm":104,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:46] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:51] [save_learning] request {"content_id":"F5","watch_tm":109,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:51] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 18:59:56] [save_learning] request {"content_id":"F5","watch_tm":114,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 18:59:56] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:01] [save_learning] request {"content_id":"F5","watch_tm":119,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:01] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:06] [save_learning] request {"content_id":"F5","watch_tm":124,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:06] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:11] [save_learning] request {"content_id":"F5","watch_tm":129,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:11] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:16] [save_learning] request {"content_id":"F5","watch_tm":134,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:16] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:21] [save_learning] request {"content_id":"F5","watch_tm":139,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:21] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:26] [save_learning] request {"content_id":"F5","watch_tm":144,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:26] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:31] [save_learning] request {"content_id":"F5","watch_tm":150,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:31] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:36] [save_learning] request {"content_id":"F5","watch_tm":155,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:36] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:00:41] [save_learning] request {"content_id":"F5","watch_tm":159,"content_tm":1364,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:00:41] [save_learning] unregistered content_id {"content_id":"F5","member_id":"U001"}
|
||||
[2026-03-10 19:12:10] [save_learning] request {"content_id":"F4","watch_tm":5,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:10] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:15] [save_learning] request {"content_id":"F4","watch_tm":10,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:15] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:20] [save_learning] request {"content_id":"F4","watch_tm":15,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:20] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:25] [save_learning] request {"content_id":"F4","watch_tm":20,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:25] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:30] [save_learning] request {"content_id":"F4","watch_tm":25,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:30] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:35] [save_learning] request {"content_id":"F4","watch_tm":30,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:35] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:40] [save_learning] request {"content_id":"F4","watch_tm":35,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:40] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:45] [save_learning] request {"content_id":"F4","watch_tm":40,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:45] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:50] [save_learning] request {"content_id":"F4","watch_tm":45,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:50] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:12:55] [save_learning] request {"content_id":"F4","watch_tm":50,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:12:55] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:00] [save_learning] request {"content_id":"F4","watch_tm":55,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:00] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:05] [save_learning] request {"content_id":"F4","watch_tm":60,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:05] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:10] [save_learning] request {"content_id":"F4","watch_tm":65,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:10] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:15] [save_learning] request {"content_id":"F4","watch_tm":70,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:15] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:20] [save_learning] request {"content_id":"F4","watch_tm":75,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:20] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:25] [save_learning] request {"content_id":"F4","watch_tm":80,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:25] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:30] [save_learning] request {"content_id":"F4","watch_tm":85,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:30] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:35] [save_learning] request {"content_id":"F4","watch_tm":90,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:35] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:40] [save_learning] request {"content_id":"F4","watch_tm":95,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:40] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:45] [save_learning] request {"content_id":"F4","watch_tm":100,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:45] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:50] [save_learning] request {"content_id":"F4","watch_tm":105,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:50] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:13:55] [save_learning] request {"content_id":"F4","watch_tm":110,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:13:55] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:00] [save_learning] request {"content_id":"F4","watch_tm":115,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:00] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:05] [save_learning] request {"content_id":"F4","watch_tm":120,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:05] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:10] [save_learning] request {"content_id":"F4","watch_tm":125,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:10] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:15] [save_learning] request {"content_id":"F4","watch_tm":130,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:15] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:20] [save_learning] request {"content_id":"F4","watch_tm":135,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:20] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:25] [save_learning] request {"content_id":"F4","watch_tm":140,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:25] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:30] [save_learning] request {"content_id":"F4","watch_tm":145,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:30] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:35] [save_learning] request {"content_id":"F4","watch_tm":150,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:35] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:40] [save_learning] request {"content_id":"F4","watch_tm":155,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:40] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:45] [save_learning] request {"content_id":"F4","watch_tm":160,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:45] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:50] [save_learning] request {"content_id":"F4","watch_tm":165,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:50] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:14:55] [save_learning] request {"content_id":"F4","watch_tm":170,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:14:55] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:00] [save_learning] request {"content_id":"F4","watch_tm":175,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:00] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:05] [save_learning] request {"content_id":"F4","watch_tm":180,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:05] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:10] [save_learning] request {"content_id":"F4","watch_tm":185,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:10] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:15] [save_learning] request {"content_id":"F4","watch_tm":190,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:15] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:20] [save_learning] request {"content_id":"F4","watch_tm":195,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:20] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:25] [save_learning] request {"content_id":"F4","watch_tm":200,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:25] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:30] [save_learning] request {"content_id":"F4","watch_tm":205,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:30] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:35] [save_learning] request {"content_id":"F4","watch_tm":210,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:35] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:40] [save_learning] request {"content_id":"F4","watch_tm":215,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:40] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:45] [save_learning] request {"content_id":"F4","watch_tm":220,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:45] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:50] [save_learning] request {"content_id":"F4","watch_tm":225,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:50] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:55] [save_learning] request {"content_id":"F4","watch_tm":230,"content_tm":234,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:15:55] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:15:59] [save_learning] request {"content_id":"F4","watch_tm":234,"content_tm":235,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-10 19:15:59] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:17:17] [save_learning] request {"content_id":"F4","watch_tm":234,"content_tm":235,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 19:17:17] [save_learning] unregistered content_id {"content_id":"F4","member_id":"U001"}
|
||||
[2026-03-10 19:32:02] [save_learning] request {"content_id":"CNT001","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 19:32:02] [save_learning] UPDATE done {"content_id":"CNT001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 19:34:33] [save_learning] request {"content_id":"F6","watch_tm":5,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:34:33] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:34:38] [save_learning] request {"content_id":"F6","watch_tm":10,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:34:38] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:34:43] [save_learning] request {"content_id":"F6","watch_tm":15,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:34:43] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:34:48] [save_learning] request {"content_id":"F6","watch_tm":20,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:34:48] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:34:53] [save_learning] request {"content_id":"F6","watch_tm":25,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:34:53] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:34:58] [save_learning] request {"content_id":"F6","watch_tm":30,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:34:58] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:03] [save_learning] request {"content_id":"F6","watch_tm":35,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:03] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:08] [save_learning] request {"content_id":"F6","watch_tm":40,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:08] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:13] [save_learning] request {"content_id":"F6","watch_tm":45,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:13] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:18] [save_learning] request {"content_id":"F6","watch_tm":50,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:18] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:23] [save_learning] request {"content_id":"F6","watch_tm":55,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:23] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:28] [save_learning] request {"content_id":"F6","watch_tm":60,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:28] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:33] [save_learning] request {"content_id":"F6","watch_tm":65,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:33] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:38] [save_learning] request {"content_id":"F6","watch_tm":70,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:38] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:43] [save_learning] request {"content_id":"F6","watch_tm":75,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:43] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:48] [save_learning] request {"content_id":"F6","watch_tm":80,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:48] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:53] [save_learning] request {"content_id":"F6","watch_tm":85,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:53] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:35:58] [save_learning] request {"content_id":"F6","watch_tm":90,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:35:58] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:03] [save_learning] request {"content_id":"F6","watch_tm":95,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:03] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:08] [save_learning] request {"content_id":"F6","watch_tm":100,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:08] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:13] [save_learning] request {"content_id":"F6","watch_tm":105,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:13] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:18] [save_learning] request {"content_id":"F6","watch_tm":110,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:18] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:23] [save_learning] request {"content_id":"F6","watch_tm":115,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:23] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:28] [save_learning] request {"content_id":"F6","watch_tm":120,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:28] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:33] [save_learning] request {"content_id":"F6","watch_tm":125,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:33] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:38] [save_learning] request {"content_id":"F6","watch_tm":130,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:38] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:43] [save_learning] request {"content_id":"F6","watch_tm":135,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:43] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:48] [save_learning] request {"content_id":"F6","watch_tm":140,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:48] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:53] [save_learning] request {"content_id":"F6","watch_tm":145,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:53] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:36:58] [save_learning] request {"content_id":"F6","watch_tm":150,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:36:58] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:37:03] [save_learning] request {"content_id":"F6","watch_tm":155,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:37:03] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:37:08] [save_learning] request {"content_id":"F6","watch_tm":160,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:37:08] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:37:13] [save_learning] request {"content_id":"F6","watch_tm":165,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:37:13] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:37:18] [save_learning] request {"content_id":"F6","watch_tm":170,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:37:18] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:37:23] [save_learning] request {"content_id":"F6","watch_tm":175,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:37:23] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:37:28] [save_learning] request {"content_id":"F6","watch_tm":180,"content_tm":3115,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 19:37:28] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:37:32] [save_learning] request {"content_id":"F6","watch_tm":184,"content_tm":3115,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-10 19:37:32] [save_learning] unregistered content_id {"content_id":"F6","member_id":"U001"}
|
||||
[2026-03-10 19:45:39] [save_learning] request {"content_id":"CNT009","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 19:45:39] [save_learning] INSERT done {"content_id":"CNT009","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 19:53:51] [save_learning] request {"content_id":"20260305-005","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 19:53:51] [save_learning] INSERT done {"content_id":"20260305-005","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 19:53:56] [save_learning] request {"content_id":"20260310-001","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 19:53:56] [save_learning] INSERT done {"content_id":"20260310-001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 20:00:04] [save_learning] request {"content_id":"20260310-001","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 20:00:05] [save_learning] UPDATE done {"content_id":"20260310-001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 20:09:56] [main_data] DB_ERROR {"error":"SQLSTATE[42S02]: Base table or view not found: 1146 Table 'baronhomep.edu_edu_recommend_keywords' doesn't exist in \/baronhomep\/www\/edu\/bbs\/main_data.php:131"}
|
||||
[2026-03-10 20:10:04] [main_data] DB_ERROR {"error":"SQLSTATE[42S02]: Base table or view not found: 1146 Table 'baronhomep.edu_edu_recommend_keywords' doesn't exist in \/baronhomep\/www\/edu\/bbs\/main_data.php:131"}
|
||||
[2026-03-10 20:11:15] [main_data] DB_ERROR {"error":"SQLSTATE[42S02]: Base table or view not found: 1146 Table 'baronhomep.edu_edu_recommend_keywords' doesn't exist in \/baronhomep\/www\/edu\/bbs\/main_data.php:131"}
|
||||
[2026-03-10 20:11:22] [main_data] DB_ERROR {"error":"SQLSTATE[42S02]: Base table or view not found: 1146 Table 'baronhomep.edu_edu_recommend_keywords' doesn't exist in \/baronhomep\/www\/edu\/bbs\/main_data.php:131"}
|
||||
[2026-03-10 20:13:35] [save_learning] request {"content_id":"20260305-004","watch_tm":5,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:13:35] [save_learning] INSERT done {"content_id":"20260305-004","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-10 20:13:40] [save_learning] request {"content_id":"20260305-004","watch_tm":10,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:13:40] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-10 20:13:45] [save_learning] request {"content_id":"20260305-004","watch_tm":15,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:13:45] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":15,"heartbeat":5}
|
||||
[2026-03-10 20:13:50] [save_learning] request {"content_id":"20260305-004","watch_tm":20,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:13:50] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":20,"heartbeat":5}
|
||||
[2026-03-10 20:13:55] [save_learning] request {"content_id":"20260305-004","watch_tm":25,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:13:55] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":25,"heartbeat":5}
|
||||
[2026-03-10 20:14:00] [save_learning] request {"content_id":"20260305-004","watch_tm":30,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:14:00] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":30,"heartbeat":5}
|
||||
[2026-03-10 20:14:05] [save_learning] request {"content_id":"20260305-004","watch_tm":35,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:14:05] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":35,"heartbeat":5}
|
||||
[2026-03-10 20:14:10] [save_learning] request {"content_id":"20260305-004","watch_tm":40,"content_tm":11111,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:14:10] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":40,"heartbeat":5}
|
||||
[2026-03-10 20:14:11] [save_learning] request {"content_id":"20260305-004","watch_tm":41,"content_tm":11111,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-10 20:14:11] [save_learning] UPDATE done {"content_id":"20260305-004","watch_tm":41,"heartbeat":1}
|
||||
[2026-03-10 20:18:15] [save_learning] request {"content_id":"CNT001","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 20:18:15] [save_learning] UPDATE done {"content_id":"CNT001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 20:40:38] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-10 20:40:38] [save_learning] INSERT done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-10 20:40:40] [save_learning] request {"content_id":"20260305-001","watch_tm":6,"content_tm":155,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-10 20:40:40] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":6,"heartbeat":1}
|
||||
[2026-03-10 20:41:04] [save_learning] request {"content_id":"20260306-004","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 20:41:04] [save_learning] INSERT done {"content_id":"20260306-004","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 20:41:08] [save_learning] request {"content_id":"20260305-006","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-10 20:41:08] [save_learning] INSERT done {"content_id":"20260305-006","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-10 20:41:12] [save_learning] request {"content_id":"20260305-001","watch_tm":1,"content_tm":155,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-10 20:41:12] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":1,"heartbeat":1}
|
||||
[2026-03-11 13:13:49] [save_learning] request {"content_id":"20260305-001","watch_tm":4,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:13:49] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":4,"heartbeat":5}
|
||||
[2026-03-11 13:13:54] [save_learning] request {"content_id":"20260305-001","watch_tm":9,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:13:54] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":9,"heartbeat":5}
|
||||
[2026-03-11 13:13:59] [save_learning] request {"content_id":"20260305-001","watch_tm":14,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:13:59] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":14,"heartbeat":5}
|
||||
[2026-03-11 13:14:04] [save_learning] request {"content_id":"20260305-001","watch_tm":19,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:14:04] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":19,"heartbeat":5}
|
||||
[2026-03-11 13:14:09] [save_learning] request {"content_id":"20260305-001","watch_tm":24,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:14:09] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":24,"heartbeat":5}
|
||||
[2026-03-11 13:14:12] [save_learning] request {"content_id":"20260305-001","watch_tm":28,"content_tm":155,"heartbeat":3,"member_id":"U001"}
|
||||
[2026-03-11 13:14:12] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":28,"heartbeat":3}
|
||||
[2026-03-11 13:14:59] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:14:59] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-11 13:15:04] [save_learning] request {"content_id":"20260305-001","watch_tm":10,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:04] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-11 13:15:10] [save_learning] request {"content_id":"20260305-001","watch_tm":16,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:10] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":16,"heartbeat":5}
|
||||
[2026-03-11 13:15:15] [save_learning] request {"content_id":"20260305-001","watch_tm":21,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:15] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":21,"heartbeat":5}
|
||||
[2026-03-11 13:15:20] [save_learning] request {"content_id":"20260305-001","watch_tm":26,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:20] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":26,"heartbeat":5}
|
||||
[2026-03-11 13:15:25] [save_learning] request {"content_id":"20260305-001","watch_tm":31,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:25] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":31,"heartbeat":5}
|
||||
[2026-03-11 13:15:30] [save_learning] request {"content_id":"20260305-001","watch_tm":36,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:30] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":36,"heartbeat":5}
|
||||
[2026-03-11 13:15:35] [save_learning] request {"content_id":"20260305-001","watch_tm":41,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:35] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":41,"heartbeat":5}
|
||||
[2026-03-11 13:15:40] [save_learning] request {"content_id":"20260305-001","watch_tm":46,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:40] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":46,"heartbeat":5}
|
||||
[2026-03-11 13:15:45] [save_learning] request {"content_id":"20260305-001","watch_tm":51,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:45] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":51,"heartbeat":5}
|
||||
[2026-03-11 13:15:50] [save_learning] request {"content_id":"20260305-001","watch_tm":56,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:50] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":56,"heartbeat":5}
|
||||
[2026-03-11 13:15:55] [save_learning] request {"content_id":"20260305-001","watch_tm":61,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:15:55] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":61,"heartbeat":5}
|
||||
[2026-03-11 13:16:00] [save_learning] request {"content_id":"20260305-001","watch_tm":66,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:00] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":66,"heartbeat":5}
|
||||
[2026-03-11 13:16:05] [save_learning] request {"content_id":"20260305-001","watch_tm":71,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:05] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":71,"heartbeat":5}
|
||||
[2026-03-11 13:16:10] [save_learning] request {"content_id":"20260305-001","watch_tm":76,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:10] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":76,"heartbeat":5}
|
||||
[2026-03-11 13:16:15] [save_learning] request {"content_id":"20260305-001","watch_tm":81,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:15] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":81,"heartbeat":5}
|
||||
[2026-03-11 13:16:20] [save_learning] request {"content_id":"20260305-001","watch_tm":86,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:20] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":86,"heartbeat":5}
|
||||
[2026-03-11 13:16:25] [save_learning] request {"content_id":"20260305-001","watch_tm":91,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:25] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":91,"heartbeat":5}
|
||||
[2026-03-11 13:16:30] [save_learning] request {"content_id":"20260305-001","watch_tm":96,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:30] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":96,"heartbeat":5}
|
||||
[2026-03-11 13:16:35] [save_learning] request {"content_id":"20260305-001","watch_tm":101,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:35] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":101,"heartbeat":5}
|
||||
[2026-03-11 13:16:40] [save_learning] request {"content_id":"20260305-001","watch_tm":106,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:40] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":106,"heartbeat":5}
|
||||
[2026-03-11 13:16:45] [save_learning] request {"content_id":"20260305-001","watch_tm":111,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:45] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":111,"heartbeat":5}
|
||||
[2026-03-11 13:16:50] [save_learning] request {"content_id":"20260305-001","watch_tm":116,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:50] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":116,"heartbeat":5}
|
||||
[2026-03-11 13:16:55] [save_learning] request {"content_id":"20260305-001","watch_tm":121,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:16:55] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":121,"heartbeat":5}
|
||||
[2026-03-11 13:17:00] [save_learning] request {"content_id":"20260305-001","watch_tm":126,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:17:00] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":126,"heartbeat":5}
|
||||
[2026-03-11 13:17:05] [save_learning] request {"content_id":"20260305-001","watch_tm":131,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:17:05] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":131,"heartbeat":5}
|
||||
[2026-03-11 13:17:10] [save_learning] request {"content_id":"20260305-001","watch_tm":136,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:17:10] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":136,"heartbeat":5}
|
||||
[2026-03-11 13:17:15] [save_learning] request {"content_id":"20260305-001","watch_tm":141,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:17:15] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":141,"heartbeat":5}
|
||||
[2026-03-11 13:17:20] [save_learning] request {"content_id":"20260305-001","watch_tm":146,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:17:20] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":146,"heartbeat":5}
|
||||
[2026-03-11 13:17:25] [save_learning] request {"content_id":"20260305-001","watch_tm":151,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-11 13:17:25] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":151,"heartbeat":5}
|
||||
[2026-03-11 13:17:29] [save_learning] request {"content_id":"20260305-001","watch_tm":155,"content_tm":156,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-11 13:17:29] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":155,"heartbeat":4}
|
||||
[2026-03-11 14:06:02] [save_learning] request {"content_id":"20260305-001","watch_tm":155,"content_tm":156,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-11 14:06:02] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":155,"heartbeat":0}
|
||||
[2026-03-11 14:32:53] [save_learning] request {"content_id":"20260306-002","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-11 14:32:53] [save_learning] INSERT done {"content_id":"20260306-002","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-12 15:32:31] [save_learning] request {"content_id":"20260305-001","watch_tm":4,"content_tm":155,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-12 15:32:31] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":4,"heartbeat":4}
|
||||
[2026-03-12 15:32:35] [save_learning] request {"content_id":"20260305-001","watch_tm":0,"content_tm":155,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-12 15:32:35] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-12 15:32:38] [save_learning] request {"content_id":"CNT003","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-12 15:32:38] [save_learning] INSERT done {"content_id":"CNT003","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-12 15:32:40] [save_learning] request {"content_id":"C018","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-12 15:32:40] [save_learning] INSERT done {"content_id":"C018","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-12 15:37:47] [save_learning] request {"content_id":"20260305-001","watch_tm":4,"content_tm":155,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-12 15:37:47] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":4,"heartbeat":4}
|
||||
[2026-03-12 15:37:48] [save_learning] request {"content_id":"20260305-001","watch_tm":4,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-12 15:37:48] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":4,"heartbeat":5}
|
||||
[2026-03-12 15:37:49] [save_learning] request {"content_id":"20260305-001","watch_tm":6,"content_tm":155,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-12 15:37:49] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":6,"heartbeat":1}
|
||||
[2026-03-12 16:11:42] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-12 16:11:42] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-12 16:11:43] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-12 16:11:43] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-12 16:11:47] [save_learning] request {"content_id":"20260305-001","watch_tm":10,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-12 16:11:47] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-12 16:11:48] [save_learning] request {"content_id":"20260305-001","watch_tm":10,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-12 16:11:48] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-12 16:11:52] [save_learning] request {"content_id":"20260305-001","watch_tm":15,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-12 16:11:52] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":15,"heartbeat":5}
|
||||
[2026-03-12 16:11:53] [save_learning] request {"content_id":"20260305-001","watch_tm":15,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-12 16:11:53] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":15,"heartbeat":5}
|
||||
[2026-03-12 16:11:56] [save_learning] request {"content_id":"20260305-001","watch_tm":18,"content_tm":155,"heartbeat":3,"member_id":"U001"}
|
||||
[2026-03-12 16:11:56] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":18,"heartbeat":3}
|
||||
[2026-03-12 16:11:57] [save_learning] request {"content_id":"20260305-001","watch_tm":19,"content_tm":155,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-12 16:11:57] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":19,"heartbeat":4}
|
||||
[2026-03-16 13:40:45] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:40:45] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-16 13:40:45] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":156,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:40:45] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-16 13:40:45] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:40:45] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":0}
|
||||
[2026-03-16 13:40:46] [save_learning] request {"content_id":"20260305-001","watch_tm":6,"content_tm":156,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-16 13:40:46] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":6,"heartbeat":1}
|
||||
[2026-03-16 13:40:52] [save_learning] request {"content_id":"20260305-001","watch_tm":1,"content_tm":155,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-16 13:40:52] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":1,"heartbeat":1}
|
||||
[2026-03-16 13:40:55] [save_learning] request {"content_id":"20260305-001","watch_tm":4,"content_tm":155,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-16 13:40:55] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":4,"heartbeat":4}
|
||||
[2026-03-16 13:41:03] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:41:03] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-16 13:41:03] [save_learning] request {"content_id":"20260305-001","watch_tm":4,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:41:03] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":4,"heartbeat":5}
|
||||
[2026-03-16 13:41:07] [save_learning] request {"content_id":"20260305-001","watch_tm":9,"content_tm":155,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-16 13:41:07] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":9,"heartbeat":4}
|
||||
[2026-03-16 13:41:08] [save_learning] request {"content_id":"20260305-001","watch_tm":10,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:41:08] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-16 13:41:11] [save_learning] request {"content_id":"20260305-001","watch_tm":12,"content_tm":155,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-16 13:41:11] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":12,"heartbeat":2}
|
||||
[2026-03-16 13:41:14] [save_learning] request {"content_id":"20260305-001","watch_tm":0,"content_tm":155,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:41:14] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 13:41:16] [save_learning] request {"content_id":"20260305-001","watch_tm":2,"content_tm":155,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-16 13:41:16] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":2,"heartbeat":2}
|
||||
[2026-03-16 13:41:24] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:41:24] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-16 13:41:24] [save_learning] request {"content_id":"20260305-001","watch_tm":4,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:41:24] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":4,"heartbeat":5}
|
||||
[2026-03-16 13:41:24] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:41:24] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":0}
|
||||
[2026-03-16 13:41:29] [save_learning] request {"content_id":"20260305-001","watch_tm":10,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 13:41:29] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-16 13:41:29] [save_learning] request {"content_id":"20260305-001","watch_tm":10,"content_tm":155,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:41:29] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":10,"heartbeat":0}
|
||||
[2026-03-16 13:41:35] [save_learning] request {"content_id":"CNT003","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:41:35] [save_learning] UPDATE done {"content_id":"CNT003","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 13:41:40] [save_learning] request {"content_id":"CNT013","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:41:40] [save_learning] INSERT done {"content_id":"CNT013","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 13:41:41] [save_learning] request {"content_id":"CNT013","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:41:41] [save_learning] UPDATE done {"content_id":"CNT013","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 13:55:06] [save_learning] request {"content_id":1,"watch_tm":0,"content_tm":1195,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:55:06] [save_learning] unregistered content_id {"content_id":1,"member_id":"U001"}
|
||||
[2026-03-16 13:55:10] [save_learning] request {"content_id":3,"watch_tm":0,"content_tm":140,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:55:10] [save_learning] unregistered content_id {"content_id":3,"member_id":"U001"}
|
||||
[2026-03-16 13:55:16] [save_learning] request {"content_id":4,"watch_tm":0,"content_tm":234,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:55:16] [save_learning] unregistered content_id {"content_id":4,"member_id":"U001"}
|
||||
[2026-03-16 13:55:35] [save_learning] request {"content_id":5,"watch_tm":1,"content_tm":1364,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-16 13:55:35] [save_learning] unregistered content_id {"content_id":5,"member_id":"U001"}
|
||||
[2026-03-16 13:55:37] [save_learning] request {"content_id":3,"watch_tm":0,"content_tm":140,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 13:55:37] [save_learning] unregistered content_id {"content_id":3,"member_id":"U001"}
|
||||
[2026-03-16 14:21:11] [save_learning] request {"content_id":"20260305-001","watch_tm":7,"content_tm":156,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 14:21:11] [save_learning] request {"content_id":"20260305-001","watch_tm":7,"content_tm":156,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 14:21:11] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":7,"heartbeat":5}
|
||||
[2026-03-16 14:21:11] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":7,"heartbeat":5}
|
||||
[2026-03-16 14:21:16] [save_learning] request {"content_id":"20260305-001","watch_tm":8,"content_tm":156,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 14:21:16] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":8,"heartbeat":5}
|
||||
[2026-03-16 14:21:16] [save_learning] request {"content_id":"20260305-001","watch_tm":9,"content_tm":156,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 14:21:16] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":9,"heartbeat":5}
|
||||
[2026-03-16 14:21:21] [save_learning] request {"content_id":"20260305-001","watch_tm":13,"content_tm":156,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 14:21:21] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":13,"heartbeat":5}
|
||||
[2026-03-16 14:21:21] [save_learning] request {"content_id":"20260305-001","watch_tm":14,"content_tm":156,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 14:21:21] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":14,"heartbeat":5}
|
||||
[2026-03-16 14:21:22] [save_learning] request {"content_id":"20260305-001","watch_tm":15,"content_tm":156,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-16 14:21:22] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":15,"heartbeat":1}
|
||||
[2026-03-16 14:21:24] [save_learning] request {"content_id":"20260305-001","watch_tm":17,"content_tm":156,"heartbeat":3,"member_id":"U001"}
|
||||
[2026-03-16 14:21:24] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":17,"heartbeat":3}
|
||||
[2026-03-16 14:33:58] [save_learning] request {"content_id":"20260305-001","watch_tm":0,"content_tm":155,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 14:33:58] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 14:35:39] [save_learning] request {"content_id":"CNT013","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 14:35:39] [save_learning] UPDATE done {"content_id":"CNT013","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 14:35:41] [save_learning] request {"content_id":"CNT003","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 14:35:41] [save_learning] UPDATE done {"content_id":"CNT003","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 14:35:47] [save_learning] request {"content_id":"20260305-001","watch_tm":5,"content_tm":155,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-16 14:35:47] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-16 14:35:48] [save_learning] request {"content_id":"20260305-001","watch_tm":6,"content_tm":155,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-16 14:35:48] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":6,"heartbeat":1}
|
||||
[2026-03-16 14:44:10] [save_learning] request {"content_id":"CNT004","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 14:44:10] [save_learning] INSERT done {"content_id":"CNT004","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 16:28:41] [save_learning] request {"content_id":"20260305-001","watch_tm":0,"content_tm":155,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 16:28:41] [save_learning] UPDATE done {"content_id":"20260305-001","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 17:54:08] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 17:54:08] [save_learning] INSERT done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 17:54:10] [save_learning] request {"content_id":"20260316-094","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 17:54:10] [save_learning] INSERT done {"content_id":"20260316-094","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 17:54:12] [save_learning] request {"content_id":"20260316-119","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 17:54:12] [save_learning] INSERT done {"content_id":"20260316-119","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 17:54:16] [save_learning] request {"content_id":"20260316-120","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 17:54:16] [save_learning] INSERT done {"content_id":"20260316-120","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 17:54:25] [save_learning] request {"content_id":"20260316-107","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 17:54:25] [save_learning] INSERT done {"content_id":"20260316-107","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 17:55:07] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 17:55:07] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 17:58:43] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 17:58:43] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 19:49:44] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 19:49:44] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 19:49:45] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 19:49:45] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 21:22:43] [save_learning] request {"content_id":"20260316-096","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 21:22:43] [save_learning] INSERT done {"content_id":"20260316-096","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 21:22:46] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 21:22:46] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 21:22:47] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 21:22:47] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 21:22:49] [save_learning] request {"content_id":"20260316-103","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 21:22:49] [save_learning] INSERT done {"content_id":"20260316-103","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-16 21:22:50] [save_learning] request {"content_id":"20260316-103","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-16 21:22:50] [save_learning] UPDATE done {"content_id":"20260316-103","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:12] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:12] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:14] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:14] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:15] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:15] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:15] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:15] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:16] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:16] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:16] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:16] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:17] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:17] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:17] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:17] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:18] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:18] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:19] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:19] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:19] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:19] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:20] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:20] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:31] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:31] [save_learning] INSERT done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:32] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:32] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:32] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:32] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:33] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:33] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:34] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:34] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:34] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:34] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:34] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:34] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:34] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:34] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 09:32:35] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 09:32:35] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:49:02] [save_learning] request {"content_id":"20260316-086","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:49:02] [save_learning] INSERT done {"content_id":"20260316-086","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:49:08] [save_learning] request {"content_id":"20260316-099","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:49:08] [save_learning] INSERT done {"content_id":"20260316-099","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:49:12] [save_learning] request {"content_id":"20260316-096","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:49:12] [save_learning] UPDATE done {"content_id":"20260316-096","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:49:14] [save_learning] request {"content_id":"20260316-096","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:49:14] [save_learning] UPDATE done {"content_id":"20260316-096","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:49:19] [save_learning] request {"content_id":"20260316-087","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:49:19] [save_learning] INSERT done {"content_id":"20260316-087","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:49:43] [save_learning] request {"content_id":"20260316-103","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:49:43] [save_learning] UPDATE done {"content_id":"20260316-103","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:50:10] [save_learning] request {"content_id":"20260316-108","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:50:10] [save_learning] UPDATE done {"content_id":"20260316-108","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:53:05] [save_learning] request {"content_id":"20260316-112","watch_tm":4,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 12:53:05] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":4,"heartbeat":5}
|
||||
[2026-03-17 12:53:12] [save_learning] request {"content_id":"20260316-112","watch_tm":11,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 12:53:12] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":11,"heartbeat":5}
|
||||
[2026-03-17 12:53:17] [save_learning] request {"content_id":"20260316-112","watch_tm":16,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 12:53:17] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":16,"heartbeat":5}
|
||||
[2026-03-17 12:53:17] [save_learning] request {"content_id":"20260316-112","watch_tm":48,"content_tm":1091,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:53:17] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":48,"heartbeat":0}
|
||||
[2026-03-17 12:53:18] [save_learning] request {"content_id":"20260316-112","watch_tm":119,"content_tm":1091,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:53:18] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":119,"heartbeat":0}
|
||||
[2026-03-17 12:53:19] [save_learning] request {"content_id":"20260316-112","watch_tm":193,"content_tm":1091,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:53:19] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":193,"heartbeat":0}
|
||||
[2026-03-17 12:53:20] [save_learning] request {"content_id":"20260316-112","watch_tm":278,"content_tm":1091,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:53:20] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":278,"heartbeat":0}
|
||||
[2026-03-17 12:53:20] [save_learning] request {"content_id":"20260316-112","watch_tm":343,"content_tm":1091,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:53:20] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":343,"heartbeat":0}
|
||||
[2026-03-17 12:53:22] [save_learning] request {"content_id":"20260316-112","watch_tm":461,"content_tm":1091,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:53:22] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":461,"heartbeat":0}
|
||||
[2026-03-17 12:53:28] [save_learning] request {"content_id":"20260316-112","watch_tm":467,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 12:53:28] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":467,"heartbeat":5}
|
||||
[2026-03-17 12:53:31] [save_learning] request {"content_id":"20260316-112","watch_tm":470,"content_tm":1091,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-17 12:53:31] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":470,"heartbeat":2}
|
||||
[2026-03-17 12:53:32] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:53:32] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 12:56:55] [save_learning] request {"content_id":"20260316-112","watch_tm":1,"content_tm":1090,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-17 12:56:55] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":1,"heartbeat":1}
|
||||
[2026-03-17 12:56:58] [save_learning] request {"content_id":"20260316-112","watch_tm":0,"content_tm":0,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 12:56:58] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 13:47:22] [save_learning] request {"content_id":"20260316-112","watch_tm":15,"content_tm":300,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 13:47:22] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":15,"heartbeat":0}
|
||||
[2026-03-17 13:49:32] [save_learning] request {"content_id":"20260316-112","watch_tm":22,"content_tm":300,"heartbeat":9,"member_id":"U001"}
|
||||
[2026-03-17 13:49:32] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":22,"heartbeat":9}
|
||||
[2026-03-17 13:50:16] [save_learning] request {"content_id":"20260316-125","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:16] [save_learning] INSERT done {"content_id":"20260316-125","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 13:50:21] [save_learning] request {"content_id":"20260316-125","watch_tm":10,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:21] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-17 13:50:26] [save_learning] request {"content_id":"20260316-125","watch_tm":54,"content_tm":1091,"heartbeat":4,"member_id":"U001"}
|
||||
[2026-03-17 13:50:26] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":54,"heartbeat":4}
|
||||
[2026-03-17 13:50:27] [save_learning] request {"content_id":"20260316-125","watch_tm":221,"content_tm":1091,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 13:50:27] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":221,"heartbeat":0}
|
||||
[2026-03-17 13:50:34] [save_learning] request {"content_id":"20260316-125","watch_tm":227,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:34] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":227,"heartbeat":5}
|
||||
[2026-03-17 13:50:39] [save_learning] request {"content_id":"20260316-125","watch_tm":232,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:39] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":232,"heartbeat":5}
|
||||
[2026-03-17 13:50:44] [save_learning] request {"content_id":"20260316-125","watch_tm":237,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:44] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":237,"heartbeat":5}
|
||||
[2026-03-17 13:50:49] [save_learning] request {"content_id":"20260316-125","watch_tm":242,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:49] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":242,"heartbeat":5}
|
||||
[2026-03-17 13:50:54] [save_learning] request {"content_id":"20260316-125","watch_tm":247,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:54] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":247,"heartbeat":5}
|
||||
[2026-03-17 13:50:59] [save_learning] request {"content_id":"20260316-125","watch_tm":252,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:50:59] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":252,"heartbeat":5}
|
||||
[2026-03-17 13:51:04] [save_learning] request {"content_id":"20260316-125","watch_tm":257,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:04] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":257,"heartbeat":5}
|
||||
[2026-03-17 13:51:09] [save_learning] request {"content_id":"20260316-125","watch_tm":262,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:09] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":262,"heartbeat":5}
|
||||
[2026-03-17 13:51:14] [save_learning] request {"content_id":"20260316-125","watch_tm":267,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:14] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":267,"heartbeat":5}
|
||||
[2026-03-17 13:51:19] [save_learning] request {"content_id":"20260316-125","watch_tm":272,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:19] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":272,"heartbeat":5}
|
||||
[2026-03-17 13:51:24] [save_learning] request {"content_id":"20260316-125","watch_tm":277,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:24] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":277,"heartbeat":5}
|
||||
[2026-03-17 13:51:29] [save_learning] request {"content_id":"20260316-125","watch_tm":282,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:29] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":282,"heartbeat":5}
|
||||
[2026-03-17 13:51:34] [save_learning] request {"content_id":"20260316-125","watch_tm":287,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:34] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":287,"heartbeat":5}
|
||||
[2026-03-17 13:51:39] [save_learning] request {"content_id":"20260316-125","watch_tm":292,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:39] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":292,"heartbeat":5}
|
||||
[2026-03-17 13:51:44] [save_learning] request {"content_id":"20260316-125","watch_tm":297,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:44] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":297,"heartbeat":5}
|
||||
[2026-03-17 13:51:49] [save_learning] request {"content_id":"20260316-125","watch_tm":302,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:49] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":302,"heartbeat":5}
|
||||
[2026-03-17 13:51:54] [save_learning] request {"content_id":"20260316-125","watch_tm":307,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:54] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":307,"heartbeat":5}
|
||||
[2026-03-17 13:51:59] [save_learning] request {"content_id":"20260316-125","watch_tm":312,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:51:59] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":312,"heartbeat":5}
|
||||
[2026-03-17 13:52:01] [save_learning] request {"content_id":"20260316-125","watch_tm":314,"content_tm":1091,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-17 13:52:01] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":314,"heartbeat":2}
|
||||
[2026-03-17 13:52:14] [save_learning] request {"content_id":"20260316-125","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 13:52:14] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 13:52:16] [save_learning] request {"content_id":"20260316-125","watch_tm":7,"content_tm":1090,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-17 13:52:16] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":7,"heartbeat":2}
|
||||
[2026-03-17 14:22:26] [save_learning] request {"content_id":"20260316-086","watch_tm":3,"content_tm":1090,"heartbeat":3,"member_id":"U001"}
|
||||
[2026-03-17 14:22:26] [save_learning] UPDATE done {"content_id":"20260316-086","watch_tm":3,"heartbeat":3}
|
||||
[2026-03-17 14:25:58] [save_learning] request {"content_id":"20260316-112","watch_tm":2,"content_tm":1090,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-17 14:25:58] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":2,"heartbeat":2}
|
||||
[2026-03-17 14:26:00] [save_learning] request {"content_id":"20260316-112","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:00] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 14:26:05] [save_learning] request {"content_id":"20260316-112","watch_tm":10,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:05] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-17 14:26:10] [save_learning] request {"content_id":"20260316-112","watch_tm":15,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:10] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":15,"heartbeat":5}
|
||||
[2026-03-17 14:26:15] [save_learning] request {"content_id":"20260316-112","watch_tm":20,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:15] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":20,"heartbeat":5}
|
||||
[2026-03-17 14:26:20] [save_learning] request {"content_id":"20260316-112","watch_tm":25,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:20] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":25,"heartbeat":5}
|
||||
[2026-03-17 14:26:25] [save_learning] request {"content_id":"20260316-112","watch_tm":30,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:25] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":30,"heartbeat":5}
|
||||
[2026-03-17 14:26:30] [save_learning] request {"content_id":"20260316-112","watch_tm":35,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:30] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":35,"heartbeat":5}
|
||||
[2026-03-17 14:26:35] [save_learning] request {"content_id":"20260316-112","watch_tm":40,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:35] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":40,"heartbeat":5}
|
||||
[2026-03-17 14:26:40] [save_learning] request {"content_id":"20260316-112","watch_tm":45,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:40] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":45,"heartbeat":5}
|
||||
[2026-03-17 14:26:45] [save_learning] request {"content_id":"20260316-112","watch_tm":50,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:45] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":50,"heartbeat":5}
|
||||
[2026-03-17 14:26:50] [save_learning] request {"content_id":"20260316-112","watch_tm":55,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:50] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":55,"heartbeat":5}
|
||||
[2026-03-17 14:26:55] [save_learning] request {"content_id":"20260316-112","watch_tm":60,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:26:55] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":60,"heartbeat":5}
|
||||
[2026-03-17 14:26:57] [save_learning] request {"content_id":"20260316-112","watch_tm":62,"content_tm":1090,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-17 14:26:57] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":62,"heartbeat":2}
|
||||
[2026-03-17 14:28:58] [save_learning] request {"content_id":"20260316-092","watch_tm":0,"content_tm":1090,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 14:28:58] [save_learning] INSERT done {"content_id":"20260316-092","watch_tm":0,"heartbeat":0}
|
||||
[2026-03-17 14:39:40] [main_data] DB_ERROR {"error":"SQLSTATE[42000]: Syntax error or access violation: 1305 FUNCTION baronhomep.group_code does not exist in \/baronhomep\/www\/edu\/bbs\/leadership_init_data.php:67"}
|
||||
[2026-03-17 14:58:45] [save_learning] request {"content_id":"20260316-112","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:58:45] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 14:58:45] [save_learning] request {"content_id":"20260316-112","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:58:45] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 14:58:50] [save_learning] request {"content_id":"20260316-112","watch_tm":10,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:58:50] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-17 14:58:50] [save_learning] request {"content_id":"20260316-112","watch_tm":10,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:58:50] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-17 14:58:55] [save_learning] request {"content_id":"20260316-112","watch_tm":15,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:58:55] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":15,"heartbeat":5}
|
||||
[2026-03-17 14:58:55] [save_learning] request {"content_id":"20260316-112","watch_tm":15,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:58:55] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":15,"heartbeat":5}
|
||||
[2026-03-17 14:59:00] [save_learning] request {"content_id":"20260316-112","watch_tm":20,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:59:00] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":20,"heartbeat":5}
|
||||
[2026-03-17 14:59:00] [save_learning] request {"content_id":"20260316-112","watch_tm":20,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:59:00] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":20,"heartbeat":5}
|
||||
[2026-03-17 14:59:05] [save_learning] request {"content_id":"20260316-112","watch_tm":25,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:59:05] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":25,"heartbeat":5}
|
||||
[2026-03-17 14:59:05] [save_learning] request {"content_id":"20260316-112","watch_tm":25,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 14:59:05] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":25,"heartbeat":5}
|
||||
[2026-03-17 14:59:07] [save_learning] request {"content_id":"20260316-112","watch_tm":26,"content_tm":1090,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-17 14:59:07] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":26,"heartbeat":1}
|
||||
[2026-03-17 14:59:08] [save_learning] request {"content_id":"20260316-112","watch_tm":28,"content_tm":1090,"heartbeat":3,"member_id":"U001"}
|
||||
[2026-03-17 14:59:08] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":28,"heartbeat":3}
|
||||
[2026-03-17 15:03:28] [save_learning] request {"content_id":"20260316-112","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 15:03:28] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 15:03:28] [save_learning] request {"content_id":"20260316-112","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 15:03:28] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 15:03:31] [save_learning] request {"content_id":"20260316-112","watch_tm":7,"content_tm":1090,"heartbeat":2,"member_id":"U001"}
|
||||
[2026-03-17 15:03:31] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":7,"heartbeat":2}
|
||||
[2026-03-17 15:03:33] [save_learning] request {"content_id":"20260316-112","watch_tm":10,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 15:03:33] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":10,"heartbeat":5}
|
||||
[2026-03-17 15:03:33] [save_learning] request {"content_id":"20260316-112","watch_tm":10,"content_tm":1090,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 15:03:33] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":10,"heartbeat":0}
|
||||
[2026-03-17 15:07:00] [save_learning] request {"content_id":"20260316-125","watch_tm":8,"content_tm":1090,"heartbeat":1,"member_id":"U001"}
|
||||
[2026-03-17 15:07:00] [save_learning] UPDATE done {"content_id":"20260316-125","watch_tm":8,"heartbeat":1}
|
||||
[2026-03-17 15:15:16] [save_learning] request {"content_id":"20260316-112","watch_tm":5,"content_tm":1090,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 15:15:16] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":5,"heartbeat":5}
|
||||
[2026-03-17 15:15:16] [save_learning] request {"content_id":"20260316-112","watch_tm":5,"content_tm":1090,"heartbeat":0,"member_id":"U001"}
|
||||
[2026-03-17 15:15:16] [save_learning] UPDATE done {"content_id":"20260316-112","watch_tm":5,"heartbeat":0}
|
||||
[2026-03-17 15:54:05] [save_learning] request {"content_id":"20260316-105","watch_tm":4,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 15:54:05] [save_learning] INSERT done {"content_id":"20260316-105","watch_tm":4,"heartbeat":5}
|
||||
[2026-03-17 15:54:10] [save_learning] request {"content_id":"20260316-105","watch_tm":9,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 15:54:10] [save_learning] UPDATE done {"content_id":"20260316-105","watch_tm":9,"heartbeat":5}
|
||||
[2026-03-17 15:54:15] [save_learning] request {"content_id":"20260316-105","watch_tm":14,"content_tm":1091,"heartbeat":5,"member_id":"U001"}
|
||||
[2026-03-17 15:54:15] [save_learning] UPDATE done {"content_id":"20260316-105","watch_tm":14,"heartbeat":5}
|
||||
[2026-03-17 15:54:19] [save_learning] request {"content_id":"20260316-105","watch_tm":18,"content_tm":1091,"heartbeat":3,"member_id":"U001"}
|
||||
[2026-03-17 15:54:19] [save_learning] UPDATE done {"content_id":"20260316-105","watch_tm":18,"heartbeat":3}
|
||||
[2026-03-18 16:19:55] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-18 17:36:19] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-18 17:46:08] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-18 17:46:27] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-18 17:53:10] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-18 20:38:25] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-18 20:38:33] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 09:02:16] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 09:33:11] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 09:33:27] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 09:33:31] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 09:33:40] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 09:47:46] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 09:54:02] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 10:04:44] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 10:06:25] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 10:07:47] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 10:13:33] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 10:17:37] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 10:27:28] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-19 10:31:30] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-21 17:51:19] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-21 17:51:57] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-21 17:51:59] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-21 17:56:06] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-21 17:56:12] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-21 17:56:14] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:157"}
|
||||
[2026-03-21 18:01:11] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:149"}
|
||||
[2026-03-21 18:01:47] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:149"}
|
||||
[2026-03-21 18:02:16] [main_data] DB_ERROR {"error":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'rk.sys_comp_code' in 'where clause' in \/baronhomep\/www\/edu\/bbs\/main_data.php:149"}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
// auth.php and session are already initialized by skin/index.php before this file is included.
|
||||
|
||||
$userName = edu_user_field('name', (string) ($_SESSION['member_name'] ?? ''));
|
||||
$userRank = (string) ($_SESSION['rank_name'] ?? edu_user_field('rank_name', ''));
|
||||
$memberId = edu_current_member_id();
|
||||
$sysCompCode = (string) ($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
// 세션에 누락된 정보 DB에서 보완
|
||||
if (($userRank === '' || $sysCompCode === '') && $memberId !== '') {
|
||||
try {
|
||||
if ($sysCompCode !== '') {
|
||||
$__st = db_conn()->prepare(
|
||||
'SELECT name, rank_name, sys_comp_code FROM edu_users WHERE member_id = ? AND sys_comp_code = ? LIMIT 1'
|
||||
);
|
||||
$__st->execute([$memberId, $sysCompCode]);
|
||||
} else {
|
||||
$__st = db_conn()->prepare(
|
||||
'SELECT name, rank_name, sys_comp_code FROM edu_users WHERE member_id = ? ORDER BY sys_comp_code LIMIT 1'
|
||||
);
|
||||
$__st->execute([$memberId]);
|
||||
}
|
||||
$__row = $__st->fetch(PDO::FETCH_ASSOC);
|
||||
if ($__row) {
|
||||
if ($userName === '')
|
||||
$userName = (string) ($__row['name'] ?? '');
|
||||
if ($userRank === '')
|
||||
$userRank = (string) ($__row['rank_name'] ?? '');
|
||||
if ($sysCompCode === '')
|
||||
$sysCompCode = (string) ($__row['sys_comp_code'] ?? '');
|
||||
}
|
||||
} catch (Throwable $__e) {
|
||||
error_log('[main_data rank fallback] ' . $__e->getMessage());
|
||||
}
|
||||
}
|
||||
$userRank = trim($userRank);
|
||||
|
||||
// ── 키워드 & 영상 설정 ─────────────────────────────────────────
|
||||
$allKeywords = []; // 전체 키워드 목록 (모달 선택용, 21개)
|
||||
$adminKeywords = []; // 회사 추천 키워드 (kw-deny)
|
||||
$myKeywords = []; // 사용자 선택 키워드 0~3개 (kw-allow)
|
||||
$videosJson = '[]';
|
||||
$myKwJson = '[]'; // JS fallback용
|
||||
$totalMin = 0;
|
||||
$avgWatchMin = 0; // 전체 평균 학습시간 (분)
|
||||
$mainDataError = ''; // DEBUG용 오류 메시지
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// edu_user_keywords 테이블 자동 생성 (권한 없으면 무시)
|
||||
try {
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS edu_user_keywords (
|
||||
member_id VARCHAR(20) NOT NULL,
|
||||
sys_comp_code VARCHAR(20) NOT NULL DEFAULT '',
|
||||
keyword_code VARCHAR(20) NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (member_id, sys_comp_code, keyword_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
");
|
||||
} catch (Throwable $__ce) {
|
||||
error_log('[main_data] CREATE edu_user_keywords: ' . $__ce->getMessage());
|
||||
}
|
||||
|
||||
// 전체 키워드 (모달 선택용) — sort_order 없는 테이블도 안전하게 base_code만으로 정렬
|
||||
$stmtKw = $pdo->query("SELECT base_code AS keyword_code, code_name AS keyword_name FROM edu_codes WHERE group_code = 'KW100' ORDER BY base_code");
|
||||
$allKeywords = $stmtKw->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 회사 추천 키워드: 반드시 edu_recommend_keywords의 코드값을 edu_codes와 한글 매핑해서 사용
|
||||
try {
|
||||
$stmtAdm = $pdo->prepare("
|
||||
SELECT rk.keyword_code, ec.code_name AS keyword_name
|
||||
FROM edu_recommend_keywords rk
|
||||
JOIN edu_codes ec ON ec.base_code = rk.keyword_code
|
||||
WHERE rk.is_active = 1
|
||||
AND rk.keyword_code IS NOT NULL
|
||||
AND rk.keyword_code <> ''
|
||||
AND ec.group_code = 'KW100'
|
||||
ORDER BY rk.keyword_code
|
||||
LIMIT 2
|
||||
");
|
||||
$stmtAdm->execute();
|
||||
$adminKeywords = $stmtAdm->fetchAll(PDO::FETCH_ASSOC);
|
||||
error_log('[main_data] edu_recommend_keywords result: ' . count($adminKeywords) . ' rows');
|
||||
} catch (Throwable $__ae) {
|
||||
error_log('[main_data] edu_recommend_keywords 쿼리 실패: ' . $__ae->getMessage());
|
||||
$adminKeywords = [];
|
||||
}
|
||||
|
||||
// 사용자 선택 키워드 (0~3개)
|
||||
// ⚠️ sys_comp_code 없어도 저장되도록 조건 완화 (저장 후 바로 조회 위함)
|
||||
// sys_comp_code 조건 추가 : 권오재
|
||||
if ($memberId !== '') {
|
||||
$stmtMy = $pdo->prepare("
|
||||
SELECT uk.keyword_code, ec.code_name AS keyword_name
|
||||
FROM edu_user_keywords uk
|
||||
JOIN edu_codes ec ON ec.base_code = uk.keyword_code
|
||||
WHERE uk.member_id = ?
|
||||
AND uk.sys_comp_code = ?
|
||||
AND uk.sys_comp_code <> ''
|
||||
ORDER BY uk.keyword_code
|
||||
LIMIT 3
|
||||
");
|
||||
$stmtMy->execute([$memberId, $sysCompCode]);
|
||||
$myKeywords = $stmtMy->fetchAll(PDO::FETCH_ASSOC);
|
||||
error_log('[main_data] myKeywords query: memberId=' . $memberId . ', result_count=' . count($myKeywords));
|
||||
}
|
||||
|
||||
// JS fallback용: 사용자 키워드명 배열 (JSON)
|
||||
$myKwJson = json_encode(
|
||||
array_column($myKeywords, 'keyword_name'),
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG
|
||||
);
|
||||
|
||||
// 누적 시청 시간 (게이지): watch_tm 기준
|
||||
if ($memberId !== '' && $sysCompCode !== '') {
|
||||
$stmtTm = $pdo->prepare(
|
||||
'SELECT COALESCE(SUM(watch_tm), 0) FROM edu_learning_histories WHERE member_id = ? AND sys_comp_code = ?'
|
||||
);
|
||||
$stmtTm->execute([$memberId, $sysCompCode]);
|
||||
$totalMin = (int) floor((int) $stmtTm->fetchColumn() / 60);
|
||||
}
|
||||
|
||||
// 전체 평균 학습시간 계산: (모든 member_id의 총 watch_tm 합계) / (학습경험이 있는 사용자 수)
|
||||
try {
|
||||
$stmtAvg = $pdo->prepare("
|
||||
SELECT
|
||||
COALESCE(SUM(h.watch_tm), 0) as total_watch_tm,
|
||||
COUNT(DISTINCT h.member_id) as unique_members
|
||||
FROM edu_learning_histories h
|
||||
WHERE h.sys_comp_code = ?
|
||||
");
|
||||
$stmtAvg->execute([$sysCompCode]);
|
||||
$avgRow = $stmtAvg->fetch(PDO::FETCH_ASSOC);
|
||||
if ($avgRow) {
|
||||
$totalWatchTm = (int) ($avgRow['total_watch_tm'] ?? 0);
|
||||
$uniqueMembers = (int) ($avgRow['unique_members'] ?? 0);
|
||||
if ($uniqueMembers > 0) {
|
||||
$avgWatchMin = (int) floor($totalWatchTm / $uniqueMembers / 60);
|
||||
// 최소 50분으로 설정해서 게이지의 maxValue 보장
|
||||
$avgWatchMin = max(50, $avgWatchMin);
|
||||
error_log('[main_data] avgWatchMin calc: totalWatchTm=' . $totalWatchTm . ', uniqueMembers=' . $uniqueMembers . ', avgWatchMin=' . $avgWatchMin);
|
||||
}
|
||||
}
|
||||
} catch (Throwable $__ae) {
|
||||
error_log('[main_data] avgWatchMin 계산 실패: ' . $__ae->getMessage());
|
||||
}
|
||||
|
||||
// ── Pick 영상(추천) + 키워드 영상 5개 조합: 항상 6개 보장 ──
|
||||
$videos = [];
|
||||
// 1. Pick(추천) 영상: is_offer=1, 본인 제외, 랜덤 1개
|
||||
$stmtPick = $pdo->prepare("SELECT * FROM edu_contents WHERE is_offer=1 AND is_active=1 AND issue_type_code = 'IS10003' AND member_id != ? ORDER BY RAND() LIMIT 1");
|
||||
$stmtPick->execute([$memberId]);
|
||||
$pick = $stmtPick->fetch(PDO::FETCH_ASSOC);
|
||||
if ($pick) {
|
||||
$pick['is_pick'] = true;
|
||||
$videos[] = $pick;
|
||||
}
|
||||
// 2. 나머지 5개: 실제 사용자 키워드 기반 영상 쿼리로 채움
|
||||
$keywordVideos = [];
|
||||
$userKeywordCodes = array_column($myKeywords, 'keyword_code');
|
||||
if (!empty($userKeywordCodes)) {
|
||||
$ph = implode(',', array_fill(0, count($userKeywordCodes), '?'));
|
||||
$sql = "SELECT * FROM edu_contents c
|
||||
JOIN edu_content_keywords k ON c.content_id = k.content_id AND k.is_active = 1
|
||||
WHERE c.is_active=1 AND k.keyword_code IN ($ph)";
|
||||
$params = $userKeywordCodes;
|
||||
// pick과 중복 제거
|
||||
if ($pick && isset($pick['content_id'])) {
|
||||
$sql .= " AND c.content_id != ?";
|
||||
$params[] = $pick['content_id'];
|
||||
}
|
||||
$sql .= " GROUP BY c.content_id ORDER BY c.content_id DESC LIMIT 5";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$row['is_pick'] = false;
|
||||
$keywordVideos[] = $row;
|
||||
}
|
||||
}
|
||||
// 부족하면 전체 영상에서 추가로 채움
|
||||
if (count($keywordVideos) < 5) {
|
||||
$already = array_column($keywordVideos, 'content_id');
|
||||
if ($pick && isset($pick['content_id']))
|
||||
$already[] = $pick['content_id'];
|
||||
$ph = implode(',', array_fill(0, count($already), '?'));
|
||||
$sql = "SELECT * FROM edu_contents WHERE is_active=1";
|
||||
if (!empty($already))
|
||||
$sql .= " AND content_id NOT IN ($ph)";
|
||||
$sql .= " ORDER BY content_id DESC LIMIT " . (5 - count($keywordVideos));
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($already);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$row['is_pick'] = false;
|
||||
$keywordVideos[] = $row;
|
||||
}
|
||||
}
|
||||
// 최대 5개만
|
||||
$keywordVideos = array_slice($keywordVideos, 0, 5);
|
||||
foreach ($keywordVideos as $v) {
|
||||
$videos[] = $v;
|
||||
}
|
||||
// 3. 부족하면 빈 카드로 채움
|
||||
while (count($videos) < 6) {
|
||||
$videos[] = [
|
||||
'content_id' => 'empty_' . count($videos),
|
||||
'title' => '',
|
||||
'is_pick' => false,
|
||||
'img' => '',
|
||||
'category' => '',
|
||||
'author' => '',
|
||||
'keywords' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// 카테고리 코드→한글명 매핑 (edu_codes)
|
||||
$codeMap = [];
|
||||
$normCode = static function (string $code): string {
|
||||
return preg_replace('/[^A-Z0-9]/', '', strtoupper(trim($code)));
|
||||
};
|
||||
try {
|
||||
$stmtCm = $pdo->query("SELECT base_code, code_name FROM edu_codes");
|
||||
foreach ($stmtCm->fetchAll(PDO::FETCH_ASSOC) as $cr) {
|
||||
$baseCode = $normCode((string) ($cr['base_code'] ?? ''));
|
||||
if ($baseCode === '')
|
||||
continue;
|
||||
$codeMap[$baseCode] = $cr['code_name'];
|
||||
}
|
||||
} catch (Throwable $e) { /* ignore */
|
||||
}
|
||||
foreach ($videos as &$v) {
|
||||
if (!is_array($v) || !isset($v['content_id']) || strpos((string) ($v['content_id'] ?? ''), 'empty_') === 0)
|
||||
continue;
|
||||
$categoryCode = $normCode((string) ($v['category_code'] ?? ''));
|
||||
$groupCode = $normCode((string) ($v['category_group'] ?? ''));
|
||||
$v['category'] = $codeMap[$categoryCode] ?? ($v['category_code'] ?? '');
|
||||
$v['subcate'] = $codeMap[$groupCode] ?? ($v['category_group'] ?? '');
|
||||
}
|
||||
unset($v);
|
||||
|
||||
// 4. JSON 직렬화
|
||||
$videosJson = json_encode($videos, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG);
|
||||
} catch (Throwable $e) {
|
||||
$mainDataError = $e->getMessage();
|
||||
error_log('[main_data] ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs/main_data.php
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
* 메인 페이지(skin/index.php) 용 데이터 조회 백엔드
|
||||
*
|
||||
* 이 파일은 직접 호출하지 않고 skin/index.php 에서 require_once 로 사용한다.
|
||||
* 실행 후 아래 변수가 설정된다:
|
||||
*
|
||||
* $userName string 사용자 이름
|
||||
* $userRank string 직위
|
||||
* $myKeywords array 내 키워드 [ ['keyword_code'=>..., 'keyword_name'=>...], ... ] (max 3)
|
||||
* $adminKeywords array 관리자 추천 키워드 (max 2)
|
||||
* $allKeywords array 모달용 전체 키워드 목록
|
||||
* $firstPageVideos array 메인 6개 영상 배열 (JS video 객체 형태)
|
||||
* $totalMin int 총 학습시간(분)
|
||||
* $videosJson string JSON 직렬화 영상 배열
|
||||
* $myKwJson string JSON 직렬화 내 키워드명 배열
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
require_once __DIR__ . '/api_log.php';
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// 현재 사용자 식별
|
||||
// TODO: 실제 세션 로그인 구현 후 아래 두 줄을 세션으로 교체
|
||||
// $memberId = $_SESSION['member_id'] ?? null;
|
||||
// $sysCompCode = $_SESSION['sys_comp_code'] ?? null;
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// 카테고리/서브카테고리 코드 → 한글 변환 맵 (DB 조회 전 fallback 기본값)
|
||||
// DB에서 edu_codes 조회 후 아래 값은 덮어쓰여짐
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
$SUBCATE_MAP = [];
|
||||
|
||||
/**
|
||||
* contents 테이블 한 행을 JS video 객체 형태 배열로 변환
|
||||
*
|
||||
* @param array $row PDO fetchAll 행 데이터
|
||||
* @param string $type 카드 타입 (main / comment / onboarding 등)
|
||||
* @param string $picker Pick 배지에 표시할 이름 (없으면 '')
|
||||
* @return array
|
||||
*/
|
||||
function mapContentRow(array $row, string $type = 'main', string $picker = '', array $catMap = [], array $subcateMap = []): array
|
||||
{
|
||||
// GROUP_CONCAT 결과 키워드 문자열 → 배열
|
||||
$kwStr = $row['keywords'] ?? '';
|
||||
$keywords = $kwStr !== '' ? explode(',', $kwStr) : [];
|
||||
|
||||
// 학습 진행률 (watch_tm / content_tm × 100, 0~100 클램프)
|
||||
$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));
|
||||
|
||||
// content_url: 풀URL(https://www.youtube.com/watch?v=ID) 또는 영상ID만 저장된 경우 모두 지원
|
||||
$raw = trim($row['content_url'] ?? '');
|
||||
if (preg_match('/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/', $raw, $m)) {
|
||||
$videoId = $m[1]; // 풀 URL에서 ID 추출
|
||||
} elseif (preg_match('/^[A-Za-z0-9_-]{11}$/', $raw)) {
|
||||
$videoId = $raw; // 정확히 11자리 유효한 YouTube ID
|
||||
} else {
|
||||
$videoId = ''; // 유효하지 않은 값 (example02, 빈값 등)
|
||||
}
|
||||
// thumbnail_url 컬럼이 있으면 우선 사용, 없으면 YouTube API로 생성
|
||||
$thumbFromDb = trim($row['thumbnail_url'] ?? '');
|
||||
$url = $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : '';
|
||||
$thumbnail = $thumbFromDb !== '' ? $thumbFromDb
|
||||
: ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : '');
|
||||
|
||||
// 카테고리 코드 → 한글
|
||||
$categoryCode = $row['category_code'] ?? '';
|
||||
$category = $catMap[$categoryCode] ?? $categoryCode;
|
||||
|
||||
// 서브카테고리 코드 → 한글
|
||||
$groupCode = $row['category_group'] ?? '';
|
||||
$subcate = $subcateMap[$groupCode] ?? $groupCode;
|
||||
|
||||
return [
|
||||
'id' => $row['content_id'],
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'category' => $category,
|
||||
'category_code' => $categoryCode,
|
||||
'subcate' => $subcate,
|
||||
'bookmark' => (bool)($row['is_bookmarked'] ?? false),
|
||||
'title' => $row['title'] ?? '',
|
||||
'picker' => $picker,
|
||||
'type' => $type,
|
||||
'keywords' => $keywords,
|
||||
'gauge' => $gauge,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// DB 데이터 조회
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
$dbSuccess = false;
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ── 0. edu_codes에서 카테고리/서브카테고리 맵 동적 빌드 ──────
|
||||
$codesRows = $pdo->query("
|
||||
SELECT group_code, code, code_name
|
||||
FROM edu_codes
|
||||
WHERE is_active = 1
|
||||
AND group_code IN ('CA100', 'CA200')
|
||||
")->fetchAll();
|
||||
foreach ($codesRows as $cr) {
|
||||
$key = $cr['group_code'] . $cr['code']; // e.g. CA10001, CA200L01
|
||||
if ($cr['group_code'] === 'CA100') {
|
||||
$CATEGORY_MAP[$key] = $cr['code_name'];
|
||||
} else {
|
||||
$SUBCATE_MAP[$key] = $cr['code_name'];
|
||||
}
|
||||
}
|
||||
|
||||
// ── 1. 사용자 기본 정보 ─────────────────────────────────────
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT name, rank_name
|
||||
FROM edu_users
|
||||
WHERE member_id = :mid AND sys_comp_code = :comp
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([':mid' => $memberId, ':comp' => $sysCompCode]);
|
||||
$userRow = $stmtUser->fetch();
|
||||
$userName = $userRow['name'] ?? '사용자';
|
||||
$userRank = $userRow['rank_name'] ?? '';
|
||||
|
||||
// ── 2. 내 키워드 (최대 3개) ──────────────────────────────────
|
||||
$stmtMyKw = $pdo->prepare("
|
||||
SELECT keyword_code, keyword_name
|
||||
FROM edu_user_keywords
|
||||
WHERE member_id = :mid AND sys_comp_code = :comp
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 3
|
||||
");
|
||||
$stmtMyKw->execute([':mid' => $memberId, ':comp' => $sysCompCode]);
|
||||
$myKeywords = $stmtMyKw->fetchAll();
|
||||
|
||||
// ── 3. 관리자(법인) 추천 키워드 (최대 2개) ──────────────────
|
||||
$stmtAdminKw = $pdo->prepare("
|
||||
SELECT rk.keyword_code,
|
||||
COALESCE(c.code_name, rk.keyword_code) AS keyword_name
|
||||
FROM edu_recommend_keywords rk
|
||||
LEFT JOIN edu_codes c
|
||||
ON c.group_code = 'KW100'
|
||||
AND c.code = rk.keyword_code
|
||||
WHERE rk.sys_comp_code = :comp
|
||||
AND rk.is_active = 1
|
||||
ORDER BY rk.created_at DESC
|
||||
LIMIT 2
|
||||
");
|
||||
$stmtAdminKw->execute([':comp' => $sysCompCode]);
|
||||
$adminKeywords = $stmtAdminKw->fetchAll();
|
||||
|
||||
// ── 4. 모달용 전체 키워드 목록 ──────────────────────────────
|
||||
$stmtAllKw = $pdo->query("
|
||||
SELECT code AS keyword_code, code_name AS keyword_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'KW100' AND is_active = 1
|
||||
ORDER BY code_name
|
||||
");
|
||||
$allKeywords = $stmtAllKw ? $stmtAllKw->fetchAll() : [];
|
||||
|
||||
// DB에 코드 데이터 없으면 하드코딩 fallback
|
||||
if (empty($allKeywords)) {
|
||||
$fallbackKwNames = [
|
||||
'온보딩','성장','코칭','인물','소통','협업','AI','IT테크',
|
||||
'중간관리자','리더십','팔로우십','동기부여','인간관계','스킬업',
|
||||
'피드백','커리어','경영','경제','마인드셋','웰니스','자기개발',
|
||||
];
|
||||
$allKeywords = array_map(
|
||||
fn($k) => ['keyword_code' => $k, 'keyword_name' => $k],
|
||||
$fallbackKwNames
|
||||
);
|
||||
}
|
||||
|
||||
// ── 5. 영상 슬롯 조합 ────────────────────────────────────────
|
||||
// [0] Pick (좌상단 고정 - 다른 사람이 추천한 영상)
|
||||
// [1] 관리자 키워드 영상 (좌중단)
|
||||
// [2] 관리자 키워드 영상 (좌하단)
|
||||
// [3] 내 키워드 영상 (우상단)
|
||||
// [4] 내 키워드 영상 (우중단)
|
||||
// [5] 내 키워드 영상 (우하단)
|
||||
|
||||
$myKwCodes = array_column($myKeywords, 'keyword_code');
|
||||
$adminKwCodes = array_column($adminKeywords, 'keyword_code');
|
||||
|
||||
// 5-1. Pick 영상 (다른 사람이 제안, is_offer=1, 본인 제외)
|
||||
$stmtPick = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
NULL AS watch_tm,
|
||||
NULL AS content_tm,
|
||||
0 AS is_bookmarked,
|
||||
GROUP_CONCAT(ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords,
|
||||
u.name AS picker_name
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
LEFT JOIN edu_content_offer co ON co.offer_id = c.offer_id
|
||||
LEFT JOIN edu_users u ON u.member_id = co.member_id
|
||||
AND u.sys_comp_code = co.sys_comp_code
|
||||
WHERE c.is_offer = 1
|
||||
AND c.is_active = 1
|
||||
AND (co.member_id IS NULL OR co.member_id != :mid)
|
||||
GROUP BY c.content_id
|
||||
ORDER BY c.sort_order, RAND()
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtPick->execute([':mid' => $memberId]);
|
||||
$pickRow = $stmtPick->fetch();
|
||||
$pickVideo = $pickRow
|
||||
? mapContentRow($pickRow, 'main', $pickRow['picker_name'] ?? '동료', $CATEGORY_MAP, $SUBCATE_MAP)
|
||||
: null;
|
||||
|
||||
// 5-2. 관리자 키워드 관련 영상 (2개)
|
||||
$adminVideos = [];
|
||||
if (!empty($adminKwCodes)) {
|
||||
$ph = implode(',', array_fill(0, count($adminKwCodes), '?'));
|
||||
$stmtAdminV = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_contents c
|
||||
JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.keyword_code IN ({$ph})
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.content_id = c.content_id
|
||||
AND cw.member_id = ?
|
||||
AND cw.sys_comp_code = ?
|
||||
AND cw.is_active = 1
|
||||
WHERE c.is_active = 1
|
||||
GROUP BY c.content_id
|
||||
ORDER BY RAND()
|
||||
LIMIT 2
|
||||
");
|
||||
$stmtAdminV->execute(array_merge(
|
||||
$adminKwCodes,
|
||||
[$memberId, $sysCompCode, $memberId, $sysCompCode]
|
||||
));
|
||||
foreach ($stmtAdminV->fetchAll() as $r) {
|
||||
$adminVideos[] = mapContentRow($r, 'main', '', $CATEGORY_MAP, $SUBCATE_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
// 5-3. 내 키워드 관련 영상 (3개)
|
||||
$myVideos = [];
|
||||
if (!empty($myKwCodes)) {
|
||||
$ph2 = implode(',', array_fill(0, count($myKwCodes), '?'));
|
||||
$stmtMyV = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_contents c
|
||||
JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.keyword_code IN ({$ph2})
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.content_id = c.content_id
|
||||
AND cw.member_id = ?
|
||||
AND cw.sys_comp_code = ?
|
||||
AND cw.is_active = 1
|
||||
WHERE c.is_active = 1
|
||||
GROUP BY c.content_id
|
||||
ORDER BY RAND()
|
||||
LIMIT 3
|
||||
");
|
||||
$stmtMyV->execute(array_merge(
|
||||
$myKwCodes,
|
||||
[$memberId, $sysCompCode, $memberId, $sysCompCode]
|
||||
));
|
||||
foreach ($stmtMyV->fetchAll() as $r) {
|
||||
$myVideos[] = mapContentRow($r, 'main', '', $CATEGORY_MAP, $SUBCATE_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
// 5-4. 6 슬롯 최종 조합
|
||||
// ① 키워드 매칭으로 채워진 영상 목록
|
||||
$slots = [
|
||||
$pickVideo,
|
||||
$adminVideos[0] ?? null,
|
||||
$adminVideos[1] ?? null,
|
||||
$myVideos[0] ?? null,
|
||||
$myVideos[1] ?? null,
|
||||
$myVideos[2] ?? null,
|
||||
];
|
||||
$usedIds = array_values(array_filter(array_map(fn($s) => $s ? $s['id'] : null, $slots)));
|
||||
$slotsLeft = 6 - count(array_filter($slots, fn($s) => $s !== null));
|
||||
|
||||
// ② 부족한 슬롯 → edu_contents 에서 직접 조회 (키워드·is_offer 무관, 랜덤)
|
||||
// 키워드를 선택하지 않아도 항상 DB 영상이 표시되도록 함
|
||||
$extraVideos = [];
|
||||
if ($slotsLeft > 0) {
|
||||
$excClause = !empty($usedIds)
|
||||
? 'AND c.content_id NOT IN (' . implode(',', array_fill(0, count($usedIds), '?')) . ')'
|
||||
: '';
|
||||
$stmtExtra = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
0 AS is_bookmarked,
|
||||
GROUP_CONCAT(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 = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
WHERE c.is_active = 1
|
||||
{$excClause}
|
||||
GROUP BY c.content_id
|
||||
ORDER BY c.sort_order, RAND()
|
||||
LIMIT {$slotsLeft}
|
||||
");
|
||||
$bindParams = [$memberId, $sysCompCode];
|
||||
if (!empty($usedIds)) {
|
||||
$bindParams = array_merge($bindParams, $usedIds);
|
||||
}
|
||||
$stmtExtra->execute($bindParams);
|
||||
foreach ($stmtExtra->fetchAll() as $r) {
|
||||
$extraVideos[] = mapContentRow($r, 'main', '', $CATEGORY_MAP, $SUBCATE_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
// ③ 슬롯 순서대로 채우기: 키워드 영상 → DB 보충 → FALLBACK (최후 수단)
|
||||
$firstPageVideos = [];
|
||||
$extraIdx = 0;
|
||||
$fbIdx = 0;
|
||||
foreach ($slots as $slot) {
|
||||
if ($slot !== null) {
|
||||
$firstPageVideos[] = $slot;
|
||||
} elseif (isset($extraVideos[$extraIdx])) {
|
||||
$firstPageVideos[] = $extraVideos[$extraIdx++];
|
||||
}
|
||||
// DB에 영상이 없으면 해당 슬롯 생략 (하드코딩 fallback 없음)
|
||||
}
|
||||
|
||||
// ── 6. 총 학습시간(게이지용) ─────────────────────────────────
|
||||
$stmtTotal = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(all_tm), 0)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :mid AND sys_comp_code = :comp
|
||||
");
|
||||
$stmtTotal->execute([':mid' => $memberId, ':comp' => $sysCompCode]);
|
||||
$totalMin = (int)$stmtTotal->fetchColumn();
|
||||
|
||||
$dbSuccess = true;
|
||||
} catch (Exception $e) {
|
||||
// DB 오류 발생 시 fallback 기본값 사용
|
||||
$errMsg = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
||||
error_log('[bbs/main_data.php] DB Error: ' . $errMsg);
|
||||
api_log('main_data', 'DB_ERROR', ['error' => $errMsg]);
|
||||
|
||||
$userName = '사용자';
|
||||
$userRank = '';
|
||||
$myKeywords = [
|
||||
['keyword_code' => 'KW004', 'keyword_name' => '인물'],
|
||||
['keyword_code' => 'KW005', 'keyword_name' => '소통'],
|
||||
['keyword_code' => 'KW006', 'keyword_name' => '협업'],
|
||||
];
|
||||
$adminKeywords = [
|
||||
['keyword_code' => 'KW019', 'keyword_name' => '마인드셋'],
|
||||
['keyword_code' => 'KW020', 'keyword_name' => '웰니스'],
|
||||
];
|
||||
$allKeywords = array_map(
|
||||
fn($k) => ['keyword_code' => $k, 'keyword_name' => $k],
|
||||
['온보딩','성장','코칭','인물','소통','협업','AI','IT테크',
|
||||
'중간관리자','리더십','팔로우십','동기부여','인간관계','스킬업',
|
||||
'피드백','커리어','경영','경제','마인드셋','웰니스','자기개발']
|
||||
);
|
||||
$firstPageVideos = [];
|
||||
$totalMin = 0;
|
||||
}
|
||||
|
||||
// ── DB 실패 시에만 기본값 보장 (DB 성공 시 빈 키워드는 그대로 유지) ──
|
||||
if (!$dbSuccess) {
|
||||
$myKeywords = [
|
||||
['keyword_code' => 'KW004', 'keyword_name' => '인물'],
|
||||
['keyword_code' => 'KW005', 'keyword_name' => '소통'],
|
||||
['keyword_code' => 'KW006', 'keyword_name' => '협업'],
|
||||
];
|
||||
$adminKeywords = [
|
||||
['keyword_code' => 'KW019', 'keyword_name' => '마인드셋'],
|
||||
['keyword_code' => 'KW020', 'keyword_name' => '웰니스'],
|
||||
];
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// 뷰(skin)에서 사용할 JSON 직렬화 변수
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
$videosJson = json_encode(
|
||||
$firstPageVideos,
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP
|
||||
);
|
||||
$myKwJson = json_encode(
|
||||
array_column($myKeywords, 'keyword_name'),
|
||||
JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
/**
|
||||
* bbs/main_data.php
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
* 메인 페이지(skin/index.php) 용 데이터 조회 백엔드
|
||||
*
|
||||
* 이 파일은 직접 호출하지 않고 skin/index.php 에서 require_once 로 사용한다.
|
||||
* 실행 후 아래 변수가 설정된다:
|
||||
*
|
||||
* $userName string 사용자 이름
|
||||
* $userRank string 직위
|
||||
* $myKeywords array 내 키워드 [ ['keyword_code'=>..., 'keyword_name'=>...], ... ] (max 3)
|
||||
* $adminKeywords array 관리자 추천 키워드 (max 2)
|
||||
* $allKeywords array 모달용 전체 키워드 목록
|
||||
* $firstPageVideos array 메인 6개 영상 배열 (JS video 객체 형태)
|
||||
* $totalMin int 총 학습시간(분)
|
||||
* $videosJson string JSON 직렬화 영상 배열
|
||||
* $myKwJson string JSON 직렬화 내 키워드명 배열
|
||||
* ─────────────────────────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
require_once __DIR__ . '/api_log.php';
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// 현재 사용자 식별
|
||||
// TODO: 실제 세션 로그인 구현 후 아래 두 줄을 세션으로 교체
|
||||
// $memberId = $_SESSION['member_id'] ?? null;
|
||||
// $sysCompCode = $_SESSION['sys_comp_code'] ?? null;
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// 카테고리/서브카테고리 코드 → 한글 변환 맵 (DB 조회 전 fallback 기본값)
|
||||
// DB에서 edu_codes 조회 후 아래 값은 덮어쓰여짐
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
$CATEGORY_MAP = [
|
||||
'CA10001' => '마이클래스',
|
||||
'CA10002' => '온보딩',
|
||||
'CA10003' => '법정교육',
|
||||
'CA10004' => '리더십',
|
||||
'CA10005' => '인사이트',
|
||||
'CA10006' => '비즈트렌드',
|
||||
];
|
||||
$SUBCATE_MAP = [];
|
||||
|
||||
/**
|
||||
* contents 테이블 한 행을 JS video 객체 형태 배열로 변환
|
||||
*
|
||||
* @param array $row PDO fetchAll 행 데이터
|
||||
* @param string $type 카드 타입 (main / comment / onboarding 등)
|
||||
* @param string $picker Pick 배지에 표시할 이름 (없으면 '')
|
||||
* @return array
|
||||
*/
|
||||
function mapContentRow(array $row, string $type = 'main', string $picker = '', array $catMap = [], array $subcateMap = []): array
|
||||
{
|
||||
// GROUP_CONCAT 결과 키워드 문자열 → 배열
|
||||
$kwStr = $row['keywords'] ?? '';
|
||||
$keywords = $kwStr !== '' ? explode(',', $kwStr) : [];
|
||||
|
||||
// 학습 진행률 (watch_tm / content_tm × 100, 0~100 클램프)
|
||||
$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));
|
||||
|
||||
// content_url: 풀URL(https://www.youtube.com/watch?v=ID) 또는 영상ID만 저장된 경우 모두 지원
|
||||
$raw = trim($row['content_url'] ?? '');
|
||||
if (preg_match('/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/', $raw, $m)) {
|
||||
$videoId = $m[1]; // 풀 URL에서 ID 추출
|
||||
} elseif (preg_match('/^[A-Za-z0-9_-]{11}$/', $raw)) {
|
||||
$videoId = $raw; // 정확히 11자리 유효한 YouTube ID
|
||||
} else {
|
||||
$videoId = ''; // 유효하지 않은 값 (example02, 빈값 등)
|
||||
}
|
||||
// thumbnail_url 컬럼이 있으면 우선 사용, 없으면 YouTube API로 생성
|
||||
$thumbFromDb = trim($row['thumbnail_url'] ?? '');
|
||||
$url = $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : '';
|
||||
$thumbnail = $thumbFromDb !== '' ? $thumbFromDb
|
||||
: ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : '');
|
||||
|
||||
// 카테고리 코드 → 한글
|
||||
$categoryCode = $row['category_code'] ?? '';
|
||||
$category = $catMap[$categoryCode] ?? $categoryCode;
|
||||
|
||||
// 서브카테고리 코드 → 한글
|
||||
$groupCode = $row['category_group'] ?? '';
|
||||
$subcate = $subcateMap[$groupCode] ?? $groupCode;
|
||||
|
||||
return [
|
||||
'id' => $row['content_id'],
|
||||
'url' => $url,
|
||||
'thumbnail' => $thumbnail,
|
||||
'category' => $category,
|
||||
'category_code' => $categoryCode,
|
||||
'subcate' => $subcate,
|
||||
'bookmark' => (bool)($row['is_bookmarked'] ?? false),
|
||||
'title' => $row['title'] ?? '',
|
||||
'picker' => $picker,
|
||||
'type' => $type,
|
||||
'keywords' => $keywords,
|
||||
'gauge' => $gauge,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// DB 데이터 조회
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
$dbSuccess = false;
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ── 0. edu_codes에서 카테고리/서브카테고리 맵 동적 빌드 ──────
|
||||
$codesRows = $pdo->query("
|
||||
SELECT group_code, code, code_name
|
||||
FROM edu_codes
|
||||
WHERE is_active = 1
|
||||
AND group_code IN ('CA100', 'CA200')
|
||||
")->fetchAll();
|
||||
foreach ($codesRows as $cr) {
|
||||
$key = $cr['group_code'] . $cr['code']; // e.g. CA10001, CA200L01
|
||||
if ($cr['group_code'] === 'CA100') {
|
||||
$CATEGORY_MAP[$key] = $cr['code_name'];
|
||||
} else {
|
||||
$SUBCATE_MAP[$key] = $cr['code_name'];
|
||||
}
|
||||
}
|
||||
|
||||
// ── 1. 사용자 기본 정보 ─────────────────────────────────────
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT name, rank_name
|
||||
FROM edu_users
|
||||
WHERE member_id = :mid AND sys_comp_code = :comp
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([':mid' => $memberId, ':comp' => $sysCompCode]);
|
||||
$userRow = $stmtUser->fetch();
|
||||
$userName = $userRow['name'] ?? '사용자';
|
||||
$userRank = $userRow['rank_name'] ?? '';
|
||||
|
||||
// ── 2. 내 키워드 (최대 3개) ──────────────────────────────────
|
||||
$stmtMyKw = $pdo->prepare("
|
||||
SELECT keyword_code, keyword_name
|
||||
FROM edu_user_keywords
|
||||
WHERE member_id = :mid AND sys_comp_code = :comp
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 3
|
||||
");
|
||||
$stmtMyKw->execute([':mid' => $memberId, ':comp' => $sysCompCode]);
|
||||
$myKeywords = $stmtMyKw->fetchAll();
|
||||
|
||||
// ── 3. 관리자(법인) 추천 키워드 (최대 2개) ──────────────────
|
||||
$stmtAdminKw = $pdo->prepare("
|
||||
SELECT rk.keyword_code,
|
||||
COALESCE(c.code_name, rk.keyword_code) AS keyword_name
|
||||
FROM edu_recommend_keywords rk
|
||||
LEFT JOIN edu_codes c
|
||||
ON c.group_code = 'KW100'
|
||||
AND c.code = rk.keyword_code
|
||||
WHERE rk.sys_comp_code = :comp
|
||||
AND rk.is_active = 1
|
||||
ORDER BY rk.created_at DESC
|
||||
LIMIT 2
|
||||
");
|
||||
$stmtAdminKw->execute([':comp' => $sysCompCode]);
|
||||
$adminKeywords = $stmtAdminKw->fetchAll();
|
||||
|
||||
// ── 4. 모달용 전체 키워드 목록 ──────────────────────────────
|
||||
$stmtAllKw = $pdo->query("
|
||||
SELECT code AS keyword_code, code_name AS keyword_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'KW100' AND is_active = 1
|
||||
ORDER BY code_name
|
||||
");
|
||||
$allKeywords = $stmtAllKw ? $stmtAllKw->fetchAll() : [];
|
||||
|
||||
// DB에 코드 데이터 없으면 하드코딩 fallback
|
||||
if (empty($allKeywords)) {
|
||||
$fallbackKwNames = [
|
||||
'온보딩','성장','코칭','인물','소통','협업','AI','IT테크',
|
||||
'중간관리자','리더십','팔로우십','동기부여','인간관계','스킬업',
|
||||
'피드백','커리어','경영','경제','마인드셋','웰니스','자기개발',
|
||||
];
|
||||
$allKeywords = array_map(
|
||||
fn($k) => ['keyword_code' => $k, 'keyword_name' => $k],
|
||||
$fallbackKwNames
|
||||
);
|
||||
}
|
||||
|
||||
// ── 5. 영상 슬롯 조합 ────────────────────────────────────────
|
||||
// [0] Pick (좌상단 고정 - 다른 사람이 추천한 영상)
|
||||
// [1] 관리자 키워드 영상 (좌중단)
|
||||
// [2] 관리자 키워드 영상 (좌하단)
|
||||
// [3] 내 키워드 영상 (우상단)
|
||||
// [4] 내 키워드 영상 (우중단)
|
||||
// [5] 내 키워드 영상 (우하단)
|
||||
|
||||
$myKwCodes = array_column($myKeywords, 'keyword_code');
|
||||
$adminKwCodes = array_column($adminKeywords, 'keyword_code');
|
||||
|
||||
// 5-1. Pick 영상 (다른 사람이 제안, is_offer=1, 본인 제외)
|
||||
$stmtPick = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
NULL AS watch_tm,
|
||||
NULL AS content_tm,
|
||||
0 AS is_bookmarked,
|
||||
GROUP_CONCAT(ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords,
|
||||
u.name AS picker_name
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
LEFT JOIN edu_content_offer co ON co.offer_id = c.offer_id
|
||||
LEFT JOIN edu_users u ON u.member_id = co.member_id
|
||||
AND u.sys_comp_code = co.sys_comp_code
|
||||
WHERE c.is_offer = 1
|
||||
AND c.is_active = 1
|
||||
AND (co.member_id IS NULL OR co.member_id != :mid)
|
||||
GROUP BY c.content_id
|
||||
ORDER BY c.sort_order, RAND()
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtPick->execute([':mid' => $memberId]);
|
||||
$pickRow = $stmtPick->fetch();
|
||||
$pickVideo = $pickRow
|
||||
? mapContentRow($pickRow, 'main', $pickRow['picker_name'] ?? '동료', $CATEGORY_MAP, $SUBCATE_MAP)
|
||||
: null;
|
||||
|
||||
// 5-2. 관리자 키워드 관련 영상 (2개)
|
||||
$adminVideos = [];
|
||||
if (!empty($adminKwCodes)) {
|
||||
$ph = implode(',', array_fill(0, count($adminKwCodes), '?'));
|
||||
$stmtAdminV = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_contents c
|
||||
JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.keyword_code IN ({$ph})
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.content_id = c.content_id
|
||||
AND cw.member_id = ?
|
||||
AND cw.sys_comp_code = ?
|
||||
AND cw.is_active = 1
|
||||
WHERE c.is_active = 1
|
||||
GROUP BY c.content_id
|
||||
ORDER BY RAND()
|
||||
LIMIT 2
|
||||
");
|
||||
$stmtAdminV->execute(array_merge(
|
||||
$adminKwCodes,
|
||||
[$memberId, $sysCompCode, $memberId, $sysCompCode]
|
||||
));
|
||||
foreach ($stmtAdminV->fetchAll() as $r) {
|
||||
$adminVideos[] = mapContentRow($r, 'main', '', $CATEGORY_MAP, $SUBCATE_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
// 5-3. 내 키워드 관련 영상 (3개)
|
||||
$myVideos = [];
|
||||
if (!empty($myKwCodes)) {
|
||||
$ph2 = implode(',', array_fill(0, count($myKwCodes), '?'));
|
||||
$stmtMyV = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked,
|
||||
GROUP_CONCAT(ck.keyword_code ORDER BY ck.keyword_code SEPARATOR ',') AS keywords
|
||||
FROM edu_contents c
|
||||
JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
AND ck.keyword_code IN ({$ph2})
|
||||
LEFT JOIN edu_learning_histories lh ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_wishlist cw ON cw.content_id = c.content_id
|
||||
AND cw.member_id = ?
|
||||
AND cw.sys_comp_code = ?
|
||||
AND cw.is_active = 1
|
||||
WHERE c.is_active = 1
|
||||
GROUP BY c.content_id
|
||||
ORDER BY RAND()
|
||||
LIMIT 3
|
||||
");
|
||||
$stmtMyV->execute(array_merge(
|
||||
$myKwCodes,
|
||||
[$memberId, $sysCompCode, $memberId, $sysCompCode]
|
||||
));
|
||||
foreach ($stmtMyV->fetchAll() as $r) {
|
||||
$myVideos[] = mapContentRow($r, 'main', '', $CATEGORY_MAP, $SUBCATE_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
// 5-4. 6 슬롯 최종 조합
|
||||
// ① 키워드 매칭으로 채워진 영상 목록
|
||||
$slots = [
|
||||
$pickVideo,
|
||||
$adminVideos[0] ?? null,
|
||||
$adminVideos[1] ?? null,
|
||||
$myVideos[0] ?? null,
|
||||
$myVideos[1] ?? null,
|
||||
$myVideos[2] ?? null,
|
||||
];
|
||||
$usedIds = array_values(array_filter(array_map(fn($s) => $s ? $s['id'] : null, $slots)));
|
||||
$slotsLeft = 6 - count(array_filter($slots, fn($s) => $s !== null));
|
||||
|
||||
// ② 부족한 슬롯 → edu_contents 에서 직접 조회 (키워드·is_offer 무관, 랜덤)
|
||||
// 키워드를 선택하지 않아도 항상 DB 영상이 표시되도록 함
|
||||
$extraVideos = [];
|
||||
if ($slotsLeft > 0) {
|
||||
$excClause = !empty($usedIds)
|
||||
? 'AND c.content_id NOT IN (' . implode(',', array_fill(0, count($usedIds), '?')) . ')'
|
||||
: '';
|
||||
$stmtExtra = $pdo->prepare("
|
||||
SELECT c.*,
|
||||
lh.watch_tm,
|
||||
lh.content_tm,
|
||||
0 AS is_bookmarked,
|
||||
GROUP_CONCAT(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 = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_keywords ck ON ck.content_id = c.content_id
|
||||
WHERE c.is_active = 1
|
||||
{$excClause}
|
||||
GROUP BY c.content_id
|
||||
ORDER BY c.sort_order, RAND()
|
||||
LIMIT {$slotsLeft}
|
||||
");
|
||||
$bindParams = [$memberId, $sysCompCode];
|
||||
if (!empty($usedIds)) {
|
||||
$bindParams = array_merge($bindParams, $usedIds);
|
||||
}
|
||||
$stmtExtra->execute($bindParams);
|
||||
foreach ($stmtExtra->fetchAll() as $r) {
|
||||
$extraVideos[] = mapContentRow($r, 'main', '', $CATEGORY_MAP, $SUBCATE_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
// ③ 슬롯 순서대로 채우기: 키워드 영상 → DB 보충 → FALLBACK (최후 수단)
|
||||
$firstPageVideos = [];
|
||||
$extraIdx = 0;
|
||||
$fbIdx = 0;
|
||||
foreach ($slots as $slot) {
|
||||
if ($slot !== null) {
|
||||
$firstPageVideos[] = $slot;
|
||||
} elseif (isset($extraVideos[$extraIdx])) {
|
||||
$firstPageVideos[] = $extraVideos[$extraIdx++];
|
||||
}
|
||||
// DB에 영상이 없으면 해당 슬롯 생략 (하드코딩 fallback 없음)
|
||||
}
|
||||
|
||||
// ── 6. 총 학습시간(게이지용) ─────────────────────────────────
|
||||
$stmtTotal = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(all_tm), 0)
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = :mid AND sys_comp_code = :comp
|
||||
");
|
||||
$stmtTotal->execute([':mid' => $memberId, ':comp' => $sysCompCode]);
|
||||
$totalMin = (int)$stmtTotal->fetchColumn();
|
||||
|
||||
$dbSuccess = true;
|
||||
} catch (Exception $e) {
|
||||
// DB 오류 발생 시 fallback 기본값 사용
|
||||
$errMsg = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
||||
error_log('[bbs/main_data.php] DB Error: ' . $errMsg);
|
||||
api_log('main_data', 'DB_ERROR', ['error' => $errMsg]);
|
||||
|
||||
$userName = '사용자';
|
||||
$userRank = '';
|
||||
$myKeywords = [
|
||||
['keyword_code' => 'KW004', 'keyword_name' => '인물'],
|
||||
['keyword_code' => 'KW005', 'keyword_name' => '소통'],
|
||||
['keyword_code' => 'KW006', 'keyword_name' => '협업'],
|
||||
];
|
||||
$adminKeywords = [
|
||||
['keyword_code' => 'KW019', 'keyword_name' => '마인드셋'],
|
||||
['keyword_code' => 'KW020', 'keyword_name' => '웰니스'],
|
||||
];
|
||||
$allKeywords = array_map(
|
||||
fn($k) => ['keyword_code' => $k, 'keyword_name' => $k],
|
||||
['온보딩','성장','코칭','인물','소통','협업','AI','IT테크',
|
||||
'중간관리자','리더십','팔로우십','동기부여','인간관계','스킬업',
|
||||
'피드백','커리어','경영','경제','마인드셋','웰니스','자기개발']
|
||||
);
|
||||
$firstPageVideos = [];
|
||||
$totalMin = 0;
|
||||
}
|
||||
|
||||
// ── DB 실패 시에만 기본값 보장 (DB 성공 시 빈 키워드는 그대로 유지) ──
|
||||
if (!$dbSuccess) {
|
||||
$myKeywords = [
|
||||
['keyword_code' => 'KW004', 'keyword_name' => '인물'],
|
||||
['keyword_code' => 'KW005', 'keyword_name' => '소통'],
|
||||
['keyword_code' => 'KW006', 'keyword_name' => '협업'],
|
||||
];
|
||||
$adminKeywords = [
|
||||
['keyword_code' => 'KW019', 'keyword_name' => '마인드셋'],
|
||||
['keyword_code' => 'KW020', 'keyword_name' => '웰니스'],
|
||||
];
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// 뷰(skin)에서 사용할 JSON 직렬화 변수
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
$videosJson = json_encode(
|
||||
$firstPageVideos,
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP
|
||||
);
|
||||
$myKwJson = json_encode(
|
||||
array_column($myKeywords, 'keyword_name'),
|
||||
JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
$pdo = new PDO('mysql:host=baroncs.co.kr;port=3306;dbname=baronhomep;charset=utf8mb4','baronhomep','baron3840!!');
|
||||
echo "=== edu_contents category_code 현황 ===\n";
|
||||
$rows = $pdo->query("SELECT content_id, category_code, category_group, title FROM edu_contents ORDER BY content_id")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach($rows as $r) {
|
||||
echo $r['content_id'].' | cat='.$r['category_code'].' | grp='.$r['category_group'].' | '.$r['title']."\n";
|
||||
}
|
||||
echo "\n=== edu_content_keywords 현황 (첫 20개) ===\n";
|
||||
$rows2 = $pdo->query("SELECT content_id, keyword_code FROM edu_content_keywords ORDER BY content_id LIMIT 20")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach($rows2 as $r) echo $r['content_id'].' | '.$r['keyword_code']."\n";
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
$pdo = new PDO('mysql:host=baroncs.co.kr;port=3306;dbname=baronhomep;charset=utf8mb4','baronhomep','baron3840!!');
|
||||
echo "=== content_url & thumbnail_url 현황 ===\n";
|
||||
$rows = $pdo->query("SELECT content_id, content_url, thumbnail_url, is_active, is_offer FROM edu_contents ORDER BY content_id")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach($rows as $r) {
|
||||
echo sprintf("%-20s | is_offer=%-2s | is_active=%-2s | url=%-40s | thumb=%s\n",
|
||||
$r['content_id'], $r['is_offer'], $r['is_active'], $r['content_url'], $r['thumbnail_url']
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
try {
|
||||
$pdo = new PDO('mysql:host=baroncs.co.kr;port=3306;dbname=baronhomep;charset=utf8mb4', 'baronhomep', 'baron3840!!', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
|
||||
echo "=== edu_contents ===" . PHP_EOL;
|
||||
$rows = $pdo->query('SELECT content_id, category_code, title, is_active, is_offer FROM edu_contents ORDER BY content_id')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) echo implode(' | ', $r) . PHP_EOL;
|
||||
|
||||
echo PHP_EOL . "=== edu_codes (KW100) ===" . PHP_EOL;
|
||||
$kws = $pdo->query("SELECT code, code_name FROM edu_codes WHERE group_code='KW100' ORDER BY code")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($kws as $r) echo implode(' | ', $r) . PHP_EOL;
|
||||
|
||||
echo PHP_EOL . "=== edu_content_keywords (현재) ===" . PHP_EOL;
|
||||
$ck = $pdo->query('SELECT content_id, keyword_code FROM edu_content_keywords ORDER BY content_id, keyword_code')->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo count($ck) . ' rows' . PHP_EOL;
|
||||
foreach ($ck as $r) echo implode(' | ', $r) . PHP_EOL;
|
||||
|
||||
echo PHP_EOL . "=== edu_user_keywords ===" . PHP_EOL;
|
||||
$uk = $pdo->query('SELECT member_id, keyword_code, keyword_name FROM edu_user_keywords ORDER BY member_id')->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode($uk, JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
|
||||
echo PHP_EOL . "=== edu_recommend_keywords ===" . PHP_EOL;
|
||||
$rk = $pdo->query('SELECT sys_comp_code, keyword_code, is_active FROM edu_recommend_keywords')->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode($rk, JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo 'ERROR: ' . $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* 마이클래스 데이터 연결 진단
|
||||
*/
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
echo '<h1>마이클래스 데이터 연결 진단</h1>';
|
||||
echo '<style>
|
||||
body { font-family: Arial; margin: 20px; }
|
||||
table { border-collapse: collapse; margin: 20px 0; width: 100%; }
|
||||
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
|
||||
th { background: #f0f0f0; }
|
||||
.ok { color: green; }
|
||||
.err { color: red; }
|
||||
.warn { color: orange; }
|
||||
</style>';
|
||||
|
||||
// 1. edu_learning_goals 현황
|
||||
echo '<h2>1. 학습목표 현황 (edu_learning_goals)</h2>';
|
||||
$goals = $pdo->query("SELECT * FROM edu_learning_goals ORDER BY goal_code")->fetchAll();
|
||||
echo '<table><tr><th>goal_code</th><th>title</th><th>quarter</th><th>is_active</th></tr>';
|
||||
foreach ($goals as $g) {
|
||||
echo "<tr><td>{$g['goal_code']}</td><td>{$g['title']}</td><td>{$g['quarter']}</td><td>{$g['is_active']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
echo "<p>총 {$goals} 개 목표</p>";
|
||||
|
||||
// 2. edu_contents 샘플 (영상 리스트)
|
||||
echo '<h2>2. 콘텐츠 현황 (edu_contents)</h2>';
|
||||
$contents = $pdo->query("SELECT * FROM edu_contents LIMIT 20")->fetchAll();
|
||||
echo '<table><tr><th>content_id</th><th>title</th><th>category_group</th><th>content_url</th></tr>';
|
||||
foreach ($contents as $c) {
|
||||
echo "<tr><td>{$c['content_id']}</td><td>{$c['title']}</td><td>{$c['category_group']}</td><td>" . substr($c['content_url'], 0, 50) . "...</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
echo "<p>총 " . $pdo->query("SELECT COUNT(*) FROM edu_contents")->fetchColumn() . " 개 영상</p>";
|
||||
|
||||
// 3. edu_goal_contents 현황
|
||||
echo '<h2>3. 목표-콘텐츠 매핑 현황 (edu_goal_contents)</h2>';
|
||||
$goalContents = $pdo->query("SELECT COUNT(*) FROM edu_goal_contents")->fetchColumn();
|
||||
if ($goalContents > 0) {
|
||||
$rows = $pdo->query("SELECT * FROM edu_goal_contents LIMIT 20")->fetchAll();
|
||||
echo '<table><tr><th>goal_code</th><th>content_id</th><th>sort_order</th><th>is_active</th></tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo "<tr><td>{$r['goal_code']}</td><td>{$r['content_id']}</td><td>{$r['sort_order']}</td><td>{$r['is_active']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">⚠ 데이터 없음 - 맵핑이 필요합니다</p>';
|
||||
}
|
||||
|
||||
// 4. edu_user_learning_goals 현황
|
||||
echo '<h2>4. 사용자 목표 선택 (edu_user_learning_goals)</h2>';
|
||||
$userGoals = $pdo->query("SELECT COUNT(*) FROM edu_user_learning_goals")->fetchColumn();
|
||||
if ($userGoals > 0) {
|
||||
$rows = $pdo->query("SELECT * FROM edu_user_learning_goals LIMIT 20")->fetchAll();
|
||||
echo '<table><tr><th>member_id</th><th>goal_code</th><th>quarter</th><th>selected_at</th></tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo "<tr><td>{$r['member_id']}</td><td>{$r['goal_code']}</td><td>{$r['quarter']}</td><td>{$r['selected_at']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">⚠ 데이터 없음 - 사용자가 아직 목표를 선택하지 않았습니다</p>';
|
||||
}
|
||||
|
||||
// 5. 액션 아이템
|
||||
echo '<h2>5. 필요한 작업</h2>';
|
||||
echo '<ol>';
|
||||
echo '<li>✗ edu_goal_contents에 goal_code ↔ content_id 맵핑 데이터 INSERT</li>';
|
||||
echo '<li>✓ myclass_list.php: 사용자 선택 목표 조회 로직</li>';
|
||||
echo '<li>✓ myclass_list.php: edu_goal_contents에서 6개 영상 조회 로직</li>';
|
||||
echo '<li>✓ goal-layer.php: 목표 선택 모달 이벤트 처리</li>';
|
||||
echo '<li>✓ 새 API 또는 기존 API: 목표 선택 저장 (edu_user_learning_goals)</li>';
|
||||
echo '</ol>';
|
||||
|
||||
// 6. 권장 맵핑 규칙
|
||||
echo '<h2>6. 맵핑 규칙 (제안)</h2>';
|
||||
echo '<pre>';
|
||||
echo "각 goal_code마다 6개의 content_id 할당:
|
||||
- 2026-001: content_id 1~6
|
||||
- 2026-002: content_id 7~12
|
||||
- 2026-003: content_id 13~18
|
||||
- ...등등 (content_id가 있는 범위 내에서 균등 분배)
|
||||
|
||||
또는 현재 content_id 수에 따라 동적으로 분배
|
||||
";
|
||||
echo '</pre>';
|
||||
?>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
try {
|
||||
$pdo = new PDO('mysql:host=baroncs.co.kr;port=3306;dbname=baronhomep;charset=utf8mb4', 'baronhomep', 'baron3840!!', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
$rows = $pdo->query('SELECT group_code, code, code_name, is_active FROM edu_codes ORDER BY group_code, code')->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo count($rows) . ' rows' . PHP_EOL;
|
||||
foreach ($rows as $r) {
|
||||
echo implode(' | ', $r) . PHP_EOL;
|
||||
}
|
||||
echo PHP_EOL . '--- SHOW TABLES ---' . PHP_EOL;
|
||||
$tables = $pdo->query('SHOW TABLES LIKE "edu_%"')->fetchAll(PDO::FETCH_COLUMN);
|
||||
foreach ($tables as $t) echo $t . PHP_EOL;
|
||||
} catch (Exception $e) {
|
||||
echo 'ERROR: ' . $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$pdo = db_conn();
|
||||
$sql = "SELECT rg.goal_code,
|
||||
rg.seq,
|
||||
rg.title AS rg_title,
|
||||
rg.description AS rg_desc,
|
||||
c.sort_order,
|
||||
c.title AS c_title,
|
||||
c.description AS c_desc
|
||||
FROM edu_recommended_goals rg
|
||||
LEFT JOIN edu_contents c
|
||||
ON c.goal_code = rg.goal_code
|
||||
AND c.sort_order = rg.seq
|
||||
AND c.is_active = '1'
|
||||
WHERE rg.is_active = '1'
|
||||
ORDER BY rg.goal_code ASC, rg.seq ASC";
|
||||
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) {
|
||||
echo ($r['goal_code'] ?? '') . '#' . ($r['seq'] ?? '')
|
||||
. ' | rg_title=' . ($r['rg_title'] ?? '')
|
||||
. ' | rg_desc=' . ($r['rg_desc'] ?? '')
|
||||
. ' | c_title=' . ($r['c_title'] ?? '')
|
||||
. ' | c_desc=' . ($r['c_desc'] ?? '')
|
||||
. PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$year = date('Y');
|
||||
$pdo = db_conn();
|
||||
|
||||
$sql = "SELECT content_id, goal_code, title, description, description1, description2, sort_order, base_year, category_code, category_group, updated_at
|
||||
FROM edu_contents
|
||||
WHERE is_active = '1'
|
||||
AND category_code = 'CA10001'
|
||||
AND category_group = 'CA200Q01'
|
||||
AND sort_order = 1
|
||||
AND base_year = ?
|
||||
ORDER BY goal_code ASC";
|
||||
$st = $pdo->prepare($sql);
|
||||
$st->execute([$year]);
|
||||
$rows = $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo "[CURRENT QUERY ROWS] count=" . count($rows) . "\n";
|
||||
foreach ($rows as $r) {
|
||||
echo ($r['goal_code'] ?? '') . " | title=" . ($r['title'] ?? '')
|
||||
. " | description=" . ($r['description'] ?? '')
|
||||
. " | description1=" . ($r['description1'] ?? '')
|
||||
. " | description2=" . ($r['description2'] ?? '')
|
||||
. " | content_id=" . ($r['content_id'] ?? '')
|
||||
. "\n";
|
||||
}
|
||||
|
||||
echo "\n[ALL GOAL ROWS SAMPLE]\n";
|
||||
$rows2 = $pdo->query("SELECT goal_code, sort_order, title, description, description1, description2, content_id
|
||||
FROM edu_contents
|
||||
WHERE is_active='1' AND category_code='CA10001' AND category_group='CA200Q01'
|
||||
ORDER BY goal_code ASC, sort_order ASC, content_id ASC
|
||||
LIMIT 40")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows2 as $r) {
|
||||
echo ($r['goal_code'] ?? '') . "#" . ($r['sort_order'] ?? '')
|
||||
. " | t=" . ($r['title'] ?? '')
|
||||
. " | d=" . ($r['description'] ?? '')
|
||||
. " | d1=" . ($r['description1'] ?? '')
|
||||
. " | d2=" . ($r['description2'] ?? '')
|
||||
. " | id=" . ($r['content_id'] ?? '')
|
||||
. "\n";
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* _tmp_index_diag.php — index.php 영상 로딩 진단
|
||||
* 확인 후 삭제할 임시 파일
|
||||
*/
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
echo '<h2>EDU 인덱스 영상 로딩 진단</h2>';
|
||||
echo '<style>table{border-collapse:collapse;margin:10px 0} td,th{border:1px solid #ccc;padding:4px 8px} .ok{color:green} .err{color:red} .warn{color:orange}</style>';
|
||||
|
||||
// 1. 테이블 목록 확인
|
||||
echo '<h3>1. 관련 테이블 존재 여부</h3>';
|
||||
$tables = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
|
||||
$checkTables = ['edu_contents','edu_content_keywords','edu_keywords','edu_users','edu_user_keywords','edu_codes'];
|
||||
echo '<table><tr><th>테이블명</th><th>존재</th><th>레코드 수</th></tr>';
|
||||
foreach ($checkTables as $tbl) {
|
||||
$exists = in_array($tbl, $tables);
|
||||
$count = 0;
|
||||
if ($exists) {
|
||||
$count = $pdo->query("SELECT COUNT(*) FROM `$tbl`")->fetchColumn();
|
||||
}
|
||||
$cls = $exists ? 'ok' : 'err';
|
||||
echo "<tr><td>$tbl</td><td class='$cls'>" . ($exists ? '✔' : '✘ 없음') . "</td><td>$count</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
// 2. edu_contents 컬럼 구조
|
||||
echo '<h3>2. edu_contents 컬럼 구조</h3>';
|
||||
if (in_array('edu_contents', $tables)) {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_contents")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th><th>Null</th><th>Key</th><th>Default</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td><td>{$c['Null']}</td><td>{$c['Key']}</td><td>{$c['Default']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
// 샘플 데이터 3건
|
||||
echo '<h3>2-1. edu_contents 샘플 (최대 5건)</h3>';
|
||||
$rows = $pdo->query("SELECT * FROM edu_contents LIMIT 5")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="err">⚠ edu_contents 에 데이터가 없습니다 (DB 초기화됨)</p>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="err">edu_contents 테이블이 존재하지 않습니다.</p>';
|
||||
}
|
||||
|
||||
// 3. edu_content_keywords 구조 & 샘플
|
||||
echo '<h3>3. edu_content_keywords (키워드-콘텐츠 매핑)</h3>';
|
||||
if (in_array('edu_content_keywords', $tables)) {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_content_keywords")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
$rows = $pdo->query("SELECT * FROM edu_content_keywords LIMIT 5")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">edu_content_keywords 에 데이터가 없습니다.</p>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="warn">edu_content_keywords 테이블이 없습니다.</p>';
|
||||
}
|
||||
|
||||
// 4. edu_keywords 구조 & 샘플
|
||||
echo '<h3>4. edu_keywords</h3>';
|
||||
if (in_array('edu_keywords', $tables)) {
|
||||
$rows = $pdo->query("SELECT * FROM edu_keywords LIMIT 10")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">edu_keywords 에 데이터가 없습니다.</p>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="warn">edu_keywords 테이블이 없습니다.</p>';
|
||||
}
|
||||
|
||||
// 5. edu_user_keywords (유저-키워드 연결)
|
||||
echo '<h3>5. edu_user_keywords</h3>';
|
||||
if (in_array('edu_user_keywords', $tables)) {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_user_keywords")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
$rows = $pdo->query("SELECT * FROM edu_user_keywords LIMIT 10")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">edu_user_keywords 에 데이터가 없습니다.</p>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="warn">edu_user_keywords 테이블이 없습니다.</p>';
|
||||
}
|
||||
|
||||
// 6. 전체 테이블 목록
|
||||
echo '<h3>6. DB 전체 테이블 목록 (' . count($tables) . '개)</h3>';
|
||||
echo '<ul>' . implode('', array_map(fn($t) => "<li>$t</li>", $tables)) . '</ul>';
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* _tmp_index_diag2.php — 키워드 상세 진단 (edu_keywords 복원용)
|
||||
*/
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
echo '<h2>EDU 키워드 상세 진단</h2>';
|
||||
echo '<style>table{border-collapse:collapse;margin:10px 0} td,th{border:1px solid #ccc;padding:4px 8px} .ok{color:green} .err{color:red} .warn{color:orange} pre{background:#f5f5f5;padding:8px;font-size:12px}</style>';
|
||||
|
||||
// 1. edu_content_keywords 전체 unique keyword_code 목록
|
||||
echo '<h3>1. edu_content_keywords - 전체 unique keyword_code</h3>';
|
||||
$rows = $pdo->query("
|
||||
SELECT keyword_code, COUNT(*) as cnt
|
||||
FROM edu_content_keywords
|
||||
WHERE is_active = '1' OR is_active IS NULL
|
||||
GROUP BY keyword_code
|
||||
ORDER BY keyword_code
|
||||
")->fetchAll();
|
||||
echo '<table><tr><th>keyword_code</th><th>연결 콘텐츠 수</th></tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo "<tr><td>{$r['keyword_code']}</td><td>{$r['cnt']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
// 2. edu_recommend_keywords 구조 및 데이터
|
||||
echo '<h3>2. edu_recommend_keywords 구조 및 데이터</h3>';
|
||||
$tables = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
|
||||
if (in_array('edu_recommend_keywords', $tables)) {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_recommend_keywords")->fetchAll();
|
||||
echo '<b>컬럼:</b><br><table><tr><th>Field</th><th>Type</th><th>Key</th><th>Default</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td><td>{$c['Key']}</td><td>{$c['Default']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
$rows = $pdo->query("SELECT * FROM edu_recommend_keywords LIMIT 20")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">데이터 없음</p>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="err">테이블 없음</p>';
|
||||
}
|
||||
|
||||
// 3. edu_user_keywords 전체 데이터
|
||||
echo '<h3>3. edu_user_keywords 전체</h3>';
|
||||
$rows = $pdo->query("SELECT * FROM edu_user_keywords ORDER BY member_id, keyword_code")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">데이터 없음</p>';
|
||||
}
|
||||
|
||||
// 4. edu_codes 에서 KW 계열 코드 확인
|
||||
echo '<h3>4. edu_codes - KW 계열 코드 (keyword 관련)</h3>';
|
||||
$rows = $pdo->query("
|
||||
SELECT * FROM edu_codes
|
||||
WHERE base_code LIKE 'KW%'
|
||||
ORDER BY base_code
|
||||
")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">KW 계열 코드 없음</p>';
|
||||
// edu_codes 전체 구조 확인
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_codes")->fetchAll();
|
||||
echo '<b>edu_codes 컬럼:</b><table><tr><th>Field</th><th>Type</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
// 샘플 10건
|
||||
$sample = $pdo->query("SELECT * FROM edu_codes LIMIT 20")->fetchAll();
|
||||
if ($sample) {
|
||||
$keys = array_keys($sample[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($sample as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
}
|
||||
|
||||
// 5. edu_code_group 구조 확인
|
||||
echo '<h3>5. edu_code_group</h3>';
|
||||
if (in_array('edu_code_group', $tables)) {
|
||||
$rows = $pdo->query("SELECT * FROM edu_code_group LIMIT 20")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
}
|
||||
|
||||
// 6. edu_contents 에서 keyword 관련 컬럼 값 샘플
|
||||
echo '<h3>6. edu_contents - category 관련 고유값</h3>';
|
||||
$rows = $pdo->query("
|
||||
SELECT category_code, category_group, COUNT(*) as cnt
|
||||
FROM edu_contents
|
||||
GROUP BY category_code, category_group
|
||||
ORDER BY category_group, category_code
|
||||
")->fetchAll();
|
||||
echo '<table><tr><th>category_code</th><th>category_group</th><th>콘텐츠 수</th></tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo "<tr><td>{$r['category_code']}</td><td>{$r['category_group']}</td><td>{$r['cnt']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
// 7. edu_contents 중 keyword 연결된 영상 샘플 (edu_content_keywords join)
|
||||
echo '<h3>7. edu_content_keywords ↔ edu_contents 조인 샘플</h3>';
|
||||
$rows = $pdo->query("
|
||||
SELECT ck.keyword_code, c.content_id, c.title, c.category_group, c.content_url
|
||||
FROM edu_content_keywords ck
|
||||
JOIN edu_contents c ON c.content_id = ck.content_id
|
||||
WHERE ck.is_active = '1'
|
||||
ORDER BY ck.keyword_code, c.content_id
|
||||
LIMIT 20
|
||||
")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="warn">결과 없음</p>';
|
||||
}
|
||||
|
||||
// 8. index.php 에서 사용하는 main_data 관련 api 로그 (있으면)
|
||||
echo '<h3>8. API 관련 파일 목록 확인</h3>';
|
||||
$apiDir = __DIR__ . '/api/';
|
||||
if (is_dir($apiDir)) {
|
||||
foreach (scandir($apiDir) as $f) {
|
||||
if ($f === '.' || $f === '..') continue;
|
||||
$fpath = $apiDir . $f;
|
||||
$size = filesize($fpath);
|
||||
echo "<li><b>$f</b> ({$size}bytes)</li>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* _tmp_index_diag3.php — API 파일 내용 + main_data 구조 진단
|
||||
*/
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo '<h2>API 파일 내용 진단</h2>';
|
||||
echo '<style>pre{background:#f5f5f5;padding:10px;font-size:11px;overflow:auto;white-space:pre-wrap;word-break:break-all;border:1px solid #ddd;max-height:600px} h3{margin-top:20px;color:#333}</style>';
|
||||
|
||||
$base = __DIR__;
|
||||
|
||||
$files = [
|
||||
'api/videos_by_keywords.php',
|
||||
'api/user_keywords.php',
|
||||
'main_data.php',
|
||||
'main_data_260316_1420.php',
|
||||
'main_data_moon.php',
|
||||
'../skin/index.php',
|
||||
'../skin/index_over.php',
|
||||
'../skin/index_moon.php',
|
||||
'../skin/index_guide.php',
|
||||
'../skin/_backup/index.php',
|
||||
'../skin/_backup/keyword.php',
|
||||
];
|
||||
|
||||
foreach ($files as $f) {
|
||||
$full = realpath($base . '/' . $f);
|
||||
echo "<h3>$f</h3>";
|
||||
if (!$full || !file_exists($full)) {
|
||||
echo '<p style="color:red">파일 없음 또는 접근 불가</p>';
|
||||
continue;
|
||||
}
|
||||
$size = filesize($full);
|
||||
if ($size === 0) {
|
||||
echo '<p style="color:orange">파일 크기 0 (empty)</p>';
|
||||
continue;
|
||||
}
|
||||
echo "<p>크기: {$size} bytes</p>";
|
||||
echo '<pre>' . htmlspecialchars(file_get_contents($full)) . '</pre>';
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
try {
|
||||
$pdo = new PDO('mysql:host=baroncs.co.kr;port=3306;dbname=baronhomep;charset=utf8mb4', 'baronhomep', 'baron3840!!', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
$pdo->exec("SET NAMES utf8mb4");
|
||||
|
||||
$sql = file_get_contents(__DIR__ . '/../sample_content_keywords.sql');
|
||||
$pdo->exec($sql);
|
||||
|
||||
$cnt = $pdo->query('SELECT COUNT(*) FROM edu_content_keywords')->fetchColumn();
|
||||
echo json_encode(['status' => 'ok', 'inserted_total' => (int)$cnt], JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
|
||||
// 키워드별 매핑 건수 확인
|
||||
$rows = $pdo->query('SELECT keyword_code, COUNT(*) as cnt FROM edu_content_keywords GROUP BY keyword_code ORDER BY cnt DESC')->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
|
||||
// recommend_keywords 확인
|
||||
$rk = $pdo->query('SELECT * FROM edu_recommend_keywords')->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode($rk, JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 'error', 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require __DIR__ . '/db_conn.php';
|
||||
|
||||
$pdo = db_conn();
|
||||
|
||||
$existsStmt = $pdo->prepare("SELECT base_code, code, code_name FROM edu_codes WHERE group_code='KW100' AND code_name = ? LIMIT 1");
|
||||
$existsStmt->execute(['자기개발']);
|
||||
$exists = $existsStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($exists) {
|
||||
echo "EXISTS|{$exists['base_code']}|{$exists['code']}|{$exists['code_name']}\n";
|
||||
} else {
|
||||
$nextCode = (int)$pdo->query("SELECT COALESCE(MAX(CAST(code AS UNSIGNED)), 0) FROM edu_codes WHERE group_code='KW100'")->fetchColumn() + 1;
|
||||
$code = str_pad((string)$nextCode, 2, '0', STR_PAD_LEFT);
|
||||
$baseCode = 'KW100' . $code;
|
||||
|
||||
$ins = $pdo->prepare("INSERT INTO edu_codes (group_code, code, base_code, code_name, is_active, created_by, created_at, updated_by, updated_at) VALUES ('KW100', ?, ?, ?, '1', 'admin', NOW(), 'admin', NOW())");
|
||||
$ins->execute([$code, $baseCode, '자기개발']);
|
||||
|
||||
echo "INSERTED|{$baseCode}|{$code}|자기개발\n";
|
||||
}
|
||||
|
||||
$rows = $pdo->query("SELECT code, base_code, code_name FROM edu_codes WHERE group_code='KW100' ORDER BY CAST(code AS UNSIGNED)")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) {
|
||||
echo "ROW|{$r['code']}|{$r['base_code']}|{$r['code_name']}\n";
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$pdo = db_conn();
|
||||
|
||||
echo "[COLUMNS]\n";
|
||||
$cols = $pdo->query('SHOW COLUMNS FROM edu_learning_goals')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($cols as $c) {
|
||||
echo $c['Field'] . '|' . $c['Type'] . PHP_EOL;
|
||||
}
|
||||
|
||||
echo "\n[SAMPLES]\n";
|
||||
$rows = $pdo->query('SELECT * FROM edu_learning_goals ORDER BY 1 ASC LIMIT 20')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) {
|
||||
$pairs = [];
|
||||
foreach ($r as $k => $v) {
|
||||
$pairs[] = $k . '=' . str_replace(["\r", "\n"], ' ', (string)$v);
|
||||
}
|
||||
echo implode(' | ', $pairs) . PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$pdo = db_conn();
|
||||
|
||||
echo "[COLUMNS]\n";
|
||||
$cols = $pdo->query('SHOW COLUMNS FROM edu_contents')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($cols as $c) {
|
||||
echo $c['Field'] . '|' . $c['Type'] . PHP_EOL;
|
||||
}
|
||||
|
||||
echo "\n[SAMPLES_WITH_DESC]\n";
|
||||
$rows = $pdo->query("SELECT content_id, title, description, goal_code, category_group, category_code, sort_order, is_active
|
||||
FROM edu_contents
|
||||
WHERE is_active = '1' AND CHAR_LENGTH(TRIM(COALESCE(description, ''))) >= 5
|
||||
ORDER BY updated_at DESC, created_at DESC
|
||||
LIMIT 20")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) {
|
||||
$id = isset($r['content_id']) ? (string)$r['content_id'] : '';
|
||||
$title = isset($r['title']) ? (string)$r['title'] : '';
|
||||
$desc = isset($r['description']) ? (string)$r['description'] : '';
|
||||
$goal = isset($r['goal_code']) ? (string)$r['goal_code'] : '';
|
||||
$group = isset($r['category_group']) ? (string)$r['category_group'] : '';
|
||||
$cate = isset($r['category_code']) ? (string)$r['category_code'] : '';
|
||||
$sort = isset($r['sort_order']) ? (string)$r['sort_order'] : '';
|
||||
$active = isset($r['is_active']) ? (string)$r['is_active'] : '';
|
||||
echo $id . '|' . $title . '|' . preg_replace('/\s+/', ' ', $desc) . '|goal=' . $goal . '|group=' . $group . '|cate=' . $cate . '|sort=' . $sort . '|active=' . $active . PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/**
|
||||
* 마이클래스 구조 진단 파일
|
||||
* edu_learning_goals와 edu_goal_contents 구조 파악
|
||||
*/
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
echo '<h1>마이클래스 데이터베이스 구조</h1>';
|
||||
echo '<style>
|
||||
body { font-family: Arial; margin: 20px; }
|
||||
table { border-collapse: collapse; margin: 20px 0; width: 100%; }
|
||||
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
|
||||
th { background: #f0f0f0; }
|
||||
.ok { color: green; }
|
||||
.err { color: red; }
|
||||
h2 { margin-top: 30px; }
|
||||
</style>';
|
||||
|
||||
// 1. 모든 테이블 목록
|
||||
echo '<h2>1. 전체 테이블 목록</h2>';
|
||||
$tables = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
|
||||
echo '<table><tr><th>테이블명</th></tr>';
|
||||
foreach ($tables as $t) {
|
||||
echo "<tr><td>$t</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
// 2. edu_learning_goals 확인
|
||||
echo '<h2>2. edu_learning_goals 테이블</h2>';
|
||||
if (in_array('edu_learning_goals', $tables)) {
|
||||
echo '<b>컬럼 구조:</b>';
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_learning_goals")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th><th>Null</th><th>Key</th><th>Default</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td><td>{$c['Null']}</td><td>{$c['Key']}</td><td>{$c['Default']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
echo '<b>샘플 데이터:</b>';
|
||||
$rows = $pdo->query("SELECT * FROM edu_learning_goals LIMIT 10")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p class="err">데이터 없음</p>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="err">테이블 없음</p>';
|
||||
}
|
||||
|
||||
// 3. edu_goal_contents 확인
|
||||
echo '<h2>3. edu_goal_contents 테이블</h2>';
|
||||
if (in_array('edu_goal_contents', $tables)) {
|
||||
echo '<b>컬럼 구조:</b>';
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_goal_contents")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th><th>Null</th><th>Key</th><th>Default</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td><td>{$c['Null']}</td><td>{$c['Key']}</td><td>{$c['Default']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
echo '<b>샘플 데이터:</b>';
|
||||
$rows = $pdo->query("SELECT * FROM edu_goal_contents LIMIT 20")->fetchAll();
|
||||
if ($rows) {
|
||||
$keys = array_keys($rows[0]);
|
||||
echo '<table><tr>' . implode('', array_map(fn($k) => "<th>$k</th>", $keys)) . '</tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo '<tr>' . implode('', array_map(fn($v) => '<td>' . htmlspecialchars((string)$v) . '</td>', $r)) . '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
// 통계
|
||||
echo '<b>통계:</b>';
|
||||
$stats = $pdo->query("
|
||||
SELECT
|
||||
COUNT(DISTINCT goal_id) as goal_cnt,
|
||||
COUNT(DISTINCT content_id) as content_cnt,
|
||||
COUNT(*) as total_rows
|
||||
FROM edu_goal_contents
|
||||
")->fetch();
|
||||
echo '<div><p>총 goal_id: ' . $stats['goal_cnt'] . '</p>';
|
||||
echo '<p>총 content_id: ' . $stats['content_cnt'] . '</p>';
|
||||
echo '<p>전체 행: ' . $stats['total_rows'] . '</p></div>';
|
||||
} else {
|
||||
echo '<p class="err">데이터 없음</p>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="err">테이블 없음</p>';
|
||||
}
|
||||
|
||||
// 4. 관계도 확인 (learning_goals ↔ goal_contents)
|
||||
echo '<h2>4. edu_learning_goals ↔ edu_goal_contents 연결</h2>';
|
||||
if (in_array('edu_learning_goals', $tables) && in_array('edu_goal_contents', $tables)) {
|
||||
echo '<b>학습목표별 영상 수:</b>';
|
||||
$rows = $pdo->query("
|
||||
SELECT
|
||||
g.goal_id,
|
||||
g.goal_name,
|
||||
COUNT(c.content_id) as video_count,
|
||||
GROUP_CONCAT(c.content_id ORDER BY c.sort_order) as content_ids
|
||||
FROM edu_learning_goals g
|
||||
LEFT JOIN edu_goal_contents c ON g.goal_id = c.goal_id
|
||||
GROUP BY g.goal_id, g.goal_name
|
||||
ORDER BY g.goal_id
|
||||
")->fetchAll();
|
||||
|
||||
if ($rows) {
|
||||
echo '<table><tr><th>goal_id</th><th>goal_name</th><th>영상 수</th><th>content_ids</th></tr>';
|
||||
foreach ($rows as $r) {
|
||||
echo "<tr>";
|
||||
echo "<td>{$r['goal_id']}</td>";
|
||||
echo "<td>{$r['goal_name']}</td>";
|
||||
echo "<td>{$r['video_count']}</td>";
|
||||
echo "<td>" . htmlspecialchars($r['content_ids'] ?? '') . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 사용자 선택 목표 (if exists)
|
||||
echo '<h2>5. 사용자별 선택된 학습목표</h2>';
|
||||
if (in_array('edu_user_goals', $tables)) {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_user_goals")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
} else if (in_array('edu_goal_user_select', $tables)) {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_goal_user_select")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p>사용자 선택 테이블을 찾을 수 없습니다.</p>';
|
||||
}
|
||||
|
||||
// 6. 영상 시청 기록 (learning status)
|
||||
echo '<h2>6. 사용자 학습 진행상황 관련 테이블</h2>';
|
||||
$learningTables = ['edu_learning_progress', 'edu_user_learning', 'edu_learning_status'];
|
||||
foreach ($learningTables as $tbl) {
|
||||
if (in_array($tbl, $tables)) {
|
||||
echo "<h3>$tbl</h3>";
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM $tbl")->fetchAll();
|
||||
echo '<table><tr><th>Field</th><th>Type</th></tr>';
|
||||
foreach ($cols as $c) {
|
||||
echo "<tr><td>{$c['Field']}</td><td>{$c['Type']}</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$pdo = db_conn();
|
||||
|
||||
echo "[COLUMNS]\n";
|
||||
$cols = $pdo->query('SHOW COLUMNS FROM edu_recommended_goals')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($cols as $c) {
|
||||
echo $c['Field'] . '|' . $c['Type'] . PHP_EOL;
|
||||
}
|
||||
|
||||
echo "\n[SAMPLES]\n";
|
||||
$rows = $pdo->query('SELECT * FROM edu_recommended_goals ORDER BY 1 ASC LIMIT 30')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) {
|
||||
$pairs = [];
|
||||
foreach ($r as $k => $v) {
|
||||
$pairs[] = $k . '=' . str_replace(["\r", "\n"], ' ', (string)$v);
|
||||
}
|
||||
echo implode(' | ', $pairs) . PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
require __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Map KW100xx codes in edu_content_keywords to Korean keyword names in edu_codes.code_name.
|
||||
$sql = "
|
||||
INSERT INTO edu_content_keywords (
|
||||
content_id,
|
||||
keyword_code,
|
||||
is_active,
|
||||
created_by,
|
||||
created_at,
|
||||
updated_by,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
ck.content_id,
|
||||
ec.code_name AS keyword_code,
|
||||
'1' AS is_active,
|
||||
'admin' AS created_by,
|
||||
NOW() AS created_at,
|
||||
'admin' AS updated_by,
|
||||
NOW() AS updated_at
|
||||
FROM edu_content_keywords ck
|
||||
JOIN edu_codes ec
|
||||
ON ec.base_code = ck.keyword_code
|
||||
AND ec.group_code = 'KW100'
|
||||
LEFT JOIN edu_content_keywords ck2
|
||||
ON ck2.content_id = ck.content_id
|
||||
AND ck2.keyword_code = ec.code_name
|
||||
WHERE ck.keyword_code LIKE 'KW%'
|
||||
AND ck.is_active = '1'
|
||||
AND ec.code_name IS NOT NULL
|
||||
AND ec.code_name <> ''
|
||||
AND ck2.content_id IS NULL
|
||||
";
|
||||
|
||||
$inserted = $pdo->exec($sql);
|
||||
$pdo->commit();
|
||||
|
||||
echo "INSERTED_ROWS|" . (int)$inserted . "\n";
|
||||
|
||||
$checkKeywords = ['AI', '리더십', '자기개발', '온보딩', '중간관리자'];
|
||||
$stmt = $pdo->prepare("SELECT keyword_code, COUNT(*) AS cnt FROM edu_content_keywords WHERE keyword_code = ? GROUP BY keyword_code");
|
||||
foreach ($checkKeywords as $kw) {
|
||||
$stmt->execute([$kw]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$cnt = $row ? (int)$row['cnt'] : 0;
|
||||
echo "KW_COUNT|{$kw}|{$cnt}\n";
|
||||
}
|
||||
|
||||
$sample = $pdo->query("SELECT content_id, keyword_code FROM edu_content_keywords WHERE keyword_code IN ('AI','리더십','자기개발') ORDER BY keyword_code, content_id LIMIT 20")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($sample as $r) {
|
||||
echo "SAMPLE|{$r['content_id']}|{$r['keyword_code']}\n";
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
$tables = [
|
||||
'edu_user_learning_goals',
|
||||
'edu_learning_histories',
|
||||
'edu_content_memos',
|
||||
'edu_goal_contents',
|
||||
'edu_learning_goals',
|
||||
'edu_contents'
|
||||
];
|
||||
foreach ($tables as $t) {
|
||||
echo '=== ' . $t . ' ===' . PHP_EOL;
|
||||
$rows = $pdo->query('SHOW COLUMNS FROM ' . $t)->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) {
|
||||
$def = $r['Default'];
|
||||
if ($def === null) {
|
||||
$def = 'NULL';
|
||||
}
|
||||
echo $r['Field'] . '|' . $r['Type'] . '|' . $r['Null'] . '|' . $r['Key'] . '|' . $def . PHP_EOL;
|
||||
}
|
||||
echo PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
$pdo = db_conn();
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM edu_users")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($cols as $c) {
|
||||
echo $c['Field'] . '|' . $c['Type'] . '|' . $c['Null'] . '|' . $c['Key'] . '|' . ($c['Default'] ?? 'NULL') . PHP_EOL;
|
||||
}
|
||||
echo "---sample---" . PHP_EOL;
|
||||
$rows = $pdo->query("SELECT member_id, intra_pw FROM edu_users ORDER BY member_id LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $r) {
|
||||
$pw = (string)($r['intra_pw'] ?? '');
|
||||
$prefix = substr($pw, 0, 4);
|
||||
echo $r['member_id'] . '|len=' . strlen($pw) . '|prefix=' . $prefix . PHP_EOL;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/db_conn.php';
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? $_SESSION['user_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? $_SESSION['company'] ?? '');
|
||||
|
||||
if ($memberId === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'not_logged_in'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$input = is_string($rawBody) && $rawBody !== '' ? json_decode($rawBody, true) : [];
|
||||
if (!is_array($input)) {
|
||||
$input = $_POST;
|
||||
}
|
||||
|
||||
$contentId = trim((string)($input['content_id'] ?? ''));
|
||||
if ($contentId === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'content_id_required'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// 학습 이력 조회
|
||||
$sql = 'SELECT watch_tm, content_tm, completed_at, last_viewed_at
|
||||
FROM edu_learning_histories
|
||||
WHERE member_id = ? AND content_id = ?';
|
||||
$params = [$memberId, $contentId];
|
||||
|
||||
if ($sysCompCode !== '') {
|
||||
$sql .= ' AND sys_comp_code = ?';
|
||||
$params[] = $sysCompCode;
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY last_viewed_at DESC LIMIT 1';
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 콘텐츠 상세정보 조회 (description)
|
||||
$stmtContent = $pdo->prepare('SELECT description FROM edu_contents WHERE content_id = ? LIMIT 1');
|
||||
$stmtContent->execute([$contentId]);
|
||||
$contentRow = $stmtContent->fetch(PDO::FETCH_ASSOC);
|
||||
$description = ($contentRow['description'] ?? '');
|
||||
|
||||
// 북마크 상태 조회
|
||||
$bookmarkSql = 'SELECT is_active FROM edu_content_wishlist WHERE content_id = ? AND member_id = ?';
|
||||
$bookmarkParams = [$contentId, $memberId];
|
||||
if ($sysCompCode !== '') {
|
||||
$bookmarkSql .= ' AND sys_comp_code = ?';
|
||||
$bookmarkParams[] = $sysCompCode;
|
||||
}
|
||||
$bookmarkSql .= ' LIMIT 1';
|
||||
$stmtBookmark = $pdo->prepare($bookmarkSql);
|
||||
$stmtBookmark->execute($bookmarkParams);
|
||||
$bookmarkRow = $stmtBookmark->fetch(PDO::FETCH_ASSOC);
|
||||
$isBookmarked = (($bookmarkRow['is_active'] ?? '0') === '1');
|
||||
|
||||
$result = [
|
||||
'success' => true,
|
||||
'watch_tm' => (int)($row['watch_tm'] ?? 0),
|
||||
'content_tm' => (int)($row['content_tm'] ?? 0),
|
||||
'completed_at' => $row['completed_at'] ?? null,
|
||||
'last_viewed_at' => $row['last_viewed_at'] ?? null,
|
||||
'description' => $description,
|
||||
'is_bookmarked' => $isBookmarked,
|
||||
];
|
||||
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('[get_video_time] ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'server_error'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 개인정보 + 배지
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
if (!function_exists('mypage_h')) {
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_level_meta')) {
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'code' => 'Master',
|
||||
'class' => 'master',
|
||||
'label' => 'Master',
|
||||
'icon' => '/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'code' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'label' => 'Elite',
|
||||
'icon' => '/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'code' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'label' => 'Learner',
|
||||
'icon' => '/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_badge_map')) {
|
||||
function mypage_badge_map(string $badgeCode): array
|
||||
{
|
||||
$badgeCode = strtoupper(trim($badgeCode));
|
||||
|
||||
$BADGE_ICON_MAP = [
|
||||
// 실제 운영 시 코드값 확정되면 여기만 교체
|
||||
'BG001' => [
|
||||
'slot' => 'hat',
|
||||
'img' => '/img/mypage/ico_school.png',
|
||||
'name' => '학습 배지',
|
||||
],
|
||||
'BG002' => [
|
||||
'slot' => 'pencil',
|
||||
'img' => '/img/mypage/ico_pencil.png',
|
||||
'name' => '작성 배지',
|
||||
],
|
||||
'BG003' => [
|
||||
'slot' => 'pick',
|
||||
'img' => '/img/mypage/ico_pick_2.png',
|
||||
'name' => 'Pick 배지',
|
||||
],
|
||||
];
|
||||
|
||||
return $BADGE_ICON_MAP[$badgeCode] ?? [
|
||||
'slot' => 'pick',
|
||||
'img' => '/img/mypage/ico_pick_2.png',
|
||||
'name' => '기본 배지',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
$comp_name = $_SESSION['comp_name'] ?? '';
|
||||
$member_name = $_SESSION['member_name'] ?? '';
|
||||
$rank_name = $_SESSION['rank_name'];
|
||||
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
//echo "memberId===".$memberId."<br>";
|
||||
//echo "sysCompCode===".$sysCompCode."<br>";exit;
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
|
||||
// ★ 선택년도 수집
|
||||
$selectedYear = trim((string)($_GET['selected_year'] ?? date('Y')));
|
||||
|
||||
if ($selectedYear === '' || !preg_match('/^\d{4}$/', $selectedYear)) {
|
||||
$selectedYear = date('Y');
|
||||
}
|
||||
$profileFileName = $memberId . '_' . $sysCompCode . '.png';
|
||||
$profileFilePath = __DIR__ . '/../img/profile/' . $profileFileName;
|
||||
$profileImageUrl = '/img/profile/' . $profileFileName;
|
||||
|
||||
|
||||
|
||||
if (file_exists($profileFilePath)) {
|
||||
$profileImage = $profileImageUrl;
|
||||
/*
|
||||
if($memberId=="M21420"){
|
||||
echo 11111111;
|
||||
exit;
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
/*
|
||||
if($memberId=="M21420"){
|
||||
echo 222222222222;
|
||||
exit;
|
||||
}*/
|
||||
|
||||
$profileImage = '/img/ico/ico_profile.svg';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
if (file_exists($profileFilePath)) {
|
||||
$profileImage = $profileImageUrl;
|
||||
} else {
|
||||
$profileImage = '/img/ico/ico_profile.svg';
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'name' => $member_name,
|
||||
'rank_name' => $rank_name,
|
||||
'working_comp' => '',
|
||||
'belong_comp_name' => $comp_name,
|
||||
'join_date' => '',
|
||||
'latest_stats_year' => $selectedYear,
|
||||
'learning_level' => 'Rookie',
|
||||
'level_class' => 'rookie',
|
||||
'level_icon' => '/img/ico/ico_level_rookie.svg',
|
||||
'total_minutes' => 0,
|
||||
'profile_image' => $profileImage, // 현재는 기본 이미지 고정
|
||||
];
|
||||
|
||||
$profileBadges = [
|
||||
'hat' => null,
|
||||
'pencil' => null,
|
||||
'pick' => null,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// ★ 선택년도 학습통계 기준
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.working_comp,
|
||||
u.join_date,
|
||||
|
||||
ec.code,
|
||||
ec.code_name,
|
||||
|
||||
ys.stats_year,
|
||||
ys.learning_level,
|
||||
ys.total_minutes
|
||||
FROM edu_users u
|
||||
LEFT JOIN edu_yearly_learning_stats ys
|
||||
ON ys.member_id = u.member_id
|
||||
AND ys.sys_comp_code = u.sys_comp_code
|
||||
AND ys.stats_year = :selected_year
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT code,code_name FROM edu_codes WHERE group_code = 'CO100'
|
||||
) ec
|
||||
ON ec.code = u.belong_comp
|
||||
WHERE u.member_id = :member_id
|
||||
AND u.sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
':selected_year' => (int)$selectedYear,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelMeta = mypage_level_meta($row['learning_level'] ?? '');
|
||||
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $row['member_id'],
|
||||
'sys_comp_code' => $row['sys_comp_code'],
|
||||
'name' => $row['name'] ?? '사용자',
|
||||
'rank_name' => $row['rank_name'] ?? '',
|
||||
'belong_comp_code' => $row['code'] ?? '',
|
||||
'belong_comp_name' => $row['code_name'] ?? $comp_name,
|
||||
'working_comp' => $row['working_comp'] ?? '',
|
||||
'join_date' => $row['join_date'] ?? '',
|
||||
'latest_stats_year' => $row['stats_year'] ?? $selectedYear,
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
'total_minutes' => (int)($row['total_minutes'] ?? 0),
|
||||
'profile_image' => $profileImage, // 현재는 기본 이미지 고정
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
// ★ 선택년도 통계가 없으면 선택년도 학습이력 합계 fallback
|
||||
if ((int)$profileData['total_minutes'] === 0) {
|
||||
$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,
|
||||
]);
|
||||
|
||||
$fallbackMin = (int)$stmtFallback->fetchColumn();
|
||||
if ($fallbackMin > 0) {
|
||||
$profileData['total_minutes'] = $fallbackMin;
|
||||
}
|
||||
}
|
||||
|
||||
// 배지 조회
|
||||
$stmtBadge = $pdo->prepare("
|
||||
SELECT badge_code, issued_at
|
||||
FROM edu_user_badges
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
ORDER BY issued_at DESC, seq DESC
|
||||
");
|
||||
$stmtBadge->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmtBadge->fetchAll(PDO::FETCH_ASSOC) as $badgeRow) {
|
||||
$badgeInfo = mypage_badge_map($badgeRow['badge_code'] ?? '');
|
||||
$slot = $badgeInfo['slot'];
|
||||
|
||||
if (!isset($profileBadges[$slot]) || $profileBadges[$slot] !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profileBadges[$slot] = [
|
||||
'badge_code' => $badgeRow['badge_code'],
|
||||
'slot' => $slot,
|
||||
'img' => $badgeInfo['img'],
|
||||
'name' => $badgeInfo['name'],
|
||||
'issued_at' => $badgeRow['issued_at'],
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
//=========================
|
||||
//개인정보 + 최신연도 학습레벨 + 배지
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
if (!function_exists('mypage_h')) {
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_level_meta')) {
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'code' => 'Master',
|
||||
'class' => 'master',
|
||||
'label' => 'Master',
|
||||
'icon' => '/edu/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'code' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'label' => 'Elite',
|
||||
'icon' => '/edu/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'code' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'label' => 'Learner',
|
||||
'icon' => '/edu/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_badge_map')) {
|
||||
function mypage_badge_map(string $badgeCode): array
|
||||
{
|
||||
$badgeCode = strtoupper(trim($badgeCode));
|
||||
|
||||
$BADGE_ICON_MAP = [
|
||||
// 실제 운영 시 코드값 확정되면 여기만 교체
|
||||
'BG001' => [
|
||||
'slot' => 'hat',
|
||||
'img' => '/edu/img/mypage/ico_school.png',
|
||||
'name' => '학습 배지',
|
||||
],
|
||||
'BG002' => [
|
||||
'slot' => 'pencil',
|
||||
'img' => '/edu/img/mypage/ico_pencil.png',
|
||||
'name' => '작성 배지',
|
||||
],
|
||||
'BG003' => [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => 'Pick 배지',
|
||||
],
|
||||
];
|
||||
|
||||
return $BADGE_ICON_MAP[$badgeCode] ?? [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => '기본 배지',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'name' => '사용자',
|
||||
'rank_name' => '',
|
||||
'working_comp' => '',
|
||||
'join_date' => '',
|
||||
'latest_stats_year' => '',
|
||||
'learning_level' => 'Rookie',
|
||||
'level_class' => 'rookie',
|
||||
'level_icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
'total_minutes' => 0,
|
||||
'profile_image' => '/edu/img/insight/profile.png', // 현재는 기본 이미지 고정
|
||||
];
|
||||
|
||||
$profileBadges = [
|
||||
'hat' => null,
|
||||
'pencil' => null,
|
||||
'pick' => null,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 최신년도 학습통계 우선
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.working_comp,
|
||||
u.join_date,
|
||||
ys.stats_year,
|
||||
ys.learning_level,
|
||||
ys.total_minutes
|
||||
FROM edu_users u
|
||||
LEFT JOIN edu_yearly_learning_stats ys
|
||||
ON ys.member_id = u.member_id
|
||||
AND ys.sys_comp_code = u.sys_comp_code
|
||||
AND ys.stats_year = (
|
||||
SELECT MAX(stats_year)
|
||||
FROM edu_yearly_learning_stats
|
||||
WHERE member_id = u.member_id
|
||||
AND sys_comp_code = u.sys_comp_code
|
||||
)
|
||||
WHERE u.member_id = :member_id
|
||||
AND u.sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelMeta = mypage_level_meta($row['learning_level'] ?? '');
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $row['member_id'],
|
||||
'sys_comp_code' => $row['sys_comp_code'],
|
||||
'name' => $row['name'] ?? '사용자',
|
||||
'rank_name' => $row['rank_name'] ?? '',
|
||||
'working_comp' => $row['working_comp'] ?? '',
|
||||
'join_date' => $row['join_date'] ?? '',
|
||||
'latest_stats_year' => $row['stats_year'] ?? '',
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
'total_minutes' => (int)($row['total_minutes'] ?? 0),
|
||||
'profile_image' => '/edu/img/insight/profile.png',
|
||||
];
|
||||
}
|
||||
|
||||
// 최신통계가 없으면 누적 이력 합계 fallback
|
||||
if ((int)$profileData['total_minutes'] === 0) {
|
||||
$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
|
||||
");
|
||||
$stmtFallback->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$fallbackMin = (int)$stmtFallback->fetchColumn();
|
||||
if ($fallbackMin > 0) {
|
||||
$profileData['total_minutes'] = $fallbackMin;
|
||||
}
|
||||
}
|
||||
|
||||
// 배지 조회
|
||||
$stmtBadge = $pdo->prepare("
|
||||
SELECT badge_code, issued_at
|
||||
FROM edu_user_badges
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
ORDER BY issued_at DESC, seq DESC
|
||||
");
|
||||
$stmtBadge->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmtBadge->fetchAll(PDO::FETCH_ASSOC) as $badgeRow) {
|
||||
$badgeInfo = mypage_badge_map($badgeRow['badge_code'] ?? '');
|
||||
$slot = $badgeInfo['slot'];
|
||||
|
||||
if (!isset($profileBadges[$slot]) || $profileBadges[$slot] !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profileBadges[$slot] = [
|
||||
'badge_code' => $badgeRow['badge_code'],
|
||||
'slot' => $slot,
|
||||
'img' => $badgeInfo['img'],
|
||||
'name' => $badgeInfo['name'],
|
||||
'issued_at' => $badgeRow['issued_at'],
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
//=========================
|
||||
//개인정보 + 최신연도 학습레벨 + 배지
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
if (!function_exists('mypage_h')) {
|
||||
function mypage_h(?string $str): string
|
||||
{
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_level_meta')) {
|
||||
function mypage_level_meta(?string $level): array
|
||||
{
|
||||
$level = trim((string)$level);
|
||||
|
||||
$map = [
|
||||
'MASTER' => [
|
||||
'code' => 'Master',
|
||||
'class' => 'master',
|
||||
'label' => 'Master',
|
||||
'icon' => '/edu/img/ico/ico_level_master.svg',
|
||||
],
|
||||
'ELITE' => [
|
||||
'code' => 'Elite',
|
||||
'class' => 'elite',
|
||||
'label' => 'Elite',
|
||||
'icon' => '/edu/img/ico/ico_level_elite.svg',
|
||||
],
|
||||
'LEARNER' => [
|
||||
'code' => 'Learner',
|
||||
'class' => 'learner',
|
||||
'label' => 'Learner',
|
||||
'icon' => '/edu/img/ico/ico_level_learner.svg',
|
||||
],
|
||||
'ROOKIE' => [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
],
|
||||
];
|
||||
|
||||
$key = strtoupper($level);
|
||||
|
||||
return $map[$key] ?? [
|
||||
'code' => 'Rookie',
|
||||
'class' => 'rookie',
|
||||
'label' => 'Rookie',
|
||||
'icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mypage_badge_map')) {
|
||||
function mypage_badge_map(string $badgeCode): array
|
||||
{
|
||||
$badgeCode = strtoupper(trim($badgeCode));
|
||||
|
||||
$BADGE_ICON_MAP = [
|
||||
// 실제 운영 시 코드값 확정되면 여기만 교체
|
||||
'BG001' => [
|
||||
'slot' => 'hat',
|
||||
'img' => '/edu/img/mypage/ico_school.png',
|
||||
'name' => '학습 배지',
|
||||
],
|
||||
'BG002' => [
|
||||
'slot' => 'pencil',
|
||||
'img' => '/edu/img/mypage/ico_pencil.png',
|
||||
'name' => '작성 배지',
|
||||
],
|
||||
'BG003' => [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => 'Pick 배지',
|
||||
],
|
||||
];
|
||||
|
||||
return $BADGE_ICON_MAP[$badgeCode] ?? [
|
||||
'slot' => 'pick',
|
||||
'img' => '/edu/img/mypage/ico_pick_2.png',
|
||||
'name' => '기본 배지',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $memberId,
|
||||
'sys_comp_code' => $sysCompCode,
|
||||
'name' => '사용자',
|
||||
'rank_name' => '',
|
||||
'working_comp' => '',
|
||||
'join_date' => '',
|
||||
'latest_stats_year' => '',
|
||||
'learning_level' => 'Rookie',
|
||||
'level_class' => 'rookie',
|
||||
'level_icon' => '/edu/img/ico/ico_level_rookie.svg',
|
||||
'total_minutes' => 0,
|
||||
'profile_image' => '/edu/img/insight/profile.png', // 현재는 기본 이미지 고정
|
||||
];
|
||||
|
||||
$profileBadges = [
|
||||
'hat' => null,
|
||||
'pencil' => null,
|
||||
'pick' => null,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 최신년도 학습통계 우선
|
||||
$sql = "
|
||||
SELECT
|
||||
u.member_id,
|
||||
u.sys_comp_code,
|
||||
u.name,
|
||||
u.rank_name,
|
||||
u.working_comp,
|
||||
u.join_date,
|
||||
ys.stats_year,
|
||||
ys.learning_level,
|
||||
ys.total_minutes
|
||||
FROM edu_users u
|
||||
LEFT JOIN edu_yearly_learning_stats ys
|
||||
ON ys.member_id = u.member_id
|
||||
AND ys.sys_comp_code = u.sys_comp_code
|
||||
AND ys.stats_year = (
|
||||
SELECT MAX(stats_year)
|
||||
FROM edu_yearly_learning_stats
|
||||
WHERE member_id = u.member_id
|
||||
AND sys_comp_code = u.sys_comp_code
|
||||
)
|
||||
WHERE u.member_id = :member_id
|
||||
AND u.sys_comp_code = :sys_comp_code
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$levelMeta = mypage_level_meta($row['learning_level'] ?? '');
|
||||
|
||||
$profileData = [
|
||||
'member_id' => $row['member_id'],
|
||||
'sys_comp_code' => $row['sys_comp_code'],
|
||||
'name' => $row['name'] ?? '사용자',
|
||||
'rank_name' => $row['rank_name'] ?? '',
|
||||
'working_comp' => $row['working_comp'] ?? '',
|
||||
'join_date' => $row['join_date'] ?? '',
|
||||
'latest_stats_year' => $row['stats_year'] ?? '',
|
||||
'learning_level' => $levelMeta['label'],
|
||||
'level_class' => $levelMeta['class'],
|
||||
'level_icon' => $levelMeta['icon'],
|
||||
'total_minutes' => (int)($row['total_minutes'] ?? 0),
|
||||
'profile_image' => '/edu/img/insight/profile.png',
|
||||
];
|
||||
}
|
||||
|
||||
// 최신통계가 없으면 누적 이력 합계 fallback
|
||||
if ((int)$profileData['total_minutes'] === 0) {
|
||||
$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
|
||||
");
|
||||
$stmtFallback->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$fallbackMin = (int)$stmtFallback->fetchColumn();
|
||||
if ($fallbackMin > 0) {
|
||||
$profileData['total_minutes'] = $fallbackMin;
|
||||
}
|
||||
}
|
||||
|
||||
// 배지 조회
|
||||
$stmtBadge = $pdo->prepare("
|
||||
SELECT badge_code, issued_at
|
||||
FROM edu_user_badges
|
||||
WHERE member_id = :member_id
|
||||
AND sys_comp_code = :sys_comp_code
|
||||
ORDER BY issued_at DESC, seq DESC
|
||||
");
|
||||
$stmtBadge->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmtBadge->fetchAll(PDO::FETCH_ASSOC) as $badgeRow) {
|
||||
$badgeInfo = mypage_badge_map($badgeRow['badge_code'] ?? '');
|
||||
$slot = $badgeInfo['slot'];
|
||||
|
||||
if (!isset($profileBadges[$slot]) || $profileBadges[$slot] !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profileBadges[$slot] = [
|
||||
'badge_code' => $badgeRow['badge_code'],
|
||||
'slot' => $slot,
|
||||
'img' => $badgeInfo['img'],
|
||||
'name' => $badgeInfo['name'],
|
||||
'issued_at' => $badgeRow['issued_at'],
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 컨텐츠 제안하기 기본값 + 제안현황
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
$offerDefaultTypeCode = 'OF10001'; // 제안
|
||||
$offerDefaultStatusCode = 'OF10001'; // 검토중
|
||||
|
||||
$offerStatusMap = [
|
||||
'OF10001' => '제안',
|
||||
'OF10002' => '검토중',
|
||||
'OF10003' => '게시완료',
|
||||
'OF10004' => '게시불가',
|
||||
];
|
||||
|
||||
$offerHistory = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 상태 코드맵 동적 조회 (기본값 fallback 유지)
|
||||
$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 $codeRow) {
|
||||
$offerStatusMap[$codeRow['base_code']] = $codeRow['code_name'];
|
||||
}
|
||||
|
||||
// 제안 이력 조회
|
||||
// 정렬 우선순위:
|
||||
// 1) 검토중(OF10002) 최상단
|
||||
// 2) 그 외 상태는 코드 우선순위대로
|
||||
// 3) 같은 상태 안에서는 최신순
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
co.offer_id,
|
||||
co.type_code,
|
||||
co.title,
|
||||
co.reference_url,
|
||||
co.reason,
|
||||
co.status_code,
|
||||
co.reason_return,
|
||||
co.created_at,
|
||||
co.updated_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,
|
||||
co.offer_id DESC
|
||||
LIMIT 20
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$statusCode = trim((string) ($row['status_code'] ?? ''));
|
||||
|
||||
$offerHistory[] = [
|
||||
'offer_id' => $row['offer_id'] ?? '',
|
||||
'type_code' => $row['type_code'] ?? '',
|
||||
'title' => $row['title'] ?? '',
|
||||
'reference_url' => $row['reference_url'] ?? '',
|
||||
'reason' => $row['reason'] ?? '',
|
||||
'status_code' => $statusCode,
|
||||
'status_name' => $offerStatusMap[$statusCode] ?? $statusCode,
|
||||
'reason_return' => $row['reason_return'] ?? '',
|
||||
'created_at' => $row['created_at'] ?? '',
|
||||
'updated_at' => $row['updated_at'] ?? '',
|
||||
'created_at_dot' => !empty($row['created_at']) ? date('y.m.d', strtotime($row['created_at'])) : '',
|
||||
'is_consider' => ($statusCode === 'OF10001'),
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
// error_log($e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 컨텐츠 제안하기 기본값 + 제안현황
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$offerDefaultTypeCode = 'OF10001'; // 제안
|
||||
$offerDefaultStatusCode = 'OF10002'; // 검토중
|
||||
|
||||
$offerStatusMap = [
|
||||
'OF10001' => '제안',
|
||||
'OF10002' => '검토중',
|
||||
'OF10003' => '게시완료',
|
||||
'OF10004' => '게시불가',
|
||||
];
|
||||
|
||||
$offerHistory = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// 상태 코드맵 동적 조회 (기본값 fallback 유지)
|
||||
$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 $codeRow) {
|
||||
$offerStatusMap[$codeRow['base_code']] = $codeRow['code_name'];
|
||||
}
|
||||
|
||||
// 제안 이력 조회
|
||||
// 정렬 우선순위:
|
||||
// 1) 검토중(OF10002) 최상단
|
||||
// 2) 그 외 상태는 코드 우선순위대로
|
||||
// 3) 같은 상태 안에서는 최신순
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
co.offer_id,
|
||||
co.type_code,
|
||||
co.title,
|
||||
co.reference_url,
|
||||
co.reason,
|
||||
co.status_code,
|
||||
co.created_at,
|
||||
co.updated_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,
|
||||
co.offer_id DESC
|
||||
LIMIT 20
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$statusCode = trim((string)($row['status_code'] ?? ''));
|
||||
|
||||
$offerHistory[] = [
|
||||
'offer_id' => $row['offer_id'] ?? '',
|
||||
'type_code' => $row['type_code'] ?? '',
|
||||
'title' => $row['title'] ?? '',
|
||||
'reference_url' => $row['reference_url'] ?? '',
|
||||
'reason' => $row['reason'] ?? '',
|
||||
'status_code' => $statusCode,
|
||||
'status_name' => $offerStatusMap[$statusCode] ?? $statusCode,
|
||||
'created_at' => $row['created_at'] ?? '',
|
||||
'updated_at' => $row['updated_at'] ?? '',
|
||||
'created_at_dot' => !empty($row['created_at']) ? date('y.m.d', strtotime($row['created_at'])) : '',
|
||||
'is_consider' => ($statusCode === 'OF10002'),
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
// error_log($e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
//=========================
|
||||
// 한줄소감
|
||||
// 동적변환처리로 인해 해당파일 사용안함 230324
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$memberId = $_SESSION['member_id'] ?? '';
|
||||
$sysCompCode = $_SESSION['sys_comp_code'] ?? '';
|
||||
//$memberId = 'U001';
|
||||
//$sysCompCode = 'COMP01';
|
||||
|
||||
$myCommentList = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
lh.content_id,
|
||||
lh.comment,
|
||||
lh.last_viewed_at,
|
||||
c.title,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
u.name
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
INNER JOIN edu_users u
|
||||
ON u.member_id = lh.member_id
|
||||
AND u.sys_comp_code = lh.sys_comp_code
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :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
|
||||
LIMIT 20
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$myCommentList[] = [
|
||||
'content_id' => $row['content_id'],
|
||||
'title' => $row['title'],
|
||||
'comment' => $row['comment'],
|
||||
'member_name' => $row['name'],
|
||||
'last_viewed_at' => $row['last_viewed_at'],
|
||||
'date_dot' => !empty($row['last_viewed_at']) ? date('y.m.d', strtotime($row['last_viewed_at'])) : '',
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
//=========================
|
||||
//한줄소감
|
||||
//=========================
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
// TODO: 실제 로그인 세션 연동 후 교체
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
$myCommentList = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
lh.content_id,
|
||||
lh.comment,
|
||||
lh.last_viewed_at,
|
||||
c.title,
|
||||
c.category_code,
|
||||
c.category_group,
|
||||
u.name
|
||||
FROM edu_learning_histories lh
|
||||
INNER JOIN edu_contents c
|
||||
ON c.content_id = lh.content_id
|
||||
INNER JOIN edu_users u
|
||||
ON u.member_id = lh.member_id
|
||||
AND u.sys_comp_code = lh.sys_comp_code
|
||||
WHERE lh.member_id = :member_id
|
||||
AND lh.sys_comp_code = :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
|
||||
LIMIT 20
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':member_id' => $memberId,
|
||||
':sys_comp_code' => $sysCompCode,
|
||||
]);
|
||||
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$myCommentList[] = [
|
||||
'content_id' => $row['content_id'],
|
||||
'title' => $row['title'],
|
||||
'comment' => $row['comment'],
|
||||
'member_name' => $row['name'],
|
||||
'last_viewed_at' => $row['last_viewed_at'],
|
||||
'date_dot' => !empty($row['last_viewed_at']) ? date('y.m.d', strtotime($row['last_viewed_at'])) : '',
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 운영 시 로깅 권장
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
require_once __DIR__ . '/api_log.php';
|
||||
|
||||
// 외부(호출하는 파일)에서 $SET_PREFIX를 정의하지 않았을 경우를 대비한 기본값 설정
|
||||
$prefixCondition = isset($SET_PREFIX) ? $SET_PREFIX : 'L'; //L=리더십(기본값), I=인사이트
|
||||
|
||||
$memberId = 'U001';
|
||||
$sysCompCode = 'COMP01';
|
||||
|
||||
//Svg
|
||||
if($prefixCondition=='L'){
|
||||
$prefixSvg = "leadership";
|
||||
}else if($prefixCondition=='I'){
|
||||
$prefixSvg = "insight";
|
||||
}
|
||||
|
||||
//배너설정
|
||||
if($prefixCondition=='L'){//L=리더십(기본값), I=인사이트
|
||||
$array_banner_img = [
|
||||
['normal' => '/edu/img/leadership/img_banner_01.png', 'mobile' => '/edu/img/leadership/img_banner_01_m.png', 'title' => '실천으로 완성하는 리더십'],
|
||||
['normal' => '/edu/img/leadership/img_banner_02.png', 'mobile' => '/edu/img/leadership/img_banner_02_m.png', 'title' => '성장하는 리더십 여정'],
|
||||
['normal' => '/edu/img/leadership/img_banner_03.png', 'mobile' => '/edu/img/leadership/img_banner_03_m.png', 'title' => '리더로 성장하는 과정'],
|
||||
];
|
||||
}else if($prefixCondition=='I'){//L=리더십(기본값), I=인사이트
|
||||
$array_banner_img = [
|
||||
['normal' => '/edu/img/insight/img_banner_01.png', 'mobile' => '/edu/img/insight/img_banner_01_m.png', 'title' => '배너 1'],
|
||||
['normal' => '/edu/img/insight/img_banner_02.png', 'mobile' => '/edu/img/insight/img_banner_02_m.png', 'title' => '배너 2'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$dbSuccess = false;
|
||||
$array_tab_info = [];
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->exec("SET NAMES 'utf8mb4'");
|
||||
|
||||
// LIKE 연산자를 안전하게 처리하기 위해 준비된 선언(Prepared Statement) 사용
|
||||
$stmtCode = $pdo->prepare("
|
||||
SELECT group_code, base_code, code, code_name
|
||||
FROM edu_codes
|
||||
WHERE is_active = 1
|
||||
AND group_code = 'CA200'
|
||||
AND code LIKE :prefix
|
||||
ORDER BY base_code ASC
|
||||
");
|
||||
|
||||
// 'L%' 형태로 바인딩하여 검색
|
||||
$stmtCode->execute([':prefix' => $prefixCondition . '%']);
|
||||
$rows = $stmtCode->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($rows) {
|
||||
$i = 1;
|
||||
foreach ($rows as $cr) {
|
||||
$index = sprintf('%02d', $i);
|
||||
$index_src = '/edu/img/ico/ico_'.$prefixSvg.'_' . $index . '.svg';
|
||||
|
||||
$array_tab_info[] = [
|
||||
'base_code' => $cr['base_code'],
|
||||
'code_name' => $cr['code_name'],
|
||||
'src' => $index_src
|
||||
];
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
$dbSuccess = true;
|
||||
} catch (Exception $e) {
|
||||
$errMsg = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
||||
error_log('[leadership_init_data.php] DB Error: ' . $errMsg);
|
||||
// api_log 함수가 있다면 기록
|
||||
if (function_exists('api_log')) {
|
||||
api_log('leadership_init', 'DB_ERROR', ['error' => $errMsg]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* sync_users.php - 낮 12시, 밤 12시 자동 실행용
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/db_conn.php';
|
||||
|
||||
try {
|
||||
$pdo = db_conn();
|
||||
|
||||
// 1. 현재 우리 DB에 등록된 전체 사용자 ID 가져오기
|
||||
$stmt = $pdo->query("SELECT member_id FROM edu_users WHERE is_active = 'Y'");
|
||||
$members = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
echo "[" . date('Y-m-d H:i:s') . "] 동기화 시작 (대상: " . count($members) . "명)\n";
|
||||
|
||||
foreach ($members as $mid) {
|
||||
// 2. 인트라넷 API 호출
|
||||
$apiUrl = "http://erp.hanmaceng.co.kr/intranet/sys/model/EduJsonAPI.php?member_id=" . urlencode($mid);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response) {
|
||||
$apiData = json_decode($response, true);
|
||||
if (($apiData['rstCd'] ?? '') == 200 && !empty($apiData['data'])) {
|
||||
$userData = $apiData['data'][0];
|
||||
$name = $userData['NM'] ?? $userData['name']; // 실제 API 키값 확인 필요
|
||||
$rank = $userData['RANK_NM'] ?? $userData['rank_name'];
|
||||
|
||||
// 3. 정보 업데이트 (이름이나 직책이 변경되었을 수 있으니 갱신)
|
||||
$upd = $pdo->prepare("UPDATE edu_users SET name = ?, rank_name = ?, updated_at = NOW() WHERE member_id = ?");
|
||||
$upd->execute([$name, $rank, $mid]);
|
||||
|
||||
echo " - ID {$mid}: 업데이트 완료 ({$name} {$rank})\n";
|
||||
}
|
||||
}
|
||||
// 서버 부하 방지를 위해 0.1초씩 쉬어줌
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
echo "전체 동기화 완료!\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("[Sync Error] " . $e->getMessage());
|
||||
echo "오류 발생: " . $e->getMessage();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
|
||||
// 트래픽 낭비를 막기 위해 단 1분 30초만 짧게 테스트합니다.
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '30s', target: 30 }, // 30초 동안 동시 접속자를 30명까지 올림
|
||||
{ duration: '30s', target: 30 }, // 30명 동시 접속 상태를 30초 유지 (이때 에러가 나는지 집중 확인!)
|
||||
{ duration: '30s', target: 0 }, // 30초 동안 접속 서서히 종료
|
||||
],
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const baseUrl = 'https://baroncs.co.kr/edu/skin/index.php'; // 실제 서비스 도메인으로 변경
|
||||
|
||||
// [Point] 불필요한 하위 페이지는 빼고, 쿼리가 가장 많이 도는 메인 페이지만 타격합니다.
|
||||
let res = http.get(`${baseUrl}/`);
|
||||
|
||||
check(res, {
|
||||
'정상 접속(200)': (r) => r.status === 200,
|
||||
'빠른 응답(<1초)': (r) => r.timings.duration < 1000,
|
||||
});
|
||||
|
||||
// [Point] 방화벽 디도스(DDoS) 영구 차단을 피하기 위해 요청 간격을 여유 있게 줍니다.
|
||||
sleep(Math.random() * 4 + 2); // 2~6초 사이 랜덤 대기
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
|
||||
// 트래픽 낭비를 막기 위해 단 1분 30초만 짧게 테스트합니다.
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '30s', target: 30 }, // 30초 동안 동시 접속자를 30명까지 올림
|
||||
{ duration: '30s', target: 30 }, // 30명 동시 접속 상태를 30초 유지 (이때 에러가 나는지 집중 확인!)
|
||||
{ duration: '30s', target: 0 }, // 30초 동안 접속 서서히 종료
|
||||
],
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const baseUrl = 'https://domain.cafe24.com'; // 실제 서비스 도메인으로 변경
|
||||
|
||||
// [Point] 불필요한 하위 페이지는 빼고, 쿼리가 가장 많이 도는 메인 페이지만 타격합니다.
|
||||
let res = http.get(`${baseUrl}/`);
|
||||
|
||||
check(res, {
|
||||
'정상 접속(200)': (r) => r.status === 200,
|
||||
'빠른 응답(<1초)': (r) => r.timings.duration < 1000,
|
||||
});
|
||||
|
||||
// [Point] 방화벽 디도스(DDoS) 영구 차단을 피하기 위해 요청 간격을 여유 있게 줍니다.
|
||||
sleep(Math.random() * 4 + 2); // 2~6초 사이 랜덤 대기
|
||||
}
|
||||
Reference in New Issue
Block a user