From be93531517e7a18a2f39925ce6bde99ae36946cd Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 7 Apr 2026 07:11:44 +0000 Subject: [PATCH] add login page --- admin.php | 2 + admin_landing.php | 185 +++++++++++++++++++++++++++++++------------- checkout.php | 9 +++ includes/app.php | 10 ++- includes/auth.php | 16 ++++ index.php | 54 +++++++++++++ login.php | 65 ++++++++++++++++ logout.php | 8 ++ patch_admin.py | 187 +++++++++++++++++++++++++++++++++++++++++++++ patch_checkout.php | 37 ++++----- patch_index.py | 161 ++++++++++++++++++++++++++++++++++++++ profile.php | 86 +++++++++++++++++++++ reset_password.php | 112 +++++++++++++++++++++++++++ 13 files changed, 855 insertions(+), 77 deletions(-) create mode 100644 includes/auth.php create mode 100644 login.php create mode 100644 logout.php create mode 100644 patch_admin.py create mode 100644 patch_index.py create mode 100644 profile.php create mode 100644 reset_password.php diff --git a/admin.php b/admin.php index 02b4749..66476a8 100644 --- a/admin.php +++ b/admin.php @@ -1,6 +1,8 @@ prepare("UPDATE landing_settings SET value_en = :en, value_ar = :ar WHERE setting_key = :key"); + // 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, @@ -16,6 +17,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' ]); } + // 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; } @@ -28,18 +51,43 @@ foreach ($all_settings as $row) { $settings[$row['setting_key']] = $row; } -$fields = [ - 'hero_eyebrow' => ['label' => 'Hero Eyebrow / النص الصغير في البداية', 'type' => 'text'], - 'hero_title' => ['label' => 'Hero Title / العنوان الرئيسي', 'type' => 'textarea'], - 'hero_desc' => ['label' => 'Hero Description / الوصف', 'type' => 'textarea'], - 'courses_eyebrow' => ['label' => 'Courses Eyebrow / نص قسم الدورات', 'type' => 'text'], - 'courses_title' => ['label' => 'Courses Title / عنوان قسم الدورات', 'type' => 'textarea'], - 'subjects_eyebrow' => ['label' => 'Subjects Eyebrow / نص قسم المواد', 'type' => 'text'], - 'subjects_title' => ['label' => 'Subjects Title / عنوان قسم المواد', 'type' => 'textarea'], - 'flow_eyebrow' => ['label' => 'Delivery Flow Eyebrow / نص قسم مسار التسليم', 'type' => 'text'], - 'flow_title' => ['label' => 'Delivery Flow Title / عنوان قسم مسار التسليم', 'type' => 'textarea'], - 'plans_eyebrow' => ['label' => 'Plans Eyebrow / نص قسم الخطط', 'type' => 'text'], - 'plans_title' => ['label' => 'Plans Title / عنوان قسم الخطط', 'type' => 'textarea'] +$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'], + ] + ] ]; ?> @@ -48,7 +96,7 @@ $fields = [

-

+

@@ -56,51 +104,80 @@ $fields = [
-
-
+
+ - - -
- -
- $field): ?> -
- - - - - - +
+ $section): ?> +
+

+ +

