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