diff --git a/admin.php b/admin.php
index 66476a8..0036a89 100644
--- a/admin.php
+++ b/admin.php
@@ -318,7 +318,9 @@ render_head(
-
+
+
+
diff --git a/admin_courses.php b/admin_courses.php
index 2549b01..1c6df1c 100644
--- a/admin_courses.php
+++ b/admin_courses.php
@@ -185,7 +185,7 @@ $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
- = h(number_format($row['price'] ?? 0, 2)) ?>
+ = h(format_price((float)($row['price'] ?? 0))) ?>
|
@@ -303,7 +303,7 @@ $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
+
@@ -392,7 +392,7 @@ $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
+
diff --git a/admin_plans.php b/admin_plans.php
new file mode 100644
index 0000000..d71c6c9
--- /dev/null
+++ b/admin_plans.php
@@ -0,0 +1,267 @@
+ 0) {
+ $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') {
+ $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);
+?>
+
+
+
+
+
+
+
+
+
+ | ID |
+ = h(t('Key', 'المفتاح')) ?> |
+ = h(t('Name', 'الاسم')) ?> |
+ = h(t('Monthly', 'شهري')) ?> |
+ = h(t('Yearly', 'سنوي')) ?> |
+ = h(t('Subjects', 'المواد')) ?> |
+ = h(t('Actions', 'إجراءات')) ?> |
+
+
+
+
+
+ | = h((string)$row['id']) ?> |
+ = h($row['plan_key']) ?> |
+
+ = h(current_lang() === 'ar' ? $row['name_ar'] : $row['name_en']) ?>
+ |
+ = h(format_price((float)$row['price_monthly'])) ?> |
+ = h(format_price((float)$row['price_yearly'])) ?> |
+ = h((string)$row['subjects_limit']) ?> |
+
+
+
+ |
+
+
+
+
+
+
+
+
+ | = h(t('No plans found.', 'لا توجد خطط.')) ?> |
+
+
+
+
+
+
+ 1): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/checkout.php b/checkout.php
index 94cdd62..6423723 100644
--- a/checkout.php
+++ b/checkout.php
@@ -265,7 +265,7 @@ render_nav('pricing.php');
= h(t('Price', 'السعر')) ?>
- = h('$' . number_format($course['price'], 2)) ?>
+ = h(format_price((float)$course['price'])) ?>
= h(price_label($plan, $cycle)) ?>
diff --git a/includes/app.php b/includes/app.php
index 1eccee7..1834784 100644
--- a/includes/app.php
+++ b/includes/app.php
@@ -434,11 +434,16 @@ 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 number_format((float)$amount, 3) . ' ' . t('OMR', 'ر.ع.') . $suffix;
+ return format_price((float)$amount) . ' ' . $suffix;
}
function ensure_subscription_table(): void
diff --git a/index.php b/index.php
index fa0e38c..eea1f2d 100644
--- a/index.php
+++ b/index.php
@@ -39,36 +39,6 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
-
-
- = h(t('Today inside the platform', 'اليوم داخل المنصة')) ?>
- = h(t('Live ready', 'جاهز للبث')) ?>
-
-
-
-
- = h(t('Student subscription', 'اشتراك الطالب')) ?>
- = h(t('Choose a plan, confirm payment intent, and unlock classrooms.', 'اختر خطة وأكد نية الدفع وافتح الفصول.')) ?>
-
- Thawani
-
-
-
- = h(t('Live lessons', 'الدروس المباشرة')) ?>
- = h(t('Weekly Google Meet rooms with schedules shown inside every subject.', 'غرف Google Meet أسبوعية مع الجداول داخل كل مادة.')) ?>
-
- Meet
-
-
-
- = h(t('WhatsApp reminders', 'تذكيرات واتساب')) ?>
- = h(t('Opt-in reminders for payment confirmations and class updates.', 'تذكيرات اختيارية لتأكيد الدفع وتحديثات الحصص.')) ?>
-
- Wablas
-
-
- = h(t('Open student dashboard', 'افتح لوحة الطالب')) ?>
-
@@ -108,7 +78,7 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?>
- = h('$' . number_format($course['price'], 2)) ?>
+ = h(format_price((float)$course['price'])) ?>
= h(current_lang() === 'ar' ? $course['description_ar'] : $course['description_en']) ?>
diff --git a/login.php b/login.php
index 5001268..f1cfde8 100644
--- a/login.php
+++ b/login.php
@@ -38,6 +38,15 @@ render_nav('login.php');
+
+
+ ) ?>)
+
+
+ = h(mb_strtoupper(mb_substr(app_name(), 0, 1))) ?>
+
+
+
= h(t('Welcome back', 'مرحباً بعودتك')) ?>
= h($error) ?>
diff --git a/patch_index2.py b/patch_index2.py
new file mode 100644
index 0000000..1d7539b
--- /dev/null
+++ b/patch_index2.py
@@ -0,0 +1,15 @@
+import re
+
+with open("index.php", "r", encoding="utf-8") as f:
+ content = f.read()
+
+# Pattern to remove the entire panel-card block
+pattern = r' [\s\S]*? \s*<\?php endif; \?>'
+replacement = ''
+
+new_content = re.sub(pattern, replacement, content)
+
+with open("index.php", "w", encoding="utf-8") as f:
+ f.write(new_content)
+
+print("done")
\ No newline at end of file
diff --git a/profile.php b/profile.php
index 432732a..ca4154a 100644
--- a/profile.php
+++ b/profile.php
@@ -14,14 +14,38 @@ $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)) {
- $error = t('Name and email are required.', 'الاسم والبريد الإلكتروني مطلوبان.');
+ if (!$error) $error = t('Name and email are required.', 'الاسم والبريد الإلكتروني مطلوبان.');
} elseif ($password !== $password_confirm) {
- $error = t('Passwords do not match.', 'كلمتا المرور غير متطابقتين.');
- } else {
+ 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()) {
@@ -29,11 +53,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} else {
if ($password) {
$hash = password_hash($password, PASSWORD_DEFAULT);
- $update = db()->prepare("UPDATE users SET name = ?, email = ?, password = ? WHERE id = ?");
- $update->execute([$name, $email, $hash, $user['id']]);
+ $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 = ? WHERE id = ?");
- $update->execute([$name, $email, $user['id']]);
+ $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
@@ -50,22 +74,45 @@ render_nav('profile.php');
- = h(t('My Profile', 'الملف الشخصي')) ?>
+
+
+ = h(t('My Profile', 'الملف الشخصي')) ?>
+
+  ?>?v=<?= time() ?>)
+
+
+ = h(strtoupper(mb_substr($user['name'] ?? '?', 0, 1, 'UTF-8'))) ?>
+
+
+
+
= h($success) ?>
= h($error) ?>
- |