+
+
+ + +
+ + +
+ Current Image +
+ + + +
+ + + +
+
+ $field): ?> + +
+ + + + + + +
+ +
+ +
+ $field): ?> + +
+ + + + + + +
+ +
+
+ +
- -
- - -
- $field): ?> -
- - - - - - -
- -
+
+
- +
-
+
\ No newline at end of file diff --git a/checkout.php b/checkout.php index 78ccd3f..94cdd62 100644 --- a/checkout.php +++ b/checkout.php @@ -29,6 +29,7 @@ $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, @@ -40,6 +41,9 @@ 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.', 'يرجى إدخال بريد إلكتروني صحيح.'); } @@ -55,6 +59,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $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'], @@ -195,6 +200,10 @@ render_nav('pricing.php');
+
+ + +
diff --git a/includes/app.php b/includes/app.php index 6b1a971..1eccee7 100644 --- a/includes/app.php +++ b/includes/app.php @@ -53,7 +53,7 @@ function get_landing_settings(): array { return $settings; } -function landing_setting(string $key, string $default_en, string $default_ar): string { +function landing_setting(string $key, string $default_en, string $default_ar = ""): string { $settings = get_landing_settings(); $lang = current_lang(); if (isset($settings[$key])) { @@ -647,6 +647,14 @@ function render_nav(string $active = ''): void EN AR
+
+ + + + + + +
diff --git a/includes/auth.php b/includes/auth.php new file mode 100644 index 0000000..6c6050f --- /dev/null +++ b/includes/auth.php @@ -0,0 +1,16 @@ +prepare("SELECT * FROM users WHERE id = ?"); + $stmt->execute([$_SESSION['user_id']]); + return $stmt->fetch(PDO::FETCH_ASSOC) ?: null; +} diff --git a/index.php b/index.php index 12b5a6c..fa0e38c 100644 --- a/index.php +++ b/index.php @@ -36,6 +36,9 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+ + Hero Image +

@@ -66,6 +69,7 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+
@@ -74,12 +78,24 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+ +
+
+ +

+
+
+ Courses +
+
+

+
@@ -121,6 +137,18 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+ +
+
+ +

+ +
+
+ Subjects +
+
+
@@ -128,6 +156,7 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+
@@ -151,12 +180,24 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+ +
+
+ +

+
+
+ Delivery Flow +
+
+

+
01

02

@@ -167,6 +208,18 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+ +
+
+ +

+ +
+
+ Plans +
+
+
@@ -174,6 +227,7 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
+
diff --git a/login.php b/login.php new file mode 100644 index 0000000..5001268 --- /dev/null +++ b/login.php @@ -0,0 +1,65 @@ +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']; + header('Location: admin.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'); +?> +
+
+
+
+
+
+

+ +
+ +
+
+ + +
+
+
+ + +
+ +
+ +
+
+
+
+
+
+
+ diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..e230326 --- /dev/null +++ b/logout.php @@ -0,0 +1,8 @@ +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'], + ] + ] +]; +""" + +new_html_form = """ +
+
+ + +
+ $section): ?> +
+

+ +

+
+
+ + +
+ + +
+ Current Image +
+ + + +
+ + + +
+
+ $field): ?> + +
+ + + + + + +
+ +
+ +
+ $field): ?> + +
+ + + + + + +
+ +
+
+ +
+
+
+ +
+ +
+ +
+
+
+""" + +# Now replace the parts in admin_landing.php +# 1. from `if ($_SERVER['REQUEST_METHOD']` to `$fields = [...];` +php_pattern = re.compile(r"if \(\$_SERVER\[\'REQUEST_METHOD\'\].*?\];\s*", re.DOTALL) +admin_landing_content = php_pattern.sub(new_php_logic, admin_landing_content) + +# 2. from `
` at the end +html_pattern = re.compile(r"
\s*$", re.DOTALL) +admin_landing_content = html_pattern.sub(new_html_form, admin_landing_content) + +with open(admin_landing_path, 'w', encoding='utf-8') as f: + f.write(admin_landing_content) diff --git a/patch_checkout.php b/patch_checkout.php index 463696d..bd1113e 100644 --- a/patch_checkout.php +++ b/patch_checkout.php @@ -2,28 +2,21 @@ $file = 'checkout.php'; $content = file_get_contents($file); -// check course limits -$limit_check = << 0) { - $course = db()->query("SELECT * FROM courses WHERE id = $courseIdForView")->fetch(); - if ($course) { - if (! $course['registration_open']) { - die(t('Registration for this course is currently closed.', 'التسجيل في هذه الدورة مغلق حالياً.')); - } - if ($course['max_students'] > 0) { - $enrolled = db()->query("SELECT COUNT(*) FROM course_students WHERE course_id = $courseIdForView")->fetchColumn(); - if ($enrolled >= $course['max_students']) { - die(t('This course has reached its maximum number of students.', 'لقد وصلت هذه الدورة إلى الحد الأقصى لعدد الطلاب.')); - } - } - } else { - die('Course not found'); - } -} -PHP; +$search1 = "'email' => trim((string) (\$_POST['email'] ?? '')),"; +$replace1 = $search1 . "\n 'password' => (string) (\$_POST['password'] ?? ''),"; +$content = str_replace($search1, $replace1, $content); -$content = preg_replace("/if \(\\$courseIdForView > 0\) \{\\s*\\\$course = db\(\)->query\(\"SELECT \* FROM courses WHERE id = \\\$courseIdForView\"\)->fetch\(\);\\s*\}", $limit_check, $content); +$search2 = "if (!filter_var(\$form['email'], FILTER_VALIDATE_EMAIL)) {"; +$replace2 = "if (empty(\$form['password']) || strlen(\$form['password']) < 6) {\n \$errors[] = t('Password must be at least 6 characters.', 'يجب أن تتكون كلمة المرور من 6 أحرف على الأقل.');\n }\n " . $search2; +$content = str_replace($search2, $replace2, $content); + +$search3 = "'email' => \$form['email'],"; +$replace3 = "'email' => \$form['email'],\n 'password' => password_hash(\$form['password'], PASSWORD_DEFAULT),"; +$content = str_replace($search3, $replace3, $content); + +$search4 = '/
\s*
+# It starts with: +#
+#
+ +old_hero_col = """
+
""" + +new_hero_col = """
+ + Hero Image + +
""" + +content = content.replace(old_hero_col, new_hero_col) + +old_hero_end = """ +
+
+
+
+
""" + +new_hero_end = """ +
+ +
+
+ +
""" + +content = content.replace(old_hero_end, new_hero_end) + + +# 2. Courses Section +old_courses = """
+
+ +

