Initial import
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
|
||||
# Local environment / secrets
|
||||
/config.php
|
||||
.env
|
||||
|
||||
# Runtime uploads and generated files
|
||||
uploads/*.pdf
|
||||
uploads/*.txt
|
||||
uploads/*.csv
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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!"
|
||||
@@ -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>
|
||||
@@ -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";
|
||||
@@ -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>
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
@@ -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'; ?>
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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">© 2569 มหาวิทยาลัยตาปี</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
session_destroy();
|
||||
header('Location: ' . BASE_URL . '/login.php');
|
||||
exit;
|
||||
@@ -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");
|
||||
}
|
||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 370 KiB |
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
</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>';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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'; ?>
|
||||
@@ -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;
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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'; ?>
|
||||
@@ -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;
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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'; ?>
|
||||
@@ -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;
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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'; ?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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;
|
||||
?>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,59 @@
|
||||
# PDF parser
|
||||
|
||||
[](//packagist.org/packages/smalot/pdfparser)
|
||||

|
||||

|
||||
[](https://scrutinizer-ci.com/g/smalot/pdfparser/?branch=master)
|
||||
[](//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.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||