Files
edu/bbs/api/save_learning.php
T

297 lines
11 KiB
PHP

<?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);
}