+
+
""" + +new_courses = """ +
+
+ +

+
+
+ Courses +
+
+ +
+
+ +

+
+
+ """ +content = content.replace(old_courses, new_courses) + + +# 3. Subjects Section +old_subjects = """
+
+ +

+
+ +
""" + +new_subjects = """ +
+
+ +

+ +
+
+ Subjects +
+
+ +
+
+ +

+
+ +
+ """ +content = content.replace(old_subjects, new_subjects) + + +# 4. Flow Section +old_flow = """
+
+ +

+
+
""" + +new_flow = """ +
+
+ +

+
+
+ Delivery Flow +
+
+ +
+
+ +

+
+
+ """ +content = content.replace(old_flow, new_flow) + + +# 5. Plans Section +old_plans = """
+
+ +

+
+ +
""" + +new_plans = """ +
+
+ +

+ +
+
+ Plans +
+
+ +
+
+ +

+
+ +
+ """ +content = content.replace(old_plans, new_plans) + +with open('index.php', 'w', encoding='utf-8') as f: + f.write(content) \ No newline at end of file diff --git a/profile.php b/profile.php new file mode 100644 index 0000000..432732a --- /dev/null +++ b/profile.php @@ -0,0 +1,86 @@ +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 = ?, password = ? WHERE id = ?"); + $update->execute([$name, $email, $hash, $user['id']]); + } else { + $update = db()->prepare("UPDATE users SET name = ?, email = ? WHERE id = ?"); + $update->execute([$name, $email, $user['id']]); + } + $success = t('Profile updated successfully.', 'تم تحديث الملف الشخصي بنجاح.'); + $user = get_logged_in_user(); // Refresh + } + } +} + +render_head(t('My Profile', 'الملف الشخصي')); +render_nav('profile.php'); +?> +
+
+
+
+
+
+

+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+ diff --git a/reset_password.php b/reset_password.php new file mode 100644 index 0000000..b4061a7 --- /dev/null +++ b/reset_password.php @@ -0,0 +1,112 @@ +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 = "

You requested a password reset. Click the link below to reset it:

{$fullResetUrl}

Link expires in 1 hour.

"; + + 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'); +?> +
+
+
+
+
+
+ +

+

+ +
+ +
+
+ + +
+ +
+
+ + +

+ +
+ +
+
+ + +
+
+ + +
+ +
+ + +
+

+

+ +
+ +
+
+
+
+
+
+