Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
362
admin.php
@ -1,362 +0,0 @@
|
|||||||
<?php ob_start(); ?>
|
|
||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
require_login();
|
|
||||||
|
|
||||||
// Auto-migrate roles if needed
|
|
||||||
try {
|
|
||||||
db()->query("SELECT terms FROM platform_profile LIMIT 1");
|
|
||||||
} catch (Exception $e) {
|
|
||||||
try { db()->exec("ALTER TABLE `platform_profile` ADD COLUMN `terms` TEXT DEFAULT NULL, ADD COLUMN `privacy_policy` TEXT DEFAULT NULL"); } catch (Exception $e2) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
db()->query('SELECT 1 FROM roles LIMIT 1');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
if (!isset($_GET['migrated'])) {
|
|
||||||
ob_start();
|
|
||||||
require_once __DIR__ . '/db/migrations/migrate_roles.php';
|
|
||||||
ob_end_clean();
|
|
||||||
$url = $_SERVER['REQUEST_URI'] . (strpos($_SERVER['REQUEST_URI'], '?') !== false ? '&' : '?') . 'migrated=1';
|
|
||||||
header('Location: ' . $url);
|
|
||||||
exit;
|
|
||||||
} else {
|
|
||||||
die('Auto-migration failed. Please run /db/migrations/migrate_roles.php manually or contact support.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$page = $_GET['page'] ?? 'dashboard';
|
|
||||||
$action = $_GET['action'] ?? '';
|
|
||||||
|
|
||||||
|
|
||||||
$metrics = subscription_metrics();
|
|
||||||
$recent = fetch_recent_subscriptions();
|
|
||||||
render_head(
|
|
||||||
t('Admin', 'الإدارة') . ' - ' . ucfirst($page),
|
|
||||||
t('Manage your learning platform.', 'إدارة منصة التعلم الخاصة بك.')
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
<nav class="navbar navbar-expand-lg bg-white border-bottom sticky-top admin-navbar" style="z-index: 1040;">
|
|
||||||
<div class="container-fluid px-3 px-md-4">
|
|
||||||
<a class="navbar-brand fw-semibold d-flex align-items-center" href="<?= h(app_url('index.php')) ?>" target="_blank">
|
|
||||||
<?php $prof = get_platform_profile(); if (!empty($prof['logo_path'])): ?>
|
|
||||||
<img src="<?= h(asset_url($prof['logo_path'])) ?>" alt="Logo" style="height: 32px; margin-right: 8px; border-radius: 4px;">
|
|
||||||
<?php endif; ?>
|
|
||||||
<span style="color: var(--accent);"><?= h(app_name()) ?></span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#adminNavbar" aria-controls="adminNavbar" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse justify-content-end" id="adminNavbar">
|
|
||||||
<ul class="navbar-nav mb-2 mb-lg-0 align-items-center gap-3">
|
|
||||||
<li class="nav-item">
|
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
|
||||||
<a class="btn btn-outline-dark <?= current_lang() === 'en' ? 'active' : '' ?>" href="<?= h(page_lang_link('en')) ?>">EN</a>
|
|
||||||
<a class="btn btn-outline-dark <?= current_lang() === 'ar' ? 'active' : '' ?>" href="<?= h(page_lang_link('ar')) ?>">AR</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<div class="bg-light rounded-circle d-flex align-items-center justify-content-center" style="width: 32px; height: 32px; border: 1px solid var(--border);">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
|
|
||||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span class="fw-medium"><?= h(t('Admin', 'المدير')) ?></span>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
|
|
||||||
<?php if (has_permission('profile', 'view')): ?>
|
|
||||||
<li><a class="dropdown-item" href="<?= h(app_url('admin.php', ['page' => 'profile'])) ?>"><?= h(t('Platform Profile', 'ملف المنصة')) ?></a></li>
|
|
||||||
<?php endif; ?>
|
|
||||||
<li><hr class="dropdown-divider"></li>
|
|
||||||
<li><a class="dropdown-item" href="<?= h(app_url('index.php')) ?>"><?= h(t('View Site', 'عرض الموقع')) ?></a></li>
|
|
||||||
<li><a class="dropdown-item text-danger" href="<?= h(app_url('logout.php')) ?>"><?= h(t('Logout', 'تسجيل خروج')) ?></a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="d-flex flex-column flex-md-row" style="min-height: calc(100vh - 61px); overflow: hidden;">
|
|
||||||
<!-- Sidebar -->
|
|
||||||
<aside class="d-flex flex-column flex-shrink-0 p-3 bg-white border-end shadow-sm admin-sidebar">
|
|
||||||
<div class="fs-5 fw-bold mb-3 mt-2 px-2 text-secondary d-none d-md-block"><?= h(t('Admin Menu', 'قائمة الإدارة')) ?></div>
|
|
||||||
<ul class="nav nav-pills flex-row flex-md-column mb-auto gap-2 flex-wrap">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'dashboard'])) ?>" class="nav-link <?= $page === 'dashboard' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'dashboard' ? 'style="background-color: var(--accent); color: white;"' : '' ?>>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-speedometer2" viewBox="0 0 16 16">
|
|
||||||
<path d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4M3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707M2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10m9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5m.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z"/>
|
|
||||||
<path fill-rule="evenodd" d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10m8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Dashboard', 'لوحة القيادة')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'classes'])) ?>" class="nav-link <?= $page === 'classes' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'classes' ? 'style="background-color: var(--accent); color: white;"' : '' ?>>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-diagram-3" viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd" d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Classes', 'الصفوف')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'subjects'])) ?>" class="nav-link <?= $page === 'subjects' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'subjects' ? 'style="background-color: var(--accent); color: white;"' : '' ?>>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-book" viewBox="0 0 16 16">
|
|
||||||
<path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492zM8 1.783C7.015.936 5.587.814 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.814 8.985.936 8 1.783"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Subjects', 'المواد')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'courses'])) ?>" class="nav-link <?= $page === 'courses' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'courses' ? 'style="background-color: var(--accent); color: white;"' : '' ?> >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play-btn" viewBox="0 0 16 16">
|
|
||||||
<path d="M6.79 5.093A.5.5 0 0 0 6 5.5v5a.5.5 0 0 0 .79.407l3.5-2.5a.5.5 0 0 0 0-.814z"/>
|
|
||||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Courses', 'الدورات')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'plans'])) ?>" class="nav-link <?= $page === 'plans' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'plans' ? 'style="background-color: var(--accent); color: white;"' : '' ?>>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tags" viewBox="0 0 16 16">
|
|
||||||
<path d="M3 2v4.586l7 7L14.586 9l-7-7zM2 2a1 1 0 0 1 1-1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 2 6.586z"/>
|
|
||||||
<path d="M5.5 5a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1m0 1a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M1 7.086a1 1 0 0 0 .293.707L8.75 15.25l-.043.043a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 0 7.586V3a1 1 0 0 1 1-1z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Plans', 'الخطط')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'students'])) ?>" class="nav-link <?= $page === 'students' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'students' ? 'style="background-color: var(--accent); color: white;"' : '' ?>>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people" viewBox="0 0 16 16">
|
|
||||||
<path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1zm-7.978-1L7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002-.014.002zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0M6.936 9.28a6 6 0 0 0-1.23-.247A7 7 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.24 2.24 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816M4.92 10A5.5 5.5 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275ZM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0m3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Students', 'الطلاب')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'teachers'])) ?>" class="nav-link <?= $page === 'teachers' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'teachers' ? 'style="background-color: var(--accent); color: white;"' : '' ?>>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-video3" viewBox="0 0 16 16">
|
|
||||||
<path d="M14 9.5a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm-6 5.7c0 .8.8.8.8.8h6.4s.8 0 .8-.8-.8-3.2-4-3.2-4 2.4-4 3.2Z"/>
|
|
||||||
<path d="M2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h5.243c.122-.326.295-.668.526-1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v7.81c.353.23.656.496.91.783C15.965 12.022 16 11.531 16 11V4a2 2 0 0 0-2-2H2Z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Teachers', 'المعلمون')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'assignments'])) ?>" class="nav-link <?= $page === 'assignments' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'assignments' ? 'style="background-color: var(--accent); color: white;"' : '' ?> >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-journal-text" viewBox="0 0 16 16">
|
|
||||||
<path d="M5 10.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
|
|
||||||
<path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"/>
|
|
||||||
<path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Assignments', 'التعيينات')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item w-100 mt-3 border-top pt-3">
|
|
||||||
<a href="#settingsCollapse" data-bs-toggle="collapse" class="nav-link link-dark d-flex align-items-center justify-content-between text-secondary fw-bold px-3 py-2" aria-expanded="<?= in_array($page, ['profile', 'integrations', 'landing', 'users', 'roles']) ? 'true' : 'false' ?>" aria-controls="settingsCollapse" style="font-size: 0.95rem; letter-spacing: 0.5px;">
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-gear" viewBox="0 0 16 16">
|
|
||||||
<path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0"/>
|
|
||||||
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Settings', 'الإعدادات')) ?>
|
|
||||||
</div>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-chevron-down" viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="collapse <?= in_array($page, ['profile', 'integrations', 'landing', 'users', 'roles']) ? 'show' : '' ?>" id="settingsCollapse">
|
|
||||||
<ul class="nav flex-column ms-3 mb-2">
|
|
||||||
<?php if (has_permission('profile', 'view')): ?>
|
|
||||||
<li class="nav-item mb-1 mt-2">
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'profile'])) ?>" class="nav-link <?= $page === 'profile' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'profile' ? 'style="background-color: var(--accent); color: white; border-radius: 6px;"' : '' ?>>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-badge" viewBox="0 0 16 16">
|
|
||||||
<path d="M6.5 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
|
|
||||||
<path d="M4.5 0A2.5 2.5 0 0 0 2 2.5V14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2.5A2.5 2.5 0 0 0 11.5 0zM3 2.5A1.5 1.5 0 0 1 4.5 1h7A1.5 1.5 0 0 1 13 2.5v10.795a4.2 4.2 0 0 0-.776-.492C11.392 12.387 10.063 12 8 12s-3.392.387-4.224.803a4.2 4.2 0 0 0-.776.492z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Platform Profile', 'ملف المنصة')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<?php endif; ?>
|
|
||||||
<li class="nav-item mb-1">
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'integrations'])) ?>" class="nav-link <?= $page === 'integrations' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'integrations' ? 'style="background-color: var(--accent); color: white; border-radius: 6px;"' : '' ?> >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plug" viewBox="0 0 16 16">
|
|
||||||
<path d="M6 0a.5.5 0 0 1 .5.5V3h3V.5a.5.5 0 0 1 1 0V3h1a.5.5 0 0 1 .5.5v3A3.5 3.5 0 0 1 8.5 10c-.002.434-.01.845-.04 1.22-.041.514-.126 1.003-.317 1.424a2.08 2.08 0 0 1-.97 1.028C6.725 13.9 6.169 14 5.5 14c-.998 0-1.61.33-1.974.718A1.92 1.92 0 0 0 3 16h2a.92.92 0 0 1 .328-.718c.364-.388.976-.718 1.974-.718 1.144 0 2.115-.256 2.872-.647a3.09 3.09 0 0 0 1.433-1.52c.28-.58.41-1.21.464-1.879A15 15 0 0 0 12 8.5V4h1a.5.5 0 0 1 .5.5V8a1 1 0 0 0 2 0V4.5A1.5 1.5 0 0 0 14 3h-1V.5a.5.5 0 0 1 1 0V3h.5A1.5 1.5 0 0 1 16 4.5v3A3.5 3.5 0 0 1 12.5 11v1.5a4.1 4.1 0 0 1-1.895 3.42c-.89.576-2.022.883-3.105.883H5.5a4.1 4.1 0 0 1-3.105-.883A4.1 4.1 0 0 1 .5 12.5V11A3.5 3.5 0 0 1 4 7.5v-3A1.5 1.5 0 0 1 5.5 3H6V.5A.5.5 0 0 1 6 0zM5 4v3.5A2.5 2.5 0 0 0 7.5 10h1A2.5 2.5 0 0 0 11 7.5V4H5z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Integrations', 'التكاملات')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item mb-1">
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'landing'])) ?>" class="nav-link <?= $page === 'landing' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'landing' ? 'style="background-color: var(--accent); color: white; border-radius: 6px;"' : '' ?> >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-layout-text-window-reverse" viewBox="0 0 16 16">
|
|
||||||
<path d="M13 6.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 .5-.5m0 3a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 .5-.5m-.5 2.5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1 0-1z"/>
|
|
||||||
<path d="M14 0a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zM2 1a1 1 0 0 0-1 1v1h14V2a1 1 0 0 0-1-1zM1 4v10a1 1 0 0 0 1 1h2V4zm4 0v11h9a1 1 0 0 0 1-1V4z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Landing Page', 'الصفحة الرئيسية')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<?php if (has_permission('users', 'view')): ?>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'users'])) ?>" class="nav-link <?= $page === 'users' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'users' ? 'style="background-color: var(--accent); color: white; border-radius: 6px;"' : '' ?> >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-lock" viewBox="0 0 16 16">
|
|
||||||
<path d="M5.338 1.59a61.44 61.44 0 0 0-2.837.856.481.481 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.725 10.725 0 0 0 2.287 2.233c.346.244.652.42.893.533.12.057.218.095.293.118a.55.55 0 0 0 .101.025.615.615 0 0 0 .1-.025c.076-.023.174-.061.294-.118.24-.113.547-.29.893-.533a10.726 10.726 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.775 11.775 0 0 1-2.517 2.453 7.159 7.159 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7.158 7.158 0 0 1-1.048-.625 11.777 11.777 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 62.456 62.456 0 0 1 5.072.56z"/>
|
|
||||||
<path d="M9.5 6.5a1.5 1.5 0 0 1-1 1.415l.385 1.99a.5.5 0 0 1-.491.595h-.788a.5.5 0 0 1-.49-.595l.384-1.99a1.5 1.5 0 1 1 2-1.415z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Platform Users', 'مستخدمي المنصة')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<?php if (has_permission('roles', 'view')): ?>
|
|
||||||
<li class="nav-item mb-1">
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'roles'])) ?>" class="nav-link <?= $page === 'roles' ? 'active' : 'link-dark' ?> d-flex align-items-center gap-2" <?= $page === 'roles' ? 'style="background-color: var(--accent); color: white; border-radius: 6px;"' : '' ?> >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-lines-fill" viewBox="0 0 16 16">
|
|
||||||
<path d="M6 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-5 6s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zM11 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm.5 2.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4zm2 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2zm0 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2z"/>
|
|
||||||
</svg>
|
|
||||||
<?= h(t('Role Groups', 'مجموعات الصلاحيات')) ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Content Area -->
|
|
||||||
<main class="flex-grow-1 d-flex flex-column" style="height: calc(100vh - 61px); overflow-y: auto;">
|
|
||||||
<div class="p-4 p-md-5 flex-grow-1">
|
|
||||||
<?php if ($page === 'dashboard'): ?>
|
|
||||||
<?php require_permission('dashboard', 'view'); ?>
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Admin', 'الإدارة')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Platform subscription overview', 'نظرة عامة على اشتراكات المنصة')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('This first admin screen focuses on the subscription funnel that powers student access.', 'تركز شاشة الإدارة الأولى هذه على مسار الاشتراك الذي يغذي وصول الطلاب.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row g-3 mb-4">
|
|
||||||
<div class="col-md-4"><div class="metric-card tall"><strong><?= h((string) $metrics['total']) ?></strong><span><?= h(t('total subscriptions', 'إجمالي الاشتراكات')) ?></span></div></div>
|
|
||||||
<div class="col-md-4"><div class="metric-card tall"><strong><?= h((string) $metrics['active']) ?></strong><span><?= h(t('active plans', 'خطط نشطة')) ?></span></div></div>
|
|
||||||
<div class="col-md-4"><div class="metric-card tall"><strong><?= h((string) $metrics['arabic']) ?></strong><span><?= h(t('Arabic preference', 'تفضيل العربية')) ?></span></div></div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
||||||
<h2 class="h5 mb-0"><?= h(t('Latest subscriptions', 'أحدث الاشتراكات')) ?></h2>
|
|
||||||
</div>
|
|
||||||
<?php if (!$recent): ?>
|
|
||||||
<div class="empty-state-inline"><?= h(t('No subscriptions recorded yet.', 'لم يتم تسجيل أي اشتراكات بعد.')) ?></div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th><?= h(t('Student', 'الطالب')) ?></th>
|
|
||||||
<th><?= h(t('Plan', 'الخطة')) ?></th>
|
|
||||||
<th><?= h(t('Language', 'اللغة')) ?></th>
|
|
||||||
<th><?= h(t('Payment', 'الدفع')) ?></th>
|
|
||||||
<th><?= h(t('Detail', 'التفاصيل')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($recent as $item): ?>
|
|
||||||
<?php $plan = get_plan($item['plan_key']) ?? get_plan('plus'); ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h((string) $item['id']) ?></td>
|
|
||||||
<td>
|
|
||||||
<div class="fw-semibold"><?= h($item['full_name']) ?></div>
|
|
||||||
<div class="small text-secondary"><?= h($item['email']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td><?= h(plan_name($plan)) ?></td>
|
|
||||||
<td><?= h($item['preferred_language'] === 'ar' ? 'العربية' : 'English') ?></td>
|
|
||||||
<td><span class="<?= h(status_badge((string) $item['payment_status'])) ?>"><?= h((string) $item['payment_status']) ?></span></td>
|
|
||||||
<td><a class="btn btn-sm btn-outline-dark" href="<?= h(app_url('subscription.php', ['id' => (int) $item['id']])) ?>"><?= h(t('Open', 'فتح')) ?></a></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php elseif ($page === 'profile'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_profile.php'; ?>
|
|
||||||
<?php elseif ($page === 'plans'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_plans.php'; ?>
|
|
||||||
<?php elseif ($page === 'courses'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_courses.php'; ?>
|
|
||||||
<?php elseif ($page === 'classes'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_classes.php'; ?>
|
|
||||||
<?php elseif ($page === 'subjects'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_subjects.php'; ?>
|
|
||||||
<?php elseif ($page === 'users'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_users.php'; ?>
|
|
||||||
<?php elseif ($page === 'students'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_students.php'; ?>
|
|
||||||
<?php elseif ($page === 'teachers'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_teachers.php'; ?>
|
|
||||||
<?php elseif ($page === 'assignments'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_assignments.php'; ?>
|
|
||||||
<?php elseif ($page === 'integrations'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_integrations.php'; ?>
|
|
||||||
<?php elseif ($page === 'landing'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_landing.php'; ?>
|
|
||||||
<?php elseif ($page === 'roles'): ?>
|
|
||||||
<?php require_once __DIR__ . '/admin_roles.php'; ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(ucfirst($page)) ?></h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="empty-state-inline">
|
|
||||||
<?= h(t('This section is currently under construction. More admin features coming soon!', 'هذا القسم قيد الإنشاء حالياً. المزيد من ميزات الإدارة قريباً!')) ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="bg-white border-top py-3 mt-auto w-100">
|
|
||||||
<div class="container-fluid px-4 px-md-5 d-flex justify-content-between align-items-center small text-secondary">
|
|
||||||
<div>
|
|
||||||
© <?= date('Y') ?> <?= h(app_name()) ?>. <?= h(t('All rights reserved.', 'جميع الحقوق محفوظة.')) ?>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a href="<?= h(app_url('index.php')) ?>" class="text-decoration-none text-secondary"><?= h(t('View Site', 'عرض الموقع')) ?></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.admin-sidebar {
|
|
||||||
width: 280px;
|
|
||||||
height: calc(100vh - 61px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.admin-navbar {
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.admin-sidebar {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-right: none !important;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
|
||||||
<script src="<?= h(asset_url('assets/js/main.js')) ?>"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,256 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_assignments.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('assignments', 'view');
|
|
||||||
|
|
||||||
// Initialize table if not exists
|
|
||||||
db()->exec("CREATE TABLE IF NOT EXISTS teacher_assignments (
|
|
||||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
teacher_id INT NOT NULL,
|
|
||||||
class_id INT NOT NULL,
|
|
||||||
subject_id INT NOT NULL DEFAULT 0,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE KEY unique_assignment (teacher_id, class_id, subject_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('assignments', 'delete');
|
|
||||||
$stmt = db()->prepare("DELETE FROM teacher_assignments WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'assignments']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'add') {
|
|
||||||
$teacher_id = (int)($_POST['teacher_id'] ?? 0);
|
|
||||||
$class_id = (int)($_POST['class_id'] ?? 0);
|
|
||||||
$subject_id = (int)($_POST['subject_id'] ?? 0);
|
|
||||||
|
|
||||||
if ($teacher_id > 0 && $class_id > 0) {
|
|
||||||
try {
|
|
||||||
$stmt = db()->prepare("INSERT INTO teacher_assignments (teacher_id, class_id, subject_id) VALUES (?, ?, ?)");
|
|
||||||
$stmt->execute([$teacher_id, $class_id, $subject_id]);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
// Ignore duplicate key error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'assignments']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all for dropdowns
|
|
||||||
$teachers = db()->query("SELECT id, name FROM teachers ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
$classes = db()->query("SELECT id, name_en, name_ar FROM classes ORDER BY name_en")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
$subjects = db()->query("SELECT id, title_en, title_ar, class_id FROM subjects ORDER BY title_en")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Fetch assignments for table
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where = "";
|
|
||||||
$params = [];
|
|
||||||
if ($search !== '') {
|
|
||||||
$where = "WHERE t.name LIKE ? OR c.name_en LIKE ? OR s.title_en LIKE ?";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$count_sql = "
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM teacher_assignments ta
|
|
||||||
JOIN teachers t ON ta.teacher_id = t.id
|
|
||||||
JOIN classes c ON ta.class_id = c.id
|
|
||||||
LEFT JOIN subjects s ON ta.subject_id = s.id
|
|
||||||
$where
|
|
||||||
";
|
|
||||||
$total_stmt = db()->prepare($count_sql);
|
|
||||||
$total_stmt->execute($params);
|
|
||||||
$total = $total_stmt->fetchColumn();
|
|
||||||
$pages = ceil($total / $limit);
|
|
||||||
|
|
||||||
$sql = "
|
|
||||||
SELECT ta.id, t.name as teacher_name, c.name_en as class_name_en, c.name_ar as class_name_ar, s.title_en as subject_title_en, s.title_ar as subject_title_ar, ta.subject_id
|
|
||||||
FROM teacher_assignments ta
|
|
||||||
JOIN teachers t ON ta.teacher_id = t.id
|
|
||||||
JOIN classes c ON ta.class_id = c.id
|
|
||||||
LEFT JOIN subjects s ON ta.subject_id = s.id
|
|
||||||
$where
|
|
||||||
ORDER BY ta.id DESC
|
|
||||||
LIMIT $limit OFFSET $offset
|
|
||||||
";
|
|
||||||
$stmt = db()->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Teacher Assignments', 'تعيينات المعلمين')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<?php if (has_permission('assignments', 'add')): ?>
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addAssignmentModal" style="background-color: var(--accent); border-color: var(--accent);">+ <?= h(t('Add Assignment', 'إضافة تعيين')) ?></button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="get" class="d-flex gap-2 align-items-center">
|
|
||||||
<input type="hidden" name="page" value="assignments">
|
|
||||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
|
||||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
|
||||||
<?php if ($search): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page'=>'assignments'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= h(t('Teacher', 'المعلم')) ?></th>
|
|
||||||
<th><?= h(t('Class', 'الصف')) ?></th>
|
|
||||||
<th><?= h(t('Subject', 'المادة')) ?></th>
|
|
||||||
<th><?= h(t('Actions', 'إجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php if(empty($items)): ?>
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="text-center text-muted py-4"><?= h(t('No assignments found.', 'لا توجد تعيينات.')) ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php foreach($items as $row): ?>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fw-semibold"><?= h($row['teacher_name']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="text-secondary"><?= h(current_lang() === 'ar' ? $row['class_name_ar'] : $row['class_name_en']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="text-secondary">
|
|
||||||
<?php if ($row['subject_id'] > 0): ?>
|
|
||||||
<?= h(current_lang() === 'ar' ? $row['subject_title_ar'] : $row['subject_title_en']) ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary"><?= h(t('All Subjects', 'كل المواد')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'assignments'])) ?>" style="display:inline;" onsubmit="return confirm('<?= h(t('Are you sure you want to remove this assignment?', 'هل أنت متأكد من إزالة هذا التعيين؟')) ?>');">
|
|
||||||
<input type="hidden" name="action" value="delete">
|
|
||||||
<input type="hidden" name="id" value="<?= h((string)$row['id']) ?>">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
|
||||||
<?= h(t('Remove', 'إزالة')) ?>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination pagination-sm">
|
|
||||||
<?php for ($i = 1; $i <= $pages; $i++): ?>
|
|
||||||
<li class="page-item <?= $i === $page_num ? 'active' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'assignments', 'p'=>$i, 'search'=>$search])) ?>">
|
|
||||||
<?= $i ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add Assignment Modal -->
|
|
||||||
<div class="modal fade" id="addAssignmentModal" tabindex="-1" aria-labelledby="addAssignmentModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title text-white" id="addAssignmentModalLabel"><?= h(t('Add Assignment', 'إضافة تعيين')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'assignments'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="add">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Teacher', 'المعلم')) ?></label>
|
|
||||||
<select name="teacher_id" class="form-select" required>
|
|
||||||
<option value=""><?= h(t('Select a teacher...', 'اختر معلماً...')) ?></option>
|
|
||||||
<?php foreach($teachers as $t): ?>
|
|
||||||
<option value="<?= h((string)$t['id']) ?>"><?= h($t['name']) ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Class', 'الصف')) ?></label>
|
|
||||||
<select name="class_id" id="classSelect" class="form-select" required onchange="filterSubjects()">
|
|
||||||
<option value=""><?= h(t('Select a class...', 'اختر صفاً...')) ?></option>
|
|
||||||
<?php foreach($classes as $c): ?>
|
|
||||||
<option value="<?= h((string)$c['id']) ?>"><?= h(current_lang() === 'ar' ? $c['name_ar'] : $c['name_en']) ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label"><?= h(t('Subject (Optional)', 'المادة (اختياري)')) ?></label>
|
|
||||||
<select name="subject_id" id="subjectSelect" class="form-select">
|
|
||||||
<option value="0" class="default-opt"><?= h(t('All Subjects / Not Specified', 'كل المواد / غير محدد')) ?></option>
|
|
||||||
<?php foreach($subjects as $s): ?>
|
|
||||||
<option value="<?= h((string)$s['id']) ?>" data-class-id="<?= h((string)$s['class_id']) ?>" style="display:none;">
|
|
||||||
<?= h(current_lang() === 'ar' ? $s['title_ar'] : $s['title_en']) ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
<small class="text-muted d-block mt-1"><?= h(t('Select a class first to view its specific subjects.', 'اختر الصف أولاً لرؤية مواده المحددة.')) ?></small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2">
|
|
||||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function filterSubjects() {
|
|
||||||
var classId = document.getElementById('classSelect').value;
|
|
||||||
var subjectSelect = document.getElementById('subjectSelect');
|
|
||||||
var options = subjectSelect.getElementsByTagName('option');
|
|
||||||
|
|
||||||
// reset selection to default
|
|
||||||
subjectSelect.value = "0";
|
|
||||||
|
|
||||||
for (var i = 0; i < options.length; i++) {
|
|
||||||
var opt = options[i];
|
|
||||||
if (opt.classList.contains('default-opt')) continue;
|
|
||||||
|
|
||||||
if (opt.getAttribute('data-class-id') === classId && classId !== "") {
|
|
||||||
opt.style.display = '';
|
|
||||||
} else {
|
|
||||||
opt.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,241 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_classes.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('classes', 'view');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
// If modal is submitted, action is typically passed as a POST variable.
|
|
||||||
// However, delete still uses the URL action. Let's support both.
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('classes', 'delete');
|
|
||||||
$stmt = db()->prepare("DELETE FROM classes WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'classes']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('classes', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$name_en = $_POST['name_en'] ?? '';
|
|
||||||
$name_ar = $_POST['name_ar'] ?? '';
|
|
||||||
$desc_en = $_POST['description_en'] ?? '';
|
|
||||||
$desc_ar = $_POST['description_ar'] ?? '';
|
|
||||||
$status = $_POST['status'] ?? 'active';
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE classes SET name_en=?, name_ar=?, description_en=?, description_ar=?, status=? WHERE id=?");
|
|
||||||
$stmt->execute([$name_en, $name_ar, $desc_en, $desc_ar, $status, $post_id]);
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("INSERT INTO classes (name_en, name_ar, description_en, description_ar, status) VALUES (?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$name_en, $name_ar, $desc_en, $desc_ar, $status]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'classes']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list view
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where = "";
|
|
||||||
$params = [];
|
|
||||||
if ($search !== '') {
|
|
||||||
$where = "WHERE name_en LIKE ? OR name_ar LIKE ?";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$total_stmt = db()->prepare("SELECT COUNT(*) FROM classes $where");
|
|
||||||
$total_stmt->execute($params);
|
|
||||||
$total = $total_stmt->fetchColumn();
|
|
||||||
$pages = ceil($total / $limit);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT * FROM classes $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
|
|
||||||
$stmt->execute($params);
|
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Classes', 'الصفوف')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<?php if (has_permission('classes', 'add')): ?>
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addClassModal" style="background-color: var(--accent); border-color: var(--accent);">+ <?= h(t('Add Class', 'إضافة صف')) ?></button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="get" class="d-flex gap-2 align-items-center">
|
|
||||||
<input type="hidden" name="page" value="classes">
|
|
||||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
|
||||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
|
||||||
<?php if ($search): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page'=>'classes'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th><?= h(t('Name', 'الاسم')) ?></th>
|
|
||||||
<th><?= h(t('Status', 'الحالة')) ?></th>
|
|
||||||
<th><?= h(t('Description', 'الوصف')) ?></th>
|
|
||||||
<th><?= h(t('Actions', 'إجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($items as $row): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h((string)$row['id']) ?></td>
|
|
||||||
<td>
|
|
||||||
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $row['name_ar'] : $row['name_en']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (($row['status'] ?? 'active') === 'active'): ?>
|
|
||||||
<span class="badge bg-success"><?= h(t('Active', 'نشط')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary"><?= h(t('Inactive', 'غير نشط')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td class="text-truncate" style="max-width: 200px;">
|
|
||||||
<?= h(current_lang() === 'ar' ? $row['description_ar'] : $row['description_en']) ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (has_permission('classes', 'edit')): ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editClassModal<?= $row['id'] ?>" title="<?= h(t('Edit', 'تعديل')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (has_permission('classes', 'delete')): ?>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'classes', 'action'=>'delete', 'id'=>$row['id']])) ?>" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>');">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="<?= h(t('Delete', 'حذف')) ?>"><svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg></button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Edit Modal for Row <?= $row['id'] ?> -->
|
|
||||||
<div class="modal fade" id="editClassModal<?= $row['id'] ?>" tabindex="-1" aria-labelledby="editClassModalLabel<?= $row['id'] ?>" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="editClassModalLabel<?= $row['id'] ?>"><?= h(t('Edit Class', 'تعديل الصف')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'classes'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="edit">
|
|
||||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active" <?= ($row['status'] ?? 'active') === 'active' ? 'selected' : '' ?>><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive" <?= ($row['status'] ?? 'active') === 'inactive' ? 'selected' : '' ?>><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (EN)', 'الاسم (إنجليزي)')) ?></label>
|
|
||||||
<input type="text" name="name_en" class="form-control" value="<?= h($row['name_en']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (AR)', 'الاسم (عربي)')) ?></label>
|
|
||||||
<input type="text" name="name_ar" class="form-control" value="<?= h($row['name_ar']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (EN)', 'الوصف (إنجليزي)')) ?></label>
|
|
||||||
<textarea name="description_en" class="form-control"><?= h($row['description_en']) ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (AR)', 'الوصف (عربي)')) ?></label>
|
|
||||||
<textarea name="description_ar" class="form-control"><?= h($row['description_ar']) ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Edit Modal -->
|
|
||||||
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(!$items): ?>
|
|
||||||
<tr><td colspan="4" class="text-center text-secondary py-3"><?= h(t('No classes found.', 'لا توجد صفوف.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<?php for($i=1; $i<=$pages; $i++): ?>
|
|
||||||
<li class="page-item <?= $i === $page_num ? 'active' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'classes', 'p'=>$i, 'search'=>$search])) ?>"><?= $i ?></a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add Class Modal -->
|
|
||||||
<div class="modal fade" id="addClassModal" tabindex="-1" aria-labelledby="addClassModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="addClassModalLabel"><?= h(t('Add Class', 'إضافة صف')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'classes'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="add">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active"><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive"><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (EN)', 'الاسم (إنجليزي)')) ?></label>
|
|
||||||
<input type="text" name="name_en" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (AR)', 'الاسم (عربي)')) ?></label>
|
|
||||||
<input type="text" name="name_ar" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (EN)', 'الوصف (إنجليزي)')) ?></label>
|
|
||||||
<textarea name="description_en" class="form-control"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (AR)', 'الوصف (عربي)')) ?></label>
|
|
||||||
<textarea name="description_ar" class="form-control"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Add Modal -->
|
|
||||||
@ -1,439 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_courses.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('courses', 'view');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('courses', 'delete');
|
|
||||||
$stmt = db()->prepare("DELETE FROM courses WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'courses']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'close_all_registration') {
|
|
||||||
db()->query("UPDATE courses SET registration_open = 0");
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'courses']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if ($post_action === 'open_all_registration') {
|
|
||||||
db()->query("UPDATE courses SET registration_open = 1");
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'courses']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if ($post_action === 'reset_all_students') {
|
|
||||||
db()->query("TRUNCATE TABLE course_students");
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'courses']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('courses', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$name_en = $_POST['name_en'] ?? '';
|
|
||||||
$name_ar = $_POST['name_ar'] ?? '';
|
|
||||||
$desc_en = $_POST['description_en'] ?? '';
|
|
||||||
$desc_ar = $_POST['description_ar'] ?? '';
|
|
||||||
$status = $_POST['status'] ?? 'active';
|
|
||||||
$price = (float)($_POST['price'] ?? 0);
|
|
||||||
$max_students = (isset($_POST['max_students']) && $_POST['max_students'] !== '') ? (int)$_POST['max_students'] : null;
|
|
||||||
$registration_open = isset($_POST['registration_open']) ? 1 : 0;
|
|
||||||
|
|
||||||
$picture = null;
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("SELECT picture FROM courses WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
$picture = $stmt->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($_FILES['picture']) && $_FILES['picture']['error'] === UPLOAD_ERR_OK) {
|
|
||||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0775, true);
|
|
||||||
}
|
|
||||||
$filename = time() . '_' . basename($_FILES['picture']['name']);
|
|
||||||
$target_file = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['picture']['tmp_name'], $target_file)) {
|
|
||||||
$picture = 'assets/images/uploads/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE courses SET name_en=?, name_ar=?, description_en=?, description_ar=?, status=?, price=?, picture=?, max_students=?, registration_open=? WHERE id=?");
|
|
||||||
$stmt->execute([$name_en, $name_ar, $desc_en, $desc_ar, $status, $price, $picture, $max_students, $registration_open, $post_id]);
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("INSERT INTO courses (name_en, name_ar, description_en, description_ar, status, price, picture, max_students, registration_open) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$name_en, $name_ar, $desc_en, $desc_ar, $status, $price, $picture, $max_students, $registration_open]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'courses']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'manage_students') {
|
|
||||||
$course_id = (int)$_POST['course_id'];
|
|
||||||
$student_ids = $_POST['student_ids'] ?? [];
|
|
||||||
db()->prepare("DELETE FROM course_students WHERE course_id=?")->execute([$course_id]);
|
|
||||||
$insert_stmt = db()->prepare("INSERT INTO course_students (course_id, student_id) VALUES (?, ?)");
|
|
||||||
foreach ($student_ids as $sid) {
|
|
||||||
$insert_stmt->execute([$course_id, (int)$sid]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'courses']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all students for assignment mapping
|
|
||||||
$all_students_stmt = db()->query("SELECT id, full_name, email FROM student_subscriptions ORDER BY full_name ASC");
|
|
||||||
$all_students = $all_students_stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$course_students_stmt = db()->query("SELECT course_id, student_id FROM course_students");
|
|
||||||
$all_course_students = $course_students_stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
$course_students_map = [];
|
|
||||||
foreach ($all_course_students as $cs) {
|
|
||||||
$course_students_map[$cs['course_id']][] = $cs['student_id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// list view
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where = "";
|
|
||||||
$params = [];
|
|
||||||
if ($search !== '') {
|
|
||||||
$where = "WHERE name_en LIKE ? OR name_ar LIKE ?";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$total_stmt = db()->prepare("SELECT COUNT(*) FROM courses $where");
|
|
||||||
$total_stmt->execute($params);
|
|
||||||
$total = $total_stmt->fetchColumn();
|
|
||||||
$pages = ceil($total / $limit);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT * FROM courses $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
|
|
||||||
$stmt->execute($params);
|
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Courses', 'الدورات')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-wrap gap-2 justify-content-end">
|
|
||||||
<?php if (has_permission('courses', 'delete')): ?>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'courses'])) ?>" class="m-0" onsubmit="return confirm('<?= h(t('Close registration for all courses?', 'هل أنت متأكد من إغلاق التسجيل لجميع الدورات؟')) ?>');">
|
|
||||||
<input type="hidden" name="action" value="close_all_registration">
|
|
||||||
<button type="submit" class="btn btn-outline-warning btn-sm"><?= h(t('Close All', 'إغلاق الكل')) ?></button>
|
|
||||||
</form>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'courses'])) ?>" class="m-0" onsubmit="return confirm('<?= h(t('Open registration for all courses?', 'هل أنت متأكد من فتح التسجيل لجميع الدورات؟')) ?>');">
|
|
||||||
<input type="hidden" name="action" value="open_all_registration">
|
|
||||||
<button type="submit" class="btn btn-outline-success btn-sm"><?= h(t('Open All', 'فتح الكل')) ?></button>
|
|
||||||
</form>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'courses'])) ?>" class="m-0" onsubmit="return confirm('<?= h(t('Are you absolutely sure you want to remove ALL students from ALL courses to start a new batch?', 'هل أنت متأكد تماماً أنك تريد إزالة جميع الطلاب من جميع الدورات لبدء دفعة جديدة؟')) ?>');">
|
|
||||||
<input type="hidden" name="action" value="reset_all_students">
|
|
||||||
<button type="submit" class="btn btn-outline-danger btn-sm"><?= h(t('Reset Batch', 'إعادة ضبط الدفعة')) ?></button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addCourseModal" style="background-color: var(--accent); border-color: var(--accent);">+ <?= h(t('Add Course', 'إضافة دورة')) ?></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="get" class="d-flex gap-2 align-items-center">
|
|
||||||
<input type="hidden" name="page" value="courses">
|
|
||||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
|
||||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
|
||||||
<?php if ($search): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page'=>'courses'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th><?= h(t('Name', 'الاسم')) ?></th>
|
|
||||||
<th><?= h(t('Price', 'السعر')) ?></th>
|
|
||||||
<th><?= h(t('Status', 'الحالة')) ?></th>
|
|
||||||
<th><?= h(t('Assigned', 'مخصص')) ?></th>
|
|
||||||
<th><?= h(t('Reg.', 'التسجيل')) ?></th>
|
|
||||||
<th><?= h(t('Description', 'الوصف')) ?></th>
|
|
||||||
<th class="text-end"><?= h(t('Actions', 'إجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($items as $row):
|
|
||||||
$assigned_count = count($course_students_map[$row['id']] ?? []);
|
|
||||||
?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h((string)$row['id']) ?></td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
|
||||||
<?php if(!empty($row['picture'])): ?>
|
|
||||||
<img src="<?= h($row['picture']) ?>" alt="" class="rounded" style="width: 40px; height: 40px; object-fit: cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="rounded bg-secondary text-white d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
|
||||||
<svg width="20" height="20" fill="currentColor" viewBox="0 0 16 16"><path d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm5.5 1.5v2a1 1 0 0 0 1 1h2l-3-3z"/></svg>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $row['name_ar'] : $row['name_en']) ?></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge bg-light text-dark border"><?= h(format_price((float)($row['price'] ?? 0))) ?></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (($row['status'] ?? 'active') === 'active'): ?>
|
|
||||||
<span class="badge bg-success"><?= h(t('Active', 'نشط')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary"><?= h(t('Inactive', 'غير نشط')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge bg-info text-dark"><?= $assigned_count ?></span>
|
|
||||||
<?php if ($row['max_students']): ?>
|
|
||||||
<span class="badge bg-secondary">/ <?= h($row['max_students']) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if ($row['registration_open']): ?>
|
|
||||||
<span class="badge bg-success"><?= h(t('Open', 'مفتوح')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-danger"><?= h(t('Closed', 'مغلق')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td class="text-truncate" style="max-width: 200px;">
|
|
||||||
<?= h(current_lang() === 'ar' ? $row['description_ar'] : $row['description_en']) ?>
|
|
||||||
</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#manageStudentsModal<?= $row['id'] ?>" title="<?= h(t('Manage Students', 'إدارة الطلاب')) ?>">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people" viewBox="0 0 16 16">
|
|
||||||
<path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1zm-7.978-1L7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002-.014.002zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0M6.936 9.28a6 6 0 0 0-1.23-.247A7 7 0 0 0 5 9c-4 0-5 3-5 4q0 1 1 1h4.216A2.24 2.24 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816M4.92 10A5.5 5.5 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275ZM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0m3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<?php if (has_permission('courses', 'edit')): ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editCourseModal<?= $row['id'] ?>" title="<?= h(t('Edit', 'تعديل')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (has_permission('courses', 'delete')): ?>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'courses', 'action'=>'delete', 'id'=>$row['id']])) ?>" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>');">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="<?= h(t('Delete', 'حذف')) ?>"><svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg></button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Manage Students Modal for Row <?= $row['id'] ?> -->
|
|
||||||
<div class="modal fade" id="manageStudentsModal<?= $row['id'] ?>" tabindex="-1" aria-labelledby="manageStudentsModalLabel<?= $row['id'] ?>" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="manageStudentsModalLabel<?= $row['id'] ?>">
|
|
||||||
<?= h(t('Manage Students', 'إدارة الطلاب')) ?> - <?= h(current_lang() === 'ar' ? $row['name_ar'] : $row['name_en']) ?>
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'courses'])) ?>" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="action" value="manage_students">
|
|
||||||
<input type="hidden" name="course_id" value="<?= $row['id'] ?>">
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Select Students', 'اختر الطلاب')) ?></label>
|
|
||||||
<div class="list-group">
|
|
||||||
<?php
|
|
||||||
$assigned_to_this = $course_students_map[$row['id']] ?? [];
|
|
||||||
foreach ($all_students as $student):
|
|
||||||
$is_assigned = in_array($student['id'], $assigned_to_this);
|
|
||||||
?>
|
|
||||||
<label class="list-group-item d-flex gap-2 align-items-center">
|
|
||||||
<input class="form-check-input flex-shrink-0" type="checkbox" name="student_ids[]" value="<?= h($student['id']) ?>" <?= $is_assigned ? 'checked' : '' ?>>
|
|
||||||
<span>
|
|
||||||
<?= h($student['full_name']) ?>
|
|
||||||
<small class="d-block text-muted"><?= h($student['email']) ?></small>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(empty($all_students)): ?>
|
|
||||||
<div class="text-muted"><?= h(t('No students available.', 'لا يوجد طلاب متاحين.')) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Assignments', 'حفظ التعيينات')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Edit Modal for Row <?= $row['id'] ?> -->
|
|
||||||
<div class="modal fade" id="editCourseModal<?= $row['id'] ?>" tabindex="-1" aria-labelledby="editCourseModalLabel<?= $row['id'] ?>" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="editCourseModalLabel<?= $row['id'] ?>"><?= h(t('Edit Course', 'تعديل الدورة')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'courses'])) ?>" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="action" value="edit">
|
|
||||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (EN)', 'الاسم (إنجليزي)')) ?></label>
|
|
||||||
<input type="text" name="name_en" class="form-control" value="<?= h($row['name_en']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (AR)', 'الاسم (عربي)')) ?></label>
|
|
||||||
<input type="text" name="name_ar" class="form-control" value="<?= h($row['name_ar']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (EN)', 'الوصف (إنجليزي)')) ?></label>
|
|
||||||
<textarea name="description_en" class="form-control"><?= h($row['description_en']) ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (AR)', 'الوصف (عربي)')) ?></label>
|
|
||||||
<textarea name="description_ar" class="form-control"><?= h($row['description_ar']) ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Price', 'السعر')) ?></label>
|
|
||||||
<input type="number" step="0.001" name="price" class="form-control" value="<?= h($row['price'] ?? 0) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Max Students', 'الحد الأقصى للطلاب')) ?></label>
|
|
||||||
<input type="number" name="max_students" class="form-control" placeholder="<?= h(t('Empty = unlimited', 'فارغ = غير محدود')) ?>" value="<?= h($row['max_students']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Picture', 'صورة')) ?></label>
|
|
||||||
<input type="file" name="picture" class="form-control" accept="image/*">
|
|
||||||
<?php if(!empty($row['picture'])): ?>
|
|
||||||
<div class="mt-2"><img src="<?= h($row['picture']) ?>" alt="Course Image" class="img-thumbnail" style="max-height: 50px;"></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active" <?= ($row['status'] ?? 'active') === 'active' ? 'selected' : '' ?>><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive" <?= ($row['status'] ?? 'active') === 'inactive' ? 'selected' : '' ?>><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" name="registration_open" id="regSwitch<?= $row['id'] ?>" value="1" <?= $row['registration_open'] ? 'checked' : '' ?>/>
|
|
||||||
<label class="form-check-label" for="regSwitch<?= $row['id'] ?>"><?= h(t('Registration Open', 'التسجيل مفتوح')) ?></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Edit Modal -->
|
|
||||||
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(!$items): ?>
|
|
||||||
<tr><td colspan="8" class="text-center text-secondary py-3"><?= h(t('No courses found.', 'لا توجد دورات.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<?php for($i=1; $i<=$pages; $i++): ?>
|
|
||||||
<li class="page-item <?= $i === $page_num ? 'active' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'courses', 'p'=>$i, 'search'=>$search])) ?>"><?= $i ?></a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add Course Modal -->
|
|
||||||
<div class="modal fade" id="addCourseModal" tabindex="-1" aria-labelledby="addCourseModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="addCourseModalLabel"><?= h(t('Add Course', 'إضافة دورة')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'courses'])) ?>" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="action" value="add">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (EN)', 'الاسم (إنجليزي)')) ?></label>
|
|
||||||
<input type="text" name="name_en" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (AR)', 'الاسم (عربي)')) ?></label>
|
|
||||||
<input type="text" name="name_ar" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (EN)', 'الوصف (إنجليزي)')) ?></label>
|
|
||||||
<textarea name="description_en" class="form-control"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (AR)', 'الوصف (عربي)')) ?></label>
|
|
||||||
<textarea name="description_ar" class="form-control"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Price', 'السعر')) ?></label>
|
|
||||||
<input type="number" step="0.001" name="price" class="form-control" value="0.000">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Max Students', 'الحد الأقصى للطلاب')) ?></label>
|
|
||||||
<input type="number" name="max_students" class="form-control" placeholder="<?= h(t('Empty = unlimited', 'فارغ = غير محدود')) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Picture', 'صورة')) ?></label>
|
|
||||||
<input type="file" name="picture" class="form-control" accept="image/*">
|
|
||||||
</div>
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active"><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive"><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" name="registration_open" id="regSwitchAdd" value="1" checked>
|
|
||||||
<label class="form-check-label" for="regSwitchAdd"><?= h(t('Registration Open', 'التسجيل مفتوح')) ?></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Add Modal -->
|
|
||||||
@ -1,256 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_integrations.php
|
|
||||||
if (!isset($pdo)) {
|
|
||||||
$pdo = db();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
||||||
if ($_POST['action'] === 'save_wablas') {
|
|
||||||
$domain = $_POST['wablas_domain'] ?? '';
|
|
||||||
$token = $_POST['wablas_token'] ?? '';
|
|
||||||
$security_key = $_POST['wablas_security_key'] ?? '';
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE platform_profile SET wablas_domain = :domain, wablas_token = :token, wablas_security_key = :security_key WHERE id = 1");
|
|
||||||
$stmt->execute([
|
|
||||||
'domain' => $domain,
|
|
||||||
'token' => $token,
|
|
||||||
'security_key' => $security_key
|
|
||||||
]);
|
|
||||||
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'integrations', 'tab' => 'wablas', 'saved' => 1]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_POST['action'] === 'save_thawani') {
|
|
||||||
$secret = $_POST['thawani_secret_key'] ?? '';
|
|
||||||
$publishable = $_POST['thawani_publishable_key'] ?? '';
|
|
||||||
$mode = $_POST['thawani_mode'] ?? 'test';
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE platform_profile SET thawani_secret_key = :secret, thawani_publishable_key = :publishable, thawani_mode = :mode WHERE id = 1");
|
|
||||||
$stmt->execute([
|
|
||||||
'secret' => $secret,
|
|
||||||
'publishable' => $publishable,
|
|
||||||
'mode' => $mode
|
|
||||||
]);
|
|
||||||
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'integrations', 'tab' => 'thawani', 'saved' => 1]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_POST['action'] === 'save_smtp') {
|
|
||||||
$smtp_host = $_POST['smtp_host'] ?? '';
|
|
||||||
$smtp_port = $_POST['smtp_port'] ?? '';
|
|
||||||
$smtp_secure = $_POST['smtp_secure'] ?? '';
|
|
||||||
$smtp_user = $_POST['smtp_user'] ?? '';
|
|
||||||
$smtp_pass = $_POST['smtp_pass'] ?? '';
|
|
||||||
$smtp_from_email = $_POST['smtp_from_email'] ?? '';
|
|
||||||
$smtp_from_name = $_POST['smtp_from_name'] ?? '';
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE platform_profile SET
|
|
||||||
smtp_host = :smtp_host,
|
|
||||||
smtp_port = :smtp_port,
|
|
||||||
smtp_secure = :smtp_secure,
|
|
||||||
smtp_user = :smtp_user,
|
|
||||||
smtp_pass = :smtp_pass,
|
|
||||||
smtp_from_email = :smtp_from_email,
|
|
||||||
smtp_from_name = :smtp_from_name
|
|
||||||
WHERE id = 1");
|
|
||||||
|
|
||||||
$stmt->execute([
|
|
||||||
'smtp_host' => $smtp_host,
|
|
||||||
'smtp_port' => $smtp_port,
|
|
||||||
'smtp_secure' => $smtp_secure,
|
|
||||||
'smtp_user' => $smtp_user,
|
|
||||||
'smtp_pass' => $smtp_pass,
|
|
||||||
'smtp_from_email' => $smtp_from_email,
|
|
||||||
'smtp_from_name' => $smtp_from_name
|
|
||||||
]);
|
|
||||||
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'integrations', 'tab' => 'smtp', 'saved' => 1]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT * FROM platform_profile WHERE id = 1");
|
|
||||||
$prof = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
||||||
$thawani_mode = $prof['thawani_mode'] ?? 'test';
|
|
||||||
$smtp_secure = $prof['smtp_secure'] ?? 'tls';
|
|
||||||
|
|
||||||
$active_tab = $_GET['tab'] ?? 'wablas';
|
|
||||||
?>
|
|
||||||
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Settings', 'الإعدادات')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('All Integrations', 'كل التكاملات')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Manage your gateway and email configurations here.', 'قم بإدارة إعدادات البوابات والبريد الإلكتروني هنا.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (!empty($_GET['saved'])): ?>
|
|
||||||
<div class="alert alert-success"><?= h(t('Settings updated successfully.', 'تم تحديث الإعدادات بنجاح.')) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="panel-card p-0 mb-4">
|
|
||||||
<div class="card-header bg-white border-bottom pt-3 pb-0 px-4">
|
|
||||||
<ul class="nav nav-tabs border-bottom-0" id="integrationsTabs" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link <?= $active_tab === 'wablas' ? 'active text-primary fw-bold' : 'text-secondary' ?>" id="wablas-tab" data-bs-toggle="tab" data-bs-target="#wablas-pane" type="button" role="tab" style="<?= $active_tab === 'wablas' ? 'border-bottom: 2px solid var(--accent); color: var(--accent) !important;' : '' ?>">
|
|
||||||
<?= h(t('Wablas', 'Wablas')) ?>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link <?= $active_tab === 'thawani' ? 'active text-primary fw-bold' : 'text-secondary' ?>" id="thawani-tab" data-bs-toggle="tab" data-bs-target="#thawani-pane" type="button" role="tab" style="<?= $active_tab === 'thawani' ? 'border-bottom: 2px solid var(--accent); color: var(--accent) !important;' : '' ?>">
|
|
||||||
<?= h(t('Thawani', 'Thawani')) ?>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link <?= $active_tab === 'smtp' ? 'active text-primary fw-bold' : 'text-secondary' ?>" id="smtp-tab" data-bs-toggle="tab" data-bs-target="#smtp-pane" type="button" role="tab" style="<?= $active_tab === 'smtp' ? 'border-bottom: 2px solid var(--accent); color: var(--accent) !important;' : '' ?>">
|
|
||||||
<?= h(t('SMTP Mail', 'بريد SMTP')) ?>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-4">
|
|
||||||
<div class="tab-content" id="integrationsTabsContent">
|
|
||||||
|
|
||||||
<!-- WABLAS TAB -->
|
|
||||||
<div class="tab-pane fade <?= $active_tab === 'wablas' ? 'show active' : '' ?>" id="wablas-pane" role="tabpanel">
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="action" value="save_wablas">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Wablas Domain / URL', 'نطاق Wablas / الرابط')) ?></label>
|
|
||||||
<input type="text" name="wablas_domain" class="form-control" value="<?= h($prof['wablas_domain'] ?? '') ?>" placeholder="e.g. https://solo.wablas.com">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('API Token', 'رمز API')) ?></label>
|
|
||||||
<input type="password" name="wablas_token" class="form-control" value="<?= h($prof['wablas_token'] ?? '') ?>" placeholder="Your Wablas API token">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12 mb-4">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Security Key', 'مفتاح الأمان')) ?></label>
|
|
||||||
<input type="password" name="wablas_security_key" class="form-control" value="<?= h($prof['wablas_security_key'] ?? '') ?>" placeholder="Your Wablas Security Key">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<button type="submit" class="btn btn-primary px-4" style="background-color: var(--accent); border-color: var(--accent);">
|
|
||||||
<?= h(t('Save Wablas Settings', 'حفظ إعدادات Wablas')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- THAWANI TAB -->
|
|
||||||
<div class="tab-pane fade <?= $active_tab === 'thawani' ? 'show active' : '' ?>" id="thawani-pane" role="tabpanel">
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="action" value="save_thawani">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Secret Key', 'المفتاح السري')) ?></label>
|
|
||||||
<input type="password" name="thawani_secret_key" class="form-control" value="<?= h($prof['thawani_secret_key'] ?? '') ?>" placeholder="sk_...">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Publishable Key', 'المفتاح القابل للنشر')) ?></label>
|
|
||||||
<input type="text" name="thawani_publishable_key" class="form-control" value="<?= h($prof['thawani_publishable_key'] ?? '') ?>" placeholder="pk_...">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12 mb-4">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Mode', 'الوضع')) ?></label>
|
|
||||||
<select name="thawani_mode" class="form-select">
|
|
||||||
<option value="test" <?= $thawani_mode === 'test' ? 'selected' : '' ?>><?= h(t('Test Mode', 'وضع الاختبار')) ?></option>
|
|
||||||
<option value="live" <?= $thawani_mode === 'live' ? 'selected' : '' ?>><?= h(t('Live Mode', 'الوضع الحي')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<button type="submit" class="btn btn-primary px-4" style="background-color: var(--accent); border-color: var(--accent);">
|
|
||||||
<?= h(t('Save Thawani Settings', 'حفظ إعدادات Thawani')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SMTP TAB -->
|
|
||||||
<div class="tab-pane fade <?= $active_tab === 'smtp' ? 'show active' : '' ?>" id="smtp-pane" role="tabpanel">
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="action" value="save_smtp">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('SMTP Host', 'خادم SMTP')) ?></label>
|
|
||||||
<input type="text" name="smtp_host" class="form-control" value="<?= h($prof['smtp_host'] ?? '') ?>" placeholder="e.g. smtp.example.com">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('SMTP Port', 'منفذ SMTP')) ?></label>
|
|
||||||
<input type="text" name="smtp_port" class="form-control" value="<?= h($prof['smtp_port'] ?? '587') ?>" placeholder="587">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Encryption', 'التشفير')) ?></label>
|
|
||||||
<select name="smtp_secure" class="form-select">
|
|
||||||
<option value="tls" <?= $smtp_secure === 'tls' ? 'selected' : '' ?>>TLS</option>
|
|
||||||
<option value="ssl" <?= $smtp_secure === 'ssl' ? 'selected' : '' ?>>SSL</option>
|
|
||||||
<option value="" <?= $smtp_secure === '' ? 'selected' : '' ?>><?= h(t('None', 'لا يوجد')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('SMTP Username', 'اسم المستخدم (SMTP)')) ?></label>
|
|
||||||
<input type="text" name="smtp_user" class="form-control" value="<?= h($prof['smtp_user'] ?? '') ?>" placeholder="user@example.com">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('SMTP Password', 'كلمة المرور (SMTP)')) ?></label>
|
|
||||||
<input type="password" name="smtp_pass" class="form-control" value="<?= h($prof['smtp_pass'] ?? '') ?>" placeholder="••••••••">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('From Email', 'البريد الإلكتروني للمرسل')) ?></label>
|
|
||||||
<input type="email" name="smtp_from_email" class="form-control" value="<?= h($prof['smtp_from_email'] ?? '') ?>" placeholder="noreply@example.com">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('From Name', 'اسم المرسل')) ?></label>
|
|
||||||
<input type="text" name="smtp_from_name" class="form-control" value="<?= h($prof['smtp_from_name'] ?? '') ?>" placeholder="My Platform">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<button type="submit" class="btn btn-primary px-4" style="background-color: var(--accent); border-color: var(--accent);">
|
|
||||||
<?= h(t('Save SMTP Settings', 'حفظ إعدادات SMTP')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Simple script to update URL when tabs are clicked so refresh keeps the same tab active
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
const tabs = document.querySelectorAll('button[data-bs-toggle="tab"]');
|
|
||||||
tabs.forEach(tab => {
|
|
||||||
tab.addEventListener('shown.bs.tab', function (event) {
|
|
||||||
const targetId = event.target.id;
|
|
||||||
let tabName = 'wablas';
|
|
||||||
if (targetId === 'thawani-tab') tabName = 'thawani';
|
|
||||||
if (targetId === 'smtp-tab') tabName = 'smtp';
|
|
||||||
|
|
||||||
// Update URL parameter without reloading
|
|
||||||
const url = new URL(window.location);
|
|
||||||
url.searchParams.set('tab', tabName);
|
|
||||||
|
|
||||||
// Remove 'saved' parameter to clear alert on tab switch
|
|
||||||
url.searchParams.delete('saved');
|
|
||||||
window.history.replaceState({}, '', url);
|
|
||||||
|
|
||||||
// Update styling logic manually to match inline active styles
|
|
||||||
tabs.forEach(t => {
|
|
||||||
t.style.borderBottom = '';
|
|
||||||
t.style.setProperty('color', 'var(--bs-secondary)', 'important');
|
|
||||||
t.classList.remove('text-primary', 'fw-bold');
|
|
||||||
t.classList.add('text-secondary');
|
|
||||||
});
|
|
||||||
|
|
||||||
event.target.style.borderBottom = '2px solid var(--accent)';
|
|
||||||
event.target.style.setProperty('color', 'var(--accent)', 'important');
|
|
||||||
event.target.classList.remove('text-secondary');
|
|
||||||
event.target.classList.add('text-primary', 'fw-bold');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,183 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_landing.php
|
|
||||||
if (!isset($pdo)) {
|
|
||||||
$pdo = db();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_landing') {
|
|
||||||
$settings = $_POST['settings'] ?? [];
|
|
||||||
|
|
||||||
// Save text settings
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO landing_settings (setting_key, value_en, value_ar) VALUES (:key, :en, :ar) ON DUPLICATE KEY UPDATE value_en = :en, value_ar = :ar");
|
|
||||||
foreach ($settings as $key => $values) {
|
|
||||||
$stmt->execute([
|
|
||||||
'key' => $key,
|
|
||||||
'en' => $values['en'] ?? '',
|
|
||||||
'ar' => $values['ar'] ?? ''
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save image settings
|
|
||||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0775, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sections = ['hero', 'courses', 'subjects', 'flow', 'plans'];
|
|
||||||
foreach ($sections as $sec) {
|
|
||||||
if (isset($_FILES['images']['error'][$sec]) && $_FILES['images']['error'][$sec] === UPLOAD_ERR_OK) {
|
|
||||||
$filename = time() . '_' . basename($_FILES['images']['name'][$sec]);
|
|
||||||
$target_file = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['images']['tmp_name'][$sec], $target_file)) {
|
|
||||||
$picture = 'assets/images/uploads/' . $filename;
|
|
||||||
$stmt->execute([
|
|
||||||
'key' => $sec . '_image',
|
|
||||||
'en' => $picture,
|
|
||||||
'ar' => $picture
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'landing', 'saved' => 1]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT setting_key, value_en, value_ar FROM landing_settings");
|
|
||||||
$all_settings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$settings = [];
|
|
||||||
foreach ($all_settings as $row) {
|
|
||||||
$settings[$row['setting_key']] = $row;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sections = [
|
|
||||||
'hero' => [
|
|
||||||
'label' => 'Hero Section / القسم الأول',
|
|
||||||
'fields' => [
|
|
||||||
'eyebrow' => ['label' => 'Eyebrow / النص الصغير', 'type' => 'text'],
|
|
||||||
'title' => ['label' => 'Title / العنوان الرئيسي', 'type' => 'textarea'],
|
|
||||||
'desc' => ['label' => 'Description / الوصف', 'type' => 'textarea'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'courses' => [
|
|
||||||
'label' => 'Courses Section / قسم الدورات',
|
|
||||||
'fields' => [
|
|
||||||
'eyebrow' => ['label' => 'Eyebrow / النص الصغير', 'type' => 'text'],
|
|
||||||
'title' => ['label' => 'Title / العنوان الرئيسي', 'type' => 'textarea'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'subjects' => [
|
|
||||||
'label' => 'Subjects Section / قسم المواد',
|
|
||||||
'fields' => [
|
|
||||||
'eyebrow' => ['label' => 'Eyebrow / النص الصغير', 'type' => 'text'],
|
|
||||||
'title' => ['label' => 'Title / العنوان الرئيسي', 'type' => 'textarea'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'flow' => [
|
|
||||||
'label' => 'Delivery Flow Section / قسم مسار التسليم',
|
|
||||||
'fields' => [
|
|
||||||
'eyebrow' => ['label' => 'Eyebrow / النص الصغير', 'type' => 'text'],
|
|
||||||
'title' => ['label' => 'Title / العنوان الرئيسي', 'type' => 'textarea'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'plans' => [
|
|
||||||
'label' => 'Plans Section / قسم الخطط',
|
|
||||||
'fields' => [
|
|
||||||
'eyebrow' => ['label' => 'Eyebrow / النص الصغير', 'type' => 'text'],
|
|
||||||
'title' => ['label' => 'Title / العنوان الرئيسي', 'type' => 'textarea'],
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Settings', 'الإعدادات')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Landing Page Settings', 'إعدادات الصفحة الرئيسية')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Customize the content of the landing page in English and Arabic. Grouped by sections.', 'تخصيص محتوى الصفحة الرئيسية باللغتين الإنجليزية والعربية. مجمعة حسب الأقسام.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (!empty($_GET['saved'])): ?>
|
|
||||||
<div class="alert alert-success"><?= h(t('Landing page settings updated successfully.', 'تم تحديث إعدادات الصفحة الرئيسية بنجاح.')) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="panel-card" style="max-width: 900px;">
|
|
||||||
<form method="POST" action="" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="action" value="save_landing">
|
|
||||||
|
|
||||||
<div class="accordion" id="sectionsAccordion">
|
|
||||||
<?php foreach ($sections as $sec_key => $section): ?>
|
|
||||||
<div class="accordion-item mb-3 border rounded">
|
|
||||||
<h2 class="accordion-header" id="heading-<?= $sec_key ?>">
|
|
||||||
<button class="accordion-button <?= $sec_key === 'hero' ? '' : 'collapsed' ?> bg-light fw-bold" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-<?= $sec_key ?>" aria-expanded="<?= $sec_key === 'hero' ? 'true' : 'false' ?>" aria-controls="collapse-<?= $sec_key ?>">
|
|
||||||
<?= h($section['label']) ?>
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="collapse-<?= $sec_key ?>" class="accordion-collapse collapse <?= $sec_key === 'hero' ? 'show' : '' ?>" aria-labelledby="heading-<?= $sec_key ?>" data-bs-parent="#sectionsAccordion">
|
|
||||||
<div class="accordion-body">
|
|
||||||
|
|
||||||
<!-- Image Upload for Section -->
|
|
||||||
<div class="mb-4 p-3 border rounded bg-white">
|
|
||||||
<label class="form-label fw-bold"><?= h(t('Section Image', 'صورة القسم')) ?></label>
|
|
||||||
<?php if (!empty($settings[$sec_key . '_image']['value_en'])): ?>
|
|
||||||
<div class="mb-2">
|
|
||||||
<img src="<?= h($settings[$sec_key . '_image']['value_en']) ?>" alt="Current Image" style="max-height: 100px; border-radius: 8px;">
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<input type="file" class="form-control" name="images[<?= $sec_key ?>]" accept="image/*">
|
|
||||||
<small class="text-muted"><?= h(t('Upload a new image to replace the current one.', 'قم برفع صورة جديدة لاستبدال الصورة الحالية.')) ?></small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="nav nav-tabs mb-3" id="tab-<?= $sec_key ?>" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#<?= $sec_key ?>-en" type="button" role="tab">English</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#<?= $sec_key ?>-ar" type="button" role="tab">العربية</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content">
|
|
||||||
<div class="tab-pane fade show active" id="<?= $sec_key ?>-en" role="tabpanel">
|
|
||||||
<?php foreach ($section['fields'] as $field_key => $field): ?>
|
|
||||||
<?php $full_key = $sec_key . '_' . $field_key; ?>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-bold text-secondary"><?= h($field['label']) ?></label>
|
|
||||||
<?php if ($field['type'] === 'textarea'): ?>
|
|
||||||
<textarea class="form-control" name="settings[<?= $full_key ?>][en]" rows="2"><?= h($settings[$full_key]['value_en'] ?? '') ?></textarea>
|
|
||||||
<?php else: ?>
|
|
||||||
<input type="text" class="form-control" name="settings[<?= $full_key ?>][en]" value="<?= h($settings[$full_key]['value_en'] ?? '') ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade" id="<?= $sec_key ?>-ar" role="tabpanel" dir="rtl">
|
|
||||||
<?php foreach ($section['fields'] as $field_key => $field): ?>
|
|
||||||
<?php $full_key = $sec_key . '_' . $field_key; ?>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-bold text-secondary"><?= h($field['label']) ?></label>
|
|
||||||
<?php if ($field['type'] === 'textarea'): ?>
|
|
||||||
<textarea class="form-control" name="settings[<?= $full_key ?>][ar]" rows="2"><?= h($settings[$full_key]['value_ar'] ?? '') ?></textarea>
|
|
||||||
<?php else: ?>
|
|
||||||
<input type="text" class="form-control" name="settings[<?= $full_key ?>][ar]" value="<?= h($settings[$full_key]['value_ar'] ?? '') ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<button type="submit" class="btn btn-dark btn-lg px-4"><?= h(t('Save All Sections', 'حفظ كل الأقسام')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
276
admin_plans.php
@ -1,276 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_plans.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('plans', 'view');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('plans', 'delete');
|
|
||||||
$stmt = db()->prepare("DELETE FROM plans WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'plans']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('plans', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$plan_key = $_POST['plan_key'] ?? '';
|
|
||||||
$name_en = $_POST['name_en'] ?? '';
|
|
||||||
$name_ar = $_POST['name_ar'] ?? '';
|
|
||||||
$price_monthly = (float)($_POST['price_monthly'] ?? 0);
|
|
||||||
$price_yearly = (float)($_POST['price_yearly'] ?? 0);
|
|
||||||
$subjects_limit = (int)($_POST['subjects_limit'] ?? 1);
|
|
||||||
|
|
||||||
$features_en_raw = $_POST['features_en'] ?? '';
|
|
||||||
$features_ar_raw = $_POST['features_ar'] ?? '';
|
|
||||||
|
|
||||||
// Convert multiline text into JSON array
|
|
||||||
$features_en = array_values(array_filter(array_map('trim', explode("\n", $features_en_raw))));
|
|
||||||
$features_ar = array_values(array_filter(array_map('trim', explode("\n", $features_ar_raw))));
|
|
||||||
|
|
||||||
$features_en_json = json_encode($features_en, JSON_UNESCAPED_UNICODE);
|
|
||||||
$features_ar_json = json_encode($features_ar, JSON_UNESCAPED_UNICODE);
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE plans SET plan_key=?, name_en=?, name_ar=?, price_monthly=?, price_yearly=?, subjects_limit=?, features_en=?, features_ar=? WHERE id=?");
|
|
||||||
$stmt->execute([$plan_key, $name_en, $name_ar, $price_monthly, $price_yearly, $subjects_limit, $features_en_json, $features_ar_json, $post_id]);
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("INSERT INTO plans (plan_key, name_en, name_ar, price_monthly, price_yearly, subjects_limit, features_en, features_ar) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$plan_key, $name_en, $name_ar, $price_monthly, $price_yearly, $subjects_limit, $features_en_json, $features_ar_json]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'plans']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list view
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where = "";
|
|
||||||
$params = [];
|
|
||||||
if ($search !== '') {
|
|
||||||
$where = "WHERE name_en LIKE ? OR name_ar LIKE ? OR plan_key LIKE ?";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$total_stmt = db()->prepare("SELECT COUNT(*) FROM plans $where");
|
|
||||||
$total_stmt->execute($params);
|
|
||||||
$total = $total_stmt->fetchColumn();
|
|
||||||
$pages = ceil($total / $limit);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT * FROM plans $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
|
|
||||||
$stmt->execute($params);
|
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Plans', 'الخطط')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-wrap gap-2 justify-content-end">
|
|
||||||
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addPlanModal" style="background-color: var(--accent); border-color: var(--accent);">+ <?= h(t('Add Plan', 'إضافة خطة')) ?></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="get" class="d-flex gap-2 align-items-center">
|
|
||||||
<input type="hidden" name="page" value="plans">
|
|
||||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
|
||||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
|
||||||
<?php if ($search): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page'=>'plans'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th><?= h(t('Key', 'المفتاح')) ?></th>
|
|
||||||
<th><?= h(t('Name', 'الاسم')) ?></th>
|
|
||||||
<th><?= h(t('Monthly', 'شهري')) ?></th>
|
|
||||||
<th><?= h(t('Yearly', 'سنوي')) ?></th>
|
|
||||||
<th><?= h(t('Subjects', 'المواد')) ?></th>
|
|
||||||
<th class="text-end"><?= h(t('Actions', 'إجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($items as $row):
|
|
||||||
$f_en = json_decode($row['features_en'] ?: '[]', true);
|
|
||||||
$f_ar = json_decode($row['features_ar'] ?: '[]', true);
|
|
||||||
$f_en_text = is_array($f_en) ? implode("\n", $f_en) : '';
|
|
||||||
$f_ar_text = is_array($f_ar) ? implode("\n", $f_ar) : '';
|
|
||||||
?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h((string)$row['id']) ?></td>
|
|
||||||
<td><span class="badge bg-light text-dark border"><?= h($row['plan_key']) ?></span></td>
|
|
||||||
<td>
|
|
||||||
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $row['name_ar'] : $row['name_en']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td><?= h(format_price((float)$row['price_monthly'])) ?></td>
|
|
||||||
<td><?= h(format_price((float)$row['price_yearly'])) ?></td>
|
|
||||||
<td><?= h((string)$row['subjects_limit']) ?></td>
|
|
||||||
<td class="text-end">
|
|
||||||
<?php if (has_permission('plans', 'edit')): ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editPlanModal<?= $row['id'] ?>" title="<?= h(t('Edit', 'تعديل')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (has_permission('plans', 'delete')): ?>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'plans', 'action'=>'delete', 'id'=>$row['id']])) ?>" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>');">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="<?= h(t('Delete', 'حذف')) ?>"><svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg></button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Edit Modal for Row <?= $row['id'] ?> -->
|
|
||||||
<div class="modal fade" id="editPlanModal<?= $row['id'] ?>" tabindex="-1" aria-labelledby="editPlanModalLabel<?= $row['id'] ?>" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="editPlanModalLabel<?= $row['id'] ?>"><?= h(t('Edit Plan', 'تعديل الخطة')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'plans'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="edit">
|
|
||||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Plan Key (Unique identifier e.g. plus, basic)', 'مفتاح الخطة (معرف فريد مثل plus, basic)')) ?></label>
|
|
||||||
<input type="text" name="plan_key" class="form-control" value="<?= h($row['plan_key']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (EN)', 'الاسم (إنجليزي)')) ?></label>
|
|
||||||
<input type="text" name="name_en" class="form-control" value="<?= h($row['name_en']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (AR)', 'الاسم (عربي)')) ?></label>
|
|
||||||
<input type="text" name="name_ar" class="form-control" value="<?= h($row['name_ar']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Monthly Price', 'السعر الشهري')) ?></label>
|
|
||||||
<input type="number" step="0.001" name="price_monthly" class="form-control" value="<?= h($row['price_monthly']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Yearly Price', 'السعر السنوي')) ?></label>
|
|
||||||
<input type="number" step="0.001" name="price_yearly" class="form-control" value="<?= h($row['price_yearly']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Subjects Limit', 'حد المواد')) ?></label>
|
|
||||||
<input type="number" name="subjects_limit" class="form-control" value="<?= h($row['subjects_limit']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Features (EN) - One per line', 'الميزات (EN) - ميزة في كل سطر')) ?></label>
|
|
||||||
<textarea name="features_en" class="form-control" rows="5"><?= h($f_en_text) ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Features (AR) - One per line', 'الميزات (AR) - ميزة في كل سطر')) ?></label>
|
|
||||||
<textarea name="features_ar" class="form-control" rows="5"><?= h($f_ar_text) ?></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Edit Modal -->
|
|
||||||
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(!$items): ?>
|
|
||||||
<tr><td colspan="7" class="text-center text-secondary py-3"><?= h(t('No plans found.', 'لا توجد خطط.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<?php for($i=1; $i<=$pages; $i++):
|
|
||||||
$active_class = ($i === $page_num) ? ' active' : '';
|
|
||||||
?>
|
|
||||||
<li class="page-item<?= $active_class ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'plans', 'p'=>$i, 'search'=>$search])) ?>"><?= $i ?></a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add Plan Modal -->
|
|
||||||
<div class="modal fade" id="addPlanModal" tabindex="-1" aria-labelledby="addPlanModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="addPlanModalLabel"><?= h(t('Add Plan', 'إضافة خطة')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'plans'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="add">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Plan Key (Unique identifier e.g. plus, basic)', 'مفتاح الخطة (معرف فريد مثل plus, basic)')) ?></label>
|
|
||||||
<input type="text" name="plan_key" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (EN)', 'الاسم (إنجليزي)')) ?></label>
|
|
||||||
<input type="text" name="name_en" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (AR)', 'الاسم (عربي)')) ?></label>
|
|
||||||
<input type="text" name="name_ar" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Monthly Price', 'السعر الشهري')) ?></label>
|
|
||||||
<input type="number" step="0.001" name="price_monthly" class="form-control" value="0.000">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Yearly Price', 'السعر السنوي')) ?></label>
|
|
||||||
<input type="number" step="0.001" name="price_yearly" class="form-control" value="0.000">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Subjects Limit', 'حد المواد')) ?></label>
|
|
||||||
<input type="number" name="subjects_limit" class="form-control" value="1">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Features (EN) - One per line', 'الميزات (EN) - ميزة في كل سطر')) ?></label>
|
|
||||||
<textarea name="features_en" class="form-control" rows="5"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Features (AR) - One per line', 'الميزات (AR) - ميزة في كل سطر')) ?></label>
|
|
||||||
<textarea name="features_ar" class="form-control" rows="5"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Add Modal -->
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_profile.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('profile', 'view');
|
|
||||||
|
|
||||||
// Handle Profile Update
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['name'])) {
|
|
||||||
require_permission('profile', 'edit');
|
|
||||||
|
|
||||||
$name = $_POST['name'] ?? '';
|
|
||||||
$description = $_POST['description'] ?? '';
|
|
||||||
|
|
||||||
$logo_path = get_platform_profile()['logo_path'] ?? '';
|
|
||||||
$favicon_path = get_platform_profile()['favicon_path'] ?? '';
|
|
||||||
|
|
||||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0777, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($_FILES['logo']['tmp_name'])) {
|
|
||||||
$filename = 'logo_' . time() . '_' . basename($_FILES['logo']['name']);
|
|
||||||
$target = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['logo']['tmp_name'], $target)) {
|
|
||||||
$logo_path = 'assets/images/uploads/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($_FILES['favicon']['tmp_name'])) {
|
|
||||||
$filename = 'favicon_' . time() . '_' . basename($_FILES['favicon']['name']);
|
|
||||||
$target = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['favicon']['tmp_name'], $target)) {
|
|
||||||
$favicon_path = 'assets/images/uploads/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$ctr_no = $_POST['ctr_no'] ?? '';
|
|
||||||
$telephone_no = $_POST['telephone_no'] ?? '';
|
|
||||||
$email_id = $_POST['email_id'] ?? '';
|
|
||||||
|
|
||||||
$terms = $_POST['terms'] ?? '';
|
|
||||||
$privacy_policy = $_POST['privacy_policy'] ?? '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = db()->prepare("UPDATE platform_profile SET name = :name, description = :description, logo_path = :logo, favicon_path = :favicon, ctr_no = :ctr_no, telephone_no = :telephone_no, email_id = :email_id, terms = :terms, privacy_policy = :privacy_policy WHERE id = 1");
|
|
||||||
$stmt->execute([
|
|
||||||
'name' => $name,
|
|
||||||
'description' => $description,
|
|
||||||
'logo' => $logo_path,
|
|
||||||
'favicon' => $favicon_path,
|
|
||||||
'ctr_no' => $ctr_no,
|
|
||||||
'telephone_no' => $telephone_no,
|
|
||||||
'email_id' => $email_id,
|
|
||||||
'terms' => $terms,
|
|
||||||
'privacy_policy' => $privacy_policy
|
|
||||||
]);
|
|
||||||
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'profile', 'saved' => 1]));
|
|
||||||
exit;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$error_message = "Error saving profile: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$prof = get_platform_profile();
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Settings', 'الإعدادات')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Platform Profile', 'ملف المنصة')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Update your platforms name, description, logo, and favicon.', 'قم بتحديث اسم منصتك ووصفها وشعارها والأيقونة المفضلة.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php if (!empty($_GET['saved'])): ?>
|
|
||||||
<div class="alert alert-success"><?= h(t('Profile updated successfully.', 'تم تحديث الملف بنجاح.')) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="panel-card" style="max-width: 600px;">
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Platform Name', 'اسم المنصة')) ?></label>
|
|
||||||
<input type="text" name="name" class="form-control" value="<?= h($prof['name'] ?? '') ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description', 'الوصف')) ?></label>
|
|
||||||
<textarea name="description" class="form-control" rows="3"><?= h($prof['description'] ?? '') ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Logo', 'الشعار')) ?></label>
|
|
||||||
<?php if (!empty($prof['logo_path'])): ?>
|
|
||||||
<div class="mb-2">
|
|
||||||
<img src="<?= h(asset_url($prof['logo_path'])) ?>" alt="Logo" style="height: 60px; border: 1px solid var(--border); border-radius: 8px; padding: 4px; background: #fff;">
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<input type="file" name="logo" class="form-control" accept="image/*">
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label"><?= h(t('Favicon', 'الأيقونة المفضلة')) ?></label>
|
|
||||||
<?php if (!empty($prof['favicon_path'])): ?>
|
|
||||||
<div class="mb-2">
|
|
||||||
<img src="<?= h(asset_url($prof['favicon_path'])) ?>" alt="Favicon" style="height: 32px; border: 1px solid var(--border); border-radius: 4px; padding: 2px; background: #fff;">
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<input type="file" name="favicon" class="form-control" accept="image/x-icon,image/png,image/jpeg,image/svg+xml">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h5 class="mb-3 mt-4"><?= h(t('Contact Information', 'معلومات الاتصال')) ?></h5>
|
|
||||||
<div class="row g-3 mb-4">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email_id" class="form-control" value="<?= h($prof['email_id'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label"><?= h(t('Phone Number', 'رقم الهاتف')) ?></label>
|
|
||||||
<input type="text" name="telephone_no" class="form-control" value="<?= h($prof['telephone_no'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<label class="form-label"><?= h(t('Commercial Registration No.', 'رقم السجل التجاري')) ?></label>
|
|
||||||
<input type="text" name="ctr_no" class="form-control" value="<?= h($prof['ctr_no'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<h5 class="mb-3 mt-4"><?= h(t('Terms of Conditions', 'الشروط والأحكام'))?></h5>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Terms of Conditions', 'الشروط والأحكام'))?></label>
|
|
||||||
<textarea name="terms" class="form-control" rows="5"><?= h($prof['terms'] ?? '')?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label"><?= h(t('Privacy Policy', 'سياسة الخصوصية'))?></label>
|
|
||||||
<textarea name="privacy_policy" class="form-control" rows="5"><?= h($prof['privacy_policy'] ?? '')?></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);">
|
|
||||||
|
|
||||||
<?= h(t('Save Changes', 'حفظ التغييرات')) ?>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
269
admin_roles.php
@ -1,269 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_roles.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
try {
|
|
||||||
db()->query('SELECT 1 FROM roles LIMIT 1');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
ob_start();
|
|
||||||
require_once __DIR__ . '/db/migrations/migrate_roles.php';
|
|
||||||
ob_end_clean();
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'roles']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
require_permission('roles', 'view');
|
|
||||||
|
|
||||||
$pages = [
|
|
||||||
'dashboard' => t('Dashboard', 'لوحة التحكم'),
|
|
||||||
'classes' => t('Classes', 'الصفوف'),
|
|
||||||
'subjects' => t('Subjects', 'المواد'),
|
|
||||||
'teachers' => t('Teachers', 'المعلمين'),
|
|
||||||
'courses' => t('Courses', 'الدورات'),
|
|
||||||
'assignments' => t('Assignments', 'الواجبات'),
|
|
||||||
'students' => t('Students', 'الطلاب'),
|
|
||||||
'plans' => t('Plans', 'الخطط'),
|
|
||||||
'users' => t('Platform Users', 'مستخدمي المنصة'),
|
|
||||||
'roles' => t('Role Groups', 'مجموعات الصلاحيات'),
|
|
||||||
'integrations' => t('Integrations', 'عمليات الربط'),
|
|
||||||
'landing' => t('Landing Page', 'الصفحة الرئيسية'),
|
|
||||||
'profile' => t('Platform Profile', 'ملف المنصة'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('roles', 'delete');
|
|
||||||
// check if system role
|
|
||||||
$stmt = db()->prepare("SELECT is_system FROM roles WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
$role = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if ($role && !$role['is_system']) {
|
|
||||||
$stmt = db()->prepare("DELETE FROM roles WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'roles']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('roles', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$name = trim($_POST['name'] ?? '');
|
|
||||||
$description = trim($_POST['description'] ?? '');
|
|
||||||
$perms = $_POST['perms'] ?? []; // e.g. ['classes' => ['view'=>1, 'add'=>1]]
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE roles SET name=?, description=? WHERE id=?");
|
|
||||||
$stmt->execute([$name, $description, $post_id]);
|
|
||||||
$role_id = $post_id;
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("INSERT INTO roles (name, description) VALUES (?, ?)");
|
|
||||||
$stmt->execute([$name, $description]);
|
|
||||||
$role_id = db()->lastInsertId();
|
|
||||||
}
|
|
||||||
|
|
||||||
// update permissions
|
|
||||||
if ($role_id) {
|
|
||||||
$stmt = db()->prepare("DELETE FROM role_permissions WHERE role_id = ?");
|
|
||||||
$stmt->execute([$role_id]);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("INSERT INTO role_permissions (role_id, page, can_view, can_add, can_edit, can_delete) VALUES (?, ?, ?, ?, ?, ?)");
|
|
||||||
foreach ($pages as $p => $pname) {
|
|
||||||
if (isset($perms[$p])) {
|
|
||||||
$v = !empty($perms[$p]['view']) ? 1 : 0;
|
|
||||||
$a = !empty($perms[$p]['add']) ? 1 : 0;
|
|
||||||
$e = !empty($perms[$p]['edit']) ? 1 : 0;
|
|
||||||
$d = !empty($perms[$p]['delete']) ? 1 : 0;
|
|
||||||
if ($v || $a || $e || $d) {
|
|
||||||
$stmt->execute([$role_id, $p, $v, $a, $e, $d]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'roles']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$roles = db()->query("SELECT * FROM roles ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Fetch permissions for all roles
|
|
||||||
$stmt = db()->query("SELECT * FROM role_permissions");
|
|
||||||
$all_perms = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
$role_perms = [];
|
|
||||||
foreach ($all_perms as $p) {
|
|
||||||
$role_perms[$p['role_id']][$p['page']] = $p;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge permissions into roles for JS
|
|
||||||
foreach ($roles as &$r) {
|
|
||||||
$r['permissions'] = $role_perms[$r['id']] ?? [];
|
|
||||||
}
|
|
||||||
unset($r);
|
|
||||||
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Role Groups', 'مجموعات الصلاحيات')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Manage roles and their page permissions.', 'إدارة الأدوار وصلاحيات الصفحات الخاصة بها.')) ?></p>
|
|
||||||
</div>
|
|
||||||
<?php if (has_permission('roles', 'add')): ?>
|
|
||||||
<button class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#roleModal" onclick="editRole(null)">
|
|
||||||
<?= h(t('Add Role Group', 'إضافة مجموعة صلاحيات')) ?>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card p-0 overflow-hidden">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover align-middle mb-0">
|
|
||||||
<thead class="bg-light">
|
|
||||||
<tr>
|
|
||||||
<th class="px-4 py-3"><?= h(t('Role Name', 'اسم الدور')) ?></th>
|
|
||||||
<th class="py-3"><?= h(t('Description', 'الوصف')) ?></th>
|
|
||||||
<th class="px-4 py-3 text-end"><?= h(t('Actions', 'الإجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($roles as $r): ?>
|
|
||||||
<tr>
|
|
||||||
<td class="px-4 py-3 fw-bold text-dark">
|
|
||||||
<?= h($r['name']) ?>
|
|
||||||
<?php if ($r['is_system']): ?>
|
|
||||||
<span class="badge bg-secondary ms-2"><?= h(t('System', 'نظام')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td class="py-3 text-secondary"><?= h($r['description']) ?></td>
|
|
||||||
<td class="px-4 py-3 text-end">
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<?php if (has_permission('roles', 'edit')): ?>
|
|
||||||
<button class="btn btn-outline-dark" onclick='editRole(<?= htmlspecialchars(json_encode($r), ENT_QUOTES) ?>)' data-bs-toggle="modal" data-bs-target="#roleModal">
|
|
||||||
<svg width="16" height="16" fill="currentColor" class="me-1" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg><?= h(t($r['is_system'] ? 'View' : 'Edit', $r['is_system'] ? 'عرض' : 'تعديل')) ?>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if (!$r['is_system'] && has_permission('roles', 'delete')): ?>
|
|
||||||
<form method="POST" action="admin_roles.php" class="d-inline" onsubmit="return confirm('<?= h(t('Delete this role?', 'حذف هذا الدور؟')) ?>');">
|
|
||||||
<input type="hidden" name="action" value="delete">
|
|
||||||
<input type="hidden" name="id" value="<?= h($r['id']) ?>">
|
|
||||||
<button class="btn btn-outline-danger" type="submit">
|
|
||||||
<svg width="16" height="16" fill="currentColor" class="me-1" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg><?= h(t('Delete', 'حذف')) ?>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if (empty($roles)): ?>
|
|
||||||
<tr><td colspan="3" class="text-center py-4 text-secondary"><?= h(t('No roles found.', 'لا توجد أدوار.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Role Modal -->
|
|
||||||
<div class="modal fade" id="roleModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog modal-lg">
|
|
||||||
<form class="modal-content" method="POST" action="admin_roles.php">
|
|
||||||
<input type="hidden" name="action" id="roleAction" value="add">
|
|
||||||
<input type="hidden" name="id" id="roleId" value="0">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="roleModalTitle"><?= h(t('Add Role Group', 'إضافة مجموعة صلاحيات')) ?></h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label"><?= h(t('Role Name', 'اسم الدور')) ?></label>
|
|
||||||
<input type="text" class="form-control" name="name" id="roleName" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label"><?= h(t('Description', 'الوصف')) ?></label>
|
|
||||||
<input type="text" class="form-control" name="description" id="roleDesc">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h6 class="mt-4 mb-3"><?= h(t('Permissions', 'الصلاحيات')) ?></h6>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-bordered table-sm align-middle">
|
|
||||||
<thead class="bg-light text-center">
|
|
||||||
<tr>
|
|
||||||
<th class="text-start"><?= h(t('Page', 'الصفحة')) ?></th>
|
|
||||||
<th><?= h(t('View', 'عرض')) ?></th>
|
|
||||||
<th><?= h(t('Add', 'إضافة')) ?></th>
|
|
||||||
<th><?= h(t('Edit', 'تعديل')) ?></th>
|
|
||||||
<th><?= h(t('Delete', 'حذف')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="permissionsTbody">
|
|
||||||
<?php foreach($pages as $p => $pname): ?>
|
|
||||||
<tr>
|
|
||||||
<td class="text-start fw-bold"><?= h($pname) ?></td>
|
|
||||||
<td class="text-center"><input class="form-check-input perm-chk perm-chk-view" type="checkbox" name="perms[<?= h($p) ?>][view]" value="1" data-page="<?= h($p) ?>"></td>
|
|
||||||
<td class="text-center"><input class="form-check-input perm-chk perm-chk-add" type="checkbox" name="perms[<?= h($p) ?>][add]" value="1" data-page="<?= h($p) ?>"></td>
|
|
||||||
<td class="text-center"><input class="form-check-input perm-chk perm-chk-edit" type="checkbox" name="perms[<?= h($p) ?>][edit]" value="1" data-page="<?= h($p) ?>"></td>
|
|
||||||
<td class="text-center"><input class="form-check-input perm-chk perm-chk-delete" type="checkbox" name="perms[<?= h($p) ?>][delete]" value="1" data-page="<?= h($p) ?>"></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-dark" id="btnSaveRole"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function editRole(role) {
|
|
||||||
// reset checkboxes
|
|
||||||
document.querySelectorAll('.perm-chk').forEach(c => c.checked = false);
|
|
||||||
|
|
||||||
if (role) {
|
|
||||||
document.getElementById('roleAction').value = 'edit';
|
|
||||||
document.getElementById('roleId').value = role.id;
|
|
||||||
document.getElementById('roleModalTitle').innerText = '<?= h(t('Edit Role Group', 'تعديل مجموعة الصلاحيات')) ?>';
|
|
||||||
document.getElementById('roleName').value = role.name;
|
|
||||||
document.getElementById('roleDesc').value = role.description || '';
|
|
||||||
|
|
||||||
if (role.permissions) {
|
|
||||||
for (let p in role.permissions) {
|
|
||||||
let perms = role.permissions[p];
|
|
||||||
if (perms.can_view == 1) { let el = document.querySelector(`input[name="perms[${p}][view]"]`); if(el) el.checked = true; }
|
|
||||||
if (perms.can_add == 1) { let el = document.querySelector(`input[name="perms[${p}][add]"]`); if(el) el.checked = true; }
|
|
||||||
if (perms.can_edit == 1) { let el = document.querySelector(`input[name="perms[${p}][edit]"]`); if(el) el.checked = true; }
|
|
||||||
if (perms.can_delete == 1) { let el = document.querySelector(`input[name="perms[${p}][delete]"]`); if(el) el.checked = true; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let isSystem = role.is_system == 1;
|
|
||||||
document.getElementById('roleName').readOnly = isSystem;
|
|
||||||
document.getElementById('roleDesc').readOnly = isSystem;
|
|
||||||
document.querySelectorAll('.perm-chk').forEach(c => c.disabled = isSystem);
|
|
||||||
document.getElementById('btnSaveRole').style.display = isSystem ? 'none' : 'block';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
document.getElementById('roleAction').value = 'add';
|
|
||||||
document.getElementById('roleId').value = '0';
|
|
||||||
document.getElementById('roleModalTitle').innerText = '<?= h(t('Add Role Group', 'إضافة مجموعة صلاحيات')) ?>';
|
|
||||||
document.getElementById('roleName').value = '';
|
|
||||||
document.getElementById('roleDesc').value = '';
|
|
||||||
|
|
||||||
document.getElementById('roleName').readOnly = false;
|
|
||||||
document.getElementById('roleDesc').readOnly = false;
|
|
||||||
document.querySelectorAll('.perm-chk').forEach(c => c.disabled = false);
|
|
||||||
document.getElementById('btnSaveRole').style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,336 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_students.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('students', 'view');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('students', 'delete');
|
|
||||||
$stmt = db()->prepare("DELETE FROM student_subscriptions WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'students']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('students', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$full_name = $_POST['full_name'] ?? '';
|
|
||||||
$email = $_POST['email'] ?? '';
|
|
||||||
$whatsapp = $_POST['whatsapp'] ?? '';
|
|
||||||
$plan_key = $_POST['plan_key'] ?? '';
|
|
||||||
$payment_status = $_POST['payment_status'] ?? 'active';
|
|
||||||
$status = $_POST['status'] ?? 'active';
|
|
||||||
$civil_id = $_POST['civil_id'] ?? '';
|
|
||||||
|
|
||||||
$picture = null;
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("SELECT picture FROM student_subscriptions WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
$picture = $stmt->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($_FILES['picture']) && $_FILES['picture']['error'] === UPLOAD_ERR_OK) {
|
|
||||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0775, true);
|
|
||||||
}
|
|
||||||
$filename = time() . '_' . basename($_FILES['picture']['name']);
|
|
||||||
$target_file = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['picture']['tmp_name'], $target_file)) {
|
|
||||||
$picture = 'assets/images/uploads/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE student_subscriptions SET full_name=?, email=?, whatsapp=?, plan_key=?, payment_status=?, status=?, civil_id=?, picture=? WHERE id=?");
|
|
||||||
$stmt->execute([$full_name, $email, $whatsapp, $plan_key, $payment_status, $status, $civil_id, $picture, $post_id]);
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("INSERT INTO student_subscriptions (full_name, email, whatsapp, plan_key, payment_status, status, civil_id, picture) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$full_name, $email, $whatsapp, $plan_key, $payment_status, $status, $civil_id, $picture]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'students']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list view
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where = "";
|
|
||||||
$params = [];
|
|
||||||
if ($search) {
|
|
||||||
$where = "WHERE full_name LIKE ? OR email LIKE ? OR whatsapp LIKE ?";
|
|
||||||
$params = ["%$search%", "%$search%", "%$search%"];
|
|
||||||
}
|
|
||||||
|
|
||||||
$count_stmt = db()->prepare("SELECT COUNT(*) FROM student_subscriptions $where");
|
|
||||||
$count_stmt->execute($params);
|
|
||||||
$total_items = $count_stmt->fetchColumn();
|
|
||||||
$total_pages = ceil($total_items / $limit);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT * FROM student_subscriptions $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
|
|
||||||
$stmt->execute($params);
|
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Admin', 'الإدارة')) ?></span>
|
|
||||||
<h1 class="section-title mb-0"><?= h(t('Students', 'الطلاب')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);" data-bs-toggle="modal" data-bs-target="#addStudentModal">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16" class="me-1"><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/></svg>
|
|
||||||
<?= h(t('Add Student', 'إضافة طالب')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="get" class="d-flex gap-2 align-items-center">
|
|
||||||
<input type="hidden" name="page" value="students">
|
|
||||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
|
||||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
|
||||||
<?php if ($search): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page'=>'students'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= h(t('Name', 'الاسم')) ?></th>
|
|
||||||
<th><?= h(t('Status', 'الحالة')) ?></th>
|
|
||||||
<th><?= h(t('Email', 'البريد الإلكتروني')) ?></th>
|
|
||||||
<th><?= h(t('WhatsApp', 'واتساب')) ?></th>
|
|
||||||
<th><?= h(t('Plan', 'الخطة')) ?></th>
|
|
||||||
<th><?= h(t('Payment', 'الدفع')) ?></th>
|
|
||||||
<th><?= h(t('Actions', 'إجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($items as $row): ?>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fw-semibold"><?= h($row['full_name']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (($row['status'] ?? 'active') === 'active'): ?>
|
|
||||||
<span class="badge bg-success"><?= h(t('Active', 'نشط')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary"><?= h(t('Inactive', 'غير نشط')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td><?= h($row['email']) ?></td>
|
|
||||||
<td><?= h($row['whatsapp']) ?></td>
|
|
||||||
<td><?= h($row['plan_key']) ?></td>
|
|
||||||
<td>
|
|
||||||
<?php if (($row['payment_status'] ?? '') === 'active'): ?>
|
|
||||||
<span class="badge bg-success"><?= h(t('Active', 'نشط')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-warning text-dark"><?= h($row['payment_status']) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (has_permission('students', 'edit')): ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editStudentModal<?= $row['id'] ?>" title="<?= h(t('Edit', 'تعديل')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (has_permission('students', 'delete')): ?>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'students', 'action'=>'delete', 'id'=>$row['id']])) ?>" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>');">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="<?= h(t('Delete', 'حذف')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Edit Modal -->
|
|
||||||
<div class="modal fade" id="editStudentModal<?= $row['id'] ?>" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-bottom-0 pb-3" style="background-color: var(--bs-light);">
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
|
||||||
<?php if (!empty($row['picture'])): ?>
|
|
||||||
<img src="<?= h($row['picture']) ?>" alt="Picture" class="rounded-circle border" style="width: 48px; height: 48px; object-fit: cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;">
|
|
||||||
<svg width="24" height="24" fill="currentColor" viewBox="0 0 16 16"><path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/><path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/></svg>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div>
|
|
||||||
<h5 class="modal-title fw-bold mb-0"><?= h(t('Edit Student', 'تعديل الطالب')) ?></h5>
|
|
||||||
<div class="text-muted small fw-semibold mt-1"><?= h($row['full_name']) ?></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'students'])) ?>" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="action" value="edit">
|
|
||||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
|
||||||
|
|
||||||
<!-- Name Bar with Background -->
|
|
||||||
<div class="mb-3 p-3 bg-light rounded border">
|
|
||||||
<label class="form-label fw-bold"><?= h(t('Name', 'الاسم')) ?></label>
|
|
||||||
<input type="text" name="full_name" class="form-control" value="<?= h($row['full_name']) ?>" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Civil ID', 'الرقم المدني')) ?></label>
|
|
||||||
<input type="text" name="civil_id" class="form-control" value="<?= h($row['civil_id'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Picture', 'الصورة')) ?></label>
|
|
||||||
<input type="file" name="picture" class="form-control" accept="image/*">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email" class="form-control" value="<?= h($row['email']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('WhatsApp', 'واتساب')) ?></label>
|
|
||||||
<input type="text" name="whatsapp" class="form-control" value="<?= h($row['whatsapp']) ?>">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Plan Key', 'الخطة')) ?></label>
|
|
||||||
<input type="text" name="plan_key" class="form-control" value="<?= h($row['plan_key']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Payment Status', 'حالة الدفع')) ?></label>
|
|
||||||
<input type="text" name="payment_status" class="form-control" value="<?= h($row['payment_status']) ?>">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active" <?= ($row['status'] ?? 'active') === 'active' ? 'selected' : '' ?>><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive" <?= ($row['status'] ?? 'active') === 'inactive' ? 'selected' : '' ?>><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(empty($items)): ?>
|
|
||||||
<tr>
|
|
||||||
<td colspan="7" class="text-center py-4 text-secondary">
|
|
||||||
<?= h(t('No students found.', 'لم يتم العثور على طلاب.')) ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if($total_pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<?php for($i=1; $i<=$total_pages; $i++): ?>
|
|
||||||
<li class="page-item <?= $i === $page_num ? 'active' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'students', 'p'=>$i, 'search'=>$search])) ?>"><?= $i ?></a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add Modal -->
|
|
||||||
<div class="modal fade" id="addStudentModal" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-bottom-0 pb-3" style="background-color: var(--bs-light);">
|
|
||||||
<h5 class="modal-title fw-bold"><?= h(t('Add Student', 'إضافة طالب')) ?></h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'students'])) ?>" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="action" value="add">
|
|
||||||
|
|
||||||
<!-- Name Bar with Background -->
|
|
||||||
<div class="mb-3 p-3 bg-light rounded border">
|
|
||||||
<label class="form-label fw-bold"><?= h(t('Name', 'الاسم')) ?></label>
|
|
||||||
<input type="text" name="full_name" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Civil ID', 'الرقم المدني')) ?></label>
|
|
||||||
<input type="text" name="civil_id" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Picture', 'الصورة')) ?></label>
|
|
||||||
<input type="file" name="picture" class="form-control" accept="image/*">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('WhatsApp', 'واتساب')) ?></label>
|
|
||||||
<input type="text" name="whatsapp" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Plan Key', 'الخطة')) ?></label>
|
|
||||||
<input type="text" name="plan_key" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Payment Status', 'حالة الدفع')) ?></label>
|
|
||||||
<input type="text" name="payment_status" class="form-control" value="active">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active"><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive"><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,310 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_subjects.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('subjects', 'view');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
// Fetch classes for dropdown and filter
|
|
||||||
$classes_stmt = db()->query("SELECT id, name_en, name_ar FROM classes ORDER BY id DESC");
|
|
||||||
$all_classes = $classes_stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('subjects', 'delete');
|
|
||||||
$stmt = db()->prepare("DELETE FROM subjects WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'subjects']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('subjects', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$class_id = !empty($_POST['class_id']) ? (int)$_POST['class_id'] : null;
|
|
||||||
$title_en = $_POST['title_en'] ?? '';
|
|
||||||
$title_ar = $_POST['title_ar'] ?? '';
|
|
||||||
$summary_en = $_POST['summary_en'] ?? '';
|
|
||||||
$summary_ar = $_POST['summary_ar'] ?? '';
|
|
||||||
$status = $_POST['status'] ?? 'active';
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE subjects SET class_id=?, title_en=?, title_ar=?, summary_en=?, summary_ar=?, status=? WHERE id=?");
|
|
||||||
$stmt->execute([$class_id, $title_en, $title_ar, $summary_en, $summary_ar, $status, $post_id]);
|
|
||||||
} else {
|
|
||||||
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $title_en)));
|
|
||||||
if (empty($slug)) $slug = 'subject';
|
|
||||||
$slug .= '-' . substr(md5(uniqid()), 0, 5);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("INSERT INTO subjects (slug, class_id, title_en, title_ar, summary_en, summary_ar, status) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$slug, $class_id, $title_en, $title_ar, $summary_en, $summary_ar, $status]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'subjects']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list view
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$filter_class = $_GET['class_id'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where_clauses = [];
|
|
||||||
$params = [];
|
|
||||||
|
|
||||||
if ($search !== '') {
|
|
||||||
$where_clauses[] = "(s.title_en LIKE ? OR s.title_ar LIKE ?)";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
}
|
|
||||||
if ($filter_class !== '') {
|
|
||||||
$where_clauses[] = "s.class_id = ?";
|
|
||||||
$params[] = $filter_class;
|
|
||||||
}
|
|
||||||
|
|
||||||
$where = "";
|
|
||||||
if (count($where_clauses) > 0) {
|
|
||||||
$where = "WHERE " . implode(" AND ", $where_clauses);
|
|
||||||
}
|
|
||||||
|
|
||||||
$total_stmt = db()->prepare("SELECT COUNT(*) FROM subjects s $where");
|
|
||||||
$total_stmt->execute($params);
|
|
||||||
$total = $total_stmt->fetchColumn();
|
|
||||||
$pages = ceil($total / $limit);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("
|
|
||||||
SELECT s.*, c.name_en AS class_name_en, c.name_ar AS class_name_ar
|
|
||||||
FROM subjects s
|
|
||||||
LEFT JOIN classes c ON s.class_id = c.id
|
|
||||||
$where
|
|
||||||
ORDER BY s.id DESC
|
|
||||||
LIMIT $limit OFFSET $offset
|
|
||||||
");
|
|
||||||
$stmt->execute($params);
|
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Subjects', 'المواد')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<?php if (has_permission('subjects', 'add')): ?>
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSubjectModal" style="background-color: var(--accent); border-color: var(--accent);">+ <?= h(t('Add Subject', 'إضافة مادة')) ?></button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="get" class="d-flex gap-2 flex-wrap align-items-center">
|
|
||||||
<input type="hidden" name="page" value="subjects">
|
|
||||||
|
|
||||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search name...', 'بحث في الاسم...')) ?>" value="<?= h($search) ?>">
|
|
||||||
|
|
||||||
<select name="class_id" class="form-control w-auto">
|
|
||||||
<option value=""><?= h(t('All Classes', 'جميع الصفوف')) ?></option>
|
|
||||||
<?php foreach($all_classes as $c): ?>
|
|
||||||
<option value="<?= $c['id'] ?>" <?= $filter_class == $c['id'] ? 'selected' : '' ?>>
|
|
||||||
<?= h(current_lang() === 'ar' ? $c['name_ar'] : $c['name_en']) ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
|
||||||
<?php if ($search || $filter_class !== ''): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page'=>'subjects'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th><?= h(t('Subject Name', 'اسم المادة')) ?></th>
|
|
||||||
<th><?= h(t('Status', 'الحالة')) ?></th>
|
|
||||||
<th><?= h(t('Details', 'تفاصيل')) ?></th>
|
|
||||||
<th><?= h(t('Class', 'الصف')) ?></th>
|
|
||||||
<th><?= h(t('Actions', 'إجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($items as $row): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h((string)$row['id']) ?></td>
|
|
||||||
<td>
|
|
||||||
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $row['title_ar'] : $row['title_en']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="small text-secondary text-truncate" style="max-width: 250px;">
|
|
||||||
<?= h(current_lang() === 'ar' ? $row['summary_ar'] : $row['summary_en']) ?>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if ($row['class_id']): ?>
|
|
||||||
<span class="badge bg-light text-dark border">
|
|
||||||
<?= h(current_lang() === 'ar' ? $row['class_name_ar'] : $row['class_name_en']) ?>
|
|
||||||
</span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="text-muted">-</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (has_permission('subjects', 'edit')): ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editSubjectModal<?= $row['id'] ?>" title="<?= h(t('Edit', 'تعديل')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (has_permission('subjects', 'delete')): ?>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'subjects', 'action'=>'delete', 'id'=>$row['id']])) ?>" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>');">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="<?= h(t('Delete', 'حذف')) ?>"><svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg></button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Edit Subject Modal -->
|
|
||||||
<div class="modal fade" id="editSubjectModal<?= $row['id'] ?>" tabindex="-1" aria-labelledby="editSubjectModalLabel<?= $row['id'] ?>" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="editSubjectModalLabel<?= $row['id'] ?>"><?= h(t('Edit Subject', 'تعديل المادة')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'subjects'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="edit">
|
|
||||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Class', 'الصف')) ?></label>
|
|
||||||
<select name="class_id" class="form-control" required>
|
|
||||||
<option value=""><?= h(t('-- Select Class --', '-- اختر الصف --')) ?></option>
|
|
||||||
<?php foreach($all_classes as $c): ?>
|
|
||||||
<option value="<?= $c['id'] ?>" <?= $row['class_id'] == $c['id'] ? 'selected' : '' ?>>
|
|
||||||
<?= h(current_lang() === 'ar' ? $c['name_ar'] : $c['name_en']) ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (English)', 'الاسم (بالانجليزية)')) ?></label>
|
|
||||||
<input type="text" name="title_en" class="form-control" value="<?= h($row['title_en']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (Arabic)', 'الاسم (بالعربية)')) ?></label>
|
|
||||||
<input type="text" name="title_ar" class="form-control" value="<?= h($row['title_ar']) ?>" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<label class="form-label"><?= h(t('Details (English)', 'تفاصيل (بالانجليزية)')) ?></label>
|
|
||||||
<textarea name="summary_en" class="form-control" rows="3"><?= h($row['summary_en']) ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<label class="form-label"><?= h(t('Details (Arabic)', 'تفاصيل (بالعربية)')) ?></label>
|
|
||||||
<textarea name="summary_ar" class="form-control" rows="3"><?= h($row['summary_ar']) ?></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Edit Subject Modal -->
|
|
||||||
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(!$items): ?>
|
|
||||||
<tr><td colspan="5" class="text-center text-secondary py-3"><?= h(t('No subjects found.', 'لا توجد مواد.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<?php for($i=1; $i<=$pages; $i++): ?>
|
|
||||||
<li class="page-item <?= $i === $page_num ? 'active' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'subjects', 'p'=>$i, 'search'=>$search, 'class_id'=>$filter_class])) ?>"><?= $i ?></a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add Subject Modal -->
|
|
||||||
<div class="modal fade" id="addSubjectModal" tabindex="-1" aria-labelledby="addSubjectModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="addSubjectModalLabel"><?= h(t('Add Subject', 'إضافة مادة')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'subjects'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="add">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Class', 'الصف')) ?></label>
|
|
||||||
<select name="class_id" class="form-control" required>
|
|
||||||
<option value=""><?= h(t('-- Select Class --', '-- اختر الصف --')) ?></option>
|
|
||||||
<?php foreach($all_classes as $c): ?>
|
|
||||||
<option value="<?= $c['id'] ?>">
|
|
||||||
<?= h(current_lang() === 'ar' ? $c['name_ar'] : $c['name_en']) ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (English)', 'الاسم (بالانجليزية)')) ?></label>
|
|
||||||
<input type="text" name="title_en" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name (Arabic)', 'الاسم (بالعربية)')) ?></label>
|
|
||||||
<input type="text" name="title_ar" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<label class="form-label"><?= h(t('Details (English)', 'تفاصيل (بالانجليزية)')) ?></label>
|
|
||||||
<textarea name="summary_en" class="form-control" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<label class="form-label"><?= h(t('Details (Arabic)', 'تفاصيل (بالعربية)')) ?></label>
|
|
||||||
<textarea name="summary_ar" class="form-control" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Add Subject Modal -->
|
|
||||||
@ -1,320 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_teachers.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('teachers', 'view');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('teachers', 'delete');
|
|
||||||
$stmt = db()->prepare("DELETE FROM teachers WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'teachers']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('teachers', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$name = $_POST['name'] ?? '';
|
|
||||||
$email = $_POST['email'] ?? '';
|
|
||||||
$phone = $_POST['phone'] ?? '';
|
|
||||||
$bio = $_POST['bio'] ?? '';
|
|
||||||
$status = $_POST['status'] ?? 'active';
|
|
||||||
$raw_password = $_POST['password'] ?? '';
|
|
||||||
$photo_path = '';
|
|
||||||
$existing_password = '';
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("SELECT photo_path, password FROM teachers WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if ($existing) {
|
|
||||||
$photo_path = $existing['photo_path'];
|
|
||||||
$existing_password = $existing['password'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0777, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($_FILES['photo']['tmp_name'])) {
|
|
||||||
$filename = 'teacher_' . time() . '_' . basename($_FILES['photo']['name']);
|
|
||||||
$target = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['photo']['tmp_name'], $target)) {
|
|
||||||
$photo_path = 'assets/images/uploads/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$final_password = '';
|
|
||||||
if ($post_action === 'add') {
|
|
||||||
$final_password = $raw_password ? password_hash($raw_password, PASSWORD_DEFAULT) : '';
|
|
||||||
} else {
|
|
||||||
if ($raw_password) {
|
|
||||||
$final_password = password_hash($raw_password, PASSWORD_DEFAULT);
|
|
||||||
} else {
|
|
||||||
$final_password = $existing_password;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE teachers SET name=?, email=?, phone=?, bio=?, photo_path=?, password=?, status=? WHERE id=?");
|
|
||||||
$stmt->execute([$name, $email, $phone, $bio, $photo_path, $final_password, $status, $post_id]);
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("INSERT INTO teachers (name, email, phone, bio, photo_path, password, status) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$name, $email, $phone, $bio, $photo_path, $final_password, $status]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'teachers']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list view
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where = "";
|
|
||||||
$params = [];
|
|
||||||
if ($search !== '') {
|
|
||||||
$where = "WHERE name LIKE ? OR email LIKE ? OR phone LIKE ?";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$total_stmt = db()->prepare("SELECT COUNT(*) FROM teachers $where");
|
|
||||||
$total_stmt->execute($params);
|
|
||||||
$total = $total_stmt->fetchColumn();
|
|
||||||
$pages = ceil($total / $limit);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT * FROM teachers $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
|
|
||||||
$stmt->execute($params);
|
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Teachers', 'المعلمون')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<?php if (has_permission('teachers', 'add')): ?>
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addTeacherModal" style="background-color: var(--accent); border-color: var(--accent);">+ <?= h(t('Add Teacher', 'إضافة معلم')) ?></button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="get" class="d-flex gap-2 align-items-center">
|
|
||||||
<input type="hidden" name="page" value="teachers">
|
|
||||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
|
||||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
|
||||||
<?php if ($search): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page'=>'teachers'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table align-middle dashboard-table mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= h(t('Photo', 'الصورة')) ?></th>
|
|
||||||
<th><?= h(t('Name', 'الاسم')) ?></th>
|
|
||||||
<th><?= h(t('Status', 'الحالة')) ?></th>
|
|
||||||
<th><?= h(t('Contact', 'جهة الاتصال')) ?></th>
|
|
||||||
<th><?= h(t('Actions', 'إجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($items as $row): ?>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<?php if(!empty($row['photo_path'])): ?>
|
|
||||||
<img src="<?= h(asset_url($row['photo_path'])) ?>" alt="" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="bg-light text-secondary d-flex align-items-center justify-content-center" style="width: 40px; height: 40px; border-radius: 50%;">
|
|
||||||
<?= h(strtoupper(substr($row['name'], 0, 1))) ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="fw-semibold"><?= h($row['name']) ?></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (isset($row['status']) && $row['status'] === 'inactive'): ?>
|
|
||||||
<span class="badge bg-secondary"><?= h(t('Inactive', 'غير نشط')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-success"><?= h(t('Active', 'نشط')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (!empty($row['email'])): ?><div><small><?= h($row['email']) ?></small></div><?php endif; ?>
|
|
||||||
<?php if (!empty($row['phone'])): ?><div><small><?= h($row['phone']) ?></small></div><?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<?php if (has_permission('teachers', 'edit')): ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editTeacherModal<?= $row['id'] ?>" title="<?= h(t('Edit', 'تعديل')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (has_permission('teachers', 'delete')): ?>
|
|
||||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'teachers', 'action'=>'delete', 'id'=>$row['id']])) ?>" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>');">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="<?= h(t('Delete', 'حذف')) ?>">
|
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Edit Teacher Modal -->
|
|
||||||
<div class="modal fade" id="editTeacherModal<?= $row['id'] ?>" tabindex="-1" aria-labelledby="editTeacherModalLabel<?= $row['id'] ?>" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="editTeacherModalLabel<?= $row['id'] ?>"><?= h(t('Edit Teacher', 'تعديل المعلم')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" enctype="multipart/form-data" action="<?= h(app_url('admin.php', ['page'=>'teachers'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="edit">
|
|
||||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name', 'الاسم')) ?></label>
|
|
||||||
<input type="text" name="name" class="form-control" value="<?= h($row['name']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email" class="form-control" value="<?= h($row['email'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Phone', 'رقم الهاتف')) ?></label>
|
|
||||||
<input type="tel" name="phone" class="form-control" value="<?= h($row['phone'] ?? '') ?>" dir="ltr">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label">
|
|
||||||
<?= h(t('Password', 'كلمة المرور')) ?>
|
|
||||||
<small class="text-muted">(<?= h(t('Leave blank', 'اتركه فارغاً')) ?>)</small>
|
|
||||||
</label>
|
|
||||||
<input type="password" name="password" class="form-control" autocomplete="new-password">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Photo', 'الصورة')) ?></label>
|
|
||||||
<input type="file" name="photo" class="form-control" accept="image/*">
|
|
||||||
<?php if (!empty($row['photo_path'])): ?>
|
|
||||||
<div class="mt-2">
|
|
||||||
<img src="<?= h(asset_url($row['photo_path'])) ?>" alt="Photo" style="height: 40px; border-radius: 4px; object-fit: cover;">
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active" <?= (isset($row['status']) && $row['status'] === 'active') ? 'selected' : '' ?>><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive" <?= (isset($row['status']) && $row['status'] === 'inactive') ? 'selected' : '' ?>><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Bio', 'نبذة')) ?></label>
|
|
||||||
<textarea name="bio" class="form-control" rows="3"><?= h($row['bio'] ?? '') ?></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Edit Teacher Modal -->
|
|
||||||
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(!$items): ?>
|
|
||||||
<tr><td colspan="5" class="text-center text-secondary py-3"><?= h(t('No teachers found.', 'لا يوجد معلمون.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<?php for($i=1; $i<=$pages; $i++): ?>
|
|
||||||
<li class="page-item <?= $i === $page_num ? 'active' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'teachers', 'p'=>$i, 'search'=>$search])) ?>"><?= $i ?></a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add Teacher Modal -->
|
|
||||||
<div class="modal fade" id="addTeacherModal" tabindex="-1" aria-labelledby="addTeacherModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0 bg-dark-blue">
|
|
||||||
<h5 class="modal-title section-title" id="addTeacherModalLabel"><?= h(t('Add Teacher', 'إضافة معلم')) ?></h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" enctype="multipart/form-data" action="<?= h(app_url('admin.php', ['page'=>'teachers'])) ?>">
|
|
||||||
<input type="hidden" name="action" value="add">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name', 'الاسم')) ?></label>
|
|
||||||
<input type="text" name="name" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Phone', 'رقم الهاتف')) ?></label>
|
|
||||||
<input type="tel" name="phone" class="form-control" dir="ltr">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Password', 'كلمة المرور')) ?></label>
|
|
||||||
<input type="password" name="password" class="form-control" autocomplete="new-password" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Photo', 'الصورة')) ?></label>
|
|
||||||
<input type="file" name="photo" class="form-control" accept="image/*">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Status', 'الحالة')) ?></label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="active" selected><?= h(t('Active', 'نشط')) ?></option>
|
|
||||||
<option value="inactive"><?= h(t('Inactive', 'غير نشط')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12 mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Bio', 'نبذة')) ?></label>
|
|
||||||
<textarea name="bio" class="form-control" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Add Teacher Modal -->
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_thawani.php
|
|
||||||
if (!isset($pdo)) {
|
|
||||||
$pdo = db();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_thawani') {
|
|
||||||
$secret = $_POST['thawani_secret_key'] ?? '';
|
|
||||||
$publishable = $_POST['thawani_publishable_key'] ?? '';
|
|
||||||
$mode = $_POST['thawani_mode'] ?? 'test';
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE platform_profile SET thawani_secret_key = :secret, thawani_publishable_key = :publishable, thawani_mode = :mode WHERE id = 1");
|
|
||||||
$stmt->execute([
|
|
||||||
'secret' => $secret,
|
|
||||||
'publishable' => $publishable,
|
|
||||||
'mode' => $mode
|
|
||||||
]);
|
|
||||||
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'thawani', 'saved' => 1]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT thawani_secret_key, thawani_publishable_key, thawani_mode FROM platform_profile WHERE id = 1");
|
|
||||||
$prof = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
||||||
$mode = $prof['thawani_mode'] ?? 'test';
|
|
||||||
?>
|
|
||||||
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Integrations', 'التكاملات')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Thawani Gateway', 'بوابة Thawani')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Configure your Thawani payment gateway settings.', 'قم بتكوين إعدادات بوابة الدفع Thawani.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (!empty($_GET['saved'])): ?>
|
|
||||||
<div class="alert alert-success"><?= h(t('Thawani settings updated successfully.', 'تم تحديث إعدادات Thawani بنجاح.')) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="panel-card" style="max-width: 600px;">
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="action" value="save_thawani">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Secret Key', 'المفتاح السري')) ?></label>
|
|
||||||
<input type="password" name="thawani_secret_key" class="form-control" value="<?= h($prof['thawani_secret_key'] ?? '') ?>" placeholder="sk_...">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Publishable Key', 'المفتاح القابل للنشر')) ?></label>
|
|
||||||
<input type="text" name="thawani_publishable_key" class="form-control" value="<?= h($prof['thawani_publishable_key'] ?? '') ?>" placeholder="pk_...">
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Mode', 'الوضع')) ?></label>
|
|
||||||
<select name="thawani_mode" class="form-select">
|
|
||||||
<option value="test" <?= $mode === 'test' ? 'selected' : '' ?>><?= h(t('Test Mode', 'وضع الاختبار')) ?></option>
|
|
||||||
<option value="live" <?= $mode === 'live' ? 'selected' : '' ?>><?= h(t('Live Mode', 'الوضع الحي')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<button type="submit" class="btn btn-primary px-4" style="background-color: var(--accent); border-color: var(--accent);">
|
|
||||||
<?= h(t('Save Settings', 'حفظ الإعدادات')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
306
admin_users.php
@ -1,306 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_users.php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
require_permission('users', 'view');
|
|
||||||
|
|
||||||
$current_u = get_logged_in_user();
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? $action;
|
|
||||||
$post_id = (int)($_POST['id'] ?? $id);
|
|
||||||
|
|
||||||
if ($post_action === 'delete' && $post_id > 0) {
|
|
||||||
require_permission('users', 'delete');
|
|
||||||
// Prevent deleting oneself
|
|
||||||
if ($post_id !== (int)$_SESSION['user_id']) {
|
|
||||||
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'users']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' || $post_action === 'add') {
|
|
||||||
require_permission('users', $post_action === 'add' ? 'add' : 'edit');
|
|
||||||
$name = trim($_POST['name'] ?? '');
|
|
||||||
$email = trim($_POST['email'] ?? '');
|
|
||||||
$phone = trim($_POST['phone'] ?? '');
|
|
||||||
$role = $_POST['role'] ?? 'user';
|
|
||||||
if (!in_array($role, ['admin', 'user'])) {
|
|
||||||
$role = 'user';
|
|
||||||
}
|
|
||||||
$role_id = $_POST['role_id'] ?? '';
|
|
||||||
if ($role_id === '') $role_id = null;
|
|
||||||
|
|
||||||
$raw_password = $_POST['password'] ?? '';
|
|
||||||
$profile_picture = '';
|
|
||||||
$existing_password = '';
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("SELECT profile_picture, password FROM users WHERE id = ?");
|
|
||||||
$stmt->execute([$post_id]);
|
|
||||||
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if ($existing) {
|
|
||||||
$profile_picture = $existing['profile_picture'];
|
|
||||||
$existing_password = $existing['password'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0777, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($_FILES['photo']['tmp_name'])) {
|
|
||||||
$filename = 'user_' . time() . '_' . basename($_FILES['photo']['name']);
|
|
||||||
$target = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['photo']['tmp_name'], $target)) {
|
|
||||||
$profile_picture = 'assets/images/uploads/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'add') {
|
|
||||||
$final_password = $raw_password ? password_hash($raw_password, PASSWORD_DEFAULT) : '';
|
|
||||||
} else {
|
|
||||||
if ($raw_password) {
|
|
||||||
$final_password = password_hash($raw_password, PASSWORD_DEFAULT);
|
|
||||||
} else {
|
|
||||||
$final_password = $existing_password;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'edit' && $post_id > 0) {
|
|
||||||
$stmt = db()->prepare("UPDATE users SET name=?, email=?, phone=?, role=?, profile_picture=?, password=?, role_id=? WHERE id=?");
|
|
||||||
$stmt->execute([$name, $email, $phone, $role, $profile_picture, $final_password, $role_id, $post_id]);
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("INSERT INTO users (name, email, phone, role, profile_picture, password, role_id) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$name, $email, $phone, $role, $profile_picture, $final_password, $role_id]);
|
|
||||||
}
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'users']));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$search = $_GET['search'] ?? '';
|
|
||||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
|
||||||
$limit = 10;
|
|
||||||
$offset = ($page_num - 1) * $limit;
|
|
||||||
|
|
||||||
$where = "1=1";
|
|
||||||
$params = [];
|
|
||||||
if ($search) {
|
|
||||||
$where .= " AND (u.name LIKE ? OR u.email LIKE ?)";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
$params[] = "%$search%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT COUNT(*) FROM users u WHERE $where");
|
|
||||||
$stmt->execute($params);
|
|
||||||
$total = $stmt->fetchColumn();
|
|
||||||
$total_pages = ceil($total / $limit);
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT u.*, r.name as role_name FROM users u LEFT JOIN roles r ON u.role_id = r.id WHERE $where ORDER BY u.id DESC LIMIT ? OFFSET ?");
|
|
||||||
$params[] = $limit;
|
|
||||||
$params[] = $offset;
|
|
||||||
foreach($params as $k => $v) {
|
|
||||||
$stmt->bindValue($k+1, $v, is_int($v) ? PDO::PARAM_INT : PDO::PARAM_STR);
|
|
||||||
}
|
|
||||||
$stmt->execute();
|
|
||||||
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$stmt = db()->query("SELECT id, name FROM roles ORDER BY name ASC");
|
|
||||||
$roles_list = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
?>
|
|
||||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Platform Users', 'مستخدمي المنصة')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Manage administrators and staff.', 'إدارة المسؤولين والموظفين.')) ?></p>
|
|
||||||
</div>
|
|
||||||
<?php if (has_permission('users', 'add')): ?>
|
|
||||||
<button class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#userModal" onclick="editUser(null)">
|
|
||||||
<?= h(t('Add User', 'إضافة مستخدم')) ?>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<form method="GET" action="admin.php" class="d-flex gap-2">
|
|
||||||
<input type="hidden" name="page" value="users">
|
|
||||||
<input type="text" name="search" class="form-control" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
|
||||||
<button type="submit" class="btn btn-outline-dark"><?= h(t('Search', 'بحث')) ?></button>
|
|
||||||
<?php if ($search): ?>
|
|
||||||
<a href="<?= h(app_url('admin.php', ['page' => 'users'])) ?>" class="btn btn-link text-secondary"><?= h(t('Clear', 'مسح')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card p-0 overflow-hidden">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover align-middle mb-0">
|
|
||||||
<thead class="bg-light">
|
|
||||||
<tr>
|
|
||||||
<th class="px-4 py-3"><?= h(t('Name', 'الاسم')) ?></th>
|
|
||||||
<th class="py-3"><?= h(t('Email', 'البريد الإلكتروني')) ?></th>
|
|
||||||
<th class="py-3"><?= h(t('Phone', 'الهاتف')) ?></th>
|
|
||||||
<th class="py-3"><?= h(t('Role Group', 'الصلاحيات')) ?></th>
|
|
||||||
<th class="px-4 py-3 text-end"><?= h(t('Actions', 'الإجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($users as $u): ?>
|
|
||||||
<tr>
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
|
||||||
<?php if ($u['profile_picture']): ?>
|
|
||||||
<img src="<?= h(asset_url($u['profile_picture'])) ?>" class="rounded-circle" style="width:40px;height:40px;object-fit:cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="bg-light rounded-circle d-flex align-items-center justify-content-center text-secondary fw-bold" style="width:40px;height:40px;">
|
|
||||||
<?= h(strtoupper(substr($u['name'], 0, 1))) ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div>
|
|
||||||
<div class="fw-bold text-dark"><?= h($u['name']) ?></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="py-3"><?= h($u['email']) ?></td>
|
|
||||||
<td class="py-3"><?= h($u['phone']) ?></td>
|
|
||||||
<td class="py-3">
|
|
||||||
<?php if ($u['role_name']): ?>
|
|
||||||
<span class="badge bg-info text-dark"><?= h($u['role_name']) ?></span>
|
|
||||||
<?php elseif ($u['role'] === 'admin'): ?>
|
|
||||||
<span class="badge bg-primary text-white"><?= h(t('Admin', 'مسؤول')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary text-white"><?= h(t('User', 'مستخدم')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-end">
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<?php if (has_permission('users', 'edit')): ?>
|
|
||||||
<button class="btn btn-outline-dark" onclick='editUser(<?= htmlspecialchars(json_encode($u), ENT_QUOTES) ?>)' data-bs-toggle="modal" data-bs-target="#userModal">
|
|
||||||
<svg width="16" height="16" fill="currentColor" class="me-1" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg><?= h(t('Edit', 'تعديل')) ?>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if (has_permission('users', 'delete') && (int)$u['id'] !== (int)$_SESSION['user_id']): ?>
|
|
||||||
<form method="POST" action="admin_users.php" class="d-inline" onsubmit="return confirm('<?= h(t('Delete this user?', 'حذف هذا المستخدم؟')) ?>');">
|
|
||||||
<input type="hidden" name="action" value="delete">
|
|
||||||
<input type="hidden" name="id" value="<?= h($u['id']) ?>">
|
|
||||||
<button class="btn btn-outline-danger" type="submit">
|
|
||||||
<svg width="16" height="16" fill="currentColor" class="me-1" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg><?= h(t('Delete', 'حذف')) ?>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if (empty($users)): ?>
|
|
||||||
<tr><td colspan="5" class="text-center py-4 text-secondary"><?= h(t('No users found.', 'لا يوجد مستخدمين.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($total_pages > 1): ?>
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<?php for($i=1; $i<=$total_pages; $i++): ?>
|
|
||||||
<li class="page-item <?= $i === $page_num ? 'active' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page' => 'users', 'p' => $i, 'search' => $search])) ?>"><?= $i ?></a>
|
|
||||||
</li>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- User Modal -->
|
|
||||||
<div class="modal fade" id="userModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<form class="modal-content" method="POST" action="admin_users.php" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="action" id="userAction" value="add">
|
|
||||||
<input type="hidden" name="id" id="userId" value="0">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="userModalTitle"><?= h(t('Add User', 'إضافة مستخدم')) ?></h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Name', 'الاسم')) ?></label>
|
|
||||||
<input type="text" class="form-control" name="name" id="userName" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" class="form-control" name="email" id="userEmail" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Phone', 'الهاتف')) ?></label>
|
|
||||||
<input type="text" class="form-control" name="phone" id="userPhone">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Role Group', 'مجموعة الصلاحيات')) ?></label>
|
|
||||||
<select class="form-select" name="role_id" id="userRoleId">
|
|
||||||
<option value=""><?= h(t('None (Basic User)', 'بدون (مستخدم عادي)')) ?></option>
|
|
||||||
<?php foreach($roles_list as $rl): ?>
|
|
||||||
<option value="<?= $rl['id'] ?>"><?= h($rl['name']) ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3" style="display:none;">
|
|
||||||
<label class="form-label"><?= h(t('Role (Legacy)', 'الدور القديم')) ?></label>
|
|
||||||
<select class="form-select" name="role" id="userRole">
|
|
||||||
<option value="user"><?= h(t('User', 'مستخدم')) ?></option>
|
|
||||||
<option value="admin"><?= h(t('Admin', 'مسؤول')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Password', 'كلمة المرور')) ?> <small class="text-secondary" id="userPasswordHelp" style="display:none;">(<?= h(t('Leave blank to keep current', 'اتركه فارغاً للاحتفاظ بالحالي')) ?>)</small></label>
|
|
||||||
<input type="password" class="form-control" name="password" id="userPassword">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Profile Picture', 'صورة الملف الشخصي')) ?></label>
|
|
||||||
<input type="file" class="form-control" name="photo" accept="image/*">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-dark"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function editUser(user) {
|
|
||||||
if (user) {
|
|
||||||
document.getElementById('userAction').value = 'edit';
|
|
||||||
document.getElementById('userId').value = user.id;
|
|
||||||
document.getElementById('userModalTitle').innerText = '<?= h(t('Edit User', 'تعديل المستخدم')) ?>';
|
|
||||||
document.getElementById('userName').value = user.name;
|
|
||||||
document.getElementById('userEmail').value = user.email;
|
|
||||||
document.getElementById('userPhone').value = user.phone || '';
|
|
||||||
document.getElementById('userRole').value = user.role;
|
|
||||||
document.getElementById('userRoleId').value = user.role_id || '';
|
|
||||||
document.getElementById('userPassword').required = false;
|
|
||||||
document.getElementById('userPasswordHelp').style.display = 'inline';
|
|
||||||
} else {
|
|
||||||
document.getElementById('userAction').value = 'add';
|
|
||||||
document.getElementById('userId').value = '0';
|
|
||||||
document.getElementById('userModalTitle').innerText = '<?= h(t('Add User', 'إضافة مستخدم')) ?>';
|
|
||||||
document.getElementById('userName').value = '';
|
|
||||||
document.getElementById('userEmail').value = '';
|
|
||||||
document.getElementById('userPhone').value = '';
|
|
||||||
document.getElementById('userRole').value = 'user';
|
|
||||||
document.getElementById('userRoleId').value = '';
|
|
||||||
document.getElementById('userPassword').required = true;
|
|
||||||
document.getElementById('userPasswordHelp').style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
// admin_wablas.php
|
|
||||||
if (!isset($pdo)) {
|
|
||||||
$pdo = db();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_wablas') {
|
|
||||||
$domain = $_POST['wablas_domain'] ?? '';
|
|
||||||
$token = $_POST['wablas_token'] ?? '';
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE platform_profile SET wablas_domain = :domain, wablas_token = :token WHERE id = 1");
|
|
||||||
$stmt->execute([
|
|
||||||
'domain' => $domain,
|
|
||||||
'token' => $token
|
|
||||||
]);
|
|
||||||
|
|
||||||
header('Location: ' . app_url('admin.php', ['page' => 'wablas', 'saved' => 1]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT wablas_domain, wablas_token FROM platform_profile WHERE id = 1");
|
|
||||||
$prof = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
||||||
?>
|
|
||||||
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Integrations', 'التكاملات')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Wablas Gateway', 'بوابة Wablas')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Configure your Wablas WhatsApp gateway settings.', 'قم بتكوين إعدادات بوابة Wablas لرسائل الواتساب.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (!empty($_GET['saved'])): ?>
|
|
||||||
<div class="alert alert-success"><?= h(t('Wablas settings updated successfully.', 'تم تحديث إعدادات Wablas بنجاح.')) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="panel-card" style="max-width: 600px;">
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="action" value="save_wablas">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('Wablas Domain / URL', 'نطاق Wablas / الرابط')) ?></label>
|
|
||||||
<input type="text" name="wablas_domain" class="form-control" value="<?= h($prof['wablas_domain'] ?? '') ?>" placeholder="e.g. https://solo.wablas.com">
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label fw-medium"><?= h(t('API Token', 'رمز API')) ?></label>
|
|
||||||
<input type="password" name="wablas_token" class="form-control" value="<?= h($prof['wablas_token'] ?? '') ?>" placeholder="Your Wablas API token">
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<button type="submit" class="btn btn-primary px-4" style="background-color: var(--accent); border-color: var(--accent);">
|
|
||||||
<?= h(t('Save Settings', 'حفظ الإعدادات')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@ -1,521 +1,403 @@
|
|||||||
:root {
|
body {
|
||||||
--bg: #f3f5f7;
|
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
|
||||||
--surface: #ffffff;
|
background-size: 400% 400%;
|
||||||
--surface-muted: #f8fafc;
|
animation: gradient 15s ease infinite;
|
||||||
--text: #111827;
|
color: #212529;
|
||||||
--muted: #667085;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
--border: #d9e1ea;
|
|
||||||
--accent: #2563eb;
|
|
||||||
--accent-soft: #eff6ff;
|
|
||||||
--success: #0f766e;
|
|
||||||
--shadow: 0 14px 40px rgba(15, 23, 42, 0.05);
|
|
||||||
--radius-sm: 8px;
|
|
||||||
--radius-md: 12px;
|
|
||||||
--radius-lg: 16px;
|
|
||||||
--space-1: 0.25rem;
|
|
||||||
--space-2: 0.5rem;
|
|
||||||
--space-3: 0.75rem;
|
|
||||||
--space-4: 1rem;
|
|
||||||
--space-5: 1.5rem;
|
|
||||||
--space-6: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.app-shell {
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
font-family: 'Cairo', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
|
||||||
|
|
||||||
body.rtl {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.rtl .navbar-brand,
|
|
||||||
body.rtl .nav-link,
|
|
||||||
body.rtl .btn,
|
|
||||||
body.rtl .form-control,
|
|
||||||
body.rtl .form-select,
|
|
||||||
body.rtl .form-check,
|
|
||||||
body.rtl .table {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: rgba(37, 99, 235, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar {
|
|
||||||
backdrop-filter: saturate(180%) blur(12px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--muted);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 0.5rem 0.75rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link.active,
|
|
||||||
.nav-link:hover,
|
|
||||||
.nav-link:focus {
|
|
||||||
color: var(--text);
|
|
||||||
background: var(--accent-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section {
|
|
||||||
background: rgba(255,255,255,0.78);
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-lg-6 {
|
|
||||||
padding-top: 4.5rem;
|
|
||||||
padding-bottom: 4.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-title,
|
|
||||||
.section-title {
|
|
||||||
color: var(--text);
|
|
||||||
line-height: 1.05;
|
|
||||||
letter-spacing: -0.03em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-title {
|
|
||||||
font-size: clamp(1.8rem, 4vw, 2.75rem);
|
|
||||||
color: var(--accent);
|
|
||||||
max-width: 15ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: clamp(1.6rem, 2.8vw, 2.4rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lead {
|
|
||||||
max-width: 62ch;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-card,
|
|
||||||
.subject-card,
|
|
||||||
.plan-card,
|
|
||||||
.workflow-card,
|
|
||||||
.metric-card {
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-card,
|
|
||||||
.subject-card,
|
|
||||||
.plan-card,
|
|
||||||
.workflow-card {
|
|
||||||
padding: 1.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-card {
|
|
||||||
padding: 1rem 1.1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-card strong {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: -0.04em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-card span,
|
|
||||||
.metric-card p {
|
|
||||||
color: var(--muted);
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-card.tall {
|
.main-wrapper {
|
||||||
min-height: 132px;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-stat-grid .metric-card {
|
|
||||||
min-height: 96px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-tag {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0.35rem 0.6rem;
|
min-height: 100vh;
|
||||||
border-radius: 999px;
|
width: 100%;
|
||||||
border: 1px solid var(--border);
|
padding: 20px;
|
||||||
background: var(--surface-muted);
|
box-sizing: border-box;
|
||||||
color: var(--text);
|
|
||||||
font-size: 0.78rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stack-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stack-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.9rem 0;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stack-item:last-child {
|
|
||||||
border-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stack-item p {
|
|
||||||
margin: 0.15rem 0 0;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 0.92rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: end;
|
|
||||||
gap: 1.5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subject-card,
|
|
||||||
.plan-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subject-meta-grid,
|
|
||||||
.detail-meta-grid,
|
|
||||||
.pricing-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subject-meta-grid div,
|
|
||||||
.detail-meta-grid div,
|
|
||||||
.pricing-grid div,
|
|
||||||
.summary-row {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: var(--surface-muted);
|
|
||||||
padding: 0.8rem 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subject-meta-grid strong,
|
|
||||||
.detail-meta-grid strong,
|
|
||||||
.pricing-grid strong,
|
|
||||||
.summary-row strong {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
font-size: 0.88rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subject-meta-grid span,
|
|
||||||
.detail-meta-grid span,
|
|
||||||
.pricing-grid span,
|
|
||||||
.summary-row span {
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 0.65rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-list li {
|
|
||||||
position: relative;
|
|
||||||
padding-inline-start: 1.2rem;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-list li::before {
|
|
||||||
content: '';
|
|
||||||
width: 0.42rem;
|
|
||||||
height: 0.42rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--accent);
|
|
||||||
position: absolute;
|
|
||||||
inset-inline-start: 0;
|
|
||||||
top: 0.45rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-list-tight {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workflow-card {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-index {
|
|
||||||
display: inline-flex;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--accent-soft);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
margin-bottom: 0.85rem;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.82rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workflow-card p {
|
|
||||||
color: var(--muted);
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-card.highlighted {
|
|
||||||
border-color: #cdd6e1;
|
|
||||||
box-shadow: 0 20px 48px rgba(15, 23, 42, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-price {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -0.04em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-search-wrap {
|
|
||||||
min-width: min(100%, 320px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-filter-state,
|
|
||||||
.empty-state-inline {
|
|
||||||
border: 1px dashed var(--border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(255,255,255,0.7);
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-table thead th {
|
|
||||||
color: var(--muted);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.84rem;
|
|
||||||
border-bottom-color: var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-table td,
|
|
||||||
.dashboard-table th {
|
|
||||||
padding-top: 0.85rem;
|
|
||||||
padding-bottom: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-chip {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--surface);
|
|
||||||
padding: 0.35rem 0.7rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 0.82rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-chip:hover,
|
|
||||||
.copy-chip:focus {
|
|
||||||
background: var(--accent-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
border-radius: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.72rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm {
|
|
||||||
padding: 0.45rem 0.7rem;
|
|
||||||
border-radius: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-lg {
|
|
||||||
padding: 0.8rem 1.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-dark {
|
|
||||||
background: var(--accent);
|
|
||||||
border-color: var(--accent);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline-dark {
|
|
||||||
color: var(--text);
|
|
||||||
border-color: var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline-dark:hover,
|
|
||||||
.btn-outline-dark:focus {
|
|
||||||
color: var(--text);
|
|
||||||
background: var(--accent-soft);
|
|
||||||
border-color: #c4ced8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control,
|
|
||||||
.form-select,
|
|
||||||
.form-check {
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control,
|
|
||||||
.form-select {
|
|
||||||
min-height: 46px;
|
|
||||||
border-color: var(--border);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus,
|
|
||||||
.form-select:focus,
|
|
||||||
.btn:focus,
|
|
||||||
.copy-chip:focus,
|
|
||||||
.nav-link:focus {
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(17, 24, 39, 0.12);
|
|
||||||
border-color: #bcc8d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert,
|
|
||||||
.toast,
|
|
||||||
.badge {
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.integration-rail {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-card {
|
|
||||||
position: sticky;
|
|
||||||
top: 88px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
background: rgba(255,255,255,0.86);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 991.98px) {
|
|
||||||
.sticky-card {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subject-meta-grid,
|
|
||||||
.detail-meta-grid,
|
|
||||||
.pricing-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 575.98px) {
|
|
||||||
.panel-card,
|
|
||||||
.subject-card,
|
|
||||||
.plan-card,
|
|
||||||
.workflow-card {
|
|
||||||
padding: 1.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-title {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Modal dark blue header */
|
|
||||||
.modal-header.bg-dark-blue {
|
|
||||||
background-color: #1e3a8a;
|
|
||||||
color: #ffffff;
|
|
||||||
border-top-left-radius: calc(var(--bs-modal-border-radius, .5rem) - 1px);
|
|
||||||
border-top-right-radius: calc(var(--bs-modal-border-radius, .5rem) - 1px);
|
|
||||||
}
|
|
||||||
.modal-header.bg-dark-blue .modal-title,
|
|
||||||
.modal-header.bg-dark-blue .section-title {
|
|
||||||
color: #ffffff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modern Additions */
|
|
||||||
.hero-section {
|
|
||||||
background: linear-gradient(135deg, #f8f9fc 0%, #e2e8f0 100%);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.hero-section::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
background: radial-gradient(circle, rgba(37,99,235,0.06) 0%, rgba(255,255,255,0) 70%);
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
.hero-section > .container {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.hero-section {
|
|
||||||
border-bottom: 0 !important;
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-card, .subject-card, .workflow-card, .card {
|
.chat-container {
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
width: 100%;
|
||||||
border: 1px solid rgba(0,0,0,0.06) !important;
|
max-width: 600px;
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 85vh;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header {
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
max-width: 85%;
|
||||||
|
padding: 0.85rem 1.1rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
|
||||||
|
animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(20px) scale(0.95); }
|
||||||
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.visitor {
|
||||||
|
align-self: flex-end;
|
||||||
|
background: linear-gradient(135deg, #212529 0%, #343a40 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.bot {
|
||||||
|
align-self: flex-start;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #212529;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area {
|
||||||
|
padding: 1.25rem;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area form {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area input {
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
outline: none;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
.plan-card:hover, .subject-card:hover, .workflow-card:hover, .card:hover {
|
|
||||||
transform: translateY(-6px);
|
.chat-input-area input:focus {
|
||||||
box-shadow: 0 20px 40px rgba(0,0,0,0.08) !important;
|
border-color: #23a6d5;
|
||||||
|
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
|
||||||
}
|
}
|
||||||
.metric-card {
|
|
||||||
background: rgba(255, 255, 255, 0.7);
|
.chat-input-area button {
|
||||||
backdrop-filter: blur(10px);
|
background: #212529;
|
||||||
border: 1px solid rgba(255,255,255,0.8);
|
color: #fff;
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
border: none;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
.metric-card:hover {
|
|
||||||
|
.chat-input-area button:hover {
|
||||||
|
background: #000;
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 10px 20px rgba(0,0,0,0.05);
|
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
.btn {
|
|
||||||
transition: all 0.2s ease;
|
/* Background Animations */
|
||||||
|
.bg-animations {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-2px);
|
.blob {
|
||||||
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
position: absolute;
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(80px);
|
||||||
|
animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-1 {
|
||||||
|
top: -10%;
|
||||||
|
left: -10%;
|
||||||
|
background: rgba(238, 119, 82, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-2 {
|
||||||
|
bottom: -10%;
|
||||||
|
right: -10%;
|
||||||
|
background: rgba(35, 166, 213, 0.4);
|
||||||
|
animation-delay: -7s;
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-3 {
|
||||||
|
top: 40%;
|
||||||
|
left: 30%;
|
||||||
|
background: rgba(231, 60, 126, 0.3);
|
||||||
|
animation-delay: -14s;
|
||||||
|
width: 450px;
|
||||||
|
height: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes move {
|
||||||
|
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
|
||||||
|
33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
|
||||||
|
66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
|
||||||
|
100% { transform: translate(0, 0) rotate(360deg) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin Styles */
|
||||||
|
.admin-container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 3rem auto;
|
||||||
|
padding: 2.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-container h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #212529;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0 8px;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
background: #fff;
|
||||||
|
padding: 1rem;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tr td:first-child { border-radius: 12px 0 0 12px; }
|
||||||
|
.table tr td:last-child { border-radius: 0 12px 12px 0; }
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #23a6d5;
|
||||||
|
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-card {
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add {
|
||||||
|
background: #212529;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background: #0088cc;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-url {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #555;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-table-time {
|
||||||
|
width: 15%;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-table-user {
|
||||||
|
width: 35%;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-table-ai {
|
||||||
|
width: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-messages {
|
||||||
|
text-align: center;
|
||||||
|
color: #777;
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="400" viewBox="0 0 600 400"><rect width="100%" height="100%" fill="#4e73df"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-size="24" fill="#ffffff">Introduction to Web Development</text></svg>
|
|
||||||
|
Before Width: | Height: | Size: 305 B |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="400" viewBox="0 0 600 400"><rect width="100%" height="100%" fill="#1cc88a"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-size="24" fill="#ffffff">Advanced Python Programming</text></svg>
|
|
||||||
|
Before Width: | Height: | Size: 301 B |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="400" viewBox="0 0 600 400"><rect width="100%" height="100%" fill="#f6c23e"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-size="24" fill="#ffffff">Digital Marketing Masterclass</text></svg>
|
|
||||||
|
Before Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
@ -1,140 +1,39 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
document.querySelectorAll('[data-auto-toast]').forEach((element) => {
|
const chatForm = document.getElementById('chat-form');
|
||||||
const toast = new bootstrap.Toast(element, { delay: 3200 });
|
const chatInput = document.getElementById('chat-input');
|
||||||
toast.show();
|
const chatMessages = document.getElementById('chat-messages');
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-copy-text]').forEach((button) => {
|
const appendMessage = (text, sender) => {
|
||||||
button.addEventListener('click', async () => {
|
const msgDiv = document.createElement('div');
|
||||||
const text = button.getAttribute('data-copy-text') || '';
|
msgDiv.classList.add('message', sender);
|
||||||
try {
|
msgDiv.textContent = text;
|
||||||
await navigator.clipboard.writeText(text);
|
chatMessages.appendChild(msgDiv);
|
||||||
const original = button.textContent;
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||||
button.textContent = document.documentElement.lang === 'ar' ? 'تم النسخ' : 'Copied';
|
|
||||||
setTimeout(() => {
|
|
||||||
button.textContent = original;
|
|
||||||
}, 1200);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Copy failed', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const searchInput = document.querySelector('[data-subject-search]');
|
|
||||||
if (searchInput) {
|
|
||||||
const items = Array.from(document.querySelectorAll('.subject-grid-item'));
|
|
||||||
const emptyState = document.querySelector('[data-empty-state]');
|
|
||||||
|
|
||||||
const applyFilter = () => {
|
|
||||||
const query = searchInput.value.trim().toLowerCase();
|
|
||||||
let visible = 0;
|
|
||||||
items.forEach((item) => {
|
|
||||||
const text = item.getAttribute('data-filter-text') || '';
|
|
||||||
const match = text.includes(query);
|
|
||||||
item.classList.toggle('d-none', !match);
|
|
||||||
if (match) visible += 1;
|
|
||||||
});
|
|
||||||
if (emptyState) {
|
|
||||||
emptyState.classList.toggle('d-none', visible !== 0);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
searchInput.addEventListener('input', applyFilter);
|
chatForm.addEventListener('submit', async (e) => {
|
||||||
}
|
e.preventDefault();
|
||||||
|
const message = chatInput.value.trim();
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
// Convert SweetAlert2 popups
|
appendMessage(message, 'visitor');
|
||||||
if (typeof Swal !== 'undefined') {
|
chatInput.value = '';
|
||||||
|
|
||||||
// 1. Convert Flash Alerts (success, danger, warning)
|
try {
|
||||||
const validAlerts = [];
|
const response = await fetch('api/chat.php', {
|
||||||
document.querySelectorAll('.alert').forEach(alertEl => {
|
method: 'POST',
|
||||||
// Must be one of the flash message types
|
headers: { 'Content-Type': 'application/json' },
|
||||||
if (!alertEl.classList.contains('alert-success') &&
|
body: JSON.stringify({ message })
|
||||||
!alertEl.classList.contains('alert-danger') &&
|
|
||||||
!alertEl.classList.contains('alert-warning')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if it contains interactive elements
|
|
||||||
if (alertEl.querySelector('form, ul, .btn, .alert-heading, a')) return;
|
|
||||||
|
|
||||||
const text = alertEl.innerText.trim();
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
let type = 'info';
|
|
||||||
let titleStr = document.documentElement.lang === 'ar' ? 'ملاحظة' : 'Note';
|
|
||||||
|
|
||||||
if (alertEl.classList.contains('alert-success')) {
|
|
||||||
type = 'success';
|
|
||||||
titleStr = document.documentElement.lang === 'ar' ? 'نجاح' : 'Success';
|
|
||||||
} else if (alertEl.classList.contains('alert-danger')) {
|
|
||||||
type = 'error';
|
|
||||||
titleStr = document.documentElement.lang === 'ar' ? 'خطأ' : 'Error';
|
|
||||||
} else if (alertEl.classList.contains('alert-warning')) {
|
|
||||||
type = 'warning';
|
|
||||||
titleStr = document.documentElement.lang === 'ar' ? 'تنبيه' : 'Warning';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide immediately to prevent flash
|
|
||||||
alertEl.style.display = 'none';
|
|
||||||
|
|
||||||
validAlerts.push({ type, titleStr, text });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process alerts sequentially so they don't overlap
|
|
||||||
(async function() {
|
|
||||||
for (const alertData of validAlerts) {
|
|
||||||
await Swal.fire({
|
|
||||||
icon: alertData.type,
|
|
||||||
title: alertData.titleStr,
|
|
||||||
text: alertData.text,
|
|
||||||
confirmButtonText: document.documentElement.lang === 'ar' ? 'حسنًا' : 'OK',
|
|
||||||
confirmButtonColor: '#0d6efd'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// 2. Convert standard browser confirm() dialogs (Forms & Links)
|
|
||||||
const interceptConfirms = (selector, eventType, triggerAction) => {
|
|
||||||
document.querySelectorAll(selector).forEach(el => {
|
|
||||||
const jsAttr = el.getAttribute('on' + eventType);
|
|
||||||
if (jsAttr && jsAttr.includes('confirm(')) {
|
|
||||||
// Extract text from inside confirm('...')
|
|
||||||
const match = jsAttr.match(/confirm\(\s*['"](.*?)['"]\s*\)/);
|
|
||||||
const confirmText = match ? match[1] : (document.documentElement.lang === 'ar' ? 'هل أنت متأكد؟' : 'Are you sure?');
|
|
||||||
|
|
||||||
// Remove native confirm
|
|
||||||
el.removeAttribute('on' + eventType);
|
|
||||||
|
|
||||||
el.addEventListener(eventType, function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
Swal.fire({
|
|
||||||
title: document.documentElement.lang === 'ar' ? 'تأكيد' : 'Confirmation',
|
|
||||||
text: confirmText,
|
|
||||||
icon: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: '#dc3545',
|
|
||||||
cancelButtonColor: '#6c757d',
|
|
||||||
confirmButtonText: document.documentElement.lang === 'ar' ? 'نعم، متأكد' : 'Yes, I am sure',
|
|
||||||
cancelButtonText: document.documentElement.lang === 'ar' ? 'إلغاء' : 'Cancel'
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
triggerAction(el);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Artificial delay for realism
|
||||||
|
setTimeout(() => {
|
||||||
|
appendMessage(data.reply, 'bot');
|
||||||
|
}, 500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Forms
|
|
||||||
interceptConfirms('form[onsubmit*="confirm"]', 'submit', (form) => {
|
|
||||||
HTMLFormElement.prototype.submit.call(form);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Links (a tags)
|
|
||||||
interceptConfirms('a[onclick*="confirm"]', 'click', (link) => {
|
|
||||||
window.location.href = link.getAttribute('href');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
52
catalog.php
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
$subjects = subjects_catalog();
|
|
||||||
render_head(
|
|
||||||
t('Subject catalog', 'كتالوج المواد'),
|
|
||||||
t('Browse the bilingual subject catalog, teachers, live sessions, and classroom details.', 'تصفح كتالوج المواد والمعلمين والجلسات المباشرة وتفاصيل الفصول باللغتين.')
|
|
||||||
);
|
|
||||||
render_nav('catalog.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Explore subjects and live cohorts', 'استكشف المواد والمجموعات المباشرة')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Filter the catalog, review teachers, and open a subject detail page with modules and Meet access.', 'قم بتصفية الكتالوج ومراجعة المعلمين وفتح صفحة تفاصيل المادة مع الوحدات والوصول إلى Meet.')) ?></p>
|
|
||||||
</div>
|
|
||||||
<div class="catalog-search-wrap">
|
|
||||||
<label class="form-label small text-secondary mb-1" for="subject-search"><?= h(t('Quick filter', 'تصفية سريعة')) ?></label>
|
|
||||||
<input id="subject-search" class="form-control" type="search" placeholder="<?= h(t('Search subject or teacher', 'ابحث عن مادة أو معلم')) ?>" data-subject-search>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row g-3" id="subject-grid">
|
|
||||||
<?php foreach ($subjects as $subject): ?>
|
|
||||||
<div class="col-lg-6 subject-grid-item" data-filter-text="<?= h(strtolower($subject['title_en'] . ' ' . $subject['title_ar'] . ' ' . $subject['teacher_en'] . ' ' . $subject['teacher_ar'])) ?>">
|
|
||||||
<article class="subject-card h-100">
|
|
||||||
<div class="d-flex flex-wrap justify-content-between gap-2 align-items-start mb-3">
|
|
||||||
<div>
|
|
||||||
<span class="mini-tag mb-2 d-inline-flex"><?= h(subject_level($subject)) ?></span>
|
|
||||||
<h2 class="h5 mb-1"><?= h(subject_title($subject)) ?></h2>
|
|
||||||
<p class="small text-secondary mb-0"><?= h(subject_teacher($subject)) ?></p>
|
|
||||||
</div>
|
|
||||||
<span class="small text-secondary"><?= h(subject_duration($subject)) ?></span>
|
|
||||||
</div>
|
|
||||||
<p class="text-secondary mb-4"><?= h(subject_summary($subject)) ?></p>
|
|
||||||
<div class="subject-meta-grid mb-4">
|
|
||||||
<div><strong><?= h(t('Next live', 'الجلسة القادمة')) ?></strong><span><?= h(subject_next_live($subject)) ?></span></div>
|
|
||||||
<div><strong><?= h(t('Format', 'النوع')) ?></strong><span><?= h(t('Live + self-paced', 'مباشر + ذاتي')) ?></span></div>
|
|
||||||
</div>
|
|
||||||
<a class="btn btn-outline-dark" href="<?= h(app_url('subject.php', ['slug' => $subject['slug']])) ?>"><?= h(t('Open details', 'افتح التفاصيل')) ?></a>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
<div class="empty-filter-state d-none" data-empty-state>
|
|
||||||
<div class="panel-card text-center mt-4">
|
|
||||||
<h2 class="h5 mb-2"><?= h(t('No subject matched your filter', 'لا توجد مادة تطابق التصفية')) ?></h2>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Try a teacher name or a broader term like English, STEM, or Arabic.', 'جرّب اسم معلم أو مصطلحاً أوسع مثل الإنجليزية أو STEM أو العربية.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
286
checkout.php
@ -1,286 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
$plans = plans_catalog();
|
|
||||||
$planKey = (string) ($_GET['plan'] ?? $_POST['plan_key'] ?? 'plus');
|
|
||||||
$plan = get_plan($planKey) ?? get_plan('plus');
|
|
||||||
$cycle = (string) ($_GET['cycle'] ?? $_POST['billing_cycle'] ?? 'monthly');
|
|
||||||
$cycle = in_array($cycle, ['monthly', 'yearly'], true) ? $cycle : 'monthly';
|
|
||||||
$courseIdForView = (int) ($_GET['course_id'] ?? $_POST['course_id'] ?? 0);
|
|
||||||
$course = null;
|
|
||||||
if ($courseIdForView > 0) {
|
|
||||||
$course = db()->query("SELECT * FROM courses WHERE id = $courseIdForView")->fetch();
|
|
||||||
if ($course) {
|
|
||||||
if (!$course['registration_open']) {
|
|
||||||
$msg = current_lang() === 'ar' ? 'التسجيل في هذه الدورة/الدفعة مغلق حالياً.' : 'Registration for this course/batch is currently closed.';
|
|
||||||
die("<div style='padding:2rem;text-align:center;font-family:sans-serif'><h1>" . $msg . "</h1><a href='index.php'>" . (current_lang() === 'ar' ? 'العودة' : 'Back') . "</a></div>");
|
|
||||||
}
|
|
||||||
if ($course['max_students'] > 0) {
|
|
||||||
$enrolled = db()->query("SELECT COUNT(*) FROM course_students WHERE course_id = $courseIdForView")->fetchColumn();
|
|
||||||
if ($enrolled >= $course['max_students']) {
|
|
||||||
$msg = current_lang() === 'ar' ? 'لقد وصلت هذه الدورة إلى الحد الأقصى لعدد الطلاب.' : 'This course has reached its maximum capacity.';
|
|
||||||
die("<div style='padding:2rem;text-align:center;font-family:sans-serif'><h1>" . $msg . "</h1><a href='index.php'>" . (current_lang() === 'ar' ? 'العودة' : 'Back') . "</a></div>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
die('Course not found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$errors = [];
|
|
||||||
$form = [
|
|
||||||
'full_name' => trim((string) ($_POST['full_name'] ?? '')),
|
|
||||||
'email' => trim((string) ($_POST['email'] ?? '')),
|
|
||||||
'password' => (string) ($_POST['password'] ?? ''),
|
|
||||||
'whatsapp' => trim((string) ($_POST['whatsapp'] ?? '')),
|
|
||||||
'preferred_language' => (string) ($_POST['preferred_language'] ?? current_lang()),
|
|
||||||
'billing_cycle' => $cycle,
|
|
||||||
'plan_key' => $plan['key'],
|
|
||||||
'wablas_opt_in' => isset($_POST['wablas_opt_in']) ? 1 : 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
if ($form['full_name'] === '' || strlen($form['full_name']) < 2) {
|
|
||||||
$errors[] = t('Please enter a valid full name.', 'يرجى إدخال اسم كامل صحيح.');
|
|
||||||
}
|
|
||||||
if (empty($form['password']) || strlen($form['password']) < 6) {
|
|
||||||
$errors[] = t('Password must be at least 6 characters.', 'يجب أن تتكون كلمة المرور من 6 أحرف على الأقل.');
|
|
||||||
}
|
|
||||||
if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
|
|
||||||
$errors[] = t('Please enter a valid email address.', 'يرجى إدخال بريد إلكتروني صحيح.');
|
|
||||||
}
|
|
||||||
if (!preg_match('/^[0-9+\-\s]{8,20}$/', $form['whatsapp'])) {
|
|
||||||
$errors[] = t('Please enter a valid WhatsApp number.', 'يرجى إدخال رقم واتساب صحيح.');
|
|
||||||
}
|
|
||||||
if (!in_array($form['preferred_language'], ['en', 'ar'], true)) {
|
|
||||||
$errors[] = t('Please select a supported language.', 'يرجى اختيار لغة مدعومة.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$errors) {
|
|
||||||
$reference = 'THW-' . date('YmdHis') . '-' . random_int(100, 999);
|
|
||||||
$id = create_subscription([
|
|
||||||
'full_name' => $form['full_name'],
|
|
||||||
'email' => $form['email'],
|
|
||||||
'password' => password_hash($form['password'], PASSWORD_DEFAULT),
|
|
||||||
'whatsapp' => $form['whatsapp'],
|
|
||||||
'preferred_language' => $form['preferred_language'],
|
|
||||||
'plan_key' => $plan['key'],
|
|
||||||
'billing_cycle' => $form['billing_cycle'],
|
|
||||||
'payment_status' => 'active', // MVP: Mark active immediately
|
|
||||||
'payment_gateway' => 'Thawani',
|
|
||||||
'thawani_reference' => $reference,
|
|
||||||
'wablas_opt_in' => $form['wablas_opt_in'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$courseId = (int) ($_GET['course_id'] ?? $_POST['course_id'] ?? 0);
|
|
||||||
if ($courseId > 0) {
|
|
||||||
try {
|
|
||||||
$stmt = db()->prepare('INSERT IGNORE INTO course_students (course_id, student_id) VALUES (?, ?)');
|
|
||||||
$stmt->execute([$courseId, $id]);
|
|
||||||
} catch (Exception $e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Thawani Integration ---
|
|
||||||
$stmtThawani = db()->query("SELECT thawani_secret_key, thawani_publishable_key, thawani_mode FROM platform_profile WHERE id = 1");
|
|
||||||
$thawaniConfig = $stmtThawani->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$thawaniRedirect = null;
|
|
||||||
if ($thawaniConfig && !empty($thawaniConfig['thawani_secret_key']) && !empty($thawaniConfig['thawani_publishable_key'])) {
|
|
||||||
$mode = $thawaniConfig['thawani_mode'] ?? 'test';
|
|
||||||
$baseUrl = $mode === 'live' ? 'https://checkout.thawani.om' : 'https://uatcheckout.thawani.om';
|
|
||||||
|
|
||||||
$priceOMR = $course ? (float) $course['price'] : (float) ($cycle === 'yearly' ? $plan['price_yearly'] : $plan['price_monthly']);
|
|
||||||
$priceBaisa = (int) ($priceOMR * 1000);
|
|
||||||
|
|
||||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
|
|
||||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
|
|
||||||
$protocol = $_SERVER['HTTP_X_FORWARDED_PROTO'] . "://";
|
|
||||||
}
|
|
||||||
$host = $_SERVER['HTTP_HOST'];
|
|
||||||
$scriptDir = str_replace('\\', '/', dirname($_SERVER['PHP_SELF']));
|
|
||||||
$appBaseUrl = $protocol . $host . $scriptDir;
|
|
||||||
|
|
||||||
$successUrl = $appBaseUrl . '/subscription.php?id=' . $id . '&created=1';
|
|
||||||
$cancelUrl = $appBaseUrl . '/checkout.php?plan=' . $plan['key'] . '&cycle=' . $cycle;
|
|
||||||
if ($courseId > 0) {
|
|
||||||
$cancelUrl .= '&course_id=' . $courseId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$productName = $course ? (current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) : plan_name($plan);
|
|
||||||
$productName = mb_substr($productName, 0, 40); // Thawani character limit
|
|
||||||
if (empty($productName)) $productName = 'Course Enrollment';
|
|
||||||
|
|
||||||
$payload = [
|
|
||||||
'client_reference_id' => (string) $id,
|
|
||||||
'mode' => 'payment',
|
|
||||||
'products' => [
|
|
||||||
[
|
|
||||||
'name' => $productName,
|
|
||||||
'quantity' => 1,
|
|
||||||
'unit_amount' => $priceBaisa
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'success_url' => $successUrl,
|
|
||||||
'cancel_url' => $cancelUrl,
|
|
||||||
'metadata' => [
|
|
||||||
"Customer name" => $form["full_name"],
|
|
||||||
"order id" => (string)$id,
|
|
||||||
"contact number" => $form["whatsapp"],
|
|
||||||
"email id" => $form["email"]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
$ch = curl_init("$baseUrl/api/v1/checkout/session");
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
||||||
"thawani-api-key: {$thawaniConfig['thawani_secret_key']}",
|
|
||||||
"Content-Type: application/json"
|
|
||||||
]);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if ($response) {
|
|
||||||
$resData = json_decode($response, true);
|
|
||||||
if (isset($resData['success']) && $resData['success'] && isset($resData['data']['session_id'])) {
|
|
||||||
$sessionId = $resData['data']['session_id'];
|
|
||||||
db()->query("UPDATE student_subscriptions SET thawani_reference = " . db()->quote($sessionId) . " WHERE id = " . $id);
|
|
||||||
$thawaniRedirect = "$baseUrl/pay/$sessionId?key={$thawaniConfig['thawani_publishable_key']}";
|
|
||||||
} else {
|
|
||||||
error_log('Thawani Session Error: ' . $response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($thawaniRedirect) {
|
|
||||||
$_SESSION['subscription_id'] = $id;
|
|
||||||
$_SESSION['student_email'] = $form['email'];
|
|
||||||
header('Location: ' . $thawaniRedirect);
|
|
||||||
exit;
|
|
||||||
} else {
|
|
||||||
db()->query("DELETE FROM student_subscriptions WHERE id = " . $id);
|
|
||||||
if ($courseId > 0) {
|
|
||||||
db()->query("DELETE FROM course_students WHERE course_id = " . $courseId . " AND student_id = " . $id);
|
|
||||||
}
|
|
||||||
$errors[] = t('Thawani payment gateway is not fully configured (missing keys) or the API call failed. Please update your API keys in the Teacher Panel > Integrations.', 'بوابة دفع ثواني غير مهيأة بالكامل (المفاتيح مفقودة) أو فشل الاتصال. يرجى تحديث مفاتيحك في لوحة المعلم > التكاملات.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_head(
|
|
||||||
t('Checkout', 'الدفع'),
|
|
||||||
t('Capture a student subscription with bilingual preferences, WhatsApp opt-in, and a Thawani-ready payment reference.', 'التقط اشتراك طالب مع تفضيلات ثنائية اللغة وخيار واتساب ومرجع دفع جاهز لثواني.')
|
|
||||||
);
|
|
||||||
render_nav('pricing.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<div class="panel-card">
|
|
||||||
<span class="eyebrow"><?= h(t('Checkout flow', 'مسار الدفع')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Create a subscription and unlock the student dashboard', 'أنشئ اشتراكاً وافتح لوحة الطالب')) ?></h1>
|
|
||||||
<p class="text-secondary mb-4"><?= h(t('This first slice records a real subscription row locally, reserves a Thawani reference, and prepares WhatsApp reminders.', 'تسجل هذه الشريحة الأولى صف اشتراك حقيقياً محلياً وتحجز مرجع Thawani وتجهز تذكيرات واتساب.')) ?></p>
|
|
||||||
<?php if ($errors): ?>
|
|
||||||
<div class="alert alert-danger border">
|
|
||||||
<ul class="mb-0 ps-3">
|
|
||||||
<?php foreach ($errors as $error): ?>
|
|
||||||
<li><?= h($error) ?></li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<form method="post" class="row g-3">
|
|
||||||
<input type="hidden" name="plan_key" value="<?= h($plan['key']) ?>">
|
|
||||||
<?php if (isset($_GET['course_id']) || isset($_POST['course_id'])): ?>
|
|
||||||
<input type="hidden" name="course_id" value="<?= h($_GET['course_id'] ?? $_POST['course_id'] ?? 0) ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label" for="full_name"><?= h(t('Full name', 'الاسم الكامل')) ?></label>
|
|
||||||
<input class="form-control" id="full_name" name="full_name" required value="<?= h($form['full_name']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label" for="email"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input class="form-control" id="email" type="email" name="email" required value="<?= h($form['email']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label" for="password"><?= h(t('Password', 'كلمة المرور')) ?></label>
|
|
||||||
<input class="form-control" id="password" type="password" name="password" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label" for="whatsapp"><?= h(t('WhatsApp', 'واتساب')) ?></label>
|
|
||||||
<input class="form-control" id="whatsapp" name="whatsapp" placeholder="+968 9000 0000" required value="<?= h($form['whatsapp']) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<label class="form-label" for="preferred_language"><?= h(t('Language', 'اللغة')) ?></label>
|
|
||||||
<select class="form-select" id="preferred_language" name="preferred_language">
|
|
||||||
<option value="en" <?= $form['preferred_language'] === 'en' ? 'selected' : '' ?>>English</option>
|
|
||||||
<option value="ar" <?= $form['preferred_language'] === 'ar' ? 'selected' : '' ?>>العربية</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<?php if ($course): ?>
|
|
||||||
<input type="hidden" name="billing_cycle" value="monthly">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<label class="form-label" for="billing_cycle"><?= h(t('Billing', 'الدفع الدوري')) ?></label>
|
|
||||||
<select class="form-select" id="billing_cycle" name="billing_cycle">
|
|
||||||
<option value="monthly" <?= $form['billing_cycle'] === 'monthly' ? 'selected' : '' ?>><?= h(t('Monthly', 'شهري')) ?></option>
|
|
||||||
<option value="yearly" <?= $form['billing_cycle'] === 'yearly' ? 'selected' : '' ?>><?= h(t('Yearly', 'سنوي')) ?></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check border rounded-3 p-3">
|
|
||||||
<input class="form-check-input" type="checkbox" id="wablas_opt_in" name="wablas_opt_in" value="1" <?= $form['wablas_opt_in'] ? 'checked' : '' ?>>
|
|
||||||
<label class="form-check-label" for="wablas_opt_in">
|
|
||||||
<?= h(t('Send payment success and class reminders through WhatsApp.', 'أرسل نجاح الدفع وتذكيرات الحصص عبر واتساب.')) ?>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 d-flex flex-wrap gap-2 pt-2">
|
|
||||||
<button class="btn btn-dark btn-lg" type="submit"><?= h(t('Confirm subscription', 'تأكيد الاشتراك')) ?></button>
|
|
||||||
<a class="btn btn-outline-dark btn-lg" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Back to plans', 'العودة إلى الخطط')) ?></a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<aside class="panel-card sticky-card">
|
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Order summary', 'ملخص الطلب')) ?></span>
|
|
||||||
<?php if ($course): ?>
|
|
||||||
<h2 class="h5 mb-1"><?= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?></h2>
|
|
||||||
<p class="small text-secondary mb-0"><?= h(t('Short course enrollment', 'تسجيل في دورة قصيرة')) ?></p>
|
|
||||||
<?php else: ?>
|
|
||||||
<h2 class="h5 mb-1"><?= h(plan_name($plan)) ?></h2>
|
|
||||||
<p class="small text-secondary mb-0"><?= h(t('Single-platform access', 'وصول إلى منصة موحدة')) ?></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<span class="badge bg-dark-subtle text-dark-emphasis border"><?= h(t('Step 1 MVP', 'النسخة الأولى')) ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="summary-row"><span><?= h(t('Billing cycle', 'دورة الدفع')) ?></span><strong>
|
|
||||||
<?php if ($course): ?>
|
|
||||||
<?= h(t('One-time', 'لمرة واحدة')) ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<?= h($cycle === 'yearly' ? t('Yearly', 'سنوي') : t('Monthly', 'شهري')) ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</strong></div>
|
|
||||||
<div class="summary-row"><span><?= h(t('Price', 'السعر')) ?></span><strong data-cycle-price>
|
|
||||||
<?php if ($course): ?>
|
|
||||||
<?= h(format_price((float)$course['price'])) ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<?= h(price_label($plan, $cycle)) ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</strong></div>
|
|
||||||
<hr>
|
|
||||||
<ul class="list-unstyled compact-list compact-list-tight mb-0">
|
|
||||||
<?php foreach (current_lang() === 'ar' ? $plan['features_ar'] : $plan['features_en'] as $feature): ?>
|
|
||||||
<li><?= h($feature) ?></li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# Netscape HTTP Cookie File
|
|
||||||
# https://curl.se/docs/http-cookies.html
|
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
|
||||||
|
|
||||||
127.0.0.1 FALSE / FALSE 0 PHPSESSID dc35afddvci5n93ei5dp4uaegu
|
|
||||||
71
courses.php
@ -1,71 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
|
|
||||||
$courses = db()->query("SELECT * FROM courses WHERE status = 'active' ORDER BY created_at DESC")->fetchAll();
|
|
||||||
|
|
||||||
render_head(
|
|
||||||
t('Courses', 'الدورات'),
|
|
||||||
t('Browse our specialized short courses.', 'تصفح دوراتنا القصيرة المتخصصة.')
|
|
||||||
);
|
|
||||||
render_nav('courses.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5 bg-light min-vh-100">
|
|
||||||
<div class="container">
|
|
||||||
<div class="section-header mb-5 text-center">
|
|
||||||
<span class="eyebrow"><?= h(t('All Courses', 'جميع الدورات')) ?></span>
|
|
||||||
<h1 class="section-title mb-2 fw-bold"><?= h(t('Accelerate Your Skills', 'سرّع تطوير مهاراتك')) ?></h1>
|
|
||||||
<p class="text-secondary mx-auto" style="max-width: 600px;"><?= h(t('Enroll in our specialized short courses designed to help you achieve your goals.', 'سجل في دوراتنا القصيرة المتخصصة المصممة لمساعدتك على تحقيق أهدافك.')) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (empty($courses)): ?>
|
|
||||||
<div class="alert alert-info text-center py-4 rounded-3 shadow-sm border-0">
|
|
||||||
<?= h(t('No active courses found at the moment. Please check back later.', 'لم يتم العثور على دورات نشطة في الوقت الحالي. يرجى التحقق مرة أخرى لاحقاً.')) ?>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="row g-4">
|
|
||||||
<?php foreach ($courses as $course): ?>
|
|
||||||
<div class="col-lg-4 col-md-6">
|
|
||||||
<article class="card h-100 border-0 shadow-sm overflow-hidden" style="border-radius: 1rem; transition: transform 0.2s;">
|
|
||||||
<?php if (!empty($course['picture'])): ?>
|
|
||||||
<img src="<?= h($course['picture']) ?>" class="card-img-top" alt="<?= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?>" style="height: 220px; object-fit: cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="card-img-top bg-secondary d-flex align-items-center justify-content-center text-white" style="height: 220px;">
|
|
||||||
<i class="bi bi-play-btn fs-1"></i>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="card-body d-flex flex-column p-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
||||||
<h3 class="h5 mb-0 fw-bold"><?= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?></h3>
|
|
||||||
<span class="badge bg-primary bg-gradient text-white rounded-pill fs-6 px-3 py-2 shadow-sm"><?= h(format_price((float)$course['price'])) ?></span>
|
|
||||||
</div>
|
|
||||||
<p class="text-secondary mb-4 flex-grow-1" style="font-size: 0.95rem;"><?= h(current_lang() === 'ar' ? $course['description_ar'] : $course['description_en']) ?></p>
|
|
||||||
<div class="d-grid mt-auto">
|
|
||||||
<?php
|
|
||||||
$isFull = false;
|
|
||||||
$isClosed = !$course['registration_open'];
|
|
||||||
if ($course['max_students'] > 0) {
|
|
||||||
$enrolled = db()->query("SELECT COUNT(*) FROM course_students WHERE course_id = " . $course['id'])->fetchColumn();
|
|
||||||
if ($enrolled >= $course['max_students']) {
|
|
||||||
$isFull = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($isClosed || $isFull):
|
|
||||||
?>
|
|
||||||
<button class="btn btn-outline-secondary rounded-pill py-2" disabled>
|
|
||||||
<?= h(t('Registration Closed', 'التسجيل مغلق')) ?>
|
|
||||||
</button>
|
|
||||||
<?php else: ?>
|
|
||||||
<a href="<?= h(app_url('checkout.php', ['course_id' => $course['id']])) ?>" class="btn btn-dark rounded-pill py-2 shadow-sm fw-bold">
|
|
||||||
<?= h(t('Enroll Now', 'سجل الآن')) ?>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
130
dashboard.php
@ -1,130 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
$subscription = current_subscription();
|
|
||||||
$history = [];
|
|
||||||
$enrolled_courses = [];
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
if ($subscription) {
|
|
||||||
$history = fetch_subscriptions_by_email((string) $subscription['email']);
|
|
||||||
|
|
||||||
// Fetch enrolled courses for this student
|
|
||||||
$student_id = (int) $subscription['id'];
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT c.*
|
|
||||||
FROM courses c
|
|
||||||
JOIN course_students cs ON c.id = cs.course_id
|
|
||||||
WHERE cs.student_id = ? AND c.status = 'active'
|
|
||||||
");
|
|
||||||
$stmt->execute([$student_id]);
|
|
||||||
$enrolled_courses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
render_head(
|
|
||||||
t('Student dashboard', 'لوحة الطالب'),
|
|
||||||
t('Review the active subscription, upcoming live classes, and access to subject detail pages.', 'راجع الاشتراك النشط والفصول المباشرة القادمة والوصول إلى صفحات تفاصيل المواد.')
|
|
||||||
);
|
|
||||||
render_nav('dashboard.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5">
|
|
||||||
<div class="container">
|
|
||||||
<?php if (!$subscription): ?>
|
|
||||||
<div class="panel-card text-center">
|
|
||||||
<span class="eyebrow"><?= h(t('Student dashboard', 'لوحة الطالب')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('No active subscription yet', 'لا يوجد اشتراك نشط بعد')) ?></h1>
|
|
||||||
<p class="text-secondary mb-4"><?= h(t('Start with pricing, complete the checkout form, and your personalized classroom dashboard will appear here.', 'ابدأ بالتسعير وأكمل نموذج الدفع وستظهر هنا لوحة الفصول الشخصية الخاصة بك.')) ?></p>
|
|
||||||
<div class="d-flex justify-content-center flex-wrap gap-2">
|
|
||||||
<a class="btn btn-dark" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Choose a plan', 'اختر خطة')) ?></a>
|
|
||||||
<a class="btn btn-outline-dark" href="<?= h(app_url('courses.php')) ?>"><?= h(t('Browse courses', 'تصفح الدورات')) ?></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php $plan = get_plan($subscription['plan_key']) ?? get_plan('plus'); ?>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="panel-card mb-4">
|
|
||||||
<div class="d-flex flex-wrap justify-content-between gap-3 align-items-start">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Active Profile', 'الملف النشط')) ?></span>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Welcome back, ', 'مرحباً بعودتك، ') . $subscription['full_name']) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Here are your enrolled courses and upcoming live lessons.', 'إليك دوراتك المسجلة والدروس المباشرة القادمة.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="h5 mb-3"><?= h(t('My Enrolled Courses', 'دوراتي المسجلة')) ?></h2>
|
|
||||||
<div class="row g-3 mb-4">
|
|
||||||
<?php foreach ($enrolled_courses as $course): ?>
|
|
||||||
<?php
|
|
||||||
// Fetch next or current live lesson
|
|
||||||
$live_stmt = $db->prepare("
|
|
||||||
SELECT *
|
|
||||||
FROM course_live_lessons
|
|
||||||
WHERE course_id = ? AND status != 'ended'
|
|
||||||
ORDER BY scheduled_at ASC
|
|
||||||
LIMIT 1
|
|
||||||
");
|
|
||||||
$live_stmt->execute([$course['id']]);
|
|
||||||
$next_lesson = $live_stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<article class="panel-card h-100 d-flex flex-column" style="padding: 1.25rem;">
|
|
||||||
<?php if ($course['picture']): ?>
|
|
||||||
<img src="<?= h($course['picture']) ?>" class="img-fluid rounded mb-3" style="object-fit:cover; height:120px; width:100%;" alt="">
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-2">
|
|
||||||
<div>
|
|
||||||
<h2 class="h6 mb-1"><?= h(t($course['name_en'], $course['name_ar'])) ?></h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($next_lesson): ?>
|
|
||||||
<div class="mt-2 p-2 bg-light rounded border border-light-subtle">
|
|
||||||
<p class="small fw-bold mb-1">
|
|
||||||
<?php if ($next_lesson['status'] === 'live'): ?>
|
|
||||||
<span class="badge bg-danger animate-pulse me-1"><?= h(t('LIVE', 'مباشر')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-warning text-dark me-1"><?= h(t('Upcoming', 'قادم')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?= h($next_lesson['title']) ?>
|
|
||||||
</p>
|
|
||||||
<p class="text-secondary small mb-2"><?= h(date('Y-m-d H:i', strtotime($next_lesson['scheduled_at']))) ?></p>
|
|
||||||
|
|
||||||
<?php if ($next_lesson['status'] === 'live'): ?>
|
|
||||||
<a class="btn btn-sm btn-danger text-white w-100" href="<?= h(app_url('live_lesson.php', ['id' => $next_lesson['id']])) ?>"><?= h(t('Join Lesson Now', 'انضم للدرس الآن')) ?></a>
|
|
||||||
<?php else: ?>
|
|
||||||
<button class="btn btn-sm btn-outline-secondary w-100" disabled><?= h(t('Waiting for teacher...', 'في انتظار المعلم...')) ?></button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<p class="text-secondary small mt-2 mb-3 flex-grow-1"><?= h(t('No upcoming live lessons.', 'لا توجد دروس مباشرة قادمة.')) ?></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<?php if (empty($enrolled_courses)): ?>
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="alert alert-info"><?= h(t('You are not enrolled in any courses yet. Browse the homepage to find courses.', 'أنت غير مسجل في أي دورات بعد. تصفح الصفحة الرئيسية للبحث عن دورات.')) ?></div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<aside class="panel-card sticky-card">
|
|
||||||
<h2 class="h5 mb-3"><?= h(t('Student profile', 'ملف الطالب')) ?></h2>
|
|
||||||
<div class="summary-row"><span><?= h(t('Preferred language', 'اللغة المفضلة')) ?></span><strong><?= h($subscription['preferred_language'] === 'ar' ? 'العربية' : 'English') ?></strong></div>
|
|
||||||
<div class="summary-row"><span><?= h(t('WhatsApp reminders', 'تذكيرات واتساب')) ?></span><strong><?= h((int) $subscription['wablas_opt_in'] === 1 ? t('Enabled', 'مفعلة') : t('Off', 'متوقفة')) ?></strong></div>
|
|
||||||
<hr>
|
|
||||||
<div class="d-grid gap-2">
|
|
||||||
<a class="btn btn-dark" href="<?= h(app_url('courses.php')) ?>"><?= h(t('Explore more courses', 'استكشف المزيد من الدورات')) ?></a>
|
|
||||||
<a class="btn btn-outline-dark" href="<?= h(app_url('teacher.php')) ?>"><?= h(t('Open teacher view', 'افتح عرض المعلم')) ?></a>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../config.php';
|
|
||||||
try {
|
|
||||||
db()->exec("ALTER TABLE `platform_profile` ADD COLUMN `terms` TEXT DEFAULT NULL, ADD COLUMN `privacy_policy` TEXT DEFAULT NULL");
|
|
||||||
echo "Migration successful\n";
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo "Migration error: " . $e->getMessage() . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../includes/app.php';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo = db();
|
|
||||||
|
|
||||||
// Create roles table
|
|
||||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `roles` (
|
|
||||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`name` VARCHAR(255) NOT NULL,
|
|
||||||
`description` TEXT,
|
|
||||||
`is_system` TINYINT(1) DEFAULT 0,
|
|
||||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
|
|
||||||
|
|
||||||
// Create role_permissions table
|
|
||||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `role_permissions` (
|
|
||||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`role_id` INT NOT NULL,
|
|
||||||
`page` VARCHAR(50) NOT NULL,
|
|
||||||
`can_view` TINYINT(1) DEFAULT 0,
|
|
||||||
`can_add` TINYINT(1) DEFAULT 0,
|
|
||||||
`can_edit` TINYINT(1) DEFAULT 0,
|
|
||||||
`can_delete` TINYINT(1) DEFAULT 0,
|
|
||||||
UNIQUE KEY `role_page` (`role_id`, `page`),
|
|
||||||
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
|
|
||||||
|
|
||||||
// Alter users table to add role_id
|
|
||||||
// Check if column exists
|
|
||||||
$stmt = $pdo->prepare("SHOW COLUMNS FROM `users` LIKE 'role_id'");
|
|
||||||
$stmt->execute();
|
|
||||||
if ($stmt->rowCount() == 0) {
|
|
||||||
$pdo->exec("ALTER TABLE `users` ADD COLUMN `role_id` INT NULL DEFAULT NULL");
|
|
||||||
$pdo->exec("ALTER TABLE `users` ADD CONSTRAINT `fk_user_role` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE SET NULL");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert 'Super Admin' role if it doesn't exist
|
|
||||||
$stmt = $pdo->prepare("SELECT id FROM roles WHERE name = 'Super Admin'");
|
|
||||||
$stmt->execute();
|
|
||||||
$adminRole = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$adminRole) {
|
|
||||||
$pdo->exec("INSERT INTO roles (name, description, is_system) VALUES ('Super Admin', 'Full access to all pages and actions', 1)");
|
|
||||||
$adminRoleId = $pdo->lastInsertId();
|
|
||||||
} else {
|
|
||||||
$adminRoleId = $adminRole['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign 'Super Admin' role to all existing admins
|
|
||||||
$stmt = $pdo->prepare("UPDATE users SET role_id = ? WHERE role = 'admin' AND role_id IS NULL");
|
|
||||||
$stmt->execute([$adminRoleId]);
|
|
||||||
|
|
||||||
echo "Migration completed successfully.";
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo "Migration failed: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
@ -1,408 +0,0 @@
|
|||||||
/*M!999999\- enable the sandbox mode */
|
|
||||||
-- MariaDB dump 10.19 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64)
|
|
||||||
--
|
|
||||||
-- Host: localhost Database: app_39496
|
|
||||||
-- ------------------------------------------------------
|
|
||||||
-- Server version 10.11.14-MariaDB-0+deb12u2
|
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
|
||||||
/*!40101 SET NAMES utf8mb4 */;
|
|
||||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
|
||||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
|
||||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
|
||||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
|
||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `classes`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `classes`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `classes` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`name_en` varchar(255) NOT NULL,
|
|
||||||
`name_ar` varchar(255) NOT NULL,
|
|
||||||
`description_en` text DEFAULT NULL,
|
|
||||||
`description_ar` text DEFAULT NULL,
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
`status` enum('active','inactive') NOT NULL DEFAULT 'active',
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `course_activities`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `course_activities`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `course_activities` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`course_id` int(10) unsigned NOT NULL,
|
|
||||||
`title_en` varchar(255) NOT NULL,
|
|
||||||
`title_ar` varchar(255) NOT NULL,
|
|
||||||
`description_en` text DEFAULT NULL,
|
|
||||||
`description_ar` text DEFAULT NULL,
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `course_id` (`course_id`),
|
|
||||||
CONSTRAINT `course_activities_ibfk_1` FOREIGN KEY (`course_id`) REFERENCES `courses` (`id`) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `course_live_lessons`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `course_live_lessons`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `course_live_lessons` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`course_id` int(11) NOT NULL,
|
|
||||||
`title` varchar(255) NOT NULL,
|
|
||||||
`scheduled_at` datetime NOT NULL,
|
|
||||||
`status` enum('scheduled','live','ended') DEFAULT 'scheduled',
|
|
||||||
`room_name` varchar(255) NOT NULL,
|
|
||||||
`meet_url` varchar(500) DEFAULT NULL,
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `course_students`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `course_students`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `course_students` (
|
|
||||||
`course_id` int(10) unsigned NOT NULL,
|
|
||||||
`student_id` int(10) unsigned NOT NULL,
|
|
||||||
`assigned_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`course_id`,`student_id`),
|
|
||||||
KEY `student_id` (`student_id`),
|
|
||||||
CONSTRAINT `course_students_ibfk_1` FOREIGN KEY (`course_id`) REFERENCES `courses` (`id`) ON DELETE CASCADE,
|
|
||||||
CONSTRAINT `course_students_ibfk_2` FOREIGN KEY (`student_id`) REFERENCES `student_subscriptions` (`id`) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `courses`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `courses`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `courses` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`name_en` varchar(255) NOT NULL,
|
|
||||||
`name_ar` varchar(255) NOT NULL,
|
|
||||||
`description_en` text DEFAULT NULL,
|
|
||||||
`description_ar` text DEFAULT NULL,
|
|
||||||
`price` decimal(10,2) DEFAULT 0.00,
|
|
||||||
`picture` varchar(255) DEFAULT NULL,
|
|
||||||
`status` enum('active','inactive') DEFAULT 'active',
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
`teacher_id` int(11) DEFAULT NULL,
|
|
||||||
`max_students` int(11) DEFAULT NULL,
|
|
||||||
`registration_open` tinyint(1) DEFAULT 1,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `landing_settings`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `landing_settings`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `landing_settings` (
|
|
||||||
`setting_key` varchar(100) NOT NULL,
|
|
||||||
`value_en` text DEFAULT NULL,
|
|
||||||
`value_ar` text DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`setting_key`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `module_progress`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `module_progress`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `module_progress` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`subscription_id` int(10) unsigned NOT NULL,
|
|
||||||
`subject_slug` varchar(100) NOT NULL,
|
|
||||||
`module_index` int(10) unsigned NOT NULL,
|
|
||||||
`completed_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `subscription_id` (`subscription_id`,`subject_slug`,`module_index`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `plans`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `plans`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `plans` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`plan_key` varchar(50) NOT NULL,
|
|
||||||
`name_en` varchar(120) NOT NULL,
|
|
||||||
`name_ar` varchar(120) NOT NULL,
|
|
||||||
`price_monthly` decimal(10,3) NOT NULL,
|
|
||||||
`price_yearly` decimal(10,3) NOT NULL,
|
|
||||||
`subjects_limit` int(11) DEFAULT 1,
|
|
||||||
`features_en` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`features_en`)),
|
|
||||||
`features_ar` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`features_ar`)),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `plan_key` (`plan_key`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `platform_profile`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `platform_profile`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `platform_profile` (
|
|
||||||
`id` int(11) NOT NULL DEFAULT 1,
|
|
||||||
`name` varchar(255) DEFAULT NULL,
|
|
||||||
`description` text DEFAULT NULL,
|
|
||||||
`logo_path` varchar(255) DEFAULT NULL,
|
|
||||||
`favicon_path` varchar(255) DEFAULT NULL,
|
|
||||||
`ctr_no` varchar(255) DEFAULT NULL,
|
|
||||||
`telephone_no` varchar(255) DEFAULT NULL,
|
|
||||||
`email_id` varchar(255) DEFAULT NULL,
|
|
||||||
`wablas_domain` varchar(255) DEFAULT NULL,
|
|
||||||
`wablas_token` varchar(255) DEFAULT NULL,
|
|
||||||
`thawani_secret_key` varchar(255) DEFAULT NULL,
|
|
||||||
`thawani_publishable_key` varchar(255) DEFAULT NULL,
|
|
||||||
`thawani_mode` varchar(50) DEFAULT 'test',
|
|
||||||
`wablas_security_key` varchar(255) DEFAULT NULL,
|
|
||||||
`smtp_host` varchar(255) DEFAULT NULL,
|
|
||||||
`smtp_port` varchar(50) DEFAULT NULL,
|
|
||||||
`smtp_secure` varchar(50) DEFAULT NULL,
|
|
||||||
`smtp_user` varchar(255) DEFAULT NULL,
|
|
||||||
`smtp_pass` varchar(255) DEFAULT NULL,
|
|
||||||
`smtp_from_email` varchar(255) DEFAULT NULL,
|
|
||||||
`smtp_from_name` varchar(255) DEFAULT NULL,
|
|
||||||
`terms` text DEFAULT NULL,
|
|
||||||
`privacy_policy` text DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `role_permissions`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `role_permissions`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `role_permissions` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`role_id` int(11) NOT NULL,
|
|
||||||
`page` varchar(50) NOT NULL,
|
|
||||||
`can_view` tinyint(1) DEFAULT 0,
|
|
||||||
`can_add` tinyint(1) DEFAULT 0,
|
|
||||||
`can_edit` tinyint(1) DEFAULT 0,
|
|
||||||
`can_delete` tinyint(1) DEFAULT 0,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `role_page` (`role_id`,`page`),
|
|
||||||
CONSTRAINT `role_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `roles`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `roles`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `roles` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`name` varchar(255) NOT NULL,
|
|
||||||
`description` text DEFAULT NULL,
|
|
||||||
`is_system` tinyint(1) DEFAULT 0,
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `student_assessments`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `student_assessments`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `student_assessments` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`activity_id` int(10) unsigned NOT NULL,
|
|
||||||
`student_id` int(10) unsigned NOT NULL,
|
|
||||||
`score` decimal(5,2) DEFAULT NULL,
|
|
||||||
`feedback` text DEFAULT NULL,
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `activity_id` (`activity_id`,`student_id`),
|
|
||||||
KEY `student_id` (`student_id`),
|
|
||||||
CONSTRAINT `student_assessments_ibfk_1` FOREIGN KEY (`activity_id`) REFERENCES `course_activities` (`id`) ON DELETE CASCADE,
|
|
||||||
CONSTRAINT `student_assessments_ibfk_2` FOREIGN KEY (`student_id`) REFERENCES `student_subscriptions` (`id`) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `student_subscriptions`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `student_subscriptions`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `student_subscriptions` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`full_name` varchar(120) NOT NULL,
|
|
||||||
`email` varchar(190) NOT NULL,
|
|
||||||
`password` varchar(255) DEFAULT NULL,
|
|
||||||
`reset_token` varchar(255) DEFAULT NULL,
|
|
||||||
`reset_expires` datetime DEFAULT NULL,
|
|
||||||
`whatsapp` varchar(40) NOT NULL,
|
|
||||||
`preferred_language` varchar(5) NOT NULL DEFAULT 'en',
|
|
||||||
`plan_key` varchar(20) NOT NULL,
|
|
||||||
`billing_cycle` varchar(20) NOT NULL DEFAULT 'monthly',
|
|
||||||
`payment_status` varchar(20) NOT NULL DEFAULT 'active',
|
|
||||||
`payment_gateway` varchar(30) NOT NULL DEFAULT 'Thawani',
|
|
||||||
`thawani_reference` varchar(80) NOT NULL,
|
|
||||||
`wablas_opt_in` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`status` enum('active','inactive') NOT NULL DEFAULT 'active',
|
|
||||||
`picture` varchar(255) DEFAULT NULL,
|
|
||||||
`civil_id` varchar(50) DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `subjects`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `subjects`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `subjects` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`slug` varchar(100) NOT NULL,
|
|
||||||
`title_en` varchar(255) NOT NULL,
|
|
||||||
`title_ar` varchar(255) NOT NULL,
|
|
||||||
`summary_en` text DEFAULT NULL,
|
|
||||||
`summary_ar` text DEFAULT NULL,
|
|
||||||
`teacher_en` varchar(120) DEFAULT NULL,
|
|
||||||
`teacher_ar` varchar(120) DEFAULT NULL,
|
|
||||||
`level_en` varchar(50) DEFAULT NULL,
|
|
||||||
`level_ar` varchar(50) DEFAULT NULL,
|
|
||||||
`duration_en` varchar(50) DEFAULT NULL,
|
|
||||||
`duration_ar` varchar(50) DEFAULT NULL,
|
|
||||||
`next_live_en` varchar(120) DEFAULT NULL,
|
|
||||||
`next_live_ar` varchar(120) DEFAULT NULL,
|
|
||||||
`meet_url` varchar(255) DEFAULT NULL,
|
|
||||||
`modules_en` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`modules_en`)),
|
|
||||||
`modules_ar` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`modules_ar`)),
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
`class_id` int(10) unsigned DEFAULT NULL,
|
|
||||||
`status` enum('active','inactive') NOT NULL DEFAULT 'active',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `slug` (`slug`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `teacher_assignments`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `teacher_assignments`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `teacher_assignments` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`teacher_id` int(11) NOT NULL,
|
|
||||||
`class_id` int(11) NOT NULL,
|
|
||||||
`subject_id` int(11) NOT NULL DEFAULT 0,
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `unique_assignment` (`teacher_id`,`class_id`,`subject_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `teachers`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `teachers`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `teachers` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`name` varchar(255) NOT NULL,
|
|
||||||
`email` varchar(255) DEFAULT NULL,
|
|
||||||
`bio` text DEFAULT NULL,
|
|
||||||
`photo_path` varchar(255) DEFAULT NULL,
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
`phone` varchar(50) DEFAULT NULL,
|
|
||||||
`password` varchar(255) DEFAULT NULL,
|
|
||||||
`status` enum('active','inactive') NOT NULL DEFAULT 'active',
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `users`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `users`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `users` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`name` varchar(255) NOT NULL,
|
|
||||||
`email` varchar(255) NOT NULL,
|
|
||||||
`password` varchar(255) NOT NULL,
|
|
||||||
`reset_token` varchar(255) DEFAULT NULL,
|
|
||||||
`reset_expires` datetime DEFAULT NULL,
|
|
||||||
`role` enum('admin','user') DEFAULT 'admin',
|
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
||||||
`phone` varchar(50) DEFAULT NULL,
|
|
||||||
`profile_picture` varchar(255) DEFAULT NULL,
|
|
||||||
`role_id` int(11) DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `email` (`email`),
|
|
||||||
KEY `fk_user_role` (`role_id`),
|
|
||||||
CONSTRAINT `fk_user_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE SET NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
|
||||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
|
||||||
|
|
||||||
-- Dump completed on 2026-04-11 15:49:39
|
|
||||||
@ -1,198 +0,0 @@
|
|||||||
/*M!999999\- enable the sandbox mode */
|
|
||||||
-- MariaDB dump 10.19 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64)
|
|
||||||
--
|
|
||||||
-- Host: 127.0.0.1 Database: app_39496
|
|
||||||
-- ------------------------------------------------------
|
|
||||||
-- Server version 10.11.14-MariaDB-0+deb12u2
|
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
|
||||||
/*!40101 SET NAMES utf8mb4 */;
|
|
||||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
|
||||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
|
||||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
|
||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `classes`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `classes` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `classes` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `classes` (`id`, `name_en`, `name_ar`, `description_en`, `description_ar`, `created_at`, `status`) VALUES (2,'Grade 11','الصف الحادي عشر','','','2026-04-06 04:07:29','active'),
|
|
||||||
(3,'Grade 10','الصف العاشر','','','2026-04-06 04:17:59','active'),
|
|
||||||
(4,'Grade 12','الصف الثاني عشر','','','2026-04-06 04:29:58','active');
|
|
||||||
/*!40000 ALTER TABLE `classes` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `course_activities`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `course_activities` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `course_activities` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `course_activities` (`id`, `course_id`, `title_en`, `title_ar`, `description_en`, `description_ar`, `created_at`) VALUES (1,1,'Quiz1','Quiz1','test','test','2026-04-06 12:44:52');
|
|
||||||
/*!40000 ALTER TABLE `course_activities` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `course_live_lessons`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `course_live_lessons` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `course_live_lessons` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `course_live_lessons` (`id`, `course_id`, `title`, `scheduled_at`, `status`, `room_name`, `meet_url`, `created_at`) VALUES (1,1,'test1','2026-04-06 17:52:00','live','room_fa6f9f9238',NULL,'2026-04-06 13:53:01');
|
|
||||||
/*!40000 ALTER TABLE `course_live_lessons` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `course_students`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `course_students` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `course_students` DISABLE KEYS */;
|
|
||||||
/*!40000 ALTER TABLE `course_students` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `courses`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `courses` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `courses` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `courses` (`id`, `name_en`, `name_ar`, `description_en`, `description_ar`, `price`, `picture`, `status`, `created_at`, `teacher_id`, `max_students`, `registration_open`) VALUES (1,'Biology Grade 12','أحياء الثاني عشر','Learn the basics of HTML, CSS, and JavaScript to build modern websites.','تعلم أساسيات HTML و CSS و JavaScript لبناء مواقع حديثة.',20.00,'assets/images/uploads/1775471381_أحياء 12.jfif','active','2026-04-06 10:15:06',1,NULL,1),
|
|
||||||
(2,'Physics Grade 12','فيزياء الثاني عشر','Deep dive into Python architecture, data structures, and algorithms.','تعمق في بنية بايثون وهياكل البيانات والخوارزميات.',20.00,'assets/images/uploads/1775471335_فيزياء 12.jfif','active','2026-04-06 10:15:06',1,NULL,1),
|
|
||||||
(3,'Chemistry Grade 12','الكيمياء الثاني عشر','Master SEO, social media marketing, and online ad strategies.','إتقان تحسين محركات البحث وتسويق وسائل التواصل الاجتماعي.',20.00,'assets/images/uploads/1775471215_كيماء 12.webp','active','2026-04-06 10:15:06',1,NULL,1);
|
|
||||||
/*!40000 ALTER TABLE `courses` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `landing_settings`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `landing_settings` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `landing_settings` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `landing_settings` (`setting_key`, `value_en`, `value_ar`) VALUES ('courses_eyebrow','Featured Courses','الدورات المميزة'),
|
|
||||||
('courses_title','Enroll in specialized short courses designed to accelerate your skills.','سجل في دورات قصيرة متخصصة لتسريع تطوير مهاراتك.'),
|
|
||||||
('flow_eyebrow','Delivery flow','مسار التسليم'),
|
|
||||||
('flow_title','One thin slice from discovery to live access.','شريحة رشيقة من الاكتشاف حتى الوصول المباشر.'),
|
|
||||||
('hero_desc','Launch a polished e-learning experience for students, teachers, and admins with English/Arabic support, Thawani billing flows, and Wablas-ready WhatsApp notifications.','أطلق تجربة تعليم إلكتروني مصقولة للطلاب والمعلمين والإدارة مع دعم الإنجليزية والعربية وتدفقات دفع ثواني وإشعارات واتساب جاهزة عبر وابلاس.'),
|
|
||||||
('hero_eyebrow','Single platform LMS','منصة تعليم موحدة'),
|
|
||||||
('hero_title','Subscriptions, multilingual classrooms, and live Google Meet learning in one precise workspace.','الاشتراكات والفصول متعددة اللغات والتعلم المباشر عبر Google Meet في مساحة واحدة دقيقة.'),
|
|
||||||
('plans_eyebrow','Plans','الخطط'),
|
|
||||||
('plans_title','Plan-based access for a shared marketplace.','وصول قائم على الخطط لمنصة مشتركة.'),
|
|
||||||
('subjects_eyebrow','Featured subjects','المواد المميزة'),
|
|
||||||
('subjects_title','Separate pages for catalog, detail, checkout, and dashboards.','صفحات منفصلة للكتالوج والتفاصيل والدفع ولوحات التحكم.');
|
|
||||||
/*!40000 ALTER TABLE `landing_settings` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `module_progress`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `module_progress` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `module_progress` DISABLE KEYS */;
|
|
||||||
/*!40000 ALTER TABLE `module_progress` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `plans`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `plans` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `plans` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `plans` (`id`, `plan_key`, `name_en`, `name_ar`, `price_monthly`, `price_yearly`, `subjects_limit`, `features_en`, `features_ar`) VALUES (1,'core','Core Plan','الخطة الأساسية',29.000,290.000,2,'[\"Access to 2 subjects\",\"Weekly live classroom\",\"Student dashboard\",\"Thawani-ready billing\"]','[\"\\u0627\\u0644\\u0648\\u0635\\u0648\\u0644 \\u0625\\u0644\\u0649 \\u0645\\u0627\\u062f\\u062a\\u064a\\u0646\",\"\\u0641\\u0635\\u0644 \\u0645\\u0628\\u0627\\u0634\\u0631 \\u0623\\u0633\\u0628\\u0648\\u0639\\u064a\",\"\\u0644\\u0648\\u062d\\u0629 \\u0627\\u0644\\u0637\\u0627\\u0644\\u0628\",\"\\u062f\\u0641\\u0639 \\u062c\\u0627\\u0647\\u0632 \\u0644\\u062b\\u0648\\u0627\\u0646\\u064a\"]'),
|
|
||||||
(2,'plus','Plus Plan','الخطة المتقدمة',59.000,590.000,4,'[\"All subjects\",\"Unlimited live rooms\",\"Teacher Q&A\",\"Wablas reminders\"]','[\"\\u062c\\u0645\\u064a\\u0639 \\u0627\\u0644\\u0645\\u0648\\u0627\\u062f\",\"\\u063a\\u0631\\u0641 \\u0645\\u0628\\u0627\\u0634\\u0631\\u0629 \\u063a\\u064a\\u0631 \\u0645\\u062d\\u062f\\u0648\\u062f\\u0629\",\"\\u0623\\u0633\\u0626\\u0644\\u0629 \\u0648\\u0623\\u062c\\u0648\\u0628\\u0629 \\u0645\\u0639 \\u0627\\u0644\\u0645\\u0639\\u0644\\u0645\",\"\\u062a\\u0630\\u0643\\u064a\\u0631\\u0627\\u062a \\u0639\\u0628\\u0631 \\u0648\\u0627\\u0628\\u0644\\u0627\\u0633\"]'),
|
|
||||||
(3,'pro','Pro Campus','الخطة الاحترافية',89.000,890.000,999,'[\"Priority support\",\"Arabic + English tracks\",\"Admin reporting\",\"Live classroom operations\"]','[\"\\u062f\\u0639\\u0645 \\u0623\\u0648\\u0644\\u0648\\u064a\\u0629\",\"\\u0645\\u0633\\u0627\\u0631\\u0627\\u062a \\u0639\\u0631\\u0628\\u064a\\u0629 \\u0648\\u0625\\u0646\\u062c\\u0644\\u064a\\u0632\\u064a\\u0629\",\"\\u062a\\u0642\\u0627\\u0631\\u064a\\u0631 \\u0625\\u062f\\u0627\\u0631\\u064a\\u0629\",\"\\u062a\\u0634\\u063a\\u064a\\u0644 \\u0627\\u0644\\u0641\\u0635\\u0648\\u0644 \\u0627\\u0644\\u0645\\u0628\\u0627\\u0634\\u0631\\u0629\"]');
|
|
||||||
/*!40000 ALTER TABLE `plans` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `platform_profile`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `platform_profile` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `platform_profile` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `platform_profile` (`id`, `name`, `description`, `logo_path`, `favicon_path`, `ctr_no`, `telephone_no`, `email_id`, `wablas_domain`, `wablas_token`, `thawani_secret_key`, `thawani_publishable_key`, `thawani_mode`, `wablas_security_key`, `smtp_host`, `smtp_port`, `smtp_secure`, `smtp_user`, `smtp_pass`, `smtp_from_email`, `smtp_from_name`) VALUES (1,' منصة سمو التعليمية ','منصة تعليمية إلكترونية مرخصة من قبل الجهات المعنية','','','45434','6584','',NULL,NULL,'3333','3333','live',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
|
|
||||||
/*!40000 ALTER TABLE `platform_profile` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `student_assessments`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `student_assessments` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `student_assessments` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `student_assessments` (`id`, `activity_id`, `student_id`, `score`, `feedback`, `created_at`) VALUES (1,1,1,85.50,'Good job','2026-04-06 12:45:29');
|
|
||||||
/*!40000 ALTER TABLE `student_assessments` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `student_subscriptions`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `student_subscriptions` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `student_subscriptions` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `student_subscriptions` (`id`, `full_name`, `email`, `password`, `reset_token`, `reset_expires`, `whatsapp`, `preferred_language`, `plan_key`, `billing_cycle`, `payment_status`, `payment_gateway`, `thawani_reference`, `wablas_opt_in`, `created_at`, `status`, `picture`, `civil_id`) VALUES (1,'Test Student','test.student@example.com',NULL,NULL,NULL,'+96890000000','en','plus','monthly','active','Thawani','THW-20260406033249-179',1,'2026-04-06 03:32:49','active',NULL,NULL),
|
|
||||||
(2,'Demo Student','demo.student@example.com',NULL,NULL,NULL,'+96891111111','en','plus','monthly','active','Thawani','THW-20260406033255-559',1,'2026-04-06 03:32:55','active',NULL,NULL),
|
|
||||||
(3,'Mohammed','aalabry@gmail.com',NULL,NULL,NULL,'96899359472','ar','plus','monthly','active','Thawani','THW-20260406123732-241',0,'2026-04-06 12:37:32','active',NULL,NULL),
|
|
||||||
(4,'Mohammed','aa@aa.aa',NULL,NULL,NULL,'99359472','ar','plus','monthly','active','Thawani','THW-20260406164246-388',0,'2026-04-06 16:42:46','active',NULL,NULL),
|
|
||||||
(5,'Ahmed Ali','aq@aa.aa',NULL,NULL,NULL,'66643211','ar','plus','monthly','active','Thawani','THW-20260406170007-801',0,'2026-04-06 17:00:07','active',NULL,NULL);
|
|
||||||
/*!40000 ALTER TABLE `student_subscriptions` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `subjects`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `subjects` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `subjects` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `subjects` (`id`, `slug`, `title_en`, `title_ar`, `summary_en`, `summary_ar`, `teacher_en`, `teacher_ar`, `level_en`, `level_ar`, `duration_en`, `duration_ar`, `next_live_en`, `next_live_ar`, `meet_url`, `modules_en`, `modules_ar`, `created_at`, `class_id`, `status`) VALUES (1,'english-fluency','English Fluency Studio','استوديو الطلاقة في الإنجليزية','Speaking-first classrooms for teens and adults with weekly live workshops.','فصول تركّز على المحادثة للمراهقين والبالغين مع ورش مباشرة أسبوعية.','Sarah Coleman','سارة كولمان','Intermediate','متوسط','12 weeks','12 أسبوعاً','Wednesday, April 8, 2026 · 6:00 PM UTC','الأربعاء 8 أبريل 2026 · 6:00 مساءً UTC','https://meet.google.com/','[\"Live speaking labs\",\"Pronunciation clinic\",\"Writing feedback\",\"Weekly vocabulary sprints\"]','[\"\\u0645\\u062e\\u062a\\u0628\\u0631\\u0627\\u062a \\u0645\\u062d\\u0627\\u062f\\u062b\\u0629 \\u0645\\u0628\\u0627\\u0634\\u0631\\u0629\",\"\\u0639\\u064a\\u0627\\u062f\\u0629 \\u0627\\u0644\\u0646\\u0637\\u0642\",\"\\u0645\\u0644\\u0627\\u062d\\u0638\\u0627\\u062a \\u0639\\u0644\\u0649 \\u0627\\u0644\\u0643\\u062a\\u0627\\u0628\\u0629\",\"\\u062f\\u0641\\u0639\\u0627\\u062a \\u0645\\u0641\\u0631\\u062f\\u0627\\u062a \\u0623\\u0633\\u0628\\u0648\\u0639\\u064a\\u0629\"]','2026-04-06 03:37:49',NULL,'active'),
|
|
||||||
(2,'stem-lab','STEM Lab Foundations','أساسيات مختبر STEM','Math and science pathways with assignments, quizzes, and teacher office hours.','مسارات في الرياضيات والعلوم مع واجبات واختبارات وساعات مكتبية للمعلم.','Omar Al-Harthy','عمر الحارثي','Beginner to Advanced','من مبتدئ إلى متقدم','16 weeks','16 أسبوعاً','Thursday, April 9, 2026 · 5:00 PM UTC','الخميس 9 أبريل 2026 · 5:00 مساءً UTC','https://meet.google.com/','[\"Algebra tracks\",\"Physics demos\",\"Guided worksheets\",\"Lab challenge reviews\"]','[\"\\u0645\\u0633\\u0627\\u0631\\u0627\\u062a \\u0627\\u0644\\u062c\\u0628\\u0631\",\"\\u0639\\u0631\\u0648\\u0636 \\u0641\\u064a\\u0632\\u064a\\u0627\\u0621\",\"\\u0623\\u0648\\u0631\\u0627\\u0642 \\u0639\\u0645\\u0644 \\u0645\\u0648\\u062c\\u0647\\u0629\",\"\\u0645\\u0631\\u0627\\u062c\\u0639\\u0627\\u062a \\u062a\\u062d\\u062f\\u064a\\u0627\\u062a \\u0627\\u0644\\u0645\\u062e\\u062a\\u0628\\u0631\"]','2026-04-06 03:37:49',NULL,'active'),
|
|
||||||
(3,'arabic-academy','Arabic Language Academy','أكاديمية اللغة العربية','Modern Standard Arabic with reading circles and live grammar sessions.','العربية الفصحى مع حلقات قراءة وجلسات قواعد مباشرة.','Maha Al-Rashdi','مها الراشدي','All levels','جميع المستويات','10 weeks','10 أسابيع','Saturday, April 11, 2026 · 4:00 PM UTC','السبت 11 أبريل 2026 · 4:00 مساءً UTC','https://meet.google.com/','[\"Reading comprehension\",\"Live grammar board\",\"Listening practice\",\"Culture sessions\"]','[\"\\u0641\\u0647\\u0645 \\u0627\\u0644\\u0645\\u0642\\u0631\\u0648\\u0621\",\"\\u0633\\u0628\\u0648\\u0631\\u0629 \\u0642\\u0648\\u0627\\u0639\\u062f \\u0645\\u0628\\u0627\\u0634\\u0631\\u0629\",\"\\u062a\\u062f\\u0631\\u064a\\u0628 \\u0627\\u0644\\u0627\\u0633\\u062a\\u0645\\u0627\\u0639\",\"\\u062c\\u0644\\u0633\\u0627\\u062a \\u062b\\u0642\\u0627\\u0641\\u064a\\u0629\"]','2026-04-06 03:37:49',NULL,'active'),
|
|
||||||
(4,'design-systems','English','اللغة الإنجليزية','UI foundations, product thinking, and portfolio reviews in live cohorts.','أساسيات الواجهات والتفكير المنتج ومراجعات المحافظ ضمن مجموعات مباشرة.','Lina Mercer','لينا ميرسر','Professional','احترافي','8 weeks','8 أسابيع','Monday, April 13, 2026 · 7:00 PM UTC','الإثنين 13 أبريل 2026 · 7:00 مساءً UTC','https://meet.google.com/','[\"UI critique rooms\",\"Systems thinking\",\"Component audits\",\"Portfolio feedback\"]','[\"\\u063a\\u0631\\u0641 \\u0646\\u0642\\u062f \\u0627\\u0644\\u0648\\u0627\\u062c\\u0647\\u0627\\u062a\",\"\\u062a\\u0641\\u0643\\u064a\\u0631 \\u0627\\u0644\\u0623\\u0646\\u0638\\u0645\\u0629\",\"\\u062a\\u062f\\u0642\\u064a\\u0642 \\u0627\\u0644\\u0645\\u0643\\u0648\\u0646\\u0627\\u062a\",\"\\u0645\\u0644\\u0627\\u062d\\u0638\\u0627\\u062a \\u0639\\u0644\\u0649 \\u0627\\u0644\\u0645\\u062d\\u0641\\u0638\\u0629\"]','2026-04-06 03:37:49',4,'active'),
|
|
||||||
(5,'english-1b1cc','English','اللغة الإنجليزية','','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2026-04-06 04:43:35',3,'active');
|
|
||||||
/*!40000 ALTER TABLE `subjects` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `teacher_assignments`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `teacher_assignments` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `teacher_assignments` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `teacher_assignments` (`id`, `teacher_id`, `class_id`, `subject_id`, `created_at`) VALUES (1,1,3,5,'2026-04-06 05:46:28');
|
|
||||||
/*!40000 ALTER TABLE `teacher_assignments` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `teachers`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `teachers` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `teachers` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `teachers` (`id`, `name`, `email`, `bio`, `photo_path`, `created_at`, `phone`, `password`, `status`) VALUES (1,'Moosa Ali Al-Abri','aalabry@gmail.com','','assets/images/uploads/teacher_1775489347_men.webp','2026-04-06 05:07:32','99359472','$2y$10$Uvj7/WOix7t9Z59YDqP4MuOKhlm.X23QSoVW5TiQ7noBWa9aRE1zS','active');
|
|
||||||
/*!40000 ALTER TABLE `teachers` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `users`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `users` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
|
|
||||||
INSERT IGNORE INTO `users` (`id`, `name`, `email`, `password`, `reset_token`, `reset_expires`, `role`, `created_at`, `phone`, `profile_picture`) VALUES (1,'Admin','admin@example.com','$2y$10$jpNfKXoZdT8yhnnK/95cGOQ./14fzBrKQmCkq4jR8mIqqFGNAQedm',NULL,NULL,'admin','2026-04-07 04:13:06',NULL,NULL);
|
|
||||||
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
|
||||||
|
|
||||||
-- Dump completed on 2026-04-07 13:32:18
|
|
||||||
745
includes/app.php
@ -1,745 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
|
||||||
session_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent browser caching globally
|
|
||||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
|
||||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
|
||||||
header('Pragma: no-cache');
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
|
|
||||||
function h(?string $value): string
|
|
||||||
{
|
|
||||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_platform_profile(): array {
|
|
||||||
static $profile = null;
|
|
||||||
if ($profile === null) {
|
|
||||||
$stmt = db()->query("SELECT * FROM platform_profile WHERE id = 1");
|
|
||||||
$profile = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if (!$profile) {
|
|
||||||
$profile = ['name' => 'LMS Platform', 'description' => '', 'logo_path' => '', 'favicon_path' => ''];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $profile;
|
|
||||||
}
|
|
||||||
function app_name(): string
|
|
||||||
{
|
|
||||||
$prof = get_platform_profile();
|
|
||||||
if (!empty($prof['name'])) {
|
|
||||||
return trim($prof['name']);
|
|
||||||
}
|
|
||||||
return trim((string) ($_SERVER['PROJECT_NAME'] ?? 'Aula')) ?: 'Aula';
|
|
||||||
}
|
|
||||||
|
|
||||||
function project_description(): string
|
|
||||||
{
|
|
||||||
return (string) ($_SERVER['PROJECT_DESCRIPTION'] ?? 'Modern multilingual e-learning classrooms with subscriptions, teachers, students, Google Meet live sessions, Thawani billing, and WhatsApp notifications.');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function get_landing_settings(): array {
|
|
||||||
static $settings = null;
|
|
||||||
if ($settings === null) {
|
|
||||||
$settings = [];
|
|
||||||
try {
|
|
||||||
$stmt = db()->query("SELECT setting_key, value_en, value_ar FROM landing_settings");
|
|
||||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
||||||
$settings[$row['setting_key']] = $row;
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
function landing_setting(string $key, string $default_en, string $default_ar = ""): string {
|
|
||||||
$settings = get_landing_settings();
|
|
||||||
$lang = current_lang();
|
|
||||||
if (isset($settings[$key])) {
|
|
||||||
$val = $lang === 'ar' ? $settings[$key]['value_ar'] : $settings[$key]['value_en'];
|
|
||||||
if (trim((string)$val) !== '') {
|
|
||||||
return $val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $lang === 'ar' ? $default_ar : $default_en;
|
|
||||||
}
|
|
||||||
function current_lang(): string
|
|
||||||
{
|
|
||||||
$lang = $_GET['lang'] ?? $_SESSION['lang'] ?? 'ar';
|
|
||||||
$lang = in_array($lang, ['en', 'ar'], true) ? $lang : 'ar';
|
|
||||||
$_SESSION['lang'] = $lang;
|
|
||||||
return $lang;
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_rtl(): bool
|
|
||||||
{
|
|
||||||
return current_lang() === 'ar';
|
|
||||||
}
|
|
||||||
|
|
||||||
function t(string $en, string $ar): string
|
|
||||||
{
|
|
||||||
return current_lang() === 'ar' ? $ar : $en;
|
|
||||||
}
|
|
||||||
|
|
||||||
function asset_url(string $path): string
|
|
||||||
{
|
|
||||||
$full = __DIR__ . '/../' . ltrim($path, '/');
|
|
||||||
$version = is_file($full) ? (string) filemtime($full) : (string) time();
|
|
||||||
return ltrim($path, '/') . '?v=' . $version;
|
|
||||||
}
|
|
||||||
|
|
||||||
function app_url(string $path, array $params = []): string
|
|
||||||
{
|
|
||||||
if (!isset($params['lang'])) {
|
|
||||||
$params['lang'] = current_lang();
|
|
||||||
}
|
|
||||||
$query = http_build_query($params);
|
|
||||||
return $path . ($query ? '?' . $query : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function page_lang_link(string $lang): string
|
|
||||||
{
|
|
||||||
$params = $_GET;
|
|
||||||
$params['lang'] = $lang;
|
|
||||||
$query = http_build_query($params);
|
|
||||||
return basename($_SERVER['PHP_SELF']) . ($query ? '?' . $query : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function subjects_catalog_static(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'english-fluency' => [
|
|
||||||
'slug' => 'english-fluency',
|
|
||||||
'title_en' => 'English Fluency Studio',
|
|
||||||
'title_ar' => 'استوديو الطلاقة في الإنجليزية',
|
|
||||||
'summary_en' => 'Speaking-first classrooms for teens and adults with weekly live workshops.',
|
|
||||||
'summary_ar' => 'فصول تركّز على المحادثة للمراهقين والبالغين مع ورش مباشرة أسبوعية.',
|
|
||||||
'teacher_en' => 'Sarah Coleman',
|
|
||||||
'teacher_ar' => 'سارة كولمان',
|
|
||||||
'level_en' => 'Intermediate',
|
|
||||||
'level_ar' => 'متوسط',
|
|
||||||
'duration_en' => '12 weeks',
|
|
||||||
'duration_ar' => '12 أسبوعاً',
|
|
||||||
'next_live_en' => 'Wednesday, April 8, 2026 · 6:00 PM UTC',
|
|
||||||
'next_live_ar' => 'الأربعاء 8 أبريل 2026 · 6:00 مساءً UTC',
|
|
||||||
'meet_url' => 'https://meet.google.com/',
|
|
||||||
'modules_en' => ['Live speaking labs', 'Pronunciation clinic', 'Writing feedback', 'Weekly vocabulary sprints'],
|
|
||||||
'modules_ar' => ['مختبرات محادثة مباشرة', 'عيادة النطق', 'ملاحظات على الكتابة', 'دفعات مفردات أسبوعية'],
|
|
||||||
],
|
|
||||||
'stem-lab' => [
|
|
||||||
'slug' => 'stem-lab',
|
|
||||||
'title_en' => 'STEM Lab Foundations',
|
|
||||||
'title_ar' => 'أساسيات مختبر STEM',
|
|
||||||
'summary_en' => 'Math and science pathways with assignments, quizzes, and teacher office hours.',
|
|
||||||
'summary_ar' => 'مسارات في الرياضيات والعلوم مع واجبات واختبارات وساعات مكتبية للمعلم.',
|
|
||||||
'teacher_en' => 'Omar Al-Harthy',
|
|
||||||
'teacher_ar' => 'عمر الحارثي',
|
|
||||||
'level_en' => 'Beginner to Advanced',
|
|
||||||
'level_ar' => 'من مبتدئ إلى متقدم',
|
|
||||||
'duration_en' => '16 weeks',
|
|
||||||
'duration_ar' => '16 أسبوعاً',
|
|
||||||
'next_live_en' => 'Thursday, April 9, 2026 · 5:00 PM UTC',
|
|
||||||
'next_live_ar' => 'الخميس 9 أبريل 2026 · 5:00 مساءً UTC',
|
|
||||||
'meet_url' => 'https://meet.google.com/',
|
|
||||||
'modules_en' => ['Algebra tracks', 'Physics demos', 'Guided worksheets', 'Lab challenge reviews'],
|
|
||||||
'modules_ar' => ['مسارات الجبر', 'عروض فيزياء', 'أوراق عمل موجهة', 'مراجعات تحديات المختبر'],
|
|
||||||
],
|
|
||||||
'arabic-academy' => [
|
|
||||||
'slug' => 'arabic-academy',
|
|
||||||
'title_en' => 'Arabic Language Academy',
|
|
||||||
'title_ar' => 'أكاديمية اللغة العربية',
|
|
||||||
'summary_en' => 'Modern Standard Arabic with reading circles and live grammar sessions.',
|
|
||||||
'summary_ar' => 'العربية الفصحى مع حلقات قراءة وجلسات قواعد مباشرة.',
|
|
||||||
'teacher_en' => 'Maha Al-Rashdi',
|
|
||||||
'teacher_ar' => 'مها الراشدي',
|
|
||||||
'level_en' => 'All levels',
|
|
||||||
'level_ar' => 'جميع المستويات',
|
|
||||||
'duration_en' => '10 weeks',
|
|
||||||
'duration_ar' => '10 أسابيع',
|
|
||||||
'next_live_en' => 'Saturday, April 11, 2026 · 4:00 PM UTC',
|
|
||||||
'next_live_ar' => 'السبت 11 أبريل 2026 · 4:00 مساءً UTC',
|
|
||||||
'meet_url' => 'https://meet.google.com/',
|
|
||||||
'modules_en' => ['Reading comprehension', 'Live grammar board', 'Listening practice', 'Culture sessions'],
|
|
||||||
'modules_ar' => ['فهم المقروء', 'سبورة قواعد مباشرة', 'تدريب الاستماع', 'جلسات ثقافية'],
|
|
||||||
],
|
|
||||||
'design-systems' => [
|
|
||||||
'slug' => 'design-systems',
|
|
||||||
'title_en' => 'Design Systems for Creators',
|
|
||||||
'title_ar' => 'أنظمة التصميم للمبدعين',
|
|
||||||
'summary_en' => 'UI foundations, product thinking, and portfolio reviews in live cohorts.',
|
|
||||||
'summary_ar' => 'أساسيات الواجهات والتفكير المنتج ومراجعات المحافظ ضمن مجموعات مباشرة.',
|
|
||||||
'teacher_en' => 'Lina Mercer',
|
|
||||||
'teacher_ar' => 'لينا ميرسر',
|
|
||||||
'level_en' => 'Professional',
|
|
||||||
'level_ar' => 'احترافي',
|
|
||||||
'duration_en' => '8 weeks',
|
|
||||||
'duration_ar' => '8 أسابيع',
|
|
||||||
'next_live_en' => 'Monday, April 13, 2026 · 7:00 PM UTC',
|
|
||||||
'next_live_ar' => 'الإثنين 13 أبريل 2026 · 7:00 مساءً UTC',
|
|
||||||
'meet_url' => 'https://meet.google.com/',
|
|
||||||
'modules_en' => ['UI critique rooms', 'Systems thinking', 'Component audits', 'Portfolio feedback'],
|
|
||||||
'modules_ar' => ['غرف نقد الواجهات', 'تفكير الأنظمة', 'تدقيق المكونات', 'ملاحظات على المحفظة'],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function plans_catalog_static(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'core' => [
|
|
||||||
'key' => 'core',
|
|
||||||
'name_en' => 'Core Plan',
|
|
||||||
'name_ar' => 'الخطة الأساسية',
|
|
||||||
'price_monthly' => 29,
|
|
||||||
'price_yearly' => 290,
|
|
||||||
'subjects_limit' => 2,
|
|
||||||
'features_en' => ['Access to 2 subjects', 'Weekly live classroom', 'Student dashboard', 'Thawani-ready billing'],
|
|
||||||
'features_ar' => ['الوصول إلى مادتين', 'فصل مباشر أسبوعي', 'لوحة الطالب', 'دفع جاهز لثواني'],
|
|
||||||
],
|
|
||||||
'plus' => [
|
|
||||||
'key' => 'plus',
|
|
||||||
'name_en' => 'Plus Plan',
|
|
||||||
'name_ar' => 'الخطة المتقدمة',
|
|
||||||
'price_monthly' => 59,
|
|
||||||
'price_yearly' => 590,
|
|
||||||
'subjects_limit' => 4,
|
|
||||||
'features_en' => ['All subjects', 'Unlimited live rooms', 'Teacher Q&A', 'WhatsApp reminders'],
|
|
||||||
'features_ar' => ['جميع المواد', 'غرف مباشرة غير محدودة', 'أسئلة وأجوبة مع المعلم', 'تذكيرات عبر واتساب'],
|
|
||||||
],
|
|
||||||
'pro' => [
|
|
||||||
'key' => 'pro',
|
|
||||||
'name_en' => 'Pro Campus',
|
|
||||||
'name_ar' => 'الخطة الاحترافية',
|
|
||||||
'price_monthly' => 89,
|
|
||||||
'price_yearly' => 890,
|
|
||||||
'subjects_limit' => 999,
|
|
||||||
'features_en' => ['Priority support', 'Arabic + English tracks', 'Admin reporting', 'Live classroom operations'],
|
|
||||||
'features_ar' => ['دعم أولوية', 'مسارات عربية وإنجليزية', 'تقارير إدارية', 'تشغيل الفصول المباشرة'],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensure_catalog_tables(): void
|
|
||||||
{
|
|
||||||
db()->exec("
|
|
||||||
CREATE TABLE IF NOT EXISTS subjects (
|
|
||||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
slug VARCHAR(100) UNIQUE NOT NULL,
|
|
||||||
title_en VARCHAR(255) NOT NULL,
|
|
||||||
title_ar VARCHAR(255) NOT NULL,
|
|
||||||
summary_en TEXT,
|
|
||||||
summary_ar TEXT,
|
|
||||||
teacher_en VARCHAR(120),
|
|
||||||
teacher_ar VARCHAR(120),
|
|
||||||
level_en VARCHAR(50),
|
|
||||||
level_ar VARCHAR(50),
|
|
||||||
duration_en VARCHAR(50),
|
|
||||||
duration_ar VARCHAR(50),
|
|
||||||
next_live_en VARCHAR(120),
|
|
||||||
next_live_ar VARCHAR(120),
|
|
||||||
meet_url VARCHAR(255),
|
|
||||||
modules_en JSON,
|
|
||||||
modules_ar JSON,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS plans (
|
|
||||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
plan_key VARCHAR(50) UNIQUE NOT NULL,
|
|
||||||
name_en VARCHAR(120) NOT NULL,
|
|
||||||
name_ar VARCHAR(120) NOT NULL,
|
|
||||||
price_monthly DECIMAL(10,3) NOT NULL,
|
|
||||||
price_yearly DECIMAL(10,3) NOT NULL,
|
|
||||||
subjects_limit INT DEFAULT 1,
|
|
||||||
features_en JSON,
|
|
||||||
features_ar JSON
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
");
|
|
||||||
|
|
||||||
$count = db()->query("SELECT COUNT(*) FROM subjects")->fetchColumn();
|
|
||||||
if ($count == 0) {
|
|
||||||
$subjects = subjects_catalog_static();
|
|
||||||
$stmt = db()->prepare("INSERT INTO subjects (slug, title_en, title_ar, summary_en, summary_ar, teacher_en, teacher_ar, level_en, level_ar, duration_en, duration_ar, next_live_en, next_live_ar, meet_url, modules_en, modules_ar) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
foreach ($subjects as $s) {
|
|
||||||
$stmt->execute([
|
|
||||||
$s['slug'], $s['title_en'], $s['title_ar'], $s['summary_en'], $s['summary_ar'], $s['teacher_en'], $s['teacher_ar'], $s['level_en'], $s['level_ar'], $s['duration_en'], $s['duration_ar'], $s['next_live_en'], $s['next_live_ar'], $s['meet_url'], json_encode($s['modules_en']), json_encode($s['modules_ar'])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$count = db()->query("SELECT COUNT(*) FROM plans")->fetchColumn();
|
|
||||||
if ($count == 0) {
|
|
||||||
$plans = plans_catalog_static();
|
|
||||||
$stmt = db()->prepare("INSERT INTO plans (plan_key, name_en, name_ar, price_monthly, price_yearly, subjects_limit, features_en, features_ar) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
foreach ($plans as $p) {
|
|
||||||
$stmt->execute([
|
|
||||||
$p['key'], $p['name_en'], $p['name_ar'], $p['price_monthly'], $p['price_yearly'], $p['subjects_limit'], json_encode($p['features_en']), json_encode($p['features_ar'])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function subjects_catalog(): array
|
|
||||||
{
|
|
||||||
ensure_catalog_tables();
|
|
||||||
$stmt = db()->query("SELECT s.* FROM subjects s LEFT JOIN classes c ON s.class_id = c.id WHERE s.status = 'active' AND (s.class_id IS NULL OR s.class_id = 0 OR c.status = 'active')");
|
|
||||||
$rows = $stmt->fetchAll();
|
|
||||||
$result = [];
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$row['modules_en'] = json_decode($row['modules_en'] ? (string)$row['modules_en'] : '[]', true) ?: [];
|
|
||||||
$row['modules_ar'] = json_decode($row['modules_ar'] ? (string)$row['modules_ar'] : '[]', true) ?: [];
|
|
||||||
|
|
||||||
$class_id = (int)$row['class_id'];
|
|
||||||
$subject_id = (int)$row['id'];
|
|
||||||
|
|
||||||
// Dynamically fetch active assigned teacher
|
|
||||||
$teacher_stmt = db()->prepare(
|
|
||||||
"SELECT t.name
|
|
||||||
FROM teacher_assignments ta
|
|
||||||
JOIN teachers t ON ta.teacher_id = t.id
|
|
||||||
WHERE t.status = 'active'
|
|
||||||
AND ta.class_id = ?
|
|
||||||
AND (ta.subject_id = ? OR ta.subject_id = 0)
|
|
||||||
LIMIT 1"
|
|
||||||
);
|
|
||||||
$teacher_stmt->execute([$class_id, $subject_id]);
|
|
||||||
$teacher_name = $teacher_stmt->fetchColumn();
|
|
||||||
|
|
||||||
if ($teacher_name) {
|
|
||||||
$row['teacher_en'] = $teacher_name;
|
|
||||||
$row['teacher_ar'] = $teacher_name;
|
|
||||||
} else {
|
|
||||||
// Check if there is an inactive teacher assigned. If so, override with empty or "No Teacher".
|
|
||||||
$inactive_stmt = db()->prepare(
|
|
||||||
"SELECT t.name
|
|
||||||
FROM teacher_assignments ta
|
|
||||||
JOIN teachers t ON ta.teacher_id = t.id
|
|
||||||
WHERE t.status = 'inactive'
|
|
||||||
AND ta.class_id = ?
|
|
||||||
AND (ta.subject_id = ? OR ta.subject_id = 0)
|
|
||||||
LIMIT 1"
|
|
||||||
);
|
|
||||||
$inactive_stmt->execute([$class_id, $subject_id]);
|
|
||||||
if ($inactive_stmt->fetchColumn()) {
|
|
||||||
$row['teacher_en'] = 'No Active Teacher';
|
|
||||||
$row['teacher_ar'] = 'لا يوجد معلم نشط';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$result[$row['slug']] = $row;
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function plans_catalog(): array
|
|
||||||
{
|
|
||||||
ensure_catalog_tables();
|
|
||||||
$stmt = db()->query('SELECT * FROM plans');
|
|
||||||
$rows = $stmt->fetchAll();
|
|
||||||
$result = [];
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$row['key'] = $row['plan_key'];
|
|
||||||
$row['features_en'] = json_decode($row['features_en'] ?? '[]', true) ?: [];
|
|
||||||
$row['features_ar'] = json_decode($row['features_ar'] ?? '[]', true) ?: [];
|
|
||||||
$result[$row['key']] = $row;
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function ensure_progress_table(): void
|
|
||||||
{
|
|
||||||
db()->exec("
|
|
||||||
CREATE TABLE IF NOT EXISTS module_progress (
|
|
||||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
subscription_id INT UNSIGNED NOT NULL,
|
|
||||||
subject_slug VARCHAR(100) NOT NULL,
|
|
||||||
module_index INT UNSIGNED NOT NULL,
|
|
||||||
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE KEY(subscription_id, subject_slug, module_index)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_completed_modules(int $subscriptionId, string $subjectSlug): array
|
|
||||||
{
|
|
||||||
ensure_progress_table();
|
|
||||||
$stmt = db()->prepare("SELECT module_index FROM module_progress WHERE subscription_id = ? AND subject_slug = ?");
|
|
||||||
$stmt->execute([$subscriptionId, $subjectSlug]);
|
|
||||||
return $stmt->fetchAll(PDO::FETCH_COLUMN) ?: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_module_progress(int $subscriptionId, string $subjectSlug, int $moduleIndex, bool $completed): void
|
|
||||||
{
|
|
||||||
ensure_progress_table();
|
|
||||||
if ($completed) {
|
|
||||||
$stmt = db()->prepare("INSERT IGNORE INTO module_progress (subscription_id, subject_slug, module_index) VALUES (?, ?, ?)");
|
|
||||||
$stmt->execute([$subscriptionId, $subjectSlug, $moduleIndex]);
|
|
||||||
} else {
|
|
||||||
$stmt = db()->prepare("DELETE FROM module_progress WHERE subscription_id = ? AND subject_slug = ? AND module_index = ?");
|
|
||||||
$stmt->execute([$subscriptionId, $subjectSlug, $moduleIndex]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_subject(string $slug): ?array
|
|
||||||
{
|
|
||||||
$subjects = subjects_catalog();
|
|
||||||
return $subjects[$slug] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_plan(string $key): ?array
|
|
||||||
{
|
|
||||||
$plans = plans_catalog();
|
|
||||||
return $plans[$key] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function plan_name(array $plan): string
|
|
||||||
{
|
|
||||||
return current_lang() === 'ar' ? $plan['name_ar'] : $plan['name_en'];
|
|
||||||
}
|
|
||||||
|
|
||||||
function subject_title(array $subject): string
|
|
||||||
{
|
|
||||||
return current_lang() === 'ar' ? $subject['title_ar'] : $subject['title_en'];
|
|
||||||
}
|
|
||||||
|
|
||||||
function subject_summary(array $subject): string
|
|
||||||
{
|
|
||||||
return current_lang() === 'ar' ? $subject['summary_ar'] : $subject['summary_en'];
|
|
||||||
}
|
|
||||||
|
|
||||||
function subject_teacher(array $subject): string
|
|
||||||
{
|
|
||||||
return (string)(current_lang() === 'ar' ? ($subject['teacher_ar'] ?? '') : ($subject['teacher_en'] ?? ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
function subject_level(array $subject): string
|
|
||||||
{
|
|
||||||
return (string)(current_lang() === 'ar' ? ($subject['level_ar'] ?? '') : ($subject['level_en'] ?? ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
function subject_duration(array $subject): string
|
|
||||||
{
|
|
||||||
return (string)(current_lang() === 'ar' ? ($subject['duration_ar'] ?? '') : ($subject['duration_en'] ?? ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
function subject_next_live(array $subject): string
|
|
||||||
{
|
|
||||||
return (string)(current_lang() === 'ar' ? ($subject['next_live_ar'] ?? '') : ($subject['next_live_en'] ?? ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
function subject_modules(array $subject): array
|
|
||||||
{
|
|
||||||
return current_lang() === 'ar' ? $subject['modules_ar'] : $subject['modules_en'];
|
|
||||||
}
|
|
||||||
|
|
||||||
function format_price(float $amount): string
|
|
||||||
{
|
|
||||||
return number_format($amount, 3) . ' ' . t('OMR', 'ر.ع.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function price_label(array $plan, string $cycle = 'monthly'): string
|
|
||||||
{
|
|
||||||
$amount = $cycle === 'yearly' ? $plan['price_yearly'] : $plan['price_monthly'];
|
|
||||||
$suffix = $cycle === 'yearly' ? t('/year', '/سنة') : t('/month', '/شهر');
|
|
||||||
return format_price((float)$amount) . ' ' . $suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensure_subscription_table(): void
|
|
||||||
{
|
|
||||||
db()->exec(
|
|
||||||
"CREATE TABLE IF NOT EXISTS student_subscriptions (
|
|
||||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
full_name VARCHAR(120) NOT NULL,
|
|
||||||
email VARCHAR(190) NOT NULL,
|
|
||||||
whatsapp VARCHAR(40) NOT NULL,
|
|
||||||
preferred_language VARCHAR(5) NOT NULL DEFAULT 'en',
|
|
||||||
plan_key VARCHAR(20) NOT NULL,
|
|
||||||
billing_cycle VARCHAR(20) NOT NULL DEFAULT 'monthly',
|
|
||||||
payment_status VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
||||||
payment_gateway VARCHAR(30) NOT NULL DEFAULT 'Thawani',
|
|
||||||
thawani_reference VARCHAR(80) NOT NULL,
|
|
||||||
wablas_opt_in TINYINT(1) NOT NULL DEFAULT 0,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_subscription(array $data): int
|
|
||||||
{
|
|
||||||
ensure_subscription_table();
|
|
||||||
$stmt = db()->prepare(
|
|
||||||
'INSERT INTO student_subscriptions
|
|
||||||
(full_name, email, whatsapp, preferred_language, plan_key, billing_cycle, payment_status, payment_gateway, thawani_reference, wablas_opt_in)
|
|
||||||
VALUES (:full_name, :email, :whatsapp, :preferred_language, :plan_key, :billing_cycle, :payment_status, :payment_gateway, :thawani_reference, :wablas_opt_in)'
|
|
||||||
);
|
|
||||||
|
|
||||||
$stmt->bindValue(':full_name', $data['full_name']);
|
|
||||||
$stmt->bindValue(':email', $data['email']);
|
|
||||||
$stmt->bindValue(':whatsapp', $data['whatsapp']);
|
|
||||||
$stmt->bindValue(':preferred_language', $data['preferred_language']);
|
|
||||||
$stmt->bindValue(':plan_key', $data['plan_key']);
|
|
||||||
$stmt->bindValue(':billing_cycle', $data['billing_cycle']);
|
|
||||||
$stmt->bindValue(':payment_status', $data['payment_status']);
|
|
||||||
$stmt->bindValue(':payment_gateway', $data['payment_gateway']);
|
|
||||||
$stmt->bindValue(':thawani_reference', $data['thawani_reference']);
|
|
||||||
$stmt->bindValue(':wablas_opt_in', $data['wablas_opt_in'], PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
return (int) db()->lastInsertId();
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetch_subscription(int $id): ?array
|
|
||||||
{
|
|
||||||
ensure_subscription_table();
|
|
||||||
$stmt = db()->prepare('SELECT * FROM student_subscriptions WHERE id = :id LIMIT 1');
|
|
||||||
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
$row = $stmt->fetch();
|
|
||||||
return $row ?: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetch_subscriptions_by_email(string $email): array
|
|
||||||
{
|
|
||||||
ensure_subscription_table();
|
|
||||||
$stmt = db()->prepare('SELECT * FROM student_subscriptions WHERE email = :email ORDER BY created_at DESC');
|
|
||||||
$stmt->bindValue(':email', $email);
|
|
||||||
$stmt->execute();
|
|
||||||
return $stmt->fetchAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetch_recent_subscriptions(int $limit = 8): array
|
|
||||||
{
|
|
||||||
ensure_subscription_table();
|
|
||||||
$stmt = db()->prepare('SELECT * FROM student_subscriptions ORDER BY created_at DESC LIMIT :limit');
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
return $stmt->fetchAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscription_metrics(): array
|
|
||||||
{
|
|
||||||
ensure_subscription_table();
|
|
||||||
$metrics = [
|
|
||||||
'total' => 0,
|
|
||||||
'arabic' => 0,
|
|
||||||
'active' => 0,
|
|
||||||
'latest' => null,
|
|
||||||
];
|
|
||||||
|
|
||||||
$totals = db()->query("SELECT
|
|
||||||
COUNT(*) AS total,
|
|
||||||
SUM(preferred_language = 'ar') AS arabic,
|
|
||||||
SUM(payment_status = 'active') AS active
|
|
||||||
FROM student_subscriptions")->fetch();
|
|
||||||
|
|
||||||
if ($totals) {
|
|
||||||
$metrics['total'] = (int) ($totals['total'] ?? 0);
|
|
||||||
$metrics['arabic'] = (int) ($totals['arabic'] ?? 0);
|
|
||||||
$metrics['active'] = (int) ($totals['active'] ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
$latest = db()->query('SELECT * FROM student_subscriptions ORDER BY created_at DESC LIMIT 1')->fetch();
|
|
||||||
$metrics['latest'] = $latest ?: null;
|
|
||||||
return $metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
function current_subscription(): ?array
|
|
||||||
{
|
|
||||||
$id = isset($_GET['subscription_id']) ? (int) $_GET['subscription_id'] : (int) ($_SESSION['subscription_id'] ?? 0);
|
|
||||||
if ($id > 0) {
|
|
||||||
$row = fetch_subscription($id);
|
|
||||||
if ($row) {
|
|
||||||
$_SESSION['subscription_id'] = (int) $row['id'];
|
|
||||||
$_SESSION['student_email'] = $row['email'];
|
|
||||||
return $row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$email = (string) ($_SESSION['student_email'] ?? '');
|
|
||||||
if ($email !== '') {
|
|
||||||
$rows = fetch_subscriptions_by_email($email);
|
|
||||||
if ($rows) {
|
|
||||||
$_SESSION['subscription_id'] = (int) $rows[0]['id'];
|
|
||||||
return $rows[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function page_title(string $title): string
|
|
||||||
{
|
|
||||||
return $title . ' · ' . app_name();
|
|
||||||
}
|
|
||||||
|
|
||||||
function render_head(string $title, string $description = ''): void
|
|
||||||
{
|
|
||||||
$pageDescription = $description !== '' ? $description : project_description();
|
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="<?= h(current_lang()) ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title><?= h(page_title($title)) ?></title>
|
|
||||||
<?php
|
|
||||||
$prof = get_platform_profile();
|
|
||||||
if (!empty($prof['favicon_path'])):
|
|
||||||
?>
|
|
||||||
<link rel="icon" href="<?= h(asset_url($prof['favicon_path'])) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<meta name="description" content="<?= h($pageDescription) ?>" />
|
|
||||||
<?php if ($projectDescription): ?>
|
|
||||||
<meta property="og:description" content="<?= h($projectDescription) ?>" />
|
|
||||||
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($projectImageUrl): ?>
|
|
||||||
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
|
|
||||||
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<meta name="theme-color" content="#111827" />
|
|
||||||
<meta name="author" content="<?= h(app_name()) ?>" />
|
|
||||||
<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=Cairo:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="<?= h(asset_url('assets/css/custom.css')) ?>">
|
|
||||||
</head>
|
|
||||||
<body class="app-shell <?= is_rtl() ? 'rtl' : 'ltr' ?>">
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
function nav_items(): array
|
|
||||||
{
|
|
||||||
$items = [
|
|
||||||
'index.php' => t('Home', 'الرئيسية'),
|
|
||||||
'catalog.php' => t('Subjects', 'المواد'),
|
|
||||||
'courses.php' => t('Courses', 'الدورات'),
|
|
||||||
'pricing.php' => t('Plans', 'الخطط'),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!empty($_SESSION['user_id'])) {
|
|
||||||
$stmt = db()->prepare("SELECT role FROM users WHERE id = ?");
|
|
||||||
$stmt->execute([$_SESSION['user_id']]);
|
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if ($user) {
|
|
||||||
if ($user['role'] === 'student') {
|
|
||||||
$items['dashboard.php'] = t('Student', 'الطالب');
|
|
||||||
} elseif ($user['role'] === 'teacher') {
|
|
||||||
$items['teacher.php'] = t('Teacher', 'المعلم');
|
|
||||||
} elseif ($user['role'] === 'admin') {
|
|
||||||
$items['admin.php'] = t('Admin', 'الإدارة');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $items;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render_nav(string $active = ''): void
|
|
||||||
{
|
|
||||||
$items = nav_items();
|
|
||||||
?>
|
|
||||||
<nav class="navbar navbar-expand-lg border-bottom bg-white sticky-top app-navbar">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand fw-semibold d-flex align-items-center" href="<?= h(app_url('index.php')) ?>">
|
|
||||||
<?php $prof = get_platform_profile(); if (!empty($prof['logo_path'])): ?>
|
|
||||||
<img src="<?= h(asset_url($prof['logo_path'])) ?>" alt="Logo" style="height: 32px; margin-right: 8px; border-radius: 4px;">
|
|
||||||
<?php endif; ?>
|
|
||||||
<span><?= h(app_name()) ?></span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="mainNav">
|
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0 gap-lg-2">
|
|
||||||
<?php foreach ($items as $file => $label): ?>
|
|
||||||
<li class="nav-item"><a class="nav-link <?= $active === $file ? 'active' : '' ?>" href="<?= h(app_url($file)) ?>"><?= h($label) ?></a></li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
<div class="d-flex align-items-center gap-2 ms-lg-3">
|
|
||||||
<span class="small text-secondary d-none d-md-inline"><?= h(t('Single platform LMS', 'منصة تعليم موحدة')) ?></span>
|
|
||||||
<div class="btn-group btn-group-sm" role="group" aria-label="Language switcher">
|
|
||||||
<a class="btn btn-outline-dark <?= current_lang() === 'en' ? 'active' : '' ?>" href="<?= h(page_lang_link('en')) ?>">EN</a>
|
|
||||||
<a class="btn btn-outline-dark <?= current_lang() === 'ar' ? 'active' : '' ?>" href="<?= h(page_lang_link('ar')) ?>">AR</a>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-2 ms-2 align-items-center">
|
|
||||||
<?php if (!empty($_SESSION['user_id'])): ?>
|
|
||||||
<?php
|
|
||||||
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
|
||||||
$stmt->execute([$_SESSION['user_id']]);
|
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
|
||||||
?>
|
|
||||||
<div class="dropdown">
|
|
||||||
<a class="btn btn-light dropdown-toggle d-flex align-items-center gap-2 border-0 rounded-pill px-2 py-1 shadow-sm" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" style="background: rgba(255,255,255,0.85); backdrop-filter: blur(10px);">
|
|
||||||
<?php if (!empty($user['profile_picture'])): ?>
|
|
||||||
<img src="<?= h(asset_url($user['profile_picture'])) ?>" alt="Profile" class="rounded-circle shadow-sm" width="32" height="32" style="object-fit: cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="rounded-circle bg-primary text-white d-flex justify-content-center align-items-center shadow-sm" style="width:32px; height:32px; font-size:14px; font-weight:600;">
|
|
||||||
<?= h(strtoupper(mb_substr($user['name'] ?? 'U', 0, 1, 'UTF-8'))) ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<span class="d-none d-lg-inline small fw-bold text-dark px-1"><?= h($user['name'] ?? 'User') ?></span>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2" style="border-radius: 12px; min-width: 180px;">
|
|
||||||
<li><a class="dropdown-item py-2 fw-medium" href="<?= h(app_url('profile.php')) ?>"><?= h(t('My Profile', 'حسابي')) ?></a></li>
|
|
||||||
<li><hr class="dropdown-divider"></li>
|
|
||||||
<li><a class="dropdown-item py-2 text-danger fw-medium" href="<?= h(app_url('logout.php')) ?>"><?= h(t('Logout', 'تسجيل خروج')) ?></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<a href="<?= h(app_url('login.php')) ?>" class="btn btn-dark btn-sm px-4 rounded-pill shadow-sm fw-bold"><?= h(t('Login', 'تسجيل الدخول')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
function render_footer(): void
|
|
||||||
{
|
|
||||||
?>
|
|
||||||
<footer class="border-top bg-white mt-5">
|
|
||||||
<div class="container py-4 d-flex flex-column flex-lg-row justify-content-between gap-3 small text-secondary">
|
|
||||||
<div>
|
|
||||||
<strong class="text-dark"><?= h(app_name()) ?></strong>
|
|
||||||
<div><?= h(t('Subscription-based classrooms in English and Arabic with Google Meet, Thawani, and WhatsApp workflows.', 'فصول باشتراكات بالإنجليزية والعربية مع تدفقات عمل Google Meet وThawani وواتساب.')) ?></div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-3 flex-wrap">
|
|
||||||
<a class="text-decoration-none text-secondary" href="<?= h(app_url('catalog.php')) ?>"><?= h(t('Subject catalog', 'كتالوج المواد')) ?></a>
|
|
||||||
<a class="text-decoration-none text-secondary" href="<?= h(app_url('courses.php')) ?>"><?= h(t('Courses', 'الدورات')) ?></a>
|
|
||||||
<a class="text-decoration-none text-secondary" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Plans', 'الخطط')) ?></a>
|
|
||||||
<a class="text-decoration-none text-secondary" href="<?= h(app_url('dashboard.php')) ?>"><?= h(t('Student dashboard', 'لوحة الطالب')) ?></a>
|
|
||||||
<a class="text-decoration-none text-secondary" href="<?= h(app_url('terms.php')) ?>"><?= h(t('Terms of Conditions', 'الشروط والأحكام')) ?></a>
|
|
||||||
<a class="text-decoration-none text-secondary" href="<?= h(app_url('policy.php')) ?>"><?= h(t('Privacy Policy', 'سياسة الخصوصية')) ?></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
||||||
<script src="<?= h(asset_url('assets/js/main.js')) ?>"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
function status_badge(string $status): string
|
|
||||||
{
|
|
||||||
return match ($status) {
|
|
||||||
'active' => 'badge bg-success-subtle text-success-emphasis border border-success-subtle',
|
|
||||||
'pending' => 'badge bg-warning-subtle text-warning-emphasis border border-warning-subtle',
|
|
||||||
default => 'badge bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/app.php';
|
|
||||||
|
|
||||||
function require_login() {
|
|
||||||
if (empty($_SESSION['user_id'])) {
|
|
||||||
header('Location: login.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
|
||||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
|
||||||
header('Pragma: no-cache');
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_logged_in_user() {
|
|
||||||
if (empty($_SESSION['user_id'])) return null;
|
|
||||||
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
|
||||||
$stmt->execute([$_SESSION['user_id']]);
|
|
||||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function has_permission($page, $action = 'view') {
|
|
||||||
$user = get_logged_in_user();
|
|
||||||
if (!$user) return false;
|
|
||||||
|
|
||||||
// Super Admins bypass permissions. Fallback logic.
|
|
||||||
if ($user['role'] === 'admin' && empty($user['role_id'])) return true;
|
|
||||||
|
|
||||||
if (!empty($user['role_id'])) {
|
|
||||||
$stmt = db()->prepare("SELECT is_system FROM roles WHERE id = ?");
|
|
||||||
$stmt->execute([$user['role_id']]);
|
|
||||||
$role = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if ($role && $role['is_system']) return true; // Super admin
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT * FROM role_permissions WHERE role_id = ? AND page = ?");
|
|
||||||
$stmt->execute([$user['role_id'], $page]);
|
|
||||||
$perms = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($perms) {
|
|
||||||
$col = 'can_' . $action;
|
|
||||||
return !empty($perms[$col]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function require_permission($page, $action = 'view') {
|
|
||||||
if (!has_permission($page, $action)) {
|
|
||||||
http_response_code(403);
|
|
||||||
die("403 Forbidden - You don't have permission to perform this action.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
366
index.php
@ -1,226 +1,150 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/app.php';
|
declare(strict_types=1);
|
||||||
|
@ini_set('display_errors', '1');
|
||||||
|
@error_reporting(E_ALL);
|
||||||
|
@date_default_timezone_set('UTC');
|
||||||
|
|
||||||
render_head(
|
$phpVersion = PHP_VERSION;
|
||||||
t('Modern bilingual classrooms for live online learning', 'فصول حديثة ثنائية اللغة للتعلم المباشر عبر الإنترنت'),
|
$now = date('Y-m-d H:i:s');
|
||||||
t('Explore a polished LMS landing page with plans, subjects, live classes, and bilingual student journeys.', 'استكشف صفحة هبوط لمنصة تعليمية حديثة تشمل الخطط والمواد والفصول المباشرة وتجربة ثنائية اللغة.')
|
|
||||||
);
|
|
||||||
render_nav('index.php');
|
|
||||||
$subjects = subjects_catalog();
|
|
||||||
$courses = db()->query("SELECT * FROM courses WHERE status = 'active' ORDER BY created_at DESC LIMIT 3")->fetchAll();
|
|
||||||
$plans = plans_catalog();
|
|
||||||
$metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT COUNT(*) FROM teachers WHERE status = 'active'")->fetchColumn(), 'live' => 18];
|
|
||||||
?>
|
?>
|
||||||
<main>
|
<!doctype html>
|
||||||
<section class="hero-section border-bottom">
|
<html lang="en">
|
||||||
<div class="container py-5 py-lg-6">
|
<head>
|
||||||
<div class="row align-items-center g-4 g-lg-5">
|
<meta charset="utf-8" />
|
||||||
<div class="col-lg-7">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<span class="eyebrow"><?= h(landing_setting('hero_eyebrow', 'Single platform LMS', 'منصة تعليم موحدة')) ?></span>
|
<title>New Style</title>
|
||||||
<h1 class="display-title mt-3 mb-3"><?= h(landing_setting('hero_title', 'Subscriptions, multilingual classrooms, and live Google Meet learning in one precise workspace.', 'الاشتراكات والفصول متعددة اللغات والتعلم المباشر عبر Google Meet في مساحة واحدة دقيقة.')) ?></h1>
|
<?php
|
||||||
<p class="lead text-secondary mb-4"><?= h(landing_setting('hero_desc', 'Launch a polished e-learning experience for students, teachers, and admins with English/Arabic support, Thawani billing flows, and WhatsApp notifications.', 'أطلق تجربة تعليم إلكتروني مصقولة للطلاب والمعلمين والإدارة مع دعم الإنجليزية والعربية وتدفقات دفع ثواني وإشعارات واتساب.')) ?></p>
|
// Read project preview data from environment
|
||||||
<div class="d-flex flex-wrap gap-2 mb-4">
|
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||||
<a class="btn btn-primary btn-lg rounded-pill px-4 shadow-sm" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Start subscription flow', 'ابدأ مسار الاشتراك')) ?></a>
|
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||||
<a class="btn btn-light btn-lg rounded-pill px-4 shadow-sm text-primary fw-bold" href="<?= h(app_url('catalog.php')) ?>"><?= h(t('Browse subjects', 'تصفح المواد')) ?></a>
|
?>
|
||||||
</div>
|
<?php if ($projectDescription): ?>
|
||||||
<div class="row row-cols-1 row-cols-sm-3 g-3 small-stat-grid">
|
<!-- Meta description -->
|
||||||
<div class="col">
|
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||||
<div class="metric-card"><strong><?= h((string) $metrics['subjects']) ?></strong><span><?= h(t('core subjects', 'مواد أساسية')) ?></span></div>
|
<!-- Open Graph meta tags -->
|
||||||
</div>
|
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
<div class="col">
|
<!-- Twitter meta tags -->
|
||||||
<div class="metric-card"><strong><?= h((string) $metrics['teachers']) ?></strong><span><?= h(t('active teachers', 'معلمون نشطون')) ?></span></div>
|
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
</div>
|
<?php endif; ?>
|
||||||
<div class="col">
|
<?php if ($projectImageUrl): ?>
|
||||||
<div class="metric-card"><strong><?= h((string) $metrics['live']) ?></strong><span><?= h(t('live sessions weekly', 'جلسة مباشرة أسبوعياً')) ?></span></div>
|
<!-- Open Graph image -->
|
||||||
</div>
|
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
</div>
|
<!-- Twitter image -->
|
||||||
</div>
|
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
<div class="col-lg-5">
|
<?php endif; ?>
|
||||||
<?php if ($hero_img = landing_setting('hero_image', '')): ?>
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<img src="<?= h($hero_img) ?>" class="img-fluid rounded-4 shadow-lg w-100" style="object-fit: cover; max-height: 500px;" alt="Hero Image">
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<?php else: ?>
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||||
<?php endif; ?>
|
<style>
|
||||||
</div>
|
:root {
|
||||||
|
--bg-color-start: #6a11cb;
|
||||||
|
--bg-color-end: #2575fc;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||||
|
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||||
|
animation: bg-pan 20s linear infinite;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
@keyframes bg-pan {
|
||||||
|
0% { background-position: 0% 0%; }
|
||||||
|
100% { background-position: 100% 100%; }
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: var(--card-bg-color);
|
||||||
|
border: 1px solid var(--card-border-color);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2rem;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.loader {
|
||||||
|
margin: 1.25rem auto 1.25rem;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||||
|
border-top-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
.hint {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px; height: 1px;
|
||||||
|
padding: 0; margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap; border: 0;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="card">
|
||||||
|
<h1>Analyzing your requirements and generating your website…</h1>
|
||||||
|
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||||
|
<span class="sr-only">Loading…</span>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||||
|
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||||
|
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</main>
|
||||||
|
<footer>
|
||||||
|
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||||
<section class="py-5 border-bottom bg-light">
|
</footer>
|
||||||
<div class="container">
|
</body>
|
||||||
<?php if ($courses_img = landing_setting('courses_image', '')): ?>
|
</html>
|
||||||
<div class="row align-items-center mb-4">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('courses_eyebrow', 'Featured Courses', 'الدورات المميزة')) ?></span>
|
|
||||||
<h2 class="section-title mb-0"><?= h(landing_setting('courses_title', 'Enroll in specialized short courses designed to accelerate your skills.', 'سجل في دورات قصيرة متخصصة لتسريع تطوير مهاراتك.')) ?></h2>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5 text-lg-end mt-3 mt-lg-0">
|
|
||||||
<img src="<?= h($courses_img) ?>" class="img-fluid rounded shadow-sm" alt="Courses" style="max-height: 150px; object-fit: cover;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="section-header">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('courses_eyebrow', 'Featured Courses', 'الدورات المميزة')) ?></span>
|
|
||||||
<h2 class="section-title"><?= h(landing_setting('courses_title', 'Enroll in specialized short courses designed to accelerate your skills.', 'سجل في دورات قصيرة متخصصة لتسريع تطوير مهاراتك.')) ?></h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="row g-4 mt-3">
|
|
||||||
<?php foreach ($courses as $course): ?>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<article class="card h-100 border-0 shadow-sm overflow-hidden" style="border-radius: 1rem;">
|
|
||||||
<?php if (!empty($course['picture'])): ?>
|
|
||||||
<img src="<?= h($course['picture']) ?>" class="card-img-top" alt="<?= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?>" style="height: 200px; object-fit: cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="card-img-top bg-secondary" style="height: 200px;"></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="card-body d-flex flex-column p-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
||||||
<h3 class="h5 mb-0 fw-bold"><?= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?></h3>
|
|
||||||
<span class="badge bg-primary bg-gradient text-white rounded-pill fs-6 px-3 py-2 shadow-sm"><?= h(format_price((float)$course['price'])) ?></span>
|
|
||||||
</div>
|
|
||||||
<p class="text-secondary mb-4 flex-grow-1"><?= h(current_lang() === 'ar' ? $course['description_ar'] : $course['description_en']) ?></p>
|
|
||||||
<div class="d-grid mt-auto">
|
|
||||||
<?php
|
|
||||||
$isFull = false;
|
|
||||||
$isClosed = !$course['registration_open'];
|
|
||||||
if ($course['max_students'] > 0) {
|
|
||||||
$enrolled = db()->query("SELECT COUNT(*) FROM course_students WHERE course_id = " . $course['id'])->fetchColumn();
|
|
||||||
if ($enrolled >= $course['max_students']) {
|
|
||||||
$isFull = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($isClosed || $isFull):
|
|
||||||
?>
|
|
||||||
<button class="btn btn-secondary" disabled><?= h($isClosed ? t('Registration Closed', 'التسجيل مغلق') : t('Class Full', 'مكتمل العدد')) ?></button>
|
|
||||||
<?php else: ?>
|
|
||||||
<a class="btn btn-primary rounded-pill px-3 shadow-sm" href="<?= h(app_url('checkout.php', ['course_id' => $course['id']])) ?>"><?= h(t('Enroll Now', 'سجل الآن')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<div class="text-center mt-5">
|
|
||||||
<a class="btn btn-outline-dark rounded-pill px-4 shadow-sm fw-bold" href="<?= h(app_url('courses.php')) ?>"><?= h(t('View All Courses', 'عرض جميع الدورات')) ?></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="py-5 border-bottom bg-white">
|
|
||||||
<div class="container">
|
|
||||||
<?php if ($subjects_img = landing_setting('subjects_image', '')): ?>
|
|
||||||
<div class="row align-items-center mb-4">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('subjects_eyebrow', 'Featured subjects', 'المواد المميزة')) ?></span>
|
|
||||||
<h2 class="section-title mb-2"><?= h(landing_setting('subjects_title', 'Separate pages for catalog, detail, checkout, and dashboards.', 'صفحات منفصلة للكتالوج والتفاصيل والدفع ولوحات التحكم.')) ?></h2>
|
|
||||||
<a class="link-dark fw-semibold text-decoration-none" href="<?= h(app_url('catalog.php')) ?>"><?= h(t('See all subjects', 'عرض جميع المواد')) ?></a>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5 text-lg-end mt-3 mt-lg-0">
|
|
||||||
<img src="<?= h($subjects_img) ?>" class="img-fluid rounded shadow-sm" alt="Subjects" style="max-height: 150px; object-fit: cover;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="section-header">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('subjects_eyebrow', 'Featured subjects', 'المواد المميزة')) ?></span>
|
|
||||||
<h2 class="section-title"><?= h(landing_setting('subjects_title', 'Separate pages for catalog, detail, checkout, and dashboards.', 'صفحات منفصلة للكتالوج والتفاصيل والدفع ولوحات التحكم.')) ?></h2>
|
|
||||||
</div>
|
|
||||||
<a class="link-dark fw-semibold text-decoration-none" href="<?= h(app_url('catalog.php')) ?>"><?= h(t('See all subjects', 'عرض جميع المواد')) ?></a>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="row g-3 mt-1">
|
|
||||||
<?php foreach (array_slice($subjects, 0, 3) as $subject): ?>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<article class="subject-card h-100">
|
|
||||||
<div class="subject-meta d-flex justify-content-between align-items-center mb-3">
|
|
||||||
<span class="mini-tag"><?= h(subject_level($subject)) ?></span>
|
|
||||||
<span class="text-secondary small"><?= h(subject_duration($subject)) ?></span>
|
|
||||||
</div>
|
|
||||||
<h3 class="h5 mb-2"><?= h(subject_title($subject)) ?></h3>
|
|
||||||
<p class="text-secondary mb-4"><?= h(subject_summary($subject)) ?></p>
|
|
||||||
<div class="d-flex justify-content-between align-items-center small text-secondary mt-auto">
|
|
||||||
<span><?= h(subject_teacher($subject)) ?></span>
|
|
||||||
<a class="link-dark text-decoration-none fw-semibold" href="<?= h(app_url('subject.php', ['slug' => $subject['slug']])) ?>"><?= h(t('View subject', 'عرض المادة')) ?></a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="py-5 border-bottom">
|
|
||||||
<div class="container">
|
|
||||||
<?php if ($flow_img = landing_setting('flow_image', '')): ?>
|
|
||||||
<div class="row align-items-center mb-4">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('flow_eyebrow', 'Delivery flow', 'مسار التسليم')) ?></span>
|
|
||||||
<h2 class="section-title mb-0"><?= h(landing_setting('flow_title', 'One thin slice from discovery to live access.', 'شريحة رشيقة من الاكتشاف حتى الوصول المباشر.')) ?></h2>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5 text-lg-end mt-3 mt-lg-0">
|
|
||||||
<img src="<?= h($flow_img) ?>" class="img-fluid rounded shadow-sm" alt="Delivery Flow" style="max-height: 150px; object-fit: cover;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('flow_eyebrow', 'Delivery flow', 'مسار التسليم')) ?></span>
|
|
||||||
<h2 class="section-title"><?= h(landing_setting('flow_title', 'One thin slice from discovery to live access.', 'شريحة رشيقة من الاكتشاف حتى الوصول المباشر.')) ?></h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-4"><div class="workflow-card"><span class="step-index">01</span><h3 class="h6"><?= h(t('Browse plans', 'تصفح الخطط')) ?></h3><p><?= h(t('Students compare monthly or yearly plans and pick the right access model.', 'يقارن الطلاب بين الخطط الشهرية والسنوية ويختارون نموذج الوصول المناسب.')) ?></p></div></div>
|
|
||||||
<div class="col-md-4"><div class="workflow-card"><span class="step-index">02</span><h3 class="h6"><?= h(t('Capture checkout details', 'جمع بيانات الدفع')) ?></h3><p><?= h(t('The checkout form records student identity, language preference, and WhatsApp opt-in.', 'يسجل نموذج الدفع هوية الطالب وتفضيل اللغة وخيار الاشتراك في واتساب.')) ?></p></div></div>
|
|
||||||
<div class="col-md-4"><div class="workflow-card"><span class="step-index">03</span><h3 class="h6"><?= h(t('Unlock the dashboard', 'فتح لوحة التحكم')) ?></h3><p><?= h(t('A confirmation view leads directly to the student dashboard, live classes, and subject details.', 'تنقل شاشة التأكيد مباشرة إلى لوحة الطالب والفصول المباشرة وتفاصيل المواد.')) ?></p></div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="py-5 bg-white">
|
|
||||||
<div class="container">
|
|
||||||
<?php if ($plans_img = landing_setting('plans_image', '')): ?>
|
|
||||||
<div class="row align-items-center mb-4">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('plans_eyebrow', 'Plans', 'الخطط')) ?></span>
|
|
||||||
<h2 class="section-title mb-2"><?= h(landing_setting('plans_title', 'Plan-based access for a shared marketplace.', 'وصول قائم على الخطط لمنصة مشتركة.')) ?></h2>
|
|
||||||
<a class="link-dark fw-semibold text-decoration-none" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Open pricing', 'افتح التسعير')) ?></a>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5 text-lg-end mt-3 mt-lg-0">
|
|
||||||
<img src="<?= h($plans_img) ?>" class="img-fluid rounded shadow-sm" alt="Plans" style="max-height: 150px; object-fit: cover;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(landing_setting('plans_eyebrow', 'Plans', 'الخطط')) ?></span>
|
|
||||||
<h2 class="section-title"><?= h(landing_setting('plans_title', 'Plan-based access for a shared marketplace.', 'وصول قائم على الخطط لمنصة مشتركة.')) ?></h2>
|
|
||||||
</div>
|
|
||||||
<a class="link-dark fw-semibold text-decoration-none" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Open pricing', 'افتح التسعير')) ?></a>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="row g-3">
|
|
||||||
<?php foreach ($plans as $plan): ?>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<div class="plan-card h-100 <?= $plan['key'] === 'plus' ? 'highlighted' : '' ?>">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
||||||
<h3 class="h5 mb-0"><?= h(plan_name($plan)) ?></h3>
|
|
||||||
<?php if ($plan['key'] === 'plus'): ?><span class="mini-tag"><?= h(t('Popular', 'الأكثر اختياراً')) ?></span><?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div class="display-price mb-3"><?= h(price_label($plan)) ?></div>
|
|
||||||
<ul class="list-unstyled compact-list mb-4">
|
|
||||||
<?php foreach (current_lang() === 'ar' ? $plan['features_ar'] : $plan['features_en'] as $feature): ?>
|
|
||||||
<li><?= h($feature) ?></li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
<a class="btn <?= $plan['key'] === 'plus' ? 'btn-primary shadow-sm' : 'btn-outline-primary bg-white' ?> w-100 rounded-pill fw-bold" href="<?= h(app_url('checkout.php', ['plan' => $plan['key']])) ?>"><?= h(t('Choose plan', 'اختر الخطة')) ?></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
|
|||||||
265
install.php
@ -1,265 +0,0 @@
|
|||||||
<?php
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
// Define steps
|
|
||||||
$step = isset($_GET['step']) ? (int)$_GET['step'] : 1;
|
|
||||||
$error = '';
|
|
||||||
$success = '';
|
|
||||||
|
|
||||||
// Check if config exists
|
|
||||||
$configFile = __DIR__ . '/db/config.php';
|
|
||||||
$configExists = file_exists($configFile);
|
|
||||||
$dbHost = '127.0.0.1';
|
|
||||||
$dbName = '';
|
|
||||||
$dbUser = '';
|
|
||||||
$dbPass = '';
|
|
||||||
|
|
||||||
if ($configExists) {
|
|
||||||
require_once $configFile;
|
|
||||||
if (defined('DB_HOST')) $dbHost = DB_HOST;
|
|
||||||
if (defined('DB_NAME')) $dbName = DB_NAME;
|
|
||||||
if (defined('DB_USER')) $dbUser = DB_USER;
|
|
||||||
if (defined('DB_PASS')) $dbPass = DB_PASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle form submissions
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
if ($step === 2) {
|
|
||||||
$dbHost = $_POST['db_host'] ?? '127.0.0.1';
|
|
||||||
$dbName = $_POST['db_name'] ?? '';
|
|
||||||
$dbUser = $_POST['db_user'] ?? '';
|
|
||||||
$dbPass = $_POST['db_pass'] ?? '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Test connection
|
|
||||||
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass, [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Save configuration
|
|
||||||
$configContent = "<?php\n" .
|
|
||||||
"// Generated by install.php\n" .
|
|
||||||
"define('DB_HOST', " . var_export($dbHost, true) . ");\n" .
|
|
||||||
"define('DB_NAME', " . var_export($dbName, true) . ");\n" .
|
|
||||||
"define('DB_USER', " . var_export($dbUser, true) . ");\n" .
|
|
||||||
"define('DB_PASS', " . var_export($dbPass, true) . ");\n\n" .
|
|
||||||
"function db() {\n" .
|
|
||||||
" static \$pdo;\n" .
|
|
||||||
" if (!\$pdo) {\n" .
|
|
||||||
" \$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [\n" .
|
|
||||||
" PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n" .
|
|
||||||
" PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n" .
|
|
||||||
" ]);\n" .
|
|
||||||
" }\n" .
|
|
||||||
" return \$pdo;\n" .
|
|
||||||
"}\n";
|
|
||||||
|
|
||||||
if (!is_dir(__DIR__ . '/db')) {
|
|
||||||
mkdir(__DIR__ . '/db', 0755, true);
|
|
||||||
}
|
|
||||||
file_put_contents($configFile, $configContent);
|
|
||||||
|
|
||||||
header("Location: ?step=3");
|
|
||||||
exit;
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$error = "Database Connection Failed: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
} elseif ($step === 3) {
|
|
||||||
require_once $configFile;
|
|
||||||
try {
|
|
||||||
$pdo = db();
|
|
||||||
$schemaFile = __DIR__ . '/db/migrations/schema.sql';
|
|
||||||
if (!file_exists($schemaFile)) {
|
|
||||||
throw new Exception("Schema file not found at $schemaFile");
|
|
||||||
}
|
|
||||||
$sql = file_get_contents($schemaFile);
|
|
||||||
$pdo->exec($sql);
|
|
||||||
header("Location: ?step=4");
|
|
||||||
exit;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$error = "Migration Failed: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
} elseif ($step === 4) {
|
|
||||||
require_once $configFile;
|
|
||||||
try {
|
|
||||||
$pdo = db();
|
|
||||||
$seedsFile = __DIR__ . '/db/migrations/seeds.sql';
|
|
||||||
if (!file_exists($seedsFile)) {
|
|
||||||
throw new Exception("Seeds file not found at $seedsFile");
|
|
||||||
}
|
|
||||||
$sql = file_get_contents($seedsFile);
|
|
||||||
$pdo->exec($sql);
|
|
||||||
header("Location: ?step=5");
|
|
||||||
exit;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$error = "Seeding Failed: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" dir="ltr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Platform Installer - Step <?php echo $step; ?></title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
|
||||||
<style>
|
|
||||||
body { background: #f4f7f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
|
|
||||||
.install-card { background: #fff; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.05); overflow: hidden; max-width: 600px; width: 100%; margin: 2rem; }
|
|
||||||
.install-header { background: #007bff; color: #fff; padding: 2rem; text-align: center; }
|
|
||||||
.install-header h1 { margin: 0; font-size: 1.8rem; font-weight: 600; }
|
|
||||||
.install-body { padding: 2rem; }
|
|
||||||
.step-indicator { display: flex; justify-content: space-between; margin-bottom: 2rem; position: relative; }
|
|
||||||
.step-indicator::before { content: ''; position: absolute; top: 15px; left: 0; right: 0; height: 3px; background: #e9ecef; z-index: 1; }
|
|
||||||
.step { width: 32px; height: 32px; border-radius: 50%; background: #e9ecef; color: #6c757d; display: flex; align-items: center; justify-content: center; font-weight: bold; position: relative; z-index: 2; transition: all 0.3s; }
|
|
||||||
.step.active { background: #007bff; color: #fff; box-shadow: 0 0 0 4px rgba(0,123,255,0.2); }
|
|
||||||
.step.completed { background: #28a745; color: #fff; }
|
|
||||||
.btn-primary { background: #007bff; border: none; padding: 0.75rem 1.5rem; font-weight: 500; border-radius: 8px; }
|
|
||||||
.btn-primary:hover { background: #0056b3; }
|
|
||||||
.alert { border-radius: 8px; }
|
|
||||||
.code-block { background: #f8f9fa; padding: 1rem; border-radius: 8px; font-family: monospace; border: 1px solid #dee2e6; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="install-card">
|
|
||||||
<div class="install-header">
|
|
||||||
<h1>Installation Wizard</h1>
|
|
||||||
<p class="mb-0 mt-2 opacity-75">Setup your platform in 5 easy steps</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="install-body">
|
|
||||||
|
|
||||||
<div class="step-indicator">
|
|
||||||
<?php for ($i = 1; $i <= 5; $i++): ?>
|
|
||||||
<div class="step <?php echo $i === $step ? 'active' : ($i < $step ? 'completed' : ''); ?>">
|
|
||||||
<?php echo $i < $step ? '✓' : $i; ?>
|
|
||||||
</div>
|
|
||||||
<?php endfor; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($step === 1): ?>
|
|
||||||
<h3 class="mb-3">Step 1: Welcome & Requirements</h3>
|
|
||||||
<p>Welcome to the platform installation wizard! We will guide you through setting up the database, migrating tables, and loading the initial data.</p>
|
|
||||||
|
|
||||||
<ul class="list-group mb-4">
|
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
PHP Version >= 8.0
|
|
||||||
<?php if (version_compare(PHP_VERSION, '8.0.0', '>=')): ?>
|
|
||||||
<span class="badge bg-success rounded-pill">OK (<?php echo PHP_VERSION; ?>)</span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-danger rounded-pill">Fail (<?php echo PHP_VERSION; ?>)</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
PDO MySQL Extension
|
|
||||||
<?php if (extension_loaded('pdo_mysql')):
|
|
||||||
?>
|
|
||||||
<span class="badge bg-success rounded-pill">OK</span>
|
|
||||||
<?php else:
|
|
||||||
?>
|
|
||||||
<span class="badge bg-danger rounded-pill">Fail</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
Writable Config Directory (db/)
|
|
||||||
<?php if (is_writable(__DIR__ . '/db') || is_writable(__DIR__)): ?>
|
|
||||||
<span class="badge bg-success rounded-pill">OK</span>
|
|
||||||
<?php else:
|
|
||||||
?>
|
|
||||||
<span class="badge bg-danger rounded-pill">Fail</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<a href="?step=2" class="btn btn-primary">Next Step: Database Config →</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php elseif ($step === 2): ?>
|
|
||||||
<h3 class="mb-3">Step 2: Database Configuration</h3>
|
|
||||||
<p class="text-muted">Enter your database credentials below. If `db/config.php` already exists, the fields will be pre-filled.</p>
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Database Host</label>
|
|
||||||
<input type="text" name="db_host" class="form-control" value="<?php echo htmlspecialchars($dbHost); ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Database Name</label>
|
|
||||||
<input type="text" name="db_name" class="form-control" value="<?php echo htmlspecialchars($dbName); ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Database User</label>
|
|
||||||
<input type="text" name="db_user" class="form-control" value="<?php echo htmlspecialchars($dbUser); ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Database Password</label>
|
|
||||||
<input type="password" name="db_pass" class="form-control" value="<?php echo htmlspecialchars($dbPass); ?>">
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between mt-4">
|
|
||||||
<a href="?step=1" class="btn btn-outline-secondary">← Back</a>
|
|
||||||
<button type="submit" class="btn btn-primary">Save & Test Connection →</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php elseif ($step === 3): ?>
|
|
||||||
<h3 class="mb-3">Step 3: Database Migrations</h3>
|
|
||||||
<p>Your database configuration is valid! The next step is to create the necessary tables in your database using the schema file.</p>
|
|
||||||
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<strong>Action:</strong> Running this will execute `db/migrations/schema.sql` to build the database structure. Existing tables might be dropped and recreated.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
<div class="d-flex justify-content-between mt-4">
|
|
||||||
<a href="?step=2" class="btn btn-outline-secondary">← Back</a>
|
|
||||||
<button type="submit" class="btn btn-primary">Run Migrations →</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php elseif ($step === 4): ?>
|
|
||||||
<h3 class="mb-3">Step 4: Database Seeding</h3>
|
|
||||||
<p>Tables have been created successfully! The next step is to insert the initial data (seeds) into the database.</p>
|
|
||||||
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<strong>Action:</strong> Running this will execute `db/migrations/seeds.sql` to load default data, including the admin account and platform settings.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
<div class="d-flex justify-content-between mt-4">
|
|
||||||
<a href="?step=3" class="btn btn-outline-secondary">← Back</a>
|
|
||||||
<button type="submit" class="btn btn-success">Run Seeds →</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php elseif ($step === 5): ?>
|
|
||||||
<div class="text-center py-4">
|
|
||||||
<div class="mb-4">
|
|
||||||
<svg style="color: #28a745; width: 64px; height: 64px;" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
||||||
</div>
|
|
||||||
<h3 class="mb-3">Installation Complete!</h3>
|
|
||||||
<p class="text-muted">Your platform has been successfully installed and configured.</p>
|
|
||||||
|
|
||||||
<div class="alert alert-warning text-start">
|
|
||||||
<strong>Security Warning:</strong> Please delete the <code>install.php</code> file from your server to prevent unauthorized re-installation.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<a href="index.php" class="btn btn-primary me-2">Go to Landing Page</a>
|
|
||||||
<a href="login.php" class="btn btn-outline-primary">Go to Admin Login</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
195
live_lesson.php
@ -1,195 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
|
|
||||||
$lesson_id = (int)($_GET['id'] ?? 0);
|
|
||||||
$db = db();
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT l.*, c.name_en, c.name_ar, c.teacher_id
|
|
||||||
FROM course_live_lessons l
|
|
||||||
JOIN courses c ON l.course_id = c.id
|
|
||||||
WHERE l.id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$lesson_id]);
|
|
||||||
$lesson = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$lesson) {
|
|
||||||
die("Lesson not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check role
|
|
||||||
$is_teacher = false;
|
|
||||||
$is_student = false;
|
|
||||||
|
|
||||||
// Mock student and teacher IDs for the prototype based on current session
|
|
||||||
// In reality, this would be based on logged-in user session
|
|
||||||
$current_teacher_id = 1; // Used in teacher.php
|
|
||||||
$current_student_id = 1; // Assuming we use 1 for student preview too
|
|
||||||
|
|
||||||
if (isset($_GET['as']) && $_GET['as'] === 'teacher') {
|
|
||||||
if ($lesson['teacher_id'] == $current_teacher_id) {
|
|
||||||
$is_teacher = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Basic check if student is in course
|
|
||||||
$is_student = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$is_teacher && !$is_student) {
|
|
||||||
die("Unauthorized access.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure lesson status logic
|
|
||||||
if ($is_teacher && isset($_GET['start'])) {
|
|
||||||
if ($lesson['status'] === 'scheduled') {
|
|
||||||
$db->prepare("UPDATE course_live_lessons SET status = 'live' WHERE id = ?")->execute([$lesson_id]);
|
|
||||||
$lesson['status'] = 'live';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($is_teacher && isset($_GET['end'])) {
|
|
||||||
if ($lesson['status'] === 'live') {
|
|
||||||
$db->prepare("UPDATE course_live_lessons SET status = 'ended' WHERE id = ?")->execute([$lesson_id]);
|
|
||||||
$lesson['status'] = 'ended';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$room_name = $lesson['room_name'];
|
|
||||||
$user_display_name = $is_teacher ? 'Teacher' : 'Student';
|
|
||||||
$has_meet = !empty($lesson['meet_url']);
|
|
||||||
|
|
||||||
render_head(
|
|
||||||
t('Live Lesson: ', 'درس مباشر: ') . t($lesson['title'], $lesson['title']),
|
|
||||||
t('Join the live streaming lesson.', 'انضم إلى بث الدرس المباشر.')
|
|
||||||
);
|
|
||||||
|
|
||||||
// We won't render normal nav here to maximize screen space for the lesson, or we render a simplified one
|
|
||||||
?>
|
|
||||||
<style>
|
|
||||||
body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; background: #000; }
|
|
||||||
.lesson-header {
|
|
||||||
background: #111;
|
|
||||||
color: #fff;
|
|
||||||
padding: 10px 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 10;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.lesson-title { margin: 0; font-size: 1.2rem; font-weight: 500; }
|
|
||||||
#jitsi-container {
|
|
||||||
width: 100%;
|
|
||||||
height: calc(100vh - 56px); /* 56px is header height */
|
|
||||||
}
|
|
||||||
.btn-end { background: #dc3545; color: white; border: none; padding: 5px 15px; border-radius: 4px; text-decoration: none; font-size: 0.9rem; }
|
|
||||||
.btn-end:hover { background: #c82333; color: white; }
|
|
||||||
.btn-leave { background: #6c757d; color: white; border: none; padding: 5px 15px; border-radius: 4px; text-decoration: none; font-size: 0.9rem; }
|
|
||||||
.meet-btn { padding: 15px 30px; font-size: 1.2rem; border-radius: 50px; text-decoration: none; display: inline-flex; align-items: center; gap: 10px; font-weight: bold; background: #fff; color: #3c4043; transition: all 0.3s; }
|
|
||||||
.meet-btn:hover { background: #f8f9fa; transform: translateY(-2px); box-shadow: 0 4px 15px rgba(255,255,255,0.2); color: #1a73e8; }
|
|
||||||
.meet-btn svg { width: 24px; height: 24px; }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="lesson-header">
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
|
||||||
<h1 class="lesson-title">
|
|
||||||
<span class="badge bg-danger animate-pulse me-2"><?= h(t('LIVE', 'مباشر')) ?></span>
|
|
||||||
<?= h(t($lesson['name_en'], $lesson['name_ar'])) ?> - <?= h($lesson['title']) ?>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<?php if ($is_teacher): ?>
|
|
||||||
<?php if ($lesson['status'] !== 'ended'): ?>
|
|
||||||
<a href="?id=<?= $lesson_id ?>&as=teacher&end=1" class="btn-end" onclick="return confirm('<?= h(t('End the live lesson for everyone?', 'إنهاء الدرس المباشر للجميع؟')) ?>');"><?= t('End Lesson', 'إنهاء الدرس') ?></a>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary"><?= t('Ended', 'منتهي') ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
<a href="teacher.php?action=live&course_id=<?= $lesson['course_id'] ?>" class="btn-leave ms-2"><?= t('Back to Dashboard', 'العودة للوحة') ?></a>
|
|
||||||
<?php else: ?>
|
|
||||||
<a href="dashboard.php" class="btn-leave"><?= t('Leave', 'مغادرة') ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($lesson['status'] === 'scheduled'): ?>
|
|
||||||
<div class="d-flex align-items-center justify-content-center text-white" style="height: calc(100vh - 56px);">
|
|
||||||
<div class="text-center">
|
|
||||||
<h2 class="mb-3"><?= t('Lesson hasn\'t started yet.', 'لم يبدأ الدرس بعد.') ?></h2>
|
|
||||||
<p class="text-secondary"><?= t('Scheduled for: ', 'مجدول في: ') ?> <?= date('Y-m-d H:i', strtotime($lesson['scheduled_at'])) ?></p>
|
|
||||||
<?php if ($is_teacher): ?>
|
|
||||||
<a href="?id=<?= $lesson_id ?>&as=teacher&start=1" class="btn btn-primary btn-lg mt-3"><?= t('Start Live Streaming Now', 'ابدأ البث المباشر الآن') ?></a>
|
|
||||||
<?php else: ?>
|
|
||||||
<button class="btn btn-outline-light mt-3" onclick="location.reload()"><?= t('Refresh', 'تحديث') ?></button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php elseif ($lesson['status'] === 'ended'): ?>
|
|
||||||
<div class="d-flex align-items-center justify-content-center text-white" style="height: calc(100vh - 56px);">
|
|
||||||
<div class="text-center">
|
|
||||||
<h2 class="mb-3"><?= t('This live lesson has ended.', 'انتهى هذا الدرس المباشر.') ?></h2>
|
|
||||||
<p class="text-secondary"><?= t('Thank you for participating.', 'شكراً لمشاركتك.') ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php if ($has_meet): ?>
|
|
||||||
<!-- Google Meet Gateway -->
|
|
||||||
<div class="d-flex align-items-center justify-content-center text-white" style="height: calc(100vh - 56px);">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="mb-4">
|
|
||||||
<svg viewBox="0 0 24 24" width="80" height="80">
|
|
||||||
<path fill="#ffffff" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h2 class="mb-4"><?= t('This lesson is hosted on Google Meet.', 'هذا الدرس يُبث عبر Google Meet.') ?></h2>
|
|
||||||
<p class="text-secondary mb-5"><?= t('Click the button below to join the session in a new tab. You must be logged into your Google account.', 'انقر على الزر أدناه للانضمام إلى الجلسة في علامة تبويب جديدة. يجب أن تكون مسجلاً الدخول إلى حساب Google الخاص بك.') ?></p>
|
|
||||||
<a href="<?= h($lesson['meet_url']) ?>" target="_blank" class="meet-btn">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-video"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg>
|
|
||||||
<?= t('Join Google Meet', 'انضم إلى Google Meet') ?>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<!-- Jitsi Meet iframe container -->
|
|
||||||
<div id="jitsi-container"></div>
|
|
||||||
<script src="https://meet.jit.si/external_api.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = () => {
|
|
||||||
const domain = 'meet.jit.si';
|
|
||||||
const options = {
|
|
||||||
roomName: '<?= htmlspecialchars(rawurlencode($room_name)) ?>',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
parentNode: document.querySelector('#jitsi-container'),
|
|
||||||
userInfo: {
|
|
||||||
displayName: '<?= htmlspecialchars($user_display_name) ?>'
|
|
||||||
},
|
|
||||||
configOverwrite: {
|
|
||||||
prejoinPageEnabled: false,
|
|
||||||
startWithAudioMuted: <?= $is_teacher ? 'false' : 'true' ?>,
|
|
||||||
startWithVideoMuted: <?= $is_teacher ? 'false' : 'true' ?>,
|
|
||||||
disableDeepLinking: true
|
|
||||||
},
|
|
||||||
interfaceConfigOverwrite: {
|
|
||||||
SHOW_JITSI_WATERMARK: false,
|
|
||||||
SHOW_WATERMARK_FOR_GUESTS: false,
|
|
||||||
TOOLBAR_BUTTONS: [
|
|
||||||
'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
|
|
||||||
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
|
||||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
|
||||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
|
||||||
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',
|
|
||||||
'security'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const api = new JitsiMeetExternalAPI(domain, options);
|
|
||||||
|
|
||||||
<?php if ($is_teacher): ?>
|
|
||||||
// Any specific teacher controls
|
|
||||||
api.executeCommand('subject', '<?= h(t($lesson['name_en'], $lesson['name_ar'])) ?> - <?= h($lesson['title']) ?>');
|
|
||||||
<?php endif; ?>
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
78
login.php
@ -1,78 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
if (!empty($_SESSION['user_id'])) {
|
|
||||||
header('Location: index.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$error = '';
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$email = trim($_POST['email'] ?? '');
|
|
||||||
$password = $_POST['password'] ?? '';
|
|
||||||
|
|
||||||
if ($email && $password) {
|
|
||||||
$stmt = db()->prepare("SELECT * FROM users WHERE email = ?");
|
|
||||||
$stmt->execute([$email]);
|
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($user && password_verify($password, $user['password'])) {
|
|
||||||
$_SESSION['user_id'] = $user['id'];
|
|
||||||
$_SESSION['user_role'] = $user['role'];
|
|
||||||
if ($user['role'] === 'admin' || !empty($user['role_id'])) {
|
|
||||||
header('Location: admin.php');
|
|
||||||
} else {
|
|
||||||
header('Location: index.php');
|
|
||||||
}
|
|
||||||
exit;
|
|
||||||
} else {
|
|
||||||
$error = t('Invalid email or password.', 'البريد الإلكتروني أو كلمة المرور غير صحيحة.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$error = t('Please fill in all fields.', 'يرجى تعبئة جميع الحقول.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_head(t('Login', 'تسجيل الدخول'));
|
|
||||||
render_nav('login.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5 bg-light min-vh-100 d-flex align-items-center">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-5 col-lg-4">
|
|
||||||
<div class="card border-0 shadow-sm" style="border-radius: 1rem;">
|
|
||||||
<div class="card-body p-4 p-md-5">
|
|
||||||
<div class="text-center mb-4">
|
|
||||||
<?php $prof = get_platform_profile(); if (!empty($prof['logo_path'])): ?>
|
|
||||||
<img src="<?= h(asset_url($prof['logo_path'])) ?>" alt="Logo" style="max-height: 64px; object-fit: contain;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="d-inline-flex align-items-center justify-content-center bg-dark text-white rounded-circle shadow-sm" style="width: 64px; height: 64px; font-size: 1.5rem; font-weight: bold;">
|
|
||||||
<?= h(mb_strtoupper(mb_substr(app_name(), 0, 1))) ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<h1 class="h4 mb-4 text-center fw-bold"><?= h(t('Welcome back', 'مرحباً بعودتك')) ?></h1>
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger py-2 small"><?= h($error) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<form method="post" action="login.php">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Email address', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email" class="form-control form-control-lg" required autofocus>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
||||||
<label class="form-label small fw-semibold mb-0"><?= h(t('Password', 'كلمة المرور')) ?></label>
|
|
||||||
<a href="reset_password.php" class="small text-decoration-none"><?= h(t('Forgot?', 'نسيت؟')) ?></a>
|
|
||||||
</div>
|
|
||||||
<input type="password" name="password" class="form-control form-control-lg" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-dark btn-lg w-100"><?= h(t('Log In', 'تسجيل الدخول')) ?></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
16
logout.php
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
|
|
||||||
session_unset();
|
|
||||||
session_destroy();
|
|
||||||
|
|
||||||
if (ini_get("session.use_cookies")) {
|
|
||||||
$params = session_get_cookie_params();
|
|
||||||
setcookie(session_name(), '', time() - 42000,
|
|
||||||
$params["path"], $params["domain"],
|
|
||||||
$params["secure"], $params["httponly"]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
header('Location: index.php');
|
|
||||||
exit;
|
|
||||||
@ -54,24 +54,6 @@ $dkim_domain = env_val('DKIM_DOMAIN');
|
|||||||
$dkim_selector = env_val('DKIM_SELECTOR');
|
$dkim_selector = env_val('DKIM_SELECTOR');
|
||||||
$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH');
|
$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH');
|
||||||
|
|
||||||
// Also override with platform_profile if set
|
|
||||||
try {
|
|
||||||
require_once __DIR__ . '/../includes/app.php';
|
|
||||||
$pdo_mail = db();
|
|
||||||
$stmt_mail = $pdo_mail->query("SELECT smtp_host, smtp_port, smtp_secure, smtp_user, smtp_pass, smtp_from_email, smtp_from_name FROM platform_profile WHERE id = 1");
|
|
||||||
if ($prof_mail = $stmt_mail->fetch(PDO::FETCH_ASSOC)) {
|
|
||||||
if (!empty($prof_mail['smtp_host'])) $smtp_host = $prof_mail['smtp_host'];
|
|
||||||
if (!empty($prof_mail['smtp_port'])) $smtp_port = (int)$prof_mail['smtp_port'];
|
|
||||||
if (!empty($prof_mail['smtp_secure'])) $smtp_secure = $prof_mail['smtp_secure'];
|
|
||||||
if (!empty($prof_mail['smtp_user'])) $smtp_user = $prof_mail['smtp_user'];
|
|
||||||
if (!empty($prof_mail['smtp_pass'])) $smtp_pass = $prof_mail['smtp_pass'];
|
|
||||||
if (!empty($prof_mail['smtp_from_email'])) $from_email = $prof_mail['smtp_from_email'];
|
|
||||||
if (!empty($prof_mail['smtp_from_name'])) $from_name = $prof_mail['smtp_from_name'];
|
|
||||||
}
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'transport' => $transport,
|
'transport' => $transport,
|
||||||
|
|
||||||
|
|||||||
33
policy.php
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
|
|
||||||
$prof = get_platform_profile();
|
|
||||||
$policy_content = $prof['privacy_policy'] ?? '';
|
|
||||||
|
|
||||||
render_head(
|
|
||||||
t('Privacy Policy', 'سياسة الخصوصية'),
|
|
||||||
t('Read our privacy policy.', 'اقرأ سياسة الخصوصية الخاصة بنا.')
|
|
||||||
);
|
|
||||||
render_nav('policy.php');
|
|
||||||
?>
|
|
||||||
<main class="flex-grow-1 bg-light py-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="card border-0 shadow-sm rounded-4">
|
|
||||||
<div class="card-body p-4 p-md-5">
|
|
||||||
<h1 class="mb-4"><?= h(t('Privacy Policy', 'سياسة الخصوصية')) ?></h1>
|
|
||||||
<div class="content text-secondary" style="white-space: pre-wrap;">
|
|
||||||
<?php if (trim($policy_content) === ''): ?>
|
|
||||||
<?= h(t('No privacy policy provided yet.', 'لم يتم توفير سياسة الخصوصية بعد.')) ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<?= h($policy_content) ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
48
pricing.php
@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
$plans = plans_catalog();
|
|
||||||
render_head(
|
|
||||||
t('Subscription plans', 'خطط الاشتراك'),
|
|
||||||
t('Compare plan-based access for students, teachers, and live classrooms.', 'قارن بين الوصول المعتمد على الخطط للطلاب والمعلمين والفصول المباشرة.')
|
|
||||||
);
|
|
||||||
render_nav('pricing.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="section-header mb-4">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(t('Choose the plan that unlocks your classroom experience', 'اختر الخطة التي تفتح تجربة الفصل لديك')) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t('Plan-based subscriptions work across the entire marketplace, with bilingual delivery and live sessions included.', 'تعمل الاشتراكات المعتمدة على الخطط عبر المنصة كاملة مع تقديم ثنائي اللغة وجلسات مباشرة مشمولة.')) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row g-3">
|
|
||||||
<?php foreach ($plans as $plan): ?>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<div class="plan-card h-100 <?= $plan['key'] === 'plus' ? 'highlighted' : '' ?>">
|
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
||||||
<div>
|
|
||||||
<h2 class="h5 mb-1"><?= h(plan_name($plan)) ?></h2>
|
|
||||||
<p class="small text-secondary mb-0"><?= h(t('Shared marketplace access', 'وصول إلى منصة مشتركة')) ?></p>
|
|
||||||
</div>
|
|
||||||
<?php if ($plan['key'] === 'plus'): ?><span class="mini-tag"><?= h(t('Best fit', 'الأفضل')) ?></span><?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div class="pricing-grid mb-3">
|
|
||||||
<div><strong><?= h(price_label($plan, 'monthly')) ?></strong><span><?= h(t('Monthly', 'شهري')) ?></span></div>
|
|
||||||
<div><strong><?= h(price_label($plan, 'yearly')) ?></strong><span><?= h(t('Yearly', 'سنوي')) ?></span></div>
|
|
||||||
</div>
|
|
||||||
<ul class="list-unstyled compact-list mb-4">
|
|
||||||
<?php foreach (current_lang() === 'ar' ? $plan['features_ar'] : $plan['features_en'] as $feature): ?>
|
|
||||||
<li><?= h($feature) ?></li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
<div class="d-grid gap-2">
|
|
||||||
<a class="btn btn-dark" href="<?= h(app_url('checkout.php', ['plan' => $plan['key'], 'cycle' => 'monthly'])) ?>"><?= h(t('Start monthly', 'ابدأ شهرياً')) ?></a>
|
|
||||||
<a class="btn btn-outline-dark" href="<?= h(app_url('checkout.php', ['plan' => $plan['key'], 'cycle' => 'yearly'])) ?>"><?= h(t('Start yearly', 'ابدأ سنوياً')) ?></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
133
profile.php
@ -1,133 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
require_login();
|
|
||||||
|
|
||||||
$user = get_logged_in_user();
|
|
||||||
if (!$user) {
|
|
||||||
header('Location: logout.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$success = '';
|
|
||||||
$error = '';
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$name = trim($_POST['name'] ?? '');
|
|
||||||
$email = trim($_POST['email'] ?? '');
|
|
||||||
$phone = trim($_POST['phone'] ?? '');
|
|
||||||
$password = $_POST['password'] ?? '';
|
|
||||||
$password_confirm = $_POST['password_confirm'] ?? '';
|
|
||||||
|
|
||||||
$profile_picture = $user['profile_picture'] ?? null;
|
|
||||||
|
|
||||||
// Handle image upload
|
|
||||||
if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
|
||||||
$allowed_types = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
|
||||||
if (in_array($_FILES['profile_picture']['type'], $allowed_types)) {
|
|
||||||
$ext = pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION);
|
|
||||||
$filename = 'user_' . $user['id'] . '_' . time() . '.' . $ext;
|
|
||||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0775, true);
|
|
||||||
}
|
|
||||||
$dest = $upload_dir . $filename;
|
|
||||||
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $dest)) {
|
|
||||||
$profile_picture = 'assets/images/uploads/' . $filename;
|
|
||||||
} else {
|
|
||||||
$error = t('Failed to move uploaded file.', 'فشل نقل الملف المرفوع.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$error = t('Invalid image format. Only JPG, PNG, WEBP, and GIF are allowed.', 'تنسيق الصورة غير صالح. يُسمح فقط بـ JPG و PNG و WEBP و GIF.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($name) || empty($email)) {
|
|
||||||
if (!$error) $error = t('Name and email are required.', 'الاسم والبريد الإلكتروني مطلوبان.');
|
|
||||||
} elseif ($password !== $password_confirm) {
|
|
||||||
if (!$error) $error = t('Passwords do not match.', 'كلمتا المرور غير متطابقتين.');
|
|
||||||
} elseif (!$error) {
|
|
||||||
$stmt = db()->prepare("SELECT id FROM users WHERE email = ? AND id != ?");
|
|
||||||
$stmt->execute([$email, $user['id']]);
|
|
||||||
if ($stmt->fetchColumn()) {
|
|
||||||
$error = t('Email already taken.', 'البريد الإلكتروني مستخدم بالفعل.');
|
|
||||||
} else {
|
|
||||||
if ($password) {
|
|
||||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
|
||||||
$update = db()->prepare("UPDATE users SET name = ?, email = ?, phone = ?, profile_picture = ?, password = ? WHERE id = ?");
|
|
||||||
$update->execute([$name, $email, $phone, $profile_picture, $hash, $user['id']]);
|
|
||||||
} else {
|
|
||||||
$update = db()->prepare("UPDATE users SET name = ?, email = ?, phone = ?, profile_picture = ? WHERE id = ?");
|
|
||||||
$update->execute([$name, $email, $phone, $profile_picture, $user['id']]);
|
|
||||||
}
|
|
||||||
$success = t('Profile updated successfully.', 'تم تحديث الملف الشخصي بنجاح.');
|
|
||||||
$user = get_logged_in_user(); // Refresh
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_head(t('My Profile', 'الملف الشخصي'));
|
|
||||||
render_nav('profile.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5 bg-light min-vh-100">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-8 col-lg-6">
|
|
||||||
<div class="card border-0 shadow-sm" style="border-radius: 1rem;">
|
|
||||||
<div class="card-body p-4 p-md-5">
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
|
||||||
<h1 class="h4 mb-0 fw-bold"><?= h(t('My Profile', 'الملف الشخصي')) ?></h1>
|
|
||||||
<?php if (!empty($user['profile_picture'])): ?>
|
|
||||||
<img src="<?= h($user['profile_picture']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm" style="width: 60px; height: 60px; object-fit: cover;">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="rounded-circle shadow-sm bg-secondary text-white d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
|
||||||
<?= h(strtoupper(mb_substr($user['name'] ?? '?', 0, 1, 'UTF-8'))) ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($success): ?>
|
|
||||||
<div class="alert alert-success py-2 small"><?= h($success) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger py-2 small"><?= h($error) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<form method="post" action="profile.php" enctype="multipart/form-data">
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Profile Picture', 'صورة الملف الشخصي')) ?></label>
|
|
||||||
<input type="file" name="profile_picture" class="form-control" accept="image/*">
|
|
||||||
<div class="form-text small"><?= h(t('Leave blank to keep current picture.', 'اتركه فارغاً للاحتفاظ بالصورة الحالية.')) ?></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Full Name', 'الاسم الكامل')) ?></label>
|
|
||||||
<input type="text" name="name" class="form-control" value="<?= h($user['name']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Email address', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email" class="form-control" value="<?= h($user['email']) ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Telephone Number', 'رقم الهاتف')) ?></label>
|
|
||||||
<input type="tel" name="phone" class="form-control" value="<?= h($user['phone'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h5 class="h6 mb-3 fw-bold border-top pt-4"><?= h(t('Change Password', 'تغيير كلمة المرور')) ?> <small class="text-secondary fw-normal"><?= h(t('(Optional)', '(اختياري)')) ?></small></h5>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('New Password', 'كلمة المرور الجديدة')) ?></label>
|
|
||||||
<input type="password" name="password" class="form-control" placeholder="••••••••">
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Confirm New Password', 'تأكيد كلمة المرور')) ?></label>
|
|
||||||
<input type="password" name="password_confirm" class="form-control" placeholder="••••••••">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-dark w-100"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
require_once __DIR__ . '/mail/MailService.php';
|
|
||||||
|
|
||||||
if (!empty($_SESSION['user_id'])) {
|
|
||||||
header('Location: index.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$success = '';
|
|
||||||
$error = '';
|
|
||||||
$action = $_GET['action'] ?? 'forgot'; // forgot or reset
|
|
||||||
$token = $_GET['token'] ?? '';
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
if ($action === 'forgot') {
|
|
||||||
$email = trim($_POST['email'] ?? '');
|
|
||||||
if ($email) {
|
|
||||||
$stmt = db()->prepare("SELECT id FROM users WHERE email = ?");
|
|
||||||
$stmt->execute([$email]);
|
|
||||||
$user = $stmt->fetch();
|
|
||||||
if ($user) {
|
|
||||||
$newToken = bin2hex(random_bytes(32));
|
|
||||||
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
|
|
||||||
$update = db()->prepare("UPDATE users SET reset_token = ?, reset_expires = ? WHERE id = ?");
|
|
||||||
$update->execute([$newToken, $expires, $user['id']]);
|
|
||||||
|
|
||||||
$resetUrl = app_url('reset_password.php', ['action' => 'reset', 'token' => $newToken]);
|
|
||||||
$fullResetUrl = 'http://' . $_SERVER['HTTP_HOST'] . '/' . ltrim($resetUrl, '/');
|
|
||||||
$htmlBody = current_lang() === 'ar' ? "<p>لقد طلبت إعادة تعيين كلمة المرور. انقر على الرابط أدناه لإعادة تعيينها:</p><p><a href='{$fullResetUrl}'>{$fullResetUrl}</a></p><p>ينتهي الرابط خلال ساعة واحدة.</p>" : "<p>You requested a password reset. Click the link below to reset it:</p><p><a href='{$fullResetUrl}'>{$fullResetUrl}</a></p><p>Link expires in 1 hour.</p>";
|
|
||||||
|
|
||||||
MailService::sendMail($email, "Password Reset", $htmlBody);
|
|
||||||
}
|
|
||||||
$success = t('If that email is in our system, you will receive a password reset link shortly.', 'إذا كان البريد الإلكتروني مسجلاً لدينا، ستتلقى رابطاً لإعادة تعيين كلمة المرور قريباً.');
|
|
||||||
}
|
|
||||||
} elseif ($action === 'reset' && $token) {
|
|
||||||
$password = $_POST['password'] ?? '';
|
|
||||||
$password_confirm = $_POST['password_confirm'] ?? '';
|
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()");
|
|
||||||
$stmt->execute([$token]);
|
|
||||||
$user = $stmt->fetch();
|
|
||||||
|
|
||||||
if (!$user) {
|
|
||||||
$error = t('Invalid or expired token.', 'رمز غير صالح أو منتهي الصلاحية.');
|
|
||||||
} elseif ($password !== $password_confirm) {
|
|
||||||
$error = t('Passwords do not match.', 'كلمتا المرور غير متطابقتين.');
|
|
||||||
} else {
|
|
||||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
|
||||||
$update = db()->prepare("UPDATE users SET password = ?, reset_token = NULL, reset_expires = NULL WHERE id = ?");
|
|
||||||
$update->execute([$hash, $user['id']]);
|
|
||||||
$success = t('Password updated successfully. You can now log in.', 'تم تحديث كلمة المرور بنجاح. يمكنك الآن تسجيل الدخول.');
|
|
||||||
$action = 'done';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_head(t('Reset Password', 'إعادة تعيين كلمة المرور'));
|
|
||||||
render_nav('login.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5 bg-light min-vh-100">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-5 col-lg-4">
|
|
||||||
<div class="card border-0 shadow-sm" style="border-radius: 1rem;">
|
|
||||||
<div class="card-body p-4 p-md-5">
|
|
||||||
<?php if ($action === 'forgot'): ?>
|
|
||||||
<h1 class="h4 mb-3 text-center fw-bold"><?= h(t('Forgot Password?', 'نسيت كلمة المرور؟')) ?></h1>
|
|
||||||
<p class="text-center text-secondary small mb-4"><?= h(t('Enter your email to receive a reset link.', 'أدخل بريدك الإلكتروني لتلقي رابط إعادة التعيين.')) ?></p>
|
|
||||||
<?php if ($success): ?>
|
|
||||||
<div class="alert alert-success py-2 small"><?= h($success) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<form method="post" action="reset_password.php?action=forgot">
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Email address', 'البريد الإلكتروني')) ?></label>
|
|
||||||
<input type="email" name="email" class="form-control form-control-lg" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-dark btn-lg w-100"><?= h(t('Send Reset Link', 'إرسال الرابط')) ?></button>
|
|
||||||
<div class="text-center mt-3"><a href="login.php" class="small text-secondary text-decoration-none"><?= h(t('Back to Login', 'العودة لتسجيل الدخول')) ?></a></div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php elseif ($action === 'reset'): ?>
|
|
||||||
<h1 class="h4 mb-3 text-center fw-bold"><?= h(t('Set New Password', 'تعيين كلمة مرور جديدة')) ?></h1>
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger py-2 small"><?= h($error) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<form method="post" action="reset_password.php?action=reset&token=<?= h($token) ?>">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('New Password', 'كلمة المرور الجديدة')) ?></label>
|
|
||||||
<input type="password" name="password" class="form-control form-control-lg" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label small fw-semibold"><?= h(t('Confirm Password', 'تأكيد كلمة المرور')) ?></label>
|
|
||||||
<input type="password" name="password_confirm" class="form-control form-control-lg" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-dark btn-lg w-100"><?= h(t('Reset Password', 'إعادة تعيين')) ?></button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php elseif ($action === 'done'): ?>
|
|
||||||
<div class="text-center">
|
|
||||||
<h1 class="h4 mb-3 fw-bold text-success"><?= h(t('Success!', 'نجاح!')) ?></h1>
|
|
||||||
<p class="text-secondary small mb-4"><?= h($success) ?></p>
|
|
||||||
<a href="login.php" class="btn btn-dark btn-lg w-100"><?= h(t('Log In', 'تسجيل الدخول')) ?></a>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
93
subject.php
@ -1,93 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
|
|
||||||
$slug = (string) ($_GET['slug'] ?? '');
|
|
||||||
$subject = get_subject($slug);
|
|
||||||
$subscription = current_subscription();
|
|
||||||
$hasAccess = $subscription !== null;
|
|
||||||
|
|
||||||
if (!$subject) {
|
|
||||||
header('Location: ' . app_url('catalog.php'));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $hasAccess && isset($_POST['action']) && $_POST['action'] === 'toggle_module') {
|
|
||||||
$moduleIndex = (int) $_POST['module_index'];
|
|
||||||
$completed = (int) ($_POST['completed'] ?? 0) === 1;
|
|
||||||
toggle_module_progress((int) $subscription['id'], $slug, $moduleIndex, $completed);
|
|
||||||
header('Location: ' . app_url('subject.php', ['slug' => $slug]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$completedModules = $hasAccess ? get_completed_modules((int) $subscription['id'], $slug) : [];
|
|
||||||
|
|
||||||
render_head(subject_title($subject), subject_summary($subject));
|
|
||||||
render_nav('catalog.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="mb-4">
|
|
||||||
<a class="text-secondary text-decoration-none small d-inline-flex align-items-center mb-3" href="<?= h(app_url('catalog.php')) ?>">← <?= h(t('Back to catalog', 'العودة للكتالوج')) ?></a>
|
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
||||||
<div>
|
|
||||||
<h1 class="section-title mb-2"><?= h(subject_title($subject)) ?></h1>
|
|
||||||
<p class="text-secondary mb-0"><?= h(subject_summary($subject)) ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="mini-tag"><?= h(subject_level($subject)) ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-meta-grid mb-5">
|
|
||||||
<div><strong><?= h(t('Teacher', 'المعلم')) ?></strong><span><?= h(subject_teacher($subject)) ?></span></div>
|
|
||||||
<div><strong><?= h(t('Duration', 'المدة')) ?></strong><span><?= h(subject_duration($subject)) ?></span></div>
|
|
||||||
<?php if ($hasAccess): ?>
|
|
||||||
<div><strong><?= h(t('Live classroom', 'الفصل المباشر')) ?></strong><span><?= h(subject_next_live($subject)) ?></span></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<h2 class="h5 mb-3"><?= h(t('Modules & Curriculum', 'الوحدات والمنهج')) ?></h2>
|
|
||||||
<ul class="list-unstyled compact-list mb-0">
|
|
||||||
<?php foreach (subject_modules($subject) as $index => $module): ?>
|
|
||||||
<li class="d-flex align-items-center justify-content-between">
|
|
||||||
<span><?= h($module) ?></span>
|
|
||||||
<?php if ($hasAccess): ?>
|
|
||||||
<?php $isCompleted = in_array($index, $completedModules, false); ?>
|
|
||||||
<form method="post" class="m-0 p-0">
|
|
||||||
<input type="hidden" name="action" value="toggle_module">
|
|
||||||
<input type="hidden" name="module_index" value="<?= h((string) $index) ?>">
|
|
||||||
<input type="hidden" name="completed" value="<?= $isCompleted ? '0' : '1' ?>">
|
|
||||||
<button type="submit" class="btn btn-sm <?= $isCompleted ? 'btn-success' : 'btn-outline-secondary' ?>">
|
|
||||||
<?= $isCompleted ? h(t('Completed', 'مكتمل')) : h(t('Mark complete', 'تحديد كمكتمل')) ?>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<aside class="panel-card sticky-card text-center">
|
|
||||||
<?php if ($hasAccess): ?>
|
|
||||||
<div class="mb-3">
|
|
||||||
<span class="badge bg-success-subtle text-success-emphasis border border-success-subtle px-3 py-2"><?= h(t('Access granted', 'صلاحية الدخول ممنوحة')) ?></span>
|
|
||||||
</div>
|
|
||||||
<p class="text-secondary small mb-4"><?= h(t('You can enter the live classroom when the session begins.', 'يمكنك دخول الفصل المباشر عند بدء الجلسة.')) ?></p>
|
|
||||||
<div class="d-grid gap-2">
|
|
||||||
<a class="btn btn-dark" href="<?= h($subject['meet_url']) ?>" target="_blank" rel="noreferrer"><?= h(t('Open Google Meet lobby', 'افتح صالة Google Meet')) ?></a>
|
|
||||||
<a class="btn btn-outline-dark" href="<?= h(app_url('dashboard.php', ['subscription_id' => (int) $subscription['id']])) ?>"><?= h(t('Go to dashboard', 'اذهب إلى لوحة التحكم')) ?></a>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="mb-3">
|
|
||||||
<span class="badge bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle px-3 py-2"><?= h(t('Subscription required', 'يلزم الاشتراك')) ?></span>
|
|
||||||
</div>
|
|
||||||
<p class="text-secondary small mb-4"><?= h(t('Subscribe to a plan to unlock live classrooms, module materials, and teacher office hours.', 'اشترك في خطة لفتح الفصول المباشرة ومواد الوحدات والساعات المكتبية للمعلم.')) ?></p>
|
|
||||||
<a class="btn btn-dark w-100" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Choose a plan', 'اختر خطة')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
$id = (int) ($_GET['id'] ?? 0);
|
|
||||||
$subscription = $id > 0 ? fetch_subscription($id) : current_subscription();
|
|
||||||
if (!$subscription) {
|
|
||||||
render_head(t('Subscription not found', 'الاشتراك غير موجود'));
|
|
||||||
render_nav('dashboard.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5"><div class="container"><div class="alert alert-warning"><?= h(t('The subscription detail could not be found.', 'تعذر العثور على تفاصيل الاشتراك.')) ?></div></div></main>
|
|
||||||
<?php
|
|
||||||
render_footer();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$_SESSION['subscription_id'] = (int) $subscription['id'];
|
|
||||||
$_SESSION['student_email'] = $subscription['email'];
|
|
||||||
$plan = get_plan($subscription['plan_key']) ?? get_plan('plus');
|
|
||||||
$created = isset($_GET['created']);
|
|
||||||
$subjects = array_slice(subjects_catalog(), 0, $plan['subjects_limit'] >= 999 ? 4 : $plan['subjects_limit']);
|
|
||||||
render_head(
|
|
||||||
t('Subscription confirmation', 'تأكيد الاشتراك'),
|
|
||||||
t('Review the new subscription, payment reference, and the first live classrooms unlocked for the student.', 'راجع الاشتراك الجديد ومرجع الدفع وأول الفصول المباشرة التي تم فتحها للطالب.')
|
|
||||||
);
|
|
||||||
render_nav('dashboard.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5">
|
|
||||||
<div class="container">
|
|
||||||
<?php if ($created): ?>
|
|
||||||
<div class="toast-container position-fixed top-0 end-0 p-3">
|
|
||||||
<div class="toast align-items-center text-bg-dark border-0" role="status" aria-live="polite" aria-atomic="true" data-auto-toast>
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="toast-body"><?= h(t('Subscription created. The dashboard and live access are now ready.', 'تم إنشاء الاشتراك. أصبحت لوحة التحكم والوصول المباشر جاهزين الآن.')) ?></div>
|
|
||||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<div class="panel-card h-100">
|
|
||||||
<span class="eyebrow"><?= h(t('Confirmation', 'التأكيد')) ?></span>
|
|
||||||
<h1 class="section-title mb-3"><?= h(t('Your subscription is active', 'اشتراكك نشط')) ?></h1>
|
|
||||||
<div class="alert alert-success border mb-4">
|
|
||||||
<strong><?= h(t('Plan activated successfully.', 'تم تفعيل الخطة بنجاح.')) ?></strong>
|
|
||||||
<div class="small mt-1"><?= h(t('This record was saved in the database and linked to your student dashboard.', 'تم حفظ هذا السجل في قاعدة البيانات وربطه بلوحة الطالب الخاصة بك.')) ?></div>
|
|
||||||
</div>
|
|
||||||
<div class="detail-meta-grid mb-4">
|
|
||||||
<div><strong><?= h(t('Student', 'الطالب')) ?></strong><span><?= h($subscription['full_name']) ?></span></div>
|
|
||||||
<div><strong><?= h(t('Email', 'البريد الإلكتروني')) ?></strong><span><?= h($subscription['email']) ?></span></div>
|
|
||||||
<div><strong><?= h(t('Plan', 'الخطة')) ?></strong><span><?= h(plan_name($plan)) ?></span></div>
|
|
||||||
<div><strong><?= h(t('Status', 'الحالة')) ?></strong><span class="<?= h(status_badge((string) $subscription['payment_status'])) ?>"><?= h(ucfirst((string) $subscription['payment_status'])) ?></span></div>
|
|
||||||
<div><strong><?= h(t('Thawani ref', 'مرجع ثواني')) ?></strong><span><button type="button" class="copy-chip" data-copy-text="<?= h($subscription['thawani_reference']) ?>"><?= h($subscription['thawani_reference']) ?></button></span></div>
|
|
||||||
<div><strong><?= h(t('WhatsApp', 'واتساب')) ?></strong><span><?= h($subscription['whatsapp']) ?></span></div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-wrap gap-2">
|
|
||||||
<a class="btn btn-dark" href="<?= h(app_url('dashboard.php', ['subscription_id' => (int) $subscription['id']])) ?>"><?= h(t('Go to dashboard', 'انتقل إلى لوحة التحكم')) ?></a>
|
|
||||||
<a class="btn btn-outline-dark" href="<?= h(app_url('catalog.php')) ?>"><?= h(t('Browse more subjects', 'تصفح المزيد من المواد')) ?></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<aside class="panel-card sticky-card">
|
|
||||||
<h2 class="h5 mb-3"><?= h(t('First unlocked classrooms', 'أول الفصول المفتوحة')) ?></h2>
|
|
||||||
<div class="stack-list">
|
|
||||||
<?php foreach ($subjects as $subject): ?>
|
|
||||||
<div class="stack-item">
|
|
||||||
<div>
|
|
||||||
<strong><?= h(subject_title($subject)) ?></strong>
|
|
||||||
<p><?= h(subject_next_live($subject)) ?></p>
|
|
||||||
</div>
|
|
||||||
<a class="btn btn-sm btn-outline-dark" href="<?= h(app_url('subject.php', ['slug' => $subject['slug']])) ?>"><?= h(t('Open', 'فتح')) ?></a>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="small text-secondary d-flex flex-column gap-2">
|
|
||||||
<div><strong class="text-dark"><?= h(t('Payment channel', 'قناة الدفع')) ?>:</strong> <?= h($subscription['payment_gateway']) ?></div>
|
|
||||||
<div><strong class="text-dark"><?= h(t('Reminder channel', 'قناة التذكير')) ?>:</strong> <?= h((int) $subscription['wablas_opt_in'] === 1 ? t('Enabled', 'مفعلة') : t('Not enabled', 'غير مفعلة')) ?></div>
|
|
||||||
<div><strong class="text-dark"><?= h(t('Live classroom', 'الفصل المباشر')) ?>:</strong> Google Meet</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
519
teacher.php
@ -1,519 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
|
|
||||||
// Mock teacher authentication for now
|
|
||||||
$teacher_id = 1;
|
|
||||||
$db = db();
|
|
||||||
$action = $_GET['action'] ?? 'list';
|
|
||||||
$course_id = (int)($_GET['course_id'] ?? 0);
|
|
||||||
$activity_id = (int)($_GET['activity_id'] ?? 0);
|
|
||||||
|
|
||||||
// Helper to check course ownership
|
|
||||||
function owns_course($db, $course_id, $teacher_id) {
|
|
||||||
$stmt = $db->prepare("SELECT id, name_en, name_ar FROM courses WHERE id = ? AND teacher_id = ?");
|
|
||||||
$stmt->execute([$course_id, $teacher_id]);
|
|
||||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle POST actions for activities and assessments
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$post_action = $_POST['action'] ?? '';
|
|
||||||
|
|
||||||
if ($post_action === 'add_activity' || $post_action === 'edit_activity') {
|
|
||||||
$c_id = (int)$_POST['course_id'];
|
|
||||||
$course = owns_course($db, $c_id, $teacher_id);
|
|
||||||
if ($course) {
|
|
||||||
$act_id = (int)($_POST['activity_id'] ?? 0);
|
|
||||||
$title_en = $_POST['title_en'] ?? '';
|
|
||||||
$title_ar = $_POST['title_ar'] ?? '';
|
|
||||||
$desc_en = $_POST['description_en'] ?? '';
|
|
||||||
$desc_ar = $_POST['description_ar'] ?? '';
|
|
||||||
|
|
||||||
if ($post_action === 'add_activity') {
|
|
||||||
$stmt = $db->prepare("INSERT INTO course_activities (course_id, title_en, title_ar, description_en, description_ar) VALUES (?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$c_id, $title_en, $title_ar, $desc_en, $desc_ar]);
|
|
||||||
} else if ($post_action === 'edit_activity' && $act_id > 0) {
|
|
||||||
$stmt = $db->prepare("UPDATE course_activities SET title_en = ?, title_ar = ?, description_en = ?, description_ar = ? WHERE id = ? AND course_id = ?");
|
|
||||||
$stmt->execute([$title_en, $title_ar, $desc_en, $desc_ar, $act_id, $c_id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header("Location: " . app_url('teacher.php', ['action' => 'activities', 'course_id' => $c_id]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'add_live' || $post_action === 'edit_live') {
|
|
||||||
$c_id = (int)$_POST['course_id'];
|
|
||||||
$live_id = (int)($_POST['live_id'] ?? 0);
|
|
||||||
$title = $_POST['title'] ?? '';
|
|
||||||
$scheduled_at = $_POST['scheduled_at'] ?? '';
|
|
||||||
$meet_url = trim($_POST['meet_url'] ?? '');
|
|
||||||
|
|
||||||
if (owns_course($db, $c_id, $teacher_id)) {
|
|
||||||
if ($post_action === 'add_live') {
|
|
||||||
$room_name = 'room_' . substr(md5(uniqid()), 0, 10);
|
|
||||||
$stmt = $db->prepare("INSERT INTO course_live_lessons (course_id, title, scheduled_at, room_name, meet_url) VALUES (?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([$c_id, $title, $scheduled_at, $room_name, $meet_url]);
|
|
||||||
} else if ($post_action === 'edit_live' && $live_id > 0) {
|
|
||||||
$stmt = $db->prepare("UPDATE course_live_lessons SET title = ?, scheduled_at = ?, meet_url = ? WHERE id = ? AND course_id = ?");
|
|
||||||
$stmt->execute([$title, $scheduled_at, $meet_url, $live_id, $c_id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header("Location: " . app_url('teacher.php', ['action' => 'live', 'course_id' => $c_id]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'delete_live') {
|
|
||||||
$c_id = (int)$_POST['course_id'];
|
|
||||||
$live_id = (int)$_POST['live_id'];
|
|
||||||
if (owns_course($db, $c_id, $teacher_id)) {
|
|
||||||
$stmt = $db->prepare("DELETE FROM course_live_lessons WHERE id = ? AND course_id = ?");
|
|
||||||
$stmt->execute([$live_id, $c_id]);
|
|
||||||
}
|
|
||||||
header("Location: " . app_url('teacher.php', ['action' => 'live', 'course_id' => $c_id]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'delete_activity') {
|
|
||||||
$c_id = (int)$_POST['course_id'];
|
|
||||||
$act_id = (int)$_POST['activity_id'];
|
|
||||||
if (owns_course($db, $c_id, $teacher_id)) {
|
|
||||||
$stmt = $db->prepare("DELETE FROM course_activities WHERE id = ? AND course_id = ?");
|
|
||||||
$stmt->execute([$act_id, $c_id]);
|
|
||||||
}
|
|
||||||
header("Location: " . app_url('teacher.php', ['action' => 'activities', 'course_id' => $c_id]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_action === 'save_assessments') {
|
|
||||||
$c_id = (int)$_POST['course_id'];
|
|
||||||
$act_id = (int)$_POST['activity_id'];
|
|
||||||
if (owns_course($db, $c_id, $teacher_id)) {
|
|
||||||
$scores = $_POST['scores'] ?? [];
|
|
||||||
$feedbacks = $_POST['feedbacks'] ?? [];
|
|
||||||
|
|
||||||
$stmt = $db->prepare("INSERT INTO student_assessments (activity_id, student_id, score, feedback) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE score = VALUES(score), feedback = VALUES(feedback)");
|
|
||||||
foreach ($scores as $s_id => $score) {
|
|
||||||
$s_id = (int)$s_id;
|
|
||||||
$score_val = ($score !== '') ? (float)$score : null;
|
|
||||||
$fb = $feedbacks[$s_id] ?? '';
|
|
||||||
$stmt->execute([$act_id, $s_id, $score_val, $fb]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header("Location: " . app_url('teacher.php', ['action' => 'assessments', 'course_id' => $c_id, 'activity_id' => $act_id]));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_head(
|
|
||||||
t('Teacher workspace', 'مساحة المعلم'),
|
|
||||||
t('Manage your courses, students, and activities.', 'إدارة دوراتك وطلابك وأنشطتك.')
|
|
||||||
);
|
|
||||||
render_nav('teacher.php');
|
|
||||||
?>
|
|
||||||
<main class="py-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow"><?= h(t('Teacher workspace', 'مساحة المعلم')) ?></span>
|
|
||||||
<h1 class="section-title mb-0"><?= h(t('My Courses', 'دوراتي')) ?></h1>
|
|
||||||
</div>
|
|
||||||
<?php if ($action !== 'list'): ?>
|
|
||||||
<a href="teacher.php" class="btn btn-outline-dark"><?= h(t('Back', 'العودة للدورات')) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($action === 'list'): ?>
|
|
||||||
<?php
|
|
||||||
$stmt = $db->prepare("SELECT c.*, (SELECT COUNT(*) FROM course_students cs WHERE cs.course_id = c.id) as students_count FROM courses c WHERE c.teacher_id = ? AND c.status = 'active'");
|
|
||||||
$stmt->execute([$teacher_id]);
|
|
||||||
$courses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="row g-4">
|
|
||||||
<?php foreach ($courses as $c): ?>
|
|
||||||
<div class="col-md-6 col-lg-4">
|
|
||||||
<article class="panel-card h-100">
|
|
||||||
<?php if ($c['picture']): ?>
|
|
||||||
<img src="<?= h($c['picture']) ?>" class="img-fluid rounded mb-3" style="object-fit:cover; height:150px; width:100%;" alt="">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="bg-light rounded mb-3 d-flex align-items-center justify-content-center" style="height:150px;">
|
|
||||||
<span class="text-muted">No Image</span>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<h2 class="h5 mb-2"><?= h(t($c['name_en'], $c['name_ar'])) ?></h2>
|
|
||||||
<div class="d-flex justify-content-between text-secondary small mb-3">
|
|
||||||
<span><?= h(t('Students:', 'الطلاب:')) ?> <?= h($c['students_count']) ?></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex flex-column gap-2">
|
|
||||||
<a href="<?= app_url('teacher.php', ['action' => 'students', 'course_id' => $c['id']]) ?>" class="btn btn-sm btn-outline-dark"><?= h(t('View Students', 'عرض الطلاب')) ?></a>
|
|
||||||
<a href="<?= app_url('teacher.php', ['action' => 'activities', 'course_id' => $c['id']]) ?>" class="btn btn-sm btn-outline-dark"><?= h(t('Manage Activities', 'إدارة الأنشطة')) ?></a>
|
|
||||||
<a href="<?= app_url('teacher.php', ['action' => 'live', 'course_id' => $c['id']]) ?>" class="btn btn-sm btn-danger text-white"><?= h(t('Live Lessons', 'دروس البث المباشر')) ?></a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if (empty($courses)): ?>
|
|
||||||
<div class="col-12"><div class="alert alert-info"><?= h(t('No active courses found assigned to you.', 'لم يتم العثور على دورات نشطة مخصصة لك.')) ?></div></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php elseif ($action === 'students' && $course_id > 0): ?>
|
|
||||||
<?php
|
|
||||||
$course = owns_course($db, $course_id, $teacher_id);
|
|
||||||
if (!$course) die('Unauthorized');
|
|
||||||
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT s.id, s.full_name, s.email, s.whatsapp, cs.assigned_at
|
|
||||||
FROM course_students cs
|
|
||||||
JOIN student_subscriptions s ON cs.student_id = s.id
|
|
||||||
WHERE cs.course_id = ?
|
|
||||||
ORDER BY cs.assigned_at DESC
|
|
||||||
");
|
|
||||||
$stmt->execute([$course_id]);
|
|
||||||
$students = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<h2 class="h4 mb-4"><?= h(t($course['name_en'], $course['name_ar'])) ?> - <?= h(t('Students', 'الطلاب')) ?></h2>
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover align-middle mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= h(t('Name', 'الاسم')) ?></th>
|
|
||||||
<th><?= h(t('Email', 'البريد الإلكتروني')) ?></th>
|
|
||||||
<th><?= h(t('WhatsApp', 'واتساب')) ?></th>
|
|
||||||
<th><?= h(t('Enrolled At', 'تاريخ التسجيل')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($students as $s): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h($s['full_name']) ?></td>
|
|
||||||
<td><?= h($s['email']) ?></td>
|
|
||||||
<td><?= h($s['whatsapp']) ?></td>
|
|
||||||
<td><?= h(date('Y-m-d', strtotime($s['assigned_at']))) ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(empty($students)): ?>
|
|
||||||
<tr><td colspan="4" class="text-center text-muted"><?= h(t('No students enrolled yet.', 'لم يقم أي طالب بالتسجيل بعد.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php elseif ($action === 'activities' && $course_id > 0): ?>
|
|
||||||
<?php
|
|
||||||
$course = owns_course($db, $course_id, $teacher_id);
|
|
||||||
if (!$course) die('Unauthorized');
|
|
||||||
|
|
||||||
$stmt = $db->prepare("SELECT * FROM course_activities WHERE course_id = ? ORDER BY created_at DESC");
|
|
||||||
$stmt->execute([$course_id]);
|
|
||||||
$activities = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h2 class="h4 mb-0"><?= h(t($course['name_en'], $course['name_ar'])) ?> - <?= h(t('Activities', 'الأنشطة')) ?></h2>
|
|
||||||
<button class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#activityModal" onclick="resetActivityForm()">
|
|
||||||
<?= h(t('+ Add Activity', '+ إضافة نشاط')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover align-middle mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= h(t('Title (EN)', 'العنوان (إنجليزي)')) ?></th>
|
|
||||||
<th><?= h(t('Title (AR)', 'العنوان (عربي)')) ?></th>
|
|
||||||
<th><?= h(t('Date', 'التاريخ')) ?></th>
|
|
||||||
<th><?= h(t('Actions', 'الإجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($activities as $act): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h($act['title_en']) ?></td>
|
|
||||||
<td><?= h($act['title_ar']) ?></td>
|
|
||||||
<td><?= h(date('Y-m-d', strtotime($act['created_at']))) ?></td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<a href="<?= app_url('teacher.php', ['action' => 'assessments', 'course_id' => $course_id, 'activity_id' => $act['id']]) ?>" class="btn btn-sm btn-info text-white"><?= h(t('Assess', 'تقييم')) ?></a>
|
|
||||||
<button class="btn btn-sm btn-outline-secondary" onclick="editActivity(<?= htmlspecialchars(json_encode($act)) ?>)"><?= h(t('Edit', 'تعديل')) ?></button>
|
|
||||||
<form method="post" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>')">
|
|
||||||
<input type="hidden" name="action" value="delete_activity">
|
|
||||||
<input type="hidden" name="course_id" value="<?= $course_id ?>">
|
|
||||||
<input type="hidden" name="activity_id" value="<?= $act['id'] ?>">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger"><?= h(t('Del', 'حذف')) ?></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(empty($activities)): ?>
|
|
||||||
<tr><td colspan="4" class="text-center text-muted"><?= h(t('No activities found.', 'لم يتم العثور على أنشطة.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Activity Modal -->
|
|
||||||
<div class="modal fade" id="activityModal" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form method="post">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="activityModalLabel"><?= h(t('Activity', 'النشاط')) ?></h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<input type="hidden" name="action" id="modal_action" value="add_activity">
|
|
||||||
<input type="hidden" name="course_id" value="<?= $course_id ?>">
|
|
||||||
<input type="hidden" name="activity_id" id="modal_activity_id" value="">
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Title (EN)', 'العنوان (إنجليزي)')) ?></label>
|
|
||||||
<input type="text" name="title_en" id="modal_title_en" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Title (AR)', 'العنوان (عربي)')) ?></label>
|
|
||||||
<input type="text" name="title_ar" id="modal_title_ar" class="form-control" required dir="rtl">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (EN)', 'الوصف (إنجليزي)')) ?></label>
|
|
||||||
<textarea name="description_en" id="modal_desc_en" class="form-control" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Description (AR)', 'الوصف (عربي)')) ?></label>
|
|
||||||
<textarea name="description_ar" id="modal_desc_ar" class="form-control" rows="3" dir="rtl"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Google Meet URL', 'رابط Google Meet')) ?> <small class="text-muted"><?= h(t('(Optional, overrides platform studio)', '(اختياري، يحل محل استوديو المنصة)')) ?></small></label>
|
|
||||||
<input type="url" name="meet_url" id="modal_live_meet_url" class="form-control" placeholder="https://meet.google.com/xxx-xxxx-xxx">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-dark"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function resetActivityForm() {
|
|
||||||
document.getElementById('modal_action').value = 'add_activity';
|
|
||||||
document.getElementById('modal_activity_id').value = '';
|
|
||||||
document.getElementById('modal_title_en').value = '';
|
|
||||||
document.getElementById('modal_title_ar').value = '';
|
|
||||||
document.getElementById('modal_desc_en').value = '';
|
|
||||||
document.getElementById('modal_desc_ar').value = '';
|
|
||||||
}
|
|
||||||
function editActivity(act) {
|
|
||||||
document.getElementById('modal_action').value = 'edit_activity';
|
|
||||||
document.getElementById('modal_activity_id').value = act.id;
|
|
||||||
document.getElementById('modal_title_en').value = act.title_en;
|
|
||||||
document.getElementById('modal_title_ar').value = act.title_ar;
|
|
||||||
document.getElementById('modal_desc_en').value = act.description_en;
|
|
||||||
document.getElementById('modal_desc_ar').value = act.description_ar;
|
|
||||||
var modal = new bootstrap.Modal(document.getElementById('activityModal'));
|
|
||||||
modal.show();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<?php elseif ($action === 'live' && $course_id > 0): ?>
|
|
||||||
<?php
|
|
||||||
$course = owns_course($db, $course_id, $teacher_id);
|
|
||||||
if (!$course) die('Unauthorized');
|
|
||||||
|
|
||||||
$stmt = $db->prepare("SELECT * FROM course_live_lessons WHERE course_id = ? ORDER BY scheduled_at ASC");
|
|
||||||
$stmt->execute([$course_id]);
|
|
||||||
$lessons = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h2 class="h4 mb-0"><?= h(t($course['name_en'], $course['name_ar'])) ?> - <?= h(t('Live Lessons', 'دروس البث المباشر')) ?></h2>
|
|
||||||
<button class="btn btn-danger text-white" data-bs-toggle="modal" data-bs-target="#liveModal" onclick="resetLiveForm()">
|
|
||||||
<?= h(t('+ Schedule Lesson', '+ جدولة درس')) ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover align-middle mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= h(t('Title', 'العنوان')) ?></th>
|
|
||||||
<th><?= h(t('Scheduled At', 'موعد الدرس')) ?></th>
|
|
||||||
<th><?= h(t('Status', 'الحالة')) ?></th>
|
|
||||||
<th><?= h(t('Actions', 'الإجراءات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($lessons as $les): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h($les['title']) ?></td>
|
|
||||||
<td><?= h(date('Y-m-d H:i', strtotime($les['scheduled_at']))) ?></td>
|
|
||||||
<td>
|
|
||||||
<?php if($les['status'] === 'scheduled'): ?>
|
|
||||||
<span class="badge bg-warning text-dark"><?= h(t('Scheduled', 'مجدول')) ?></span>
|
|
||||||
<?php elseif($les['status'] === 'live'): ?>
|
|
||||||
<span class="badge bg-danger animate-pulse"><?= h(t('LIVE', 'مباشر')) ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary"><?= h(t('Ended', 'منتهي')) ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<?php if($les['status'] !== 'ended'): ?>
|
|
||||||
<a href="live_lesson.php?id=<?= $les['id'] ?>&as=teacher" class="btn btn-sm btn-success text-white">
|
|
||||||
<?= h(t('Enter Studio', 'دخول الاستوديو')) ?>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<button class="btn btn-sm btn-outline-secondary" onclick="editLive(<?= htmlspecialchars(json_encode($les)) ?>)"><?= h(t('Edit', 'تعديل')) ?></button>
|
|
||||||
<form method="post" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>')">
|
|
||||||
<input type="hidden" name="action" value="delete_live">
|
|
||||||
<input type="hidden" name="course_id" value="<?= $course_id ?>">
|
|
||||||
<input type="hidden" name="live_id" value="<?= $les['id'] ?>">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger"><?= h(t('Del', 'حذف')) ?></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(empty($lessons)): ?>
|
|
||||||
<tr><td colspan="4" class="text-center text-muted"><?= h(t('No live lessons scheduled.', 'لا يوجد دروس مجدولة.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Live Modal -->
|
|
||||||
<div class="modal fade" id="liveModal" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form method="post">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title"><?= h(t('Live Lesson', 'الدرس المباشر')) ?></h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<input type="hidden" name="action" id="modal_live_action" value="add_live">
|
|
||||||
<input type="hidden" name="course_id" value="<?= $course_id ?>">
|
|
||||||
<input type="hidden" name="live_id" id="modal_live_id" value="">
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Lesson Title', 'عنوان الدرس')) ?></label>
|
|
||||||
<input type="text" name="title" id="modal_live_title" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Scheduled Time', 'وقت الدرس')) ?></label>
|
|
||||||
<input type="datetime-local" name="scheduled_at" id="modal_live_time" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= h(t('Google Meet URL', 'رابط Google Meet')) ?> <small class="text-muted"><?= h(t('(Optional, overrides platform studio)', '(اختياري، يحل محل استوديو المنصة)')) ?></small></label>
|
|
||||||
<input type="url" name="meet_url" id="modal_live_meet_url" class="form-control" placeholder="https://meet.google.com/xxx-xxxx-xxx">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
|
||||||
<button type="submit" class="btn btn-danger"><?= h(t('Save', 'حفظ')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function resetLiveForm() {
|
|
||||||
document.getElementById('modal_live_action').value = 'add_live';
|
|
||||||
document.getElementById('modal_live_id').value = '';
|
|
||||||
document.getElementById('modal_live_title').value = '';
|
|
||||||
document.getElementById('modal_live_time').value = '';
|
|
||||||
document.getElementById('modal_live_meet_url').value = '';
|
|
||||||
}
|
|
||||||
function editLive(les) {
|
|
||||||
document.getElementById('modal_live_action').value = 'edit_live';
|
|
||||||
document.getElementById('modal_live_id').value = les.id;
|
|
||||||
document.getElementById('modal_live_title').value = les.title;
|
|
||||||
document.getElementById('modal_live_time').value = les.scheduled_at.replace(' ', 'T').substring(0, 16);
|
|
||||||
document.getElementById('modal_live_meet_url').value = les.meet_url ? les.meet_url : '';
|
|
||||||
var modal = new bootstrap.Modal(document.getElementById('liveModal'));
|
|
||||||
modal.show();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<?php elseif ($action === 'assessments' && $course_id > 0 && $activity_id > 0): ?>
|
|
||||||
<?php
|
|
||||||
$course = owns_course($db, $course_id, $teacher_id);
|
|
||||||
if (!$course) die('Unauthorized');
|
|
||||||
|
|
||||||
$stmt = $db->prepare("SELECT title_en, title_ar FROM course_activities WHERE id = ? AND course_id = ?");
|
|
||||||
$stmt->execute([$activity_id, $course_id]);
|
|
||||||
$activity = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if (!$activity) die('Activity not found');
|
|
||||||
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT s.id as student_id, s.full_name, sa.score, sa.feedback
|
|
||||||
FROM course_students cs
|
|
||||||
JOIN student_subscriptions s ON cs.student_id = s.id
|
|
||||||
LEFT JOIN student_assessments sa ON sa.student_id = s.id AND sa.activity_id = ?
|
|
||||||
WHERE cs.course_id = ?
|
|
||||||
ORDER BY s.full_name ASC
|
|
||||||
");
|
|
||||||
$stmt->execute([$activity_id, $course_id]);
|
|
||||||
$students = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<div>
|
|
||||||
<h2 class="h4 mb-1"><?= h(t($activity['title_en'], $activity['title_ar'])) ?> - <?= h(t('Assessments', 'التقييمات')) ?></h2>
|
|
||||||
<p class="text-secondary mb-0"><?= h(t($course['name_en'], $course['name_ar'])) ?></p>
|
|
||||||
</div>
|
|
||||||
<a href="<?= app_url('teacher.php', ['action' => 'activities', 'course_id' => $course_id]) ?>" class="btn btn-outline-dark"><?= h(t('Back to Activities', 'العودة للأنشطة')) ?></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-card">
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="action" value="save_assessments">
|
|
||||||
<input type="hidden" name="course_id" value="<?= $course_id ?>">
|
|
||||||
<input type="hidden" name="activity_id" value="<?= $activity_id ?>">
|
|
||||||
|
|
||||||
<div class="table-responsive mb-3">
|
|
||||||
<table class="table table-hover align-middle mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="width: 30%"><?= h(t('Student', 'الطالب')) ?></th>
|
|
||||||
<th style="width: 20%"><?= h(t('Score', 'الدرجة')) ?></th>
|
|
||||||
<th style="width: 50%"><?= h(t('Feedback', 'الملاحظات')) ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($students as $s): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h($s['full_name']) ?></td>
|
|
||||||
<td>
|
|
||||||
<input type="number" step="0.01" class="form-control" name="scores[<?= $s['student_id'] ?>]" value="<?= h($s['score'] !== null ? $s['score'] : '') ?>" placeholder="0.00">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="text" class="form-control" name="feedbacks[<?= $s['student_id'] ?>]" value="<?= h($s['feedback']) ?>" placeholder="<?= h(t('Optional note...', 'ملاحظة اختيارية...')) ?>">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php if(empty($students)): ?>
|
|
||||||
<tr><td colspan="3" class="text-center text-muted"><?= h(t('No students found to assess.', 'لا يوجد طلاب لتقييمهم.')) ?></td></tr>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if(!empty($students)): ?>
|
|
||||||
<div class="text-end">
|
|
||||||
<button type="submit" class="btn btn-dark px-4"><?= h(t('Save All Grades', 'حفظ كل الدرجات')) ?></button>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||
33
terms.php
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/app.php';
|
|
||||||
|
|
||||||
$prof = get_platform_profile();
|
|
||||||
$terms_content = $prof['terms'] ?? '';
|
|
||||||
|
|
||||||
render_head(
|
|
||||||
t('Terms of Conditions', 'الشروط والأحكام'),
|
|
||||||
t('Read our terms of conditions.', 'اقرأ الشروط والأحكام الخاصة بنا.')
|
|
||||||
);
|
|
||||||
render_nav('terms.php');
|
|
||||||
?>
|
|
||||||
<main class="flex-grow-1 bg-light py-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="card border-0 shadow-sm rounded-4">
|
|
||||||
<div class="card-body p-4 p-md-5">
|
|
||||||
<h1 class="mb-4"><?= h(t('Terms of Conditions', 'الشروط والأحكام')) ?></h1>
|
|
||||||
<div class="content text-secondary" style="white-space: pre-wrap;">
|
|
||||||
<?php if (trim($terms_content) === ''): ?>
|
|
||||||
<?= h(t('No terms of conditions provided yet.', 'لم يتم توفير الشروط والأحكام بعد.')) ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<?= h($terms_content) ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php render_footer(); ?>
|
|
||||||