Initial import
This commit is contained in:
@@ -0,0 +1,741 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db.php';
|
||||
|
||||
function getCurricula() {
|
||||
return getDB()->query("SELECT * FROM curricula ORDER BY code")->fetchAll();
|
||||
}
|
||||
|
||||
function getCurriculum($id) {
|
||||
$stmt = getDB()->prepare("SELECT * FROM curricula WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
function getCourseGroups($curriculum_id, $parent_id = null) {
|
||||
if ($parent_id === null) {
|
||||
$stmt = getDB()->prepare("SELECT * FROM course_groups WHERE curriculum_id = ? AND parent_id IS NULL ORDER BY sort_order");
|
||||
$stmt->execute([$curriculum_id]);
|
||||
} else {
|
||||
$stmt = getDB()->prepare("SELECT * FROM course_groups WHERE parent_id = ? ORDER BY sort_order");
|
||||
$stmt->execute([$parent_id]);
|
||||
}
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function getCoursesByGroup($group_id) {
|
||||
$stmt = getDB()->prepare("SELECT * FROM courses WHERE group_id = ? ORDER BY sort_order");
|
||||
$stmt->execute([$group_id]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function getStudents($curriculum_id = null) {
|
||||
if ($curriculum_id === null && isStudent()) {
|
||||
$stmt = getDB()->prepare("
|
||||
SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
|
||||
FROM students s
|
||||
JOIN curricula c ON s.curriculum_id = c.id
|
||||
WHERE s.id = ?
|
||||
ORDER BY s.student_code
|
||||
");
|
||||
$stmt->execute([getCurrentUserId()]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
if ($curriculum_id === null && isAdvisor()) {
|
||||
$stmt = getDB()->prepare("
|
||||
SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
|
||||
FROM students s
|
||||
JOIN curricula c ON s.curriculum_id = c.id
|
||||
WHERE s.advisor_id = ?
|
||||
ORDER BY s.student_code
|
||||
");
|
||||
$stmt->execute([getCurrentUserId()]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
if ($curriculum_id) {
|
||||
$stmt = getDB()->prepare("
|
||||
SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
|
||||
FROM students s
|
||||
JOIN curricula c ON s.curriculum_id = c.id
|
||||
WHERE s.curriculum_id = ?
|
||||
ORDER BY s.student_code
|
||||
");
|
||||
$stmt->execute([$curriculum_id]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return getDB()->query("
|
||||
SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
|
||||
FROM students s
|
||||
JOIN curricula c ON s.curriculum_id = c.id
|
||||
ORDER BY s.student_code
|
||||
")->fetchAll();
|
||||
}
|
||||
|
||||
function getStudent($id) {
|
||||
$stmt = getDB()->prepare("
|
||||
SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name, c.total_credits
|
||||
FROM students s
|
||||
JOIN curricula c ON s.curriculum_id = c.id
|
||||
WHERE s.id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
function getFacultyList() {
|
||||
return [
|
||||
'12' => 'คณะนวัตกรรมดิจิทัลเทคโนโลยี',
|
||||
'11' => 'คณะบริหารธุรกิจ',
|
||||
'13' => 'คณะศึกษาศาสตร์และศิลปศาสตร์',
|
||||
'14' => 'คณะบัญชี',
|
||||
'15' => 'คณะนิติศาสตร์และรัฐศาสตร์',
|
||||
];
|
||||
}
|
||||
|
||||
function getTransferCreditsTotal($student_id) {
|
||||
$stmt = getDB()->prepare("SELECT COALESCE(SUM(credits), 0) FROM student_courses WHERE student_id = ? AND source_type = 'transfer'");
|
||||
$stmt->execute([$student_id]);
|
||||
return floatval($stmt->fetchColumn());
|
||||
}
|
||||
|
||||
function getCompletedCreditsTotal($student_id) {
|
||||
$stmt = getDB()->prepare("SELECT COALESCE(SUM(credits), 0) FROM student_courses WHERE student_id = ?");
|
||||
$stmt->execute([$student_id]);
|
||||
return floatval($stmt->fetchColumn());
|
||||
}
|
||||
|
||||
function getStudentCourses($student_id) {
|
||||
$stmt = getDB()->prepare("
|
||||
SELECT sc.*, c.code AS curriculum_course_code
|
||||
FROM student_courses sc
|
||||
LEFT JOIN courses c ON sc.course_id = c.id
|
||||
WHERE sc.student_id = ?
|
||||
ORDER BY sc.course_code
|
||||
");
|
||||
$stmt->execute([$student_id]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function getStudentCoursesByGroup($student_id, $group_id) {
|
||||
$stmt = getDB()->prepare("
|
||||
SELECT sc.* FROM student_courses sc
|
||||
JOIN courses c ON sc.course_id = c.id
|
||||
WHERE sc.student_id = ? AND c.group_id = ?
|
||||
");
|
||||
$stmt->execute([$student_id, $group_id]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build recursive tree of course groups for a curriculum
|
||||
*/
|
||||
function buildGroupTree($curriculum_id, $parent_id = null) {
|
||||
$groups = getCourseGroups($curriculum_id, $parent_id);
|
||||
$tree = [];
|
||||
foreach ($groups as $g) {
|
||||
$children = buildGroupTree($curriculum_id, $g['id']);
|
||||
$courses = getCoursesByGroup($g['id']);
|
||||
$node = [
|
||||
'group' => $g,
|
||||
'children' => $children,
|
||||
'courses' => $courses,
|
||||
'total_credits' => 0,
|
||||
'completed_credits' => 0,
|
||||
];
|
||||
// Calculate total credits from courses
|
||||
foreach ($courses as $c) {
|
||||
$node['total_credits'] += $c['credits'];
|
||||
}
|
||||
// Add children totals
|
||||
foreach ($children as $ch) {
|
||||
$node['total_credits'] += $ch['total_credits'];
|
||||
}
|
||||
$tree[] = $node;
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
function getStudentSummary($student_id) {
|
||||
$student = getStudent($student_id);
|
||||
if (!$student) return null;
|
||||
|
||||
$tree = buildGroupTree($student['curriculum_id']);
|
||||
|
||||
$student_courses = getStudentCourses($student_id);
|
||||
$completed_map = [];
|
||||
foreach ($student_courses as $sc) {
|
||||
$completed_map[$sc['course_code']] = $sc;
|
||||
}
|
||||
|
||||
// Mark completed courses recursively
|
||||
$total_completed = 0;
|
||||
foreach ($student_courses as $sc) {
|
||||
$total_completed += $sc['credits'];
|
||||
}
|
||||
|
||||
$markCompleted = function(&$nodes) use (&$markCompleted, $completed_map) {
|
||||
foreach ($nodes as &$node) {
|
||||
foreach ($node['courses'] as &$course) {
|
||||
if (isset($completed_map[$course['code']])) {
|
||||
$course['_completed'] = $completed_map[$course['code']];
|
||||
$node['completed_credits'] += $course['credits'];
|
||||
}
|
||||
}
|
||||
if (!empty($node['children'])) {
|
||||
$markCompleted($node['children']);
|
||||
// Sum children completed credits
|
||||
foreach ($node['children'] as $ch) {
|
||||
$node['completed_credits'] += $ch['completed_credits'];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
$markCompleted($tree);
|
||||
|
||||
return [
|
||||
'student' => $student,
|
||||
'groups' => $tree,
|
||||
'total_completed' => $total_completed,
|
||||
'student_courses' => $student_courses,
|
||||
'completed_map' => $completed_map
|
||||
];
|
||||
}
|
||||
|
||||
function findCourseInCurriculum($curriculum_id, $code) {
|
||||
$stmt = getDB()->prepare("
|
||||
SELECT c.* FROM courses c
|
||||
JOIN course_groups cg ON c.group_id = cg.id
|
||||
WHERE cg.curriculum_id = ? AND c.code = ?
|
||||
");
|
||||
$stmt->execute([$curriculum_id, $code]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
function getGroupSelectOptions($curriculum_id, $selected_id = null, $parent_id = null, $prefix = '') {
|
||||
$groups = getCourseGroups($curriculum_id, $parent_id);
|
||||
$html = '';
|
||||
foreach ($groups as $g) {
|
||||
$sel = $g['id'] == $selected_id ? ' selected' : '';
|
||||
$html .= '<option value="' . $g['id'] . '"' . $sel . '>' . $prefix . htmlspecialchars($g['name_th']) . '</option>';
|
||||
$html .= getGroupSelectOptions($curriculum_id, $selected_id, $g['id'], $prefix . '— ');
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
function getGradeOptions($selected = null) {
|
||||
$grades = ['A', 'B+', 'B', 'C+', 'C', 'D+', 'D', 'F', 'S', 'U', 'กำลังเรียน'];
|
||||
$html = '';
|
||||
foreach ($grades as $g) {
|
||||
$sel = $g === $selected ? ' selected' : '';
|
||||
$html .= '<option value="' . $g . '"' . $sel . '>' . $g . '</option>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
function getCourseTypeOptions($selected = null) {
|
||||
$types = [
|
||||
'เรียนปกติ' => 'เรียนปกติ',
|
||||
'เทียบโอนรายวิชา' => 'เทียบโอนรายวิชา',
|
||||
'เทียบโอนกลุ่มวิชา' => 'เทียบโอนกลุ่มวิชา',
|
||||
'กิจกรรม/สหกิจศึกษา' => 'กิจกรรม/สหกิจศึกษา',
|
||||
'ฝึกอบรม' => 'ฝึกอบรม',
|
||||
];
|
||||
$html = '';
|
||||
foreach ($types as $val => $label) {
|
||||
$sel = $val === $selected ? ' selected' : '';
|
||||
$html .= '<option value="' . $val . '"' . $sel . '>' . $label . '</option>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
function getTotalCurriculumCredits($curriculum_id) {
|
||||
$stmt = getDB()->prepare("SELECT total_credits FROM curricula WHERE id = ?");
|
||||
$stmt->execute([$curriculum_id]);
|
||||
$r = $stmt->fetch();
|
||||
return $r ? $r['total_credits'] : 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Access Control Helpers
|
||||
// ============================================================
|
||||
|
||||
function requireStudentAccess($student_id) {
|
||||
if (isAdmin()) return;
|
||||
if (isStudent() && getCurrentUserId() == $student_id) return;
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT advisor_id, curriculum_id FROM students WHERE id = ?");
|
||||
$stmt->execute([$student_id]);
|
||||
$student = $stmt->fetch();
|
||||
if (!$student || $student['advisor_id'] != getCurrentUserId()) {
|
||||
header('Location: ' . BASE_URL . '/index.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// User Management Functions
|
||||
// ============================================================
|
||||
|
||||
function getUsers() {
|
||||
return getDB()->query("SELECT u.*, c.code AS curriculum_code, c.name_th AS curriculum_name FROM users u LEFT JOIN curricula c ON u.curriculum_id = c.id ORDER BY u.staff_code")->fetchAll();
|
||||
}
|
||||
|
||||
function getAdvisors() {
|
||||
return getDB()->query("SELECT u.id, u.name, u.staff_code, c.code AS curriculum_code FROM users u LEFT JOIN curricula c ON u.curriculum_id = c.id WHERE u.role = 'advisor' ORDER BY u.name")->fetchAll();
|
||||
}
|
||||
|
||||
function getUser($id) {
|
||||
$stmt = getDB()->prepare("SELECT u.*, c.code AS curriculum_code, c.name_th AS curriculum_name FROM users u LEFT JOIN curricula c ON u.curriculum_id = c.id WHERE u.id = ?");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
function authenticateUser($staff_code, $password) {
|
||||
$stmt = getDB()->prepare("SELECT * FROM users WHERE staff_code = ?");
|
||||
$stmt->execute([$staff_code]);
|
||||
$user = $stmt->fetch();
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
return $user;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PDF Parsing Functions
|
||||
// ============================================================
|
||||
|
||||
function extractTextFromPDF($filepath) {
|
||||
$text = trySmalotParser($filepath);
|
||||
if (!empty(trim($text))) return $text;
|
||||
|
||||
$text = tryPdftotext($filepath);
|
||||
if (!empty(trim($text))) return $text;
|
||||
|
||||
$text = tryBasicPdfParse($filepath);
|
||||
if (!empty(trim($text))) return $text;
|
||||
|
||||
return tryRawPdfRead($filepath);
|
||||
}
|
||||
|
||||
function trySmalotParser($filepath) {
|
||||
if (!class_exists('Smalot\\PdfParser\\Parser')) {
|
||||
spl_autoload_register(function ($class) {
|
||||
$prefix = 'Smalot\\PdfParser\\';
|
||||
$base_dir = __DIR__ . '/../vendor/smalot/pdfparser/src/';
|
||||
if (strncmp($prefix, $class, strlen($prefix)) === 0) {
|
||||
$file = $base_dir . str_replace('\\', '/', $class) . '.php';
|
||||
if (file_exists($file)) { require $file; }
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
$parser = new Smalot\PdfParser\Parser();
|
||||
$pdf = $parser->parseFile($filepath);
|
||||
return $pdf->getText();
|
||||
} catch (Throwable $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function tryPdftotext($filepath) {
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||
$exe_paths = [
|
||||
'C:\xampp\htdocs\Transfer\vendor\pdftotext.exe',
|
||||
'pdftotext',
|
||||
'C:\Program Files\pdftotext\pdftotext.exe',
|
||||
];
|
||||
} else {
|
||||
$exe_paths = [
|
||||
'pdftotext',
|
||||
'/usr/bin/pdftotext'
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($exe_paths as $exe) {
|
||||
$output = [];
|
||||
$cmd = escapeshellcmd($exe) . ' ' . escapeshellarg($filepath) . ' -';
|
||||
@exec($cmd, $output, $return_var);
|
||||
if ($return_var === 0 && !empty(implode('', $output))) {
|
||||
return implode("\n", $output);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function tryBasicPdfParse($filepath) {
|
||||
$content = file_get_contents($filepath);
|
||||
if ($content === false) return '';
|
||||
|
||||
$text = '';
|
||||
$offset = 0;
|
||||
|
||||
while (($start = strpos($content, 'stream', $offset)) !== false) {
|
||||
$stream_start = $start + 6;
|
||||
// Skip leading whitespace (usually CR/LF or LF) after 'stream'
|
||||
while ($stream_start < strlen($content) && ctype_space($content[$stream_start])) {
|
||||
$stream_start++;
|
||||
}
|
||||
|
||||
$end = strpos($content, 'endstream', $stream_start);
|
||||
if ($end === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
$stream = substr($content, $stream_start, $end - $stream_start);
|
||||
$stream = rtrim($stream);
|
||||
|
||||
$decoded = @gzuncompress($stream);
|
||||
if ($decoded === false) {
|
||||
$decoded = @gzuncompress(trim($stream));
|
||||
}
|
||||
|
||||
if ($decoded !== false) {
|
||||
if (strlen($decoded) > 100000) continue;
|
||||
preg_match_all('/\((.*?)\)\s*Tj/s', $decoded, $text_matches);
|
||||
$text .= implode(' ', $text_matches[1]) . "\n";
|
||||
|
||||
preg_match_all('/\[(.*?)\]\s*TJ/s', $decoded, $tj_matches);
|
||||
foreach ($tj_matches[1] as $tj) {
|
||||
preg_match_all('/\((.*?)\)/', $tj, $tj_text);
|
||||
$text .= implode('', $tj_text[1]) . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
$offset = $end + 9;
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
function tryRawPdfRead($filepath) {
|
||||
$content = file_get_contents($filepath);
|
||||
if ($content === false) return '';
|
||||
|
||||
// Cap binary parsing to prevent CPU exhaustion on very large or corrupt files
|
||||
if (strlen($content) > 500000) {
|
||||
$content = substr($content, 0, 500000);
|
||||
}
|
||||
|
||||
$text = '';
|
||||
$len = strlen($content);
|
||||
$in_paren = false;
|
||||
$buffer = '';
|
||||
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$char = $content[$i];
|
||||
if ($char === '(') {
|
||||
$in_paren = true;
|
||||
$buffer = '';
|
||||
} else if ($char === ')') {
|
||||
if ($in_paren) {
|
||||
if (strlen($buffer) > 0 && strlen($buffer) <= 200) {
|
||||
$text .= $buffer . "\n";
|
||||
}
|
||||
$in_paren = false;
|
||||
}
|
||||
} else if ($in_paren) {
|
||||
$buffer .= $char;
|
||||
if (strlen($buffer) > 200) {
|
||||
$in_paren = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
function parseCoursesFromText($text) {
|
||||
$courses = [];
|
||||
|
||||
// Try Pattern A: 9-digit code (transfer PDF format with multi-line text)
|
||||
// Strip first 2 digits for 7-digit curriculum code
|
||||
$courses = parseTransferPdfCourses($text);
|
||||
|
||||
// Fallback: Standard 7-digit code format
|
||||
if (empty($courses)) {
|
||||
$courses = parseStandardCourses($text);
|
||||
}
|
||||
|
||||
return $courses;
|
||||
}
|
||||
|
||||
function parseTransferPdfCourses($text) {
|
||||
$courses = [];
|
||||
$lines = explode("\n", $text);
|
||||
|
||||
$lines = array_map(function($l) {
|
||||
return str_replace("\t", " ", trim($l));
|
||||
}, $lines);
|
||||
$lines = array_values(array_filter($lines, function($l) {
|
||||
return $l !== '';
|
||||
}));
|
||||
|
||||
$current_code = '';
|
||||
$current_name_parts = [];
|
||||
|
||||
$flushCourse = function() use (&$current_code, &$current_name_parts, &$courses) {
|
||||
if ($current_code && !empty($current_name_parts)) {
|
||||
$courses[] = finalizeTransferCourse($current_code, $current_name_parts);
|
||||
$current_code = '';
|
||||
$current_name_parts = [];
|
||||
}
|
||||
};
|
||||
|
||||
for ($i = 0; $i < count($lines); $i++) {
|
||||
$line = $lines[$i];
|
||||
|
||||
// Single regex: optional seq# (1-2 digits with optional trailing space) + 9-digit code + rest
|
||||
$m = [];
|
||||
if (preg_match('/^(\d{1,2})?\s*(\d{9})(.*)$/', $line, $m)) {
|
||||
$pdf_code = $m[2];
|
||||
$after_code = trim($m[3]);
|
||||
|
||||
if ($current_code) {
|
||||
$courses[] = finalizeTransferCourse($current_code, $current_name_parts);
|
||||
$current_code = '';
|
||||
$current_name_parts = [];
|
||||
}
|
||||
|
||||
$current_code = $pdf_code;
|
||||
$current_name_parts = [];
|
||||
|
||||
if ($after_code !== '') {
|
||||
if (preg_match('/^(.+?)\s+(\d+\.\d)\s*$/', $after_code, $nm)) {
|
||||
$current_name_parts[] = trim($nm[1]);
|
||||
$courses[] = finalizeTransferCourse($current_code, $current_name_parts, floatval($nm[2]));
|
||||
$current_code = '';
|
||||
$current_name_parts = [];
|
||||
} else {
|
||||
$current_name_parts[] = $after_code;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($current_code) {
|
||||
if (preg_match('/^(\d+\.\d)\s*$/', $line, $cm)) {
|
||||
$courses[] = finalizeTransferCourse($current_code, $current_name_parts, floatval($cm[1]));
|
||||
$current_code = '';
|
||||
$current_name_parts = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/^(.+?)\s+(\d+\.\d)\s*$/', $line, $nm)) {
|
||||
$current_name_parts[] = trim($nm[1]);
|
||||
$courses[] = finalizeTransferCourse($current_code, $current_name_parts, floatval($nm[2]));
|
||||
$current_code = '';
|
||||
$current_name_parts = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
$current_name_parts[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
if ($current_code && !empty($current_name_parts)) {
|
||||
$courses[] = finalizeTransferCourse($current_code, $current_name_parts);
|
||||
}
|
||||
|
||||
return $courses;
|
||||
}
|
||||
|
||||
function finalizeTransferCourse($pdf_code, $name_parts, $credits = null) {
|
||||
// Clean the name: remove extra whitespace, collapsed newlines
|
||||
$name = preg_replace('/\s+/', ' ', implode(' ', $name_parts));
|
||||
$name = trim($name);
|
||||
// Remove trailing orphan characters like "-"
|
||||
$name = rtrim($name, ' -');
|
||||
|
||||
// Strip first 2 digits from 9-digit code to get 7-digit curriculum code
|
||||
$curriculum_code = substr($pdf_code, 2);
|
||||
|
||||
// If credits not specified, try to find a number in the name
|
||||
if ($credits === null) {
|
||||
if (preg_match('/\b(\d+\.\d)\b/', $name, $cm)) {
|
||||
$credits = floatval($cm[1]);
|
||||
$name = trim(str_replace($cm[0], '', $name));
|
||||
} else {
|
||||
$credits = 3.0; // default
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => $curriculum_code,
|
||||
'name_th' => $name,
|
||||
'credits' => $credits,
|
||||
'lecture' => 0,
|
||||
'practice' => 0,
|
||||
'self_study' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
function parseStandardCourses($text) {
|
||||
$courses = [];
|
||||
|
||||
// Pattern: Standard format "0001101 จิตวิทยาทั่วไป 3(3-0-6)"
|
||||
preg_match_all('/(\d{7})\s+([^\d]+?)\s+(\d+(?:\.\d)?)\s*\(\s*(\d+)\s*[-–]\s*(\d+)\s*[-–]\s*(\d+)\s*\)/', $text, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $m) {
|
||||
$courses[] = [
|
||||
'code' => trim($m[1]),
|
||||
'name_th' => trim($m[2]),
|
||||
'credits' => floatval($m[3]),
|
||||
'lecture' => intval($m[4]),
|
||||
'practice' => intval($m[5]),
|
||||
'self_study' => intval($m[6]),
|
||||
];
|
||||
}
|
||||
|
||||
// Pattern: Simple format "0001101 จิตวิทยาทั่วไป 3"
|
||||
if (empty($courses)) {
|
||||
preg_match_all('/(\d{7})\s+([^\d]{2,80}?)\s+(\d+(?:\.\d)?)(?:\s|$)/', $text, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $m) {
|
||||
$courses[] = [
|
||||
'code' => trim($m[1]),
|
||||
'name_th' => trim($m[2]),
|
||||
'credits' => floatval($m[3]),
|
||||
'lecture' => 0, 'practice' => 0, 'self_study' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $courses;
|
||||
}
|
||||
|
||||
function getStudentTransferPdfPath($student_id) {
|
||||
$stmt = getDB()->prepare("SELECT DISTINCT source_file FROM student_courses WHERE student_id = ? AND source_type = 'transfer' AND source_file IS NOT NULL LIMIT 1");
|
||||
$stmt->execute([$student_id]);
|
||||
$filename = $stmt->fetchColumn();
|
||||
if ($filename) {
|
||||
return __DIR__ . '/../uploads/' . $filename;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseTransferReportFromPdf($filepath) {
|
||||
if (!$filepath || !file_exists($filepath)) return null;
|
||||
|
||||
// Register autoloader for Smalot Parser just in case it is not registered yet
|
||||
spl_autoload_register(function ($class) {
|
||||
$prefix = 'Smalot\\PdfParser\\';
|
||||
$base_dir = __DIR__ . '/../vendor/smalot/pdfparser/src/';
|
||||
if (strncmp($prefix, $class, strlen($prefix)) === 0) {
|
||||
$file = $base_dir . str_replace('\\', '/', $class) . '.php';
|
||||
if (file_exists($file)) { require $file; }
|
||||
}
|
||||
});
|
||||
|
||||
$text = extractTextFromPDF($filepath);
|
||||
$lines = explode("\n", $text);
|
||||
|
||||
$pattern = '/^(?:(\d+)\s+)?(\d*)(\d{9})\s*(.+?)\s+(\d+\.\d)\s+(\d{5}-\d{4})\s*(.+?)\s+(\d+(?:\.\d)?)\s+([A-F][+-]?|[0-4](?:\.[0-9])?|S|U)\s*(\d+(?:\.\d+)?%)/u';
|
||||
$source_pattern = '/^(\d{5}-\d{4})\s*(.+?)\s+(\d+(?:\.\d)?)\s+([A-F][+-]?|[0-4](?:\.[0-9])?|S|U)\s*(\d+(?:\.\d+)?%)/u';
|
||||
$target_only_pattern = '/^(?:(\d+)\s+)?(\d*)(\d{9})\s*(.+?)\s+(\d+\.\d)\s*$/u';
|
||||
$untransferable_pattern = '/^(?:(\d+)\s+)?(\d*)(\d{5}-\d{4})\s*(.+?)\s+(\d+(?:\.\d)?)\s+([A-F][+-]?|[0-4](?:\.[0-9])?|[ก-๙]\.?|S|U|W|I)\s*(.+)$/u';
|
||||
|
||||
$results = [];
|
||||
$untransferable = [];
|
||||
|
||||
$start_transfer = false;
|
||||
$start_untransfer = false;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') continue;
|
||||
|
||||
if (mb_strpos($line, 'รายละเอียดวิชาที่เทียบโอนได้') !== false) {
|
||||
$start_transfer = true;
|
||||
$start_untransfer = false;
|
||||
continue;
|
||||
}
|
||||
if (mb_strpos($line, 'วิชาต้นทางที่ไม่สามารถเทียบโอนได้') !== false) {
|
||||
$start_transfer = false;
|
||||
$start_untransfer = true;
|
||||
continue;
|
||||
}
|
||||
if (mb_strpos($line, 'ผู้ประเมิน') !== false || mb_strpos($line, '(............................................)') !== false) {
|
||||
$start_transfer = false;
|
||||
$start_untransfer = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($start_transfer) {
|
||||
if (preg_match($pattern, $line, $m)) {
|
||||
$seq = !empty($m[1]) ? $m[1] : (!empty($m[2]) ? $m[2] : (count($results) + 1));
|
||||
$target_code = $m[3];
|
||||
$target_name = trim($m[4]);
|
||||
$target_credits = floatval($m[5]);
|
||||
|
||||
$source_code = $m[6];
|
||||
$source_name = trim($m[7]);
|
||||
$source_credits = floatval($m[8]);
|
||||
$source_grade = $m[9];
|
||||
$similarity = $m[10];
|
||||
|
||||
$results[] = [
|
||||
'seq' => $seq,
|
||||
'target_code' => $target_code,
|
||||
'target_name' => $target_name,
|
||||
'target_credits' => $target_credits,
|
||||
'sources' => [
|
||||
[
|
||||
'code' => $source_code,
|
||||
'name' => $source_name,
|
||||
'credits' => $source_credits,
|
||||
'grade' => $source_grade,
|
||||
'similarity' => $similarity
|
||||
]
|
||||
]
|
||||
];
|
||||
} else if (preg_match($target_only_pattern, $line, $m)) {
|
||||
$seq = !empty($m[1]) ? $m[1] : (!empty($m[2]) ? $m[2] : (count($results) + 1));
|
||||
$target_code = $m[3];
|
||||
$target_name = trim($m[4]);
|
||||
$target_credits = floatval($m[5]);
|
||||
|
||||
$results[] = [
|
||||
'seq' => $seq,
|
||||
'target_code' => $target_code,
|
||||
'target_name' => $target_name,
|
||||
'target_credits' => $target_credits,
|
||||
'sources' => []
|
||||
];
|
||||
} else if (preg_match($source_pattern, $line, $m) && !empty($results)) {
|
||||
$source_code = $m[1];
|
||||
$source_name = trim($m[2]);
|
||||
$source_credits = floatval($m[3]);
|
||||
$source_grade = $m[4];
|
||||
$similarity = $m[5];
|
||||
|
||||
$results[count($results) - 1]['sources'][] = [
|
||||
'code' => $source_code,
|
||||
'name' => $source_name,
|
||||
'credits' => $source_credits,
|
||||
'grade' => $source_grade,
|
||||
'similarity' => $similarity
|
||||
];
|
||||
}
|
||||
} else if ($start_untransfer) {
|
||||
if (preg_match($untransferable_pattern, $line, $m)) {
|
||||
$seq = !empty($m[1]) ? $m[1] : (!empty($m[2]) ? $m[2] : (count($untransferable) + 1));
|
||||
$source_code = $m[3];
|
||||
$source_name = trim($m[4]);
|
||||
$source_credits = floatval($m[5]);
|
||||
$source_grade = $m[6];
|
||||
$reason = trim($m[7]);
|
||||
|
||||
$untransferable[] = [
|
||||
'seq' => $seq,
|
||||
'code' => $source_code,
|
||||
'name' => $source_name,
|
||||
'credits' => $source_credits,
|
||||
'grade' => $source_grade,
|
||||
'reason' => $reason
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'transfers' => $results,
|
||||
'untransferable' => $untransferable
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user