Sách Giáo Khoa Việt Nam - sachgiaokhoa.org

CodeIgniter 4 website:
- Catalog 999 SGK (lớp 1-12) từ API, lọc theo lớp/môn/NXB, tìm kiếm
- Trang chủ grouped browse (bậc học -> lớp -> môn, ưu tiên môn quan trọng)
- Chi tiết sách: metadata đầy đủ, cover nghệ thuật /thumb/{W}x{H}/
- Đọc online qua gate captcha + countdown, PDF viewer pdf.js
- Download: captcha + token một lần + auto cache-bust version
- Tự đồng bộ catalog/cover khi API thay đổi (refresh:catalog)
- Legal: DMCA, miễn trừ trách nhiệm, điều khoản, bảo mật, liên hệ
This commit is contained in:
sachgiaokhoaorg
2026-08-23 07:05:49 +00:00
commit e6b265db99
494 changed files with 134829 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="hero">
<h1>Sách Giáo Khoa Việt Nam — Lớp 1 đến lớp 12</h1>
<p>Kho tra cứu đầy đủ <?= number_format($total) ?> đầu sách giáo khoa thuộc mọi bộ sách: Kết nối tri thức với cuộc sống, Chân trời sáng tạo, Cánh Diều, Giáo dục Việt Nam... Chọn lớp hoặc môn học để bắt đầu.</p>
<div class="stats">
<div class="stat"><b><?= number_format($total) ?></b><span>Đầu sách</span></div>
<div class="stat"><b>12</b><span>Khoảng lớp</span></div>
<div class="stat"><b><?= count($publishers) ?></b><span>Nhà xuất bản / bộ sách</span></div>
</div>
</div>
<?php foreach ($levels as $lv): ?>
<section class="level-section">
<h2 class="level-title"><?= esc($lv['label']) ?></h2>
<div class="grade-grid">
<?php foreach ($lv['grades'] as $g): ?>
<div class="grade-card">
<div class="grade-covers">
<?php foreach (array_slice($g['covers'], 0, 5) as $c): ?>
<a href="/sach/<?= esc($c['id']) ?>" title="<?= esc($c['title']) ?>">
<img loading="lazy" src="/thumb/240x320/<?= esc($c['id']) ?>.jpg?v=<?= media_version() ?>" alt="<?= esc($c['title']) ?>"
onerror="this.parentNode.style.display='none'">
</a>
<?php endforeach; ?>
</div>
<a class="grade-head" href="/?lop=<?= $g['grade'] ?>">
<span class="grade-num">Lớp <?= $g['grade'] ?></span>
<span class="grade-count"><?= $g['total'] ?> sách</span>
</a>
<div class="subject-pills">
<?php $i = 0; foreach ($g['subjects'] as $s => $n): if ($i++ >= 8): ?>
<a class="pill more" href="/?lop=<?= $g['grade'] ?>">+ <?= count($g['subjects']) - 8 ?> môn khác</a>
<?php break; endif; ?>
<a class="pill" href="/?lop=<?= $g['grade'] ?>&mon=<?= urlencode($s) ?>"><?= esc($s) ?></a>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endforeach; ?>
<section class="level-section">
<h2 class="level-title">Theo nhà xuất bản / bộ sách</h2>
<div class="pub-grid">
<?php foreach ($publishers as $p => $n): ?>
<a class="pub-card" href="/?nxb=<?= urlencode($p) ?>">
<span class="pub-name"><?= esc($p) ?></span>
<span class="pub-count"><?= $n ?> đầu sách</span>
</a>
<?php endforeach; ?>
</div>
</section>
<?= $this->endSection() ?>
+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Xác nhận đọc online <?= esc($book['title']) ?></title>
<meta name="robots" content="noindex, nofollow">
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/app.css">
</head>
<body class="reader-body">
<main class="container">
<div class="gate-card" id="tai-xuong">
<div class="gate-book">
<img src="/thumb/240x320/<?= esc($book['id']) ?>.jpg?v=<?= media_version() ?>" alt="Bìa <?= esc($book['title']) ?>" onerror="this.style.display='none'">
<div>
<h1><?= esc($book['title']) ?></h1>
<p class="gate-sub">
<?= $book['grade'] ? 'Lớp ' . esc($book['grade']) . ' · ' : '' ?><?= esc($book['subject']) ?> · <?= esc($book['publisher']) ?>
<?php if (! empty($book['meta']['pages'])): ?> · <?= (int) $book['meta']['pages'] ?> trang<?php endif; ?>
</p>
<a href="/sach/<?= esc($book['id']) ?>">← Quay lại trang sách</a>
</div>
</div>
<div class="countdown-note">
⏳ Nhập mã captcha và chờ <b id="cd-seconds"><?= $countdown ?></b> giây để tiếp tục đọc online.
Phiên đọc có hiệu lực 2 giờ, bao gồm cả tải xuống PDF.
</div>
<?php if ($error): ?>
<div class="error-box"><?= esc($error) ?></div>
<?php endif; ?>
<form method="post" action="/doc/<?= esc($book['id']) ?>" id="dl-form">
<input type="hidden" name="download" value="1">
<div class="captcha-row">
<img src="/captcha" alt="Mã captcha" id="captcha-img">
<button type="button" class="refresh-captcha" title="Đổi mã khác" onclick="document.getElementById('captcha-img').src='/captcha?'+Date.now()">🔄</button>
<input type="text" name="captcha" required maxlength="5" autocomplete="off" placeholder="Nhập mã">
</div>
<button class="btn btn-primary btn-block" type="submit" id="dl-btn" disabled>
Tiếp tục đọc online
</button>
<p class="gate-legal">⚖️ Tài liệu thuộc bản quyền Nhà xuất bản — chỉ phục vụ mục đích tra cứu, phi thương mại.</p>
</form>
</div>
</main>
<script>
(function () {
var seconds = <?= $countdown ?>;
var btn = document.getElementById('dl-btn');
var s1 = document.getElementById('cd-seconds');
var t = setInterval(function () {
seconds--;
if (s1) s1.textContent = seconds;
if (seconds <= 0) {
clearInterval(t);
btn.disabled = false;
btn.textContent = '✓ Tiếp tục đọc online';
}
}, 1000);
})();
</script>
</body>
</html>
+103
View File
@@ -0,0 +1,103 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="hero">
<h1>
<?php if ($filters['q'] !== ''): ?>
Kết quả tìm kiếm "<?= esc($filters['q']) ?>"
<?php elseif ($filters['lop'] !== ''): ?>
Sách lớp <?= esc($filters['lop']) ?>
<?php elseif ($filters['mon'] !== ''): ?>
Môn <?= esc($filters['mon']) ?>
<?php else: ?>
Sách <?= esc($filters['nxb']) ?>
<?php endif; ?>
</h1>
<p><?= number_format($total) ?> đầu sách phù hợp.</p>
</div>
<form class="filters" method="get" action="/">
<div class="grade-pills">
<a href="<?= $filters['q'] !== '' ? '/?q=' . urlencode($filters['q']) : '/' ?>" class="<?= $filters['lop'] === '' ? 'active' : '' ?>">Tất cả</a>
<?php foreach ($grades as $g): ?>
<a class="<?= $filters['lop'] === (string) $g ? 'active' : '' ?>"
href="/?<?= esc(http_build_query(array_merge($_GET, ['trang' => 1, 'lop' => $g]))) ?>">Lớp <?= $g ?></a>
<?php endforeach; ?>
</div>
<label>Môn học
<select name="mon" onchange="this.form.submit()">
<option value="">-- Tất cả --</option>
<?php foreach ($subjects as $s => $n): ?>
<option value="<?= esc($s) ?>" <?= $filters['mon'] === $s ? 'selected' : '' ?>><?= esc($s ?: 'Khác') ?> (<?= $n ?>)</option>
<?php endforeach; ?>
</select>
</label>
<label>Nhà xuất bản
<select name="nxb" onchange="this.form.submit()">
<option value="">-- Tất cả --</option>
<?php foreach ($publishers as $p => $n): ?>
<option value="<?= esc($p) ?>" <?= $filters['nxb'] === $p ? 'selected' : '' ?>><?= esc($p) ?> (<?= $n ?>)</option>
<?php endforeach; ?>
</select>
</label>
<?php if ($filters['q'] !== ''): ?><input type="hidden" name="q" value="<?= esc($filters['q']) ?>"><?php endif; ?>
<button class="btn btn-primary" type="submit">Lọc</button>
<a class="btn btn-outline reset" href="/">Xóa lọc</a>
</form>
<?php foreach ($grouped as $groupLabel => $groupBooks): ?>
<div class="group-header">
<h2><?= esc($groupLabel) ?> <span class="group-count"><?= count($groupBooks) ?> sách</span></h2>
</div>
<div class="book-grid">
<?php foreach ($groupBooks as $b): ?>
<?php $m = $b['meta']; ?>
<a class="book-card" href="/sach/<?= esc($b['id']) ?>">
<div class="book-cover">
<img loading="lazy" src="/thumb/360x480/<?= esc($b['id']) ?>.jpg?v=<?= media_version() ?>" alt="Bìa <?= esc($b['title']) ?>"
onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 3 4%22%3E%3Crect width=%223%22 height=%224%22 fill=%22%23e8ecf4%22/%3E%3C/svg%3E'">
<?php if (! empty($m['pages'])): ?><span class="cover-pages"><?= (int) $m['pages'] ?> tr</span><?php endif; ?>
</div>
<div class="book-info">
<h3><?= esc($b['title']) ?></h3>
<?php if ($b['subject']): ?><span class="book-subject"><?= esc($b['subject']) ?></span><?php endif; ?>
<div class="badges">
<?php if ($b['grade']): ?><span class="badge">Lớp <?= esc($b['grade']) ?></span><?php endif; ?>
<?php if ($b['publisher']): ?><span class="badge pub"><?= esc($b['publisher']) ?></span><?php endif; ?>
</div>
<div class="book-meta">
<?php if (! empty($m['sizeMB'])): ?><span>💾 <?= number_format((float) $m['sizeMB'], 1) ?> MB</span><?php endif; ?>
<?php if (! empty($m['authors'])): ?><span class="authors">✍ <?= esc(mb_strimwidth($m['authors'], 0, 46, '…')) ?></span><?php endif; ?>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<?php if (empty($grouped)): ?>
<div class="legal-page"><p>Không tìm thấy sách phù hợp với bộ lọc. <a href="/">Xóa bộ lọc</a>.</p></div>
<?php endif; ?>
<?php if ($totalPage > 1): ?>
<div class="pagination">
<?php
$qs = fn($p) => '/?' . esc(http_build_query(array_merge($_GET, ['trang' => $p])));
?>
<?php if ($page > 1): ?><a href="<?= $qs($page - 1) ?>"> Trước</a><?php else: ?><span class="disabled"> Trước</span><?php endif; ?>
<?php
$start = max(1, $page - 2);
$end = min($totalPage, $start + 4);
$start = max(1, $end - 4);
for ($p = $start; $p <= $end; $p++):
?>
<?php if ($p === $page): ?><span class="active"><?= $p ?></span>
<?php else: ?><a href="<?= $qs($p) ?>"><?= $p ?></a><?php endif; ?>
<?php endfor; ?>
<?php if ($page < $totalPage): ?><a href="<?= $qs($page + 1) ?>">Sau </a><?php else: ?><span class="disabled">Sau </span><?php endif; ?>
</div>
<?php endif; ?>
<?= $this->endSection() ?>
+50
View File
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Đọc online <?= esc($book['title']) ?></title>
<meta name="robots" content="noindex, nofollow">
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/app.css">
</head>
<body class="reader-body">
<header class="reader-header">
<a class="reader-back" href="/sach/<?= esc($book['id']) ?>" title="Về trang sách">←</a>
<div class="reader-title">
<strong><?= esc($book['title']) ?></strong>
<span><?= $book['grade'] ? 'Lớp ' . esc($book['grade']) . ' · ' : '' ?><?= esc($book['subject']) ?> · <?= esc($book['publisher']) ?></span>
</div>
<button class="btn btn-green reader-dl" id="dl-btn" data-id="<?= esc($book['id']) ?>">⬇ Tải PDF</button>
</header>
<main class="reader-main">
<div class="viewer-frame reader-frame" id="pdfViewer" data-src="/media/pdf/<?= esc($book['id']) ?>"></div>
</main>
<script>
(function () {
var el = document.getElementById('pdfViewer');
var iframe = document.createElement('iframe');
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = '0';
iframe.title = 'Trình đọc PDF';
iframe.src = '/assets/pdfjs/web/viewer.html?file=' + encodeURIComponent(el.dataset.src);
el.appendChild(iframe);
var dl = document.getElementById('dl-btn');
dl.addEventListener('click', function () {
dl.disabled = true; dl.textContent = 'Đang tạo liên kết…';
fetch('/pdf-token/' + dl.dataset.id, { method: 'POST', credentials: 'same-origin' })
.then(function (r) { return r.json(); })
.then(function (d) {
if (d.ok) { window.location.href = d.url; dl.textContent = '⬇ Đang tải xuống…'; }
else { dl.textContent = 'Hết phiên — tải lại trang'; }
})
.catch(function () { dl.textContent = 'Lỗi mạng, thử lại'; dl.disabled = false; });
});
})();
</script>
</body>
</html>
+60
View File
@@ -0,0 +1,60 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<?php $meta = $book['meta']; ?>
<div class="book-detail">
<div>
<div class="detail-cover">
<img src="/media/cover/<?= esc($book['id']) ?>?v=<?= media_version() ?>" alt="Bìa sách <?= esc($book['title']) ?>"
onerror="this.style.display='none'">
</div>
<div class="actions" style="flex-direction:column">
<a class="btn btn-primary" href="/doc/<?= esc($book['id']) ?>">📖 Đọc online</a>
<a class="btn btn-green" href="/doc/<?= esc($book['id']) ?>#tai-xuong">⬇ Tải xuống PDF</a>
</div>
</div>
<div class="detail-info">
<h1><?= esc($book['title']) ?></h1>
<table class="meta-table">
<?php if ($book['grade']): ?>
<tr><th>Lớp</th><td>Lớp <?= esc($book['grade']) ?></td></tr>
<?php endif; ?>
<?php if ($book['subject']): ?>
<tr><th>Môn học</th><td><?= esc($book['subject']) ?></td></tr>
<?php endif; ?>
<tr><th>Nhà xuất bản / Bộ sách</th><td><?= esc($book['publisher']) ?><?= ! empty($meta['publisherFull']) ? ' — ' . esc($meta['publisherFull']) : '' ?></td></tr>
<?php if (! empty($meta['authors'])): ?>
<tr><th>Tác giả / Chủ biên</th><td><?= esc($meta['authors']) ?></td></tr>
<?php endif; ?>
<?php if (! empty($meta['pages'])): ?>
<tr><th>Số trang</th><td><?= (int) $meta['pages'] ?> trang</td></tr>
<?php endif; ?>
<?php if (! empty($meta['sizeMB'])): ?>
<tr><th>Dung lượng</th><td><?= number_format((float) $meta['sizeMB'], 2) ?> MB</td></tr>
<?php endif; ?>
<?php if (! empty($meta['isbn'])): ?>
<tr><th>ISBN</th><td><?= esc($meta['isbn']) ?></td></tr>
<?php endif; ?>
<?php if (! empty($meta['price'])): ?>
<tr><th>Giá bìa</th><td><?= esc($meta['price']) ?></td></tr>
<?php endif; ?>
<?php if (! empty($meta['pdfVersion'])): ?>
<tr><th>Phiên bản PDF</th><td>PDF <?= esc($meta['pdfVersion']) ?></td></tr>
<?php endif; ?>
<?php if (! empty($meta['creationDate'])): ?>
<tr><th>Ngày tạo tài liệu</th><td><?= esc($meta['creationDate']) ?></td></tr>
<?php endif; ?>
<tr><th>Lượt tải</th><td><?= number_format((int) $downloads) ?></td></tr>
</table>
<div class="countdown-note">
⚖️ <strong>Lưu ý bản quyền:</strong> Tài liệu này thuộc bản quyền của Nhà xuất bản và Bộ Giáo dục &amp; Đào tạo.
Nội dung chỉ được hiển thị phục vụ mục đích tra cứu, tham khảo. Vui lòng mua sách giấy chính hãng để hỗ trợ tác giả và nhà xuất bản.
Báo cáo vi phạm <a href="/lien-he">tại đây</a>.
</div>
</div>
</div>
<?= $this->endSection() ?>
+7
View File
@@ -0,0 +1,7 @@
<?php
use CodeIgniter\CLI\CLI;
CLI::error('ERROR: ' . $code);
CLI::write($message);
CLI::newLine();
+65
View File
@@ -0,0 +1,65 @@
<?php
use CodeIgniter\CLI\CLI;
// The main Exception
CLI::write('[' . $exception::class . ']', 'light_gray', 'red');
CLI::write($message);
CLI::write('at ' . CLI::color(clean_path($exception->getFile()) . ':' . $exception->getLine(), 'green'));
CLI::newLine();
$last = $exception;
while ($prevException = $last->getPrevious()) {
$last = $prevException;
CLI::write(' Caused by:');
CLI::write(' [' . $prevException::class . ']', 'red');
CLI::write(' ' . $prevException->getMessage());
CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green'));
CLI::newLine();
}
// The backtrace
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
$backtraces = $last->getTrace();
if ($backtraces) {
CLI::write('Backtrace:', 'green');
}
foreach ($backtraces as $i => $error) {
$padFile = ' '; // 4 spaces
$padClass = ' '; // 7 spaces
$c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT);
if (isset($error['file'])) {
$filepath = clean_path($error['file']) . ':' . $error['line'];
CLI::write($c . $padFile . CLI::color($filepath, 'yellow'));
} else {
CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow'));
}
$function = '';
if (isset($error['class'])) {
$type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type'];
$function .= $padClass . $error['class'] . $type . $error['function'];
} elseif (! isset($error['class']) && isset($error['function'])) {
$function .= $padClass . $error['function'];
}
$args = implode(', ', array_map(static fn ($value): string => match (true) {
is_object($value) => 'Object(' . $value::class . ')',
is_array($value) => $value !== [] ? '[...]' : '[]',
$value === null => 'null', // return the lowercased version
default => var_export($value, true),
}, array_values($error['args'] ?? [])));
$function .= '(' . $args . ')';
CLI::write($function);
CLI::newLine();
}
}
+5
View File
@@ -0,0 +1,5 @@
<?php
// On the CLI, we still want errors in productions
// so just use the exception template.
include __DIR__ . '/error_exception.php';
+194
View File
@@ -0,0 +1,194 @@
:root {
--main-bg-color: #fff;
--main-text-color: #555;
--dark-text-color: #222;
--light-text-color: #c7c7c7;
--brand-primary-color: #DC4814;
--light-bg-color: #ededee;
--dark-bg-color: #404040;
}
body {
height: 100%;
background: var(--main-bg-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
color: var(--main-text-color);
font-weight: 300;
margin: 0;
padding: 0;
}
h1 {
font-weight: lighter;
font-size: 3rem;
color: var(--dark-text-color);
margin: 0;
}
h1.headline {
margin-top: 20%;
font-size: 5rem;
}
.text-center {
text-align: center;
}
p.lead {
font-size: 1.6rem;
}
.container {
max-width: 75rem;
margin: 0 auto;
padding: 1rem;
}
.header {
background: var(--light-bg-color);
color: var(--dark-text-color);
margin-top: 2.17rem;
}
.header .container {
padding: 1rem;
}
.header h1 {
font-size: 2.5rem;
font-weight: 500;
}
.header p {
font-size: 1.2rem;
margin: 0;
line-height: 2.5;
}
.header a {
color: var(--brand-primary-color);
margin-left: 2rem;
display: none;
text-decoration: none;
}
.header:hover a {
display: inline;
}
.environment {
background: var(--brand-primary-color);
color: var(--main-bg-color);
text-align: center;
padding: calc(4px + 0.2083vw);
width: 100%;
top: 0;
position: fixed;
}
.source {
background: #343434;
color: var(--light-text-color);
padding: 0.5em 1em;
border-radius: 5px;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-size: 0.85rem;
margin: 0;
overflow-x: scroll;
}
.source span.line {
line-height: 1.4;
}
.source span.line .number {
color: #666;
}
.source .line .highlight {
display: block;
background: var(--dark-text-color);
color: var(--light-text-color);
}
.source span.highlight .number {
color: #fff;
}
.tabs {
list-style: none;
list-style-position: inside;
margin: 0;
padding: 0;
margin-bottom: -1px;
}
.tabs li {
display: inline;
}
.tabs a:link,
.tabs a:visited {
padding: 0 1rem;
line-height: 2.7;
text-decoration: none;
color: var(--dark-text-color);
background: var(--light-bg-color);
border: 1px solid rgba(0,0,0,0.15);
border-bottom: 0;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
display: inline-block;
}
.tabs a:hover {
background: var(--light-bg-color);
border-color: rgba(0,0,0,0.15);
}
.tabs a.active {
background: var(--main-bg-color);
color: var(--main-text-color);
}
.tab-content {
background: var(--main-bg-color);
border: 1px solid rgba(0,0,0,0.15);
}
.content {
padding: 1rem;
}
.hide {
display: none;
}
.alert {
margin-top: 2rem;
display: block;
text-align: center;
line-height: 3.0;
background: #d9edf7;
border: 1px solid #bcdff1;
border-radius: 5px;
color: #31708f;
}
table {
width: 100%;
overflow: hidden;
}
th {
text-align: left;
border-bottom: 1px solid #e7e7e7;
padding-bottom: 0.5rem;
}
td {
padding: 0.2rem 0.5rem 0.2rem 0;
}
tr:hover td {
background: #f1f1f1;
}
td pre {
white-space: pre-wrap;
}
.trace a {
color: inherit;
}
.trace table {
width: auto;
}
.trace tr td:first-child {
min-width: 5em;
font-weight: bold;
}
.trace td {
background: var(--light-bg-color);
padding: 0 1rem;
}
.trace td pre {
margin: 0;
}
.args {
display: none;
}
+116
View File
@@ -0,0 +1,116 @@
var tabLinks = new Array();
var contentDivs = new Array();
function init()
{
// Grab the tab links and content divs from the page
var tabListItems = document.getElementById('tabs').childNodes;
console.log(tabListItems);
for (var i = 0; i < tabListItems.length; i ++)
{
if (tabListItems[i].nodeName == "LI")
{
var tabLink = getFirstChildWithTagName(tabListItems[i], 'A');
var id = getHash(tabLink.getAttribute('href'));
tabLinks[id] = tabLink;
contentDivs[id] = document.getElementById(id);
}
}
// Assign onclick events to the tab links, and
// highlight the first tab
var i = 0;
for (var id in tabLinks)
{
tabLinks[id].onclick = showTab;
tabLinks[id].onfocus = function () {
this.blur()
};
if (i == 0)
{
tabLinks[id].className = 'active';
}
i ++;
}
// Hide all content divs except the first
var i = 0;
for (var id in contentDivs)
{
if (i != 0)
{
console.log(contentDivs[id]);
contentDivs[id].className = 'content hide';
}
i ++;
}
}
function showTab()
{
var selectedId = getHash(this.getAttribute('href'));
// Highlight the selected tab, and dim all others.
// Also show the selected content div, and hide all others.
for (var id in contentDivs)
{
if (id == selectedId)
{
tabLinks[id].className = 'active';
contentDivs[id].className = 'content';
}
else
{
tabLinks[id].className = '';
contentDivs[id].className = 'content hide';
}
}
// Stop the browser following the link
return false;
}
function getFirstChildWithTagName(element, tagName)
{
for (var i = 0; i < element.childNodes.length; i ++)
{
if (element.childNodes[i].nodeName == tagName)
{
return element.childNodes[i];
}
}
}
function getHash(url)
{
var hashPos = url.lastIndexOf('#');
return url.substring(hashPos + 1);
}
function toggle(elem)
{
elem = document.getElementById(elem);
if (elem.style && elem.style['display'])
{
// Only works with the "style" attr
var disp = elem.style['display'];
}
else if (elem.currentStyle)
{
// For MSIE, naturally
var disp = elem.currentStyle['display'];
}
else if (window.getComputedStyle)
{
// For most other browsers
var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
}
// Toggle the state of the "display" style
elem.style.display = disp == 'block' ? 'none' : 'block';
return false;
}
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= lang('Errors.badRequest') ?></title>
<style>
div.logo {
height: 200px;
width: 155px;
display: inline-block;
opacity: 0.08;
position: absolute;
top: 2rem;
left: 50%;
margin-left: -73px;
}
body {
height: 100%;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #777;
font-weight: 300;
}
h1 {
font-weight: lighter;
letter-spacing: normal;
font-size: 3rem;
margin-top: 0;
margin-bottom: 0;
color: #222;
}
.wrap {
max-width: 1024px;
margin: 5rem auto;
padding: 2rem;
background: #fff;
text-align: center;
border: 1px solid #efefef;
border-radius: 0.5rem;
position: relative;
}
pre {
white-space: normal;
margin-top: 1.5rem;
}
code {
background: #fafafa;
border: 1px solid #efefef;
padding: 0.5rem 1rem;
border-radius: 5px;
display: block;
}
p {
margin-top: 1.5rem;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #efefef;
padding: 1em 2em 0 2em;
font-size: 85%;
color: #999;
}
a:active,
a:link,
a:visited {
color: #dd4814;
}
</style>
</head>
<body>
<div class="wrap">
<h1>400</h1>
<p>
<?php if (ENVIRONMENT !== 'production') : ?>
<?= nl2br(esc($message)) ?>
<?php else : ?>
<?= lang('Errors.sorryBadRequest') ?>
<?php endif; ?>
</p>
</div>
</body>
</html>
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= lang('Errors.pageNotFound') ?></title>
<style>
div.logo {
height: 200px;
width: 155px;
display: inline-block;
opacity: 0.08;
position: absolute;
top: 2rem;
left: 50%;
margin-left: -73px;
}
body {
height: 100%;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #777;
font-weight: 300;
}
h1 {
font-weight: lighter;
letter-spacing: normal;
font-size: 3rem;
margin-top: 0;
margin-bottom: 0;
color: #222;
}
.wrap {
max-width: 1024px;
margin: 5rem auto;
padding: 2rem;
background: #fff;
text-align: center;
border: 1px solid #efefef;
border-radius: 0.5rem;
position: relative;
}
pre {
white-space: normal;
margin-top: 1.5rem;
}
code {
background: #fafafa;
border: 1px solid #efefef;
padding: 0.5rem 1rem;
border-radius: 5px;
display: block;
}
p {
margin-top: 1.5rem;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #efefef;
padding: 1em 2em 0 2em;
font-size: 85%;
color: #999;
}
a:active,
a:link,
a:visited {
color: #dd4814;
}
</style>
</head>
<body>
<div class="wrap">
<h1>404</h1>
<p>
<?php if (ENVIRONMENT !== 'production') : ?>
<?= nl2br(esc($message)) ?>
<?php else : ?>
<?= lang('Errors.sorryCannotFind') ?>
<?php endif; ?>
</p>
</div>
</body>
</html>
+429
View File
@@ -0,0 +1,429 @@
<?php
use CodeIgniter\HTTP\Header;
use CodeIgniter\CodeIgniter;
$errorId = uniqid('error', true);
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title><?= esc($title) ?></title>
<style>
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
</style>
<script>
<?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
</script>
</head>
<body onload="init()">
<!-- Header -->
<div class="header">
<div class="environment">
Displayed at <?= esc(date('H:i:s')) ?> &mdash;
PHP: <?= esc(PHP_VERSION) ?> &mdash;
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
Environment: <?= ENVIRONMENT ?>
</div>
<div class="container">
<h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
<p>
<?= nl2br(esc($exception->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
</p>
</div>
</div>
<!-- Source -->
<div class="container">
<p><b><?= esc(clean_path($file)) ?></b> at line <b><?= esc($line) ?></b></p>
<?php if (is_file($file)) : ?>
<div class="source">
<?= static::highlightFile($file, $line, 15); ?>
</div>
<?php endif; ?>
</div>
<div class="container">
<?php
$last = $exception;
while ($prevException = $last->getPrevious()) {
$last = $prevException;
?>
<pre>
Caused by:
<?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
<?= nl2br(esc($prevException->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($prevException::class . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
<?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
</pre>
<?php
}
?>
</div>
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) : ?>
<div class="container">
<ul class="tabs" id="tabs">
<li><a href="#backtrace">Backtrace</a></li>
<li><a href="#server">Server</a></li>
<li><a href="#request">Request</a></li>
<li><a href="#response">Response</a></li>
<li><a href="#files">Files</a></li>
<li><a href="#memory">Memory</a></li>
</ul>
<div class="tab-content">
<!-- Backtrace -->
<div class="content" id="backtrace">
<ol class="trace">
<?php foreach ($trace as $index => $row) : ?>
<li>
<p>
<!-- Trace info -->
<?php if (isset($row['file']) && is_file($row['file'])) : ?>
<?php
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) {
echo esc($row['function'] . ' ' . clean_path($row['file']));
} else {
echo esc(clean_path($row['file']) . ' : ' . $row['line']);
}
?>
<?php else: ?>
{PHP internal code}
<?php endif; ?>
<!-- Class/Method -->
<?php if (isset($row['class'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;<?= esc($row['class'] . $row['type'] . $row['function']) ?>
<?php if (! empty($row['args'])) : ?>
<?php $argsId = $errorId . 'args' . $index ?>
( <a href="#" onclick="return toggle('<?= esc($argsId, 'attr') ?>');">arguments</a> )
<div class="args" id="<?= esc($argsId, 'attr') ?>">
<table cellspacing="0">
<?php
$params = null;
// Reflection by name is not available for closure function
if (! str_ends_with($row['function'], '}')) {
$mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
$params = $mirror->getParameters();
}
foreach ($row['args'] as $key => $value) : ?>
<tr>
<td><code><?= esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?></code></td>
<td><pre><?= esc(print_r($value, true)) ?></pre></td>
</tr>
<?php endforeach ?>
</table>
</div>
<?php else : ?>
()
<?php endif; ?>
<?php endif; ?>
<?php if (! isset($row['class']) && isset($row['function'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp; <?= esc($row['function']) ?>()
<?php endif; ?>
</p>
<!-- Source? -->
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
<div class="source">
<?= static::highlightFile($row['file'], $row['line']) ?>
</div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<!-- Server -->
<div class="content" id="server">
<?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<h3>$<?= esc($var) ?></h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<!-- Constants -->
<?php $constants = get_defined_constants(true); ?>
<?php if (! empty($constants['user'])) : ?>
<h3>Constants</h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($constants['user'] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Request -->
<div class="content" id="request">
<?php $request = service('request'); ?>
<table>
<tbody>
<tr>
<td style="width: 10em">Path</td>
<td><?= esc($request->getUri()) ?></td>
</tr>
<tr>
<td>HTTP Method</td>
<td><?= esc($request->getMethod()) ?></td>
</tr>
<tr>
<td>IP Address</td>
<td><?= esc($request->getIPAddress()) ?></td>
</tr>
<tr>
<td style="width: 10em">Is AJAX Request?</td>
<td><?= $request->isAJAX() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is CLI Request?</td>
<td><?= $request->isCLI() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is Secure Request?</td>
<td><?= $request->isSecure() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>User Agent</td>
<td><?= esc($request->getUserAgent()->getAgentString()) ?></td>
</tr>
</tbody>
</table>
<?php $empty = true; ?>
<?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<?php $empty = false; ?>
<h3>$<?= esc($var) ?></h3>
<table style="width: 100%">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<?php if ($empty) : ?>
<div class="alert">
No $_GET, $_POST, or $_COOKIE Information to show.
</div>
<?php endif; ?>
<?php $headers = $request->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($value->getValueLine(), 'html');
} else {
foreach ($value as $i => $header) {
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Response -->
<?php
$response = service('response');
$response->setStatusCode(http_response_code());
?>
<div class="content" id="response">
<table>
<tr>
<td style="width: 15em">Response Status</td>
<td><?= esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?></td>
</tr>
</table>
<?php $headers = $response->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($response->getHeaderLine($name), 'html');
} else {
foreach ($value as $i => $header) {
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Files -->
<div class="content" id="files">
<?php $files = get_included_files(); ?>
<ol>
<?php foreach ($files as $file) :?>
<li><?= esc(clean_path($file)) ?></li>
<?php endforeach ?>
</ol>
</div>
<!-- Memory -->
<div class="content" id="memory">
<table>
<tbody>
<tr>
<td>Memory Usage</td>
<td><?= esc(static::describeMemory(memory_get_usage(true))) ?></td>
</tr>
<tr>
<td style="width: 12em">Peak Memory Usage:</td>
<td><?= esc(static::describeMemory(memory_get_peak_usage(true))) ?></td>
</tr>
<tr>
<td>Memory Limit:</td>
<td><?= esc(ini_get('memory_limit')) ?></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /tab-content -->
</div> <!-- /container -->
<?php endif; ?>
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title><?= lang('Errors.whoops') ?></title>
<style>
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
</style>
</head>
<body>
<div class="container text-center">
<h1 class="headline"><?= lang('Errors.whoops') ?></h1>
<p class="lead"><?= lang('Errors.weHitASnag') ?></p>
</div>
</body>
</html>
+66
View File
@@ -0,0 +1,66 @@
<?php $title = $title ?? 'Sách Giáo Khoa Việt Nam - Toàn bộ SGK các môn, lớp, nhà xuất bản'; ?>
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= esc($title) ?></title>
<meta name="description" content="<?= esc($description ?? 'Tra cứu đầy đủ sách giáo khoa Việt Nam từ lớp 1 đến lớp 12 theo môn học, lớp và nhà xuất bản: Kết nối tri thức, Chân trời sáng tạo, Cánh Diều, Giáo dục Việt Nam... Xem trực tuyến miễn phí.') ?>">
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/app.css">
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="/">
<span class="brand-mark">SGK</span>
<span class="brand-text">
<strong>Sách Giáo Khoa</strong>
<small>sachgiaokhoa.org</small>
</span>
</a>
<form class="search-form" action="/" method="get" role="search">
<input type="search" name="q" placeholder="Tìm sách, môn học, nhà xuất bản..." value="<?= esc($filters['q'] ?? '') ?>" autocomplete="off">
<button type="submit" aria-label="Tìm kiếm">🔍</button>
</form>
<nav class="header-nav">
<a href="/sach">Kho sách</a>
<a href="/kien-thuc-ban-quyen">Bản quyền</a>
<a href="/lien-he">Liên hệ</a>
</nav>
</div>
</header>
<main class="container">
<?= $this->renderSection('content') ?>
</main>
<footer class="site-footer">
<div class="container">
<div class="footer-grid">
<div>
<h4>Sách Giáo Khoa Việt Nam</h4>
<p>Cổng tra cứu sách giáo khoa Việt Nam từ lớp 1 đến lớp 12, đầy đủ các bộ sách: Kết nối tri thức với cuộc sống, Chân trời sáng tạo, Cánh Diều và các nhà xuất bản giáo dục.</p>
</div>
<div>
<h4>Pháp lý</h4>
<a href="/mien-tru-trach-nhiem">Miễn trừ trách nhiệm</a>
<a href="/dieu-khoan-su-dung">Điều khoản sử dụng</a>
<a href="/chinh-sach-bao-mat">Chính sách bảo mật</a>
<a href="/kien-thuc-ban-quyen">Bản quyền &amp; DMCA</a>
</div>
<div>
<h4>Khác</h4>
<a href="/sach">Kho sách</a>
<a href="/lien-he">Liên hệ / Báo cáo vi phạm</a>
</div>
</div>
<div class="footer-legal">
<p><strong>© <?= date('Y') ?> sachgiaokhoa.org</strong> — Website phi thương mại phục vụ mục đích tham khảo. Toàn bộ nội dung sách thuộc bản quyền của các Nhà xuất bản và Bộ Giáo dục &amp; Đào tạo.</p>
</div>
</div>
</footer>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="legal-page">
<h1>Chính sách bảo mật</h1>
<p class="updated">Cập nhật: <?= date('d/m/Y') ?></p>
<h2>1. Dữ liệu chúng tôi thu thập</h2>
<ul>
<li><strong>Dữ liệu phiên (session cookie):</strong> dùng để xác thực captcha và quy trình tải xuống, tự hủy khi bạn đóng trình duyệt;</li>
<li><strong>Thông tin bạn tự cung cấp</strong> khi liên hệ (họ tên, email, nội dung) — chỉ dùng để xử lý yêu cầu của bạn;</li>
<li><strong>Thống kê ẩn danh:</strong> tổng số lượt tải mỗi đầu sách.</li>
</ul>
<h2>2. Chúng tôi KHÔNG</h2>
<ul>
<li>Không bán, cho thuê hay chia sẻ dữ liệu cá nhân của bạn với bên thứ ba;</li>
<li>Không theo dõi bạn qua các website khác;</li>
<li>Không yêu cầu thông tin nhạy cảm (tài khoản ngân hàng, mật khẩu…).</li>
</ul>
<h2>3. Cookie</h2>
<p>Website chỉ sử dụng cookie phiên kỹ thuật (PHP session) để vận hành captcha và chống lạm dụng tải xuống. Không có cookie quảng cáo hay theo dõi bên thứ ba.</p>
<h2>4. Bảo mật dữ liệu</h2>
<p>Dữ liệu được lưu trữ trên máy chủ có kiểm soát truy cập. Tuy nhiên không có phương thức truyền tải nào tuyệt đối an toàn, và chúng tôi không thể cam kết bảo mật tuyệt đối.</p>
<h2>5. Quyền của bạn</h2>
<p>Bạn có thể yêu cầu chúng tôi xóa mọi thông tin cá nhân đã cung cấp qua trang <a href="/lien-he">Liên hệ</a> bất cứ lúc nào.</p>
<h2>6. Dành cho trẻ em</h2>
<p>Website phục vụ học sinh, giáo viên nhưng không hướng tới thu thập thông tin từ trẻ dưới 13 tuổi. Nếu phát hiện thông tin của trẻ được gửi đến, chúng tôi sẽ xóa ngay.</p>
</div>
<?= $this->endSection() ?>
+43
View File
@@ -0,0 +1,43 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="legal-page">
<h1>Liên hệ &amp; Báo cáo vi phạm</h1>
<?php if ($sent): ?>
<div class="alert-box">✅ Cảm ơn bạn! Tin nhắn đã được ghi nhận. Chúng tôi sẽ phản hồi trong thời gian sớm nhất (thường trong 72 giờ với các báo cáo bản quyền).</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error-box"><?= esc($error) ?></div>
<?php endif; ?>
<p>Bạn có thể liên hệ chúng tôi về:</p>
<ul>
<li><strong>Báo cáo vi phạm bản quyền (DMCA):</strong> mô tả rõ tác phẩm và URL vi phạm;</li>
<li><strong>Lỗi kỹ thuật:</strong> liên kết hỏng, không xem/tải được sách;</li>
<li><strong>Đóng góp, góp ý</strong> cho website.</li>
</ul>
<form method="post" action="/lien-he">
<div class="form-group">
<label for="name">Họ và tên *</label>
<input type="text" id="name" name="name" required value="<?= esc(old('name')) ?>">
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required value="<?= esc(old('email')) ?>">
</div>
<div class="form-group">
<label for="message">Nội dung *</label>
<textarea id="message" name="message" rows="6" required><?= esc(old('message')) ?></textarea>
</div>
<div class="honeypot">
<label>Website (để trống)</label>
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
<button class="btn btn-primary" type="submit" name="submit" value="1">Gửi tin nhắn</button>
</form>
</div>
<?= $this->endSection() ?>
+33
View File
@@ -0,0 +1,33 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="legal-page">
<h1>Điều khoản sử dụng</h1>
<p class="updated">Cập nhật: <?= date('d/m/Y') ?></p>
<h2>1. Chấp nhận điều khoản</h2>
<p>Bằng việc truy cập và sử dụng sachgiaokhoa.org, bạn đồng ý bị ràng buộc bởi các điều khoản sử dụng này và <a href="/mien-tru-trach-nhiem">Miễn trừ trách nhiệm</a>. Nếu bạn không đồng ý, vui lòng ngừng sử dụng website.</p>
<h2>2. Phạm vi sử dụng cho phép</h2>
<ul>
<li>Tra cứu, tìm kiếm thông tin về sách giáo khoa Việt Nam;</li>
<li>Xem trực tuyến phục vụ học tập, giảng dạy, nghiên cứu cá nhân;</li>
<li>Tải xuống để lưu trữ, tham khảo cá nhân.</li>
</ul>
<h2>3. Hành vi bị cấm</h2>
<ul>
<li>Sử dụng nội dung cho mục đích thương mại, in ấn, phát hành lại;</li>
<li>Tự động hóa tải hàng loạt (crawler, script) gây quá tải hệ thống;</li>
<li>Chia sẻ lại tệp PDF tràn lan trên các nền tảng khác mà không có sự cho phép của chủ bản quyền;</li>
<li>Gây hại, tấn công hoặc can thiệp vào hoạt động của website.</li>
</ul>
<h2>4. Dịch vụ được cung cấp "nguyên trạng"</h2>
<p>Website được vận hành theo nguyên trạng, không kèm bảo đảm về tính liên tục, không lỗi. Chúng tôi có quyền thay đổi, tạm ngừng hoặc ngừng cung cấp bất kỳ phần nào của website mà không cần báo trước.</p>
<h2>5. Thay đổi điều khoản</h2>
<p>Chúng tôi có thể cập nhật điều khoản này bất cứ lúc nào. Phiên bản mới có hiệu lực ngay khi được đăng trên trang này.</p>
</div>
<?= $this->endSection() ?>
+10
View File
@@ -0,0 +1,10 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="legal-page">
<h1>Không thể hoàn thành yêu cầu</h1>
<div class="error-box"><?= esc($message ?? 'Đã xảy ra lỗi không xác định. Vui lòng thử lại sau.') ?></div>
<p><a class="btn btn-primary" href="/">← Về trang chủ</a></p>
</div>
<?= $this->endSection() ?>
+35
View File
@@ -0,0 +1,35 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="legal-page">
<h1>Bản quyền &amp; Chính sách DMCA</h1>
<p class="updated">Cập nhật: <?= date('d/m/Y') ?></p>
<h2>1. Công nhận quyền tác giả</h2>
<p>sachgiaokhoa.org tôn trọng quyền sở hữu trí tuệ của người khác và cam kết tuân thủ Luật Sở hữu trí tuệ Việt Nam cũng như các điều ước quốc tế liên quan. Mọi nội dung sách giáo khoa được hiển thị trên website thuộc bản quyền của:</p>
<ul>
<li>Nhà xuất bản Giáo dục Việt Nam</li>
<li>Nhà xuất Bản Đại học Sư phạm TP. Hồ Chí Minh</li>
<li>Các nhà xuất bản của bộ sách Kết nối tri thức với cuộc sống, Chân trời sáng tạo, Cánh Diều</li>
<li>Bộ Giáo dục &amp; Đào tạo và các tác giả, chủ biên tương ứng</li>
</ul>
<h2>2. Cách chúng tôi vận hành</h2>
<p>Website chỉ nhúng/liên kết tới nội dung có sẵn từ nguồn bên thứ ba và không tự lưu trữ các tệp sách trên máy chủ của mình. Nội dung được hiển thị phục vụ mục đích tra cứu và tham khảo, không nhằm thay thế việc mua sách chính hãng.</p>
<h2>3. Thông báo khiếu nại bản quyền (DMCA Takedown)</h2>
<p>Nếu bạn là chủ sở hữu bản quyền (hoặc đại diện được ủy quyền) và phát hiện nội dung vi phạm trên sachgiaokhoa.org, vui lòng gửi khiếu nại gồm:</p>
<ul>
<li>Thông tin liên hệ của bạn (họ tên, email, điện thoại);</li>
<li>Tài liệu chứng minh quyền sở hữu với tác phẩm;</li>
<li>Liên kết (URL) chính xác trên sachgiaokhoa.org chứa nội dung vi phạm;</li>
<li>Tuyên bố về thiện chí rằng bạn cho rằng cách sử dụng đó không được chủ sở hữu cho phép;</li>
<li>Cam kết thông tin cung cấp là chính xác, dưới hình phạt về làm giả.</li>
</ul>
<p>Gửi khiếu nại qua trang <a href="/lien-he">Liên hệ</a>. Chúng tôi sẽ phản hồi trong vòng <strong>72 giờ</strong> và gỡ bỏ nội dung vi phạm nếu khiếu nại hợp lệ.</p>
<h2>4. Biện pháp chống lạm dụng</h2>
<p>Để hạn chế lạm dụng, việc tải xuống được kiểm soát bằng captcha, thời gian chờ và liên kết tải một lần có thời hạn. Chúng tôi ghi nhận số lượt tải phục vụ thống kê nội bộ, không phân phối lại dưới bất kỳ hình thức nào.</p>
</div>
<?= $this->endSection() ?>
+25
View File
@@ -0,0 +1,25 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="legal-page">
<h1>Miễn trừ trách nhiệm (Disclaimer)</h1>
<p class="updated">Cập nhật: <?= date('d/m/Y') ?></p>
<h2>1. Về tính chất của website</h2>
<p><strong>sachgiaokhoa.org</strong> là website phi thương mại, được xây dựng nhằm mục đích <em>tra cứu và tham khảo</em> thông tin về sách giáo khoa Việt Nam. Chúng tôi không xuất bản, không in sao, không bán và không trực tiếp lưu trữ toàn bộ nội dung sách trên hệ thống của mình.</p>
<h2>2. Về nội dung hiển thị</h2>
<p>Toàn bộ dữ liệu sách (tiêu đề, tác giả, số trang, bìa, tệp PDF) được lấy từ các nguồn công khai thông qua giao thức API và được hiển thị lại tại chỗ. Mọi quyền tác giả đối với nội dung sách thuộc về các Nhà xuất bản, tác giả, chủ biên và Bộ Giáo dục &amp; Đào tạo.</p>
<p>Chúng tôi không chịu trách nhiệm về tính chính xác, đầy đủ hoặc tính pháp lý của nội dung tài liệu. Trước khi sử dụng vào mục đích chính thức (giảng dạy, học tập, in ấn), vui lòng đối chiếu với ấn bản gốc do nhà xuất bản phát hành.</p>
<h2>3. Không phục vụ mục đích thương mại</h2>
<p>Website không thu phí, không đặt quảng cáo trên nội dung sách và không cho phép người dùng sử dụng tài liệu cho bất kỳ mục đích thương mại nào. Người dùng được khuyến khích <strong>mua sách giấy chính hãng</strong> từ các nhà xuất bản hoặc đơn vị phát hành được ủy quyền.</p>
<h2>4. Giới hạn trách nhiệm</h2>
<p>Chúng tôi không chịu trách nhiệm đối với bất kỳ thiệt hại trực tiếp hay gián tiếp nào phát sinh từ việc sử dụng hoặc lạm dụng nội dung website. Việc sử dụng website đồng nghĩa với việc bạn đồng ý với điều khoản này và <a href="/dieu-khoan-su-dung">Điều khoản sử dụng</a> của chúng tôi.</p>
<h2>5. Báo cáo vi phạm</h2>
<p>Nếu bạn là chủ sở hữu bản quyền và cho rằng nội dung được hiển thị trên website vi phạm quyền của mình, vui lòng gửi thông báo qua trang <a href="/lien-he">Liên hệ</a>. Chúng tôi cam kết xử lý và gỡ bỏ trong thời gian sớm nhất.</p>
</div>
<?= $this->endSection() ?>
File diff suppressed because one or more lines are too long