Initial import

This commit is contained in:
pongpon pitisuk
2026-06-25 16:49:13 +07:00
commit 72701ad58c
120 changed files with 17590 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
.DS_Store
# Local environment / secrets
/config.php
.env
# Runtime uploads and generated files
uploads/*.pdf
uploads/*.txt
uploads/*.csv
+65
View File
@@ -0,0 +1,65 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$student_id = intval($_POST['student_id'] ?? 0);
requireStudentAccess($student_id);
$course_code = trim($_POST['course_code'] ?? '');
$course_name = trim($_POST['course_name'] ?? '');
$credits = floatval($_POST['credits'] ?? 0);
$lecture = intval($_POST['lecture'] ?? 0);
$practice = intval($_POST['practice'] ?? 0);
$self_study = intval($_POST['self_study'] ?? 0);
$group_id = intval($_POST['group_id'] ?? 0);
$grade = trim($_POST['grade'] ?? '');
$course_type = trim($_POST['course_type'] ?? '');
$semester = trim($_POST['semester'] ?? '');
$academic_year = trim($_POST['academic_year'] ?? '');
$notes = trim($_POST['notes'] ?? '');
$student = getStudent($student_id);
if (!$student || empty($course_code) || empty($course_name) || $credits <= 0) {
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&error=invalid_data');
exit;
}
try {
$db = getDB();
// Try to find existing course in the curriculum
$course_id = null;
$curriculum_course = findCourseInCurriculum($student['curriculum_id'], $course_code);
if ($curriculum_course) {
$course_id = $curriculum_course['id'];
} elseif ($group_id > 0) {
// Check if course already exists under selected group
$stmt = $db->prepare("SELECT id FROM courses WHERE group_id = ? AND code = ?");
$stmt->execute([$group_id, $course_code]);
$existing = $stmt->fetch();
if ($existing) {
$course_id = $existing['id'];
} else {
// Create a new course record under the selected group
$stmt = $db->prepare("
INSERT INTO courses (group_id, code, name_th, credits, lecture_hours, practice_hours, self_study_hours, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, 0)
");
$stmt->execute([$group_id, $course_code, $course_name, $credits, $lecture, $practice, $self_study]);
$course_id = $db->lastInsertId();
}
}
$stmt = $db->prepare("INSERT INTO student_courses (student_id, course_id, course_code, course_name_th, credits, lecture_hours, practice_hours, self_study_hours, grade, course_type, semester, academic_year, source_type, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'manual', ?)");
$stmt->execute([$student_id, $course_id, $course_code, $course_name, $credits, $lecture, $practice, $self_study, $grade ?: null, $course_type ?: null, $semester ?: null, $academic_year ?: null, $notes]);
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&added=1');
} catch (PDOException $e) {
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&error=' . urlencode($e->getMessage()));
}
exit;
+20
View File
@@ -0,0 +1,20 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$id = intval($_GET['id'] ?? 0);
$student_id = intval($_GET['student_id'] ?? 0);
requireStudentAccess($student_id);
if ($id > 0) {
$stmt = getDB()->prepare("DELETE FROM student_courses WHERE id = ?");
$stmt->execute([$id]);
}
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id);
exit;
+22
View File
@@ -0,0 +1,22 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$id = intval($_POST['id'] ?? 0);
$student_id = intval($_POST['student_id'] ?? 0);
$grade = trim($_POST['grade'] ?? '');
requireStudentAccess($student_id);
if ($id > 0) {
$stmt = getDB()->prepare("UPDATE student_courses SET grade = ? WHERE id = ?");
$stmt->execute([$grade, $id]);
}
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id);
exit;
+105
View File
@@ -0,0 +1,105 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
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; }
}
});
$student_id = intval($_POST['student_id'] ?? 0);
requireStudentAccess($student_id);
$student = getStudent($student_id);
if (!$student || !isset($_FILES['pdf_file']) || $_FILES['pdf_file']['error'] !== UPLOAD_ERR_OK) {
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&error=upload_failed');
exit;
}
$upload_dir = __DIR__ . '/../uploads/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);
$filename = 'transfer_' . $student_id . '_' . time() . '.pdf';
$filepath = $upload_dir . $filename;
move_uploaded_file($_FILES['pdf_file']['tmp_name'], $filepath);
// Extract text from PDF
$text = extractTextFromPDF($filepath);
if (empty(trim($text))) {
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&error=no_text');
exit;
}
// Log the extracted text for debugging
file_put_contents(__DIR__ . '/../uploads/last_extracted_text_' . $student_id . '.txt', $text);
// Parse course data from text
$parsed_courses = parseCoursesFromText($text);
// For each parsed course, try to match with curriculum courses
$db = getDB();
$curriculum_id = $student['curriculum_id'];
// Get all curriculum courses for matching
$curriculum_courses = $db->prepare("
SELECT c.id, c.code, c.name_th, c.credits, cg.name_th AS group_name
FROM courses c
JOIN course_groups cg ON c.group_id = cg.id
WHERE cg.curriculum_id = ?
");
$curriculum_courses->execute([$curriculum_id]);
$curriculum_map = [];
foreach ($curriculum_courses->fetchAll() as $cc) {
$curriculum_map[$cc['code']] = $cc;
}
$added = 0;
$skipped = 0;
$check_stmt = $db->prepare("SELECT COUNT(*) FROM student_courses WHERE student_id = ? AND course_code = ? AND source_type = 'transfer'");
foreach ($parsed_courses as $course) {
$code = $course['code'];
// Only add courses that match the curriculum
if (!isset($curriculum_map[$code])) {
$skipped++;
continue;
}
$cc = $curriculum_map[$code];
// Skip if already added
$check_stmt->execute([$student_id, $code]);
if ($check_stmt->fetchColumn() > 0) {
$skipped++;
continue;
}
// Add the course as transfer credit
$stmt = $db->prepare("INSERT INTO student_courses (student_id, course_id, course_code, course_name_th, credits, lecture_hours, practice_hours, self_study_hours, grade, course_type, source_type, source_file) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'S', 'เทียบโอนรายวิชา', 'transfer', ?)");
$stmt->execute([
$student_id,
$cc['id'],
$code,
$cc['name_th'],
$cc['credits'],
$course['lecture'],
$course['practice'],
$course['self_study'],
$filename
]);
$added++;
}
$msg = $added > 0 ? "added=$added" : "added=0";
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&' . $msg);
exit;
+87
View File
@@ -0,0 +1,87 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$student_id = intval($_POST['student_id'] ?? 0);
requireStudentAccess($student_id);
$student = getStudent($student_id);
if (!$student || !isset($_POST['pdf_text'])) {
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&error=invalid_data');
exit;
}
$text = $_POST['pdf_text'];
if (empty(trim($text))) {
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&error=no_text');
exit;
}
// Log the pasted text for debugging
file_put_contents(__DIR__ . '/../uploads/last_pasted_text_' . $student_id . '.txt', $text);
// Parse course data from text
$parsed_courses = parseCoursesFromText($text);
// For each parsed course, try to match with curriculum courses
$db = getDB();
$curriculum_id = $student['curriculum_id'];
// Get all curriculum courses for matching
$curriculum_courses = $db->prepare("
SELECT c.id, c.code, c.name_th, c.credits, cg.name_th AS group_name
FROM courses c
JOIN course_groups cg ON c.group_id = cg.id
WHERE cg.curriculum_id = ?
");
$curriculum_courses->execute([$curriculum_id]);
$curriculum_map = [];
foreach ($curriculum_courses->fetchAll() as $cc) {
$curriculum_map[$cc['code']] = $cc;
}
$added = 0;
$skipped = 0;
$check_stmt = $db->prepare("SELECT COUNT(*) FROM student_courses WHERE student_id = ? AND course_code = ? AND source_type = 'transfer'");
foreach ($parsed_courses as $course) {
$code = $course['code'];
// Only add courses that match the curriculum
if (!isset($curriculum_map[$code])) {
$skipped++;
continue;
}
$cc = $curriculum_map[$code];
// Skip if already added
$check_stmt->execute([$student_id, $code]);
if ($check_stmt->fetchColumn() > 0) {
$skipped++;
continue;
}
// Add the course as transfer credit
$stmt = $db->prepare("INSERT INTO student_courses (student_id, course_id, course_code, course_name_th, credits, lecture_hours, practice_hours, self_study_hours, grade, course_type, source_type, source_file) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'S', 'เทียบโอนรายวิชา', 'transfer', 'pasted_text')");
$stmt->execute([
$student_id,
$cc['id'],
$code,
$cc['name_th'],
$cc['credits'],
$course['lecture'],
$course['practice'],
$course['self_study']
]);
$added++;
}
$msg = $added > 0 ? "added=$added" : "added=0";
header('Location: ' . BASE_URL . '/students/view.php?id=' . $student_id . '&' . $msg);
exit;
+380
View File
@@ -0,0 +1,380 @@
:root {
--app-bg: #f4f7fb;
--app-surface: #ffffff;
--app-surface-soft: #f8fafc;
--app-border: #dbe4ef;
--app-text: #172033;
--app-muted: #64748b;
--app-primary: #1455a3;
--app-primary-dark: #0d376d;
--app-primary-soft: #e8f1fb;
--app-success: #16845b;
--app-warning: #c47a08;
--app-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
--app-radius: 8px;
}
body {
font-family: 'Sarabun', 'Segoe UI', Tahoma, sans-serif;
background:
radial-gradient(circle at top left, rgba(20, 85, 163, 0.08), transparent 34rem),
linear-gradient(180deg, #f7faff 0%, var(--app-bg) 42%, #eef3f8 100%);
color: var(--app-text);
min-height: 100vh;
}
.app-navbar {
background: linear-gradient(90deg, var(--app-primary-dark), var(--app-primary));
box-shadow: 0 10px 28px rgba(13, 55, 109, 0.24);
padding-block: 0.65rem;
}
.app-navbar .navbar-brand,
.app-navbar .nav-link {
color: rgba(255, 255, 255, 0.88);
}
.app-navbar .nav-link {
border-radius: 6px;
font-weight: 500;
padding-inline: 0.85rem;
}
.app-navbar .nav-link:hover,
.app-navbar .nav-link:focus {
background: rgba(255, 255, 255, 0.12);
color: #fff;
}
.navbar-brand {
font-weight: 800;
}
.user-chip {
background: rgba(255, 255, 255, 0.12);
color: #fff !important;
}
.app-shell {
padding: 1.5rem;
}
.page-heading {
align-items: center;
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
}
.page-heading h4,
.page-heading h5 {
color: var(--app-primary);
font-weight: 800;
margin: 0;
}
.page-heading .text-muted {
color: rgba(20, 85, 163, 0.75) !important;
font-weight: 500;
}
.card {
background: var(--app-surface);
border: 1px solid rgba(219, 228, 239, 0.9);
border-radius: var(--app-radius);
box-shadow: var(--app-shadow);
margin-bottom: 20px;
}
.card-header {
background: var(--app-surface) !important;
border-bottom: 1px solid var(--app-border);
border-radius: var(--app-radius) var(--app-radius) 0 0 !important;
color: var(--app-text) !important;
font-weight: 700;
padding: 0.95rem 1.1rem;
}
.card-header h4,
.card-header h5 {
color: inherit;
font-weight: 800;
}
.card-header.bg-primary,
.card-header.bg-info,
.card-header.bg-success,
.card-header.bg-secondary,
.card-header.bg-warning {
border-left: 4px solid var(--app-primary);
}
.card-header.bg-success {
border-left-color: var(--app-success);
}
.card-header.bg-warning {
border-left-color: var(--app-warning);
}
.dashboard-hero {
background: linear-gradient(135deg, #0d376d, #1455a3 56%, #1570b8);
border: 0;
color: #fff;
overflow: hidden;
position: relative;
}
.dashboard-hero .card-header,
.dashboard-hero .card-body {
background: transparent !important;
border: 0;
color: #fff !important;
position: relative;
z-index: 1;
}
.stat-card {
border: none;
border-radius: 16px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.05);
overflow: hidden;
position: relative;
transition: transform 0.25s ease, box-shadow 0.25s ease;
min-height: 120px;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.1);
}
.stat-card .card-body {
padding: 1.25rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.stat-card.card-curriculum {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: #ffffff;
}
.stat-card.card-dt {
background: linear-gradient(135deg, #1d3557 0%, #457b9d 100%);
color: #ffffff;
}
.stat-card.card-dbc {
background: linear-gradient(135deg, #134e5e 0%, #71b280 100%);
color: #ffffff;
}
.stat-card.card-all {
background: linear-gradient(135deg, #4b6cb7 0%, #182848 100%);
color: #ffffff;
}
.stat-card.card-transfer {
background: linear-gradient(135deg, #d35400 0%, #f1c40f 100%);
color: #ffffff;
}
.stat-card .stat-label {
font-size: 0.95rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.stat-card.card-curriculum .stat-label,
.stat-card.card-dt .stat-label,
.stat-card.card-dbc .stat-label,
.stat-card.card-all .stat-label,
.stat-card.card-transfer .stat-label {
color: rgba(255, 255, 255, 0.85);
}
.stat-card .stat-value {
font-size: 2.25rem;
font-weight: 800;
line-height: 1.1;
}
.stat-card.card-curriculum .stat-value,
.stat-card.card-dt .stat-value,
.stat-card.card-dbc .stat-value,
.stat-card.card-all .stat-value,
.stat-card.card-transfer .stat-value {
color: #ffffff;
}
.stat-img-wrapper {
align-items: center;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 18px;
display: flex;
height: 4.8rem;
justify-content: center;
width: 4.8rem;
padding: 0.5rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.stat-card:hover .stat-img-wrapper {
transform: scale(1.1) rotate(5deg);
}
.stat-img {
height: 100%;
object-fit: contain;
width: 100%;
}
.summary-card {
background: var(--app-surface);
color: var(--app-text);
}
.table {
vertical-align: middle;
}
.table th {
background-color: var(--app-surface-soft);
color: #334155;
font-size: 0.88rem;
font-weight: 700;
white-space: nowrap;
}
.table td {
color: #243044;
}
.table-hover tbody tr:hover {
background-color: #f2f7fd;
}
.table-responsive {
border: 1px solid var(--app-border);
border-radius: var(--app-radius);
}
.table-responsive .table {
margin-bottom: 0;
}
.btn {
border-radius: 7px;
font-weight: 600;
}
.btn-primary,
.bg-primary {
background-color: var(--app-primary) !important;
border-color: var(--app-primary) !important;
}
.btn-success,
.bg-success {
background-color: var(--app-success) !important;
border-color: var(--app-success) !important;
}
.btn-warning,
.bg-warning {
background-color: #f6c453 !important;
border-color: #f6c453 !important;
}
.btn-info,
.bg-info {
background-color: #2c7fb8 !important;
border-color: #2c7fb8 !important;
}
.form-control,
.form-select {
border-color: #cfd9e6;
border-radius: 7px;
}
.form-control:focus,
.form-select:focus {
border-color: var(--app-primary);
box-shadow: 0 0 0 0.22rem rgba(20, 85, 163, 0.14);
}
.badge {
border-radius: 999px;
font-weight: 700;
}
.badge-credit {
background-color: var(--app-primary-soft);
color: var(--app-primary);
padding: 4px 10px;
border-radius: 999px;
font-size: 0.85rem;
font-weight: 700;
}
.curriculum-tree {
padding-left: 20px;
border-left: 3px solid var(--app-primary);
}
.course-item {
border-left: 3px solid transparent;
border-radius: 6px;
transition: background-color 0.2s, border-color 0.2s;
}
.course-item:hover {
background-color: #eef5ff;
border-left-color: var(--app-primary);
}
.status-badge {
font-size: 0.8rem;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
.soft-panel {
background: var(--app-surface-soft);
border: 1px solid var(--app-border);
border-radius: var(--app-radius);
padding: 1rem;
}
@media (max-width: 767.98px) {
.app-shell {
padding: 1rem;
}
.page-heading {
align-items: stretch;
flex-direction: column;
}
.card-header.d-flex {
align-items: flex-start !important;
flex-direction: column;
gap: 0.75rem;
}
.stat-value {
font-size: 1.8rem;
}
}
+21
View File
@@ -0,0 +1,21 @@
$(function() {
$('.table-responsive').each(function() {
if ($(this).find('table').width() > $(this).width()) {
$(this).append('<div class="text-muted small mt-1"><i class="bi bi-arrow-left-right"></i> เลื่อนดูตารางด้านข้างได้</div>');
}
});
// Fail-safe tab switcher for import tabs
$('#importTab button').on('click', function(e) {
e.preventDefault();
// Switch active tab class
$('#importTab button').removeClass('active').attr('aria-selected', 'false');
$(this).addClass('active').attr('aria-selected', 'true');
// Switch active pane class
var target = $(this).attr('data-bs-target');
$('#importTabContent .tab-pane').removeClass('show active');
$(target).addClass('show active');
});
});
+59
View File
@@ -0,0 +1,59 @@
<?php
define("DB_HOST", "localhost");
define("DB_NAME", "transfer_db");
define("DB_USER", "transfer_user");
define("DB_PASS", "change_me");
define("BASE_URL", "");
define("ADMIN_USER", "admin");
define("ADMIN_PASS", "change_me");
// Google OAuth
define("GOOGLE_CLIENT_ID", "your-google-client-id.apps.googleusercontent.com");
define("GOOGLE_CLIENT_SECRET", "your-google-client-secret");
define("GOOGLE_REDIRECT_URI", "https://transfer.tapee.ac.th/login-google-callback.php");
date_default_timezone_set("Asia/Bangkok");
if (php_sapi_name() !== 'cli') {
session_start();
}
function requireLogin() {
if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {
header("Location: " . BASE_URL . "/login.php");
exit;
}
}
function isAdmin() {
return isset($_SESSION["role"]) && $_SESSION["role"] === "admin";
}
function isAdvisor() {
return isset($_SESSION["role"]) && $_SESSION["role"] === "advisor";
}
function isStudent() {
return isset($_SESSION["role"]) && $_SESSION["role"] === "student";
}
function requireAdmin() {
requireLogin();
if (!isAdmin()) {
header("Location: " . BASE_URL . "/index.php");
exit;
}
}
function getCurrentUserRole() {
return $_SESSION["role"] ?? null;
}
function getCurrentUserId() {
return $_SESSION["user_id"] ?? null;
}
function getCurrentUserCurriculumId() {
return $_SESSION["curriculum_id"] ?? null;
}
+79
View File
@@ -0,0 +1,79 @@
<?php
$page_title = 'หลักสูตรคอมพิวเตอร์ธุรกิจดิจิทัล (DBC)';
require_once __DIR__ . '/../includes/functions.php';
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
require_once __DIR__ . '/../includes/header.php';
$curriculum = getDB()->prepare("SELECT * FROM curricula WHERE code = 'DBC'");
$curriculum->execute();
$curriculum = $curriculum->fetch();
if (!$curriculum) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลหลักสูตร กรุณา import ข้อมูลก่อน</div>';
require_once __DIR__ . '/../includes/footer.php';
exit;
}
$tree = buildGroupTree($curriculum['id']);
$is_dbc = true;
?>
<div class="card">
<div class="card-header bg-success text-white">
<h4 class="mb-0"><i class="bi bi-book"></i> หลักสูตรบริหารธุรกิจบัณฑิต สาขาวิชาคอมพิวเตอร์ธุรกิจดิจิทัล (DBC)</h4>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4"><strong>จำนวนหน่วยกิตรวม:</strong> <?= $curriculum['total_credits'] ?> หน่วยกิต</div>
</div>
<hr>
<?php renderCurriculumTree($tree, $is_dbc); ?>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
<?php
function renderCurriculumTree($nodes, $is_dbc, $level = 0) {
$accent = $is_dbc ? 'success' : 'primary';
foreach ($nodes as $node) {
$g = $node['group'];
$has_children = !empty($node['children']);
$has_courses = !empty($node['courses']);
echo '<div class="mb-3">';
echo '<h' . (3 + $level) . ' class="text-' . ($level == 0 ? $accent : 'secondary') . '">';
echo htmlspecialchars($g['name_th']);
if ($g['min_credits'] > 0) {
echo ' <small class="text-muted">(ไม่น้อยกว่า ' . $g['min_credits'] . ' หน่วยกิต)</small>';
}
echo '</h' . (3 + $level) . '>';
if ($has_courses) {
echo '<div class="table-responsive">';
echo '<table class="table table-sm table-bordered">';
echo '<thead class="table-light"><tr><th>รหัสวิชา</th><th>ชื่อวิชา</th><th>ชื่อวิชา (EN)</th><th>หน่วยกิต</th></tr></thead>';
echo '<tbody>';
foreach ($node['courses'] as $c) {
echo '<tr class="course-item">';
echo '<td><code>' . htmlspecialchars($c['code']) . '</code></td>';
echo '<td>' . htmlspecialchars($c['name_th']) . '</td>';
echo '<td><small class="text-muted">' . htmlspecialchars($c['name_en']) . '</small></td>';
echo '<td><span class="badge-credit">' . $c['credits'] . '(' . $c['lecture_hours'] . '-' . $c['practice_hours'] . '-' . $c['self_study_hours'] . ')</span></td>';
echo '</tr>';
}
echo '</tbody></table></div>';
}
if ($has_children) {
echo '<div class="ms-' . ($level > 0 ? 3 : 0) . '">';
renderCurriculumTree($node['children'], $is_dbc, $level + 1);
echo '</div>';
}
echo '</div>';
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
$page_title = 'หลักสูตรเทคโนโลยีดิจิทัล (DT)';
require_once __DIR__ . '/../includes/functions.php';
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
require_once __DIR__ . '/../includes/header.php';
$curriculum = getDB()->prepare("SELECT * FROM curricula WHERE code = 'DT'");
$curriculum->execute();
$curriculum = $curriculum->fetch();
if (!$curriculum) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลหลักสูตร กรุณา import ข้อมูลก่อน</div>';
require_once __DIR__ . '/../includes/footer.php';
exit;
}
$tree = buildGroupTree($curriculum['id']);
$is_dbc = false;
?>
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0"><i class="bi bi-book"></i> หลักสูตรวิทยาศาสตรบัณฑิต สาขาวิชาเทคโนโลยีดิจิทัล (DT)</h4>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4"><strong>จำนวนหน่วยกิตรวม:</strong> <?= $curriculum['total_credits'] ?> หน่วยกิต</div>
</div>
<hr>
<?php renderCurriculumTree($tree, $is_dbc); ?>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
<?php
function renderCurriculumTree($nodes, $is_dbc, $level = 0) {
$accent = $is_dbc ? 'success' : 'primary';
foreach ($nodes as $node) {
$g = $node['group'];
$has_children = !empty($node['children']);
$has_courses = !empty($node['courses']);
echo '<div class="mb-3">';
echo '<h' . (3 + $level) . ' class="text-' . ($level == 0 ? $accent : 'secondary') . '">';
echo htmlspecialchars($g['name_th']);
if ($g['min_credits'] > 0) {
echo ' <small class="text-muted">(ไม่น้อยกว่า ' . $g['min_credits'] . ' หน่วยกิต)</small>';
}
echo '</h' . (3 + $level) . '>';
if ($has_courses) {
echo '<div class="table-responsive">';
echo '<table class="table table-sm table-bordered">';
echo '<thead class="table-light"><tr><th>รหัสวิชา</th><th>ชื่อวิชา</th><th>ชื่อวิชา (EN)</th><th>หน่วยกิต</th></tr></thead>';
echo '<tbody>';
foreach ($node['courses'] as $c) {
echo '<tr class="course-item">';
echo '<td><code>' . htmlspecialchars($c['code']) . '</code></td>';
echo '<td>' . htmlspecialchars($c['name_th']) . '</td>';
echo '<td><small class="text-muted">' . htmlspecialchars($c['name_en']) . '</small></td>';
echo '<td><span class="badge-credit">' . $c['credits'] . '(' . $c['lecture_hours'] . '-' . $c['practice_hours'] . '-' . $c['self_study_hours'] . ')</span></td>';
echo '</tr>';
}
echo '</tbody></table></div>';
}
if ($has_children) {
echo '<div class="ms-' . ($level > 0 ? 3 : 0) . '">';
renderCurriculumTree($node['children'], $is_dbc, $level + 1);
echo '</div>';
}
echo '</div>';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
require_once __DIR__ . '/config.php';
function getDB() {
static $pdo = null;
if ($pdo === null) {
try {
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
return $pdo;
}
Executable
+108
View File
@@ -0,0 +1,108 @@
#!/bin/bash
# 🚀 Script for deploying transfer credit files to Tapi University Server
HOST="110.164.69.50"
USER="pongpon"
PASS="P@ngP@n29012520"
REMOTE_STAGING="/home/pongpon/transfer_deploy"
REMOTE_LIVE="/var/www/html/transfer"
echo "--------------------------------------------------------"
echo "📦 1. Packaging modified files..."
echo "--------------------------------------------------------"
tar -czf deploy.tar.gz \
index.php \
includes/header.php \
includes/functions.php \
students/list.php \
students/add.php \
students/delete.php \
students/import_csv.php \
students/view.php \
students/edit.php \
curriculum/dt.php \
curriculum/dbc.php \
users/profile.php \
users/profile_edit.php \
api/add_manual_course.php \
api/delete_course.php \
api/update_grade.php \
api/upload_pdf.php \
api/upload_text.php \
pic/dashboard_curriculum.png \
pic/dashboard_student_dt.png \
pic/dashboard_student_dbc.png \
pic/dashboard_student_all.png \
pic/dashboard_transfer_credits.png
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to create tarball."
exit 1
fi
echo "✓ Tarball deploy.tar.gz created successfully."
echo ""
echo "--------------------------------------------------------"
echo "📤 2. Uploading tarball to server..."
echo "🔑 Please enter password: $PASS"
echo "--------------------------------------------------------"
scrap_file="deploy.tar.gz"
scp $scrap_file $USER@$HOST:$REMOTE_STAGING.tar.gz
if [ $? -ne 0 ]; then
echo "❌ Error: SCP upload failed."
rm -f deploy.tar.gz
exit 1
fi
echo "✓ Upload completed."
echo ""
echo "--------------------------------------------------------"
echo "⚙ 3. Extracting and deploying to live webroot..."
echo "🔑 Password for ssh and sudo is: $PASS"
echo "--------------------------------------------------------"
ssh $USER@$HOST "
echo 'Extracting staging files...' && \
rm -rf $REMOTE_STAGING && \
mkdir -p $REMOTE_STAGING && \
tar -xzf $REMOTE_STAGING.tar.gz -C $REMOTE_STAGING && \
rm -f $REMOTE_STAGING.tar.gz && \
\
echo 'Copying files and setting permissions...' && \
echo '$PASS' | sudo -S bash -c \"
mkdir -p $REMOTE_LIVE/includes $REMOTE_LIVE/students $REMOTE_LIVE/curriculum $REMOTE_LIVE/users $REMOTE_LIVE/api $REMOTE_LIVE/pic && \
cp $REMOTE_STAGING/index.php $REMOTE_LIVE/index.php && \
cp $REMOTE_STAGING/includes/header.php $REMOTE_LIVE/includes/header.php && \
cp $REMOTE_STAGING/includes/functions.php $REMOTE_LIVE/includes/functions.php && \
cp $REMOTE_STAGING/students/list.php $REMOTE_LIVE/students/list.php && \
cp $REMOTE_STAGING/students/add.php $REMOTE_LIVE/students/add.php && \
cp $REMOTE_STAGING/students/delete.php $REMOTE_LIVE/students/delete.php && \
cp $REMOTE_STAGING/students/import_csv.php $REMOTE_LIVE/students/import_csv.php && \
cp $REMOTE_STAGING/students/view.php $REMOTE_LIVE/students/view.php && \
cp $REMOTE_STAGING/students/edit.php $REMOTE_LIVE/students/edit.php && \
cp $REMOTE_STAGING/curriculum/dt.php $REMOTE_LIVE/curriculum/dt.php && \
cp $REMOTE_STAGING/curriculum/dbc.php $REMOTE_LIVE/curriculum/dbc.php && \
cp $REMOTE_STAGING/users/profile.php $REMOTE_LIVE/users/profile.php && \
cp $REMOTE_STAGING/users/profile_edit.php $REMOTE_LIVE/users/profile_edit.php && \
cp $REMOTE_STAGING/api/add_manual_course.php $REMOTE_LIVE/api/add_manual_course.php && \
cp $REMOTE_STAGING/api/delete_course.php $REMOTE_LIVE/api/delete_course.php && \
cp $REMOTE_STAGING/api/update_grade.php $REMOTE_LIVE/api/update_grade.php && \
cp $REMOTE_STAGING/api/upload_pdf.php $REMOTE_LIVE/api/upload_pdf.php && \
cp $REMOTE_STAGING/api/upload_text.php $REMOTE_LIVE/api/upload_text.php && \
cp $REMOTE_STAGING/pic/dashboard_curriculum.png $REMOTE_LIVE/pic/dashboard_curriculum.png && \
cp $REMOTE_STAGING/pic/dashboard_student_dt.png $REMOTE_LIVE/pic/dashboard_student_dt.png && \
cp $REMOTE_STAGING/pic/dashboard_student_dbc.png $REMOTE_LIVE/pic/dashboard_student_dbc.png && \
cp $REMOTE_STAGING/pic/dashboard_student_all.png $REMOTE_LIVE/pic/dashboard_student_all.png && \
cp $REMOTE_STAGING/pic/dashboard_transfer_credits.png $REMOTE_LIVE/pic/dashboard_transfer_credits.png && \
chown -R www-data:www-data $REMOTE_LIVE/index.php $REMOTE_LIVE/includes/header.php $REMOTE_LIVE/includes/functions.php $REMOTE_LIVE/students $REMOTE_LIVE/curriculum $REMOTE_LIVE/users $REMOTE_LIVE/api $REMOTE_LIVE/pic && \
chmod 644 $REMOTE_LIVE/index.php $REMOTE_LIVE/includes/header.php $REMOTE_LIVE/includes/functions.php $REMOTE_LIVE/students/*.php $REMOTE_LIVE/curriculum/*.php $REMOTE_LIVE/users/*.php $REMOTE_LIVE/api/*.php $REMOTE_LIVE/pic/*.png && \
chmod 755 $REMOTE_LIVE/students $REMOTE_LIVE/curriculum $REMOTE_LIVE/users $REMOTE_LIVE/api $REMOTE_LIVE/pic
\" && \
\
echo '✓ Deployment completed successfully!'
"
# Clean up local tarball
rm -f deploy.tar.gz
echo ""
echo "🎉 Done! All changes are live on transfer.tapee.ac.th!"
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>Identify Images</title>
<style>
body { font-family: sans-serif; display: flex; gap: 20px; padding: 20px; background: #eee; }
.card { background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center; }
img { width: 128px; height: 128px; object-fit: contain; }
h4 { margin: 10px 0 0; }
</style>
</head>
<body>
<div class="card">
<img src="pic/media__1781942624014.png" alt="Image 1">
<h4>media__1781942624014.png</h4>
</div>
<div class="card">
<img src="pic/media__1781942624039.png" alt="Image 2">
<h4>media__1781942624039.png</h4>
</div>
<div class="card">
<img src="pic/media__1781942624659.png" alt="Image 3">
<h4>media__1781942624659.png</h4>
</div>
<div class="card">
<img src="pic/media__1781942624664.png" alt="Image 4">
<h4>media__1781942624664.png</h4>
</div>
</body>
</html>
+434
View File
@@ -0,0 +1,434 @@
<?php
require_once __DIR__ . '/db.php';
$pdo = getDB();
// ============================================================
// 1. หลักสูตรเทคโนโลยีดิจิทัล (DT)
// ============================================================
$pdo->exec("INSERT INTO curricula (code, name_th, name_en, total_credits) VALUES
('DT', 'เทคโนโลยีดิจิทัล', 'Digital Technology', 121)");
$dt_id = $pdo->lastInsertId();
// --- หมวดวิชาศึกษาทั่วไป DT ---
$pdo->exec("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES
($dt_id, NULL, 'DT_GEN', 'หมวดวิชาศึกษาทั่วไป', 24, 1)");
$dt_gen_id = $pdo->lastInsertId();
$groups_ge = [
['DT_GEN1', 'กลุ่มสาระที่ 1 การคิดและการแก้ปัญหา', 6, 1],
['DT_GEN2', 'กลุ่มสาระที่ 2 การใช้ชีวิตอยู่ร่วมกับผู้อื่น', 6, 2],
['DT_GEN3', 'กลุ่มสาระที่ 3 การสื่อสารระหว่างบุคคล', 6, 3],
['DT_GEN4', 'กลุ่มสาระที่ 4 การใช้เทคโนโลยีดิจิทัลและปัญญาประดิษฐ์', 6, 4],
];
foreach ($groups_ge as $g) {
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dt_id, $dt_gen_id, $g[0], $g[1], $g[2], $g[3]]);
}
// --- หมวดวิชาเฉพาะ DT ---
$pdo->exec("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES
($dt_id, NULL, 'DT_SPEC', 'หมวดวิชาเฉพาะ', 91, 2)");
$dt_spec_id = $pdo->lastInsertId();
// กลุ่มวิชาแกน
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dt_id, $dt_spec_id, 'DT_CORE', 'กลุ่มวิชาแกน', 9, 1]);
$dt_core_id = $pdo->lastInsertId();
// กลุ่มวิชาเฉพาะด้าน
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dt_id, $dt_spec_id, 'DT_MAJOR', 'กลุ่มวิชาเฉพาะด้าน', 75, 2]);
$dt_major_id = $pdo->lastInsertId();
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dt_id, $dt_major_id, 'DT_MAJOR_REQ', 'กลุ่มวิชาบังคับ', 60, 1]);
$dt_major_req_id = $pdo->lastInsertId();
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dt_id, $dt_major_id, 'DT_MAJOR_ELEC', 'กลุ่มวิชาเลือกทางเทคโนโลยีดิจิทัล', 15, 2]);
$dt_major_elec_id = $pdo->lastInsertId();
// กลุ่มวิชาพื้นฐานวิชาชีพและวิชาชีพ
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dt_id, $dt_spec_id, 'DT_PROF', 'กลุ่มวิชาพื้นฐานวิชาชีพและวิชาชีพ', 7, 3]);
$dt_prof_id = $pdo->lastInsertId();
// --- หมวดวิชาเลือกเสรี DT ---
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dt_id, NULL, 'DT_FREE', 'หมวดวิชาเลือกเสรี', 6, 3]);
$dt_free_id = $pdo->lastInsertId();
// ============================================================
// 2. หลักสูตรคอมพิวเตอร์ธุรกิจดิจิทัล (DBC)
// ============================================================
$pdo->exec("INSERT INTO curricula (code, name_th, name_en, total_credits) VALUES
('DBC', 'คอมพิวเตอร์ธุรกิจดิจิทัล', 'Digital Business Computer', 121)");
$dbc_id = $pdo->lastInsertId();
// --- หมวดวิชาศึกษาทั่วไป DBC ---
$pdo->exec("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES
($dbc_id, NULL, 'DBC_GEN', 'หมวดวิชาศึกษาทั่วไป', 24, 1)");
$dbc_gen_id = $pdo->lastInsertId();
foreach ($groups_ge as $g) {
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dbc_id, $dbc_gen_id, 'DBC_' . $g[0], $g[1], $g[2], $g[3]]);
}
// --- หมวดวิชาเฉพาะ DBC ---
$pdo->exec("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES
($dbc_id, NULL, 'DBC_SPEC', 'หมวดวิชาเฉพาะ', 91, 2)");
$dbc_spec_id = $pdo->lastInsertId();
// กลุ่มวิชาแกนธุรกิจ
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dbc_id, $dbc_spec_id, 'DBC_CORE', 'กลุ่มวิชาแกนธุรกิจ', 30, 1]);
$dbc_core_id = $pdo->lastInsertId();
// กลุ่มวิชาเฉพาะด้าน
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dbc_id, $dbc_spec_id, 'DBC_MAJOR', 'กลุ่มวิชาเฉพาะด้าน', 54, 2]);
$dbc_major_id = $pdo->lastInsertId();
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dbc_id, $dbc_major_id, 'DBC_MAJOR_REQ', 'กลุ่มวิชาบังคับ', 45, 1]);
$dbc_major_req_id = $pdo->lastInsertId();
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dbc_id, $dbc_major_id, 'DBC_MAJOR_ELEC', 'กลุ่มวิชาเลือก', 9, 2]);
$dbc_major_elec_id = $pdo->lastInsertId();
// กลุ่มวิชาพื้นฐานวิชาชีพและวิชาชีพ
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dbc_id, $dbc_spec_id, 'DBC_PROF', 'กลุ่มวิชาพื้นฐานวิชาชีพและวิชาชีพ', 7, 3]);
$dbc_prof_id = $pdo->lastInsertId();
// --- หมวดวิชาเลือกเสรี DBC ---
$pdo->prepare("INSERT INTO course_groups (curriculum_id, parent_id, code, name_th, min_credits, sort_order) VALUES (?,?,?,?,?,?)")
->execute([$dbc_id, NULL, 'DBC_FREE', 'หมวดวิชาเลือกเสรี', 6, 3]);
// ============================================================
// 3. เพิ่มรายวิชา DT
// ============================================================
// Helper function
function add_course($pdo, $group_id, $code, $name_th, $name_en, $credits, $lecture, $practice, $self, $order) {
$stmt = $pdo->prepare("INSERT INTO courses (group_id, code, name_th, name_en, credits, lecture_hours, practice_hours, self_study_hours, sort_order) VALUES (?,?,?,?,?,?,?,?,?)");
$stmt->execute([$group_id, $code, $name_th, $name_en, $credits, $lecture, $practice, $self, $order]);
}
// DT Get group IDs
$dt_ge_groups = [];
$stmt = $pdo->query("SELECT id, code FROM course_groups WHERE parent_id = $dt_gen_id ORDER BY sort_order");
foreach ($stmt->fetchAll() as $r) {
$dt_ge_groups[$r['code']] = $r['id'];
}
// DT GEN1 - การคิดและการแก้ปัญหา
$gid = $dt_ge_groups['DT_GEN1'];
$courses = [
['0001101','จิตวิทยาทั่วไป','General Psychology',3,3,0,6],
['0001102','สังคม เศรษฐกิจและการเมือง','Society Economic and Politics',3,3,0,6],
['0001103','ภูมิปัญญาท้องถิ่น','Local Wisdom',3,3,0,6],
['0001104','ไทยในพลวัตอาเซียน','The Thai in ASEAN Dynamics',3,3,0,6],
['0001105','บัณฑิตในอุดมคติไทย','Graduate Thai Ideal',3,3,0,6],
['0001106','กฎหมายในชีวิตประจำวัน','Law in Everyday Life',3,3,0,6],
['0001107','ฉลาดคิด','Smart Thinking',3,3,0,6],
['0001108','ศาสตร์พระราชาเพื่อการบูรณาการที่ยั่งยืน','King Philosophy for Sustainable Integration',3,3,0,6],
['0001109','นวัตกรสังคมเพื่อการพัฒนาท้องถิ่น','Social Innovators for Local Development',3,2,2,5],
['0001110','คณิตศาสตร์และสถิติในชีวิตประจำวัน','Mathematics and Statistics for Daily Life',3,3,0,6],
['0001111','ชีวิตกับสิ่งแวดล้อม','Life and Environment',3,3,0,6],
['0001112','วิทยาศาสตร์และเทคโนโลยีเพื่อคุณภาพชีวิตและสังคม','Science and Technology for Quality of Life and Society',3,3,0,6],
['0001113','ความรู้พื้นฐานทางคณิตศาสตร์ วิทยาศาสตร์ และเทคโนโลยีเพื่อการดำรงชีวิต','Fundamental Knowledge in Mathematics Science and Technology for Everyday Life',6,6,0,12],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DT GEN2 - การใช้ชีวิตอยู่ร่วมกับผู้อื่น
$gid = $dt_ge_groups['DT_GEN2'];
$courses = [
['0002101','องค์รวมแห่งชีวิต','Holistic Approaches to Life',3,3,0,6],
['0002102','มนุษย์สัมพันธ์และการพัฒนาบุคลิกภาพ','Human Relations and Personality Development',3,3,0,6],
['0002103','สารสนเทศเพื่อการศึกษาและค้นคว้า','Information for Education and Research',3,3,0,6],
['0002104','ความเป็นพลเมืองและจิตสาธารณะ','Citizenship and Public Consciousness',3,3,0,6],
['0002105','สันติศึกษา','Peace Studies',3,3,0,6],
['0002106','ทักษะชีวิต','Life Skills',3,3,0,6],
['0002107','การเรียนรู้ในระดับอุดมศึกษา','University Study',3,3,0,6],
['0002108','รู้ทันสุขภาพ','Health Literacy',3,2,2,5],
['0002109','พลเมืองโลก','Global Citizens',3,3,0,6],
['0002110','วิถีชีวิตที่ยั่งยืน','Sustainable Lifestyles',3,3,0,6],
['0002111','ใส่ใจภัยพิบัติในโลกสมัยใหม่','Disaster Intentions in the Modern World',3,3,0,6],
['0002112','งานช่างในชีวิตประจำวัน','Engineering Work in Daily Life',3,2,2,5],
['0002113','สมาธิเพื่อพัฒนาชีวิต','Meditation for Life Development',3,3,0,6],
['0002114','ทักษะชีวิตเพื่อการอยู่ร่วมกันอย่างยั่งยืน','Life Skills for Sustainable Living and Coexistence',3,3,0,6],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DT GEN3 - การสื่อสารระหว่างบุคคล
$gid = $dt_ge_groups['DT_GEN3'];
$courses = [
['0003101','ภาษาอังกฤษพื้นฐาน','Fundamental English',3,3,0,6],
['0003102','การใช้ภาษาอังกฤษ','English Usage',3,3,0,6],
['0003103','การพัฒนาทักษะการอ่านและเขียนภาษาอังกฤษ','Development of Reading and Writing Skills in English',3,3,0,6],
['0003104','ภาษาอังกฤษเพื่อการสื่อสารในบริบทสากล','Communicative English in Global Context',3,3,0,6],
['0003105','ภาษาอังกฤษในบริบทการทำงานอย่างมีประสิทธิภาพ','Effective English in Professional Contexts',3,3,0,6],
['0003106','ภาษาจีนเพื่อการสื่อสาร','Chinese for Communication',3,3,0,6],
['0003107','ภาษาฝรั่งเศสเบื้องต้น','Introduction to French',3,3,0,6],
['0003108','ภาษาญี่ปุ่นเบื้องต้น','Introduction to Japanese',3,3,0,6],
['0003109','ภาษาญี่ปุ่นเพื่อการสื่อสาร','Japanese for Communication',3,3,0,6],
['0003110','ภาษามาเลย์เพื่อการสื่อสาร','Malay for Communication',3,3,0,6],
['0003111','ภาษาอินโดนีเซียเพื่อการสื่อสาร','Indonesian for Communication',3,3,0,6],
['0003112','การพัฒนาทักษะทางภาษาไทย','Development of Thai Language Skills',3,3,0,6],
['0003113','ภาษาไทยเพื่อการสื่อสาร','Thai for Communication',3,3,0,6],
['0003114','ภาษาไทยถิ่นและภาษาเฉพาะถิ่นในสังคมไทย','Thai Dialects and Ethnic Languages in Thai Society',3,3,0,6],
['0003115','ทักษะภาษาเพื่อการสื่อสารในชีวิตประจำวัน','Language Skills for Everyday Communication',6,6,0,12],
['0003116','ภาษาเพื่อวิชาชีพและการทำงาน','Professional and Workplace Communication',6,6,0,12],
['0003117','ภาษาเพื่อวัฒนธรรมและความเข้าใจระหว่างประเทศ','Language and Intercultural Understanding',6,6,0,12],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DT GEN4 - การใช้เทคโนโลยีดิจิทัลและปัญญาประดิษฐ์
$gid = $dt_ge_groups['DT_GEN4'];
$courses = [
['0004101','เทคโนโลยีการสื่อสารกับมนุษย์','Communication Technology and Human',3,3,0,6],
['0004102','เทคโนโลยีดิจิทัลและปัญญาประดิษฐ์เพื่อชีวิตวิถีใหม่','Digital Technology and AI for the New Normal',3,2,2,6],
['0004103','ความมั่นคงปลอดภัยทางไซเบอร์','Cyber Security',3,3,0,6],
['0004104','สารสนเทศดิจิทัลและปัญญาประดิษฐ์','Digital Information and Artificial Intelligence',3,3,0,6],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DT กลุ่มวิชาแกน
$courses = [
['1211101','คณิตศาสตร์สำหรับเทคโนโลยีดิจิทัล','Mathematics for Digital Technology',3,2,2,5],
['1211102','สถิติสำหรับเทคโนโลยีดิจิทัล','Statistics for Digital Technology',3,2,2,5],
['1211203','เทคโนโลยีดิจิทัลพื้นฐาน','Basic Digital Technology',3,2,2,5],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dt_core_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DT กลุ่มวิชาบังคับ
$courses = [
['1212101','การเขียนโปรแกรมคอมพิวเตอร์','Computer Programming',3,2,2,5],
['1212102','ปัญญาประดิษฐ์เพื่อการวิเคราะห์ข้อมูล','Artificial Intelligence for Data Analytics',3,2,2,5],
['1212103','เทคโนโลยีเว็บและแอปพลิเคชัน','Web and Application Technology',3,2,2,5],
['1212104','เครือข่ายคอมพิวเตอร์และความมั่นคงปลอดภัย','Computer Networks and Security',3,2,2,5],
['1212205','การจัดการระบบฐานข้อมูล','Database System Management',3,2,2,5],
['1212206','วิทยาการข้อมูลเบื้องต้น','Introduction to Data Science',3,2,2,5],
['1212207','เทคโนโลยีดิจิทัลและนวัตกรรมธุรกิจ','Digital Technology and Business Innovation',3,2,2,5],
['1212208','การวิเคราะห์และออกแบบระบบนวัตกรรมดิจิทัล','Digital Innovation Design and Analysis',3,2,2,5],
['1212309','การออกแบบระบบอัจฉริยะ','Intelligent Systems Design',3,2,2,5],
['1212310','การสร้างเนื้อหาดิจิทัลและมัลติมิเดีย','Digital Content and Multimedia Creation',3,2,2,5],
['1212311','การบริหารโครงการเทคโนโลยีดิจิทัล','Digital Project Management',3,2,2,5],
['1212312','เทคโนโลยีบนคลาวด์','Cloud Technology',3,2,2,5],
['1212313','เทคโนโลยีธุรกิจอิเล็กทรอนิกส์ดิจิทัล','Digital Electronic Business Technology',3,2,2,5],
['1212314','การสื่อสารทางวิชาชีพเทคโนโลยีดิจิทัล','Digital Technology Professional Communication',3,2,2,5],
['1212315','การประยุกต์ใช้งานปัญญาประดิษฐ์ด้านธุรกิจดิจิทัล','AI Applications in Digital Business',3,2,2,5],
['1212416','อินเทอร์เน็ตสรรพสิ่งสำหรับธุรกิจดิจิทัล','Internet of Things for Business Digital',3,2,2,5],
['1212417','กฎหมายและจริยธรรมทางเทคโนโลยีดิจิทัล','Digital Technology Law and Ethics',3,2,2,5],
['1212418','การเป็นผู้ประกอบการดิจิทัล','Digital Entrepreneurship',3,2,2,5],
['1212419','ภาษาอังกฤษสำหรับเทคโนโลยีดิจิทัล','English for Digital Technology',3,2,2,5],
['1212420','สัมมนาเทคโนโลยีดิจิทัล','Digital Technology Seminar',3,1,4,9],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dt_major_req_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DT กลุ่มวิชาเลือกทางเทคโนโลยีดิจิทัล
$courses = [
['1212421','การพัฒนาโปรแกรมประยุกต์สำหรับอุปกรณ์เคลื่อนที่','Application Development for Mobile Devices',3,2,2,5],
['1212422','ปฏิบัติการโปรแกรมภาษาจาวา','Java Programming Workshop',3,2,2,5],
['1212423','การออกแบบกราฟิกบนคอมพิวเตอร์','Computer Graphic Design',3,2,2,5],
['1212424','การสร้างสื่อดิจิทัล','Digital Media Production',3,2,2,5],
['1212425','เทคโนโลยีเว็บเซอร์วิส','Web Services Technology',3,2,2,5],
['1212426','การบริหารบริการเทคโนโลยีดิจิทัล','Digital Technology Service Management',3,2,2,5],
['1212427','ปฏิบัติการการโปรแกรมฐานข้อมูล','Database Programming Workshop',3,2,2,5],
['1212428','ปฏิบัติการพัฒนาโปรแกรมประยุกต์ฐานข้อมูลบนเว็บ','Web Database Application Development Workshop',3,2,2,5],
['1212429','ปฏิบัติการสถาปัตยกรรมและการบริหารฐานข้อมูล','Database Architecture and Administration Workshop',3,2,2,5],
['1212430','ปฏิบัติการเครือข่ายในสำนักงาน','Office Networking Workshop',3,2,2,5],
['1212431','ปฏิบัติการการใช้ซอฟต์แวร์สำเร็จรูปในสำนักงาน','Office Package Workshop',3,2,2,5],
['1212432','สถาปัตยกรรมคอมพิวเตอร์และระบบปฏิบัติการ','Computer Architecture and Operating System',3,2,2,5],
['1212433','ระเบียบวิธีวิจัยทางเทคโนโลยีดิจิทัล','Digital Technology Research Methodology',3,2,2,5],
['1212434','การศึกษาเฉพาะเรื่องทางเทคโนโลยีดิจิทัล 1','Specialized Study in Digital Technology 1',3,2,2,5],
['1212435','การศึกษาเฉพาะเรื่องทางเทคโนโลยีดิจิทัล 2','Specialized Study in Digital Technology 2',3,2,2,5],
['1212436','การศึกษาเฉพาะเรื่องทางเทคโนโลยีดิจิทัล 3','Specialized Study in Digital Technology 3',3,2,2,5],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dt_major_elec_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DT กลุ่มวิชาพื้นฐานวิชาชีพและวิชาชีพ
$courses = [
['1213401','โครงงานเทคโนโลยีดิจิทัล','Digital Technology Project',3,0,6,12],
['1213402','การเตรียมความพร้อมก่อนฝึกประสบการณ์วิชาชีพ','Pre-Professional Internship Preparation',1,0,2,4],
['1213403','การฝึกประสบการณ์วิชาชีพทางเทคโนโลยีดิจิทัล','Internship in Digital Technology',3,3,0,0],
['1213404','การเตรียมสหกิจศึกษาทางเทคโนโลยีดิจิทัล','Pre-Cooperative Education in Digital Technology',1,0,2,4],
['1213405','สหกิจศึกษาทางด้านเทคโนโลยีดิจิทัล','Digital Technology Cooperative Education',6,5,60,0],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dt_prof_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// ============================================================
// 4. เพิ่มรายวิชา DBC
// ============================================================
$dbc_ge_groups = [];
$stmt = $pdo->query("SELECT id, code FROM course_groups WHERE parent_id = $dbc_gen_id ORDER BY sort_order");
foreach ($stmt->fetchAll() as $r) {
$dbc_ge_groups[$r['code']] = $r['id'];
}
// DBC GEN1 - การคิดและการแก้ปัญหา
$gid = $dbc_ge_groups['DBC_DT_GEN1'];
$courses = [
['0001101','จิตวิทยาทั่วไป','General Psychology',3,3,0,6],
['0001102','สังคม เศรษฐกิจและการเมือง','Society Economic and Politics',3,3,0,6],
['0001103','ภูมิปัญญาท้องถิ่น','Local Wisdom',3,3,0,6],
['0001104','ไทยในพลวัตอาเซียน','The Thai in ASEAN Dynamics',3,3,0,6],
['0001105','บัณฑิตในอุดมคติไทย','Graduate Thai Ideal',3,3,0,6],
['0001106','กฎหมายในชีวิตประจำวัน','Law in Everyday Life',3,3,0,6],
['0001107','ฉลาดคิด','Smart Thinking',3,3,0,6],
['0001108','ศาสตร์พระราชาเพื่อการบูรณาการที่ยั่งยืน','King Philosophy for Sustainable Integration',3,3,0,6],
['0001109','นวัตกรสังคมเพื่อการพัฒนาท้องถิ่น','Social Innovators for Local Development',3,2,2,5],
['0001110','คณิตศาสตร์และสถิติในชีวิตประจำวัน','Mathematics and Statistics for Daily Life',3,3,0,6],
['0001111','ชีวิตกับสิ่งแวดล้อม','Life and Environment',3,3,0,6],
['0001112','วิทยาศาสตร์และเทคโนโลยีเพื่อคุณภาพชีวิตและสังคม','Science and Technology for Quality of Life and Society',3,3,0,6],
['0001113','ความรู้พื้นฐานทางคณิตศาสตร์ วิทยาศาสตร์ และเทคโนโลยีเพื่อการดำรงชีวิต','Fundamental Knowledge in Mathematics Science and Technology for Everyday Life',6,6,0,12],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, 'DBC_'.$c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DBC GEN2
$gid = $dbc_ge_groups['DBC_DT_GEN2'];
$courses = [
['0002101','องค์รวมแห่งชีวิต','Holistic Approaches to Life',3,3,0,6],
['0002102','มนุษย์สัมพันธ์และการพัฒนาบุคลิกภาพ','Human Relations and Personality Development',3,3,0,6],
['0002103','สารสนเทศเพื่อการศึกษาและค้นคว้า','Information for Education and Research',3,3,0,6],
['0002104','ความเป็นพลเมืองและจิตสาธารณะ','Citizenship and Public Consciousness',3,3,0,6],
['0002105','สันติศึกษา','Peace Studies',3,3,0,6],
['0002106','ทักษะชีวิต','Life Skills',3,3,0,6],
['0002107','การเรียนรู้ในระดับอุดมศึกษา','University Study',3,3,0,6],
['0002108','รู้ทันสุขภาพ','Health Literacy',3,2,2,5],
['0002109','พลเมืองโลก','Global Citizens',3,3,0,6],
['0002110','วิถีชีวิตที่ยั่งยืน','Sustainable Lifestyles',3,3,0,6],
['0002111','ใส่ใจภัยพิบัติในโลกสมัยใหม่','Disaster Intentions in the Modern World',3,3,0,6],
['0002112','งานช่างในชีวิตประจำวัน','Engineering Work in Daily Life',3,2,2,5],
['0002113','สมาธิเพื่อพัฒนาชีวิต','Meditation for Life Development',3,3,0,6],
['0002114','ทักษะชีวิตเพื่อการอยู่ร่วมกันอย่างยั่งยืน','Life Skills for Sustainable Living and Coexistence',3,3,0,6],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, 'DBC_'.$c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DBC GEN3
$gid = $dbc_ge_groups['DBC_DT_GEN3'];
$courses = [
['0003101','ภาษาอังกฤษพื้นฐาน','Fundamental English',3,3,0,6],
['0003102','การใช้ภาษาอังกฤษ','English Usage',3,3,0,6],
['0003103','การพัฒนาทักษะการอ่านและเขียนภาษาอังกฤษ','Development of Reading and Writing Skills in English',3,3,0,6],
['0003104','ภาษาอังกฤษเพื่อการสื่อสารในบริบทสากล','Communicative English in Global Context',3,3,0,6],
['0003105','ภาษาอังกฤษในบริบทการทำงานอย่างมีประสิทธิภาพ','Effective English in Professional Contexts',3,3,0,6],
['0003106','ภาษาจีนเพื่อการสื่อสาร','Chinese for Communication',3,3,0,6],
['0003107','ภาษาฝรั่งเศสเบื้องต้น','Introduction to French',3,3,0,6],
['0003108','ภาษาญี่ปุ่นเบื้องต้น','Introduction to Japanese',3,3,0,6],
['0003109','ภาษาญี่ปุ่นเพื่อการสื่อสาร','Japanese for Communication',3,3,0,6],
['0003110','ภาษามาเลย์เพื่อการสื่อสาร','Malay for Communication',3,3,0,6],
['0003111','ภาษาอินโดนีเซียเพื่อการสื่อสาร','Indonesian for Communication',3,3,0,6],
['0003112','การพัฒนาทักษะทางภาษาไทย','Development of Thai Language Skills',3,3,0,6],
['0003113','ภาษาไทยเพื่อการสื่อสาร','Thai for Communication',3,3,0,6],
['0003114','ภาษาไทยถิ่นและภาษาเฉพาะถิ่นในสังคมไทย','Thai Dialects and Ethnic Languages in Thai Society',3,3,0,6],
['0003115','ทักษะภาษาเพื่อการสื่อสารในชีวิตประจำวัน','Language Skills for Everyday Communication',6,6,0,12],
['0003116','ภาษาเพื่อวิชาชีพและการทำงาน','Professional and Workplace Communication',6,6,0,12],
['0003117','ภาษาเพื่อวัฒนธรรมและความเข้าใจระหว่างประเทศ','Language and Intercultural Understanding',6,6,0,12],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, 'DBC_'.$c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DBC GEN4
$gid = $dbc_ge_groups['DBC_DT_GEN4'];
$courses = [
['0004101','เทคโนโลยีการสื่อสารกับมนุษย์','Communication Technology and Human',3,3,0,6],
['0004102','เทคโนโลยีดิจิทัลและปัญญาประดิษฐ์เพื่อชีวิตวิถีใหม่','Digital Technology and AI for the New Normal',3,2,2,6],
['0004103','ความมั่นคงปลอดภัยทางไซเบอร์','Cyber Security',3,3,0,6],
['0004104','สารสนเทศดิจิทัลและปัญญาประดิษฐ์','Digital Information and Artificial Intelligence',3,3,0,6],
];
foreach ($courses as $i => $c) {
add_course($pdo, $gid, 'DBC_'.$c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DBC กลุ่มวิชาแกนธุรกิจ
$courses = [
['1111101','หลักการตลาดในยุคดิจิทัล','Principles of Marketing in Digital Era',3,2,2,5],
['1121101','องค์การและการจัดการ','Organization and Management',3,2,2,5],
['1121201','เศรษฐศาสตร์ธุรกิจ','Business Economics',3,2,2,5],
['1121202','กฎหมายธุรกิจ','Business Law',3,2,2,5],
['1121203','การบัญชีขั้นต้น','Fundamentals of Accounting',3,2,2,5],
['1121204','การเป็นผู้ประกอบการและการสร้างธุรกิจใหม่','Entrepreneurship and New Venture Creation',3,2,2,5],
['1221301','ภาษาอังกฤษธุรกิจ','English for Business',3,2,2,5],
['1121303','การภาษีอากร','Taxation',3,2,2,5],
['1121403','การจัดการเชิงกลยุทธ์','Strategic Management',3,2,2,5],
['1221201','โปรแกรมสำเร็จรูปทางธุรกิจ','Package Program in Business',3,2,2,5],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dbc_core_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DBC กลุ่มวิชาบังคับ
$courses = [
['1222101','คอมพิวเตอร์ธุรกิจดิจิทัล','Digital Business Computer',3,2,2,5],
['1222102','ปัญญาประดิษฐ์เพื่องานคอมพิวเตอร์ธุรกิจดิจิทัล','AI for Digital Business Computer',3,2,2,5],
['1222103','การโปรแกรมคอมพิวเตอร์ด้วยภาษาสมัยใหม่','Computer Programming with Modern Language',3,2,2,5],
['1222104','สถิติการวิเคราะห์ข้อมูลสำหรับคอมพิวเตอร์ธุรกิจดิจิทัล','Statistics Data Analytics for Digital Business Computer',3,2,2,5],
['1222205','โครงสร้างข้อมูลสำหรับคอมพิวเตอร์ธุรกิจดิจิทัล','Data Structure for Digital Business Computer',3,2,2,5],
['1222206','การบริหารเครือข่ายคอมพิวเตอร์','Network Administration Computer',3,2,2,5],
['1222307','การวิเคราะห์และออกแบบระบบสารสนเทศ','Information System Analysis and Design',3,2,2,5],
['1222308','ระบบจัดการฐานข้อมูล','Database Management System',3,2,2,5],
['1222309','การออกแบบและพัฒนาเว็บไซต์','Web Design and Development',3,2,2,5],
['1222310','การบริหารโครงการ','Project Management',3,2,2,5],
['1222311','ระบบสารสนเทศทางคอมพิวเตอร์ธุรกิจดิจิทัล','Business Information Systems Computer',3,2,2,5],
['1222312','การออกแบบงานกราฟฟิกและมัลติมีเดีย','Graphic Design and Multimedia',3,2,2,5],
['1222313','การใช้สื่อสังคมออนไลน์เพื่อธุรกิจ','Social Media Usages for Business',3,2,2,5],
['1222414','การจัดการธุรกิจอิเล็กทรอนิกส์','Electronic Business Management',3,2,2,5],
['1222415','สัมมนาคอมพิวเตอร์ธุรกิจดิจิทัล','Seminar in Digital Business Computer',3,1,4,9],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dbc_major_req_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DBC กลุ่มวิชาเลือก
$courses = [
['1222316','การรักษาความปลอดภัยทางไซเบอร์และระบบสารสนเทศ','Cyber and Information System Security',3,2,2,5],
['1222317','การพัฒนาอินเทอร์เน็ตสรรพสิ่ง','Internet of Things Development',3,2,2,5],
['1222318','การพัฒนาโปรแกรมประยุกต์สำหรับอุปกรณ์เคลื่อนที่','Application Development for Mobile Devices',3,2,2,5],
['1222419','ธุรกิจอัจฉริยะ','Business Intelligence',3,2,2,5],
['1222420','หัวข้อพิเศษทางคอมพิวเตอร์ธุรกิจดิจิทัล 1','Special Topic in Digital Business Computer 1',3,2,2,5],
['1222421','หัวข้อพิเศษทางคอมพิวเตอร์ธุรกิจดิจิทัล 2','Special Topic in Digital Business Computer 2',3,2,2,5],
['1222422','หัวข้อพิเศษทางคอมพิวเตอร์ธุรกิจดิจิทัล 3','Special Topic in Digital Business Computer 3',3,2,2,5],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dbc_major_elec_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
// DBC กลุ่มวิชาพื้นฐานวิชาชีพและวิชาชีพ
$courses = [
['1223401','โครงงานทางคอมพิวเตอร์ธุรกิจดิจิทัล','Project in Digital Business Computer',3,0,6,12],
['1223402','การเตรียมการฝึกประสบการณ์วิชาชีพทางคอมพิวเตอร์ธุรกิจดิจิทัล','Pre-Cooperative Education in Digital Business Computer',1,1,1,2],
['1223403','การฝึกประสบการณ์วิชาชีพทางคอมพิวเตอร์ธุรกิจดิจิทัล','Internship in Digital Business Computer',3,3,0,0],
['1223404','การเตรียมสหกิจศึกษาทางคอมพิวเตอร์ธุรกิจดิจิทัล','Pre-Cooperative Education in Digital Business Computer',1,1,1,2],
['1223405','สหกิจศึกษาทางคอมพิวเตอร์ธุรกิจดิจิทัล','Cooperative Education in Digital Business Computer',6,5,60,0],
];
foreach ($courses as $i => $c) {
add_course($pdo, $dbc_prof_id, $c[0], $c[1], $c[2], $c[3], $c[4], $c[5], $c[6], $i+1);
}
echo "✅ Curriculum data imported successfully!\n";
+6
View File
@@ -0,0 +1,6 @@
</main>
<script src="<?= BASE_URL ?>/assets/js/bootstrap.bundle.min.js"></script>
<script src="<?= BASE_URL ?>/assets/js/jquery.min.js"></script>
<script src="<?= BASE_URL ?>/assets/js/script.js"></script>
</body>
</html>
+741
View File
@@ -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
];
}
+81
View File
@@ -0,0 +1,81 @@
<?php requireLogin(); ?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= isset($page_title) ? htmlspecialchars($page_title) . ' - ' : '' ?>ระบบใบควบคุมผลการเรียน</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link href="<?= BASE_URL ?>/assets/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<link href="<?= BASE_URL ?>/assets/css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark app-navbar sticky-top">
<div class="container-fluid">
<a class="navbar-brand" href="<?= BASE_URL ?>/index.php">
<i class="bi bi-journal-text"></i> ระบบใบควบคุมผลการเรียน
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="<?= BASE_URL ?>/index.php">
<i class="bi bi-speedometer2"></i> หน้าหลัก
</a>
</li>
<?php if (!isStudent()): ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">
<i class="bi bi-book"></i> หลักสูตร
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="<?= BASE_URL ?>/curriculum/dt.php">เทคโนโลยีดิจิทัล (DT)</a></li>
<li><a class="dropdown-item" href="<?= BASE_URL ?>/curriculum/dbc.php">คอมพิวเตอร์ธุรกิจดิจิทัล (DBC)</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="<?= BASE_URL ?>/students/list.php">
<i class="bi bi-people"></i> จัดการนักศึกษา
</a>
</li>
<?php endif; ?>
<?php if (isAdmin()): ?>
<li class="nav-item">
<a class="nav-link" href="<?= BASE_URL ?>/users/list.php">
<i class="bi bi-person-gear"></i> จัดการผู้ใช้
</a>
</li>
<?php endif; ?>
</ul>
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle user-chip" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> <?= htmlspecialchars($_SESSION['username'] ?? '') ?>
<span class="badge bg-light text-primary ms-1" style="font-size: 0.72rem; vertical-align: middle;">
<?= $_SESSION['role'] === 'admin' ? 'ผู้ดูแลระบบ' : ($_SESSION['role'] === 'student' ? 'นักศึกษา' : 'อาจารย์ที่ปรึกษา') ?>
</span>
</a>
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2">
<?php if (isset($_SESSION['user_id']) && $_SESSION['user_id'] !== null): ?>
<?php if (isStudent()): ?>
<li><a class="dropdown-item py-2" href="<?= BASE_URL ?>/students/view.php?id=<?= $_SESSION['user_id'] ?>"><i class="bi bi-person-bounding-box me-2"></i> ข้อมูลของฉัน</a></li>
<li><a class="dropdown-item py-2" href="<?= BASE_URL ?>/students/edit.php?id=<?= $_SESSION['user_id'] ?>"><i class="bi bi-pencil-square me-2"></i> แก้ไขข้อมูลของฉัน</a></li>
<?php else: ?>
<li><a class="dropdown-item py-2" href="<?= BASE_URL ?>/users/profile.php"><i class="bi bi-person-bounding-box me-2"></i> ข้อมูลส่วนตัว</a></li>
<li><a class="dropdown-item py-2" href="<?= BASE_URL ?>/users/profile_edit.php"><i class="bi bi-pencil-square me-2"></i> แก้ไขข้อมูลส่วนตัว</a></li>
<?php endif; ?>
<li><hr class="dropdown-divider"></li>
<?php endif; ?>
<li><a class="dropdown-item py-2 text-danger" href="<?= BASE_URL ?>/logout.php"><i class="bi bi-box-arrow-right me-2"></i> ออกจากระบบ</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="app-shell container-fluid">
+226
View File
@@ -0,0 +1,226 @@
<?php
$page_title = 'หน้าหลัก';
require_once __DIR__ . '/includes/functions.php';
require_once __DIR__ . '/includes/header.php';
$curricula = getCurricula();
$students = getStudents();
$dt_count = 0; $dbc_count = 0;
$total_transfer_credits = 0;
foreach ($students as $s) {
if ($s['curriculum_code'] == 'DT') $dt_count++;
if ($s['curriculum_code'] == 'DBC') $dbc_count++;
$total_transfer_credits += getTransferCreditsTotal($s['id']);
}
?>
<?php
$role_label = $_SESSION['role'] === 'admin' ? 'ผู้ดูแลระบบ' : ($_SESSION['role'] === 'student' ? 'นักศึกษา' : 'อาจารย์ที่ปรึกษา');
if (isStudent()) {
$student_id = getCurrentUserId();
$student = getStudent($student_id);
$summary = $student ? getStudentSummary($student_id) : null;
$transfer_total = $student ? getTransferCreditsTotal($student_id) : 0;
$completed_total = $student ? getCompletedCreditsTotal($student_id) : 0;
}
?>
<div class="row g-4 mb-4">
<?php if (!isStudent()): ?>
<div class="col-md-6 col-xl">
<div class="card stat-card card-curriculum">
<div class="card-body">
<div>
<div class="stat-label">หลักสูตร</div>
<div class="stat-value"><?= count($curricula) ?></div>
</div>
<div class="stat-img-wrapper">
<img src="<?= BASE_URL ?>/pic/dashboard_curriculum.png" alt="หลักสูตร" class="stat-img">
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl">
<div class="card stat-card card-dt">
<div class="card-body">
<div>
<div class="stat-label">นักศึกษา DT</div>
<div class="stat-value"><?= $dt_count ?></div>
</div>
<div class="stat-img-wrapper">
<img src="<?= BASE_URL ?>/pic/dashboard_student_dt.png" alt="นักศึกษา DT" class="stat-img">
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl">
<div class="card stat-card card-dbc">
<div class="card-body">
<div>
<div class="stat-label">นักศึกษา DBC</div>
<div class="stat-value"><?= $dbc_count ?></div>
</div>
<div class="stat-img-wrapper">
<img src="<?= BASE_URL ?>/pic/dashboard_student_dbc.png" alt="นักศึกษา DBC" class="stat-img">
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if (isStudent()): ?>
<div class="col-md-6 col-xl">
<div class="card stat-card card-all">
<div class="card-body">
<div>
<div class="stat-label">หน่วยกิตที่เรียนแล้ว</div>
<div class="stat-value"><?= $completed_total ?></div>
</div>
<div class="stat-img-wrapper">
<img src="<?= BASE_URL ?>/pic/dashboard_student_all.png" alt="หน่วยกิต" class="stat-img">
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl">
<div class="card stat-card card-transfer">
<div class="card-body">
<div>
<div class="stat-label">หน่วยกิตเทียบโอน</div>
<div class="stat-value"><?= $transfer_total ?></div>
</div>
<div class="stat-img-wrapper">
<img src="<?= BASE_URL ?>/pic/dashboard_transfer_credits.png" alt="หน่วยกิตเทียบโอน" class="stat-img">
</div>
</div>
</div>
</div>
<?php else: ?>
<div class="col-md-6 col-xl">
<div class="card stat-card card-all">
<div class="card-body">
<div>
<div class="stat-label">นักศึกษาทั้งหมด</div>
<div class="stat-value"><?= count($students) ?></div>
</div>
<div class="stat-img-wrapper">
<img src="<?= BASE_URL ?>/pic/dashboard_student_all.png" alt="นักศึกษาทั้งหมด" class="stat-img">
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl">
<div class="card stat-card card-transfer">
<div class="card-body">
<div>
<div class="stat-label">หน่วยกิตเทียบโอนรวม</div>
<div class="stat-value"><?= $total_transfer_credits ?></div>
</div>
<div class="stat-img-wrapper">
<img src="<?= BASE_URL ?>/pic/dashboard_transfer_credits.png" alt="หน่วยกิตเทียบโอนรวม" class="stat-img">
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php if (isStudent() && $summary): ?>
<div class="row g-4">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-primary text-white">
<i class="bi bi-person"></i> ข้อมูลของฉัน
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<small class="text-muted">รหัสนักศึกษา</small>
<div class="fw-bold"><?= htmlspecialchars($student['student_code']) ?></div>
</div>
<div class="col-md-3">
<small class="text-muted">ชื่อ-นามสกุล</small>
<div class="fw-bold"><?= htmlspecialchars($student['name_th']) ?></div>
</div>
<div class="col-md-3">
<small class="text-muted">สาขาวิชา</small>
<div class="fw-bold"><?= htmlspecialchars($student['curriculum_code'] ?? '') ?> - <?= htmlspecialchars($student['curriculum_name'] ?? '') ?></div>
</div>
<div class="col-md-3">
<small class="text-muted">ปีการศึกษา</small>
<div class="fw-bold"><?= $student['enrollment_year'] ?></div>
</div>
</div>
<hr>
<a href="<?= BASE_URL ?>/students/view.php?id=<?= $student['id'] ?>" class="btn btn-primary">
<i class="bi bi-eye"></i> ดูผลการเรียนของฉัน
</a>
</div>
</div>
</div>
</div>
<?php else: ?>
<div class="row g-4">
<div class="col-md-4">
<div class="card">
<div class="card-header bg-primary text-white">
<i class="bi bi-book"></i> หลักสูตร
</div>
<div class="card-body">
<div class="list-group">
<?php foreach ($curricula as $c): ?>
<a href="<?= BASE_URL ?>/curriculum/<?= strtolower($c['code']) ?>.php" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
<div>
<strong><?= htmlspecialchars($c['name_th']) ?></strong>
<small class="text-muted d-block"><?= htmlspecialchars($c['code']) ?> - <?= htmlspecialchars($c['name_en']) ?></small>
</div>
<span class="badge bg-primary rounded-pill"><?= $c['total_credits'] ?> หน่วยกิต</span>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-primary text-white">
<i class="bi bi-people"></i> จัดการนักศึกษา
</div>
<div class="card-body">
<a href="<?= BASE_URL ?>/students/add.php" class="btn btn-success mb-2">
<i class="bi bi-person-plus"></i> เพิ่มนักศึกษา
</a>
<a href="<?= BASE_URL ?>/students/list.php" class="btn btn-primary mb-2">
<i class="bi bi-list"></i> ดูรายชื่อนักศึกษา
</a>
<hr>
<p class="text-muted mb-0 small">เลือกนักศึกษาเพื่อดูข้อมูลและจัดการรายวิชา:</p>
<select class="form-select mt-2" id="quickStudent" onchange="if(this.value) window.location.href='<?= BASE_URL ?>/students/view.php?id='+this.value;">
<option value="">-- เลือกนักศึกษา --</option>
<?php foreach ($students as $s): ?>
<option value="<?= $s['id'] ?>"><?= htmlspecialchars($s['student_code']) ?> - <?= htmlspecialchars($s['name_th']) ?> (<?= htmlspecialchars($s['curriculum_code']) ?>)</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<?php if (isAdmin()): ?>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-primary text-white">
<i class="bi bi-person-gear"></i> จัดการผู้ใช้
</div>
<div class="card-body">
<a href="<?= BASE_URL ?>/users/list.php" class="btn btn-primary mb-2">
<i class="bi bi-list"></i> รายชื่อผู้ใช้ทั้งหมด
</a>
<a href="<?= BASE_URL ?>/users/add.php" class="btn btn-success mb-2">
<i class="bi bi-person-plus"></i> เพิ่มผู้ใช้
</a>
<hr>
<p class="text-muted mb-0 small">จัดการบัญชีผู้ใช้สำหรับเข้าใช้งานระบบ</p>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php require_once __DIR__ . '/includes/footer.php'; ?>
+102
View File
@@ -0,0 +1,102 @@
<?php
require_once __DIR__ . '/config.php';
try {
// สร้างฐานข้อมูล
$pdo = new PDO("mysql:host=" . DB_HOST, DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("CREATE DATABASE IF NOT EXISTS `" . DB_NAME . "` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$pdo->exec("USE `" . DB_NAME . "`");
// สร้างตาราง
$pdo->exec("
CREATE TABLE IF NOT EXISTS curricula (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(10) NOT NULL UNIQUE,
name_th VARCHAR(255) NOT NULL,
name_en VARCHAR(255) NOT NULL,
total_credits INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
$pdo->exec("
CREATE TABLE IF NOT EXISTS course_groups (
id INT AUTO_INCREMENT PRIMARY KEY,
curriculum_id INT NOT NULL,
parent_id INT DEFAULT NULL,
code VARCHAR(50) NOT NULL,
name_th VARCHAR(255) NOT NULL,
min_credits INT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
FOREIGN KEY (curriculum_id) REFERENCES curricula(id) ON DELETE CASCADE,
FOREIGN KEY (parent_id) REFERENCES course_groups(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
$pdo->exec("
CREATE TABLE IF NOT EXISTS courses (
id INT AUTO_INCREMENT PRIMARY KEY,
group_id INT NOT NULL,
code VARCHAR(20) NOT NULL UNIQUE,
name_th VARCHAR(255) NOT NULL,
name_en VARCHAR(255) DEFAULT '',
credits DECIMAL(3,1) NOT NULL,
lecture_hours INT NOT NULL DEFAULT 0,
practice_hours INT NOT NULL DEFAULT 0,
self_study_hours INT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
FOREIGN KEY (group_id) REFERENCES course_groups(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
$pdo->exec("
CREATE TABLE IF NOT EXISTS students (
id INT AUTO_INCREMENT PRIMARY KEY,
student_code VARCHAR(20) NOT NULL UNIQUE,
name_th VARCHAR(255) NOT NULL,
name_en VARCHAR(255) DEFAULT '',
faculty VARCHAR(255) DEFAULT '',
graduated_institution VARCHAR(255) DEFAULT '',
curriculum_id INT NOT NULL,
enrollment_year INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (curriculum_id) REFERENCES curricula(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
$pdo->exec("
CREATE TABLE IF NOT EXISTS student_courses (
id INT AUTO_INCREMENT PRIMARY KEY,
student_id INT NOT NULL,
course_id INT DEFAULT NULL,
course_code VARCHAR(20) NOT NULL,
course_name_th VARCHAR(255) NOT NULL,
credits DECIMAL(3,1) NOT NULL,
lecture_hours INT NOT NULL DEFAULT 0,
practice_hours INT NOT NULL DEFAULT 0,
self_study_hours INT NOT NULL DEFAULT 0,
source_type ENUM('transfer','manual') NOT NULL DEFAULT 'manual',
source_file VARCHAR(255) DEFAULT NULL,
notes TEXT DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
echo "✅ Database and tables created successfully!\n";
// ตรวจสอบว่ามีข้อมูลหลักสูตรหรือยัง
$stmt = $pdo->query("SELECT COUNT(*) FROM curricula");
if ($stmt->fetchColumn() == 0) {
echo "⏳ Importing curriculum data...\n";
require_once __DIR__ . '/import_curriculum.php';
echo "✅ Curriculum data imported!\n";
} else {
echo "️ Curriculum data already exists, skipping import.\n";
}
} catch (PDOException $e) {
die("❌ Error: " . $e->getMessage() . "\n");
}
+100
View File
@@ -0,0 +1,100 @@
<?php
require_once __DIR__ . '/includes/functions.php';
$error = '';
if (!isset($_GET['state']) || !isset($_SESSION['google_state']) || $_GET['state'] !== $_SESSION['google_state']) {
$error = 'คำขอไม่ถูกต้อง (invalid state)';
}
unset($_SESSION['google_state']);
if (empty($error) && isset($_GET['code'])) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://oauth2.googleapis.com/token',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'code' => $_GET['code'],
'client_id' => GOOGLE_CLIENT_ID,
'client_secret' => GOOGLE_CLIENT_SECRET,
'redirect_uri' => GOOGLE_REDIRECT_URI,
'grant_type' => 'authorization_code',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code !== 200) {
$error = 'ไม่สามารถยืนยันตัวตนกับ Google ได้';
} else {
$token_data = json_decode($response, true);
if (!isset($token_data['id_token'])) {
$error = 'ไม่ได้รับข้อมูลจาก Google';
} else {
$parts = explode('.', $token_data['id_token']);
$encoded = strtr($parts[1], '-_', '+/');
$encoded = str_pad($encoded, strlen($encoded) % 4 ? 4 - strlen($encoded) % 4 + strlen($encoded) : strlen($encoded), '=', STR_PAD_RIGHT);
$payload = json_decode(base64_decode($encoded), true);
$email = $payload['email'] ?? '';
$google_id = $payload['sub'] ?? '';
if (empty($email)) {
$error = 'ไม่ได้รับอีเมลจาก Google';
} else {
$db = getDB();
$stmt = $db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user) {
if (empty($user['google_id'])) {
$stmt = $db->prepare("UPDATE users SET google_id = ? WHERE id = ?");
$stmt->execute([$google_id, $user['id']]);
}
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $user['name'];
$_SESSION['user_id'] = $user['id'];
$_SESSION['staff_code'] = $user['staff_code'];
$_SESSION['role'] = $user['role'] ?? 'advisor';
$_SESSION['curriculum_id'] = $user['curriculum_id'];
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$stmt = $db->prepare("SELECT * FROM students WHERE email = ?");
$stmt->execute([$email]);
$student = $stmt->fetch();
if ($student) {
if (empty($student['google_id'])) {
try {
$stmt = $db->prepare("UPDATE students SET google_id = ? WHERE id = ?");
$stmt->execute([$google_id, $student['id']]);
} catch (PDOException $e) {
// column may not exist yet
}
}
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $student['name_th'];
$_SESSION['user_id'] = $student['id'];
$_SESSION['student_code'] = $student['student_code'];
$_SESSION['role'] = 'student';
$_SESSION['curriculum_id'] = $student['curriculum_id'];
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$error = 'ไม่พบอีเมล ' . htmlspecialchars($email) . ' ในระบบ กรุณาติดต่อผู้ดูแลระบบ';
}
}
}
}
$_SESSION['google_login_error'] = $error;
header('Location: ' . BASE_URL . '/login.php');
exit;
+22
View File
@@ -0,0 +1,22 @@
<?php
require_once __DIR__ . '/config.php';
if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$state = bin2hex(random_bytes(16));
$_SESSION['google_state'] = $state;
$params = [
'client_id' => GOOGLE_CLIENT_ID,
'redirect_uri' => GOOGLE_REDIRECT_URI,
'response_type' => 'code',
'scope' => 'openid email profile',
'access_type' => 'online',
'state' => $state,
];
header('Location: https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query($params));
exit;
+193
View File
@@ -0,0 +1,193 @@
<?php
require_once __DIR__ . '/config.php';
if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$error = '';
$google_error = $_SESSION['google_login_error'] ?? '';
unset($_SESSION['google_login_error']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');
if ($username === ADMIN_USER && $password === ADMIN_PASS) {
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $username;
$_SESSION['role'] = 'admin';
$_SESSION['user_id'] = null;
$_SESSION['curriculum_id'] = null;
header('Location: ' . BASE_URL . '/index.php');
exit;
}
require_once __DIR__ . '/includes/functions.php';
$user = authenticateUser($username, $password);
if ($user) {
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $user['name'];
$_SESSION['user_id'] = $user['id'];
$_SESSION['staff_code'] = $user['staff_code'];
$_SESSION['role'] = $user['role'] ?? 'advisor';
$_SESSION['curriculum_id'] = $user['curriculum_id'];
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$error = 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง';
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>เข้าสู่ระบบ - ระบบใบควบคุมผลการเรียน</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { 'thai': ['"Noto Sans Thai"', 'sans-serif'] },
colors: {
primary: { 50:'#eff6ff',100:'#dbeafe',200:'#bfdbfe',300:'#93c5fd',400:'#60a5fa',500:'#3b82f6',600:'#2563eb',700:'#1d4ed8',800:'#1e40af',900:'#1e3a8a' }
}
}
}
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
* { font-family: 'Noto Sans Thai', sans-serif; }
.fade-in { animation: fadeIn 0.6s ease-in; }
@keyframes fadeIn { from{opacity:0;transform:translateY(20px)} to{opacity:1;transform:translateY(0)} }
input:focus { outline: none; border-color: #fbbf24 !important; box-shadow: 0 0 0 3px rgba(251,191,36,0.3) !important; }
</style>
</head>
<body class="font-thai min-h-screen flex flex-col bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100">
<!-- Navbar -->
<nav class="bg-gradient-to-r from-blue-900 via-blue-800 to-blue-700 shadow-xl">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center gap-3">
<a href="<?= BASE_URL ?>/login.php" class="flex items-center gap-3">
<img src="<?= BASE_URL ?>/pic/Logo DIT.png" alt="DIT Logo" class="h-10 w-10 rounded-full bg-white p-0.5 shadow-md">
<div class="hidden sm:block">
<h1 class="text-white font-bold text-sm leading-tight">คณะนวัตกรรมดิจิทัลเทคโนโลยี</h1>
<p class="text-blue-200 text-xs">มหาวิทยาลัยตาปี</p>
</div>
</a>
</div>
<div>
<span class="text-white/70 text-sm"><i class="fas fa-journal-text mr-1"></i> ระบบใบควบคุมผลการเรียนนักศึกษา</span>
</div>
</div>
</div>
</nav>
<div class="h-1 bg-gradient-to-r from-yellow-300 via-yellow-400 to-yellow-500"></div>
<main class="flex-1 flex items-center justify-center py-12 px-4">
<div class="w-full max-w-md fade-in">
<!-- Logo row -->
<div class="text-center mb-8">
<div class="flex items-center justify-center gap-6 mb-5">
<img src="<?= BASE_URL ?>/pic/logo.png">
<img src="<?= BASE_URL ?>/pic/Logo DIT.png" >
</div>
<h1 class="text-2xl font-bold text-gray-800">มหาวิทยาลัยตาปี</h1>
<p class="text-gray-500">คณะนวัตกรรมดิจิทัลเทคโนโลยี</p>
<div class="flex items-center justify-center gap-2 mt-4">
<div class="h-px w-12 bg-gradient-to-r from-transparent via-yellow-400 to-transparent"></div>
<span class="inline-flex items-center gap-2 bg-blue-50 text-blue-700 text-base font-bold px-6 py-2.5 rounded-full border border-blue-200 shadow-sm">
<i class="fas fa-journal-text text-blue-500"></i> ระบบใบควบคุมผลการเรียนนักศึกษา
</span>
<div class="h-px w-12 bg-gradient-to-r from-transparent via-yellow-400 to-transparent"></div>
</div>
</div>
<!-- Login Card -->
<div class="bg-white rounded-2xl shadow-xl p-8 border-t-4 border-yellow-400">
<h2 class="text-2xl font-bold text-gray-800 mb-1">เข้าสู่ระบบ</h2>
<p class="text-gray-400 text-sm mb-6">กรุณาใส่ชื่อผู้ใช้และรหัสผ่าน</p>
<?php if ($error): ?>
<div class="bg-red-50 border-l-4 border-red-500 text-red-700 px-4 py-3 rounded-lg mb-4 text-sm flex items-center gap-2">
<i class="fas fa-exclamation-circle"></i>
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<?php if ($google_error): ?>
<div class="bg-red-50 border-l-4 border-red-500 text-red-700 px-4 py-3 rounded-lg mb-4 text-sm flex items-center gap-2">
<i class="fas fa-exclamation-circle"></i>
<?= htmlspecialchars($google_error) ?>
</div>
<?php endif; ?>
<form method="POST">
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1"><i class="fas fa-user text-gray-400 mr-1"></i> ชื่อผู้ใช้</label>
<input type="text" name="username" class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl text-gray-700 focus:border-yellow-400 focus:ring-4 focus:ring-yellow-100 transition-all duration-300" required autofocus>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-1"><i class="fas fa-lock text-gray-400 mr-1"></i> รหัสผ่าน</label>
<input type="password" name="password" class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl text-gray-700 focus:border-yellow-400 focus:ring-4 focus:ring-yellow-100 transition-all duration-300" required>
</div>
<button type="submit" class="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-semibold py-3 px-6 rounded-xl transition-all duration-300 shadow-md hover:shadow-lg flex items-center justify-center gap-2">
<i class="fas fa-sign-in-alt"></i> เข้าสู่ระบบ
</button>
</form>
<div class="relative my-6">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-200"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-3 bg-white text-gray-400 font-medium">หรือ</span>
</div>
</div>
<a href="<?= BASE_URL ?>/login-google.php"
class="w-full flex items-center justify-center gap-3 bg-white hover:bg-gray-50 text-gray-700 font-medium py-3 px-6 rounded-xl border-2 border-gray-200 transition-all duration-300 shadow-sm hover:shadow-md">
<svg class="w-5 h-5" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
เข้าสู่ระบบด้วยอีเมลมหาวิทยาลัยตาปี
</a>
</div>
<p class="text-center text-gray-400 text-xs mt-6">
<i class="fas fa-journal-text mr-1"></i> ระบบใบควบคุมผลการเรียน มหาวิทยาลัยตาปี
</p>
</div>
</main>
<!-- Footer -->
<footer class="bg-gradient-to-r from-blue-900 via-blue-800 to-blue-700 text-white mt-auto">
<div class="h-1 bg-gradient-to-r from-yellow-300 via-yellow-400 to-yellow-500"></div>
<div class="max-w-7xl mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-3">
<img src="<?= BASE_URL ?>/pic/Logo DIT.png" alt="Logo" class="h-10 w-10 rounded-full bg-white p-0.5" onerror="this.style.display='none'">
<div>
<p class="font-semibold text-sm">คณะนวัตกรรมดิจิทัลเทคโนโลยี</p>
<p class="text-blue-200 text-xs">มหาวิทยาลัยตาปี</p>
</div>
</div>
<p class="text-blue-300 text-xs">&copy; 2569 มหาวิทยาลัยตาปี</p>
</div>
</div>
</footer>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
<?php
require_once __DIR__ . '/config.php';
session_destroy();
header('Location: ' . BASE_URL . '/login.php');
exit;
+45
View File
@@ -0,0 +1,45 @@
<?php
require_once __DIR__ . '/db.php';
try {
$db = getDB();
// Users table
try {
$db->exec("ALTER TABLE users ADD COLUMN google_id VARCHAR(255) DEFAULT NULL AFTER email");
echo "✅ Added google_id to users table\n";
} catch (PDOException $e) {
if (strpos($e->getMessage(), 'Duplicate column') !== false) {
echo "️ google_id already exists in users\n";
} else {
throw $e;
}
}
// Students table
$columns = [
'email' => "ALTER TABLE students ADD COLUMN email VARCHAR(255) DEFAULT NULL AFTER name_en",
'password' => "ALTER TABLE students ADD COLUMN password VARCHAR(255) DEFAULT NULL AFTER email",
'google_id' => "ALTER TABLE students ADD COLUMN google_id VARCHAR(255) DEFAULT NULL AFTER password",
'advisor_id' => "ALTER TABLE students ADD COLUMN advisor_id INT DEFAULT NULL AFTER curriculum_id",
'previous_qualification' => "ALTER TABLE students ADD COLUMN previous_qualification VARCHAR(255) DEFAULT NULL AFTER graduated_institution",
];
foreach ($columns as $name => $sql) {
try {
$db->exec($sql);
echo "✅ Added $name to students table\n";
} catch (PDOException $e) {
if (strpos($e->getMessage(), 'Duplicate column') !== false) {
echo "$name already exists in students\n";
} else {
throw $e;
}
}
}
echo "\n✅ Migration completed successfully!\n";
} catch (Exception $e) {
die("❌ Error: " . $e->getMessage() . "\n");
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

+260
View File
@@ -0,0 +1,260 @@
<?php
$page_title = 'สรุปผลการเรียน';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$student_id = intval($_GET['student_id'] ?? 0);
requireStudentAccess($student_id);
$student = getStudent($student_id);
if (!$student) {
die('ไม่พบข้อมูลนักศึกษา');
}
$summary = getStudentSummary($student_id);
$student_courses = getStudentCourses($student_id);
$completed_map = $summary['completed_map'];
$total_curriculum_credits = getTotalCurriculumCredits($student['curriculum_id']);
$transfer_credits_total = getTransferCreditsTotal($student_id);
$total_completed = getCompletedCreditsTotal($student_id);
$is_dbc = ($student['curriculum_code'] == 'DBC');
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>สรุปผลการเรียน - <?= htmlspecialchars($student['student_code']) ?></title>
<style>
@media print { .no-print { display: none; } body { font-family: 'TH Sarabun New', 'Leelawadee', 'Tahoma', sans-serif; } }
body {
font-family: 'TH Sarabun New', 'Leelawadee', 'Tahoma', sans-serif;
font-size: 14px; padding: 20px; color: #333;
}
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 20px; }
.header h1 { font-size: 22px; margin: 0; }
.header h2 { font-size: 18px; margin: 5px 0; font-weight: normal; }
.student-info { margin-bottom: 15px; }
.student-info table { width: 100%; }
.student-info td { padding: 2px 10px; }
.progress-bar { background: #e9ecef; border-radius: 10px; height: 20px; margin: 10px 0; overflow: hidden; }
.progress-bar-fill { background: #28a745; height: 100%; border-radius: 10px; text-align: center; color: white; line-height: 20px; font-size: 12px; }
.section { margin-bottom: 15px; }
.section-title { background: #0d6efd; color: white; padding: 5px 10px; font-size: 15px; font-weight: bold; }
.section-title.dbc { background: #198754; }
.subsection-title { background: #e9ecef; padding: 3px 10px; margin: 5px 0; font-size: 13px; font-weight: bold; color: #555; }
.subsubsection-title { font-weight: bold; color: #666; margin: 3px 0; font-size: 12px; }
table { width: 100%; border-collapse: collapse; margin: 5px 0; }
th, td { border: 1px solid #ccc; padding: 3px 6px; text-align: left; font-size: 13px; }
th { background: #f8f9fa; }
.center { text-align: center; }
.completed { background: #d4edda; }
.transfer { background: #fff3cd; }
.pending { color: #999; }
.summary-row { background: #f0f0f0; font-weight: bold; }
.footer { text-align: center; margin-top: 30px; padding-top: 10px; border-top: 1px solid #ccc; font-size: 12px; color: #666; }
.btn-print { display: inline-block; padding: 8px 16px; background: #0d6efd; color: white; text-decoration: none; border-radius: 4px; margin-bottom: 15px; }
.btn-print:hover { background: #0b5ed7; }
.conclusion { text-align: center; font-size: 16px; margin: 15px 0; padding: 10px; border: 2px solid #28a745; border-radius: 5px; }
.credit-cards { display: flex; gap: 10px; margin-bottom: 15px; }
.credit-card { flex: 1; padding: 8px; border-radius: 5px; text-align: center; font-size: 18px; }
</style>
</head>
<body>
<div class="no-print" style="margin-bottom:10px;">
<a href="#" onclick="window.print();return false;" class="btn-print">🖨️ พิมพ์ / บันทึกเป็น PDF</a>
<a href="summary_grouped_pdf.php?student_id=<?= $student_id ?>" class="btn-print" style="background:#6f42c1;">📊 สรุปผลการเรียนแยกตามโครงสร้าง</a>
<a href="transfer_report.php?student_id=<?= $student_id ?>" class="btn-print" style="background:#20c997;">📋 ผลการประเมินเทียบโอน</a>
<a href="../students/view.php?id=<?= $student_id ?>" class="btn-print" style="background:#6c757d;">⬅ กลับ</a>
</div>
<div class="header">
<h1>ใบควบคุมผลการเรียน</h1>
<h2>มหาวิทยาลัยตาปี</h2>
</div>
<div class="student-info">
<table>
<tr><td style="width:140px;"><strong>รหัสนักศึกษา:</strong></td><td><?= htmlspecialchars($student['student_code']) ?></td></tr>
<tr><td><strong>ชื่อ-นามสกุล:</strong></td><td><?= htmlspecialchars($student['name_th']) ?> (<?= htmlspecialchars($student['name_en']) ?>)</td></tr>
<tr><td><strong>สาขาวิชา:</strong></td><td><?= htmlspecialchars($student['curriculum_code']) ?> - <?= htmlspecialchars($student['curriculum_name']) ?></td></tr>
<tr><td><strong>คณะ:</strong></td><td><?= htmlspecialchars($student['faculty'] ?: '-') ?></td></tr>
<tr><td><strong>สถาบันที่จบ:</strong></td><td><?= htmlspecialchars($student['graduated_institution'] ?: '-') ?></td></tr>
<tr><td><strong>ปีการศึกษา:</strong></td><td><?= $student['enrollment_year'] ?></td></tr>
</table>
</div>
<div class="progress-bar">
<?php $pct = $total_curriculum_credits > 0 ? round(($total_completed / $total_curriculum_credits) * 100) : 0; ?>
<div class="progress-bar-fill" style="width:<?= min(100, $pct) ?>%;">
<?= $total_completed ?> / <?= $total_curriculum_credits ?> หน่วยกิต (<?= $pct ?>%)
</div>
</div>
<div class="credit-cards">
<div class="credit-card" style="background:#ffc107;">
<strong><?= $transfer_credits_total ?></strong><br><small>หน่วยกิตเทียบโอน</small>
</div>
<div class="credit-card" style="background:#28a745;color:white;">
<strong><?= $total_completed ?></strong><br><small>หน่วยกิตเรียนผ่านแล้ว</small>
</div>
<div class="credit-card" style="background:#0d6efd;color:white;">
<strong><?= max(0, $total_curriculum_credits - $total_completed) ?></strong><br><small>หน่วยกิตคงเหลือ</small>
</div>
</div>
<?php renderPrintTree($summary['groups'], $is_dbc); ?>
<div class="section">
<div class="section-title" style="background:#6c757d;">สรุปรายวิชาที่เรียนผ่านแล้วทั้งหมด (แยกตามโครงสร้างหลักสูตร)</div>
<?php renderGroupedSummary($summary['groups'], $is_dbc); ?>
<table style="margin-top:10px;">
<tr class="summary-row">
<td colspan="2" class="center">รวมหน่วยกิตที่เรียนผ่านแล้วทั้งหมด</td>
<td class="center"><?= $total_completed ?> หน่วยกิต</td><td colspan="4"></td>
</tr>
</table>
</div>
<?php $remaining = max(0, $total_curriculum_credits - $total_completed); ?>
<div class="conclusion" style="border-color: <?= $remaining <= 0 ? '#28a745' : '#ffc107' ?>;">
<?php if ($remaining <= 0): ?>
<strong style="color:#28a745;">✓ ครบตามหลักสูตรแล้ว (<?= $total_completed ?> / <?= $total_curriculum_credits ?> หน่วยกิต)</strong>
<?php else: ?>
<strong>หน่วยกิตคงเหลือที่ต้องเรียน: <?= $remaining ?> หน่วยกิต (<?= $total_completed ?> / <?= $total_curriculum_credits ?> หน่วยกิต)</strong>
<?php endif; ?>
</div>
<div class="footer">
ระบบใบควบคุมผลการเรียน มหาวิทยาลัยตาปี<br>
พิมพ์เมื่อ <?= date('d/m/Y H:i') ?> น.
</div>
</body>
</html>
<?php
function renderPrintTree($nodes, $is_dbc, $level = 0) {
foreach ($nodes as $node) {
$g = $node['group'];
$has_courses = !empty($node['courses']);
$has_children = !empty($node['children']);
$style = $level == 0 ? 'section-title' : ($level == 1 ? 'subsection-title' : 'subsubsection-title');
echo '<div class="section" style="margin-left:' . ($level * 10) . 'px;">';
echo '<div class="' . $style . '">' . htmlspecialchars($g['name_th']);
if ($g['min_credits'] > 0) echo ' (ไม่น้อยกว่า ' . $g['min_credits'] . ' หน่วยกิต)';
echo '</div>';
if ($has_courses) {
echo '<table style="table-layout: fixed; width: 100%;">';
echo '<thead>';
echo '<tr>';
echo '<th class="center" style="width: 13%;">รหัสวิชา</th>';
echo '<th style="width: 38%;">ชื่อวิชา</th>';
echo '<th class="center" style="width: 17%;">หน่วยกิต(ท-ป-อ)</th>';
echo '<th class="center" style="width: 10%;">ภาคเรียน</th>';
echo '<th class="center" style="width: 10%;">ปีการศึกษา</th>';
echo '<th class="center" style="width: 12%;">สถานะ</th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
foreach ($node['courses'] as $course) {
$row_class = '';
$status_text = 'ยังไม่เรียน';
$sem = $yr = '-';
if (isset($course['_completed'])) {
$row_class = $course['_completed']['source_type'] == 'transfer' ? 'transfer' : 'completed';
$status_text = $course['_completed']['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียนแล้ว';
$sem = $course['_completed']['semester'] ? 'ภาค ' . htmlspecialchars($course['_completed']['semester']) : '-';
$yr = htmlspecialchars($course['_completed']['academic_year'] ?: '-');
}
echo '<tr class="' . $row_class . '">';
echo '<td class="center"><code>' . htmlspecialchars($course['code']) . '</code></td>';
echo '<td>' . htmlspecialchars($course['name_th']) . '</td>';
echo '<td class="center">' . number_format($course['credits'], 1) . '(' . $course['lecture_hours'] . '-' . $course['practice_hours'] . '-' . $course['self_study_hours'] . ')</td>';
echo '<td class="center">' . $sem . '</td>';
echo '<td class="center">' . $yr . '</td>';
echo '<td class="center bold">' . $status_text . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
if ($has_children) {
renderPrintTree($node['children'], $is_dbc, $level + 1);
}
echo '</div>';
}
}
function renderGroupedSummary($nodes, $level = 0) {
foreach ($nodes as $node) {
$g = $node['group'];
$has_completed = $node['completed_credits'] > 0;
$has_courses = !empty($node['courses']);
$has_children = !empty($node['children']);
if (!$has_completed && !$has_children) {
continue;
}
$style = $level == 0 ? 'section-title' : ($level == 1 ? 'subsection-title' : 'subsubsection-title');
echo '<div style="margin-left:' . ($level * 10) . 'px;">';
echo '<div class="' . $style . '">' . htmlspecialchars($g['name_th']);
if ($g['min_credits'] > 0) echo ' (ไม่น้อยกว่า ' . $g['min_credits'] . ' หน่วยกิต)';
if ($level == 0 && $has_completed) {
echo ' <span style="font-weight:normal;font-size:12px;">— เรียนผ่านแล้ว ' . $node['completed_credits'] . ' หน่วยกิต</span>';
}
echo '</div>';
if ($has_courses) {
$group_total = 0;
echo '<table style="table-layout: fixed; width: 100%;">';
echo '<thead>';
echo '<tr>';
echo '<th class="center" style="width: 13%;">รหัสวิชา</th>';
echo '<th style="width: 41%;">ชื่อวิชา</th>';
echo '<th class="center" style="width: 10%;">หน่วยกิต</th>';
echo '<th class="center" style="width: 8%;">เกรด</th>';
echo '<th class="center" style="width: 10%;">ภาคเรียน</th>';
echo '<th class="center" style="width: 10%;">ปีการศึกษา</th>';
echo '<th class="center" style="width: 8%;">ประเภท</th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
foreach ($node['courses'] as $course) {
if (!isset($course['_completed'])) continue;
$sc = $course['_completed'];
$group_total += $sc['credits'];
$type = htmlspecialchars($sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียน'));
$sem = $sc['semester'] ? 'ภาค ' . htmlspecialchars($sc['semester']) : '-';
$yr = htmlspecialchars($sc['academic_year'] ?: '-');
$row_class = $sc['source_type'] == 'transfer' ? 'transfer' : 'completed';
echo '<tr class="' . $row_class . '">';
echo '<td class="center"><code>' . htmlspecialchars($sc['course_code']) . '</code></td>';
echo '<td>' . htmlspecialchars($sc['course_name_th']) . '</td>';
echo '<td class="center">' . number_format($sc['credits'], 1) . '</td>';
echo '<td class="center bold">' . htmlspecialchars($sc['grade'] ?: '-') . '</td>';
echo '<td class="center">' . $sem . '</td>';
echo '<td class="center">' . $yr . '</td>';
echo '<td class="center">' . $type . '</td>';
echo '</tr>';
}
echo '<tr class="summary-row">';
echo '<td colspan="2" class="right bold" style="text-align: right; padding-right: 15px;">รวมหน่วยกิต</td>';
echo '<td class="center bold">' . number_format($group_total, 1) . '</td>';
echo '<td colspan="4"></td>';
echo '</tr>';
echo '</tbody></table>';
}
if ($has_children) {
renderGroupedSummary($node['children'], $level + 1);
}
echo '</div>';
}
}
+215
View File
@@ -0,0 +1,215 @@
<?php
$page_title = 'สรุปผลการเรียนแยกตามโครงสร้าง';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$student_id = intval($_GET['student_id'] ?? 0);
requireStudentAccess($student_id);
$student = getStudent($student_id);
if (!$student) {
die('ไม่พบข้อมูลนักศึกษา');
}
$summary = getStudentSummary($student_id);
$total_curriculum_credits = getTotalCurriculumCredits($student['curriculum_id']);
$transfer_credits_total = getTransferCreditsTotal($student_id);
$total_completed = getCompletedCreditsTotal($student_id);
$remaining = max(0, $total_curriculum_credits - $total_completed);
$pct = $total_curriculum_credits > 0 ? round(($total_completed / $total_curriculum_credits) * 100) : 0;
// ดึงข้อมูลชื่ออาจารย์ที่ปรึกษาจากระบบ
$advisor_name = '................................';
if (!empty($student['advisor_id'])) {
$advisor = getUser($student['advisor_id']);
if ($advisor) {
$advisor_name = $advisor['name'];
}
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>สรุปผลการเรียนแยกตามโครงสร้าง - <?= htmlspecialchars($student['student_code']) ?></title>
<style>
@media print { .no-print { display: none; } body { font-family: 'TH Sarabun New', 'Leelawadee', 'Tahoma', sans-serif; } }
body {
font-family: 'TH Sarabun New', 'Leelawadee', 'Tahoma', sans-serif;
font-size: 14px; padding: 20px; color: #333;
}
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 20px; }
.header h1 { font-size: 22px; margin: 0; }
.header h2 { font-size: 18px; margin: 5px 0; font-weight: normal; }
.student-info { margin-bottom: 15px; }
.student-info table { width: 100%; }
.student-info td { padding: 2px 10px; }
.credit-cards { display: flex; gap: 10px; margin-bottom: 15px; }
.credit-card { flex: 1; padding: 8px; border-radius: 5px; text-align: center; font-size: 18px; }
.section { margin-bottom: 15px; }
.section-title { background: #0d6efd; color: white; padding: 5px 10px; font-size: 15px; font-weight: bold; }
.subsection-title { background: #e9ecef; padding: 3px 10px; margin: 5px 0; font-size: 13px; font-weight: bold; color: #555; }
.subsubsection-title { font-weight: bold; color: #666; margin: 3px 0; font-size: 12px; }
table { width: 100%; border-collapse: collapse; margin: 5px 0; }
th, td { border: 1px solid #ccc; padding: 3px 6px; text-align: left; font-size: 13px; }
th { background: #f8f9fa; }
.center { text-align: center; }
.completed { background: #d4edda; }
.transfer { background: #fff3cd; }
.summary-row { background: #f0f0f0; font-weight: bold; }
.footer { text-align: center; margin-top: 30px; padding-top: 10px; border-top: 1px solid #ccc; font-size: 12px; color: #666; }
.btn-print { display: inline-block; padding: 8px 16px; background: #0d6efd; color: white; text-decoration: none; border-radius: 4px; margin-bottom: 15px; }
.btn-print:hover { background: #0b5ed7; }
.conclusion { text-align: center; font-size: 16px; margin: 15px 0; padding: 10px; border: 2px solid #28a745; border-radius: 5px; }
</style>
</head>
<body>
<div class="no-print" style="margin-bottom:10px;">
<a href="#" onclick="window.print();return false;" class="btn-print">🖨️ พิมพ์ / บันทึกเป็น PDF</a>
<a href="../students/view.php?id=<?= $student_id ?>" class="btn-print" style="background:#6c757d;">⬅ กลับ</a>
</div>
<div class="header">
<h1>สรุปผลการเรียน</h1>
<h2>มหาวิทยาลัยตาปี</h2>
</div>
<div class="student-info">
<table>
<tr><td style="width:140px;"><strong>รหัสนักศึกษา:</strong></td><td><?= htmlspecialchars($student['student_code']) ?></td></tr>
<tr><td><strong>ชื่อ-นามสกุล:</strong></td><td><?= htmlspecialchars($student['name_th']) ?> (<?= htmlspecialchars($student['name_en']) ?>)</td></tr>
<tr><td><strong>สาขาวิชา:</strong></td><td><?= htmlspecialchars($student['curriculum_code']) ?> - <?= htmlspecialchars($student['curriculum_name']) ?></td></tr>
<tr><td><strong>คณะ:</strong></td><td><?= htmlspecialchars($student['faculty'] ?: '-') ?></td></tr>
<tr><td><strong>สถาบันที่จบ:</strong></td><td><?= htmlspecialchars($student['graduated_institution'] ?: '-') ?></td></tr>
<tr><td><strong>ปีการศึกษา:</strong></td><td><?= $student['enrollment_year'] ?></td></tr>
</table>
</div>
<div class="credit-cards">
<div class="credit-card" style="background:#ffc107;">
<strong><?= $transfer_credits_total ?></strong><br><small>หน่วยกิตเทียบโอน</small>
</div>
<div class="credit-card" style="background:#28a745;color:white;">
<strong><?= $total_completed ?></strong><br><small>หน่วยกิตเรียนผ่านแล้ว</small>
</div>
<div class="credit-card" style="background:#0d6efd;color:white;">
<strong><?= $remaining ?></strong><br><small>หน่วยกิตคงเหลือ</small>
</div>
</div>
<div class="section">
<div class="section-title">สรุปรายวิชาที่เรียนผ่านแล้วทั้งหมด (แยกตามโครงสร้างหลักสูตร)</div>
<?php renderGroupedSummary($summary['groups']); ?>
<table style="margin-top:10px;">
<tr class="summary-row">
<td colspan="2" class="center">รวมหน่วยกิตที่เรียนผ่านแล้วทั้งหมด</td>
<td class="center"><?= $total_completed ?> หน่วยกิต</td><td colspan="4"></td>
</tr>
</table>
</div>
<div class="conclusion" style="border-color: <?= $remaining <= 0 ? '#28a745' : '#ffc107' ?>;">
<?php if ($remaining <= 0): ?>
<strong style="color:#28a745;">✓ ครบตามหลักสูตรแล้ว (<?= $total_completed ?> / <?= $total_curriculum_credits ?> หน่วยกิต)</strong>
<?php else: ?>
<strong>หน่วยกิตคงเหลือที่ต้องเรียน: <?= $remaining ?> หน่วยกิต (<?= $total_completed ?> / <?= $total_curriculum_credits ?> หน่วยกิต)</strong>
<?php endif; ?>
</div>
<!-- ส่วนลงชื่อ 3 ช่องท้ายรายงาน -->
<table style="width: 100%; border: none !important; margin-top: 50px; margin-bottom: 20px; page-break-inside: avoid;">
<tr style="border: none !important;">
<td style="width: 33%; text-align: center; border: none !important; padding: 10px; font-size: 14px; line-height: 1.8; vertical-align: top;">
............................................................<br>
( <?= htmlspecialchars($advisor_name) ?> )<br>
อาจารย์ที่ปรึกษา<br>&nbsp;
</td>
<td style="width: 33%; text-align: center; border: none !important; padding: 10px; font-size: 14px; line-height: 1.8; vertical-align: top;">
............................................................<br>
( ............................................................ )<br>
ประธานหลักสูตรสาขาวิชา<br><?= htmlspecialchars($student['curriculum_name']) ?>
</td>
<td style="width: 34%; text-align: center; border: none !important; padding: 10px; font-size: 14px; line-height: 1.8; vertical-align: top;">
............................................................<br>
( ............................................................ )<br>
คณบดี<br><?= htmlspecialchars($student['faculty'] ?: 'คณะ................................') ?>
</td>
</tr>
</table>
<div class="footer">
ระบบใบควบคุมผลการเรียน มหาวิทยาลัยตาปี<br>
พิมพ์เมื่อ <?= date('d/m/Y H:i') ?> น.
</div>
</body>
</html>
<?php
function renderGroupedSummary($nodes, $level = 0) {
foreach ($nodes as $node) {
$g = $node['group'];
$has_completed = $node['completed_credits'] > 0;
$has_courses = !empty($node['courses']);
$has_children = !empty($node['children']);
if (!$has_completed && !$has_children) continue;
$style = $level == 0 ? 'section-title' : ($level == 1 ? 'subsection-title' : 'subsubsection-title');
echo '<div style="margin-left:' . ($level * 10) . 'px;">';
echo '<div class="' . $style . '">' . htmlspecialchars($g['name_th']);
if ($g['min_credits'] > 0) echo ' (ไม่น้อยกว่า ' . $g['min_credits'] . ' หน่วยกิต)';
if ($level == 0 && $has_completed) {
echo ' <span style="font-weight:normal;font-size:12px;">— เรียนผ่านแล้ว ' . $node['completed_credits'] . ' หน่วยกิต</span>';
}
echo '</div>';
if ($has_courses) {
$group_total = 0;
echo '<table style="table-layout: fixed; width: 100%;">';
echo '<thead>';
echo '<tr>';
echo '<th class="center" style="width: 13%;">รหัสวิชา</th>';
echo '<th style="width: 41%;">ชื่อวิชา</th>';
echo '<th class="center" style="width: 10%;">หน่วยกิต</th>';
echo '<th class="center" style="width: 8%;">เกรด</th>';
echo '<th class="center" style="width: 10%;">ภาคเรียน</th>';
echo '<th class="center" style="width: 10%;">ปีการศึกษา</th>';
echo '<th class="center" style="width: 8%;">ประเภท</th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
foreach ($node['courses'] as $course) {
if (!isset($course['_completed'])) continue;
$sc = $course['_completed'];
$group_total += $sc['credits'];
$type = htmlspecialchars($sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียน'));
$sem = $sc['semester'] ? 'ภาค ' . htmlspecialchars($sc['semester']) : '-';
$yr = htmlspecialchars($sc['academic_year'] ?: '-');
$row_class = $sc['source_type'] == 'transfer' ? 'transfer' : 'completed';
echo '<tr class="' . $row_class . '">';
echo '<td class="center"><code>' . htmlspecialchars($sc['course_code']) . '</code></td>';
echo '<td>' . htmlspecialchars($sc['course_name_th']) . '</td>';
echo '<td class="center">' . number_format($sc['credits'], 1) . '</td>';
echo '<td class="center bold">' . htmlspecialchars($sc['grade'] ?: '-') . '</td>';
echo '<td class="center">' . $sem . '</td>';
echo '<td class="center">' . $yr . '</td>';
echo '<td class="center">' . $type . '</td>';
echo '</tr>';
}
echo '<tr class="summary-row">';
echo '<td colspan="2" class="right bold" style="text-align: right; padding-right: 15px;">รวมหน่วยกิต</td>';
echo '<td class="center bold">' . number_format($group_total, 1) . '</td>';
echo '<td colspan="4"></td>';
echo '</tr>';
echo '</tbody></table>';
}
if ($has_children) {
renderGroupedSummary($node['children'], $level + 1);
}
echo '</div>';
}
}
+167
View File
@@ -0,0 +1,167 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../vendor/fpdf.php';
requireLogin();
$student_id = intval($_GET['student_id'] ?? 0);
requireStudentAccess($student_id);
$student = getStudent($id = $student_id);
if (!$student) die('ไม่พบข้อมูลนักศึกษา');
$summary = getStudentSummary($student_id);
if (!$summary) die('ไม่พบข้อมูล');
$student_courses = getStudentCourses($student_id);
$completed_map = $summary['completed_map'];
$total_curriculum_credits = getTotalCurriculumCredits($student['curriculum_id']);
$transfer_credits_total = getTransferCreditsTotal($student_id);
$total_completed = getCompletedCreditsTotal($student_id);
class SummaryPDF extends FPDF {
function header() {
$this->SetFont('Courier', '', 10);
$this->Cell(0, 8, iconv('UTF-8','cp874','ใบควบคุมผลการเรียน'), 0, 1, 'C');
$this->Ln(2);
}
function footer() {
$this->SetY(-15);
$this->SetFont('Courier', '', 8);
$this->Cell(0, 10, iconv('UTF-8','cp874','หน้าที่ ').$this->PageNo().'/{nb}', 0, 0, 'C');
}
}
$pdf = new SummaryPDF('P', 'mm', 'A4');
$pdf->AliasNbPages();
$pdf->SetMargins(8, 10, 8);
$pdf->AddPage();
// Student info
$pdf->SetFont('Courier', '', 9);
$info = [
['รหัสนักศึกษา:', $student['student_code']],
['ชื่อ-นามสกุล:', $student['name_th'] . ' (' . $student['name_en'] . ')'],
['สาขาวิชา:', $student['curriculum_code'] . ' - ' . $student['curriculum_name']],
['คณะ:', $student['faculty'] ?: '-'],
['สถาบันที่จบ:', $student['graduated_institution'] ?: '-'],
['ปีการศึกษา:', $student['enrollment_year']],
];
foreach ($info as $row) {
$pdf->Cell(45, 5, iconv('UTF-8','cp874',$row[0]), 0, 0);
$pdf->Cell(0, 5, iconv('UTF-8','cp874//IGNORE',$row[1]), 0, 1);
}
$pdf->Ln(1);
$remaining = max(0, $total_curriculum_credits - $total_completed);
$pct = $total_curriculum_credits > 0 ? round(($total_completed / $total_curriculum_credits) * 100) : 0;
$pdf->SetFont('Courier', 'B', 9);
$pdf->Cell(0, 6, iconv('UTF-8','cp874','เทียบโอน: '.$transfer_credits_total.' | เรียนแล้ว: '.$total_completed.' | คงเหลือ: '.$remaining.' | คิดเป็น '.$pct.'%'), 0, 1);
$pdf->Ln(3);
// Render tree recursively
$level = 0;
renderPdfTree($summary['groups'], $pdf, $level);
// Summary table
$pdf->Ln(3);
$pdf->SetFont('Courier', 'B', 9);
$pdf->Cell(0, 6, iconv('UTF-8','cp874','--- สรุปรายวิชาที่เรียนผ่านแล้วทั้งหมด ---'), 0, 1);
$pdf->Ln(1);
$pdf->SetFont('Courier', '', 8);
$pdf->Cell(28, 4, iconv('UTF-8','cp874','รหัสวิชา'), 1, 0);
$pdf->Cell(46, 4, iconv('UTF-8','cp874','ชื่อวิชา'), 1, 0);
$pdf->Cell(12, 4, iconv('UTF-8','cp874','หน่วยกิต'), 1, 0, 'C');
$pdf->Cell(10, 4, iconv('UTF-8','cp874','เกรด'), 1, 0, 'C');
$pdf->Cell(16, 4, iconv('UTF-8','cp874','ภาคเรียน'), 1, 0, 'C');
$pdf->Cell(16, 4, iconv('UTF-8','cp874','ปีการศึกษา'), 1, 0, 'C');
$pdf->Cell(19, 4, iconv('UTF-8','cp874','ประเภท'), 1, 1);
$total = 0;
foreach ($student_courses as $sc) {
$total += $sc['credits'];
$name = mb_substr($sc['course_name_th'], 0, 24, 'UTF-8');
$type = iconv('UTF-8','cp874//IGNORE', $sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียน'));
$sem = $sc['semester'] ? 'ภาค '.$sc['semester'] : '-';
$yr = $sc['academic_year'] ?: '-';
$grade = $sc['grade'] ?: '-';
$pdf->SetFont('Courier', '', 8);
$pdf->Cell(28, 4, iconv('UTF-8','cp874//IGNORE',$sc['course_code']), 1, 0);
$pdf->Cell(46, 4, iconv('UTF-8','cp874//IGNORE',$name), 1, 0);
$pdf->Cell(12, 4, $sc['credits'], 1, 0, 'C');
$pdf->Cell(10, 4, $grade, 1, 0, 'C');
$pdf->Cell(16, 4, iconv('UTF-8','cp874//IGNORE',$sem), 1, 0, 'C');
$pdf->Cell(16, 4, iconv('UTF-8','cp874//IGNORE',$yr), 1, 0, 'C');
$pdf->Cell(19, 4, $type, 1, 1);
}
$pdf->SetFont('Courier', 'B', 8);
$pdf->Cell(147, 5, iconv('UTF-8','cp874','รวมหน่วยกิตที่เรียนผ่านแล้วทั้งหมด: '.$total.' หน่วยกิต'), 0, 1);
$pdf->Ln(3);
if ($remaining <= 0) {
$pdf->SetFont('Courier', 'B', 10);
$pdf->Cell(0, 6, iconv('UTF-8','cp874','*** ครบตามหลักสูตรแล้ว ***'), 0, 1, 'C');
} else {
$pdf->SetFont('Courier', '', 9);
$pdf->Cell(0, 6, iconv('UTF-8','cp874','หน่วยกิตคงเหลือที่ต้องเรียน: '.$remaining.' หน่วยกิต'), 0, 1);
}
$filename = 'Summary_' . $student['student_code'] . '_' . date('Ymd') . '.pdf';
$pdf->Output('I', $filename);
// ============================================================
function renderPdfTree($nodes, &$pdf, $level) {
foreach ($nodes as $node) {
$g = $node['group'];
$has_courses = !empty($node['courses']);
$has_children = !empty($node['children']);
$indent = $level * 4;
$pdf->SetX(8 + $indent);
if ($level == 0) {
$pdf->SetFont('Courier', 'B', 9);
} elseif ($level == 1) {
$pdf->SetFont('Courier', 'B', 8);
} else {
$pdf->SetFont('Courier', '', 8);
}
$title = $g['name_th'];
if ($g['min_credits'] > 0) $title .= ' ('.iconv('UTF-8','cp874','ไม่น้อยกว่า ').$g['min_credits'].iconv('UTF-8','cp874',' หน่วยกิต').')';
$pdf->Cell(0, 5, iconv('UTF-8','cp874//IGNORE',$title), 0, 1);
if ($has_courses) {
$pdf->SetX(8 + $indent);
$pdf->SetFont('Courier', '', 7);
$pdf->Cell(30, 3, iconv('UTF-8','cp874','รหัสวิชา'), 0, 0);
$pdf->Cell(50, 3, iconv('UTF-8','cp874','ชื่อวิชา'), 0, 0);
$pdf->Cell(12, 3, iconv('UTF-8','cp874','หน่วยกิต'), 0, 0, 'C');
$pdf->Cell(10, 3, iconv('UTF-8','cp874','เกรด'), 0, 0, 'C');
$pdf->Cell(18, 3, iconv('UTF-8','cp874','ประเภท'), 0, 1);
foreach ($node['courses'] as $course) {
$status_text = '';
$grade_text = '';
if (isset($course['_completed'])) {
$sc = $course['_completed'];
$status_text = iconv('UTF-8','cp874//IGNORE', $sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียนแล้ว'));
$grade_text = $sc['grade'] ?: '-';
}
$name = mb_substr($course['name_th'], 0, 28, 'UTF-8');
$pdf->SetX(8 + $indent);
$pdf->SetFont('Courier', '', 8);
$pdf->Cell(30, 4, iconv('UTF-8','cp874//IGNORE',$course['code']), 0, 0);
$pdf->Cell(50, 4, iconv('UTF-8','cp874//IGNORE',$name), 0, 0);
$pdf->Cell(12, 4, $course['credits'], 0, 0, 'C');
$pdf->Cell(10, 4, $grade_text, 0, 0, 'C');
$pdf->Cell(18, 4, $status_text, 0, 1);
}
$pdf->Ln(1);
}
if ($has_children) {
renderPdfTree($node['children'], $pdf, $level + 1);
}
}
}
+216
View File
@@ -0,0 +1,216 @@
<?php
$page_title = 'รายงานการประเมินเทียบโอนรายวิชา';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$student_id = intval($_GET['student_id'] ?? 0);
requireStudentAccess($student_id);
$student = getStudent($student_id);
if (!$student) {
die('ไม่พบข้อมูลนักศึกษา');
}
$pdf_path = getStudentTransferPdfPath($student_id);
$report = null;
$error_msg = null;
if ($pdf_path && file_exists($pdf_path)) {
try {
$report = parseTransferReportFromPdf($pdf_path);
} catch (Exception $e) {
$error_msg = 'เกิดข้อผิดพลาดในการประมวลผลไฟล์ PDF: ' . $e->getMessage();
}
} else {
$error_msg = 'ยังไม่ได้นำเข้าไฟล์ PDF สำหรับการเทียบโอนวิชานี้ หรือไม่พบไฟล์ PDF ในระบบ';
}
$is_dbc = ($student['curriculum_code'] == 'DBC');
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>รายงานผลการประเมินเทียบโอนรายวิชา - <?= htmlspecialchars($student['student_code']) ?></title>
<style>
@media print {
.no-print { display: none; }
body { font-family: 'TH Sarabun New', 'Leelawadee', 'Tahoma', sans-serif; }
}
body {
font-family: 'TH Sarabun New', 'Leelawadee', 'Tahoma', sans-serif;
font-size: 14px; padding: 20px; color: #333;
background-color: #f8f9fa;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
padding: 30px;
box-shadow: 0 0 10px rgba(0,0,0,0.05);
border-radius: 8px;
}
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 20px; }
.header h1 { font-size: 22px; margin: 0; font-weight: bold; }
.header h2 { font-size: 18px; margin: 5px 0; font-weight: normal; }
.student-info { margin-bottom: 25px; }
.student-info table { width: 100%; border: none; }
.student-info td { padding: 4px 10px; border: none; font-size: 14px; }
.section-title {
background: #0d6efd;
color: white;
padding: 6px 12px;
font-size: 15px;
font-weight: bold;
margin-top: 25px;
margin-bottom: 10px;
border-radius: 4px;
}
.section-title.dbc { background: #198754; }
.section-title.secondary { background: #6c757d; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td { border: 1px solid #ccc; padding: 6px 10px; text-align: left; font-size: 13px; }
th { background: #f1f3f5; font-weight: bold; }
.center { text-align: center; }
.right { text-align: right; }
.bold { font-weight: bold; }
.text-muted { color: #6c757d; }
.btn-print {
display: inline-block;
padding: 8px 16px;
background: #0d6efd;
color: white;
text-decoration: none;
border-radius: 4px;
margin-bottom: 15px;
font-size: 13px;
font-weight: bold;
}
.btn-print:hover { background: #0b5ed7; }
.alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
font-size: 14px;
}
.alert-danger {
color: #842029;
background-color: #f8d7da;
border-color: #f5c2c7;
}
.footer { text-align: center; margin-top: 40px; padding-top: 15px; border-top: 1px solid #eee; font-size: 12px; color: #888; }
</style>
</head>
<body>
<div class="container">
<div class="no-print" style="margin-bottom:20px;">
<a href="#" onclick="window.print();return false;" class="btn-print">🖨️ พิมพ์รายงานนี้</a>
<a href="../students/view.php?id=<?= $student_id ?>" class="btn-print" style="background:#6c757d;">⬅ กลับไปข้อมูลนักศึกษา</a>
</div>
<div class="header">
<h1>ผลการประเมินเทียบโอนรายวิชา</h1>
<h2>มหาวิทยาลัยตาปี (Tapee University)</h2>
</div>
<div class="student-info">
<table>
<tr>
<td style="width:15%;"><strong>รหัสนักศึกษา:</strong></td>
<td style="width:35%;"><?= htmlspecialchars($student['student_code']) ?></td>
<td style="width:15%;"><strong>ปีที่เข้าศึกษา:</strong></td>
<td style="width:35%;"><?= $student['enrollment_year'] ?></td>
</tr>
<tr>
<td><strong>ชื่อ-นามสกุล:</strong></td>
<td><?= htmlspecialchars($student['name_th']) ?> (<?= htmlspecialchars($student['name_en']) ?>)</td>
<td><strong>คณะ:</strong></td>
<td><?= htmlspecialchars($student['faculty'] ?: '-') ?></td>
</tr>
<tr>
<td><strong>สาขาวิชา:</strong></td>
<td>
<?= htmlspecialchars($student['curriculum_code']) ?> - <?= htmlspecialchars($student['curriculum_name']) ?>
<div style="margin-top: 4px; font-size: 13px; color: #555;">
<strong>หน่วยกิตที่เทียบโอนได้:</strong> <span style="font-weight: bold; color: #198754; font-size: 14px;"><?= getTransferCreditsTotal($student_id) ?></span> หน่วยกิต
</div>
</td>
<td><strong>สถาบันที่จบเดิม:</strong></td>
<td><?= htmlspecialchars($student['graduated_institution'] ?: '-') ?></td>
</tr>
</table>
</div>
<?php if ($error_msg): ?>
<div class="alert alert-danger">
⚠️ <?= htmlspecialchars($error_msg) ?>
</div>
<?php else: ?>
<div class="section-title <?= $is_dbc ? 'dbc' : '' ?>">รายละเอียดวิชาที่เทียบโอนได้</div>
<table>
<thead>
<tr>
<th rowspan="2" class="center" style="vertical-align: middle; width: 5%;">ลำดับ</th>
<th colspan="3" class="center">วิชาปลายทาง (ตามหลักสูตร)</th>
<th colspan="5" class="center">วิชาต้นทางที่ใช้เทียบ (สถาบันเดิม)</th>
</tr>
<tr>
<th class="center" style="width: 10%;">รหัส</th>
<th>ชื่อวิชา</th>
<th class="center" style="width: 8%;">หน่วยกิต</th>
<th class="center" style="width: 12%;">รหัส</th>
<th>ชื่อวิชา</th>
<th class="center" style="width: 8%;">หน่วยกิต</th>
<th class="center" style="width: 6%;">เกรด</th>
<th class="center" style="width: 10%;">ความสอดคล้อง</th>
</tr>
</thead>
<tbody>
<?php if (empty($report['transfers'])): ?>
<tr>
<td colspan="9" class="center text-muted">ไม่พบข้อมูลรายวิชาที่เทียบโอนได้</td>
</tr>
<?php else: ?>
<?php foreach ($report['transfers'] as $transfer): ?>
<?php
$num_sources = count($transfer['sources']);
$first_source = $transfer['sources'][0];
?>
<tr>
<td class="center" rowspan="<?= $num_sources ?>"><?= htmlspecialchars($transfer['seq']) ?></td>
<td class="center bold" rowspan="<?= $num_sources ?>"><?= htmlspecialchars($transfer['target_code']) ?></td>
<td class="bold" rowspan="<?= $num_sources ?>"><?= htmlspecialchars($transfer['target_name']) ?></td>
<td class="center bold" rowspan="<?= $num_sources ?>"><?= number_format($transfer['target_credits'], 1) ?></td>
<td class="center"><?= htmlspecialchars($first_source['code']) ?></td>
<td><?= htmlspecialchars($first_source['name']) ?></td>
<td class="center"><?= htmlspecialchars($first_source['credits']) ?></td>
<td class="center bold"><?= htmlspecialchars($first_source['grade']) ?></td>
<td class="center"><?= htmlspecialchars($first_source['similarity']) ?></td>
</tr>
<?php for ($i = 1; $i < $num_sources; $i++): $src = $transfer['sources'][$i]; ?>
<tr>
<td class="center"><?= htmlspecialchars($src['code']) ?></td>
<td><?= htmlspecialchars($src['name']) ?></td>
<td class="center"><?= htmlspecialchars($src['credits']) ?></td>
<td class="center bold"><?= htmlspecialchars($src['grade']) ?></td>
<td class="center"><?= htmlspecialchars($src['similarity']) ?></td>
</tr>
<?php endfor; ?>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php endif; ?>
<div class="footer">
ระบบเทียบโอนรายวิชา มหาวิทยาลัยตาปี<br>
เอกสารฉบับนี้วิเคราะห์และแสดงผลข้อมูลเทียบเคียงโดยตรงจากเอกสารผลประเมิน (PDF) ณ วันที่ <?= date('d/m/Y H:i') ?> น.
</div>
</div>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
-- Add google_id column to users table
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_id VARCHAR(255) DEFAULT NULL AFTER email;
-- Add columns to students table (if not already exist)
ALTER TABLE students ADD COLUMN IF NOT EXISTS email VARCHAR(255) DEFAULT NULL AFTER name_en;
ALTER TABLE students ADD COLUMN IF NOT EXISTS password VARCHAR(255) DEFAULT NULL AFTER email;
ALTER TABLE students ADD COLUMN IF NOT EXISTS google_id VARCHAR(255) DEFAULT NULL AFTER password;
ALTER TABLE students ADD COLUMN IF NOT EXISTS advisor_id INT DEFAULT NULL AFTER curriculum_id;
ALTER TABLE students ADD COLUMN IF NOT EXISTS previous_qualification VARCHAR(255) DEFAULT NULL AFTER graduated_institution;
+73
View File
@@ -0,0 +1,73 @@
CREATE DATABASE IF NOT EXISTS transfer_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE transfer_db;
-- หลักสูตร
CREATE TABLE curricula (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(10) NOT NULL UNIQUE,
name_th VARCHAR(255) NOT NULL,
name_en VARCHAR(255) NOT NULL,
total_credits INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- กลุ่มวิชาในหลักสูตร (โครงสร้าง)
CREATE TABLE course_groups (
id INT AUTO_INCREMENT PRIMARY KEY,
curriculum_id INT NOT NULL,
parent_id INT DEFAULT NULL,
code VARCHAR(50) NOT NULL,
name_th VARCHAR(255) NOT NULL,
min_credits INT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
FOREIGN KEY (curriculum_id) REFERENCES curricula(id) ON DELETE CASCADE,
FOREIGN KEY (parent_id) REFERENCES course_groups(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- รายวิชาในหลักสูตร
CREATE TABLE courses (
id INT AUTO_INCREMENT PRIMARY KEY,
group_id INT NOT NULL,
code VARCHAR(20) NOT NULL UNIQUE,
name_th VARCHAR(255) NOT NULL,
name_en VARCHAR(255) DEFAULT '',
credits DECIMAL(3,1) NOT NULL,
lecture_hours INT NOT NULL DEFAULT 0,
practice_hours INT NOT NULL DEFAULT 0,
self_study_hours INT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
FOREIGN KEY (group_id) REFERENCES course_groups(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- นักศึกษา
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
student_code VARCHAR(20) NOT NULL UNIQUE,
name_th VARCHAR(255) NOT NULL,
name_en VARCHAR(255) DEFAULT '',
faculty VARCHAR(255) DEFAULT '',
graduated_institution VARCHAR(255) DEFAULT '',
curriculum_id INT NOT NULL,
enrollment_year INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (curriculum_id) REFERENCES curricula(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- รายวิชาที่นักศึกษาเรียนผ่านแล้ว (ทั้งเทียบโอนและลงทะเบียน)
CREATE TABLE student_courses (
id INT AUTO_INCREMENT PRIMARY KEY,
student_id INT NOT NULL,
course_id INT DEFAULT NULL,
course_code VARCHAR(20) NOT NULL,
course_name_th VARCHAR(255) NOT NULL,
credits DECIMAL(3,1) NOT NULL,
lecture_hours INT NOT NULL DEFAULT 0,
practice_hours INT NOT NULL DEFAULT 0,
self_study_hours INT NOT NULL DEFAULT 0,
source_type ENUM('transfer','manual') NOT NULL DEFAULT 'manual',
source_file VARCHAR(255) DEFAULT NULL,
notes TEXT DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+145
View File
@@ -0,0 +1,145 @@
<?php
$page_title = 'เพิ่มนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
require_once __DIR__ . '/../includes/header.php';
$curricula = getCurricula();
$faculties = getFacultyList();
$message = '';
$error = '';
$advisor_curriculum_id = isAdvisor() ? getCurrentUserCurriculumId() : null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$student_code = trim($_POST['student_code'] ?? '');
$name_th = trim($_POST['name_th'] ?? '');
$name_en = trim($_POST['name_en'] ?? '');
$email = trim($_POST['email'] ?? '');
$curriculum_id = $advisor_curriculum_id ?: intval($_POST['curriculum_id'] ?? 0);
$faculty = trim($_POST['faculty'] ?? '');
$graduated_institution = trim($_POST['graduated_institution'] ?? '');
$previous_qualification = trim($_POST['previous_qualification'] ?? '');
$enrollment_year = intval($_POST['enrollment_year'] ?? date('Y'));
if (empty($student_code) || empty($name_th) || empty($curriculum_id)) {
$error = 'กรุณากรอกข้อมูลให้ครบถ้วน (รหัสนักศึกษา, ชื่อ-นามสกุล, สาขาวิชา)';
} else {
try {
if (isAdvisor()) {
$advisor_id = getCurrentUserId();
} else {
$advisor_id = !empty($_POST['advisor_id']) ? intval($_POST['advisor_id']) : null;
}
$stmt = getDB()->prepare("INSERT INTO students (student_code, name_th, name_en, email, faculty, graduated_institution, curriculum_id, enrollment_year, advisor_id, previous_qualification) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$student_code, $name_th, $name_en, $email, $faculty, $graduated_institution, $curriculum_id, $enrollment_year, $advisor_id, $previous_qualification ?: null]);
$message = 'เพิ่มนักศึกษาสำเร็จ';
echo "<script>window.location.href='view.php?id=" . getDB()->lastInsertId() . "';</script>";
exit;
} catch (PDOException $e) {
if ($e->getCode() == 23000) {
$error = 'รหัสนักศึกษานี้มีอยู่ในระบบแล้ว';
} else {
$error = 'เกิดข้อผิดพลาด: ' . $e->getMessage();
}
}
}
}
?>
<div class="card">
<div class="card-header bg-success text-white">
<h4 class="mb-0"><i class="bi bi-person-plus"></i> เพิ่มนักศึกษาใหม่</h4>
</div>
<div class="card-body">
<?php if ($message): ?>
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST" class="row g-3">
<div class="col-md-3">
<label class="form-label">รหัสนักศึกษา <span class="text-danger">*</span></label>
<input type="text" name="student_code" class="form-control" required maxlength="20">
</div>
<div class="col-md-5">
<label class="form-label">ชื่อ-นามสกุล (ภาษาไทย) <span class="text-danger">*</span></label>
<input type="text" name="name_th" class="form-control" required>
</div>
<div class="col-md-4">
<label class="form-label">ชื่อ-นามสกุล (ภาษาอังกฤษ)</label>
<input type="text" name="name_en" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">อีเมล</label>
<input type="email" name="email" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">สาขาวิชา <span class="text-danger">*</span></label>
<?php if ($advisor_curriculum_id): ?>
<input type="text" class="form-control" value="<?php foreach($curricula as $c) { if ($c['id'] == $advisor_curriculum_id) { echo htmlspecialchars($c['code'] . ' - ' . $c['name_th']); break; } } ?>" disabled>
<input type="hidden" name="curriculum_id" value="<?= $advisor_curriculum_id ?>">
<?php else: ?>
<select name="curriculum_id" class="form-select" required>
<option value="">-- เลือกสาขาวิชา --</option>
<?php foreach ($curricula as $c): ?>
<option value="<?= $c['id'] ?>" data-faculty="<?= $c['code'] == 'DT' ? '12' : '12' ?>"><?= htmlspecialchars($c['code']) ?> - <?= htmlspecialchars($c['name_th']) ?></option>
<?php endforeach; ?>
</select>
<?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label">คณะ</label>
<select name="faculty" class="form-select">
<option value="">-- เลือกคณะ --</option>
<option value="คณะนวัตกรรมดิจิทัลเทคโนโลยี">คณะนวัตกรรมดิจิทัลเทคโนโลยี (12)</option>
<option value="คณะบริหารธุรกิจ">คณะบริหารธุรกิจ (11)</option>
<option value="คณะศึกษาศาสตร์และศิลปศาสตร์">คณะศึกษาศาสตร์และศิลปศาสตร์ (13)</option>
<option value="คณะบัญชี">คณะบัญชี (14)</option>
<option value="คณะนิติศาสตร์และรัฐศาสตร์">คณะนิติศาสตร์และรัฐศาสตร์ (15)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ชื่อสถาบันที่จบ</label>
<input type="text" name="graduated_institution" class="form-control" placeholder="ชื่อสถานศึกษาเดิม">
</div>
<div class="col-md-4">
<label class="form-label">วุฒิเดิม</label>
<select name="previous_qualification" class="form-select">
<option value="">-- เลือกวุฒิเดิม --</option>
<option value="ม.6 (กศน,สกร)">ม.6 (กศน,สกร)</option>
<option value="ปวช.3">ปวช.3</option>
<option value="ปวส.2">ปวส.2</option>
<option value="ปริญญาตรีใบที่ 2">ปริญญาตรีใบที่ 2</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ปีการศึกษาที่เข้าศึกษา</label>
<input type="number" name="enrollment_year" class="form-control" value="<?= date('Y') + 543 ?>" min="2569" step="1">
</div>
<?php if (!isAdvisor()): ?>
<div class="col-md-4">
<label class="form-label">อาจารย์ที่ปรึกษา</label>
<select name="advisor_id" class="form-select">
<option value="">-- เลือกอาจารย์ที่ปรึกษา --</option>
<?php
$advisors = getAdvisors();
foreach ($advisors as $adv):
?>
<option value="<?= $adv['id'] ?>"><?= htmlspecialchars($adv['name']) ?> (<?= htmlspecialchars($adv['staff_code']) ?>) - <?= htmlspecialchars($adv['curriculum_code'] ?: 'ไม่มีสาขา') ?></option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="col-12">
<button type="submit" class="btn btn-success"><i class="bi bi-save"></i> บันทึก</button>
<a href="list.php" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> กลับ</a>
</div>
</form>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+26
View File
@@ -0,0 +1,26 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$id = intval($_GET['id'] ?? 0);
requireStudentAccess($id);
$student = getStudent($id);
if (!$student) {
header('Location: list.php');
exit;
}
try {
$stmt = getDB()->prepare("DELETE FROM students WHERE id = ?");
$stmt->execute([$id]);
} catch (PDOException $e) {
// ignore
}
header('Location: list.php');
exit;
+143
View File
@@ -0,0 +1,143 @@
<?php
$page_title = 'แก้ไขนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/header.php';
$id = intval($_GET['id'] ?? 0);
requireStudentAccess($id);
$student = getStudent($id);
if (!$student) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลนักศึกษา</div>';
require_once __DIR__ . '/../includes/footer.php';
exit;
}
$curricula = getCurricula();
$faculties = getFacultyList();
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isStudent()) {
$student_code = $student['student_code'];
$curriculum_id = intval($student['curriculum_id']);
$faculty = $student['faculty'];
$enrollment_year = intval($student['enrollment_year']);
$advisor_id = $student['advisor_id'] ? intval($student['advisor_id']) : null;
} else {
$student_code = trim($_POST['student_code'] ?? '');
$curriculum_id = intval($_POST['curriculum_id'] ?? 0);
$faculty = trim($_POST['faculty'] ?? '');
$enrollment_year = intval($_POST['enrollment_year'] ?? date('Y'));
$advisor_id = !empty($_POST['advisor_id']) ? intval($_POST['advisor_id']) : null;
}
$name_th = trim($_POST['name_th'] ?? '');
$name_en = trim($_POST['name_en'] ?? '');
$email = trim($_POST['email'] ?? '');
$graduated_institution = trim($_POST['graduated_institution'] ?? '');
$previous_qualification = trim($_POST['previous_qualification'] ?? '');
if (empty($student_code) || empty($name_th) || empty($curriculum_id)) {
$error = 'กรุณากรอกข้อมูลให้ครบถ้วน';
} else {
try {
$stmt = getDB()->prepare("UPDATE students SET student_code=?, name_th=?, name_en=?, email=?, faculty=?, graduated_institution=?, curriculum_id=?, enrollment_year=?, previous_qualification=?, advisor_id=? WHERE id=?");
$stmt->execute([$student_code, $name_th, $name_en, $email, $faculty, $graduated_institution, $curriculum_id, $enrollment_year, $previous_qualification ?: null, $advisor_id, $id]);
echo "<script>window.location.href='view.php?id=$id';</script>";
exit;
} catch (PDOException $e) {
$error = 'เกิดข้อผิดพลาด: ' . $e->getMessage();
}
}
}
?>
<div class="card">
<div class="card-header bg-warning">
<h4 class="mb-0"><i class="bi bi-pencil"></i> แก้ไขข้อมูลนักศึกษา</h4>
</div>
<div class="card-body">
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST" class="row g-3">
<div class="col-md-3">
<label class="form-label">รหัสนักศึกษา <span class="text-danger">*</span></label>
<input type="text" name="student_code" class="form-control" required value="<?= htmlspecialchars($student['student_code']) ?>" <?= isStudent() ? 'disabled' : '' ?>>
</div>
<div class="col-md-5">
<label class="form-label">ชื่อ-นามสกุล (ภาษาไทย) <span class="text-danger">*</span></label>
<input type="text" name="name_th" class="form-control" required value="<?= htmlspecialchars($student['name_th']) ?>">
</div>
<div class="col-md-4">
<label class="form-label">ชื่อ-นามสกุล (ภาษาอังกฤษ)</label>
<input type="text" name="name_en" class="form-control" value="<?= htmlspecialchars($student['name_en']) ?>">
</div>
<div class="col-md-4">
<label class="form-label">อีเมล</label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($student['email'] ?? '') ?>">
</div>
<div class="col-md-4">
<label class="form-label">สาขาวิชา <span class="text-danger">*</span></label>
<select name="curriculum_id" class="form-select" required <?= isStudent() ? 'disabled' : '' ?>>
<option value="">-- เลือกสาขาวิชา --</option>
<?php foreach ($curricula as $c): ?>
<option value="<?= $c['id'] ?>" <?= $c['id'] == $student['curriculum_id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['code']) ?> - <?= htmlspecialchars($c['name_th']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">คณะ</label>
<select name="faculty" class="form-select" <?= isStudent() ? 'disabled' : '' ?>>
<option value="">-- เลือกคณะ --</option>
<option value="คณะนวัตกรรมดิจิทัลเทคโนโลยี" <?= $student['faculty'] == 'คณะนวัตกรรมดิจิทัลเทคโนโลยี' ? 'selected' : '' ?>>คณะนวัตกรรมดิจิทัลเทคโนโลยี (12)</option>
<option value="คณะบริหารธุรกิจ" <?= $student['faculty'] == 'คณะบริหารธุรกิจ' ? 'selected' : '' ?>>คณะบริหารธุรกิจ (11)</option>
<option value="คณะศึกษาศาสตร์และศิลปศาสตร์" <?= $student['faculty'] == 'คณะศึกษาศาสตร์และศิลปศาสตร์' ? 'selected' : '' ?>>คณะศึกษาศาสตร์และศิลปศาสตร์ (13)</option>
<option value="คณะบัญชี" <?= $student['faculty'] == 'คณะบัญชี' ? 'selected' : '' ?>>คณะบัญชี (14)</option>
<option value="คณะนิติศาสตร์และรัฐศาสตร์" <?= $student['faculty'] == 'คณะนิติศาสตร์และรัฐศาสตร์' ? 'selected' : '' ?>>คณะนิติศาสตร์และรัฐศาสตร์ (15)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ชื่อสถาบันที่จบ</label>
<input type="text" name="graduated_institution" class="form-control" value="<?= htmlspecialchars($student['graduated_institution']) ?>">
</div>
<div class="col-md-4">
<label class="form-label">วุฒิเดิม</label>
<select name="previous_qualification" class="form-select">
<option value="">-- เลือกวุฒิเดิม --</option>
<?php
$quals = ['ม.6 (กศน,สกร)', 'ปวช.3', 'ปวส.2', 'ปริญญาตรีใบที่ 2'];
foreach ($quals as $q):
$sel = ($student['previous_qualification'] == $q) ? 'selected' : '';
?>
<option value="<?= htmlspecialchars($q) ?>" <?= $sel ?>><?= htmlspecialchars($q) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ปีการศึกษาที่เข้าศึกษา</label>
<select name="enrollment_year" class="form-select" <?= isStudent() ? 'disabled' : '' ?>>
<?php for ($y = date('Y')+543; $y >= 2560; $y--): ?>
<option value="<?= $y ?>" <?= $y == $student['enrollment_year'] ? 'selected' : '' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">อาจารย์ที่ปรึกษา</label>
<select name="advisor_id" class="form-select" <?= isStudent() ? 'disabled' : '' ?>>
<option value="">-- เลือกอาจารย์ที่ปรึกษา --</option>
<?php
$advisors = getAdvisors();
foreach ($advisors as $adv):
$sel = ($student['advisor_id'] == $adv['id']) ? 'selected' : '';
?>
<option value="<?= $adv['id'] ?>" <?= $sel ?>><?= htmlspecialchars($adv['name']) ?> (<?= htmlspecialchars($adv['staff_code']) ?>) - <?= htmlspecialchars($adv['curriculum_code'] ?: 'ไม่มีสาขา') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12">
<button type="submit" class="btn btn-warning"><i class="bi bi-save"></i> บันทึกการแก้ไข</button>
<a href="view.php?id=<?= $id ?>" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> กลับ</a>
</div>
</form>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+213
View File
@@ -0,0 +1,213 @@
<?php
$page_title = 'นำเข้า CSV';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$student_id = intval($_GET['student_id'] ?? 0);
if (isStudent()) {
if (!$student_id || $student_id !== intval(getCurrentUserId())) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
}
if (isset($_GET['template'])) {
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=import_template.csv');
echo "\xEF\xBB\xBF";
$cols = $student_id ? ['course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'] : ['student_code','course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'];
echo implode(',', $cols) . "\n";
if ($student_id) {
echo "1012301,การเขียนโปรแกรมเบื้องต้น,3,A,เรียนปกติ,1,2569,2,1,3,,\n";
} else {
echo "STU001,1012301,การเขียนโปรแกรมเบื้องต้น,3,A,เรียนปกติ,1,2569,2,1,3,,\n";
}
exit;
}
if ($student_id) {
requireStudentAccess($student_id);
$student = getStudent($student_id);
if (!$student) { echo '<div class="alert alert-danger">ไม่พบนักศึกษา</div>'; require_once __DIR__ . '/../includes/footer.php'; exit; }
}
require_once __DIR__ . '/../includes/header.php';
$results = [];
$total_success = 0;
$total_error = 0;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK) {
$tmp = $_FILES['csv_file']['tmp_name'];
// Read all rows at once
$all_rows = [];
$handle = fopen($tmp, 'r');
if ($handle) {
while (($r = fgetcsv($handle)) !== false) $all_rows[] = $r;
fclose($handle);
}
if (empty($all_rows)) { $results[] = 'ไฟล์ CSV ว่างเปล่า'; $total_error++; }
else {
$first = $all_rows[0];
$first[0] = preg_replace('/^\xEF\xBB\xBF/', '', $first[0]);
$first = array_map(function($v) { return trim($v, " \t\n\r\0\x0B\""); }, $first);
// Auto-detect header: if first cell starts with a digit, it's data (no header)
$has_header = !preg_match('/^\d/', $first[0]);
if ($has_header) {
$header_norm = array_map(function($v) {
return preg_replace('/[^a-z0-9_]/', '', str_replace(' ', '_', strtolower(trim($v))));
}, $first);
$results[] = "พบหัวคอลัมน์: " . implode(', ', $first);
$col_map = [];
$expected = $student_id ? ['course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'] : ['student_code','course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'];
foreach ($expected as $e) {
$idx = array_search($e, $header_norm);
if ($idx !== false) $col_map[$e] = $idx;
}
array_shift($all_rows);
} else {
$results[] = "ไม่พบแถวหัวคอลัมน์ — ใช้ตำแหน่งคอลัมน์ตามลำดับ";
if ($student_id) {
$col_map = ['course_code' => 0, 'course_name' => 1, 'credits' => 2, 'grade' => 3, 'course_type' => 4, 'semester' => 5, 'academic_year' => 6, 'lecture' => 7, 'practice' => 8, 'self_study' => 9, 'notes' => 10, 'group_id' => 11];
} else {
$col_map = ['student_code' => 0, 'course_code' => 1, 'course_name' => 2, 'credits' => 3, 'grade' => 4, 'course_type' => 5, 'semester' => 6, 'academic_year' => 7, 'lecture' => 8, 'practice' => 9, 'self_study' => 10, 'notes' => 11, 'group_id' => 12];
}
}
$db = getDB();
foreach ($all_rows as $idx => $row) {
$line = $idx + 1;
$c = fn($k) => $col_map[$k] ?? null;
if ($student_id) {
$student = getStudent($student_id);
$student_code = $student['student_code'];
} else {
$student_code = trim($row[$c('student_code')] ?? '');
}
$course_code = trim($row[$c('course_code')] ?? '');
$course_name = trim($row[$c('course_name')] ?? '');
$credits = floatval($row[$c('credits')] ?? 0);
$grade = trim($row[$c('grade')] ?? '');
$course_type_col = trim($row[$c('course_type')] ?? '');
$semester = trim($row[$c('semester')] ?? '');
$academic_year = trim($row[$c('academic_year')] ?? '');
$lecture = intval($row[$c('lecture')] ?? 0);
$practice = intval($row[$c('practice')] ?? 0);
$self_study = intval($row[$c('self_study')] ?? 0);
$notes = trim($row[$c('notes')] ?? '');
$group_id = intval($row[$c('group_id')] ?? 0);
if (empty($course_code) || empty($course_name) || $credits <= 0) {
$results[] = "แถวที่ $line: ข้าม (ข้อมูลไม่ครบ: course_code, course_name, credits)";
$total_error++;
continue;
}
try {
if (!$student_id) {
$stmt = $db->prepare("SELECT id, curriculum_id, advisor_id FROM students WHERE student_code = ?");
$stmt->execute([$student_code]);
$student = $stmt->fetch();
if (!$student) {
$results[] = "แถวที่ $line: ไม่พบรหัสนักศึกษา '$student_code'";
$total_error++;
continue;
}
if (isAdvisor() && $student['advisor_id'] != getCurrentUserId()) {
$results[] = "แถวที่ $line: นักศึกษา '$student_code' ไม่อยู่ในที่ปรึกษาของคุณ";
$total_error++;
continue;
}
}
$curriculum_course = findCourseInCurriculum($student['curriculum_id'], $course_code);
$course_id = null;
if ($curriculum_course) {
$course_id = $curriculum_course['id'];
} elseif ($group_id > 0) {
$stmt = $db->prepare("SELECT id FROM courses WHERE group_id = ? AND code = ?");
$stmt->execute([$group_id, $course_code]);
$existing = $stmt->fetch();
if ($existing) {
$course_id = $existing['id'];
} else {
$stmt = $db->prepare("INSERT INTO courses (group_id, code, name_th, credits, lecture_hours, practice_hours, self_study_hours, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, 0)");
$stmt->execute([$group_id, $course_code, $course_name, $credits, $lecture, $practice, $self_study]);
$course_id = $db->lastInsertId();
}
}
$stmt = $db->prepare("INSERT INTO student_courses (student_id, course_id, course_code, course_name_th, credits, lecture_hours, practice_hours, self_study_hours, grade, course_type, semester, academic_year, source_type, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'manual', ?)");
$stmt->execute([$student['id'], $course_id, $course_code, $course_name, $credits, $lecture, $practice, $self_study, $grade ?: null, $course_type_col ?: null, $semester ?: null, $academic_year ?: null, $notes]);
$results[] = "แถวที่ $line: ✓ $student_code - $course_code ($course_name)";
$total_success++;
} catch (PDOException $e) {
$results[] = "แถวที่ $line: ผิดพลาด - " . $e->getMessage();
$total_error++;
}
}
}
}
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-upload"></i> นำเข้ารายวิชาจากไฟล์ CSV</h4>
<div class="text-muted small">รองรับไฟล์ .csv ตามรูปแบบคอลัมน์ที่ระบบกำหนด</div>
</div>
<a href="<?= $student_id ? 'view.php?id=' . $student_id : 'list.php' ?>" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> กลับ</a>
</div>
<?php if ($student_id): ?>
<div class="alert alert-info">กำลังนำเข้าสำหรับนักศึกษา: <strong><?= htmlspecialchars($student['student_code'] ?? '') ?> - <?= htmlspecialchars($student['name_th'] ?? '') ?></strong> (ไม่ต้องระบุ student_code ใน CSV)</div>
<?php endif; ?>
<div class="row g-4">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-filetype-csv"></i> เลือกไฟล์สำหรับนำเข้า</h5>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">เลือกไฟล์ CSV</label>
<input type="file" name="csv_file" class="form-control" accept=".csv" required>
</div>
<div class="mb-3">
<label class="form-label">รูปแบบไฟล์</label>
<p class="text-muted small mb-2">ไฟล์ CSV ต้องมีหัวคอลัมน์ด้านล่างนี้ (คั่นด้วย comma):</p>
<code class="d-block p-2 bg-light rounded small"><?= $student_id ? 'course_code,course_name,credits,grade,course_type,semester,academic_year,lecture,practice,self_study,notes,group_id' : 'student_code,course_code,course_name,credits,grade,course_type,semester,academic_year,lecture,practice,self_study,notes,group_id' ?></code>
<p class="text-muted small mt-2 mb-0">คอลัมน์ที่จำเป็น: <strong><?= $student_id ? 'course_code, course_name, credits' : 'student_code, course_code, course_name, credits' ?></strong></p>
</div>
<button type="submit" class="btn btn-primary"><i class="bi bi-cloud-upload"></i> นำเข้า</button>
</form>
</div>
</div>
<div class="card mt-3">
<div class="card-body">
<p class="mb-1 fw-bold">📥 ดาวน์โหลด Template</p>
<a href="import_csv.php?template=1<?= $student_id ? '&student_id=' . $student_id : '' ?>" class="btn btn-outline-success btn-sm"><i class="bi bi-download"></i> template.csv</a>
</div>
</div>
</div>
<div class="col-md-6">
<?php if ($total_success > 0 || $total_error > 0): ?>
<div class="card">
<div class="card-header bg-<?= $total_error > 0 ? 'warning' : 'success' ?> text-white d-flex justify-content-between">
<span><i class="bi bi-list-check"></i> ผลลัพธ์</span>
<span>สำเร็จ <?= $total_success ?> | ล้มเหลว <?= $total_error ?></span>
</div>
<div class="card-body" style="max-height:500px;overflow-y:auto">
<ul class="list-unstyled mb-0 small">
<?php foreach ($results as $r): ?>
<li class="mb-1 <?= str_starts_with($r, 'แถว') && str_contains($r, '✓') ? 'text-success' : (str_starts_with($r, 'แถว') ? 'text-danger' : 'text-muted') ?>"><?= htmlspecialchars($r) ?></li>
<?php endforeach; ?>
</ul>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+100
View File
@@ -0,0 +1,100 @@
<?php
$page_title = 'รายชื่อนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
require_once __DIR__ . '/../includes/header.php';
$students = getStudents();
$db = getDB();
$credits_map = [];
if (!empty($students)) {
$ids = array_column($students, 'id');
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $db->prepare("
SELECT
student_id,
COALESCE(SUM(CASE WHEN source_type = 'transfer' THEN credits ELSE 0 END), 0) AS transfer_total,
COALESCE(SUM(CASE WHEN source_type = 'manual' THEN credits ELSE 0 END), 0) AS manual_total,
COALESCE(SUM(credits), 0) AS total
FROM student_courses
WHERE student_id IN ($placeholders)
GROUP BY student_id
");
$stmt->execute($ids);
foreach ($stmt->fetchAll() as $row) {
$credits_map[$row['student_id']] = [
'transfer' => floatval($row['transfer_total']),
'manual' => floatval($row['manual_total']),
'total' => floatval($row['total'])
];
}
}
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-people"></i> รายชื่อนักศึกษา</h4>
<div class="text-muted small">ทั้งหมด <?= count($students) ?> รายการ</div>
</div>
<a href="add.php" class="btn btn-primary"><i class="bi bi-person-plus"></i> เพิ่มนักศึกษา</a>
</div>
<div class="card">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h4 class="mb-0"><i class="bi bi-table"></i> ตารางข้อมูลนักศึกษา</h4>
<span class="badge bg-primary"><?= count($students) ?> รายการ</span>
</div>
<div class="card-body">
<?php if (empty($students)): ?>
<div class="alert alert-info">ยังไม่มีข้อมูลนักศึกษา <a href="add.php">คลิกเพิ่มนักศึกษา</a></div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead class="table-light">
<tr>
<th>รหัสนักศึกษา</th>
<th>ชื่อ-นามสกุล</th>
<th>สาขาวิชา</th>
<th>คณะ</th>
<th>สถาบันที่จบ</th>
<th>วุฒิเดิม</th>
<th>ปีการศึกษา</th>
<th class="text-center">หน่วยกิตที่เทียบโอน</th>
<th class="text-center">หน่วยกิตที่เรียนแล้ว</th>
<th class="text-center">หน่วยกิตทั้งหมดที่เทียบโอนและเรียนแล้ว</th>
<th>การจัดการ</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $s): ?>
<tr>
<td><strong><?= htmlspecialchars($s['student_code']) ?></strong></td>
<td><?= htmlspecialchars($s['name_th']) ?></td>
<td><span class="badge bg-<?= $s['curriculum_code'] == 'DT' ? 'primary' : 'success' ?>"><?= htmlspecialchars($s['curriculum_code']) ?></span></td>
<td><small><?= htmlspecialchars($s['faculty'] ?: '-') ?></small></td>
<td><small><?= htmlspecialchars($s['graduated_institution'] ?: '-') ?></small></td>
<td><small><?= htmlspecialchars($s['previous_qualification'] ?: '-') ?></small></td>
<td><?= $s['enrollment_year'] ?></td>
<td class="text-center"><?= $credits_map[$s['id']]['transfer'] ?? 0 ?></td>
<td class="text-center"><?= $credits_map[$s['id']]['manual'] ?? 0 ?></td>
<td class="text-center fw-bold"><?= $credits_map[$s['id']]['total'] ?? 0 ?></td>
<td>
<div class="action-buttons">
<a href="view.php?id=<?= $s['id'] ?>" class="btn btn-info btn-sm"><i class="bi bi-eye"></i> ดูข้อมูล</a>
<a href="edit.php?id=<?= $s['id'] ?>" class="btn btn-warning btn-sm"><i class="bi bi-pencil"></i> แก้ไข</a>
<a href="delete.php?id=<?= $s['id'] ?>" class="btn btn-danger btn-sm" onclick="return confirm('ยืนยันการลบข้อมูลนักศึกษา?')"><i class="bi bi-trash"></i> ลบ</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+373
View File
@@ -0,0 +1,373 @@
<?php
$page_title = 'ข้อมูลนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/header.php';
$id = intval($_GET['id'] ?? 0);
requireStudentAccess($id);
$student = getStudent($id);
if (!$student) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลนักศึกษา</div>';
require_once __DIR__ . '/../includes/footer.php';
exit;
}
if (isset($_GET['added'])) {
$msg_class = 'success';
$msg_text = 'เพิ่มรายวิชาเรียบร้อยแล้ว';
if ($_GET['added'] === '0') $msg_text = 'ไม่พบรายวิชาใหม่จากไฟล์ (อาจซ้ำหรือไม่ตรงหลักสูตร)';
echo "<div class='alert alert-$msg_class alert-dismissible fade show'>$msg_text<button type='button' class='btn-close' data-bs-dismiss='alert'></button></div>";
}
if (isset($_GET['error'])) {
$err = $_GET['error'];
$map = ['upload_failed' => 'อัปโหลดไฟล์ล้มเหลว', 'no_text' => 'ไม่พบข้อความในไฟล์ PDF', 'invalid_data' => 'ข้อมูลไม่ถูกต้อง'];
$txt = $map[$err] ?? $err;
echo "<div class='alert alert-danger alert-dismissible fade show'>$txt<button type='button' class='btn-close' data-bs-dismiss='alert'></button></div>";
}
$summary = getStudentSummary($id);
$student_courses = getStudentCourses($id);
// เรียง: ลงเรียนปกติ+ยังไม่ออกเกรด → เทียบโอน/ออกเกรดแล้ว
usort($student_courses, function($a, $b) {
$isUngraded = fn($r) => $r['grade'] === null || $r['grade'] === '' || $r['grade'] === '-' || mb_strpos((string)$r['grade'], 'กำลัง') === 0;
$a_ungraded = $isUngraded($a);
$b_ungraded = $isUngraded($b);
if ($a_ungraded && !$b_ungraded) return -1;
if (!$a_ungraded && $b_ungraded) return 1;
return strcasecmp($a['course_code'], $b['course_code']);
});
$total_curriculum_credits = getTotalCurriculumCredits($student['curriculum_id']);
$total_completed = getCompletedCreditsTotal($id);
$transfer_credits = getTransferCreditsTotal($id);
$remaining_credits = max(0, $total_curriculum_credits - $total_completed);
$is_dbc = ($student['curriculum_code'] == 'DBC');
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-person-vcard"></i> <?= htmlspecialchars($student['student_code']) ?> - <?= htmlspecialchars($student['name_th']) ?></h4>
<div class="text-muted small"><?= htmlspecialchars($student['curriculum_code']) ?> - <?= htmlspecialchars($student['curriculum_name']) ?></div>
</div>
<div class="action-buttons">
<?php if (!isStudent() || getCurrentUserId() == $id): ?>
<a href="edit.php?id=<?= $id ?>" class="btn btn-warning btn-sm"><i class="bi bi-pencil"></i> แก้ไข</a>
<?php endif; ?>
<a href="<?= BASE_URL ?>/reports/transfer_report.php?student_id=<?= $id ?>" class="btn btn-dark btn-sm" target="_blank"><i class="bi bi-file-earmark-text"></i> ผลการประเมินเทียบโอน</a>
<a href="<?= BASE_URL ?>/reports/print_summary.php?student_id=<?= $id ?>" class="btn btn-primary btn-sm" target="_blank"><i class="bi bi-printer"></i> พิมพ์สรุปผล</a>
</div>
</div>
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
<h4 class="mb-0"><i class="bi bi-person-vcard"></i> ข้อมูลนักศึกษา</h4>
</div>
<div class="card-body">
<table class="table table-sm table-borderless">
<tr><td style="width:180px;"><strong>รหัสนักศึกษา:</strong></td><td><?= htmlspecialchars($student['student_code']) ?></td></tr>
<tr><td><strong>ชื่อ-นามสกุล:</strong></td><td><?= htmlspecialchars($student['name_th']) ?> (<?= htmlspecialchars($student['name_en']) ?>)</td></tr>
<tr><td><strong>อีเมล:</strong></td><td><?= htmlspecialchars($student['email'] ?? '-') ?></td></tr>
<tr><td><strong>สาขาวิชา:</strong></td><td><span class="badge bg-<?= $is_dbc ? 'success' : 'primary' ?> fs-6"><?= htmlspecialchars($student['curriculum_code']) ?> - <?= htmlspecialchars($student['curriculum_name']) ?></span></td></tr>
<tr><td><strong>คณะ:</strong></td><td><?= htmlspecialchars($student['faculty'] ?: '-') ?></td></tr>
<tr><td><strong>สถาบันที่จบ:</strong></td><td><?= htmlspecialchars($student['graduated_institution'] ?: '-') ?></td></tr>
<tr><td><strong>วุฒิเดิม:</strong></td><td><?= htmlspecialchars($student['previous_qualification'] ?: '-') ?></td></tr>
<tr><td><strong>ปีการศึกษาที่เข้าศึกษา:</strong></td><td><?= $student['enrollment_year'] ?></td></tr>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card stat-card mb-3"><div class="card-body">
<span class="stat-icon"><i class="bi bi-arrow-left-right"></i></span>
<div class="stat-label">หน่วยกิตที่เทียบโอนได้</div>
<div class="stat-value"><?= $transfer_credits ?></div>
</div></div>
<div class="card stat-card mb-3"><div class="card-body">
<span class="stat-icon"><i class="bi bi-check2-circle"></i></span>
<div class="stat-label">หน่วยกิตที่เรียนผ่านแล้วทั้งหมด</div>
<div class="stat-value"><?= $total_completed ?></div>
</div></div>
<div class="card stat-card"><div class="card-body">
<span class="stat-icon"><i class="bi bi-mortarboard"></i></span>
<div class="stat-label">หน่วยกิตที่ต้องเรียนอีก</div>
<div class="stat-value"><?= $remaining_credits ?> / <?= $total_curriculum_credits ?></div>
<div class="progress mt-2" style="height: 12px;">
<div class="progress-bar bg-success" style="width: <?= min(100, ($total_completed / $total_curriculum_credits) * 100) ?>%">
<?= $total_curriculum_credits > 0 ? round(($total_completed / $total_curriculum_credits) * 100) : 0 ?>%
</div>
</div>
</div></div>
</div>
</div>
<div class="row">
<div class="col-md-7">
<div class="card">
<div class="card-header bg-<?= $is_dbc ? 'success' : 'primary' ?> text-white">
<h5 class="mb-0"><i class="bi bi-journal-text"></i> รายวิชาตามหลักสูตร</h5>
</div>
<div class="card-body" style="max-height: 650px; overflow-y: auto;">
<div class="small text-muted mb-2">
<span class="badge bg-success"><i class="bi bi-check-circle"></i> เรียนแล้ว</span>
<span class="badge bg-warning text-dark"><i class="bi bi-check-circle"></i> เทียบโอน</span>
<span class="badge bg-secondary">ยังไม่เรียน</span>
</div>
<?php renderStudentTree($summary['groups']); ?>
</div>
</div>
</div>
<div class="col-md-5">
<?php if (!isStudent()): ?>
<div class="card mb-3 border-warning">
<div class="card-header bg-warning">
<h5 class="mb-0"><i class="bi bi-upload"></i> นำเข้าข้อมูลเทียบโอน (PDF)</h5>
</div>
<div class="card-body">
<form action="<?= BASE_URL ?>/api/upload_pdf.php" method="POST" enctype="multipart/form-data" class="mb-2">
<input type="hidden" name="student_id" value="<?= $id ?>">
<div class="mb-2">
<label class="form-label">เลือกไฟล์ PDF รายวิชาที่เทียบโอน</label>
<input type="file" name="pdf_file" class="form-control" accept=".pdf" required>
</div>
<button type="submit" class="btn btn-warning"><i class="bi bi-cloud-upload"></i> อัปโหลดและวิเคราะห์</button>
</form>
<div class="alert alert-info small mb-0">
<i class="bi bi-info-circle"></i> ระบบจะอ่านรหัสวิชา ชื่อวิชา และหน่วยกิตจากไฟล์ PDF โดยอัตโนมัติ เฉพาะรายวิชาที่เทียบโอนได้จะถูกบันทึกลงแบบฟอร์มตามหลักสูตร
</div>
</div>
</div>
<div class="card mb-3 border-success">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-plus-circle"></i> เพิ่มรายวิชาที่กำลังเรียน</h5>
<a href="import_csv.php?student_id=<?= $id ?>" class="btn btn-light btn-sm"><i class="bi bi-upload"></i> นำเข้า CSV</a>
</div>
<div class="card-body">
<form method="POST" action="<?= BASE_URL ?>/api/add_manual_course.php">
<input type="hidden" name="student_id" value="<?= $id ?>">
<div class="mb-2">
<label class="form-label">รหัสวิชา</label>
<input type="text" name="course_code" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">ชื่อวิชา</label>
<input type="text" name="course_name" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">หมวดวิชา (เลือกถ้าต้องการให้แสดงในกลุ่มที่ถูกต้อง)</label>
<select name="group_id" class="form-select">
<option value="">-- อัตโนมัติ (จับคู่ตามรหัสวิชา) --</option>
<?= getGroupSelectOptions($student['curriculum_id']) ?>
</select>
</div>
<div class="mb-2">
<label class="form-label">หน่วยกิต</label>
<input type="number" name="credits" class="form-control" step="0.5" required>
</div>
<div class="row mb-2">
<div class="col-4">
<label class="form-label">บรรยาย</label>
<input type="number" name="lecture" class="form-control" value="0">
</div>
<div class="col-4">
<label class="form-label">ปฏิบัติ</label>
<input type="number" name="practice" class="form-control" value="0">
</div>
<div class="col-4">
<label class="form-label">ศึกษาด้วยตนเอง</label>
<input type="number" name="self_study" class="form-control" value="0">
</div>
</div>
<div class="row mb-2">
<div class="col-6">
<label class="form-label">เกรดที่ได้</label>
<select name="grade" class="form-select">
<option value="">-- เลือก --</option>
<?= getGradeOptions() ?>
</select>
</div>
<div class="col-6">
<label class="form-label">ประเภท</label>
<select name="course_type" class="form-select">
<option value="">-- เลือก --</option>
<?= getCourseTypeOptions() ?>
</select>
</div>
</div>
<div class="row mb-2">
<div class="col-6">
<label class="form-label">ภาคเรียน</label>
<select name="semester" class="form-select">
<option value="">-- ไม่ระบุ --</option>
<option value="1">ภาคเรียนที่ 1</option>
<option value="2">ภาคเรียนที่ 2</option>
<option value="summer">ภาคฤดูร้อน</option>
</select>
</div>
<div class="col-6">
<label class="form-label">ปีการศึกษา</label>
<select name="academic_year" class="form-select">
<option value="">-- ไม่ระบุ --</option>
<?php for ($y = 2569; $y >= 2565; $y--): ?>
<option value="<?= $y ?>"><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
</div>
<div class="mb-2">
<label class="form-label">หมายเหตุ</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
<button type="submit" class="btn btn-success"><i class="bi bi-plus-lg"></i> เพิ่มรายวิชา</button>
</form>
</div>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header bg-secondary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-list-check"></i> รายวิชาเทียบโอนและกำลังเรียนรอออกเกรด</h5>
<?php
$ungraded_total = 0;
foreach ($student_courses as $sc_tmp) {
if ($sc_tmp['grade'] === null || $sc_tmp['grade'] === '' || $sc_tmp['grade'] === '-' || mb_strpos((string)$sc_tmp['grade'], 'กำลัง') === 0) {
$ungraded_total++;
}
}
?>
<div>
<?php if ($ungraded_total > 0): ?>
<span class="badge bg-warning text-dark"><i class="bi bi-exclamation-triangle-fill"></i> รอออกเกรด <?= $ungraded_total ?> วิชา</span>
<?php endif; ?>
<span class="badge bg-light text-dark"><?= count($student_courses) ?> รายการ | <?= $total_completed ?> หน่วยกิต</span>
</div>
</div>
<div class="card-body" style="max-height: 400px; overflow-y: auto;">
<?php if (empty($student_courses)): ?>
<div class="text-muted text-center py-3">ยังไม่มีรายวิชาที่บันทึก</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead class="table-light">
<tr>
<th>รหัสวิชา</th>
<th>ชื่อวิชา</th>
<th>หน่วยกิต</th>
<th>เกรด</th>
<th>ภาคเรียน</th>
<th>ปีการศึกษา</th>
<th>ประเภท</th>
<?php if (!isStudent()): ?>
<th style="width:40px"></th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php
$t = 0;
$ungraded_count = 0;
$graded_count = 0;
$isUngraded2 = fn($r) => $r['grade'] === null || $r['grade'] === '' || $r['grade'] === '-' || mb_strpos((string)$r['grade'], 'กำลัง') === 0;
foreach ($student_courses as $sc_check) {
if ($isUngraded2($sc_check)) $ungraded_count++;
else $graded_count++;
}
$printed_ungraded_header = false;
$printed_separator = false;
foreach ($student_courses as $sc):
$t += $sc['credits'];
$is_ungraded = $isUngraded2($sc);
if ($is_ungraded && !$printed_ungraded_header) {
$printed_ungraded_header = true;
echo '<tr class="table-secondary"><td colspan="' . (isStudent() ? 7 : 8) . '" class="text-center small py-1"><i class="bi bi-clock-history"></i> รายวิชาที่กำลังเรียน/รอออกเกรด</td></tr>';
}
if (!$is_ungraded && !$printed_separator && $printed_ungraded_header) {
$printed_separator = true;
echo '<tr class="table-dark"><td colspan="' . (isStudent() ? 7 : 8) . '" class="text-center small py-1"><i class="bi bi-check-all"></i> รายวิชาที่เทียบโอน/ออกเกรดแล้ว</td></tr>';
}
?>
<tr class="<?= $is_ungraded ? 'table-warning' : '' ?>">
<td>
<?php if ($is_ungraded): ?><i class="bi bi-exclamation-triangle-fill text-warning me-1" title="ยังไม่ออกเกรด"></i><?php endif; ?>
<code><?= htmlspecialchars($sc['course_code']) ?></code>
</td>
<td class="small"><?= htmlspecialchars($sc['course_name_th']) ?></td>
<td><?= $sc['credits'] ?></td>
<td>
<?php if (isStudent()): ?>
<span class="badge bg-light text-dark"><?= htmlspecialchars($sc['grade'] ?: '-') ?></span>
<?php else: ?>
<form method="POST" action="<?= BASE_URL ?>/api/update_grade.php" class="d-inline">
<input type="hidden" name="id" value="<?= $sc['id'] ?>">
<input type="hidden" name="student_id" value="<?= $id ?>">
<select name="grade" class="form-select form-select-sm <?= $is_ungraded ? 'border-warning' : '' ?>" style="width:auto;min-width:60px" onchange="this.form.submit()">
<option value="">-</option>
<?php $grades = ['A','B+','B','C+','C','D+','D','F','S','U','กำลังเรียน']; foreach ($grades as $g): ?>
<option value="<?= $g ?>" <?= $sc['grade'] == $g ? 'selected' : '' ?>><?= $g ?></option>
<?php endforeach; ?>
</select>
</form>
<?php endif; ?>
</td>
<td class="small"><?= htmlspecialchars($sc['semester'] ? 'ภาค ' . $sc['semester'] : '-') ?></td>
<td class="small"><?= htmlspecialchars($sc['academic_year'] ?: '-') ?></td>
<td><span class="badge bg-<?= $sc['source_type'] == 'transfer' ? 'warning text-dark' : 'success' ?>"><?= htmlspecialchars($sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียนแล้ว')) ?></span></td>
<?php if (!isStudent()): ?>
<td><a href="<?= BASE_URL ?>/api/delete_course.php?id=<?= $sc['id'] ?>&student_id=<?= $id ?>" class="btn btn-danger btn-sm py-0 px-1" onclick="return confirm('ลบรายวิชานี้?')"><i class="bi bi-x"></i></a></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot class="table-secondary fw-bold">
<tr><td colspan="2" class="text-end">รวม</td><td><?= $t ?></td><td colspan="<?= isStudent() ? 4 : 5 ?>"></td></tr>
</tfoot>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
<?php
function renderStudentTree($nodes, $level = 0) {
foreach ($nodes as $node) {
$g = $node['group'];
echo '<div class="mb-2" style="margin-left: ' . ($level * 15) . 'px;">';
echo '<div class="fw-bold small text-secondary">' . htmlspecialchars($g['name_th']);
if ($g['min_credits'] > 0) {
echo ' <small class="text-muted">(' . $g['min_credits'] . ' หน่วยกิต)</small>';
}
echo '</div>';
foreach ($node['courses'] as $course) {
$status_class = '';
$status_badge = '<span class="badge bg-secondary status-badge">ยังไม่เรียน</span>';
if (isset($course['_completed'])) {
$sc = $course['_completed'];
$label = $sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียนแล้ว');
$grade_part = $sc['grade'] ? ' เกรด ' . htmlspecialchars($sc['grade']) : '';
if ($sc['source_type'] == 'transfer') {
$status_class = 'bg-warning bg-opacity-10';
$status_badge = '<span class="badge bg-warning text-dark status-badge"><i class="bi bi-check-circle"></i> ' . $label . $grade_part . '</span>';
} else {
$status_class = 'bg-success bg-opacity-10';
$status_badge = '<span class="badge bg-success status-badge"><i class="bi bi-check-circle"></i> ' . $label . $grade_part . '</span>';
}
}
echo '<div class="course-item d-flex justify-content-between align-items-center py-1 px-2 ' . $status_class . '">';
echo '<div><code>' . htmlspecialchars($course['code']) . '</code> <span class="small">' . htmlspecialchars($course['name_th']) . '</span></div>';
echo '<div><span class="badge-credit small">' . $course['credits'] . '(' . $course['lecture_hours'] . '-' . $course['practice_hours'] . '-' . $course['self_study_hours'] . ')</span> ' . $status_badge . '</div>';
echo '</div>';
}
if (!empty($node['children'])) {
renderStudentTree($node['children'], $level + 1);
}
echo '</div>';
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
$page_title = 'เพิ่มนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/header.php';
$curricula = getCurricula();
$faculties = getFacultyList();
$message = '';
$error = '';
$advisor_curriculum_id = isAdvisor() ? getCurrentUserCurriculumId() : null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$student_code = trim($_POST['student_code'] ?? '');
$name_th = trim($_POST['name_th'] ?? '');
$name_en = trim($_POST['name_en'] ?? '');
$email = trim($_POST['email'] ?? '');
$curriculum_id = $advisor_curriculum_id ?: intval($_POST['curriculum_id'] ?? 0);
$faculty = trim($_POST['faculty'] ?? '');
$graduated_institution = trim($_POST['graduated_institution'] ?? '');
$previous_qualification = trim($_POST['previous_qualification'] ?? '');
$enrollment_year = intval($_POST['enrollment_year'] ?? date('Y'));
if (empty($student_code) || empty($name_th) || empty($curriculum_id)) {
$error = 'กรุณากรอกข้อมูลให้ครบถ้วน (รหัสนักศึกษา, ชื่อ-นามสกุล, สาขาวิชา)';
} else {
try {
$advisor_id = isAdvisor() ? getCurrentUserId() : null;
$stmt = getDB()->prepare("INSERT INTO students (student_code, name_th, name_en, email, faculty, graduated_institution, curriculum_id, enrollment_year, advisor_id, previous_qualification) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$student_code, $name_th, $name_en, $email, $faculty, $graduated_institution, $curriculum_id, $enrollment_year, $advisor_id, $previous_qualification ?: null]);
$message = 'เพิ่มนักศึกษาสำเร็จ';
echo "<script>window.location.href='view.php?id=" . getDB()->lastInsertId() . "';</script>";
exit;
} catch (PDOException $e) {
if ($e->getCode() == 23000) {
$error = 'รหัสนักศึกษานี้มีอยู่ในระบบแล้ว';
} else {
$error = 'เกิดข้อผิดพลาด: ' . $e->getMessage();
}
}
}
}
?>
<div class="card">
<div class="card-header bg-success text-white">
<h4 class="mb-0"><i class="bi bi-person-plus"></i> เพิ่มนักศึกษาใหม่</h4>
</div>
<div class="card-body">
<?php if ($message): ?>
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST" class="row g-3">
<div class="col-md-3">
<label class="form-label">รหัสนักศึกษา <span class="text-danger">*</span></label>
<input type="text" name="student_code" class="form-control" required maxlength="20">
</div>
<div class="col-md-5">
<label class="form-label">ชื่อ-นามสกุล (ภาษาไทย) <span class="text-danger">*</span></label>
<input type="text" name="name_th" class="form-control" required>
</div>
<div class="col-md-4">
<label class="form-label">ชื่อ-นามสกุล (ภาษาอังกฤษ)</label>
<input type="text" name="name_en" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">อีเมล</label>
<input type="email" name="email" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">สาขาวิชา <span class="text-danger">*</span></label>
<?php if ($advisor_curriculum_id): ?>
<input type="text" class="form-control" value="<?php foreach($curricula as $c) { if ($c['id'] == $advisor_curriculum_id) { echo htmlspecialchars($c['code'] . ' - ' . $c['name_th']); break; } } ?>" disabled>
<input type="hidden" name="curriculum_id" value="<?= $advisor_curriculum_id ?>">
<?php else: ?>
<select name="curriculum_id" class="form-select" required>
<option value="">-- เลือกสาขาวิชา --</option>
<?php foreach ($curricula as $c): ?>
<option value="<?= $c['id'] ?>" data-faculty="<?= $c['code'] == 'DT' ? '12' : '12' ?>"><?= htmlspecialchars($c['code']) ?> - <?= htmlspecialchars($c['name_th']) ?></option>
<?php endforeach; ?>
</select>
<?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label">คณะ</label>
<select name="faculty" class="form-select">
<option value="">-- เลือกคณะ --</option>
<option value="คณะนวัตกรรมดิจิทัลเทคโนโลยี">คณะนวัตกรรมดิจิทัลเทคโนโลยี (12)</option>
<option value="คณะบริหารธุรกิจ">คณะบริหารธุรกิจ (11)</option>
<option value="คณะศึกษาศาสตร์และศิลปศาสตร์">คณะศึกษาศาสตร์และศิลปศาสตร์ (13)</option>
<option value="คณะบัญชี">คณะบัญชี (14)</option>
<option value="คณะนิติศาสตร์และรัฐศาสตร์">คณะนิติศาสตร์และรัฐศาสตร์ (15)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ชื่อสถาบันที่จบ</label>
<input type="text" name="graduated_institution" class="form-control" placeholder="ชื่อสถานศึกษาเดิม">
</div>
<div class="col-md-4">
<label class="form-label">วุฒิเดิม</label>
<select name="previous_qualification" class="form-select">
<option value="">-- เลือกวุฒิเดิม --</option>
<option value="ม.6 (กศน,สกร)">ม.6 (กศน,สกร)</option>
<option value="ปวช.3">ปวช.3</option>
<option value="ปวส.2">ปวส.2</option>
<option value="ปริญญาตรีใบที่ 2">ปริญญาตรีใบที่ 2</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ปีการศึกษาที่เข้าศึกษา</label>
<input type="number" name="enrollment_year" class="form-control" value="<?= date('Y') + 543 ?>" min="2569" step="1">
</div>
<div class="col-12">
<button type="submit" class="btn btn-success"><i class="bi bi-save"></i> บันทึก</button>
<a href="list.php" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> กลับ</a>
</div>
</form>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+20
View File
@@ -0,0 +1,20 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$id = intval($_GET['id'] ?? 0);
requireStudentAccess($id);
$student = getStudent($id);
if (!$student) {
header('Location: list.php');
exit;
}
try {
$stmt = getDB()->prepare("DELETE FROM students WHERE id = ?");
$stmt->execute([$id]);
} catch (PDOException $e) {
// ignore
}
header('Location: list.php');
exit;
+121
View File
@@ -0,0 +1,121 @@
<?php
$page_title = 'แก้ไขนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/header.php';
$id = intval($_GET['id'] ?? 0);
requireStudentAccess($id);
$student = getStudent($id);
if (!$student) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลนักศึกษา</div>';
require_once __DIR__ . '/../includes/footer.php';
exit;
}
$curricula = getCurricula();
$faculties = getFacultyList();
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$student_code = trim($_POST['student_code'] ?? '');
$name_th = trim($_POST['name_th'] ?? '');
$name_en = trim($_POST['name_en'] ?? '');
$email = trim($_POST['email'] ?? '');
$curriculum_id = intval($_POST['curriculum_id'] ?? 0);
$faculty = trim($_POST['faculty'] ?? '');
$graduated_institution = trim($_POST['graduated_institution'] ?? '');
$previous_qualification = trim($_POST['previous_qualification'] ?? '');
$enrollment_year = intval($_POST['enrollment_year'] ?? date('Y'));
if (empty($student_code) || empty($name_th) || empty($curriculum_id)) {
$error = 'กรุณากรอกข้อมูลให้ครบถ้วน';
} else {
try {
$stmt = getDB()->prepare("UPDATE students SET student_code=?, name_th=?, name_en=?, email=?, faculty=?, graduated_institution=?, curriculum_id=?, enrollment_year=?, previous_qualification=? WHERE id=?");
$stmt->execute([$student_code, $name_th, $name_en, $email, $faculty, $graduated_institution, $curriculum_id, $enrollment_year, $previous_qualification ?: null, $id]);
echo "<script>window.location.href='view.php?id=$id';</script>";
exit;
} catch (PDOException $e) {
$error = 'เกิดข้อผิดพลาด: ' . $e->getMessage();
}
}
}
?>
<div class="card">
<div class="card-header bg-warning">
<h4 class="mb-0"><i class="bi bi-pencil"></i> แก้ไขข้อมูลนักศึกษา</h4>
</div>
<div class="card-body">
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST" class="row g-3">
<div class="col-md-3">
<label class="form-label">รหัสนักศึกษา <span class="text-danger">*</span></label>
<input type="text" name="student_code" class="form-control" required value="<?= htmlspecialchars($student['student_code']) ?>">
</div>
<div class="col-md-5">
<label class="form-label">ชื่อ-นามสกุล (ภาษาไทย) <span class="text-danger">*</span></label>
<input type="text" name="name_th" class="form-control" required value="<?= htmlspecialchars($student['name_th']) ?>">
</div>
<div class="col-md-4">
<label class="form-label">ชื่อ-นามสกุล (ภาษาอังกฤษ)</label>
<input type="text" name="name_en" class="form-control" value="<?= htmlspecialchars($student['name_en']) ?>">
</div>
<div class="col-md-4">
<label class="form-label">อีเมล</label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($student['email'] ?? '') ?>">
</div>
<div class="col-md-4">
<label class="form-label">สาขาวิชา <span class="text-danger">*</span></label>
<select name="curriculum_id" class="form-select" required>
<option value="">-- เลือกสาขาวิชา --</option>
<?php foreach ($curricula as $c): ?>
<option value="<?= $c['id'] ?>" <?= $c['id'] == $student['curriculum_id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['code']) ?> - <?= htmlspecialchars($c['name_th']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">คณะ</label>
<select name="faculty" class="form-select">
<option value="">-- เลือกคณะ --</option>
<option value="คณะนวัตกรรมดิจิทัลเทคโนโลยี" <?= $student['faculty'] == 'คณะนวัตกรรมดิจิทัลเทคโนโลยี' ? 'selected' : '' ?>>คณะนวัตกรรมดิจิทัลเทคโนโลยี (12)</option>
<option value="คณะบริหารธุรกิจ" <?= $student['faculty'] == 'คณะบริหารธุรกิจ' ? 'selected' : '' ?>>คณะบริหารธุรกิจ (11)</option>
<option value="คณะศึกษาศาสตร์และศิลปศาสตร์" <?= $student['faculty'] == 'คณะศึกษาศาสตร์และศิลปศาสตร์' ? 'selected' : '' ?>>คณะศึกษาศาสตร์และศิลปศาสตร์ (13)</option>
<option value="คณะบัญชี" <?= $student['faculty'] == 'คณะบัญชี' ? 'selected' : '' ?>>คณะบัญชี (14)</option>
<option value="คณะนิติศาสตร์และรัฐศาสตร์" <?= $student['faculty'] == 'คณะนิติศาสตร์และรัฐศาสตร์' ? 'selected' : '' ?>>คณะนิติศาสตร์และรัฐศาสตร์ (15)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ชื่อสถาบันที่จบ</label>
<input type="text" name="graduated_institution" class="form-control" value="<?= htmlspecialchars($student['graduated_institution']) ?>">
</div>
<div class="col-md-4">
<label class="form-label">วุฒิเดิม</label>
<select name="previous_qualification" class="form-select">
<option value="">-- เลือกวุฒิเดิม --</option>
<?php
$quals = ['ม.6 (กศน,สกร)', 'ปวช.3', 'ปวส.2', 'ปริญญาตรีใบที่ 2'];
foreach ($quals as $q):
$sel = ($student['previous_qualification'] == $q) ? 'selected' : '';
?>
<option value="<?= htmlspecialchars($q) ?>" <?= $sel ?>><?= htmlspecialchars($q) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">ปีการศึกษาที่เข้าศึกษา</label>
<select name="enrollment_year" class="form-select">
<?php for ($y = date('Y')+543; $y >= 2560; $y--): ?>
<option value="<?= $y ?>" <?= $y == $student['enrollment_year'] ? 'selected' : '' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
<div class="col-12">
<button type="submit" class="btn btn-warning"><i class="bi bi-save"></i> บันทึกการแก้ไข</button>
<a href="view.php?id=<?= $id ?>" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> กลับ</a>
</div>
</form>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+206
View File
@@ -0,0 +1,206 @@
<?php
$page_title = 'นำเข้า CSV';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$student_id = intval($_GET['student_id'] ?? 0);
if (isset($_GET['template'])) {
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=import_template.csv');
echo "\xEF\xBB\xBF";
$cols = $student_id ? ['course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'] : ['student_code','course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'];
echo implode(',', $cols) . "\n";
if ($student_id) {
echo "1012301,การเขียนโปรแกรมเบื้องต้น,3,A,เรียนปกติ,1,2569,2,1,3,,\n";
} else {
echo "STU001,1012301,การเขียนโปรแกรมเบื้องต้น,3,A,เรียนปกติ,1,2569,2,1,3,,\n";
}
exit;
}
if ($student_id) {
requireStudentAccess($student_id);
$student = getStudent($student_id);
if (!$student) { echo '<div class="alert alert-danger">ไม่พบนักศึกษา</div>'; require_once __DIR__ . '/../includes/footer.php'; exit; }
}
require_once __DIR__ . '/../includes/header.php';
$results = [];
$total_success = 0;
$total_error = 0;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK) {
$tmp = $_FILES['csv_file']['tmp_name'];
// Read all rows at once
$all_rows = [];
$handle = fopen($tmp, 'r');
if ($handle) {
while (($r = fgetcsv($handle)) !== false) $all_rows[] = $r;
fclose($handle);
}
if (empty($all_rows)) { $results[] = 'ไฟล์ CSV ว่างเปล่า'; $total_error++; }
else {
$first = $all_rows[0];
$first[0] = preg_replace('/^\xEF\xBB\xBF/', '', $first[0]);
$first = array_map(function($v) { return trim($v, " \t\n\r\0\x0B\""); }, $first);
// Auto-detect header: if first cell starts with a digit, it's data (no header)
$has_header = !preg_match('/^\d/', $first[0]);
if ($has_header) {
$header_norm = array_map(function($v) {
return preg_replace('/[^a-z0-9_]/', '', str_replace(' ', '_', strtolower(trim($v))));
}, $first);
$results[] = "พบหัวคอลัมน์: " . implode(', ', $first);
$col_map = [];
$expected = $student_id ? ['course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'] : ['student_code','course_code','course_name','credits','grade','course_type','semester','academic_year','lecture','practice','self_study','notes','group_id'];
foreach ($expected as $e) {
$idx = array_search($e, $header_norm);
if ($idx !== false) $col_map[$e] = $idx;
}
array_shift($all_rows);
} else {
$results[] = "ไม่พบแถวหัวคอลัมน์ — ใช้ตำแหน่งคอลัมน์ตามลำดับ";
if ($student_id) {
$col_map = ['course_code' => 0, 'course_name' => 1, 'credits' => 2, 'grade' => 3, 'course_type' => 4, 'semester' => 5, 'academic_year' => 6, 'lecture' => 7, 'practice' => 8, 'self_study' => 9, 'notes' => 10, 'group_id' => 11];
} else {
$col_map = ['student_code' => 0, 'course_code' => 1, 'course_name' => 2, 'credits' => 3, 'grade' => 4, 'course_type' => 5, 'semester' => 6, 'academic_year' => 7, 'lecture' => 8, 'practice' => 9, 'self_study' => 10, 'notes' => 11, 'group_id' => 12];
}
}
$db = getDB();
foreach ($all_rows as $idx => $row) {
$line = $idx + 1;
$c = fn($k) => $col_map[$k] ?? null;
if ($student_id) {
$student = getStudent($student_id);
$student_code = $student['student_code'];
} else {
$student_code = trim($row[$c('student_code')] ?? '');
}
$course_code = trim($row[$c('course_code')] ?? '');
$course_name = trim($row[$c('course_name')] ?? '');
$credits = floatval($row[$c('credits')] ?? 0);
$grade = trim($row[$c('grade')] ?? '');
$course_type_col = trim($row[$c('course_type')] ?? '');
$semester = trim($row[$c('semester')] ?? '');
$academic_year = trim($row[$c('academic_year')] ?? '');
$lecture = intval($row[$c('lecture')] ?? 0);
$practice = intval($row[$c('practice')] ?? 0);
$self_study = intval($row[$c('self_study')] ?? 0);
$notes = trim($row[$c('notes')] ?? '');
$group_id = intval($row[$c('group_id')] ?? 0);
if (empty($course_code) || empty($course_name) || $credits <= 0) {
$results[] = "แถวที่ $line: ข้าม (ข้อมูลไม่ครบ: course_code, course_name, credits)";
$total_error++;
continue;
}
try {
if (!$student_id) {
$stmt = $db->prepare("SELECT id, curriculum_id, advisor_id FROM students WHERE student_code = ?");
$stmt->execute([$student_code]);
$student = $stmt->fetch();
if (!$student) {
$results[] = "แถวที่ $line: ไม่พบรหัสนักศึกษา '$student_code'";
$total_error++;
continue;
}
if (isAdvisor() && $student['advisor_id'] != getCurrentUserId()) {
$results[] = "แถวที่ $line: นักศึกษา '$student_code' ไม่อยู่ในที่ปรึกษาของคุณ";
$total_error++;
continue;
}
}
$curriculum_course = findCourseInCurriculum($student['curriculum_id'], $course_code);
$course_id = null;
if ($curriculum_course) {
$course_id = $curriculum_course['id'];
} elseif ($group_id > 0) {
$stmt = $db->prepare("SELECT id FROM courses WHERE group_id = ? AND code = ?");
$stmt->execute([$group_id, $course_code]);
$existing = $stmt->fetch();
if ($existing) {
$course_id = $existing['id'];
} else {
$stmt = $db->prepare("INSERT INTO courses (group_id, code, name_th, credits, lecture_hours, practice_hours, self_study_hours, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, 0)");
$stmt->execute([$group_id, $course_code, $course_name, $credits, $lecture, $practice, $self_study]);
$course_id = $db->lastInsertId();
}
}
$stmt = $db->prepare("INSERT INTO student_courses (student_id, course_id, course_code, course_name_th, credits, lecture_hours, practice_hours, self_study_hours, grade, course_type, semester, academic_year, source_type, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'manual', ?)");
$stmt->execute([$student['id'], $course_id, $course_code, $course_name, $credits, $lecture, $practice, $self_study, $grade ?: null, $course_type_col ?: null, $semester ?: null, $academic_year ?: null, $notes]);
$results[] = "แถวที่ $line: ✓ $student_code - $course_code ($course_name)";
$total_success++;
} catch (PDOException $e) {
$results[] = "แถวที่ $line: ผิดพลาด - " . $e->getMessage();
$total_error++;
}
}
}
}
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-upload"></i> นำเข้ารายวิชาจากไฟล์ CSV</h4>
<div class="text-muted small">รองรับไฟล์ .csv ตามรูปแบบคอลัมน์ที่ระบบกำหนด</div>
</div>
<a href="<?= $student_id ? 'view.php?id=' . $student_id : 'list.php' ?>" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> กลับ</a>
</div>
<?php if ($student_id): ?>
<div class="alert alert-info">กำลังนำเข้าสำหรับนักศึกษา: <strong><?= htmlspecialchars($student['student_code'] ?? '') ?> - <?= htmlspecialchars($student['name_th'] ?? '') ?></strong> (ไม่ต้องระบุ student_code ใน CSV)</div>
<?php endif; ?>
<div class="row g-4">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-filetype-csv"></i> เลือกไฟล์สำหรับนำเข้า</h5>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">เลือกไฟล์ CSV</label>
<input type="file" name="csv_file" class="form-control" accept=".csv" required>
</div>
<div class="mb-3">
<label class="form-label">รูปแบบไฟล์</label>
<p class="text-muted small mb-2">ไฟล์ CSV ต้องมีหัวคอลัมน์ด้านล่างนี้ (คั่นด้วย comma):</p>
<code class="d-block p-2 bg-light rounded small"><?= $student_id ? 'course_code,course_name,credits,grade,course_type,semester,academic_year,lecture,practice,self_study,notes,group_id' : 'student_code,course_code,course_name,credits,grade,course_type,semester,academic_year,lecture,practice,self_study,notes,group_id' ?></code>
<p class="text-muted small mt-2 mb-0">คอลัมน์ที่จำเป็น: <strong><?= $student_id ? 'course_code, course_name, credits' : 'student_code, course_code, course_name, credits' ?></strong></p>
</div>
<button type="submit" class="btn btn-primary"><i class="bi bi-cloud-upload"></i> นำเข้า</button>
</form>
</div>
</div>
<div class="card mt-3">
<div class="card-body">
<p class="mb-1 fw-bold">📥 ดาวน์โหลด Template</p>
<a href="import_csv.php?template=1<?= $student_id ? '&student_id=' . $student_id : '' ?>" class="btn btn-outline-success btn-sm"><i class="bi bi-download"></i> template.csv</a>
</div>
</div>
</div>
<div class="col-md-6">
<?php if ($total_success > 0 || $total_error > 0): ?>
<div class="card">
<div class="card-header bg-<?= $total_error > 0 ? 'warning' : 'success' ?> text-white d-flex justify-content-between">
<span><i class="bi bi-list-check"></i> ผลลัพธ์</span>
<span>สำเร็จ <?= $total_success ?> | ล้มเหลว <?= $total_error ?></span>
</div>
<div class="card-body" style="max-height:500px;overflow-y:auto">
<ul class="list-unstyled mb-0 small">
<?php foreach ($results as $r): ?>
<li class="mb-1 <?= str_starts_with($r, 'แถว') && str_contains($r, '✓') ? 'text-success' : (str_starts_with($r, 'แถว') ? 'text-danger' : 'text-muted') ?>"><?= htmlspecialchars($r) ?></li>
<?php endforeach; ?>
</ul>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+94
View File
@@ -0,0 +1,94 @@
<?php
$page_title = 'รายชื่อนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/header.php';
$students = getStudents();
$db = getDB();
$credits_map = [];
if (!empty($students)) {
$ids = array_column($students, 'id');
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $db->prepare("
SELECT
student_id,
COALESCE(SUM(CASE WHEN source_type = 'transfer' THEN credits ELSE 0 END), 0) AS transfer_total,
COALESCE(SUM(CASE WHEN source_type = 'manual' THEN credits ELSE 0 END), 0) AS manual_total,
COALESCE(SUM(credits), 0) AS total
FROM student_courses
WHERE student_id IN ($placeholders)
GROUP BY student_id
");
$stmt->execute($ids);
foreach ($stmt->fetchAll() as $row) {
$credits_map[$row['student_id']] = [
'transfer' => floatval($row['transfer_total']),
'manual' => floatval($row['manual_total']),
'total' => floatval($row['total'])
];
}
}
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-people"></i> รายชื่อนักศึกษา</h4>
<div class="text-muted small">ทั้งหมด <?= count($students) ?> รายการ</div>
</div>
<a href="add.php" class="btn btn-primary"><i class="bi bi-person-plus"></i> เพิ่มนักศึกษา</a>
</div>
<div class="card">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h4 class="mb-0"><i class="bi bi-table"></i> ตารางข้อมูลนักศึกษา</h4>
<span class="badge bg-primary"><?= count($students) ?> รายการ</span>
</div>
<div class="card-body">
<?php if (empty($students)): ?>
<div class="alert alert-info">ยังไม่มีข้อมูลนักศึกษา <a href="add.php">คลิกเพิ่มนักศึกษา</a></div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead class="table-light">
<tr>
<th>รหัสนักศึกษา</th>
<th>ชื่อ-นามสกุล</th>
<th>สาขาวิชา</th>
<th>คณะ</th>
<th>สถาบันที่จบ</th>
<th>วุฒิเดิม</th>
<th>ปีการศึกษา</th>
<th class="text-center">หน่วยกิตที่เทียบโอน</th>
<th class="text-center">หน่วยกิตที่เรียนแล้ว</th>
<th class="text-center">หน่วยกิตทั้งหมดที่เทียบโอนและเรียนแล้ว</th>
<th>การจัดการ</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $s): ?>
<tr>
<td><strong><?= htmlspecialchars($s['student_code']) ?></strong></td>
<td><?= htmlspecialchars($s['name_th']) ?></td>
<td><span class="badge bg-<?= $s['curriculum_code'] == 'DT' ? 'primary' : 'success' ?>"><?= htmlspecialchars($s['curriculum_code']) ?></span></td>
<td><small><?= htmlspecialchars($s['faculty'] ?: '-') ?></small></td>
<td><small><?= htmlspecialchars($s['graduated_institution'] ?: '-') ?></small></td>
<td><small><?= htmlspecialchars($s['previous_qualification'] ?: '-') ?></small></td>
<td><?= $s['enrollment_year'] ?></td>
<td class="text-center"><?= $credits_map[$s['id']]['transfer'] ?? 0 ?></td>
<td class="text-center"><?= $credits_map[$s['id']]['manual'] ?? 0 ?></td>
<td class="text-center fw-bold"><?= $credits_map[$s['id']]['total'] ?? 0 ?></td>
<td>
<div class="action-buttons">
<a href="view.php?id=<?= $s['id'] ?>" class="btn btn-info btn-sm"><i class="bi bi-eye"></i> ดูข้อมูล</a>
<a href="edit.php?id=<?= $s['id'] ?>" class="btn btn-warning btn-sm"><i class="bi bi-pencil"></i> แก้ไข</a>
<a href="delete.php?id=<?= $s['id'] ?>" class="btn btn-danger btn-sm" onclick="return confirm('ยืนยันการลบข้อมูลนักศึกษา?')"><i class="bi bi-trash"></i> ลบ</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+389
View File
@@ -0,0 +1,389 @@
<?php
$page_title = 'ข้อมูลนักศึกษา';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/header.php';
$id = intval($_GET['id'] ?? 0);
requireStudentAccess($id);
$student = getStudent($id);
if (!$student) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลนักศึกษา</div>';
require_once __DIR__ . '/../includes/footer.php';
exit;
}
if (isset($_GET['added'])) {
$msg_class = 'success';
$msg_text = 'เพิ่มรายวิชาเรียบร้อยแล้ว';
if ($_GET['added'] === '0') $msg_text = 'ไม่พบรายวิชาใหม่จากไฟล์ (อาจซ้ำหรือไม่ตรงหลักสูตร)';
echo "<div class='alert alert-$msg_class alert-dismissible fade show'>$msg_text<button type='button' class='btn-close' data-bs-dismiss='alert'></button></div>";
}
if (isset($_GET['error'])) {
$err = $_GET['error'];
$map = ['upload_failed' => 'อัปโหลดไฟล์ล้มเหลว', 'no_text' => 'ไม่พบข้อความในไฟล์ PDF', 'invalid_data' => 'ข้อมูลไม่ถูกต้อง'];
$txt = $map[$err] ?? $err;
echo "<div class='alert alert-danger alert-dismissible fade show'>$txt<button type='button' class='btn-close' data-bs-dismiss='alert'></button></div>";
}
$summary = getStudentSummary($id);
$student_courses = getStudentCourses($id);
// เรียง: ลงเรียนปกติ+ยังไม่ออกเกรด → เทียบโอน/ออกเกรดแล้ว
usort($student_courses, function($a, $b) {
$isUngraded = fn($r) => $r['grade'] === null || $r['grade'] === '' || $r['grade'] === '-' || mb_strpos((string)$r['grade'], 'กำลัง') === 0;
$a_ungraded = $isUngraded($a);
$b_ungraded = $isUngraded($b);
if ($a_ungraded && !$b_ungraded) return -1;
if (!$a_ungraded && $b_ungraded) return 1;
return strcasecmp($a['course_code'], $b['course_code']);
});
$total_curriculum_credits = getTotalCurriculumCredits($student['curriculum_id']);
$total_completed = getCompletedCreditsTotal($id);
$transfer_credits = getTransferCreditsTotal($id);
$remaining_credits = max(0, $total_curriculum_credits - $total_completed);
$is_dbc = ($student['curriculum_code'] == 'DBC');
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-person-vcard"></i> <?= htmlspecialchars($student['student_code']) ?> - <?= htmlspecialchars($student['name_th']) ?></h4>
<div class="text-muted small"><?= htmlspecialchars($student['curriculum_code']) ?> - <?= htmlspecialchars($student['curriculum_name']) ?></div>
</div>
<div class="action-buttons">
<a href="edit.php?id=<?= $id ?>" class="btn btn-warning btn-sm"><i class="bi bi-pencil"></i> แก้ไข</a>
<a href="<?= BASE_URL ?>/reports/transfer_report.php?student_id=<?= $id ?>" class="btn btn-dark btn-sm" target="_blank"><i class="bi bi-file-earmark-text"></i> ผลการประเมินเทียบโอน</a>
<a href="<?= BASE_URL ?>/reports/print_summary.php?student_id=<?= $id ?>" class="btn btn-primary btn-sm" target="_blank"><i class="bi bi-printer"></i> พิมพ์สรุปผล</a>
</div>
</div>
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
<h4 class="mb-0"><i class="bi bi-person-vcard"></i> ข้อมูลนักศึกษา</h4>
</div>
<div class="card-body">
<table class="table table-sm table-borderless">
<tr><td style="width:180px;"><strong>รหัสนักศึกษา:</strong></td><td><?= htmlspecialchars($student['student_code']) ?></td></tr>
<tr><td><strong>ชื่อ-นามสกุล:</strong></td><td><?= htmlspecialchars($student['name_th']) ?> (<?= htmlspecialchars($student['name_en']) ?>)</td></tr>
<tr><td><strong>อีเมล:</strong></td><td><?= htmlspecialchars($student['email'] ?? '-') ?></td></tr>
<tr><td><strong>สาขาวิชา:</strong></td><td><span class="badge bg-<?= $is_dbc ? 'success' : 'primary' ?> fs-6"><?= htmlspecialchars($student['curriculum_code']) ?> - <?= htmlspecialchars($student['curriculum_name']) ?></span></td></tr>
<tr><td><strong>คณะ:</strong></td><td><?= htmlspecialchars($student['faculty'] ?: '-') ?></td></tr>
<tr><td><strong>สถาบันที่จบ:</strong></td><td><?= htmlspecialchars($student['graduated_institution'] ?: '-') ?></td></tr>
<tr><td><strong>วุฒิเดิม:</strong></td><td><?= htmlspecialchars($student['previous_qualification'] ?: '-') ?></td></tr>
<tr><td><strong>ปีการศึกษาที่เข้าศึกษา:</strong></td><td><?= $student['enrollment_year'] ?></td></tr>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card stat-card mb-3"><div class="card-body">
<span class="stat-icon"><i class="bi bi-arrow-left-right"></i></span>
<div class="stat-label">หน่วยกิตที่เทียบโอนได้</div>
<div class="stat-value"><?= $transfer_credits ?></div>
</div></div>
<div class="card stat-card mb-3"><div class="card-body">
<span class="stat-icon"><i class="bi bi-check2-circle"></i></span>
<div class="stat-label">หน่วยกิตที่เรียนผ่านแล้วทั้งหมด</div>
<div class="stat-value"><?= $total_completed ?></div>
</div></div>
<div class="card stat-card"><div class="card-body">
<span class="stat-icon"><i class="bi bi-mortarboard"></i></span>
<div class="stat-label">หน่วยกิตที่ต้องเรียนอีก</div>
<div class="stat-value"><?= $remaining_credits ?> / <?= $total_curriculum_credits ?></div>
<div class="progress mt-2" style="height: 12px;">
<div class="progress-bar bg-success" style="width: <?= min(100, ($total_completed / $total_curriculum_credits) * 100) ?>%">
<?= $total_curriculum_credits > 0 ? round(($total_completed / $total_curriculum_credits) * 100) : 0 ?>%
</div>
</div>
</div></div>
</div>
</div>
<div class="row">
<div class="col-md-7">
<div class="card">
<div class="card-header bg-<?= $is_dbc ? 'success' : 'primary' ?> text-white">
<h5 class="mb-0"><i class="bi bi-journal-text"></i> รายวิชาตามหลักสูตร</h5>
</div>
<div class="card-body" style="max-height: 650px; overflow-y: auto;">
<div class="small text-muted mb-2">
<span class="badge bg-success"><i class="bi bi-check-circle"></i> เรียนแล้ว</span>
<span class="badge bg-warning text-dark"><i class="bi bi-check-circle"></i> เทียบโอน</span>
<span class="badge bg-secondary">ยังไม่เรียน</span>
</div>
<?php renderStudentTree($summary['groups']); ?>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card mb-3 border-warning">
<div class="card-header bg-warning text-dark">
<h5 class="mb-0"><i class="bi bi-file-earmark-arrow-up"></i> นำเข้าข้อมูลเทียบโอน</h5>
</div>
<div class="card-body">
<!-- Nav Tabs -->
<ul class="nav nav-pills nav-justified mb-3" id="importTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active btn-sm" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload" type="button" role="tab" aria-controls="upload" aria-selected="true"><i class="bi bi-upload"></i> อัปโหลด PDF</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn-sm" id="paste-tab" data-bs-toggle="tab" data-bs-target="#paste" type="button" role="tab" aria-controls="paste" aria-selected="false"><i class="bi bi-clipboard-plus"></i> คัดลอกข้อความวาง</button>
</li>
</ul>
<div class="tab-content" id="importTabContent">
<!-- Tab 1: Upload File -->
<div class="tab-pane fade show active" id="upload" role="tabpanel" aria-labelledby="upload-tab">
<form action="<?= BASE_URL ?>/api/upload_pdf.php" method="POST" enctype="multipart/form-data" class="mb-2">
<input type="hidden" name="student_id" value="<?= $id ?>">
<div class="mb-3">
<label class="form-label small text-muted">เลือกไฟล์ PDF รายวิชาที่เทียบโอน</label>
<input type="file" name="pdf_file" class="form-control form-control-sm" accept=".pdf" required>
</div>
<button type="submit" class="btn btn-warning btn-sm w-100"><i class="bi bi-cloud-upload"></i> อัปโหลดและวิเคราะห์</button>
</form>
</div>
<!-- Tab 2: Paste Text -->
<div class="tab-pane fade" id="paste" role="tabpanel" aria-labelledby="paste-tab">
<form action="<?= BASE_URL ?>/api/upload_text.php" method="POST" class="mb-2">
<input type="hidden" name="student_id" value="<?= $id ?>">
<div class="mb-3">
<label class="form-label small text-muted">เปิดไฟล์ PDF คัดลอกข้อความทั้งหมด (Ctrl+A -> Ctrl+C) แล้ววางที่นี่</label>
<textarea name="pdf_text" class="form-control form-control-sm" rows="5" placeholder="วางข้อความจาก PDF ที่นี่..." required></textarea>
</div>
<button type="submit" class="btn btn-dark btn-sm w-100"><i class="bi bi-clipboard-check"></i> วิเคราะห์จากข้อความที่วาง</button>
</form>
</div>
</div>
<div class="alert alert-info small mb-0 mt-2 p-2" style="font-size: 11px;">
<i class="bi bi-info-circle"></i> ระบบจะอ่านรหัสวิชา ชื่อวิชา และหน่วยกิตโดยอัตโนมัติ เฉพาะรายวิชาที่ตรงตามหลักสูตรจะได้รับการเทียบโอน
</div>
</div>
</div>
<div class="card mb-3 border-success">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-plus-circle"></i> เพิ่มรายวิชาที่กำลังเรียน</h5>
<a href="import_csv.php?student_id=<?= $id ?>" class="btn btn-light btn-sm"><i class="bi bi-upload"></i> นำเข้า CSV</a>
</div>
<div class="card-body">
<form method="POST" action="<?= BASE_URL ?>/api/add_manual_course.php">
<input type="hidden" name="student_id" value="<?= $id ?>">
<div class="mb-2">
<label class="form-label">รหัสวิชา</label>
<input type="text" name="course_code" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">ชื่อวิชา</label>
<input type="text" name="course_name" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">หมวดวิชา (เลือกถ้าต้องการให้แสดงในกลุ่มที่ถูกต้อง)</label>
<select name="group_id" class="form-select">
<option value="">-- อัตโนมัติ (จับคู่ตามรหัสวิชา) --</option>
<?= getGroupSelectOptions($student['curriculum_id']) ?>
</select>
</div>
<div class="mb-2">
<label class="form-label">หน่วยกิต</label>
<input type="number" name="credits" class="form-control" step="0.5" required>
</div>
<div class="row mb-2">
<div class="col-4">
<label class="form-label">บรรยาย</label>
<input type="number" name="lecture" class="form-control" value="0">
</div>
<div class="col-4">
<label class="form-label">ปฏิบัติ</label>
<input type="number" name="practice" class="form-control" value="0">
</div>
<div class="col-4">
<label class="form-label">ศึกษาด้วยตนเอง</label>
<input type="number" name="self_study" class="form-control" value="0">
</div>
</div>
<div class="row mb-2">
<div class="col-6">
<label class="form-label">เกรดที่ได้</label>
<select name="grade" class="form-select">
<option value="">-- เลือก --</option>
<?= getGradeOptions() ?>
</select>
</div>
<div class="col-6">
<label class="form-label">ประเภท</label>
<select name="course_type" class="form-select">
<option value="">-- เลือก --</option>
<?= getCourseTypeOptions() ?>
</select>
</div>
</div>
<div class="row mb-2">
<div class="col-6">
<label class="form-label">ภาคเรียน</label>
<select name="semester" class="form-select">
<option value="">-- ไม่ระบุ --</option>
<option value="1">ภาคเรียนที่ 1</option>
<option value="2">ภาคเรียนที่ 2</option>
<option value="summer">ภาคฤดูร้อน</option>
</select>
</div>
<div class="col-6">
<label class="form-label">ปีการศึกษา</label>
<select name="academic_year" class="form-select">
<option value="">-- ไม่ระบุ --</option>
<?php for ($y = 2569; $y >= 2565; $y--): ?>
<option value="<?= $y ?>"><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
</div>
<div class="mb-2">
<label class="form-label">หมายเหตุ</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
<button type="submit" class="btn btn-success"><i class="bi bi-plus-lg"></i> เพิ่มรายวิชา</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header bg-secondary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-list-check"></i> รายวิชาเทียบโอนและกำลังเรียนรอออกเกรด</h5>
<?php
$ungraded_total = 0;
foreach ($student_courses as $sc_tmp) {
if ($sc_tmp['grade'] === null || $sc_tmp['grade'] === '' || $sc_tmp['grade'] === '-' || mb_strpos((string)$sc_tmp['grade'], 'กำลัง') === 0) {
$ungraded_total++;
}
}
?>
<div>
<?php if ($ungraded_total > 0): ?>
<span class="badge bg-warning text-dark"><i class="bi bi-exclamation-triangle-fill"></i> รอออกเกรด <?= $ungraded_total ?> วิชา</span>
<?php endif; ?>
<span class="badge bg-light text-dark"><?= count($student_courses) ?> รายการ | <?= $total_completed ?> หน่วยกิต</span>
</div>
</div>
<div class="card-body" style="max-height: 400px; overflow-y: auto;">
<?php if (empty($student_courses)): ?>
<div class="text-muted text-center py-3">ยังไม่มีรายวิชาที่บันทึก</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead class="table-light">
<tr>
<th>รหัสวิชา</th>
<th>ชื่อวิชา</th>
<th>หน่วยกิต</th>
<th>เกรด</th>
<th>ภาคเรียน</th>
<th>ปีการศึกษา</th>
<th>ประเภท</th>
<th style="width:40px"></th>
</tr>
</thead>
<tbody>
<?php
$t = 0;
$ungraded_count = 0;
$graded_count = 0;
$isUngraded2 = fn($r) => $r['grade'] === null || $r['grade'] === '' || $r['grade'] === '-' || mb_strpos((string)$r['grade'], 'กำลัง') === 0;
foreach ($student_courses as $sc_check) {
if ($isUngraded2($sc_check)) $ungraded_count++;
else $graded_count++;
}
$printed_ungraded_header = false;
$printed_separator = false;
foreach ($student_courses as $sc):
$t += $sc['credits'];
$is_ungraded = $isUngraded2($sc);
if ($is_ungraded && !$printed_ungraded_header) {
$printed_ungraded_header = true;
echo '<tr class="table-secondary"><td colspan="8" class="text-center small py-1"><i class="bi bi-clock-history"></i> รายวิชาที่กำลังเรียน/รอออกเกรด</td></tr>';
}
if (!$is_ungraded && !$printed_separator && $printed_ungraded_header) {
$printed_separator = true;
echo '<tr class="table-dark"><td colspan="8" class="text-center small py-1"><i class="bi bi-check-all"></i> รายวิชาที่เทียบโอน/ออกเกรดแล้ว</td></tr>';
}
?>
<tr class="<?= $is_ungraded ? 'table-warning' : '' ?>">
<td>
<?php if ($is_ungraded): ?><i class="bi bi-exclamation-triangle-fill text-warning me-1" title="ยังไม่ออกเกรด"></i><?php endif; ?>
<code><?= htmlspecialchars($sc['course_code']) ?></code>
</td>
<td class="small"><?= htmlspecialchars($sc['course_name_th']) ?></td>
<td><?= $sc['credits'] ?></td>
<td>
<form method="POST" action="<?= BASE_URL ?>/api/update_grade.php" class="d-inline">
<input type="hidden" name="id" value="<?= $sc['id'] ?>">
<input type="hidden" name="student_id" value="<?= $id ?>">
<select name="grade" class="form-select form-select-sm <?= $is_ungraded ? 'border-warning' : '' ?>" style="width:auto;min-width:60px" onchange="this.form.submit()">
<option value="">-</option>
<?php $grades = ['A','B+','B','C+','C','D+','D','F','S','U','กำลังเรียน']; foreach ($grades as $g): ?>
<option value="<?= $g ?>" <?= $sc['grade'] == $g ? 'selected' : '' ?>><?= $g ?></option>
<?php endforeach; ?>
</select>
</form>
</td>
<td class="small"><?= htmlspecialchars($sc['semester'] ? 'ภาค ' . $sc['semester'] : '-') ?></td>
<td class="small"><?= htmlspecialchars($sc['academic_year'] ?: '-') ?></td>
<td><span class="badge bg-<?= $sc['source_type'] == 'transfer' ? 'warning text-dark' : 'success' ?>"><?= htmlspecialchars($sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียนแล้ว')) ?></span></td>
<td><a href="<?= BASE_URL ?>/api/delete_course.php?id=<?= $sc['id'] ?>&student_id=<?= $id ?>" class="btn btn-danger btn-sm py-0 px-1" onclick="return confirm('ลบรายวิชานี้?')"><i class="bi bi-x"></i></a></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot class="table-secondary fw-bold">
<tr><td colspan="2" class="text-end">รวม</td><td><?= $t ?></td><td colspan="5"></td></tr>
</tfoot>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
<?php
function renderStudentTree($nodes, $level = 0) {
foreach ($nodes as $node) {
$g = $node['group'];
echo '<div class="mb-2" style="margin-left: ' . ($level * 15) . 'px;">';
echo '<div class="fw-bold small text-secondary">' . htmlspecialchars($g['name_th']);
if ($g['min_credits'] > 0) {
echo ' <small class="text-muted">(' . $g['min_credits'] . ' หน่วยกิต)</small>';
}
echo '</div>';
foreach ($node['courses'] as $course) {
$status_class = '';
$status_badge = '<span class="badge bg-secondary status-badge">ยังไม่เรียน</span>';
if (isset($course['_completed'])) {
$sc = $course['_completed'];
$label = $sc['course_type'] ?: ($sc['source_type'] == 'transfer' ? 'เทียบโอน' : 'เรียนแล้ว');
$grade_part = $sc['grade'] ? ' เกรด ' . htmlspecialchars($sc['grade']) : '';
if ($sc['source_type'] == 'transfer') {
$status_class = 'bg-warning bg-opacity-10';
$status_badge = '<span class="badge bg-warning text-dark status-badge"><i class="bi bi-check-circle"></i> ' . $label . $grade_part . '</span>';
} else {
$status_class = 'bg-success bg-opacity-10';
$status_badge = '<span class="badge bg-success status-badge"><i class="bi bi-check-circle"></i> ' . $label . $grade_part . '</span>';
}
}
echo '<div class="course-item d-flex justify-content-between align-items-center py-1 px-2 ' . $status_class . '">';
echo '<div><code>' . htmlspecialchars($course['code']) . '</code> <span class="small">' . htmlspecialchars($course['name_th']) . '</span></div>';
echo '<div><span class="badge-credit small">' . $course['credits'] . '(' . $course['lecture_hours'] . '-' . $course['practice_hours'] . '-' . $course['self_study_hours'] . ')</span> ' . $status_badge . '</div>';
echo '</div>';
}
if (!empty($node['children'])) {
renderStudentTree($node['children'], $level + 1);
}
echo '</div>';
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
$page_title = 'เพิ่มผู้ใช้';
require_once __DIR__ . '/../includes/functions.php';
requireAdmin();
require_once __DIR__ . '/../includes/header.php';
$curricula = getCurricula();
$error = '';
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$staff_code = trim($_POST['staff_code'] ?? '');
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$curriculum_id = !empty($_POST['curriculum_id']) ? intval($_POST['curriculum_id']) : null;
$role = $_POST['role'] ?? 'advisor';
$password = trim($_POST['password'] ?? '');
if (empty($staff_code) || empty($name) || empty($email) || empty($password)) {
$error = 'กรุณากรอกข้อมูลให้ครบถ้วน';
} else {
try {
$db = getDB();
$stmt = $db->prepare("SELECT COUNT(*) FROM users WHERE staff_code = ?");
$stmt->execute([$staff_code]);
if ($stmt->fetchColumn() > 0) {
$error = 'รหัสบุคลากรนี้มีอยู่ในระบบแล้ว';
} else {
$stmt = $db->prepare("INSERT INTO users (staff_code, name, email, curriculum_id, role, password) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$staff_code, $name, $email, $curriculum_id, $role, password_hash($password, PASSWORD_DEFAULT)]);
$message = 'เพิ่มผู้ใช้สำเร็จ';
echo "<script>window.location.href='list.php';</script>";
exit;
}
} catch (PDOException $e) {
$error = 'เกิดข้อผิดพลาด: ' . $e->getMessage();
}
}
}
?>
<h4 class="mb-3">เพิ่มผู้ใช้</h4>
<?php if ($error): ?><div class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<?php if ($message): ?><div class="alert alert-success"><?= htmlspecialchars($message) ?></div><?php endif; ?>
<div class="card">
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">รหัสบุคลากร <span class="text-danger">*</span></label>
<input type="text" name="staff_code" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">ชื่อ-สกุล <span class="text-danger">*</span></label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">อีเมล <span class="text-danger">*</span></label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">สาขาวิชา</label>
<select name="curriculum_id" class="form-select">
<option value="">-- ไม่ระบุ --</option>
<?php foreach ($curricula as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['code']) ?> - <?= htmlspecialchars($c['name_th']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">บทบาท</label>
<select name="role" class="form-select">
<option value="advisor">อาจารย์ที่ปรึกษา</option>
<option value="admin">ผู้ดูแลระบบ</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">รหัสผ่าน <span class="text-danger">*</span></label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> บันทึก</button>
<a href="list.php" class="btn btn-secondary">ยกเลิก</a>
</form>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+11
View File
@@ -0,0 +1,11 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
requireAdmin();
$id = intval($_GET['id'] ?? 0);
if ($id > 0) {
$db = getDB();
$stmt = $db->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
}
header('Location: list.php?deleted=1');
exit;
+96
View File
@@ -0,0 +1,96 @@
<?php
$page_title = 'แก้ไขผู้ใช้';
require_once __DIR__ . '/../includes/functions.php';
requireAdmin();
require_once __DIR__ . '/../includes/header.php';
$id = intval($_GET['id'] ?? 0);
$user = getUser($id);
if (!$user) {
echo '<div class="alert alert-danger">ไม่พบผู้ใช้</div>';
require_once __DIR__ . '/../includes/footer.php';
exit;
}
$curricula = getCurricula();
$error = '';
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$staff_code = trim($_POST['staff_code'] ?? '');
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$curriculum_id = !empty($_POST['curriculum_id']) ? intval($_POST['curriculum_id']) : null;
$role = $_POST['role'] ?? 'advisor';
$password = trim($_POST['password'] ?? '');
if (empty($staff_code) || empty($name) || empty($email)) {
$error = 'กรุณากรอกข้อมูลให้ครบถ้วน';
} else {
try {
$db = getDB();
$stmt = $db->prepare("SELECT COUNT(*) FROM users WHERE staff_code = ? AND id != ?");
$stmt->execute([$staff_code, $id]);
if ($stmt->fetchColumn() > 0) {
$error = 'รหัสบุคลากรนี้มีผู้ใช้อื่นใช้อยู่แล้ว';
} else {
if (!empty($password)) {
$stmt = $db->prepare("UPDATE users SET staff_code=?, name=?, email=?, curriculum_id=?, role=?, password=? WHERE id=?");
$stmt->execute([$staff_code, $name, $email, $curriculum_id, $role, password_hash($password, PASSWORD_DEFAULT), $id]);
} else {
$stmt = $db->prepare("UPDATE users SET staff_code=?, name=?, email=?, curriculum_id=?, role=? WHERE id=?");
$stmt->execute([$staff_code, $name, $email, $curriculum_id, $role, $id]);
}
$message = 'บันทึกการแก้ไขเรียบร้อย';
$user = getUser($id);
}
} catch (PDOException $e) {
$error = 'เกิดข้อผิดพลาด: ' . $e->getMessage();
}
}
}
?>
<h4 class="mb-3">แก้ไขผู้ใช้: <?= htmlspecialchars($user['name']) ?></h4>
<?php if ($error): ?><div class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<?php if ($message): ?><div class="alert alert-success"><?= htmlspecialchars($message) ?></div><?php endif; ?>
<div class="card">
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">รหัสบุคลากร <span class="text-danger">*</span></label>
<input type="text" name="staff_code" class="form-control" value="<?= htmlspecialchars($user['staff_code']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">ชื่อ-สกุล <span class="text-danger">*</span></label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($user['name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">อีเมล <span class="text-danger">*</span></label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">สาขาวิชา</label>
<select name="curriculum_id" class="form-select">
<option value="">-- ไม่ระบุ --</option>
<?php foreach ($curricula as $c): ?>
<option value="<?= $c['id'] ?>" <?= $user['curriculum_id'] == $c['id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['code']) ?> - <?= htmlspecialchars($c['name_th']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">บทบาท</label>
<select name="role" class="form-select">
<option value="advisor" <?= $user['role'] == 'advisor' ? 'selected' : '' ?>>อาจารย์ที่ปรึกษา</option>
<option value="admin" <?= $user['role'] == 'admin' ? 'selected' : '' ?>>ผู้ดูแลระบบ</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">รหัสผ่านใหม่ <small class="text-muted">(เว้นว่างไว้ถ้าไม่ต้องการเปลี่ยน)</small></label>
<input type="password" name="password" class="form-control">
</div>
<button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> บันทึก</button>
<a href="list.php" class="btn btn-secondary">ยกเลิก</a>
</form>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+58
View File
@@ -0,0 +1,58 @@
<?php
$page_title = 'จัดการผู้ใช้';
require_once __DIR__ . '/../includes/functions.php';
requireAdmin();
require_once __DIR__ . '/../includes/header.php';
$db = getDB();
$users = $db->query("SELECT u.*, 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();
if (isset($_GET['deleted'])): ?>
<div class="alert alert-success alert-dismissible fade show">ลบผู้ใช้เรียบร้อยแล้ว<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>
<?php endif; ?>
<div class="page-heading">
<div>
<h4>จัดการผู้ใช้</h4>
<div class="text-muted small">ทั้งหมด <?= count($users) ?> บัญชี</div>
</div>
<a href="add.php" class="btn btn-primary"><i class="bi bi-plus-lg"></i> เพิ่มผู้ใช้</a>
</div>
<div class="card">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>รหัสบุคลากร</th>
<th>ชื่อ-สกุล</th>
<th>อีเมล</th>
<th>สาขาวิชา</th>
<th>บทบาท</th>
<th style="width:140px">จัดการ</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td><?= htmlspecialchars($u['staff_code']) ?></td>
<td><?= htmlspecialchars($u['name']) ?></td>
<td><?= htmlspecialchars($u['email']) ?></td>
<td><?= htmlspecialchars($u['curriculum_name'] ?? '-') ?></td>
<td><?= $u['role'] == 'admin' ? 'ผู้ดูแลระบบ' : 'อาจารย์ที่ปรึกษา' ?></td>
<td>
<div class="action-buttons">
<a href="edit.php?id=<?= $u['id'] ?>" class="btn btn-sm btn-warning"><i class="bi bi-pencil"></i></a>
<a href="delete.php?id=<?= $u['id'] ?>" class="btn btn-sm btn-danger" onclick="return confirm('ยืนยันลบผู้ใช้ <?= htmlspecialchars($u['name']) ?>?')"><i class="bi bi-trash"></i></a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+81
View File
@@ -0,0 +1,81 @@
<?php
$page_title = 'ข้อมูลส่วนตัว';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
// Hardcoded admin has no DB record
if ($_SESSION['user_id'] === null) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$user = getUser($_SESSION['user_id']);
if (!$user) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลผู้ใช้</div>';
exit;
}
require_once __DIR__ . '/../includes/header.php';
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-person-bounding-box"></i> ข้อมูลส่วนตัว</h4>
<div class="text-muted small">ดูรายละเอียดข้อมูลบัญชีผู้ใช้ของคุณ</div>
</div>
<a href="<?= BASE_URL ?>/index.php" class="btn btn-secondary btn-sm"><i class="bi bi-arrow-left"></i> กลับหน้าหลัก</a>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-person-circle"></i> รายละเอียดผู้ใช้งาน</h5>
</div>
<div class="card-body">
<table class="table table-borderless table-striped">
<tr>
<th style="width: 200px;">รหัสบุคลากร:</th>
<td><?= htmlspecialchars($user['staff_code']) ?></td>
</tr>
<tr>
<th>ชื่อ-สกุล:</th>
<td><?= htmlspecialchars($user['name']) ?></td>
</tr>
<tr>
<th>อีเมล:</th>
<td><?= htmlspecialchars($user['email'] ?: '-') ?></td>
</tr>
<tr>
<th>บทบาทในระบบ:</th>
<td>
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>">
<?= $user['role'] === 'admin' ? 'ผู้ดูแลระบบ' : 'อาจารย์ที่ปรึกษา' ?>
</span>
</td>
</tr>
<tr>
<th>สาขาวิชาที่ดูแล:</th>
<td>
<?= htmlspecialchars($user['curriculum_name'] ?: 'ไม่ระบุ') ?>
<?php if ($user['curriculum_code']): ?>
(<?= htmlspecialchars($user['curriculum_code']) ?>)
<?php endif; ?>
</td>
</tr>
</table>
<hr>
<div class="d-flex justify-content-between">
<a href="profile_edit.php" class="btn btn-warning"><i class="bi bi-pencil-square"></i> แก้ไขข้อมูลส่วนตัว</a>
</div>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+115
View File
@@ -0,0 +1,115 @@
<?php
$page_title = 'แก้ไขข้อมูลส่วนตัว';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
if (isStudent()) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
// Hardcoded admin has no DB record
if ($_SESSION['user_id'] === null) {
header('Location: ' . BASE_URL . '/index.php');
exit;
}
$id = $_SESSION['user_id'];
$user = getUser($id);
if (!$user) {
echo '<div class="alert alert-danger">ไม่พบข้อมูลผู้ใช้</div>';
exit;
}
$error = '';
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$staff_code = trim($_POST['staff_code'] ?? '');
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = trim($_POST['password'] ?? '');
if (empty($staff_code) || empty($name) || empty($email)) {
$error = 'กรุณากรอกข้อมูลให้ครบถ้วน';
} else {
try {
$db = getDB();
// Check if staff code is already in use by another user
$stmt = $db->prepare("SELECT COUNT(*) FROM users WHERE staff_code = ? AND id != ?");
$stmt->execute([$staff_code, $id]);
if ($stmt->fetchColumn() > 0) {
$error = 'รหัสบุคลากรนี้มีผู้ใช้อื่นใช้อยู่แล้ว';
} else {
if (!empty($password)) {
$stmt = $db->prepare("UPDATE users SET staff_code=?, name=?, email=?, password=? WHERE id=?");
$stmt->execute([$staff_code, $name, $email, password_hash($password, PASSWORD_DEFAULT), $id]);
} else {
$stmt = $db->prepare("UPDATE users SET staff_code=?, name=?, email=? WHERE id=?");
$stmt->execute([$staff_code, $name, $email, $id]);
}
// Update session username to match new name
$_SESSION['username'] = $name;
$message = 'บันทึกการแก้ไขเรียบร้อย';
$user = getUser($id);
}
} catch (PDOException $e) {
$error = 'เกิดข้อผิดพลาด: ' . $e->getMessage();
}
}
}
require_once __DIR__ . '/../includes/header.php';
?>
<div class="page-heading">
<div>
<h4><i class="bi bi-pencil-square"></i> แก้ไขข้อมูลส่วนตัว</h4>
<div class="text-muted small">แก้ไขข้อมูลส่วนตัวและเปลี่ยนรหัสผ่านของคุณ</div>
</div>
<a href="profile.php" class="btn btn-secondary btn-sm"><i class="bi bi-arrow-left"></i> กลับ</a>
</div>
<?php if ($error): ?><div class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<?php if ($message): ?><div class="alert alert-success"><?= htmlspecialchars($message) ?></div><?php endif; ?>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">รหัสบุคลากร <span class="text-danger">*</span></label>
<input type="text" name="staff_code" class="form-control" value="<?= htmlspecialchars($user['staff_code']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">ชื่อ-สกุล <span class="text-danger">*</span></label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($user['name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">อีเมล <span class="text-danger">*</span></label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">บทบาทในระบบ</label>
<input type="text" class="form-control" value="<?= $user['role'] === 'admin' ? 'ผู้ดูแลระบบ' : 'อาจารย์ที่ปรึกษา' ?>" disabled readonly>
<small class="text-muted">ไม่สามารถแก้ไขบทบาทด้วยตัวเองได้</small>
</div>
<div class="mb-3">
<label class="form-label">รหัสผ่านใหม่ <small class="text-muted">(เว้นว่างไว้หากไม่ต้องการเปลี่ยน)</small></label>
<input type="password" name="password" class="form-control" placeholder="กรอกรหัสผ่านใหม่">
</div>
<hr>
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> บันทึกการเปลี่ยนแปลง</button>
<a href="profile.php" class="btn btn-secondary">ยกเลิก</a>
</div>
</form>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
+16
View File
@@ -0,0 +1,16 @@
<?php
$type = 'Core';
$name = 'Courier';
$up = -100;
$ut = 50;
for($i=0;$i<=255;$i++)
$cw[chr($i)] = 600;
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+16
View File
@@ -0,0 +1,16 @@
<?php
$type = 'Core';
$name = 'Courier-Bold';
$up = -100;
$ut = 50;
for($i=0;$i<=255;$i++)
$cw[chr($i)] = 600;
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+16
View File
@@ -0,0 +1,16 @@
<?php
$type = 'Core';
$name = 'Courier-BoldOblique';
$up = -100;
$ut = 50;
for($i=0;$i<=255;$i++)
$cw[chr($i)] = 600;
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+16
View File
@@ -0,0 +1,16 @@
<?php
$type = 'Core';
$name = 'Courier-Oblique';
$up = -100;
$ut = 50;
for($i=0;$i<=255;$i++)
$cw[chr($i)] = 600;
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Helvetica';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278,
chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>278,'"'=>355,'#'=>556,'$'=>556,'%'=>889,'&'=>667,'\''=>191,'('=>333,')'=>333,'*'=>389,'+'=>584,
','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>278,';'=>278,'<'=>584,'='=>584,'>'=>584,'?'=>556,'@'=>1015,'A'=>667,
'B'=>667,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>500,'K'=>667,'L'=>556,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944,
'X'=>667,'Y'=>667,'Z'=>611,'['=>278,'\\'=>278,']'=>278,'^'=>469,'_'=>556,'`'=>333,'a'=>556,'b'=>556,'c'=>500,'d'=>556,'e'=>556,'f'=>278,'g'=>556,'h'=>556,'i'=>222,'j'=>222,'k'=>500,'l'=>222,'m'=>833,
'n'=>556,'o'=>556,'p'=>556,'q'=>556,'r'=>333,'s'=>500,'t'=>278,'u'=>556,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>500,'{'=>334,'|'=>260,'}'=>334,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>222,chr(131)=>556,
chr(132)=>333,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>222,chr(146)=>222,chr(147)=>333,chr(148)=>333,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000,
chr(154)=>500,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>260,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333,
chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>556,chr(182)=>537,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667,
chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>500,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>556,chr(241)=>556,
chr(242)=>556,chr(243)=>556,chr(244)=>556,chr(245)=>556,chr(246)=>556,chr(247)=>584,chr(248)=>611,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Helvetica-Bold';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278,
chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>333,'"'=>474,'#'=>556,'$'=>556,'%'=>889,'&'=>722,'\''=>238,'('=>333,')'=>333,'*'=>389,'+'=>584,
','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>333,';'=>333,'<'=>584,'='=>584,'>'=>584,'?'=>611,'@'=>975,'A'=>722,
'B'=>722,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>556,'K'=>722,'L'=>611,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944,
'X'=>667,'Y'=>667,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>584,'_'=>556,'`'=>333,'a'=>556,'b'=>611,'c'=>556,'d'=>611,'e'=>556,'f'=>333,'g'=>611,'h'=>611,'i'=>278,'j'=>278,'k'=>556,'l'=>278,'m'=>889,
'n'=>611,'o'=>611,'p'=>611,'q'=>611,'r'=>389,'s'=>556,'t'=>333,'u'=>611,'v'=>556,'w'=>778,'x'=>556,'y'=>556,'z'=>500,'{'=>389,'|'=>280,'}'=>389,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>278,chr(131)=>556,
chr(132)=>500,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>278,chr(146)=>278,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000,
chr(154)=>556,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>280,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333,
chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>611,chr(182)=>556,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722,
chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>556,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>611,chr(241)=>611,
chr(242)=>611,chr(243)=>611,chr(244)=>611,chr(245)=>611,chr(246)=>611,chr(247)=>584,chr(248)=>611,chr(249)=>611,chr(250)=>611,chr(251)=>611,chr(252)=>611,chr(253)=>556,chr(254)=>611,chr(255)=>556);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Helvetica-BoldOblique';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278,
chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>333,'"'=>474,'#'=>556,'$'=>556,'%'=>889,'&'=>722,'\''=>238,'('=>333,')'=>333,'*'=>389,'+'=>584,
','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>333,';'=>333,'<'=>584,'='=>584,'>'=>584,'?'=>611,'@'=>975,'A'=>722,
'B'=>722,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>556,'K'=>722,'L'=>611,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944,
'X'=>667,'Y'=>667,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>584,'_'=>556,'`'=>333,'a'=>556,'b'=>611,'c'=>556,'d'=>611,'e'=>556,'f'=>333,'g'=>611,'h'=>611,'i'=>278,'j'=>278,'k'=>556,'l'=>278,'m'=>889,
'n'=>611,'o'=>611,'p'=>611,'q'=>611,'r'=>389,'s'=>556,'t'=>333,'u'=>611,'v'=>556,'w'=>778,'x'=>556,'y'=>556,'z'=>500,'{'=>389,'|'=>280,'}'=>389,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>278,chr(131)=>556,
chr(132)=>500,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>278,chr(146)=>278,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000,
chr(154)=>556,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>280,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333,
chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>611,chr(182)=>556,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722,
chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>556,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>611,chr(241)=>611,
chr(242)=>611,chr(243)=>611,chr(244)=>611,chr(245)=>611,chr(246)=>611,chr(247)=>584,chr(248)=>611,chr(249)=>611,chr(250)=>611,chr(251)=>611,chr(252)=>611,chr(253)=>556,chr(254)=>611,chr(255)=>556);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Helvetica-Oblique';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278,
chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>278,'"'=>355,'#'=>556,'$'=>556,'%'=>889,'&'=>667,'\''=>191,'('=>333,')'=>333,'*'=>389,'+'=>584,
','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>278,';'=>278,'<'=>584,'='=>584,'>'=>584,'?'=>556,'@'=>1015,'A'=>667,
'B'=>667,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>500,'K'=>667,'L'=>556,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944,
'X'=>667,'Y'=>667,'Z'=>611,'['=>278,'\\'=>278,']'=>278,'^'=>469,'_'=>556,'`'=>333,'a'=>556,'b'=>556,'c'=>500,'d'=>556,'e'=>556,'f'=>278,'g'=>556,'h'=>556,'i'=>222,'j'=>222,'k'=>500,'l'=>222,'m'=>833,
'n'=>556,'o'=>556,'p'=>556,'q'=>556,'r'=>333,'s'=>500,'t'=>278,'u'=>556,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>500,'{'=>334,'|'=>260,'}'=>334,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>222,chr(131)=>556,
chr(132)=>333,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>222,chr(146)=>222,chr(147)=>333,chr(148)=>333,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000,
chr(154)=>500,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>260,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333,
chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>556,chr(182)=>537,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667,
chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>500,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>556,chr(241)=>556,
chr(242)=>556,chr(243)=>556,chr(244)=>556,chr(245)=>556,chr(246)=>556,chr(247)=>584,chr(248)=>611,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
$type = 'Core';
$name = 'Symbol';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250,
chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>713,'#'=>500,'$'=>549,'%'=>833,'&'=>778,'\''=>439,'('=>333,')'=>333,'*'=>500,'+'=>549,
','=>250,'-'=>549,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>278,';'=>278,'<'=>549,'='=>549,'>'=>549,'?'=>444,'@'=>549,'A'=>722,
'B'=>667,'C'=>722,'D'=>612,'E'=>611,'F'=>763,'G'=>603,'H'=>722,'I'=>333,'J'=>631,'K'=>722,'L'=>686,'M'=>889,'N'=>722,'O'=>722,'P'=>768,'Q'=>741,'R'=>556,'S'=>592,'T'=>611,'U'=>690,'V'=>439,'W'=>768,
'X'=>645,'Y'=>795,'Z'=>611,'['=>333,'\\'=>863,']'=>333,'^'=>658,'_'=>500,'`'=>500,'a'=>631,'b'=>549,'c'=>549,'d'=>494,'e'=>439,'f'=>521,'g'=>411,'h'=>603,'i'=>329,'j'=>603,'k'=>549,'l'=>549,'m'=>576,
'n'=>521,'o'=>549,'p'=>549,'q'=>521,'r'=>549,'s'=>603,'t'=>439,'u'=>576,'v'=>713,'w'=>686,'x'=>493,'y'=>686,'z'=>494,'{'=>480,'|'=>200,'}'=>480,'~'=>549,chr(127)=>0,chr(128)=>0,chr(129)=>0,chr(130)=>0,chr(131)=>0,
chr(132)=>0,chr(133)=>0,chr(134)=>0,chr(135)=>0,chr(136)=>0,chr(137)=>0,chr(138)=>0,chr(139)=>0,chr(140)=>0,chr(141)=>0,chr(142)=>0,chr(143)=>0,chr(144)=>0,chr(145)=>0,chr(146)=>0,chr(147)=>0,chr(148)=>0,chr(149)=>0,chr(150)=>0,chr(151)=>0,chr(152)=>0,chr(153)=>0,
chr(154)=>0,chr(155)=>0,chr(156)=>0,chr(157)=>0,chr(158)=>0,chr(159)=>0,chr(160)=>750,chr(161)=>620,chr(162)=>247,chr(163)=>549,chr(164)=>167,chr(165)=>713,chr(166)=>500,chr(167)=>753,chr(168)=>753,chr(169)=>753,chr(170)=>753,chr(171)=>1042,chr(172)=>987,chr(173)=>603,chr(174)=>987,chr(175)=>603,
chr(176)=>400,chr(177)=>549,chr(178)=>411,chr(179)=>549,chr(180)=>549,chr(181)=>713,chr(182)=>494,chr(183)=>460,chr(184)=>549,chr(185)=>549,chr(186)=>549,chr(187)=>549,chr(188)=>1000,chr(189)=>603,chr(190)=>1000,chr(191)=>658,chr(192)=>823,chr(193)=>686,chr(194)=>795,chr(195)=>987,chr(196)=>768,chr(197)=>768,
chr(198)=>823,chr(199)=>768,chr(200)=>768,chr(201)=>713,chr(202)=>713,chr(203)=>713,chr(204)=>713,chr(205)=>713,chr(206)=>713,chr(207)=>713,chr(208)=>768,chr(209)=>713,chr(210)=>790,chr(211)=>790,chr(212)=>890,chr(213)=>823,chr(214)=>549,chr(215)=>250,chr(216)=>713,chr(217)=>603,chr(218)=>603,chr(219)=>1042,
chr(220)=>987,chr(221)=>603,chr(222)=>987,chr(223)=>603,chr(224)=>494,chr(225)=>329,chr(226)=>790,chr(227)=>790,chr(228)=>786,chr(229)=>713,chr(230)=>384,chr(231)=>384,chr(232)=>384,chr(233)=>384,chr(234)=>384,chr(235)=>384,chr(236)=>494,chr(237)=>494,chr(238)=>494,chr(239)=>494,chr(240)=>0,chr(241)=>329,
chr(242)=>274,chr(243)=>686,chr(244)=>686,chr(245)=>686,chr(246)=>384,chr(247)=>384,chr(248)=>384,chr(249)=>384,chr(250)=>384,chr(251)=>384,chr(252)=>494,chr(253)=>494,chr(254)=>494,chr(255)=>0);
$uv = array(32=>160,33=>33,34=>8704,35=>35,36=>8707,37=>array(37,2),39=>8715,40=>array(40,2),42=>8727,43=>array(43,2),45=>8722,46=>array(46,18),64=>8773,65=>array(913,2),67=>935,68=>array(916,2),70=>934,71=>915,72=>919,73=>921,74=>977,75=>array(922,4),79=>array(927,2),81=>920,82=>929,83=>array(931,3),86=>962,87=>937,88=>926,89=>936,90=>918,91=>91,92=>8756,93=>93,94=>8869,95=>95,96=>63717,97=>array(945,2),99=>967,100=>array(948,2),102=>966,103=>947,104=>951,105=>953,106=>981,107=>array(954,4),111=>array(959,2),113=>952,114=>961,115=>array(963,3),118=>982,119=>969,120=>958,121=>968,122=>950,123=>array(123,3),126=>8764,160=>8364,161=>978,162=>8242,163=>8804,164=>8725,165=>8734,166=>402,167=>9827,168=>9830,169=>9829,170=>9824,171=>8596,172=>array(8592,4),176=>array(176,2),178=>8243,179=>8805,180=>215,181=>8733,182=>8706,183=>8226,184=>247,185=>array(8800,2),187=>8776,188=>8230,189=>array(63718,2),191=>8629,192=>8501,193=>8465,194=>8476,195=>8472,196=>8855,197=>8853,198=>8709,199=>array(8745,2),201=>8835,202=>8839,203=>8836,204=>8834,205=>8838,206=>array(8712,2),208=>8736,209=>8711,210=>63194,211=>63193,212=>63195,213=>8719,214=>8730,215=>8901,216=>172,217=>array(8743,2),219=>8660,220=>array(8656,4),224=>9674,225=>9001,226=>array(63720,3),229=>8721,230=>array(63723,10),241=>9002,242=>8747,243=>8992,244=>63733,245=>8993,246=>array(63734,9));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Times-Roman';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250,
chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>408,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>180,'('=>333,')'=>333,'*'=>500,'+'=>564,
','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>278,';'=>278,'<'=>564,'='=>564,'>'=>564,'?'=>444,'@'=>921,'A'=>722,
'B'=>667,'C'=>667,'D'=>722,'E'=>611,'F'=>556,'G'=>722,'H'=>722,'I'=>333,'J'=>389,'K'=>722,'L'=>611,'M'=>889,'N'=>722,'O'=>722,'P'=>556,'Q'=>722,'R'=>667,'S'=>556,'T'=>611,'U'=>722,'V'=>722,'W'=>944,
'X'=>722,'Y'=>722,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>469,'_'=>500,'`'=>333,'a'=>444,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>333,'g'=>500,'h'=>500,'i'=>278,'j'=>278,'k'=>500,'l'=>278,'m'=>778,
'n'=>500,'o'=>500,'p'=>500,'q'=>500,'r'=>333,'s'=>389,'t'=>278,'u'=>500,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>444,'{'=>480,'|'=>200,'}'=>480,'~'=>541,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500,
chr(132)=>444,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>889,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>444,chr(148)=>444,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>980,
chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>444,chr(159)=>722,chr(160)=>250,chr(161)=>333,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>200,chr(167)=>500,chr(168)=>333,chr(169)=>760,chr(170)=>276,chr(171)=>500,chr(172)=>564,chr(173)=>333,chr(174)=>760,chr(175)=>333,
chr(176)=>400,chr(177)=>564,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>500,chr(182)=>453,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>310,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>444,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722,
chr(198)=>889,chr(199)=>667,chr(200)=>611,chr(201)=>611,chr(202)=>611,chr(203)=>611,chr(204)=>333,chr(205)=>333,chr(206)=>333,chr(207)=>333,chr(208)=>722,chr(209)=>722,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>564,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>722,chr(222)=>556,chr(223)=>500,chr(224)=>444,chr(225)=>444,chr(226)=>444,chr(227)=>444,chr(228)=>444,chr(229)=>444,chr(230)=>667,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>500,
chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>564,chr(248)=>500,chr(249)=>500,chr(250)=>500,chr(251)=>500,chr(252)=>500,chr(253)=>500,chr(254)=>500,chr(255)=>500);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Times-Bold';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250,
chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>555,'#'=>500,'$'=>500,'%'=>1000,'&'=>833,'\''=>278,'('=>333,')'=>333,'*'=>500,'+'=>570,
','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>570,'='=>570,'>'=>570,'?'=>500,'@'=>930,'A'=>722,
'B'=>667,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>778,'I'=>389,'J'=>500,'K'=>778,'L'=>667,'M'=>944,'N'=>722,'O'=>778,'P'=>611,'Q'=>778,'R'=>722,'S'=>556,'T'=>667,'U'=>722,'V'=>722,'W'=>1000,
'X'=>722,'Y'=>722,'Z'=>667,'['=>333,'\\'=>278,']'=>333,'^'=>581,'_'=>500,'`'=>333,'a'=>500,'b'=>556,'c'=>444,'d'=>556,'e'=>444,'f'=>333,'g'=>500,'h'=>556,'i'=>278,'j'=>333,'k'=>556,'l'=>278,'m'=>833,
'n'=>556,'o'=>500,'p'=>556,'q'=>556,'r'=>444,'s'=>389,'t'=>333,'u'=>556,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>444,'{'=>394,'|'=>220,'}'=>394,'~'=>520,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500,
chr(132)=>500,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>667,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>1000,
chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>444,chr(159)=>722,chr(160)=>250,chr(161)=>333,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>220,chr(167)=>500,chr(168)=>333,chr(169)=>747,chr(170)=>300,chr(171)=>500,chr(172)=>570,chr(173)=>333,chr(174)=>747,chr(175)=>333,
chr(176)=>400,chr(177)=>570,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>556,chr(182)=>540,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>330,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722,
chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>389,chr(205)=>389,chr(206)=>389,chr(207)=>389,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>570,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>722,chr(222)=>611,chr(223)=>556,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>722,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>556,
chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>570,chr(248)=>500,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Times-BoldItalic';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250,
chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>389,'"'=>555,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>278,'('=>333,')'=>333,'*'=>500,'+'=>570,
','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>570,'='=>570,'>'=>570,'?'=>500,'@'=>832,'A'=>667,
'B'=>667,'C'=>667,'D'=>722,'E'=>667,'F'=>667,'G'=>722,'H'=>778,'I'=>389,'J'=>500,'K'=>667,'L'=>611,'M'=>889,'N'=>722,'O'=>722,'P'=>611,'Q'=>722,'R'=>667,'S'=>556,'T'=>611,'U'=>722,'V'=>667,'W'=>889,
'X'=>667,'Y'=>611,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>570,'_'=>500,'`'=>333,'a'=>500,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>333,'g'=>500,'h'=>556,'i'=>278,'j'=>278,'k'=>500,'l'=>278,'m'=>778,
'n'=>556,'o'=>500,'p'=>500,'q'=>500,'r'=>389,'s'=>389,'t'=>278,'u'=>556,'v'=>444,'w'=>667,'x'=>500,'y'=>444,'z'=>389,'{'=>348,'|'=>220,'}'=>348,'~'=>570,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500,
chr(132)=>500,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>944,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>1000,
chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>389,chr(159)=>611,chr(160)=>250,chr(161)=>389,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>220,chr(167)=>500,chr(168)=>333,chr(169)=>747,chr(170)=>266,chr(171)=>500,chr(172)=>606,chr(173)=>333,chr(174)=>747,chr(175)=>333,
chr(176)=>400,chr(177)=>570,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>576,chr(182)=>500,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>300,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667,
chr(198)=>944,chr(199)=>667,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>389,chr(205)=>389,chr(206)=>389,chr(207)=>389,chr(208)=>722,chr(209)=>722,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>570,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>611,chr(222)=>611,chr(223)=>500,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>722,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>556,
chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>570,chr(248)=>500,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>444,chr(254)=>500,chr(255)=>444);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
$type = 'Core';
$name = 'Times-Italic';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250,
chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>420,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>214,'('=>333,')'=>333,'*'=>500,'+'=>675,
','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>675,'='=>675,'>'=>675,'?'=>500,'@'=>920,'A'=>611,
'B'=>611,'C'=>667,'D'=>722,'E'=>611,'F'=>611,'G'=>722,'H'=>722,'I'=>333,'J'=>444,'K'=>667,'L'=>556,'M'=>833,'N'=>667,'O'=>722,'P'=>611,'Q'=>722,'R'=>611,'S'=>500,'T'=>556,'U'=>722,'V'=>611,'W'=>833,
'X'=>611,'Y'=>556,'Z'=>556,'['=>389,'\\'=>278,']'=>389,'^'=>422,'_'=>500,'`'=>333,'a'=>500,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>278,'g'=>500,'h'=>500,'i'=>278,'j'=>278,'k'=>444,'l'=>278,'m'=>722,
'n'=>500,'o'=>500,'p'=>500,'q'=>500,'r'=>389,'s'=>389,'t'=>278,'u'=>500,'v'=>444,'w'=>667,'x'=>444,'y'=>444,'z'=>389,'{'=>400,'|'=>275,'}'=>400,'~'=>541,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500,
chr(132)=>556,chr(133)=>889,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>500,chr(139)=>333,chr(140)=>944,chr(141)=>350,chr(142)=>556,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>556,chr(148)=>556,chr(149)=>350,chr(150)=>500,chr(151)=>889,chr(152)=>333,chr(153)=>980,
chr(154)=>389,chr(155)=>333,chr(156)=>667,chr(157)=>350,chr(158)=>389,chr(159)=>556,chr(160)=>250,chr(161)=>389,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>275,chr(167)=>500,chr(168)=>333,chr(169)=>760,chr(170)=>276,chr(171)=>500,chr(172)=>675,chr(173)=>333,chr(174)=>760,chr(175)=>333,
chr(176)=>400,chr(177)=>675,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>500,chr(182)=>523,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>310,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>611,chr(193)=>611,chr(194)=>611,chr(195)=>611,chr(196)=>611,chr(197)=>611,
chr(198)=>889,chr(199)=>667,chr(200)=>611,chr(201)=>611,chr(202)=>611,chr(203)=>611,chr(204)=>333,chr(205)=>333,chr(206)=>333,chr(207)=>333,chr(208)=>722,chr(209)=>667,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>675,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722,
chr(220)=>722,chr(221)=>556,chr(222)=>611,chr(223)=>500,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>667,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>500,
chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>675,chr(248)=>500,chr(249)=>500,chr(250)=>500,chr(251)=>500,chr(252)=>500,chr(253)=>444,chr(254)=>500,chr(255)=>444);
$enc = 'cp1252';
$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
$type = 'Core';
$name = 'ZapfDingbats';
$up = -100;
$ut = 50;
$cw = array(
chr(0)=>0,chr(1)=>0,chr(2)=>0,chr(3)=>0,chr(4)=>0,chr(5)=>0,chr(6)=>0,chr(7)=>0,chr(8)=>0,chr(9)=>0,chr(10)=>0,chr(11)=>0,chr(12)=>0,chr(13)=>0,chr(14)=>0,chr(15)=>0,chr(16)=>0,chr(17)=>0,chr(18)=>0,chr(19)=>0,chr(20)=>0,chr(21)=>0,
chr(22)=>0,chr(23)=>0,chr(24)=>0,chr(25)=>0,chr(26)=>0,chr(27)=>0,chr(28)=>0,chr(29)=>0,chr(30)=>0,chr(31)=>0,' '=>278,'!'=>974,'"'=>961,'#'=>974,'$'=>980,'%'=>719,'&'=>789,'\''=>790,'('=>791,')'=>690,'*'=>960,'+'=>939,
','=>549,'-'=>855,'.'=>911,'/'=>933,'0'=>911,'1'=>945,'2'=>974,'3'=>755,'4'=>846,'5'=>762,'6'=>761,'7'=>571,'8'=>677,'9'=>763,':'=>760,';'=>759,'<'=>754,'='=>494,'>'=>552,'?'=>537,'@'=>577,'A'=>692,
'B'=>786,'C'=>788,'D'=>788,'E'=>790,'F'=>793,'G'=>794,'H'=>816,'I'=>823,'J'=>789,'K'=>841,'L'=>823,'M'=>833,'N'=>816,'O'=>831,'P'=>923,'Q'=>744,'R'=>723,'S'=>749,'T'=>790,'U'=>792,'V'=>695,'W'=>776,
'X'=>768,'Y'=>792,'Z'=>759,'['=>707,'\\'=>708,']'=>682,'^'=>701,'_'=>826,'`'=>815,'a'=>789,'b'=>789,'c'=>707,'d'=>687,'e'=>696,'f'=>689,'g'=>786,'h'=>787,'i'=>713,'j'=>791,'k'=>785,'l'=>791,'m'=>873,
'n'=>761,'o'=>762,'p'=>762,'q'=>759,'r'=>759,'s'=>892,'t'=>892,'u'=>788,'v'=>784,'w'=>438,'x'=>138,'y'=>277,'z'=>415,'{'=>392,'|'=>392,'}'=>668,'~'=>668,chr(127)=>0,chr(128)=>390,chr(129)=>390,chr(130)=>317,chr(131)=>317,
chr(132)=>276,chr(133)=>276,chr(134)=>509,chr(135)=>509,chr(136)=>410,chr(137)=>410,chr(138)=>234,chr(139)=>234,chr(140)=>334,chr(141)=>334,chr(142)=>0,chr(143)=>0,chr(144)=>0,chr(145)=>0,chr(146)=>0,chr(147)=>0,chr(148)=>0,chr(149)=>0,chr(150)=>0,chr(151)=>0,chr(152)=>0,chr(153)=>0,
chr(154)=>0,chr(155)=>0,chr(156)=>0,chr(157)=>0,chr(158)=>0,chr(159)=>0,chr(160)=>0,chr(161)=>732,chr(162)=>544,chr(163)=>544,chr(164)=>910,chr(165)=>667,chr(166)=>760,chr(167)=>760,chr(168)=>776,chr(169)=>595,chr(170)=>694,chr(171)=>626,chr(172)=>788,chr(173)=>788,chr(174)=>788,chr(175)=>788,
chr(176)=>788,chr(177)=>788,chr(178)=>788,chr(179)=>788,chr(180)=>788,chr(181)=>788,chr(182)=>788,chr(183)=>788,chr(184)=>788,chr(185)=>788,chr(186)=>788,chr(187)=>788,chr(188)=>788,chr(189)=>788,chr(190)=>788,chr(191)=>788,chr(192)=>788,chr(193)=>788,chr(194)=>788,chr(195)=>788,chr(196)=>788,chr(197)=>788,
chr(198)=>788,chr(199)=>788,chr(200)=>788,chr(201)=>788,chr(202)=>788,chr(203)=>788,chr(204)=>788,chr(205)=>788,chr(206)=>788,chr(207)=>788,chr(208)=>788,chr(209)=>788,chr(210)=>788,chr(211)=>788,chr(212)=>894,chr(213)=>838,chr(214)=>1016,chr(215)=>458,chr(216)=>748,chr(217)=>924,chr(218)=>748,chr(219)=>918,
chr(220)=>927,chr(221)=>928,chr(222)=>928,chr(223)=>834,chr(224)=>873,chr(225)=>828,chr(226)=>924,chr(227)=>924,chr(228)=>917,chr(229)=>930,chr(230)=>931,chr(231)=>463,chr(232)=>883,chr(233)=>836,chr(234)=>836,chr(235)=>867,chr(236)=>867,chr(237)=>696,chr(238)=>696,chr(239)=>874,chr(240)=>0,chr(241)=>874,
chr(242)=>760,chr(243)=>946,chr(244)=>771,chr(245)=>865,chr(246)=>771,chr(247)=>888,chr(248)=>967,chr(249)=>888,chr(250)=>831,chr(251)=>873,chr(252)=>927,chr(253)=>970,chr(254)=>918,chr(255)=>0);
$uv = array(32=>32,33=>array(9985,4),37=>9742,38=>array(9990,4),42=>9755,43=>9758,44=>array(9996,28),72=>9733,73=>array(10025,35),108=>9679,109=>10061,110=>9632,111=>array(10063,4),115=>9650,116=>9660,117=>9670,118=>10070,119=>9687,120=>array(10072,7),128=>array(10088,14),161=>array(10081,7),168=>9827,169=>9830,170=>9829,171=>9824,172=>array(9312,10),182=>array(10102,31),213=>8594,214=>array(8596,2),216=>array(10136,24),241=>array(10161,14));
$desc = '';
$diff = '';
$file = '';
$size1 = 0;
$size2 = 0;
$originalsize = 0;
?>
+1551
View File
File diff suppressed because it is too large Load Diff
+165
View File
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
+59
View File
@@ -0,0 +1,59 @@
# PDF parser
[![Version](https://poser.pugx.org/smalot/pdfparser/v)](//packagist.org/packages/smalot/pdfparser)
![CI](https://github.com/smalot/pdfparser/workflows/CI/badge.svg)
![CS](https://github.com/smalot/pdfparser/workflows/CS/badge.svg)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/smalot/pdfparser/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/smalot/pdfparser/?branch=master)
[![Downloads](https://poser.pugx.org/smalot/pdfparser/downloads)](//packagist.org/packages/smalot/pdfparser)
The `smalot/pdfparser` is a standalone PHP package that provides various tools to extract data from PDF files.
This library is under **active maintenance**.
There is no active development by the author of this library (at the moment), but we welcome any pull request adding/extending functionality!
See [CONTRIBUTING.md](./CONTRIBUTING.md) for further information about how to contribute.
## Features
- Load/parse objects and headers
- Extract metadata (author, description, ...)
- Extract text from ordered pages
- Support of compressed PDFs
- Support of MAC OS Roman charset encoding
- Handling of hexa and octal encoding in text sections
- Create custom configurations (see [CustomConfig.md](/doc/CustomConfig.md)).
Currently, secured documents and extracting form data are not supported.
## License
This library is under the [LGPLv3 license](https://github.com/smalot/pdfparser/blob/master/LICENSE.txt).
## Install
This library requires PHP 7.1+ since [v1](https://github.com/smalot/pdfparser/releases/tag/v1.0.0).
You can install it via [Composer](https://getcomposer.org/):
```bash
composer require smalot/pdfparser
```
In case you can't use Composer, you can include `alt_autoload.php-dist`. It will include all required files automatically.
## Quick example
```php
<?php
// Parse PDF file and build necessary objects.
$parser = new \Smalot\PdfParser\Parser();
$pdf = $parser->parseFile('/path/to/document.pdf');
$text = $pdf->getText();
echo $text;
```
Further usage information can be found [here](/doc/Usage.md).
## Documentation
Documentation can be found in the [doc](/doc) folder.
+37
View File
@@ -0,0 +1,37 @@
{
"name": "smalot/pdfparser",
"description": "Pdf parser library. Can read and extract information from pdf file.",
"keywords": ["PDF", "text", "parser", "parse", "extract"],
"type": "library",
"license": "LGPL-3.0",
"authors": [
{
"name": "Sebastien MALOT",
"email": "sebastien@malot.fr"
}
],
"support": {
"issues": "https://github.com/smalot/pdfparser/issues"
},
"homepage": "https://www.pdfparser.org",
"require": {
"php": ">=7.1",
"symfony/polyfill-mbstring": "^1.18",
"ext-zlib": "*",
"ext-iconv": "*"
},
"autoload": {
"psr-0": {
"Smalot\\PdfParser\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"PerformanceTests\\": "tests/Performance/",
"PHPUnitTests\\": "tests/PHPUnit/"
}
},
"config": {
"process-timeout": 1200
}
}
+175
View File
@@ -0,0 +1,175 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Konrad Abicht <hi@inspirito.de>
*
* @date 2020-11-22
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser;
/**
* This class contains configurations used in various classes. You can override them
* manually, in case default values aren't working.
*
* @see https://github.com/smalot/pdfparser/issues/305
*/
class Config
{
private $fontSpaceLimit = -50;
/**
* @var string
*/
private $horizontalOffset = ' ';
/**
* Represents: (NUL, HT, LF, FF, CR, SP)
*
* @var string
*/
private $pdfWhitespaces = "\0\t\n\f\r ";
/**
* Represents: (NUL, HT, LF, FF, CR, SP)
*
* @var string
*/
private $pdfWhitespacesRegex = '[\0\t\n\f\r ]';
/**
* Whether to retain raw image data as content or discard it to save memory
*
* @var bool
*/
private $retainImageContent = true;
/**
* Memory limit to use when de-compressing files, in bytes.
*
* @var int
*/
private $decodeMemoryLimit = 0;
/**
* Whether to include font id and size in dataTm array
*
* @var bool
*/
private $dataTmFontInfoHasToBeIncluded = false;
/**
* Whether to attempt to read PDFs even if they are marked as encrypted.
*
* @var bool
*/
private $ignoreEncryption = false;
public function getFontSpaceLimit()
{
return $this->fontSpaceLimit;
}
public function setFontSpaceLimit($value)
{
$this->fontSpaceLimit = $value;
}
public function getHorizontalOffset(): string
{
return $this->horizontalOffset;
}
public function setHorizontalOffset($value): void
{
$this->horizontalOffset = $value;
}
public function getPdfWhitespaces(): string
{
return $this->pdfWhitespaces;
}
public function setPdfWhitespaces(string $pdfWhitespaces): void
{
$this->pdfWhitespaces = $pdfWhitespaces;
}
public function getPdfWhitespacesRegex(): string
{
return $this->pdfWhitespacesRegex;
}
public function setPdfWhitespacesRegex(string $pdfWhitespacesRegex): void
{
$this->pdfWhitespacesRegex = $pdfWhitespacesRegex;
}
public function getRetainImageContent(): bool
{
return $this->retainImageContent;
}
public function setRetainImageContent(bool $retainImageContent): void
{
$this->retainImageContent = $retainImageContent;
}
public function getDecodeMemoryLimit(): int
{
return $this->decodeMemoryLimit;
}
public function setDecodeMemoryLimit(int $decodeMemoryLimit): void
{
$this->decodeMemoryLimit = $decodeMemoryLimit;
}
public function getDataTmFontInfoHasToBeIncluded(): bool
{
return $this->dataTmFontInfoHasToBeIncluded;
}
public function setDataTmFontInfoHasToBeIncluded(bool $dataTmFontInfoHasToBeIncluded): void
{
$this->dataTmFontInfoHasToBeIncluded = $dataTmFontInfoHasToBeIncluded;
}
public function getIgnoreEncryption(): bool
{
return $this->ignoreEncryption;
}
/**
* @deprecated this is a temporary workaround, don't rely on it
* @see https://github.com/smalot/pdfparser/pull/653
*/
public function setIgnoreEncryption(bool $ignoreEncryption): void
{
$this->ignoreEncryption = $ignoreEncryption;
}
}
@@ -0,0 +1,470 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser;
use Smalot\PdfParser\Encoding\PDFDocEncoding;
use Smalot\PdfParser\Exception\MissingCatalogException;
/**
* Technical references :
* - http://www.mactech.com/articles/mactech/Vol.15/15.09/PDFIntro/index.html
* - http://framework.zend.com/issues/secure/attachment/12512/Pdf.php
* - http://www.php.net/manual/en/ref.pdf.php#74211
* - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin1Encoding.pm
* - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin9Encoding.pm
* - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/StandardEncoding.pm
* - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/WinAnsiEncoding.pm
*
* Class Document
*/
class Document
{
/**
* @var PDFObject[]
*/
protected $objects = [];
/**
* @var array
*/
protected $dictionary = [];
/**
* @var Header
*/
protected $trailer;
/**
* @var array<mixed>
*/
protected $metadata = [];
/**
* @var array
*/
protected $details;
public function __construct()
{
$this->trailer = new Header([], $this);
}
public function init()
{
$this->buildDictionary();
$this->buildDetails();
// Propagate init to objects.
foreach ($this->objects as $object) {
$object->getHeader()->init();
$object->init();
}
}
/**
* Build dictionary based on type header field.
*/
protected function buildDictionary()
{
// Build dictionary.
$this->dictionary = [];
foreach ($this->objects as $id => $object) {
// Cache objects by type and subtype
$type = $object->getHeader()->get('Type')->getContent();
if (null != $type) {
if (!isset($this->dictionary[$type])) {
$this->dictionary[$type] = [
'all' => [],
'subtype' => [],
];
}
$this->dictionary[$type]['all'][$id] = $object;
$subtype = $object->getHeader()->get('Subtype')->getContent();
if (null != $subtype) {
if (!isset($this->dictionary[$type]['subtype'][$subtype])) {
$this->dictionary[$type]['subtype'][$subtype] = [];
}
$this->dictionary[$type]['subtype'][$subtype][$id] = $object;
}
}
}
}
/**
* Build details array.
*/
protected function buildDetails()
{
// Build details array.
$details = [];
// Extract document info
if ($this->trailer->has('Info')) {
/** @var PDFObject $info */
$info = $this->trailer->get('Info');
// This could be an ElementMissing object, so we need to check for
// the getHeader method first.
if (null !== $info && method_exists($info, 'getHeader')) {
$details = $info->getHeader()->getDetails();
}
}
// Retrieve the page count
try {
$pages = $this->getPages();
$details['Pages'] = \count($pages);
} catch (\Exception $e) {
$details['Pages'] = 0;
}
// Decode and repair encoded document properties
foreach ($details as $key => $value) {
if (\is_string($value)) {
// If the string is already UTF-8 encoded, that means we only
// need to repair Adobe's ham-fisted insertion of line-feeds
// every ~127 characters, which doesn't seem to be multi-byte
// safe
if (mb_check_encoding($value, 'UTF-8')) {
// Remove literal backslash + line-feed "\\r"
$value = str_replace("\x5c\x0d", '', $value);
// Remove backslash plus bytes written into high part of
// multibyte unicode character
while (preg_match("/\x5c\x5c\xe0([\xb4-\xb8])(.)/", $value, $match)) {
$diff = (\ord($match[1]) - 182) * 64;
$newbyte = PDFDocEncoding::convertPDFDoc2UTF8(\chr(\ord($match[2]) + $diff));
$value = preg_replace("/\x5c\x5c\xe0".$match[1].$match[2].'/', $newbyte, $value);
}
// Remove bytes written into low part of multibyte unicode
// character
while (preg_match("/(.)\x9c\xe0([\xb3-\xb7])/", $value, $match)) {
$diff = \ord($match[2]) - 181;
$newbyte = \chr(\ord($match[1]) + $diff);
$value = preg_replace('/'.$match[1]."\x9c\xe0".$match[2].'/', $newbyte, $value);
}
// Remove this byte string that Adobe occasionally adds
// between two single byte characters in a unicode string
$value = str_replace("\xe5\xb0\x8d", '', $value);
$details[$key] = $value;
} else {
// If the string is just PDFDocEncoding, remove any line-feeds
// and decode the whole thing.
$value = str_replace("\\\r", '', $value);
$details[$key] = PDFDocEncoding::convertPDFDoc2UTF8($value);
}
}
}
$details = array_merge($details, $this->metadata);
$this->details = $details;
}
/**
* Extract XMP Metadata
*/
public function extractXMPMetadata(string $content): void
{
$xml = xml_parser_create();
xml_parser_set_option($xml, \XML_OPTION_SKIP_WHITE, 1);
if (1 === xml_parse_into_struct($xml, $content, $values, $index)) {
/*
* short overview about the following code parts:
*
* The output of xml_parse_into_struct is a single dimensional array (= $values), and the $stack is a last-on,
* first-off array of pointers to positions in $metadata, while iterating through it, that potentially turn the
* results into a more intuitive multi-dimensional array. When an "open" XML tag is encountered,
* we save the current $metadata context in the $stack, then create a child array of $metadata and
* make that the current $metadata context. When a "close" XML tag is encountered, the operations are
* reversed: the most recently added $metadata context from $stack (IOW, the parent of the current
* element) is set as the current $metadata context.
*/
$metadata = [];
$stack = [];
foreach ($values as $val) {
// Standardize to lowercase
$val['tag'] = strtolower($val['tag']);
// Ignore structural x: and rdf: XML elements
if (0 === strpos($val['tag'], 'x:')) {
continue;
} elseif (0 === strpos($val['tag'], 'rdf:') && 'rdf:li' != $val['tag']) {
continue;
}
switch ($val['type']) {
case 'open':
// Create an array of list items
if ('rdf:li' == $val['tag']) {
$metadata[] = [];
// Move up one level in the stack
$stack[\count($stack)] = &$metadata;
$metadata = &$metadata[\count($metadata) - 1];
} else {
// Else create an array of named values
$metadata[$val['tag']] = [];
// Move up one level in the stack
$stack[\count($stack)] = &$metadata;
$metadata = &$metadata[$val['tag']];
}
break;
case 'complete':
if (isset($val['value'])) {
// Assign a value to this list item
if ('rdf:li' == $val['tag']) {
$metadata[] = $val['value'];
// Else assign a value to this property
} else {
$metadata[$val['tag']] = $val['value'];
}
}
break;
case 'close':
// If the value of this property is an array
if (\is_array($metadata)) {
// If the value is a single element array
// where the element is of type string, use
// the value of the first list item as the
// value for this property
if (1 == \count($metadata) && isset($metadata[0]) && \is_string($metadata[0])) {
$metadata = $metadata[0];
} elseif (0 == \count($metadata)) {
// if the value is an empty array, set
// the value of this property to the empty
// string
$metadata = '';
}
}
// Move down one level in the stack
$metadata = &$stack[\count($stack) - 1];
unset($stack[\count($stack) - 1]);
break;
}
}
// Only use this metadata if it's referring to a PDF
if (!isset($metadata['dc:format']) || 'application/pdf' == $metadata['dc:format']) {
// According to the XMP specifications: 'Conflict resolution
// for separate packets that describe the same resource is
// beyond the scope of this document.' - Section 6.1
// Source: https://www.adobe.com/devnet/xmp.html
// Source: https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart1.pdf
// So if there are multiple XMP blocks, just merge the values
// of each found block over top of the existing values
$this->metadata = array_merge($this->metadata, $metadata);
}
}
// TODO: remove this if-clause and its content when dropping PHP 7 support
if (version_compare(PHP_VERSION, '8.0.0', '<')) {
// ref: https://www.php.net/manual/en/function.xml-parser-free.php
xml_parser_free($xml);
// to avoid memory leaks; documentation said:
// > it was necessary to also explicitly unset the reference to parser to avoid memory leaks
unset($xml);
}
}
public function getDictionary(): array
{
return $this->dictionary;
}
/**
* @param PDFObject[] $objects
*/
public function setObjects($objects = [])
{
$this->objects = (array) $objects;
$this->init();
}
/**
* @return PDFObject[]
*/
public function getObjects()
{
return $this->objects;
}
/**
* @return PDFObject|Font|Page|Element|null
*/
public function getObjectById(string $id)
{
if (isset($this->objects[$id])) {
return $this->objects[$id];
}
return null;
}
public function hasObjectsByType(string $type, ?string $subtype = null): bool
{
return 0 < \count($this->getObjectsByType($type, $subtype));
}
public function getObjectsByType(string $type, ?string $subtype = null): array
{
if (!isset($this->dictionary[$type])) {
return [];
}
if (null != $subtype) {
if (!isset($this->dictionary[$type]['subtype'][$subtype])) {
return [];
}
return $this->dictionary[$type]['subtype'][$subtype];
}
return $this->dictionary[$type]['all'];
}
/**
* @return Font[]
*/
public function getFonts()
{
return $this->getObjectsByType('Font');
}
public function getFirstFont(): ?Font
{
$fonts = $this->getFonts();
if ([] === $fonts) {
return null;
}
return reset($fonts);
}
/**
* @return Page[]
*
* @throws MissingCatalogException
*/
public function getPages()
{
if ($this->hasObjectsByType('Catalog')) {
// Search for catalog to list pages.
$catalogues = $this->getObjectsByType('Catalog');
$catalogue = reset($catalogues);
/** @var Pages $object */
$object = $catalogue->get('Pages');
if (method_exists($object, 'getPages')) {
return $object->getPages(true);
}
}
if ($this->hasObjectsByType('Pages')) {
// Search for pages to list kids.
$pages = [];
/** @var Pages[] $objects */
$objects = $this->getObjectsByType('Pages');
foreach ($objects as $object) {
$pages = array_merge($pages, $object->getPages(true));
}
return $pages;
}
if ($this->hasObjectsByType('Page')) {
// Search for 'page' (unordered pages).
$pages = $this->getObjectsByType('Page');
return array_values($pages);
}
throw new MissingCatalogException('Missing catalog.');
}
public function getText(?int $pageLimit = null): string
{
$texts = [];
$pages = $this->getPages();
// Only use the first X number of pages if $pageLimit is set and numeric.
if (\is_int($pageLimit) && 0 < $pageLimit) {
$pages = \array_slice($pages, 0, $pageLimit);
}
foreach ($pages as $index => $page) {
/**
* In some cases, the $page variable may be null.
*/
if (null === $page) {
continue;
}
if ($text = trim($page->getText())) {
$texts[] = $text;
}
}
return implode("\n\n", $texts);
}
public function getTrailer(): Header
{
return $this->trailer;
}
public function setTrailer(Header $trailer)
{
$this->trailer = $trailer;
}
public function getDetails(): array
{
return $this->details;
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser;
use Smalot\PdfParser\Element\ElementArray;
use Smalot\PdfParser\Element\ElementBoolean;
use Smalot\PdfParser\Element\ElementDate;
use Smalot\PdfParser\Element\ElementHexa;
use Smalot\PdfParser\Element\ElementName;
use Smalot\PdfParser\Element\ElementNull;
use Smalot\PdfParser\Element\ElementNumeric;
use Smalot\PdfParser\Element\ElementString;
use Smalot\PdfParser\Element\ElementStruct;
use Smalot\PdfParser\Element\ElementXRef;
/**
* Class Element
*/
class Element
{
/**
* @var Document|null
*/
protected $document;
protected $value;
public function __construct($value, ?Document $document = null)
{
$this->value = $value;
$this->document = $document;
}
public function init()
{
}
public function equals($value): bool
{
return $value == $this->value;
}
public function contains($value): bool
{
if (\is_array($this->value)) {
/** @var Element $val */
foreach ($this->value as $val) {
if ($val->equals($value)) {
return true;
}
}
return false;
}
return $this->equals($value);
}
public function getContent()
{
return $this->value;
}
public function __toString(): string
{
return (string) $this->value;
}
public static function parse(string $content, ?Document $document = null, int &$position = 0)
{
$args = \func_get_args();
$only_values = isset($args[3]) ? $args[3] : false;
$content = trim($content);
$values = [];
do {
$old_position = $position;
if (!$only_values) {
if (!preg_match('/\G\s*(?P<name>\/[A-Z#0-9\._]+)(?P<value>.*)/si', $content, $match, 0, $position)) {
break;
} else {
$name = preg_replace_callback(
'/#([0-9a-f]{2})/i',
function ($m) {
return \chr(base_convert($m[1], 16, 10));
},
ltrim($match['name'], '/')
);
$value = $match['value'];
$position = strpos($content, $value, $position + \strlen($match['name']));
}
} else {
$name = \count($values);
$value = substr($content, $position);
}
if ($element = ElementName::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementXRef::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementNumeric::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementStruct::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementBoolean::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementNull::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementDate::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementString::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementHexa::parse($value, $document, $position)) {
$values[$name] = $element;
} elseif ($element = ElementArray::parse($value, $document, $position)) {
$values[$name] = $element;
} else {
$position = $old_position;
break;
}
} while ($position < \strlen($content));
return $values;
}
}
@@ -0,0 +1,139 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
use Smalot\PdfParser\Header;
use Smalot\PdfParser\PDFObject;
/**
* Class ElementArray
*/
class ElementArray extends Element
{
public function __construct($value, ?Document $document = null)
{
parent::__construct($value, $document);
}
public function getContent()
{
foreach ($this->value as $name => $element) {
$this->resolveXRef($name);
}
return parent::getContent();
}
public function getRawContent(): array
{
return $this->value;
}
public function getDetails(bool $deep = true): array
{
$values = [];
$elements = $this->getContent();
foreach ($elements as $key => $element) {
if ($element instanceof Header && $deep) {
$values[$key] = $element->getDetails($deep);
} elseif ($element instanceof PDFObject && $deep) {
$values[$key] = $element->getDetails(false);
} elseif ($element instanceof self) {
if ($deep) {
$values[$key] = $element->getDetails();
}
} elseif ($element instanceof Element && !($element instanceof self)) {
$values[$key] = $element->getContent();
}
}
return $values;
}
public function __toString(): string
{
return implode(',', $this->value);
}
/**
* @return Element|PDFObject
*/
protected function resolveXRef(string $name)
{
if (($obj = $this->value[$name]) instanceof ElementXRef) {
/** @var ElementXRef $obj */
$obj = $this->document->getObjectById($obj->getId());
$this->value[$name] = $obj;
}
return $this->value[$name];
}
/**
* @todo: These methods return mixed and mismatched types throughout the hierarchy
*
* @return bool|ElementArray
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*\[(?P<array>.*)/is', $content, $match)) {
preg_match_all('/(.*?)(\[|\])/s', trim($content), $matches);
$level = 0;
$sub = '';
foreach ($matches[0] as $part) {
$sub .= $part;
$level += (false !== strpos($part, '[') ? 1 : -1);
if ($level <= 0) {
break;
}
}
// Removes 1 level [ and ].
$sub = substr(trim($sub), 1, -1);
$sub_offset = 0;
$values = Element::parse($sub, $document, $sub_offset, true);
$offset += strpos($content, '[') + 1;
// Find next ']' position
$offset += \strlen($sub) + 1;
return new self($values, $document);
}
return false;
}
}
@@ -0,0 +1,75 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
/**
* Class ElementBoolean
*/
class ElementBoolean extends Element
{
/**
* @param string|bool $value
*/
public function __construct($value)
{
parent::__construct('true' == strtolower($value) || true === $value, null);
}
public function __toString(): string
{
return $this->value ? 'true' : 'false';
}
public function equals($value): bool
{
return $this->getContent() === $value;
}
/**
* @return bool|ElementBoolean
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*(?P<value>true|false)/is', $content, $match)) {
$value = $match['value'];
$offset += strpos($content, $value) + \strlen($value);
return new self($value);
}
return false;
}
}
@@ -0,0 +1,139 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHPi, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
/**
* Class ElementDate
*/
class ElementDate extends ElementString
{
/**
* @var array<int,string>
*/
protected static $formats = [
4 => 'Y',
6 => 'Ym',
8 => 'Ymd',
10 => 'YmdH',
12 => 'YmdHi',
14 => 'YmdHis',
15 => 'YmdHise',
17 => 'YmdHisO',
18 => 'YmdHisO',
19 => 'YmdHisO',
];
/**
* @var string
*/
protected $format = 'c';
/**
* @var \DateTime
*/
protected $value;
public function __construct($value)
{
if (!($value instanceof \DateTime)) {
throw new \Exception('DateTime required.'); // FIXME: Sometimes strings are passed to this function
}
parent::__construct($value);
}
public function setFormat(string $format)
{
$this->format = $format;
}
public function equals($value): bool
{
if ($value instanceof \DateTime) {
$timestamp = $value->getTimeStamp();
} else {
$timestamp = strtotime($value);
}
return $timestamp == $this->value->getTimeStamp();
}
public function __toString(): string
{
return (string) $this->value->format($this->format);
}
/**
* @return bool|ElementDate
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*\(D\:(?P<name>.*?)\)/s', $content, $match)) {
$name = $match['name'];
$name = str_replace("'", '', $name);
$date = false;
// Smallest format : Y
// Full format : YmdHisP
if (preg_match('/^\d{4}(\d{2}(\d{2}(\d{2}(\d{2}(\d{2}(Z(\d{2,4})?|[\+-]?\d{2}(\d{2})?)?)?)?)?)?)?$/', $name)) {
if ($pos = strpos($name, 'Z')) {
$name = substr($name, 0, $pos + 1);
} elseif (18 == \strlen($name) && preg_match('/[^\+-]0000$/', $name)) {
$name = substr($name, 0, -4).'+0000';
}
$format = self::$formats[\strlen($name)];
$date = \DateTime::createFromFormat($format, $name, new \DateTimeZone('UTC'));
} else {
// special cases
if (preg_match('/^\d{1,2}-\d{1,2}-\d{4},?\s+\d{2}:\d{2}:\d{2}[\+-]\d{4}$/', $name)) {
$name = str_replace(',', '', $name);
$format = 'n-j-Y H:i:sO';
$date = \DateTime::createFromFormat($format, $name, new \DateTimeZone('UTC'));
}
}
if (!$date) {
return false;
}
$offset += strpos($content, '(D:') + \strlen($match['name']) + 4; // 1 for '(D:' and ')'
return new self($date);
}
return false;
}
}
@@ -0,0 +1,91 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
/**
* Class ElementHexa
*/
class ElementHexa extends ElementString
{
/**
* @return bool|ElementHexa|ElementDate
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*\<(?P<name>[A-F0-9]+)\>/is', $content, $match)) {
$name = $match['name'];
$offset += strpos($content, '<'.$name) + \strlen($name) + 2; // 1 for '>'
// repackage string as standard
$name = '('.self::decode($name).')';
$element = ElementDate::parse($name, $document);
if (!$element) {
$element = ElementString::parse($name, $document);
}
return $element;
}
return false;
}
public static function decode(string $value): string
{
$text = '';
// Filter $value of non-hexadecimal characters
$value = (string) preg_replace('/[^0-9a-f]/i', '', $value);
// Check for leading zeros (4-byte hexadecimal indicator), or
// the BE BOM
if ('00' === substr($value, 0, 2) || 'feff' === strtolower(substr($value, 0, 4))) {
$value = (string) preg_replace('/^feff/i', '', $value);
for ($i = 0, $length = \strlen($value); $i < $length; $i += 4) {
$hex = substr($value, $i, 4);
$text .= '&#'.str_pad(hexdec($hex), 4, '0', \STR_PAD_LEFT).';';
}
} else {
// Otherwise decode this as 2-byte hexadecimal
for ($i = 0, $length = \strlen($value); $i < $length; $i += 2) {
$hex = substr($value, $i, 2);
$text .= \chr(hexdec($hex));
}
}
$text = html_entity_decode($text, \ENT_NOQUOTES, 'UTF-8');
return $text;
}
}
@@ -0,0 +1,66 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Element;
/**
* Class ElementMissing
*/
class ElementMissing extends Element
{
public function __construct()
{
parent::__construct(null, null);
}
public function equals($value): bool
{
return false;
}
public function contains($value): bool
{
return false;
}
public function getContent(): bool
{
return false;
}
public function __toString(): string
{
return '';
}
}
@@ -0,0 +1,69 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
use Smalot\PdfParser\Font;
/**
* Class ElementName
*/
class ElementName extends Element
{
public function __construct(string $value)
{
parent::__construct($value, null);
}
public function equals($value): bool
{
return $value == $this->value;
}
/**
* @return bool|ElementName
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*\/([A-Z0-9\-\+,#\.]+)/is', $content, $match)) {
$name = $match[1];
$offset += strpos($content, $name) + \strlen($name);
$name = Font::decodeEntities($name);
return new self($name);
}
return false;
}
}
@@ -0,0 +1,71 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
/**
* Class ElementNull
*/
class ElementNull extends Element
{
public function __construct()
{
parent::__construct(null, null);
}
public function __toString(): string
{
return 'null';
}
public function equals($value): bool
{
return $this->getContent() === $value;
}
/**
* @return bool|ElementNull
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*(null)/s', $content, $match)) {
$offset += strpos($content, 'null') + \strlen('null');
return new self();
}
return false;
}
}
@@ -0,0 +1,62 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
/**
* Class ElementNumeric
*/
class ElementNumeric extends Element
{
public function __construct(string $value)
{
parent::__construct((float) $value, null);
}
/**
* @return bool|ElementNumeric
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*(?P<value>\-?[0-9\.]+)/s', $content, $match)) {
$value = $match['value'];
$offset += strpos($content, $value) + \strlen($value);
return new self($value);
}
return false;
}
}
@@ -0,0 +1,93 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
use Smalot\PdfParser\Font;
/**
* Class ElementString
*/
class ElementString extends Element
{
public function __construct($value)
{
parent::__construct($value, null);
}
public function equals($value): bool
{
return $value == $this->value;
}
/**
* @return bool|ElementString
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*\((?P<name>.*)/s', $content, $match)) {
$name = $match['name'];
// Find next ')' not escaped.
$cur_start_text = $start_search_end = 0;
while (false !== ($cur_start_pos = strpos($name, ')', $start_search_end))) {
$cur_extract = substr($name, $cur_start_text, $cur_start_pos - $cur_start_text);
preg_match('/(?P<escape>[\\\]*)$/s', $cur_extract, $match);
if (!(\strlen($match['escape']) % 2)) {
break;
}
$start_search_end = $cur_start_pos + 1;
}
// Extract string.
$name = substr($name, 0, (int) $cur_start_pos);
$offset += strpos($content, '(') + $cur_start_pos + 2; // 2 for '(' and ')'
$name = str_replace(
['\\\\', '\\ ', '\\/', '\(', '\)', '\n', '\r', '\t'],
['\\', ' ', '/', '(', ')', "\n", "\r", "\t"],
$name
);
// Decode string.
$name = Font::decodeOctal($name);
$name = Font::decodeEntities($name);
$name = Font::decodeHexadecimal($name, false);
$name = Font::decodeUnicode($name);
return new self($name);
}
return false;
}
}
@@ -0,0 +1,75 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
use Smalot\PdfParser\Header;
/**
* Class ElementStruct
*/
class ElementStruct extends Element
{
/**
* @return false|Header
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*<<(?P<struct>.*)/is', $content)) {
preg_match_all('/(.*?)(<<|>>)/s', trim($content), $matches);
$level = 0;
$sub = '';
foreach ($matches[0] as $part) {
$sub .= $part;
$level += (false !== strpos($part, '<<') ? 1 : -1);
if ($level <= 0) {
break;
}
}
$offset += strpos($content, '<<') + \strlen(rtrim($sub));
// Removes '<<' and '>>'.
$sub = trim((string) preg_replace('/^\s*<<(.*)>>\s*$/s', '\\1', $sub));
$position = 0;
$elements = Element::parse($sub, $document, $position);
return new Header($elements, $document);
}
return false;
}
}
@@ -0,0 +1,98 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser\Element;
use Smalot\PdfParser\Document;
use Smalot\PdfParser\Element;
/**
* Class ElementXRef
*/
class ElementXRef extends Element
{
public function getId(): string
{
return $this->getContent();
}
public function getObject()
{
return $this->document->getObjectById($this->getId());
}
public function equals($value): bool
{
/**
* In case $value is a number and $this->value is a string like 5_0
*
* Without this if-clause code like:
*
* $element = new ElementXRef('5_0');
* $this->assertTrue($element->equals(5));
*
* would fail (= 5_0 and 5 are not equal in PHP 8.0+).
*/
if (
true === is_numeric($value)
&& true === \is_string($this->getContent())
&& 1 === preg_match('/[0-9]+\_[0-9]+/', $this->getContent(), $matches)
) {
return (float) $this->getContent() == $value;
}
$id = ($value instanceof self) ? $value->getId() : $value;
return $this->getId() == $id;
}
public function __toString(): string
{
return '#Obj#'.$this->getId();
}
/**
* @return bool|ElementXRef
*/
public static function parse(string $content, ?Document $document = null, int &$offset = 0)
{
if (preg_match('/^\s*(?P<id>[0-9]+\s+[0-9]+\s+R)/s', $content, $match)) {
$id = $match['id'];
$offset += strpos($content, $id) + \strlen($id);
$id = str_replace(' ', '_', rtrim($id, ' R'));
return new self($id, $document);
}
return false;
}
}
@@ -0,0 +1,162 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
namespace Smalot\PdfParser;
use Smalot\PdfParser\Element\ElementNumeric;
use Smalot\PdfParser\Encoding\EncodingLocator;
use Smalot\PdfParser\Encoding\PostScriptGlyphs;
use Smalot\PdfParser\Exception\EncodingNotFoundException;
/**
* Class Encoding
*/
class Encoding extends PDFObject
{
/**
* @var array
*/
protected $encoding;
/**
* @var array
*/
protected $differences;
/**
* @var array
*/
protected $mapping;
public function init()
{
$this->mapping = [];
$this->differences = [];
$this->encoding = [];
if ($this->has('BaseEncoding')) {
$this->encoding = EncodingLocator::getEncoding($this->getEncodingClass())->getTranslations();
// Build table including differences.
$differences = $this->get('Differences')->getContent();
$code = 0;
if (!\is_array($differences)) {
return;
}
foreach ($differences as $difference) {
/** @var ElementNumeric $difference */
if ($difference instanceof ElementNumeric) {
$code = $difference->getContent();
continue;
}
// ElementName
$this->differences[$code] = $difference;
if (\is_object($difference)) {
$this->differences[$code] = $difference->getContent();
}
// For the next char.
++$code;
}
$this->mapping = $this->encoding;
foreach ($this->differences as $code => $difference) {
/* @var string $difference */
$this->mapping[$code] = $difference;
}
}
}
public function getDetails(bool $deep = true): array
{
$details = [];
$details['BaseEncoding'] = ($this->has('BaseEncoding') ? (string) $this->get('BaseEncoding') : 'Ansi');
$details['Differences'] = ($this->has('Differences') ? (string) $this->get('Differences') : '');
$details += parent::getDetails($deep);
return $details;
}
public function translateChar($dec): ?int
{
if (isset($this->mapping[$dec])) {
$dec = $this->mapping[$dec];
}
return PostScriptGlyphs::getCodePoint($dec);
}
/**
* Returns encoding class name if available or empty string (only prior PHP 7.4).
*
* @throws \Exception On PHP 7.4+ an exception is thrown if encoding class doesn't exist.
*/
public function __toString(): string
{
try {
return $this->getEncodingClass();
} catch (\Exception $e) {
// prior to PHP 7.4 toString has to return an empty string.
if (version_compare(\PHP_VERSION, '7.4.0', '<')) {
return '';
}
throw $e;
}
}
/**
* @throws EncodingNotFoundException
*/
protected function getEncodingClass(): string
{
// Load reference table charset.
$baseEncoding = preg_replace('/[^A-Z0-9]/is', '', $this->get('BaseEncoding')->getContent());
// Check for empty BaseEncoding field value
if (!\is_string($baseEncoding) || 0 == \strlen($baseEncoding)) {
$baseEncoding = 'StandardEncoding';
}
$className = '\\Smalot\\PdfParser\\Encoding\\'.$baseEncoding;
if (!class_exists($className)) {
throw new EncodingNotFoundException('Missing encoding data for: "'.$baseEncoding.'".');
}
return $className;
}
}
@@ -0,0 +1,8 @@
<?php
namespace Smalot\PdfParser\Encoding;
abstract class AbstractEncoding
{
abstract public function getTranslations(): array;
}
@@ -0,0 +1,17 @@
<?php
namespace Smalot\PdfParser\Encoding;
class EncodingLocator
{
protected static $encodings;
public static function getEncoding(string $encodingClassName): AbstractEncoding
{
if (!isset(self::$encodings[$encodingClassName])) {
self::$encodings[$encodingClassName] = new $encodingClassName();
}
return self::$encodings[$encodingClassName];
}
}
@@ -0,0 +1,76 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin1Encoding.pm
namespace Smalot\PdfParser\Encoding;
/**
* Class ISOLatin1Encoding
*/
class ISOLatin1Encoding extends AbstractEncoding
{
public function getTranslations(): array
{
$encoding =
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'space exclam quotedbl numbersign dollar percent ampersand quoteright '.
'parenleft parenright asterisk plus comma minus period slash zero one '.
'two three four five six seven eight nine colon semicolon less equal '.
'greater question at A B C D E F G H I J K L M N O P Q R S T U V W X '.
'Y Z bracketleft backslash bracketright asciicircum underscore '.
'quoteleft a b c d e f g h i j k l m n o p q r s t u v w x y z '.
'braceleft bar braceright asciitilde .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef dotlessi grave acute '.
'circumflex tilde macron breve dotaccent dieresis .notdef ring '.
'cedilla .notdef hungarumlaut ogonek caron space exclamdown cent '.
'sterling currency yen brokenbar section dieresis copyright '.
'ordfeminine guillemotleft logicalnot hyphen registered macron degree '.
'plusminus twosuperior threesuperior acute mu paragraph '.
'periodcentered cedilla onesuperior ordmasculine guillemotright '.
'onequarter onehalf threequarters questiondown Agrave Aacute '.
'Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute '.
'Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde '.
'Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave '.
'Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute '.
'acircumflex atilde adieresis aring ae ccedilla egrave eacute '.
'ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde '.
'ograve oacute ocircumflex otilde odieresis divide oslash ugrave '.
'uacute ucircumflex udieresis yacute thorn ydieresis';
return explode(' ', $encoding);
}
}
@@ -0,0 +1,76 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin9Encoding.pm
namespace Smalot\PdfParser\Encoding;
/**
* Class ISOLatin9Encoding
*/
class ISOLatin9Encoding extends AbstractEncoding
{
public function getTranslations(): array
{
$encoding =
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'space exclam quotedbl numbersign dollar percent ampersand quoteright '.
'parenleft parenright asterisk plus comma minus period slash zero one '.
'two three four five six seven eight nine colon semicolon less equal '.
'greater question at A B C D E F G H I J K L M N O P Q R S T U V W X '.
'Y Z bracketleft backslash bracketright asciicircum underscore '.
'quoteleft a b c d e f g h i j k l m n o p q r s t u v w x y z '.
'braceleft bar braceright asciitilde .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef dotlessi grave acute '.
'circumflex tilde macron breve dotaccent dieresis .notdef ring '.
'cedilla .notdef hungarumlaut ogonek caron space exclamdown cent '.
'sterling Euro yen Scaron section scaron copyright '.
'ordfeminine guillemotleft logicalnot hyphen registered macron degree '.
'plusminus twosuperior threesuperior Zcaron mu paragraph '.
'periodcentered zcaron onesuperior ordmasculine guillemotright '.
'OE oe Ydieresis questiondown Agrave Aacute '.
'Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute '.
'Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde '.
'Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave '.
'Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute '.
'acircumflex atilde adieresis aring ae ccedilla egrave eacute '.
'ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde '.
'ograve oacute ocircumflex otilde odieresis divide oslash ugrave '.
'uacute ucircumflex udieresis yacute thorn ydieresis';
return explode(' ', $encoding);
}
}
@@ -0,0 +1,80 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
// Source : http://www.opensource.apple.com/source/vim/vim-34/vim/runtime/print/mac-roman.ps
namespace Smalot\PdfParser\Encoding;
/**
* Class MacRomanEncoding
*/
class MacRomanEncoding extends AbstractEncoding
{
public function getTranslations(): array
{
$encoding =
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'space exclam quotedbl numbersign dollar percent ampersand quotesingle '.
'parenleft parenright asterisk plus comma minus period slash '.
'zero one two three four five six seven '.
'eight nine colon semicolon less equal greater question '.
'at A B C D E F G '.
'H I J K L M N O '.
'P Q R S T U V W '.
'X Y Z bracketleft backslash bracketright asciicircum underscore '.
'grave a b c d e f g '.
'h i j k l m n o '.
'p q r s t u v w '.
'x y z braceleft bar braceright asciitilde .notdef '.
'Adieresis Aring Ccedilla Eacute Ntilde Odieresis Udieresis aacute '.
'agrave acircumflex adieresis atilde aring ccedilla eacute egrave '.
'ecircumflex edieresis iacute igrave icircumflex idieresis ntilde oacute '.
'ograve ocircumflex odieresis otilde uacute ugrave ucircumflex udieresis '.
'dagger degree cent sterling section bullet paragraph germandbls '.
'registered copyright trademark acute dieresis notequal AE Oslash '.
'infinity plusminus lessequal greaterequal yen mu partialdiff summation '.
'Pi pi integral ordfeminine ordmasculine Omega ae oslash '.
'questiondown exclamdown logicalnot radical florin approxequal delta guillemotleft '.
'guillemotright ellipsis space Agrave Atilde Otilde OE oe '.
'endash emdash quotedblleft quotedblright quoteleft quoteright divide lozenge '.
'ydieresis Ydieresis fraction currency guilsinglleft guilsinglright fi fl '.
'daggerdbl periodcentered quotesinglbase quotedblbase perthousand Acircumflex Ecircumflex Aacute '.
'Edieresis Egrave Iacute Icircumflex Idieresis Igrave Oacute Ocircumflex '.
'heart Ograve Uacute Ucircumflex Ugrave dotlessi circumflex tilde '.
'macron breve dotaccent ring cedilla hungarumlaut ogonek caron';
return explode(' ', $encoding);
}
}
@@ -0,0 +1,189 @@
<?php
/**
* @file This file is part of the PdfParser library.
*
* @author Brian Huisman <bhuisman@greywyvern.com>
*
* @date 2023-06-28
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
// Source : https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.2.pdf
// Source : https://ia801001.us.archive.org/1/items/pdf1.7/pdf_reference_1-7.pdf
namespace Smalot\PdfParser\Encoding;
/**
* Class PDFDocEncoding
*/
class PDFDocEncoding
{
public static function getCodePage(): array
{
return [
"\x18" => "\u{02d8}", // breve
"\x19" => "\u{02c7}", // caron
"\x1a" => "\u{02c6}", // circumflex
"\x1b" => "\u{02d9}", // dotaccent
"\x1c" => "\u{02dd}", // hungarumlaut
"\x1d" => "\u{02db}", // ogonek
"\x1e" => "\u{02de}", // ring
"\x1f" => "\u{02dc}", // tilde
"\x7f" => '',
"\x80" => "\u{2022}", // bullet
"\x81" => "\u{2020}", // dagger
"\x82" => "\u{2021}", // daggerdbl
"\x83" => "\u{2026}", // ellipsis
"\x84" => "\u{2014}", // emdash
"\x85" => "\u{2013}", // endash
"\x86" => "\u{0192}", // florin
"\x87" => "\u{2044}", // fraction
"\x88" => "\u{2039}", // guilsinglleft
"\x89" => "\u{203a}", // guilsinglright
"\x8a" => "\u{2212}", // minus
"\x8b" => "\u{2030}", // perthousand
"\x8c" => "\u{201e}", // quotedblbase
"\x8d" => "\u{201c}", // quotedblleft
"\x8e" => "\u{201d}", // quotedblright
"\x8f" => "\u{2018}", // quoteleft
"\x90" => "\u{2019}", // quoteright
"\x91" => "\u{201a}", // quotesinglbase
"\x92" => "\u{2122}", // trademark
"\x93" => "\u{fb01}", // fi
"\x94" => "\u{fb02}", // fl
"\x95" => "\u{0141}", // Lslash
"\x96" => "\u{0152}", // OE
"\x97" => "\u{0160}", // Scaron
"\x98" => "\u{0178}", // Ydieresis
"\x99" => "\u{017d}", // Zcaron
"\x9a" => "\u{0131}", // dotlessi
"\x9b" => "\u{0142}", // lslash
"\x9c" => "\u{0153}", // oe
"\x9d" => "\u{0161}", // scaron
"\x9e" => "\u{017e}", // zcaron
"\x9f" => '',
"\xa0" => "\u{20ac}", // Euro
"\xa1" => "\u{00a1}", // exclamdown
"\xa2" => "\u{00a2}", // cent
"\xa3" => "\u{00a3}", // sterling
"\xa4" => "\u{00a4}", // currency
"\xa5" => "\u{00a5}", // yen
"\xa6" => "\u{00a6}", // brokenbar
"\xa7" => "\u{00a7}", // section
"\xa8" => "\u{00a8}", // dieresis
"\xa9" => "\u{00a9}", // copyright
"\xaa" => "\u{00aa}", // ordfeminine
"\xab" => "\u{00ab}", // guillemotleft
"\xac" => "\u{00ac}", // logicalnot
"\xad" => '',
"\xae" => "\u{00ae}", // registered
"\xaf" => "\u{00af}", // macron
"\xb0" => "\u{00b0}", // degree
"\xb1" => "\u{00b1}", // plusminus
"\xb2" => "\u{00b2}", // twosuperior
"\xb3" => "\u{00b3}", // threesuperior
"\xb4" => "\u{00b4}", // acute
"\xb5" => "\u{00b5}", // mu
"\xb6" => "\u{00b6}", // paragraph
"\xb7" => "\u{00b7}", // periodcentered
"\xb8" => "\u{00b8}", // cedilla
"\xb9" => "\u{00b9}", // onesuperior
"\xba" => "\u{00ba}", // ordmasculine
"\xbb" => "\u{00bb}", // guillemotright
"\xbc" => "\u{00bc}", // onequarter
"\xbd" => "\u{00bd}", // onehalf
"\xbe" => "\u{00be}", // threequarters
"\xbf" => "\u{00bf}", // questiondown
"\xc0" => "\u{00c0}", // Agrave
"\xc1" => "\u{00c1}", // Aacute
"\xc2" => "\u{00c2}", // Acircumflex
"\xc3" => "\u{00c3}", // Atilde
"\xc4" => "\u{00c4}", // Adieresis
"\xc5" => "\u{00c5}", // Aring
"\xc6" => "\u{00c6}", // AE
"\xc7" => "\u{00c7}", // Ccedill
"\xc8" => "\u{00c8}", // Egrave
"\xc9" => "\u{00c9}", // Eacute
"\xca" => "\u{00ca}", // Ecircumflex
"\xcb" => "\u{00cb}", // Edieresis
"\xcc" => "\u{00cc}", // Igrave
"\xcd" => "\u{00cd}", // Iacute
"\xce" => "\u{00ce}", // Icircumflex
"\xcf" => "\u{00cf}", // Idieresis
"\xd0" => "\u{00d0}", // Eth
"\xd1" => "\u{00d1}", // Ntilde
"\xd2" => "\u{00d2}", // Ograve
"\xd3" => "\u{00d3}", // Oacute
"\xd4" => "\u{00d4}", // Ocircumflex
"\xd5" => "\u{00d5}", // Otilde
"\xd6" => "\u{00d6}", // Odieresis
"\xd7" => "\u{00d7}", // multiply
"\xd8" => "\u{00d8}", // Oslash
"\xd9" => "\u{00d9}", // Ugrave
"\xda" => "\u{00da}", // Uacute
"\xdb" => "\u{00db}", // Ucircumflex
"\xdc" => "\u{00dc}", // Udieresis
"\xdd" => "\u{00dd}", // Yacute
"\xde" => "\u{00de}", // Thorn
"\xdf" => "\u{00df}", // germandbls
"\xe0" => "\u{00e0}", // agrave
"\xe1" => "\u{00e1}", // aacute
"\xe2" => "\u{00e2}", // acircumflex
"\xe3" => "\u{00e3}", // atilde
"\xe4" => "\u{00e4}", // adieresis
"\xe5" => "\u{00e5}", // aring
"\xe6" => "\u{00e6}", // ae
"\xe7" => "\u{00e7}", // ccedilla
"\xe8" => "\u{00e8}", // egrave
"\xe9" => "\u{00e9}", // eacute
"\xea" => "\u{00ea}", // ecircumflex
"\xeb" => "\u{00eb}", // edieresis
"\xec" => "\u{00ec}", // igrave
"\xed" => "\u{00ed}", // iacute
"\xee" => "\u{00ee}", // icircumflex
"\xef" => "\u{00ef}", // idieresis
"\xf0" => "\u{00f0}", // eth
"\xf1" => "\u{00f1}", // ntilde
"\xf2" => "\u{00f2}", // ograve
"\xf3" => "\u{00f3}", // oacute
"\xf4" => "\u{00f4}", // ocircumflex
"\xf5" => "\u{00f5}", // otilde
"\xf6" => "\u{00f6}", // odieresis
"\xf7" => "\u{00f7}", // divide
"\xf8" => "\u{00f8}", // oslash
"\xf9" => "\u{00f9}", // ugrave
"\xfa" => "\u{00fa}", // uacute
"\xfb" => "\u{00fb}", // ucircumflex
"\xfc" => "\u{00fc}", // udieresis
"\xfd" => "\u{00fd}", // yacute
"\xfe" => "\u{00fe}", // thorn
"\xff" => "\u{00ff}", // ydieresis
];
}
public static function convertPDFDoc2UTF8(string $content): string
{
return strtr($content, static::getCodePage());
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/StandardEncoding.pm
namespace Smalot\PdfParser\Encoding;
/**
* Class StandardEncoding
*/
class StandardEncoding extends AbstractEncoding
{
public function getTranslations(): array
{
$encoding =
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'space exclam quotedbl numbersign dollar percent ampersand quoteright '.
'parenleft parenright asterisk plus comma hyphen period slash zero '.
'one two three four five six seven eight nine colon semicolon less '.
'equal greater question at A B C D E F G H I J K L M N O P Q R S T U '.
'V W X Y Z bracketleft backslash bracketright asciicircum underscore '.
'quoteleft a b c d e f g h i j k l m n o p q r s t u v w x y z '.
'braceleft bar braceright asciitilde .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef exclamdown cent '.
'sterling fraction yen florin section currency quotesingle '.
'quotedblleft guillemotleft guilsinglleft guilsinglright fi fl '.
'.notdef endash dagger daggerdbl periodcentered .notdef paragraph '.
'bullet quotesinglbase quotedblbase quotedblright guillemotright '.
'ellipsis perthousand .notdef questiondown .notdef grave acute '.
'circumflex tilde macron breve dotaccent dieresis .notdef ring '.
'cedilla .notdef hungarumlaut ogonek caron emdash .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef AE .notdef '.
'ordfeminine .notdef .notdef .notdef .notdef Lslash Oslash OE '.
'ordmasculine .notdef .notdef .notdef .notdef .notdef ae .notdef '.
'.notdef .notdef dotlessi .notdef .notdef lslash oslash oe germandbls '.
'.notdef .notdef .notdef .notdef';
return explode(' ', $encoding);
}
}
@@ -0,0 +1,76 @@
<?php
/**
* @file
* This file is part of the PdfParser library.
*
* @author Sébastien MALOT <sebastien@malot.fr>
*
* @date 2017-01-03
*
* @license LGPLv3
*
* @url <https://github.com/smalot/pdfparser>
*
* PdfParser is a pdf library written in PHP, extraction oriented.
* Copyright (C) 2017 - Sébastien MALOT <sebastien@malot.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.
* If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
*/
// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/WinANSIEncoding.pm
namespace Smalot\PdfParser\Encoding;
/**
* Class WinAnsiEncoding
*/
class WinAnsiEncoding extends AbstractEncoding
{
public function getTranslations(): array
{
$encoding =
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '.
'space exclam quotedbl numbersign dollar percent ampersand quotesingle '.
'parenleft parenright asterisk plus comma hyphen period slash zero one '.
'two three four five six seven eight nine colon semicolon less equal '.
'greater question at A B C D E F G H I J K L M N O P Q R S T U V W X '.
'Y Z bracketleft backslash bracketright asciicircum underscore '.
'grave a b c d e f g h i j k l m n o p q r s t u v w x y z '.
'braceleft bar braceright asciitilde bullet Euro bullet quotesinglbase '.
'florin quotedblbase ellipsis dagger daggerdbl circumflex perthousand '.
'Scaron guilsinglleft OE bullet Zcaron bullet bullet quoteleft quoteright '.
'quotedblleft quotedblright bullet endash emdash tilde trademark scaron '.
'guilsinglright oe bullet zcaron Ydieresis space exclamdown cent '.
'sterling currency yen brokenbar section dieresis copyright '.
'ordfeminine guillemotleft logicalnot hyphen registered macron degree '.
'plusminus twosuperior threesuperior acute mu paragraph '.
'periodcentered cedilla onesuperior ordmasculine guillemotright '.
'onequarter onehalf threequarters questiondown Agrave Aacute '.
'Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute '.
'Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde '.
'Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave '.
'Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute '.
'acircumflex atilde adieresis aring ae ccedilla egrave eacute '.
'ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde '.
'ograve oacute ocircumflex otilde odieresis divide oslash ugrave '.
'uacute ucircumflex udieresis yacute thorn ydieresis';
return explode(' ', $encoding);
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Smalot\PdfParser\Exception;
/**
* This Exception is thrown when no PDF data was given.
*/
class EmptyPdfException extends \Exception
{
}
@@ -0,0 +1,7 @@
<?php
namespace Smalot\PdfParser\Exception;
class EncodingNotFoundException extends \Exception
{
}

Some files were not shown because too many files have changed in this diff Show More