Initial commit

This commit is contained in:
Mohamed Elsheikh 2024-12-25 13:05:50 +02:00
commit dcc2289717
76 changed files with 12456 additions and 0 deletions

26
.env.example Normal file
View File

@ -0,0 +1,26 @@
# Database Configuration
DB_SERVER=localhost
DB_USERNAME=root
DB_PASSWORD=
DB_NAME=shubraveil_db
# SMTP Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_FROM_EMAIL=noreply@shubraveil.com
SMTP_FROM_NAME=ShubraVeil
# Site Configuration
SITE_URL=http://localhost/shubraveil
DEBUG_MODE=true
# Security
JWT_SECRET=change_this_to_a_secure_secret_key
RECAPTCHA_SITE_KEY=your_recaptcha_site_key
RECAPTCHA_SECRET_KEY=your_recaptcha_secret_key
# Cache
CACHE_ENABLED=true
CACHE_DURATION=3600

39
.github/workflows/php.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: PHP Composer
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
# - name: Run test suite
# run: composer run-script test

39
.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
# Environment files
.env
.env.*
!.env.example
# Composer
/vendor/
composer.lock
composer.phar
# Cache and logs
/cache/*
/logs/*
!cache/.gitkeep
!logs/.gitkeep
# Uploads
/uploads/*
!uploads/.gitkeep
# Backups
/backups/*
!backups/.gitkeep
# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

49
.htaccess Normal file
View File

@ -0,0 +1,49 @@
# Enable Rewrite Engine
RewriteEngine On
RewriteBase /
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Protect sensitive files
<FilesMatch "^\.env">
Order allow,deny
Deny from all
</FilesMatch>
# Protect directories
<DirectoryMatch "^/.*/(?:logs|backups|cache)/">
Order allow,deny
Deny from all
</DirectoryMatch>
# Handle PHP errors
php_flag display_errors off
php_value error_reporting E_ALL
php_value error_log logs/php_errors.log
# Security Headers
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Cache Control
<FilesMatch "\.(jpg|jpeg|png|gif|ico|css|js)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
# Prevent directory listing
Options -Indexes
# Custom error pages
ErrorDocument 404 /404.html
ErrorDocument 403 /403.html
ErrorDocument 500 /500.html
# URL Rewriting Rules
RewriteRule ^product/([0-9]+)/?$ product.php?id=$1 [NC,L]
RewriteRule ^category/([^/]+)/?$ category.php?slug=$1 [NC,L]
RewriteRule ^blog/([^/]+)/?$ blog.php?slug=$1 [NC,L]

17
404.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - الصفحة غير موجودة | ShubraVeil</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body class="error-page">
<div class="error-container">
<h1>404</h1>
<h2>عذراً، الصفحة غير موجودة</h2>
<p>الصفحة التي تبحث عنها غير موجودة أو تم نقلها.</p>
<a href="/" class="btn btn-primary">العودة للصفحة الرئيسية</a>
</div>
</body>
</html>

105
README.md Normal file
View File

@ -0,0 +1,105 @@
# ShubraVeil Essential Oils Website
A modern, responsive website for ShubraVeil Essential Oils company, showcasing their premium quality essential oils sourced from the fertile lands of Shubra Balloula.
## Features
- Responsive design that works on all devices
- Modern and clean user interface
- Brand-consistent color scheme
- Product showcase section
- About section highlighting the company's heritage
- Contact information
## Directory Structure
```
shubraveil/
├── css/
│ └── style.css
├── images/
│ ├── logo.svg
│ ├── logo-white.svg
│ ├── hero-bg.jpg
│ ├── jasmine-absolute.jpg
│ └── rosemary.jpg
├── index.html
└── README.md
```
## Brand Colors
- Salem: #0c814a
- Spring Bud: #cddf96
- Palm Leaf: #768b46
- Pine Tree: #252a16
- Raisin Black: #231f20
- Cultured: #f5f5f5
## Setup
1. Clone the repository
2. Add the required images to the `images` directory
3. Open `index.html` in a web browser
## التثبيت والتكوين
### المتطلبات الأساسية
- PHP 7.4 أو أحدث
- MySQL 5.7 أو أحدث
- Composer
- Node.js و npm
- خادم ويب (Apache/Nginx)
- تمكين امتدادات PHP التالية:
- GD
- MySQLi
- ZIP
- JSON
- OpenSSL
### التثبيت
1. استنساخ المستودع:
```bash
git clone https://github.com/yourusername/shubraveil.git
cd shubraveil
```
2. تثبيت اعتماديات PHP:
```bash
composer install
```
3. إنشاء ملف .env:
```bash
cp .env.example .env
```
قم بتحديث المتغيرات في ملف .env بالقيم المناسبة.
4. إعداد قاعدة البيانات:
```bash
mysql -u root -p
CREATE DATABASE shubraveil_db;
```
5. تهيئة المجلدات:
```bash
mkdir -p uploads/products
mkdir -p cache
mkdir -p backups
chmod -R 755 uploads cache backups
```
### التكوين
#### الإعدادات الأساسية
1. تكوين قاعدة البيانات في ملف .env
2. تكوين SMTP لإرسال البريد الإلكتروني
3. تكوين مفاتيح reCAPTCHA
#### الأمان
- تأكد من تعيين كلمات مرور قوية
- قم بتحديث مفتاح JWT_SECRET
- قم بتكوين HTTPS
## Development
The website uses vanilla HTML, CSS, and JavaScript for simplicity and performance. The design follows modern web development practices and is built to be easily maintainable and extensible.

140
admin/backup.php Normal file
View File

@ -0,0 +1,140 @@
<?php
require_once __DIR__ . '/../includes/config.php';
class Backup {
private $backup_path;
private $db_config;
public function __construct() {
$this->backup_path = __DIR__ . '/../backups';
if (!file_exists($this->backup_path)) {
mkdir($this->backup_path, 0755, true);
}
$this->db_config = [
'host' => DB_SERVER,
'user' => DB_USERNAME,
'pass' => DB_PASSWORD,
'name' => DB_NAME
];
}
public function createBackup() {
$timestamp = date('Y-m-d_H-i-s');
$backup_file = $this->backup_path . '/backup_' . $timestamp;
// Backup database
$this->backupDatabase($backup_file . '.sql');
// Backup files
$this->backupFiles($backup_file . '.zip');
// Clean old backups (keep last 5)
$this->cleanOldBackups();
return true;
}
private function backupDatabase($file) {
$command = sprintf(
'mysqldump --host=%s --user=%s --password=%s %s > %s',
escapeshellarg($this->db_config['host']),
escapeshellarg($this->db_config['user']),
escapeshellarg($this->db_config['pass']),
escapeshellarg($this->db_config['name']),
escapeshellarg($file)
);
exec($command, $output, $return_var);
if ($return_var !== 0) {
throw new Exception('Database backup failed');
}
}
private function backupFiles($file) {
$root_path = __DIR__ . '/..';
$exclude_dirs = ['backups', 'cache', 'logs', 'vendor'];
$zip = new ZipArchive();
if ($zip->open($file, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new Exception('Cannot create zip file');
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root_path),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file) {
if ($file->isDir()) {
continue;
}
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($root_path) + 1);
// Skip excluded directories
$skip = false;
foreach ($exclude_dirs as $exclude) {
if (strpos($relativePath, $exclude . '/') === 0) {
$skip = true;
break;
}
}
if (!$skip) {
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
private function cleanOldBackups($keep = 5) {
$files = glob($this->backup_path . '/*');
usort($files, function($a, $b) {
return filemtime($b) - filemtime($a);
});
if (count($files) > $keep) {
for ($i = $keep; $i < count($files); $i++) {
unlink($files[$i]);
}
}
}
public function restore($backup_date) {
$sql_file = $this->backup_path . '/backup_' . $backup_date . '.sql';
$zip_file = $this->backup_path . '/backup_' . $backup_date . '.zip';
if (!file_exists($sql_file) || !file_exists($zip_file)) {
throw new Exception('Backup files not found');
}
// Restore database
$command = sprintf(
'mysql --host=%s --user=%s --password=%s %s < %s',
escapeshellarg($this->db_config['host']),
escapeshellarg($this->db_config['user']),
escapeshellarg($this->db_config['pass']),
escapeshellarg($this->db_config['name']),
escapeshellarg($sql_file)
);
exec($command, $output, $return_var);
if ($return_var !== 0) {
throw new Exception('Database restore failed');
}
// Restore files
$zip = new ZipArchive();
if ($zip->open($zip_file) !== true) {
throw new Exception('Cannot open backup archive');
}
$zip->extractTo(__DIR__ . '/..');
$zip->close();
return true;
}
}

61
admin/config/database.php Normal file
View File

@ -0,0 +1,61 @@
<?php
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_NAME', 'shubraveil_db');
$conn = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD);
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
// Create database if not exists
$sql = "CREATE DATABASE IF NOT EXISTS " . DB_NAME;
if (mysqli_query($conn, $sql)) {
mysqli_select_db($conn, DB_NAME);
} else {
die("Error creating database: " . mysqli_error($conn));
}
// Create tables
$sql = "CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)";
mysqli_query($conn, $sql);
$sql = "CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10,2),
image VARCHAR(255),
category VARCHAR(50),
product_type ENUM('essential_oils', 'fixed_oils', 'hydrosols', 'natural_cosmetics') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)";
mysqli_query($conn, $sql);
$sql = "CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100),
customer_email VARCHAR(100),
customer_phone VARCHAR(20),
order_details TEXT,
status VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)";
mysqli_query($conn, $sql);
$sql = "CREATE TABLE IF NOT EXISTS settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_name VARCHAR(50) NOT NULL UNIQUE,
setting_value TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)";
mysqli_query($conn, $sql);
?>

230
admin/config/setup.sql Normal file
View File

@ -0,0 +1,230 @@
-- Users table
CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
full_name VARCHAR(100),
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE,
role ENUM('user', 'admin') DEFAULT 'user'
);
-- Addresses table
CREATE TABLE IF NOT EXISTS addresses (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
address_line1 VARCHAR(255) NOT NULL,
address_line2 VARCHAR(255),
city VARCHAR(100) NOT NULL,
state VARCHAR(100),
postal_code VARCHAR(20),
is_default BOOLEAN DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Categories table
CREATE TABLE IF NOT EXISTS categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
name_ar VARCHAR(100) NOT NULL,
description TEXT,
description_ar TEXT,
parent_id INT,
image_url VARCHAR(255),
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);
-- Products table
CREATE TABLE IF NOT EXISTS products (
id INT PRIMARY KEY AUTO_INCREMENT,
category_id INT,
name VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) NOT NULL,
description TEXT,
description_ar TEXT,
price DECIMAL(10,2) NOT NULL,
sale_price DECIMAL(10,2),
stock_quantity INT DEFAULT 0,
sku VARCHAR(50) UNIQUE,
weight DECIMAL(10,2),
benefits TEXT,
benefits_ar TEXT,
ingredients TEXT,
ingredients_ar TEXT,
usage_instructions TEXT,
usage_instructions_ar TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
-- Product Images table
CREATE TABLE IF NOT EXISTS product_images (
id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT,
image_url VARCHAR(255) NOT NULL,
is_primary BOOLEAN DEFAULT FALSE,
sort_order INT DEFAULT 0,
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
);
-- Reviews table
CREATE TABLE IF NOT EXISTS reviews (
id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT,
user_id INT,
rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5),
comment TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_verified BOOLEAN DEFAULT FALSE,
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Shopping Cart table
CREATE TABLE IF NOT EXISTS shopping_cart (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
product_id INT,
quantity INT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- Wishlist table
CREATE TABLE IF NOT EXISTS wishlist (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
product_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- Orders table
CREATE TABLE IF NOT EXISTS orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
total_amount DECIMAL(10,2) NOT NULL,
shipping_address_id INT,
payment_method VARCHAR(50),
payment_status ENUM('pending', 'paid', 'failed') DEFAULT 'pending',
shipping_method VARCHAR(50),
shipping_cost DECIMAL(10,2) DEFAULT 0,
discount_amount DECIMAL(10,2) DEFAULT 0,
coupon_code VARCHAR(50),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (shipping_address_id) REFERENCES addresses(id)
);
-- Order Items table
CREATE TABLE IF NOT EXISTS order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT,
product_id INT,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- Coupons table
CREATE TABLE IF NOT EXISTS coupons (
id INT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(50) UNIQUE NOT NULL,
type ENUM('percentage', 'fixed') NOT NULL,
value DECIMAL(10,2) NOT NULL,
min_purchase DECIMAL(10,2),
max_discount DECIMAL(10,2),
start_date TIMESTAMP,
end_date TIMESTAMP,
usage_limit INT,
used_count INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE
);
-- Blog Posts table
CREATE TABLE IF NOT EXISTS blog_posts (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
title_ar VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
content_ar TEXT NOT NULL,
author_id INT,
featured_image VARCHAR(255),
status ENUM('draft', 'published') DEFAULT 'draft',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
published_at TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id)
);
-- Newsletter Subscribers table
CREATE TABLE IF NOT EXISTS newsletter_subscribers (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) UNIQUE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
subscribed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Chat Messages table
CREATE TABLE IF NOT EXISTS chat_messages (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
admin_id INT,
message TEXT NOT NULL,
is_admin BOOLEAN DEFAULT FALSE,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (admin_id) REFERENCES users(id)
);
-- FAQ Categories table
CREATE TABLE IF NOT EXISTS faq_categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
name_ar VARCHAR(100) NOT NULL,
sort_order INT DEFAULT 0
);
-- FAQs table
CREATE TABLE IF NOT EXISTS faqs (
id INT PRIMARY KEY AUTO_INCREMENT,
category_id INT,
question VARCHAR(255) NOT NULL,
question_ar VARCHAR(255) NOT NULL,
answer TEXT NOT NULL,
answer_ar TEXT NOT NULL,
sort_order INT DEFAULT 0,
FOREIGN KEY (category_id) REFERENCES faq_categories(id)
);
-- User Points table
CREATE TABLE IF NOT EXISTS user_points (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
points INT DEFAULT 0,
earned_from VARCHAR(50),
earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Referral System table
CREATE TABLE IF NOT EXISTS referrals (
id INT PRIMARY KEY AUTO_INCREMENT,
referrer_id INT,
referred_id INT,
status ENUM('pending', 'completed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP,
FOREIGN KEY (referrer_id) REFERENCES users(id),
FOREIGN KEY (referred_id) REFERENCES users(id)
);

32
admin/includes/auth.php Normal file
View File

@ -0,0 +1,32 @@
<?php
session_start();
// التحقق من تسجيل الدخول
function isLoggedIn() {
return isset($_SESSION["loggedin"]) && $_SESSION["loggedin"] === true;
}
// التحقق من صلاحيات المستخدم
function checkUserRole($required_role = 'editor') {
if (!isLoggedIn()) {
return false;
}
return $_SESSION["role"] === 'admin' || $_SESSION["role"] === $required_role;
}
// تسجيل الخروج
function logout() {
$_SESSION = array();
session_destroy();
header("location: login.php");
exit;
}
// تأمين المدخلات
function sanitize_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
?>

179
admin/index.php Normal file
View File

@ -0,0 +1,179 @@
<?php
session_start();
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true){
header("location: login.php");
exit;
}
require_once "config/database.php";
// Get counts for dashboard
$products_count = mysqli_fetch_array(mysqli_query($conn, "SELECT COUNT(*) FROM products"))[0];
$orders_count = mysqli_fetch_array(mysqli_query($conn, "SELECT COUNT(*) FROM orders"))[0];
$pending_orders = mysqli_fetch_array(mysqli_query($conn, "SELECT COUNT(*) FROM orders WHERE status='pending'"))[0];
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>لوحة التحكم - ShubraVeil</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Tajawal', sans-serif;
background-color: #f8f9fa;
}
.sidebar {
min-height: 100vh;
background-color: #343a40;
padding-top: 20px;
}
.sidebar a {
color: #fff;
padding: 10px 15px;
display: block;
text-decoration: none;
}
.sidebar a:hover {
background-color: #495057;
}
.main-content {
padding: 20px;
}
.dashboard-card {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.card-icon {
font-size: 2.5rem;
margin-bottom: 15px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<nav class="col-md-2 d-none d-md-block sidebar">
<div class="text-center mb-4">
<img src="../images/logo.jpg" alt="Logo" style="max-width: 120px;">
</div>
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="index.php">
<i class="fas fa-home ml-2"></i>
الرئيسية
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="products.php">
<i class="fas fa-box ml-2"></i>
المنتجات
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">
<i class="fas fa-shopping-cart ml-2"></i>
الطلبات
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="settings.php">
<i class="fas fa-cog ml-2"></i>
الإعدادات
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">
<i class="fas fa-sign-out-alt ml-2"></i>
تسجيل الخروج
</a>
</li>
</ul>
</div>
</nav>
<!-- Main content -->
<main role="main" class="col-md-10 mr-auto ml-auto col-lg-10 px-4 main-content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">لوحة التحكم</h1>
</div>
<div class="row">
<div class="col-md-4">
<div class="dashboard-card">
<div class="text-primary card-icon">
<i class="fas fa-box"></i>
</div>
<h3>المنتجات</h3>
<p class="h2"><?php echo $products_count; ?></p>
</div>
</div>
<div class="col-md-4">
<div class="dashboard-card">
<div class="text-success card-icon">
<i class="fas fa-shopping-cart"></i>
</div>
<h3>إجمالي الطلبات</h3>
<p class="h2"><?php echo $orders_count; ?></p>
</div>
</div>
<div class="col-md-4">
<div class="dashboard-card">
<div class="text-warning card-icon">
<i class="fas fa-clock"></i>
</div>
<h3>الطلبات المعلقة</h3>
<p class="h2"><?php echo $pending_orders; ?></p>
</div>
</div>
</div>
<!-- Recent Orders -->
<div class="dashboard-card mt-4">
<h3>آخر الطلبات</h3>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>اسم العميل</th>
<th>التاريخ</th>
<th>الحالة</th>
</tr>
</thead>
<tbody>
<?php
$sql = "SELECT * FROM orders ORDER BY created_at DESC LIMIT 5";
$result = mysqli_query($conn, $sql);
while($row = mysqli_fetch_assoc($result)) {
echo "<tr>";
echo "<td>" . $row['id'] . "</td>";
echo "<td>" . $row['customer_name'] . "</td>";
echo "<td>" . $row['created_at'] . "</td>";
echo "<td><span class='badge badge-" .
($row['status'] == 'completed' ? 'success' :
($row['status'] == 'pending' ? 'warning' : 'secondary')) .
"'>" . $row['status'] . "</span></td>";
echo "</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

179
admin/login.php Normal file
View File

@ -0,0 +1,179 @@
<?php
require_once "includes/auth.php";
require_once "config/database.php";
// إذا كان المستخدم مسجل دخوله بالفعل، قم بتحويله إلى الصفحة الرئيسية
if(isLoggedIn()) {
header("location: index.php");
exit;
}
$username = $password = "";
$username_err = $password_err = $login_err = "";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// التحقق من اسم المستخدم
if(empty(trim($_POST["username"]))) {
$username_err = "الرجاء إدخال اسم المستخدم";
} else {
$username = sanitize_input($_POST["username"]);
}
// التحقق من كلمة المرور
if(empty(trim($_POST["password"]))) {
$password_err = "الرجاء إدخال كلمة المرور";
} else {
$password = trim($_POST["password"]);
}
// التحقق من صحة بيانات تسجيل الدخول
if(empty($username_err) && empty($password_err)) {
$sql = "SELECT id, username, password, role, full_name FROM users WHERE username = ?";
if($stmt = mysqli_prepare($conn, $sql)) {
mysqli_stmt_bind_param($stmt, "s", $param_username);
$param_username = $username;
if(mysqli_stmt_execute($stmt)) {
mysqli_stmt_store_result($stmt);
if(mysqli_stmt_num_rows($stmt) == 1) {
mysqli_stmt_bind_result($stmt, $id, $username, $hashed_password, $role, $full_name);
if(mysqli_stmt_fetch($stmt)) {
if(password_verify($password, $hashed_password)) {
// تم تسجيل الدخول بنجاح
$_SESSION["loggedin"] = true;
$_SESSION["id"] = $id;
$_SESSION["username"] = $username;
$_SESSION["role"] = $role;
$_SESSION["full_name"] = $full_name;
// تسجيل وقت آخر تسجيل دخول
$update_sql = "UPDATE users SET last_login = NOW() WHERE id = ?";
if($update_stmt = mysqli_prepare($conn, $update_sql)) {
mysqli_stmt_bind_param($update_stmt, "i", $id);
mysqli_stmt_execute($update_stmt);
mysqli_stmt_close($update_stmt);
}
header("location: index.php");
} else {
$login_err = "اسم المستخدم أو كلمة المرور غير صحيحة";
}
}
} else {
$login_err = "اسم المستخدم أو كلمة المرور غير صحيحة";
}
} else {
$login_err = "حدث خطأ ما. الرجاء المحاولة لاحقاً";
}
mysqli_stmt_close($stmt);
}
}
}
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تسجيل الدخول - لوحة التحكم</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Tajawal', sans-serif;
background-color: #f8f9fa;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-container {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
.login-logo {
text-align: center;
margin-bottom: 30px;
}
.login-logo img {
max-width: 150px;
height: auto;
}
.form-control {
border-radius: 5px;
padding: 12px;
height: auto;
}
.btn-login {
background-color: #0c814a;
border: none;
padding: 12px;
font-weight: bold;
transition: all 0.3s ease;
}
.btn-login:hover {
background-color: #096b3b;
transform: translateY(-2px);
}
.alert {
border-radius: 5px;
}
.back-to-site {
text-align: center;
margin-top: 20px;
}
.back-to-site a {
color: #0c814a;
text-decoration: none;
}
.back-to-site a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-logo">
<img src="../images/logo.jpg" alt="ShubraVeil">
</div>
<h2 class="text-center mb-4">تسجيل الدخول</h2>
<?php
if(!empty($login_err)){
echo '<div class="alert alert-danger">' . $login_err . '</div>';
}
?>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<div class="form-group">
<label>اسم المستخدم</label>
<input type="text" name="username" class="form-control <?php echo (!empty($username_err)) ? 'is-invalid' : ''; ?>" value="<?php echo $username; ?>">
<span class="invalid-feedback"><?php echo $username_err; ?></span>
</div>
<div class="form-group">
<label>كلمة المرور</label>
<input type="password" name="password" class="form-control <?php echo (!empty($password_err)) ? 'is-invalid' : ''; ?>">
<span class="invalid-feedback"><?php echo $password_err; ?></span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block btn-login">دخول</button>
</div>
</form>
<div class="back-to-site">
<a href="../index.html">العودة إلى الموقع الرئيسي</a>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

7
admin/logout.php Normal file
View File

@ -0,0 +1,7 @@
<?php
session_start();
$_SESSION = array();
session_destroy();
header("location: login.php");
exit;
?>

237
admin/products.php Normal file
View File

@ -0,0 +1,237 @@
<?php
session_start();
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true){
header("location: login.php");
exit;
}
require_once "config/database.php";
// Handle product deletion
if(isset($_GET['delete'])) {
$id = mysqli_real_escape_string($conn, $_GET['delete']);
mysqli_query($conn, "DELETE FROM products WHERE id = $id");
header("location: products.php");
}
// Handle product addition/editing
if($_SERVER["REQUEST_METHOD"] == "POST") {
$name = mysqli_real_escape_string($conn, $_POST['name']);
$description = mysqli_real_escape_string($conn, $_POST['description']);
$price = mysqli_real_escape_string($conn, $_POST['price']);
$category = mysqli_real_escape_string($conn, $_POST['category']);
$product_type = mysqli_real_escape_string($conn, $_POST['product_type']);
if(isset($_POST['id'])) {
// Update existing product
$id = mysqli_real_escape_string($conn, $_POST['id']);
$sql = "UPDATE products SET name='$name', description='$description',
price='$price', category='$category', product_type='$product_type' WHERE id=$id";
} else {
// Add new product
if(isset($_FILES['image']) && $_FILES['image']['error'] == 0) {
$target_dir = "../images/products/";
if (!file_exists($target_dir)) {
mkdir($target_dir, 0777, true);
}
$target_file = $target_dir . basename($_FILES["image"]["name"]);
move_uploaded_file($_FILES["image"]["tmp_name"], $target_file);
$image_path = "images/products/" . basename($_FILES["image"]["name"]);
} else {
$image_path = "";
}
$sql = "INSERT INTO products (name, description, price, category, product_type, image)
VALUES ('$name', '$description', '$price', '$category', '$product_type', '$image_path')";
}
mysqli_query($conn, $sql);
header("location: products.php");
}
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>إدارة المنتجات - ShubraVeil</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Tajawal', sans-serif; }
.sidebar {
min-height: 100vh;
background-color: #343a40;
padding-top: 20px;
}
.sidebar a {
color: #fff;
padding: 10px 15px;
display: block;
text-decoration: none;
}
.sidebar a:hover {
background-color: #495057;
}
.main-content {
padding: 20px;
}
.product-image {
max-width: 100px;
max-height: 100px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<nav class="col-md-2 d-none d-md-block sidebar">
<div class="text-center mb-4">
<img src="../images/logo.jpg" alt="Logo" style="max-width: 120px;">
</div>
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="index.php">
<i class="fas fa-home ml-2"></i>
الرئيسية
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="products.php">
<i class="fas fa-box ml-2"></i>
المنتجات
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">
<i class="fas fa-shopping-cart ml-2"></i>
الطلبات
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="settings.php">
<i class="fas fa-cog ml-2"></i>
الإعدادات
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">
<i class="fas fa-sign-out-alt ml-2"></i>
تسجيل الخروج
</a>
</li>
</ul>
</div>
</nav>
<!-- Main content -->
<main role="main" class="col-md-10 mr-auto ml-auto col-lg-10 px-4 main-content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<h1 class="h2">إدارة المنتجات</h1>
<button class="btn btn-primary" data-toggle="modal" data-target="#addProductModal">
<i class="fas fa-plus ml-2"></i>إضافة منتج جديد
</button>
</div>
<!-- Products Table -->
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>الصورة</th>
<th>اسم المنتج</th>
<th>السعر</th>
<th>الفئة</th>
<th>نوع المنتج</th>
<th>الإجراءات</th>
</tr>
</thead>
<tbody>
<?php
$result = mysqli_query($conn, "SELECT * FROM products ORDER BY id DESC");
while($row = mysqli_fetch_assoc($result)) {
echo "<tr>";
echo "<td>" . $row['id'] . "</td>";
echo "<td><img src='../" . $row['image'] . "' class='product-image' alt='" . $row['name'] . "'></td>";
echo "<td>" . $row['name'] . "</td>";
echo "<td>" . $row['price'] . "</td>";
echo "<td>" . $row['category'] . "</td>";
echo "<td>" . $row['product_type'] . "</td>";
echo "<td>
<button class='btn btn-sm btn-info edit-product' data-id='" . $row['id'] . "'>
<i class='fas fa-edit'></i>
</button>
<a href='?delete=" . $row['id'] . "' class='btn btn-sm btn-danger' onclick='return confirm(\"هل أنت متأكد من حذف هذا المنتج؟\")'>
<i class='fas fa-trash'></i>
</a>
</td>";
echo "</tr>";
}
?>
</tbody>
</table>
</div>
<!-- Add Product Modal -->
<div class="modal fade" id="addProductModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">إضافة منتج جديد</h5>
<button type="button" class="close ml-0" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="" method="post" enctype="multipart/form-data">
<div class="modal-body">
<div class="form-group">
<label>اسم المنتج</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="form-group">
<label>الوصف</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="form-group">
<label>السعر</label>
<input type="number" name="price" class="form-control" step="0.01" required>
</div>
<div class="form-group">
<label>الفئة</label>
<input type="text" name="category" class="form-control">
</div>
<div class="form-group">
<label>نوع المنتج</label>
<select name="product_type" class="form-control" required>
<option value="">اختر نوع المنتج</option>
<option value="essential_oils">الزيوت الأساسية</option>
<option value="fixed_oils">الزيوت الثابتة</option>
<option value="hydrosols">الهيدروسولات العطرية</option>
<option value="natural_cosmetics">مستحضرات تجميل طبيعية</option>
</select>
</div>
<div class="form-group">
<label>صورة المنتج</label>
<input type="file" name="image" class="form-control-file">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">إلغاء</button>
<button type="submit" class="btn btn-primary">حفظ</button>
</div>
</form>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

13
apache.conf Normal file
View File

@ -0,0 +1,13 @@
<VirtualHost *:80>
ServerName shubraveil.local
DocumentRoot /home/momaher/Public/Websites/shubraveil
<Directory /home/momaher/Public/Websites/shubraveil>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/shubraveil_error.log
CustomLog ${APACHE_LOG_DIR}/shubraveil_access.log combined
</VirtualHost>

30
api/products.php Normal file
View File

@ -0,0 +1,30 @@
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../admin/config/database.php';
$type = isset($_GET['type']) ? mysqli_real_escape_string($conn, $_GET['type']) : null;
$sql = "SELECT * FROM products";
if ($type) {
$sql .= " WHERE product_type = '$type'";
}
$sql .= " ORDER BY created_at DESC";
$result = mysqli_query($conn, $sql);
$products = [];
while ($row = mysqli_fetch_assoc($result)) {
$products[] = [
'id' => $row['id'],
'name' => $row['name'],
'description' => $row['description'],
'price' => $row['price'],
'image' => $row['image'],
'category' => $row['category']
];
}
echo json_encode($products);
?>

1
backups/.gitkeep Normal file
View File

@ -0,0 +1 @@

103
blog.html Normal file
View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>المدونة - ShubraVeil</title>
<meta name="description" content="اكتشف مقالات حصرية عن الزيوت العطرية وفوائدها وطرق استخدامها">
<meta name="keywords" content="زيوت عطرية, مدونة, نصائح, فوائد الزيوت">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<header>
<!-- نفس الهيدر السابق -->
</header>
<main>
<section class="blog-section">
<div class="container">
<h1 class="section-title">المدونة</h1>
<div class="blog-filters">
<div class="search-box">
<input type="text" placeholder="ابحث في المقالات...">
<button><i class="fas fa-search"></i></button>
</div>
<div class="categories">
<button class="category-btn active" data-category="all">الكل</button>
<button class="category-btn" data-category="tips">نصائح واستخدامات</button>
<button class="category-btn" data-category="benefits">فوائد صحية</button>
<button class="category-btn" data-category="news">أخبار وتحديثات</button>
</div>
</div>
<div class="blog-grid">
<!-- مقال 1 -->
<article class="blog-card">
<div class="blog-image">
<img src="images/blog/lavender-benefits.jpg" alt="فوائد اللافندر">
<span class="category">فوائد صحية</span>
</div>
<div class="blog-content">
<h2>10 فوائد مذهلة لزيت اللافندر</h2>
<p>اكتشف الفوائد المتعددة لزيت اللافندر العطري وكيفية استخدامه للحصول على أفضل النتائج...</p>
<div class="blog-meta">
<span><i class="far fa-calendar"></i> 8 ديسمبر 2024</span>
<span><i class="far fa-clock"></i> 5 دقائق قراءة</span>
</div>
<a href="blog/lavender-benefits.html" class="read-more">اقرأ المزيد</a>
</div>
</article>
<!-- مقال 2 -->
<article class="blog-card">
<div class="blog-image">
<img src="images/blog/essential-oils-guide.jpg" alt="دليل الزيوت العطرية">
<span class="category">نصائح واستخدامات</span>
</div>
<div class="blog-content">
<h2>دليلك الشامل لاستخدام الزيوت العطرية</h2>
<p>تعرف على الطرق الصحيحة لاستخدام الزيوت العطرية وكيفية مزجها للحصول على أفضل النتائج...</p>
<div class="blog-meta">
<span><i class="far fa-calendar"></i> 7 ديسمبر 2024</span>
<span><i class="far fa-clock"></i> 8 دقائق قراءة</span>
</div>
<a href="blog/essential-oils-guide.html" class="read-more">اقرأ المزيد</a>
</div>
</article>
</div>
<div class="pagination">
<button class="prev-page" disabled><i class="fas fa-chevron-right"></i></button>
<span class="current-page">1</span>
<button class="next-page"><i class="fas fa-chevron-left"></i></button>
</div>
</div>
</section>
<!-- قسم الاشتراك في النشرة البريدية -->
<section class="blog-newsletter">
<div class="container">
<div class="newsletter-content">
<h2>اشترك في نشرتنا البريدية</h2>
<p>احصل على أحدث المقالات والنصائح مباشرة إلى بريدك الإلكتروني</p>
<form id="blog-newsletter-form" class="newsletter-form">
<div class="form-group">
<input type="email" placeholder="أدخل بريدك الإلكتروني" required>
<button type="submit" class="btn">اشترك الآن</button>
</div>
</form>
</div>
</div>
</section>
</main>
<footer>
<!-- نفس الفوتر السابق -->
</footer>
<script src="js/blog.js"></script>
</body>
</html>

1
cache/.gitkeep vendored Normal file
View File

@ -0,0 +1 @@

1748
composer-setup.php Normal file

File diff suppressed because it is too large Load Diff

44
composer.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "shubraveil/website",
"description": "ShubraVeil - نظام إدارة متجر الحجاب",
"type": "project",
"require": {
"php": ">=7.4",
"phpmailer/phpmailer": "^6.8",
"firebase/php-jwt": "^6.4",
"vlucas/phpdotenv": "^5.5",
"intervention/image": "^2.7",
"guzzlehttp/guzzle": "^7.0",
"monolog/monolog": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"phpstan/phpstan": "^1.10",
"squizlabs/php_codesniffer": "^3.7"
},
"autoload": {
"psr-4": {
"ShubraVeil\\": "includes/"
}
},
"autoload-dev": {
"psr-4": {
"ShubraVeil\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"analyse": "phpstan analyse",
"cs": "phpcs",
"post-install-cmd": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
]
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"minimum-stability": "stable",
"prefer-stable": true
}

3412
css/style.css Normal file

File diff suppressed because it is too large Load Diff

185
database/backup.sql Normal file
View File

@ -0,0 +1,185 @@
-- MySQL dump 10.13 Distrib 8.0.40, for Linux (x86_64)
--
-- Host: localhost Database: shubraveil_db
-- ------------------------------------------------------
-- Server version 8.0.40-0ubuntu0.24.04.1
/*!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 */;
/*!50503 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 `order_items`
--
DROP TABLE IF EXISTS `order_items`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `order_items` (
`id` int NOT NULL AUTO_INCREMENT,
`order_id` int NOT NULL,
`product_id` int NOT NULL,
`quantity` int NOT NULL,
`price` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_product_id` (`product_id`),
CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`),
CONSTRAINT `order_items_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `order_items`
--
LOCK TABLES `order_items` WRITE;
/*!40000 ALTER TABLE `order_items` DISABLE KEYS */;
INSERT INTO `order_items` VALUES (1,1,2,1,199.99);
/*!40000 ALTER TABLE `order_items` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `orders`
--
DROP TABLE IF EXISTS `orders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`status` enum('pending','processing','shipped','delivered','cancelled') COLLATE utf8mb4_unicode_ci DEFAULT 'pending',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `orders`
--
LOCK TABLES `orders` WRITE;
/*!40000 ALTER TABLE `orders` DISABLE KEYS */;
INSERT INTO `orders` VALUES (1,4,199.99,'pending','2024-12-09 18:44:16','2024-12-09 18:44:16');
/*!40000 ALTER TABLE `orders` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `password_resets`
--
DROP TABLE IF EXISTS `password_resets`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `password_resets` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
`expiry` timestamp NOT NULL,
`used` tinyint(1) DEFAULT '0',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_token` (`token`),
KEY `idx_user_id` (`user_id`),
CONSTRAINT `password_resets_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `password_resets`
--
LOCK TABLES `password_resets` WRITE;
/*!40000 ALTER TABLE `password_resets` DISABLE KEYS */;
/*!40000 ALTER TABLE `password_resets` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `products`
--
DROP TABLE IF EXISTS `products`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `products` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`price` decimal(10,2) NOT NULL,
`stock` int NOT NULL DEFAULT '0',
`category` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category`),
KEY `idx_price` (`price`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `products`
--
LOCK TABLES `products` WRITE;
/*!40000 ALTER TABLE `products` DISABLE KEYS */;
INSERT INTO `products` VALUES (2,'زيت الورد الطبيعي','زيت ورد طبيعي 100% للعناية بالبشرة',199.99,30,NULL,NULL,'2024-12-09 18:44:16','2024-12-09 18:44:16');
/*!40000 ALTER TABLE `products` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`role` enum('admin','user') COLLATE utf8mb4_unicode_ci DEFAULT 'user',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`),
KEY `idx_email` (`email`),
KEY `idx_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `users`
--
LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` VALUES (1,'admin','admin@shubraveil.com','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi','admin','2024-12-09 18:32:26','2024-12-09 18:32:26'),(2,'test_user','test@example.com','$2y$10$gDH0ZaW06YRdGSFQL2y4h.Ee1H40ux3d5nB5RGfkA52G5coEFNkWS','user','2024-12-09 18:32:33','2024-12-09 18:32:33'),(4,'test_user_1733769856','test_1733769856@example.com','$2y$10$fgy4gUfBeVRFzELpj8v2guPMpSjcxJS.s6BoKpKHwX9cpTjT8HU5a','user','2024-12-09 18:44:16','2024-12-09 18:44:16');
/*!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 */;
/*!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 2024-12-09 21:10:55

75
database/schema.sql Normal file
View File

@ -0,0 +1,75 @@
-- Create database if not exists
CREATE DATABASE IF NOT EXISTS shubraveil_db;
USE shubraveil_db;
-- Users table
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role ENUM('admin', 'user') DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Products table
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
category VARCHAR(50),
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_category (category),
INDEX idx_price (price)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Orders table
CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_user_id (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Order items table
CREATE TABLE IF NOT EXISTS order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id),
INDEX idx_order_id (order_id),
INDEX idx_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Password resets table
CREATE TABLE IF NOT EXISTS password_resets (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
token VARCHAR(64) NOT NULL,
expiry TIMESTAMP NOT NULL,
used BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_token (token),
INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create default admin user
INSERT INTO users (username, email, password, role)
VALUES ('admin', 'admin@shubraveil.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin')
ON DUPLICATE KEY UPDATE role = 'admin';

198
database/shubraveil_db.sql Normal file
View File

@ -0,0 +1,198 @@
-- phpMyAdmin SQL Dump
-- version 5.2.0
-- Encoding: UTF-8
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+02:00";
--
-- قاعدة البيانات: `shubraveil_db`
--
CREATE DATABASE IF NOT EXISTS `shubraveil_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `shubraveil_db`;
-- --------------------------------------------------------
--
-- بنية الجدول `users`
--
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`email` varchar(100) NOT NULL,
`full_name` varchar(100) DEFAULT NULL,
`role` enum('admin','editor') NOT NULL DEFAULT 'editor',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- إدراج بيانات تجريبية للجدول `users`
--
INSERT INTO `users` (`username`, `password`, `email`, `full_name`, `role`) VALUES
('admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin@shubraveil.com', 'مدير النظام', 'admin');
-- --------------------------------------------------------
--
-- بنية الجدول `products`
--
CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`price` decimal(10,2) NOT NULL,
`image` varchar(255) DEFAULT NULL,
`category` varchar(50) DEFAULT NULL,
`product_type` enum('essential_oils','fixed_oils','hydrosols','natural_cosmetics') NOT NULL,
`stock` int(11) NOT NULL DEFAULT 0,
`is_featured` tinyint(1) NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- إدراج بيانات تجريبية للجدول `products`
--
INSERT INTO `products` (`name`, `description`, `price`, `category`, `product_type`, `stock`, `is_featured`) VALUES
-- الزيوت الأساسية
('زيت الياسمين المطلق', 'زيت عطري نقي 100% مستخلص من زهور الياسمين البلدي', 299.99, 'زيوت عطرية', 'essential_oils', 50, 1),
('زيت اللافندر', 'زيت عطري طبيعي مستخلص من زهور اللافندر', 149.99, 'زيوت عطرية', 'essential_oils', 75, 1),
('زيت النعناع', 'زيت عطري منعش مستخلص من أوراق النعناع الطازجة', 89.99, 'زيوت عطرية', 'essential_oils', 100, 0),
('زيت الورد', 'زيت عطري فاخر مستخلص من بتلات الورد البلدي', 399.99, 'زيوت عطرية', 'essential_oils', 30, 1),
-- الزيوت الثابتة
('زيت اللوز الحلو', 'زيت طبيعي غني بالفيتامينات مستخلص من اللوز', 79.99, 'زيوت ثابتة', 'fixed_oils', 80, 1),
('زيت الجوجوبا', 'زيت مغذي للبشرة والشعر', 129.99, 'زيوت ثابتة', 'fixed_oils', 60, 1),
('زيت الأرغان', 'زيت مغربي أصلي للعناية بالشعر والبشرة', 199.99, 'زيوت ثابتة', 'fixed_oils', 40, 1),
('زيت جنين القمح', 'زيت غني بفيتامين E للبشرة', 89.99, 'زيوت ثابتة', 'fixed_oils', 70, 0),
-- الهيدروسولات العطرية
('ماء الورد البلدي', 'ماء ورد طبيعي 100% للبشرة والطهي', 49.99, 'هيدروسولات', 'hydrosols', 150, 1),
('ماء الزهر', 'ماء زهر البرتقال الطبيعي', 44.99, 'هيدروسولات', 'hydrosols', 120, 1),
('ماء اللافندر', 'هيدروسول اللافندر المهدئ للبشرة', 54.99, 'هيدروسولات', 'hydrosols', 90, 0),
('ماء النعناع', 'هيدروسول منعش للبشرة والشعر', 39.99, 'هيدروسولات', 'hydrosols', 100, 0),
-- مستحضرات تجميل طبيعية
('كريم الياسمين المغذي', 'كريم طبيعي بخلاصة الياسمين للترطيب', 149.99, 'مستحضرات تجميل', 'natural_cosmetics', 45, 1),
('مقشر الورد الطبيعي', 'مقشر للوجه والجسم بالورد البلدي', 99.99, 'مستحضرات تجميل', 'natural_cosmetics', 60, 1),
('بلسم الشفاه الطبيعي', 'بلسم شفاه مرطب بالعسل والزيوت الطبيعية', 29.99, 'مستحضرات تجميل', 'natural_cosmetics', 200, 0),
('ماسك الطين المغربي', 'ماسك تنظيف عميق بالطين المغربي', 79.99, 'مستحضرات تجميل', 'natural_cosmetics', 80, 1);
-- --------------------------------------------------------
--
-- بنية الجدول `orders`
--
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_number` varchar(20) NOT NULL,
`customer_name` varchar(100) NOT NULL,
`customer_email` varchar(100) NOT NULL,
`customer_phone` varchar(20) NOT NULL,
`customer_address` text NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`status` enum('pending','processing','shipped','delivered','cancelled') NOT NULL DEFAULT 'pending',
`payment_method` enum('cash_on_delivery','bank_transfer','credit_card') NOT NULL,
`payment_status` enum('pending','paid','failed') NOT NULL DEFAULT 'pending',
`notes` text DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `order_number` (`order_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- بنية الجدول `order_items`
--
CREATE TABLE `order_items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`quantity` int(11) NOT NULL,
`price` decimal(10,2) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `order_id` (`order_id`),
KEY `product_id` (`product_id`),
CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE,
CONSTRAINT `order_items_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- بنية الجدول `settings`
--
CREATE TABLE `settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`setting_name` varchar(50) NOT NULL,
`setting_value` text DEFAULT NULL,
`setting_type` enum('text','number','boolean','json') NOT NULL DEFAULT 'text',
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `setting_name` (`setting_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- إدراج بيانات تجريبية للجدول `settings`
--
INSERT INTO `settings` (`setting_name`, `setting_value`, `setting_type`) VALUES
('site_name', 'ShubraVeil - زيوت طبيعية عطرية', 'text'),
('site_description', 'متجر متخصص في الزيوت الطبيعية والعطرية من شبرا بلولة', 'text'),
('contact_email', 'info@shubraveil.com', 'text'),
('contact_phone', '+20 123 456 789', 'text'),
('shipping_cost', '30.00', 'number'),
('free_shipping_minimum', '500.00', 'number'),
('social_media', '{"facebook":"https://facebook.com/shubraveil","instagram":"https://instagram.com/shubraveil","whatsapp":"+201234567890"}', 'json');
-- --------------------------------------------------------
--
-- بنية الجدول `product_reviews`
--
CREATE TABLE `product_reviews` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`customer_name` varchar(100) NOT NULL,
`rating` tinyint(1) NOT NULL,
`review` text DEFAULT NULL,
`status` enum('pending','approved','rejected') NOT NULL DEFAULT 'pending',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `product_id` (`product_id`),
CONSTRAINT `product_reviews_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- بنية الجدول `subscribers`
--
CREATE TABLE `subscribers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(100) NOT NULL,
`status` enum('active','unsubscribed') NOT NULL DEFAULT 'active',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
COMMIT;

50
deploy.sh Normal file
View File

@ -0,0 +1,50 @@
#!/bin/bash
# تعيين المتغيرات
REMOTE_HOST="" # أدخل عنوان الخادم
REMOTE_USER="" # أدخل اسم المستخدم
REMOTE_PATH="" # أدخل المسار على الخادم
LOCAL_PATH="/home/momaher/Public/Websites/shubraveil"
# إنشاء نسخة احتياطية من قاعدة البيانات
echo "Creating database backup..."
mysqldump -u momaher -pMohamed@9498# shubraveil_db > database/backup.sql
# تجهيز الملفات للرفع
echo "Preparing files for upload..."
# إنشاء مجلد مؤقت للرفع
TEMP_DIR=$(mktemp -d)
cp -r * $TEMP_DIR/
# حذف الملفات غير الضرورية
cd $TEMP_DIR
rm -rf .git .gitignore deploy.sh
rm -rf node_modules vendor/*/test vendor/*/tests
find . -name "*.log" -type f -delete
find . -name "*.tmp" -type f -delete
find . -name "*.cache" -type f -delete
# ضبط صلاحيات الملفات
find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 755 {} \;
# تغيير ملف الإعدادات
mv includes/config.production.php includes/config.php
# رفع الملفات (قم بإزالة التعليق وأدخل معلومات الخادم)
# echo "Uploading files..."
# rsync -avz --delete --exclude '.git' --exclude 'deploy.sh' ./ $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH
# تنظيف
cd ..
rm -rf $TEMP_DIR
echo "Deployment preparation completed!"
echo "Please review the files and upload them to your hosting using FTP or your hosting control panel."
echo "Don't forget to:"
echo "1. Create the database on your hosting"
echo "2. Import the database backup (database/backup.sql)"
echo "3. Update includes/config.php with your hosting database credentials"
echo "4. Set up proper file permissions on your hosting"
echo "5. Test the website after deployment"

112
faq.html Normal file
View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>الأسئلة الشائعة - ShubraVeil</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- Header -->
<header>
<nav class="navbar">
<div class="container">
<a href="index.html" class="logo">
<img src="images/logo.jpg" alt="ShubraVeil">
</a>
<ul class="nav-links">
<li><a href="index.html">الرئيسية</a></li>
<li><a href="index.html#about">من نحن</a></li>
<li><a href="index.html#products">منتجاتنا</a></li>
<li><a href="index.html#contact">تواصل معنا</a></li>
</ul>
</div>
</nav>
</header>
<main>
<!-- قسم الأسئلة الشائعة -->
<section class="faq-section">
<div class="container">
<h1 class="section-title">الأسئلة الشائعة</h1>
<div class="faq-container">
<div class="faq-item">
<div class="faq-question">
<h3>ما هي الزيوت العطرية؟</h3>
<i class="fas fa-chevron-down"></i>
</div>
<div class="faq-answer">
<p>الزيوت العطرية هي مركبات نباتية مركزة تستخرج من النباتات والأعشاب. تتميز برائحتها القوية وفوائدها العلاجية المتعددة.</p>
</div>
</div>
<div class="faq-item">
<div class="faq-question">
<h3>كيف يمكنني استخدام الزيوت العطرية بأمان؟</h3>
<i class="fas fa-chevron-down"></i>
</div>
<div class="faq-answer">
<p>يجب تخفيف معظم الزيوت العطرية قبل الاستخدام المباشر على الجلد. استخدم زيت حامل مثل زيت جوز الهند أو زيت اللوز. قم دائماً باختبار الحساسية قبل الاستخدام الكامل.</p>
</div>
</div>
<div class="faq-item">
<div class="faq-question">
<h3>ما هي مدة صلاحية الزيوت العطرية؟</h3>
<i class="fas fa-chevron-down"></i>
</div>
<div class="faq-answer">
<p>تختلف مدة الصلاحية حسب نوع الزيت، لكن معظم الزيوت العطرية تدوم من سنة إلى سنتين عند تخزينها بشكل صحيح في مكان بارد ومظلم.</p>
</div>
</div>
<div class="faq-item">
<div class="faq-question">
<h3>هل منتجاتكم طبيعية 100%؟</h3>
<i class="fas fa-chevron-down"></i>
</div>
<div class="faq-answer">
<p>نعم، جميع منتجاتنا طبيعية 100% ومستخلصة من مصادر عضوية موثوقة. نحن نضمن جودة منتجاتنا ونقدم شهادات الجودة عند الطلب.</p>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer>
<div class="container">
<div class="footer-content">
<div class="footer-about">
<img src="images/logo.jpg" alt="ShubraVeil" class="footer-logo">
<p>نقدم أجود أنواع الزيوت العطرية الطبيعية المستخلصة من أراضي شبرا بلولة الخصبة</p>
</div>
<div class="footer-links">
<h4>روابط سريعة</h4>
<ul>
<li><a href="index.html">الرئيسية</a></li>
<li><a href="index.html#about">من نحن</a></li>
<li><a href="index.html#products">منتجاتنا</a></li>
<li><a href="index.html#contact">تواصل معنا</a></li>
</ul>
</div>
<div class="footer-social">
<h4>تابعنا</h4>
<div class="social-links">
<a href="#"><i class="fab fa-facebook"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-twitter"></i></a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>جميع الحقوق محفوظة &copy; 2024 ShubraVeil</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

103
forgot-password.php Normal file
View File

@ -0,0 +1,103 @@
<?php
require_once 'includes/config.php';
require_once 'includes/Security.php';
session_start();
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$email = Security::sanitizeInput($_POST['email']);
if (empty($email)) {
throw new Exception('يرجى إدخال البريد الإلكتروني');
}
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception('فشل الاتصال بقاعدة البيانات');
}
// التحقق من وجود البريد الإلكتروني
$stmt = $conn->prepare("SELECT id, username FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
throw new Exception('البريد الإلكتروني غير مسجل في النظام');
}
$user = $result->fetch_assoc();
// إنشاء رمز إعادة تعيين كلمة المرور
$token = bin2hex(random_bytes(32));
$expiry = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $conn->prepare("INSERT INTO password_resets (user_id, token, expiry) VALUES (?, ?, ?)");
$stmt->bind_param("iss", $user['id'], $token, $expiry);
$stmt->execute();
// إرسال رابط إعادة تعيين كلمة المرور
$reset_link = "https://" . $_SERVER['HTTP_HOST'] . "/reset-password.php?token=" . $token;
// TODO: استخدام نظام البريد الإلكتروني لإرسال الرابط
$success = 'تم إرسال رابط إعادة تعيين كلمة المرور إلى بريدك الإلكتروني';
} catch (Exception $e) {
$error = $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>نسيت كلمة المرور - ShubraVeil</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card shadow">
<div class="card-body">
<h2 class="text-center mb-4">استعادة كلمة المرور</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?php echo $success; ?></div>
<?php endif; ?>
<form method="POST" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
<div class="mb-3">
<label for="email" class="form-label">البريد الإلكتروني</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">إرسال رابط إعادة التعيين</button>
</div>
</form>
<div class="text-center mt-3">
<p><a href="login.php">العودة إلى تسجيل الدخول</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

BIN
images/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

BIN
images/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

1
images/hero-bg.jpg Normal file
View File

@ -0,0 +1 @@
The image from the first slide showing a person in green holding an essential oil bottle against a natural background would make an excellent hero background image. However, I cannot directly create or manipulate image files. You'll need to save that image as 'hero-bg.jpg' in the images directory.

BIN
images/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
images/patrin .jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
images/products 2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

BIN
images/proudctus.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

76
includes/Cache.php Normal file
View File

@ -0,0 +1,76 @@
<?php
class Cache {
private static $instance = null;
private $cache_path;
private $duration;
private function __construct() {
$this->cache_path = CACHE_PATH;
$this->duration = CACHE_DURATION;
if (!file_exists($this->cache_path)) {
mkdir($this->cache_path, 0755, true);
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function set($key, $data) {
if (!CACHE_ENABLED) return false;
$cache_file = $this->getCacheFile($key);
$cache_data = [
'expires' => time() + $this->duration,
'data' => $data
];
return file_put_contents($cache_file, serialize($cache_data)) !== false;
}
public function get($key) {
if (!CACHE_ENABLED) return false;
$cache_file = $this->getCacheFile($key);
if (!file_exists($cache_file)) {
return false;
}
$cache_data = unserialize(file_get_contents($cache_file));
if ($cache_data['expires'] < time()) {
unlink($cache_file);
return false;
}
return $cache_data['data'];
}
public function delete($key) {
$cache_file = $this->getCacheFile($key);
if (file_exists($cache_file)) {
return unlink($cache_file);
}
return true;
}
public function clear() {
$files = glob($this->cache_path . '/*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
return true;
}
private function getCacheFile($key) {
return $this->cache_path . '/' . md5($key) . '.cache';
}
}

115
includes/ImageHandler.php Normal file
View File

@ -0,0 +1,115 @@
<?php
class ImageHandler {
private $upload_path;
private $allowed_types;
private $max_size;
public function __construct() {
$this->upload_path = UPLOAD_PATH;
$this->allowed_types = ALLOWED_IMAGE_TYPES;
$this->max_size = MAX_IMAGE_SIZE;
if (!file_exists($this->upload_path)) {
mkdir($this->upload_path, 0755, true);
}
}
public function upload($file, $optimize = true) {
if (!isset($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
throw new Exception('No file uploaded');
}
// Validate file
$this->validateFile($file);
// Generate unique filename
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = uniqid() . '_' . time() . '.' . $extension;
$filepath = $this->upload_path . '/' . $filename;
// Move and optimize
if (move_uploaded_file($file['tmp_name'], $filepath)) {
if ($optimize) {
$this->optimizeImage($filepath);
}
return $filename;
}
throw new Exception('Failed to move uploaded file');
}
private function validateFile($file) {
// Check file size
if ($file['size'] > $this->max_size) {
throw new Exception('File size exceeds limit');
}
// Check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime_type, $this->allowed_types)) {
throw new Exception('Invalid file type');
}
}
private function optimizeImage($filepath) {
$image = null;
$mime = mime_content_type($filepath);
switch ($mime) {
case 'image/jpeg':
$image = imagecreatefromjpeg($filepath);
imagejpeg($image, $filepath, 85); // 85% quality
break;
case 'image/png':
$image = imagecreatefrompng($filepath);
imagepng($image, $filepath, 8); // Compression level 8
break;
case 'image/webp':
$image = imagecreatefromwebp($filepath);
imagewebp($image, $filepath, 85);
break;
}
if ($image) {
imagedestroy($image);
}
}
public function createThumbnail($filename, $width = 200, $height = 200) {
$source_path = $this->upload_path . '/' . $filename;
$thumb_path = $this->upload_path . '/thumbnails/' . $filename;
if (!file_exists(dirname($thumb_path))) {
mkdir(dirname($thumb_path), 0755, true);
}
$source_image = imagecreatefromstring(file_get_contents($source_path));
$source_width = imagesx($source_image);
$source_height = imagesy($source_image);
$thumb = imagecreatetruecolor($width, $height);
imagecopyresampled($thumb, $source_image, 0, 0, 0, 0, $width, $height, $source_width, $source_height);
$mime = mime_content_type($source_path);
switch ($mime) {
case 'image/jpeg':
imagejpeg($thumb, $thumb_path, 85);
break;
case 'image/png':
imagepng($thumb, $thumb_path, 8);
break;
case 'image/webp':
imagewebp($thumb, $thumb_path, 85);
break;
}
imagedestroy($source_image);
imagedestroy($thumb);
return 'thumbnails/' . $filename;
}
}

View File

@ -0,0 +1,84 @@
<?php
use Intervention\Image\ImageManager;
class ImageProcessor {
private $manager;
private $uploadPath;
public function __construct() {
$this->manager = new ImageManager(['driver' => 'gd']);
$this->uploadPath = __DIR__ . '/../uploads';
}
public function process($file, $type = 'product', $width = 800, $height = null) {
// Validate file
if (!isset($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
throw new Exception('No file uploaded');
}
// Create type directory if it doesn't exist
$targetDir = $this->uploadPath . '/' . $type;
if (!file_exists($targetDir)) {
mkdir($targetDir, 0755, true);
}
// Generate unique filename
$filename = uniqid() . '_' . time() . '.' . pathinfo($file['name'], PATHINFO_EXTENSION);
$targetPath = $targetDir . '/' . $filename;
// Process image
$image = $this->manager->make($file['tmp_name']);
// Resize image
if ($height) {
$image->fit($width, $height, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
} else {
$image->resize($width, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
// Optimize and save
$image->save($targetPath, 85);
// Create thumbnail
$this->createThumbnail($image, $type, $filename);
return [
'filename' => $filename,
'path' => $type . '/' . $filename,
'thumbnail' => $type . '/thumbnails/' . $filename
];
}
private function createThumbnail($image, $type, $filename) {
$thumbDir = $this->uploadPath . '/' . $type . '/thumbnails';
if (!file_exists($thumbDir)) {
mkdir($thumbDir, 0755, true);
}
$image->fit(200, 200, function ($constraint) {
$constraint->aspectRatio();
})->save($thumbDir . '/' . $filename, 85);
}
public function delete($path) {
$fullPath = $this->uploadPath . '/' . $path;
if (file_exists($fullPath)) {
unlink($fullPath);
// Delete thumbnail if exists
$thumbPath = dirname($fullPath) . '/thumbnails/' . basename($fullPath);
if (file_exists($thumbPath)) {
unlink($thumbPath);
}
return true;
}
return false;
}
}

72
includes/Logger.php Normal file
View File

@ -0,0 +1,72 @@
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;
class AppLogger {
private static $instance = null;
private $logger;
private function __construct() {
$this->logger = new Logger('shubraveil');
// Create logs directory if it doesn't exist
if (!file_exists(__DIR__ . '/../logs')) {
mkdir(__DIR__ . '/../logs', 0755, true);
}
// Add handlers
$this->addFileHandler();
if (getenv('DEBUG_MODE') === 'true') {
$this->addDebugHandler();
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function addFileHandler() {
$formatter = new LineFormatter(
"[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
"Y-m-d H:i:s"
);
$handler = new RotatingFileHandler(
__DIR__ . '/../logs/app.log',
30, // Keep last 30 days of logs
Logger::INFO
);
$handler->setFormatter($formatter);
$this->logger->pushHandler($handler);
}
private function addDebugHandler() {
$handler = new StreamHandler(
__DIR__ . '/../logs/debug.log',
Logger::DEBUG
);
$this->logger->pushHandler($handler);
}
public function info($message, array $context = []) {
$this->logger->info($message, $context);
}
public function error($message, array $context = []) {
$this->logger->error($message, $context);
}
public function debug($message, array $context = []) {
$this->logger->debug($message, $context);
}
public function warning($message, array $context = []) {
$this->logger->warning($message, $context);
}
}

92
includes/Mailer.php Normal file
View File

@ -0,0 +1,92 @@
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
class Mailer {
private static $instance = null;
private $mailer;
private $logger;
private function __construct() {
$this->mailer = new PHPMailer(true);
$this->logger = AppLogger::getInstance();
// Server settings
$this->mailer->isSMTP();
$this->mailer->Host = SMTP_HOST;
$this->mailer->SMTPAuth = true;
$this->mailer->Username = SMTP_USERNAME;
$this->mailer->Password = SMTP_PASSWORD;
$this->mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$this->mailer->Port = SMTP_PORT;
$this->mailer->CharSet = 'UTF-8';
// Default sender
$this->mailer->setFrom(SMTP_FROM_EMAIL, SMTP_FROM_NAME);
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function sendOrderConfirmation($order, $user) {
try {
$this->mailer->addAddress($user['email'], $user['name']);
$this->mailer->Subject = 'تأكيد الطلب - ShubraVeil';
// Load email template
$template = file_get_contents(__DIR__ . '/../templates/emails/order-confirmation.html');
// Replace placeholders
$template = str_replace('{ORDER_ID}', $order['id'], $template);
$template = str_replace('{USER_NAME}', $user['name'], $template);
$template = str_replace('{ORDER_TOTAL}', $order['total'], $template);
$this->mailer->isHTML(true);
$this->mailer->Body = $template;
$this->mailer->send();
$this->logger->info('Order confirmation email sent', ['order_id' => $order['id']]);
return true;
} catch (Exception $e) {
$this->logger->error('Failed to send order confirmation email', [
'error' => $e->getMessage(),
'order_id' => $order['id']
]);
return false;
}
}
public function sendPasswordReset($user, $token) {
try {
$this->mailer->addAddress($user['email'], $user['name']);
$this->mailer->Subject = 'إعادة تعيين كلمة المرور - ShubraVeil';
$resetLink = SITE_URL . '/reset-password?token=' . $token;
// Load email template
$template = file_get_contents(__DIR__ . '/../templates/emails/password-reset.html');
// Replace placeholders
$template = str_replace('{USER_NAME}', $user['name'], $template);
$template = str_replace('{RESET_LINK}', $resetLink, $template);
$this->mailer->isHTML(true);
$this->mailer->Body = $template;
$this->mailer->send();
$this->logger->info('Password reset email sent', ['user_id' => $user['id']]);
return true;
} catch (Exception $e) {
$this->logger->error('Failed to send password reset email', [
'error' => $e->getMessage(),
'user_id' => $user['id']
]);
return false;
}
}
}

116
includes/Security.php Normal file
View File

@ -0,0 +1,116 @@
<?php
class Security {
private static $instance = null;
private $rate_limit_file;
private static $encryptionKey = 'your-secure-encryption-key-here';
private function __construct() {
$this->rate_limit_file = __DIR__ . '/../cache/rate_limits.json';
if (!file_exists($this->rate_limit_file)) {
if (!is_dir(dirname($this->rate_limit_file))) {
mkdir(dirname($this->rate_limit_file), 0755, true);
}
file_put_contents($this->rate_limit_file, '{}');
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public static function sanitizeInput($data) {
if (is_array($data)) {
return array_map([self::class, 'sanitizeInput'], $data);
}
return htmlspecialchars(strip_tags(trim($data)), ENT_QUOTES, 'UTF-8');
}
public static function validateRecaptcha($response) {
$url = 'https://www.google.com/recaptcha/api/siteverify';
$data = [
'secret' => RECAPTCHA_SECRET_KEY,
'response' => $response
];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
$result_json = json_decode($result, true);
return isset($result_json['success']) && $result_json['success'] === true;
}
public function checkRateLimit($ip, $endpoint, $limit = 60, $window = 3600) {
$limits = json_decode(file_get_contents($this->rate_limit_file), true);
$now = time();
$key = $ip . ':' . $endpoint;
if (!isset($limits[$key])) {
$limits[$key] = ['count' => 0, 'window_start' => $now];
}
if ($now - $limits[$key]['window_start'] > $window) {
$limits[$key] = ['count' => 0, 'window_start' => $now];
}
$limits[$key]['count']++;
file_put_contents($this->rate_limit_file, json_encode($limits));
return $limits[$key]['count'] <= $limit;
}
public static function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function verifyCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
public static function encrypt($data) {
$iv = random_bytes(16);
$encrypted = openssl_encrypt(
$data,
'AES-256-CBC',
self::$encryptionKey,
0,
$iv
);
return base64_encode($iv . $encrypted);
}
public static function decrypt($data) {
$data = base64_decode($data);
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
return openssl_decrypt(
$encrypted,
'AES-256-CBC',
self::$encryptionKey,
0,
$iv
);
}
public static function hashPassword($password) {
return password_hash($password, PASSWORD_DEFAULT);
}
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
}

120
includes/auth.php Normal file
View File

@ -0,0 +1,120 @@
<?php
require_once 'config.php';
class Auth {
private $conn;
public function __construct($conn) {
$this->conn = $conn;
}
public function register($data) {
$username = sanitize_input($data['username']);
$email = sanitize_input($data['email']);
$password = password_hash($data['password'], PASSWORD_DEFAULT);
$full_name = sanitize_input($data['full_name']);
$phone = sanitize_input($data['phone']);
// Check if email exists
$stmt = $this->conn->prepare("SELECT id FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
if ($stmt->get_result()->num_rows > 0) {
return ['success' => false, 'message' => 'البريد الإلكتروني مسجل مسبقاً'];
}
// Insert new user
$stmt = $this->conn->prepare("INSERT INTO users (username, email, password, full_name, phone) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("sssss", $username, $email, $password, $full_name, $phone);
if ($stmt->execute()) {
$user_id = $stmt->insert_id;
$this->login(['email' => $email, 'password' => $data['password']]);
return ['success' => true, 'message' => 'تم التسجيل بنجاح'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء التسجيل'];
}
public function login($data) {
$email = sanitize_input($data['email']);
$password = $data['password'];
$stmt = $this->conn->prepare("SELECT id, username, password, role FROM users WHERE email = ? AND is_active = 1");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 1) {
$user = $result->fetch_assoc();
if (password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['user_role'] = $user['role'];
return ['success' => true, 'message' => 'تم تسجيل الدخول بنجاح'];
}
}
return ['success' => false, 'message' => 'البريد الإلكتروني أو كلمة المرور غير صحيحة'];
}
public function logout() {
session_destroy();
return ['success' => true, 'message' => 'تم تسجيل الخروج بنجاح'];
}
public function resetPassword($email) {
$email = sanitize_input($email);
$token = bin2hex(random_bytes(32));
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $this->conn->prepare("UPDATE users SET reset_token = ?, reset_expires = ? WHERE email = ?");
$stmt->bind_param("sss", $token, $expires, $email);
if ($stmt->execute()) {
// Send reset email
$reset_link = SITE_URL . "/reset-password.php?token=" . $token;
// TODO: Implement email sending
return ['success' => true, 'message' => 'تم إرسال رابط إعادة تعيين كلمة المرور إلى بريدك الإلكتروني'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء إعادة تعيين كلمة المرور'];
}
public function updateProfile($user_id, $data) {
$full_name = sanitize_input($data['full_name']);
$phone = sanitize_input($data['phone']);
$stmt = $this->conn->prepare("UPDATE users SET full_name = ?, phone = ? WHERE id = ?");
$stmt->bind_param("ssi", $full_name, $phone, $user_id);
if ($stmt->execute()) {
return ['success' => true, 'message' => 'تم تحديث الملف الشخصي بنجاح'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء تحديث الملف الشخصي'];
}
public function changePassword($user_id, $old_password, $new_password) {
$stmt = $this->conn->prepare("SELECT password FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 1) {
$user = $result->fetch_assoc();
if (password_verify($old_password, $user['password'])) {
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $this->conn->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hashed_password, $user_id);
if ($stmt->execute()) {
return ['success' => true, 'message' => 'تم تغيير كلمة المرور بنجاح'];
}
}
}
return ['success' => false, 'message' => 'كلمة المرور الحالية غير صحيحة'];
}
}

214
includes/cart.php Normal file
View File

@ -0,0 +1,214 @@
<?php
require_once 'config.php';
class Cart {
private $conn;
public function __construct($conn) {
$this->conn = $conn;
}
public function addToCart($product_id, $quantity = 1) {
if (!is_logged_in()) {
return ['success' => false, 'message' => 'يجب تسجيل الدخول لإضافة منتجات إلى السلة'];
}
// Check if product exists and has enough stock
$stmt = $this->conn->prepare("SELECT stock_quantity FROM products WHERE id = ?");
$stmt->bind_param("i", $product_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return ['success' => false, 'message' => 'المنتج غير موجود'];
}
$product = $result->fetch_assoc();
if ($product['stock_quantity'] < $quantity) {
return ['success' => false, 'message' => 'الكمية المطلوبة غير متوفرة'];
}
// Check if product already in cart
$stmt = $this->conn->prepare("SELECT id, quantity FROM shopping_cart WHERE user_id = ? AND product_id = ?");
$stmt->bind_param("ii", $_SESSION['user_id'], $product_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
$cart_item = $result->fetch_assoc();
$new_quantity = $cart_item['quantity'] + $quantity;
if ($new_quantity > $product['stock_quantity']) {
return ['success' => false, 'message' => 'الكمية المطلوبة تتجاوز المخزون المتوفر'];
}
$stmt = $this->conn->prepare("UPDATE shopping_cart SET quantity = ? WHERE id = ?");
$stmt->bind_param("ii", $new_quantity, $cart_item['id']);
} else {
$stmt = $this->conn->prepare("INSERT INTO shopping_cart (user_id, product_id, quantity) VALUES (?, ?, ?)");
$stmt->bind_param("iii", $_SESSION['user_id'], $product_id, $quantity);
}
if ($stmt->execute()) {
return ['success' => true, 'message' => 'تمت إضافة المنتج إلى السلة'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء إضافة المنتج إلى السلة'];
}
public function updateCartItem($cart_id, $quantity) {
if (!is_logged_in()) {
return ['success' => false, 'message' => 'يجب تسجيل الدخول لتحديث السلة'];
}
// Check if cart item exists and belongs to user
$stmt = $this->conn->prepare("
SELECT c.*, p.stock_quantity
FROM shopping_cart c
JOIN products p ON c.product_id = p.id
WHERE c.id = ? AND c.user_id = ?
");
$stmt->bind_param("ii", $cart_id, $_SESSION['user_id']);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return ['success' => false, 'message' => 'المنتج غير موجود في السلة'];
}
$cart_item = $result->fetch_assoc();
if ($quantity > $cart_item['stock_quantity']) {
return ['success' => false, 'message' => 'الكمية المطلوبة غير متوفرة'];
}
if ($quantity <= 0) {
return $this->removeFromCart($cart_id);
}
$stmt = $this->conn->prepare("UPDATE shopping_cart SET quantity = ? WHERE id = ? AND user_id = ?");
$stmt->bind_param("iii", $quantity, $cart_id, $_SESSION['user_id']);
if ($stmt->execute()) {
return ['success' => true, 'message' => 'تم تحديث الكمية'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء تحديث الكمية'];
}
public function removeFromCart($cart_id) {
if (!is_logged_in()) {
return ['success' => false, 'message' => 'يجب تسجيل الدخول لحذف منتجات من السلة'];
}
$stmt = $this->conn->prepare("DELETE FROM shopping_cart WHERE id = ? AND user_id = ?");
$stmt->bind_param("ii", $cart_id, $_SESSION['user_id']);
if ($stmt->execute()) {
return ['success' => true, 'message' => 'تم حذف المنتج من السلة'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء حذف المنتج من السلة'];
}
public function getCart() {
if (!is_logged_in()) {
return ['items' => [], 'total' => 0];
}
$query = "
SELECT c.id as cart_id, c.quantity,
p.id as product_id, p.name, p.name_ar, p.price, p.sale_price,
(SELECT image_url FROM product_images WHERE product_id = p.id AND is_primary = 1 LIMIT 1) as image
FROM shopping_cart c
JOIN products p ON c.product_id = p.id
WHERE c.user_id = ?
ORDER BY c.created_at DESC
";
$stmt = $this->conn->prepare($query);
$stmt->bind_param("i", $_SESSION['user_id']);
$stmt->execute();
$result = $stmt->get_result();
$items = [];
$total = 0;
while ($row = $result->fetch_assoc()) {
$price = $row['sale_price'] ?? $row['price'];
$subtotal = $price * $row['quantity'];
$total += $subtotal;
$row['price'] = $price;
$row['subtotal'] = $subtotal;
$items[] = $row;
}
return [
'items' => $items,
'total' => $total
];
}
public function clearCart() {
if (!is_logged_in()) {
return ['success' => false, 'message' => 'يجب تسجيل الدخول لتفريغ السلة'];
}
$stmt = $this->conn->prepare("DELETE FROM shopping_cart WHERE user_id = ?");
$stmt->bind_param("i", $_SESSION['user_id']);
if ($stmt->execute()) {
return ['success' => true, 'message' => 'تم تفريغ السلة'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء تفريغ السلة'];
}
public function applyCoupon($code) {
if (!is_logged_in()) {
return ['success' => false, 'message' => 'يجب تسجيل الدخول لاستخدام الكوبون'];
}
$stmt = $this->conn->prepare("
SELECT * FROM coupons
WHERE code = ?
AND is_active = 1
AND (start_date IS NULL OR start_date <= NOW())
AND (end_date IS NULL OR end_date >= NOW())
AND (usage_limit IS NULL OR used_count < usage_limit)
");
$stmt->bind_param("s", $code);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return ['success' => false, 'message' => 'الكوبون غير صالح'];
}
$coupon = $result->fetch_assoc();
$cart = $this->getCart();
if ($coupon['min_purchase'] && $cart['total'] < $coupon['min_purchase']) {
return ['success' => false, 'message' => sprintf('الحد الأدنى للطلب %s ريال', $coupon['min_purchase'])];
}
$discount = $coupon['type'] === 'percentage'
? $cart['total'] * ($coupon['value'] / 100)
: $coupon['value'];
if ($coupon['max_discount'] && $discount > $coupon['max_discount']) {
$discount = $coupon['max_discount'];
}
$_SESSION['coupon'] = [
'code' => $coupon['code'],
'discount' => $discount
];
return [
'success' => true,
'message' => 'تم تطبيق الكوبون',
'discount' => $discount,
'total_after_discount' => $cart['total'] - $discount
];
}
}

103
includes/config.php Normal file
View File

@ -0,0 +1,103 @@
<?php
session_start();
// Error reporting
if (getenv('DEBUG_MODE') === 'true') {
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
} else {
ini_set('display_errors', 0);
error_reporting(0);
}
// Load environment variables
$env = parse_ini_file(__DIR__ . '/../.env');
// Database configuration
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'momaher');
define('DB_PASSWORD', 'Mohamed@9498#');
define('DB_NAME', 'shubraveil_db');
// Site configuration
define('SITE_NAME', 'ShubraVeil');
define('SITE_URL', $env['SITE_URL']);
define('UPLOAD_PATH', __DIR__ . '/../uploads');
define('ALLOWED_IMAGE_TYPES', ['image/jpeg', 'image/png', 'image/webp']);
define('MAX_IMAGE_SIZE', 5 * 1024 * 1024); // 5MB
// Email configuration
define('SMTP_HOST', $env['SMTP_HOST']);
define('SMTP_PORT', $env['SMTP_PORT']);
define('SMTP_USERNAME', $env['SMTP_USERNAME']);
define('SMTP_PASSWORD', $env['SMTP_PASSWORD']);
define('SMTP_FROM_EMAIL', $env['SMTP_FROM_EMAIL']);
define('SMTP_FROM_NAME', $env['SMTP_FROM_NAME']);
// Security configuration
define('JWT_SECRET', $env['JWT_SECRET']);
define('RECAPTCHA_SITE_KEY', $env['RECAPTCHA_SITE_KEY']);
define('RECAPTCHA_SECRET_KEY', $env['RECAPTCHA_SECRET_KEY']);
// Cache configuration
define('CACHE_ENABLED', true);
define('CACHE_PATH', __DIR__ . '/../cache');
define('CACHE_DURATION', 3600); // 1 hour
// Time zone
date_default_timezone_set('Africa/Cairo');
// Functions
function sanitize_input($data) {
global $conn;
return mysqli_real_escape_string($conn, htmlspecialchars(trim($data)));
}
function generate_csrf_token() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function verify_csrf_token($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
function is_logged_in() {
return isset($_SESSION['user_id']);
}
function is_admin() {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin';
}
function redirect($path) {
header("Location: " . SITE_URL . $path);
exit();
}
function flash_message($type, $message) {
$_SESSION['flash'] = [
'type' => $type,
'message' => $message
];
}
function get_flash_message() {
if (isset($_SESSION['flash'])) {
$flash = $_SESSION['flash'];
unset($_SESSION['flash']);
return $flash;
}
return null;
}
// API Response helper
function json_response($data, $status = 200) {
header('Content-Type: application/json');
http_response_code($status);
echo json_encode($data);
exit();
}

View File

@ -0,0 +1,34 @@
<?php
// إعدادات قاعدة البيانات للاستضافة
define('DB_SERVER', 'localhost'); // عادة ما يكون localhost في Hostinger
define('DB_USERNAME', ''); // أدخل اسم المستخدم من Hostinger
define('DB_PASSWORD', ''); // أدخل كلمة المرور من Hostinger
define('DB_NAME', ''); // أدخل اسم قاعدة البيانات من Hostinger
// إعدادات الموقع
define('SITE_URL', 'https://yourdomain.com'); // أدخل نطاق موقعك
define('SITE_NAME', 'ShubraVeil');
define('ADMIN_EMAIL', ''); // بريدك الإلكتروني
// إعدادات الأمان
define('SECURE_SESSION', true);
define('SESSION_LIFETIME', 3600);
define('CSRF_TOKEN_SECRET', bin2hex(random_bytes(32))); // مفتاح عشوائي آمن
// إعدادات التحميل
define('UPLOAD_PATH', __DIR__ . '/../uploads/');
define('MAX_FILE_SIZE', 5242880); // 5 ميجابايت
define('ALLOWED_FILE_TYPES', ['jpg', 'jpeg', 'png', 'gif']);
// إعدادات النسخ الاحتياطي
define('BACKUP_PATH', __DIR__ . '/../backups/');
define('MAX_BACKUP_FILES', 5);
// تكوين التاريخ والوقت
date_default_timezone_set('Africa/Cairo');
// معالجة الأخطاء
error_reporting(0);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/../logs/error.log');

173
includes/products.php Normal file
View File

@ -0,0 +1,173 @@
<?php
require_once 'config.php';
class Products {
private $conn;
public function __construct($conn) {
$this->conn = $conn;
}
public function getProducts($filters = [], $page = 1, $limit = 12) {
$offset = ($page - 1) * $limit;
$where = [];
$params = [];
$types = "";
if (!empty($filters['category'])) {
$where[] = "category_id = ?";
$params[] = $filters['category'];
$types .= "i";
}
if (!empty($filters['search'])) {
$search = "%" . $filters['search'] . "%";
$where[] = "(name LIKE ? OR name_ar LIKE ? OR description LIKE ? OR description_ar LIKE ?)";
$params = array_merge($params, [$search, $search, $search, $search]);
$types .= "ssss";
}
if (!empty($filters['min_price'])) {
$where[] = "price >= ?";
$params[] = $filters['min_price'];
$types .= "d";
}
if (!empty($filters['max_price'])) {
$where[] = "price <= ?";
$params[] = $filters['max_price'];
$types .= "d";
}
$whereClause = !empty($where) ? "WHERE " . implode(" AND ", $where) : "";
// Get total count
$countQuery = "SELECT COUNT(*) as total FROM products $whereClause";
$stmt = $this->conn->prepare($countQuery);
if (!empty($params)) {
$stmt->bind_param($types, ...$params);
}
$stmt->execute();
$total = $stmt->get_result()->fetch_assoc()['total'];
// Get products
$query = "SELECT p.*,
c.name as category_name,
c.name_ar as category_name_ar,
(SELECT image_url FROM product_images WHERE product_id = p.id AND is_primary = 1 LIMIT 1) as primary_image,
(SELECT AVG(rating) FROM reviews WHERE product_id = p.id) as average_rating,
(SELECT COUNT(*) FROM reviews WHERE product_id = p.id) as review_count
FROM products p
LEFT JOIN categories c ON p.category_id = c.id
$whereClause
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?";
$stmt = $this->conn->prepare($query);
$params[] = $limit;
$params[] = $offset;
$types .= "ii";
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
$products = [];
while ($row = $result->fetch_assoc()) {
$products[] = $row;
}
return [
'products' => $products,
'total' => $total,
'pages' => ceil($total / $limit),
'current_page' => $page
];
}
public function getProduct($id) {
$query = "SELECT p.*,
c.name as category_name,
c.name_ar as category_name_ar,
(SELECT AVG(rating) FROM reviews WHERE product_id = p.id) as average_rating,
(SELECT COUNT(*) FROM reviews WHERE product_id = p.id) as review_count
FROM products p
LEFT JOIN categories c ON p.category_id = c.id
WHERE p.id = ?";
$stmt = $this->conn->prepare($query);
$stmt->bind_param("i", $id);
$stmt->execute();
$product = $stmt->get_result()->fetch_assoc();
if ($product) {
// Get images
$stmt = $this->conn->prepare("SELECT * FROM product_images WHERE product_id = ? ORDER BY is_primary DESC, sort_order ASC");
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
$product['images'] = [];
while ($row = $result->fetch_assoc()) {
$product['images'][] = $row;
}
// Get reviews
$stmt = $this->conn->prepare("
SELECT r.*, u.username, u.full_name
FROM reviews r
LEFT JOIN users u ON r.user_id = u.id
WHERE r.product_id = ?
ORDER BY r.created_at DESC
");
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
$product['reviews'] = [];
while ($row = $result->fetch_assoc()) {
$product['reviews'][] = $row;
}
// Get related products
$stmt = $this->conn->prepare("
SELECT p.*,
(SELECT image_url FROM product_images WHERE product_id = p.id AND is_primary = 1 LIMIT 1) as primary_image
FROM products p
WHERE p.category_id = ? AND p.id != ?
LIMIT 4
");
$stmt->bind_param("ii", $product['category_id'], $id);
$stmt->execute();
$result = $stmt->get_result();
$product['related_products'] = [];
while ($row = $result->fetch_assoc()) {
$product['related_products'][] = $row;
}
}
return $product;
}
public function addReview($data) {
if (!is_logged_in()) {
return ['success' => false, 'message' => 'يجب تسجيل الدخول لإضافة تقييم'];
}
$stmt = $this->conn->prepare("INSERT INTO reviews (product_id, user_id, rating, comment) VALUES (?, ?, ?, ?)");
$stmt->bind_param("iiis", $data['product_id'], $_SESSION['user_id'], $data['rating'], $data['comment']);
if ($stmt->execute()) {
return ['success' => true, 'message' => 'تم إضافة التقييم بنجاح'];
}
return ['success' => false, 'message' => 'حدث خطأ أثناء إضافة التقييم'];
}
public function getCategories() {
$query = "SELECT * FROM categories ORDER BY parent_id ASC, name ASC";
$result = $this->conn->query($query);
$categories = [];
while ($row = $result->fetch_assoc()) {
$categories[] = $row;
}
return $categories;
}
}

570
index.html Normal file
View File

@ -0,0 +1,570 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ShubraVeil - زيوت طبيعية عطرية</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<header>
<nav class="navbar">
<div class="container">
<div class="logo">
<a href="/" class="brand-name">
<span class="brand-first">Veil</span>
<span class="brand-second">Shubra</span>
</a>
</div>
<div class="header-search">
<div class="search-container">
<input type="text" class="search-input" placeholder="ابحث عن منتجاتنا الطبيعية...">
<i class="fas fa-search search-icon"></i>
<button class="voice-search-btn" title="البحث الصوتي">
<i class="fas fa-microphone"></i>
</button>
<div class="search-results">
<div class="search-filters">
<div class="filter-group">
<h3>الفئات</h3>
<div class="category-filters">
<label class="category-filter">
<input type="checkbox" name="category" value="essential-oils">
زيوت عطرية
</label>
<label class="category-filter">
<input type="checkbox" name="category" value="fixed-oils">
زيوت ثابتة
</label>
<label class="category-filter">
<input type="checkbox" name="category" value="hydrosols">
ماء الزهر
</label>
<label class="category-filter">
<input type="checkbox" name="category" value="natural-cosmetics">
مستحضرات تجميل طبيعية
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<nav class="nav-links">
<a href="/" class="active">الرئيسية</a>
<a href="/about">من نحن</a>
<div class="dropdown">
<a href="/products" class="dropdown-toggle">منتجاتنا</a>
<ul class="dropdown-menu">
<li><a href="/products/essential-oils">الزيوت العطرية</a></li>
<li><a href="/products/fixed-oils">الزيوت الثابتة</a></li>
<li><a href="/products/hydrosols">ماء الزهر</a></li>
<li><a href="/products/natural-cosmetics">مستحضرات تجميل طبيعية</a></li>
</ul>
</div>
<div class="dropdown">
<a href="/benefits" class="dropdown-toggle">فوائد الزيوت</a>
<ul class="dropdown-menu">
<li><a href="/benefits/skin">العناية بالبشرة</a></li>
<li><a href="/benefits/hair">العناية بالشعر</a></li>
<li><a href="/benefits/health">الفوائد الصحية</a></li>
<li><a href="/benefits/aromatherapy">العلاج بالعطور</a></li>
</ul>
</div>
<a href="/contact">تواصل معنا</a>
</nav>
<div class="nav-actions">
<div class="user-account">
<a href="/account" class="account-btn">
<i class="fas fa-user"></i>
<span class="btn-text">حسابي</span>
</a>
<div class="account-dropdown">
<a href="/login">تسجيل الدخول</a>
<a href="/register">حساب جديد</a>
<a href="/profile">الملف الشخصي</a>
<a href="/orders">طلباتي</a>
<a href="/wishlist">المفضلة</a>
</div>
</div>
<div class="shopping-cart">
<a href="/cart" class="cart-btn">
<i class="fas fa-shopping-cart"></i>
<span class="cart-count">0</span>
<span class="btn-text">السلة</span>
</a>
</div>
</div>
</div>
</nav>
</header>
<main>
<section id="hero" class="hero">
<div class="container">
<div class="hero-content">
<h1>زيوت طبيعية نقية 100%</h1>
<p>نقدم لكم أجود أنواع الزيوت العطرية المستخلصة من أراضي شبرا بلولة الخصبة</p>
<div class="hero-buttons">
<a href="#products" class="btn primary">تصفح منتجاتنا</a>
<a href="#about" class="btn secondary">تعرف علينا</a>
</div>
</div>
</div>
</section>
<section id="features" class="features">
<div class="container">
<div class="features-grid">
<div class="feature-card">
<i class="fas fa-leaf"></i>
<h3>طبيعي 100%</h3>
<p>زيوت مستخلصة بطريقة طبيعية</p>
</div>
<div class="feature-card">
<i class="fas fa-certificate"></i>
<h3>جودة عالية</h3>
<p>معتمدة من أفضل المختبرات</p>
</div>
<div class="feature-card">
<i class="fas fa-shipping-fast"></i>
<h3>شحن سريع</h3>
<p>توصيل لجميع المناطق</p>
</div>
</div>
</div>
</section>
<section id="featured" class="featured-section">
<div class="container">
<h2 class="section-title">منتجاتنا المميزة</h2>
<div class="featured-products">
<div class="product-card">
<div class="product-image">
<img src="images/products/lavender.jpg" alt="زيت اللافندر">
<span class="badge">الأكثر مبيعاً</span>
</div>
<div class="product-info">
<h3>زيت اللافندر العطري</h3>
<p>زيت عطري طبيعي 100% للاسترخاء والعناية بالبشرة</p>
<button class="btn">اطلب الآن</button>
</div>
</div>
<div class="product-card">
<div class="product-image">
<img src="images/products/rosemary.jpg" alt="زيت إكليل الجبل">
<span class="badge">جديد</span>
</div>
<div class="product-info">
<h3>زيت إكليل الجبل</h3>
<p>يساعد على تنشيط الذهن وتقوية الشعر</p>
<button class="btn">اطلب الآن</button>
</div>
</div>
</div>
</div>
</section>
<section id="special-offers" class="special-offers">
<div class="container">
<div class="section-header">
<h2>عروض خاصة</h2>
<p>اكتشف أفضل العروض والتخفيضات على منتجاتنا</p>
</div>
<div class="offers-grid">
<!-- العرض الرئيسي -->
<div class="main-offer">
<div class="offer-card featured">
<div class="offer-badge">خصم 30%</div>
<img src="images/products/lavender-oil.jpg" alt="زيت اللافندر">
<div class="offer-content">
<h3>زيت اللافندر الأصلي</h3>
<p>استمتع بخصم حصري على زيت اللافندر النقي 100%</p>
<div class="price">
<span class="old-price">300 ج.م</span>
<span class="new-price">210 ج.م</span>
</div>
<button class="btn primary">أضف للسلة</button>
</div>
</div>
</div>
<!-- العروض الفرعية -->
<div class="offers-list">
<div class="offer-card">
<div class="offer-badge">جديد</div>
<img src="images/products/rosemary-oil.jpg" alt="زيت إكليل الجبل">
<div class="offer-content">
<h3>زيت إكليل الجبل</h3>
<p>منتج جديد - عرض خاص لفترة محدودة</p>
<div class="price">
<span class="new-price">180 ج.م</span>
</div>
<button class="btn primary">أضف للسلة</button>
</div>
</div>
<div class="offer-card">
<div class="offer-badge">خصم 20%</div>
<img src="images/products/tea-tree-oil.jpg" alt="زيت شجرة الشاي">
<div class="offer-content">
<h3>زيت شجرة الشاي</h3>
<p>العرض ساري حتى نفاذ الكمية</p>
<div class="price">
<span class="old-price">250 ج.م</span>
<span class="new-price">200 ج.م</span>
</div>
<button class="btn primary">أضف للسلة</button>
</div>
</div>
<div class="offer-card">
<div class="offer-badge">عرض خاص</div>
<img src="images/products/peppermint-oil.jpg" alt="زيت النعناع">
<div class="offer-content">
<h3>زيت النعناع الطبيعي</h3>
<p>اشترِ قارورتين واحصل على الثالثة مجاناً</p>
<div class="price">
<span class="new-price">150 ج.م</span>
</div>
<button class="btn primary">أضف للسلة</button>
</div>
</div>
</div>
<!-- شريط العروض المتحرك -->
<div class="offers-ticker">
<div class="ticker-content">
<span>🌟 خصم 15% على جميع المنتجات للأعضاء الجدد</span>
<span>🚚 شحن مجاني للطلبات أكثر من 500 ج.م</span>
<span>🎁 هدية مجانية مع كل طلب أكثر من 1000 ج.م</span>
</div>
</div>
</div>
</div>
</section>
<section id="products" class="products">
<div class="container">
<div class="section-header">
<h2>منتجاتنا</h2>
<p>مجموعة متنوعة من الزيوت العطرية الطبيعية</p>
</div>
<div class="products-grid">
<div class="product-card">
<div class="product-image">
<img src="images/products 2.jpg" alt="زيت الياسمين المطلق">
</div>
<div class="product-info">
<h3>زيت الياسمين المطلق</h3>
<p>زيت عطري طبيعي 100% من زهور الياسمين</p>
<div class="product-features">
<span>طبيعي 100%</span>
<span>استخلاص مباشر</span>
</div>
<a href="#contact" class="btn primary">اطلب الآن</a>
</div>
</div>
<div class="product-card">
<div class="product-image">
<img src="images/proudctus.jpg" alt="زيت إكليل الجبل">
</div>
<div class="product-info">
<h3>زيت إكليل الجبل</h3>
<p>زيت عطري نقي مستخلص من نبات إكليل الجبل</p>
<div class="product-features">
<span>طبيعي 100%</span>
<span>استخلاص مباشر</span>
</div>
<a href="#contact" class="btn primary">اطلب الآن</a>
</div>
</div>
</div>
</div>
</section>
<section id="about" class="about">
<div class="container">
<div class="section-header">
<h2>قرية شبرا بلولة</h2>
<p>مصدر الكنوز العطرية الطبيعية</p>
</div>
<div class="about-content">
<div class="about-text">
<p>تعد قرية شبرا بلولة مصدراً للكنوز العطرية التي تنمو بين أراضيها الخصبة. من هنا، تستخلص شركتنا زيوتها، معتمدة على هذا المورد الطبيعي الثمين لتقديم منتجات عالية الجودة، تتميز برائحة الياسمين الفريدة، التي تساهم في تعزيز الصحة والجمال.</p>
<ul class="about-features">
<li>استخلاص طبيعي 100%</li>
<li>زراعة عضوية مستدامة</li>
<li>جودة عالمية</li>
</ul>
</div>
<div class="about-image">
<img src="images/1.jpg" alt="حقول شبرا بلولة">
</div>
</div>
</div>
</section>
<section id="benefits" class="benefits">
<div class="container">
<div class="section-header">
<h2>فوائد الزيوت العطرية</h2>
<p>اكتشف الفوائد الصحية والجمالية لزيوتنا الطبيعية</p>
</div>
<div class="benefits-grid">
<div class="benefit-card">
<i class="fas fa-heart"></i>
<h3>الصحة النفسية</h3>
<p>تساعد على الاسترخاء وتحسين المزاج</p>
</div>
<div class="benefit-card">
<i class="fas fa-spa"></i>
<h3>العناية بالبشرة</h3>
<p>تغذية وترطيب طبيعي للبشرة</p>
</div>
<div class="benefit-card">
<i class="fas fa-wind"></i>
<h3>تعطير المنزل</h3>
<p>روائح طبيعية منعشة للمنزل</p>
</div>
</div>
</div>
</section>
<section id="testimonials" class="testimonials-section">
<div class="container">
<h2 class="section-title">آراء عملائنا</h2>
<div class="testimonials-slider">
<div class="testimonial-card">
<div class="testimonial-content">
<div class="rating">
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
</div>
<p>"زيوت عطرية رائعة وطبيعية 100%. استخدمت زيت اللافندر للاسترخاء وكانت النتائج مذهلة!"</p>
<div class="testimonial-author">
<img src="images/testimonials/user1.jpg" alt="سارة أحمد">
<div class="author-info">
<h4>سارة أحمد</h4>
<span>عميلة منذ 2023</span>
</div>
</div>
</div>
</div>
<div class="testimonial-card">
<div class="testimonial-content">
<div class="rating">
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
</div>
<p>"جودة استثنائية وخدمة عملاء ممتازة. أنصح بشدة بزيت إكليل الجبل لتقوية الشعر."</p>
<div class="testimonial-author">
<img src="images/testimonials/user2.jpg" alt="محمد علي">
<div class="author-info">
<h4>محمد علي</h4>
<span>عميل منذ 2024</span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="newsletter" class="newsletter-section">
<div class="container">
<div class="newsletter-content">
<h2>اشترك في نشرتنا البريدية</h2>
<p>احصل على آخر العروض والأخبار عن منتجاتنا</p>
<form id="newsletter-form" class="newsletter-form">
<div class="form-group">
<input type="email" id="email" name="email" placeholder="أدخل بريدك الإلكتروني" required>
<button type="submit" class="btn">اشترك الآن</button>
</div>
</form>
</div>
</div>
</section>
<section id="contact" class="contact">
<div class="container">
<div class="section-header">
<h2>تواصل معنا</h2>
<p>نحن هنا للإجابة على استفساراتكم</p>
</div>
<div class="contact-content">
<div class="contact-info">
<div class="info-item">
<i class="fas fa-phone"></i>
<h3>اتصل بنا</h3>
<p>+20 123 456 789</p>
</div>
<div class="info-item">
<i class="fas fa-envelope"></i>
<h3>راسلنا</h3>
<p>info@shubraveil.com</p>
</div>
<div class="info-item">
<i class="fas fa-map-marker-alt"></i>
<h3>زورنا</h3>
<p>شبرا بلولة، مصر</p>
</div>
</div>
</div>
</div>
</section>
<section class="reviews-section">
<h2>مراجعات العملاء</h2>
<div class="review-summary">
<div class="average-rating">
<h3>التقييم العام</h3>
<div class="rating-value">4.5</div>
<div class="rating-stars">
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star-half-alt"></i>
</div>
<span class="total-reviews">125 مراجعة</span>
</div>
<div class="rating-breakdown">
<div class="rating-bar">
<span>5 نجوم</span>
<div class="progress">
<div class="progress-bar" style="width: 70%"></div>
</div>
<span>70%</span>
</div>
<div class="rating-bar">
<span>4 نجوم</span>
<div class="progress">
<div class="progress-bar" style="width: 20%"></div>
</div>
<span>20%</span>
</div>
<div class="rating-bar">
<span>3 نجوم</span>
<div class="progress">
<div class="progress-bar" style="width: 5%"></div>
</div>
<span>5%</span>
</div>
<div class="rating-bar">
<span>2 نجوم</span>
<div class="progress">
<div class="progress-bar" style="width: 3%"></div>
</div>
<span>3%</span>
</div>
<div class="rating-bar">
<span>1 نجمة</span>
<div class="progress">
<div class="progress-bar" style="width: 2%"></div>
</div>
<span>2%</span>
</div>
</div>
</div>
<div class="review-controls">
<div class="review-filters">
<button class="review-filter active" data-rating="all">الكل</button>
<button class="review-filter" data-rating="5">5 نجوم</button>
<button class="review-filter" data-rating="4">4 نجوم</button>
<button class="review-filter" data-rating="3">3 نجوم</button>
<button class="review-filter" data-rating="2">2 نجوم</button>
<button class="review-filter" data-rating="1">نجمة واحدة</button>
</div>
<select id="review-sort" class="review-sort">
<option value="newest">الأحدث</option>
<option value="highest">الأعلى تقييماً</option>
<option value="lowest">الأقل تقييماً</option>
<option value="helpful">الأكثر إفادة</option>
</select>
</div>
<div id="reviews-container">
<!-- سيتم ملء هذا القسم ديناميكياً بالمراجعات -->
</div>
<form id="review-form" class="review-form">
<h3>أضف مراجعتك</h3>
<div class="rating-input-container">
<label>تقييمك:</label>
<div class="rating-stars">
<i class="far fa-star" data-rating="1"></i>
<i class="far fa-star" data-rating="2"></i>
<i class="far fa-star" data-rating="3"></i>
<i class="far fa-star" data-rating="4"></i>
<i class="far fa-star" data-rating="5"></i>
</div>
<input type="hidden" id="rating-input" name="rating" required>
</div>
<div class="form-group">
<label for="review-content">مراجعتك:</label>
<textarea id="review-content" name="content" placeholder="اكتب رأيك في المنتج..." required></textarea>
</div>
<div class="form-group">
<label for="review-images">إضافة صور:</label>
<input type="file" id="review-images" name="images[]" multiple accept="image/*">
</div>
<button type="submit" class="submit-review">نشر المراجعة</button>
</form>
</section>
</main>
<footer>
<div class="container">
<div class="footer-content">
<div class="footer-about">
<img src="images/logo.jpg" alt="ShubraVeil" class="footer-logo">
<p>نقدم أجود أنواع الزيوت العطرية الطبيعية المستخلصة من أراضي شبرا بلولة الخصبة</p>
</div>
<div class="footer-links">
<h4>روابط سريعة</h4>
<ul>
<li><a href="#home">الرئيسية</a></li>
<li><a href="#about">من نحن</a></li>
<li><a href="#products">منتجاتنا</a></li>
<li><a href="#contact">تواصل معنا</a></li>
</ul>
</div>
<div class="footer-social">
<h4>تابعنا</h4>
<div class="social-links">
<a href="#"><i class="fab fa-facebook"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-twitter"></i></a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>جميع الحقوق محفوظة &copy; 2024 ShubraVeil</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

100
index.php Normal file
View File

@ -0,0 +1,100 @@
<?php
session_start();
require_once 'includes/config.php';
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ShubraVeil - متجر الزيوت الطبيعية</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css">
</head>
<body>
<!-- شريط التنقل -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="index.php">ShubraVeil</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="index.php">الرئيسية</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#products">المنتجات</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#about">من نحن</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#contact">اتصل بنا</a>
</li>
</ul>
<ul class="navbar-nav">
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
<?php echo htmlspecialchars($_SESSION['username']); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="profile.php">الملف الشخصي</a></li>
<li><a class="dropdown-item" href="orders.php">طلباتي</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="logout.php">تسجيل الخروج</a></li>
</ul>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link" href="login.php">تسجيل الدخول</a>
</li>
<li class="nav-item">
<a class="nav-link" href="register.php">حساب جديد</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<!-- القسم الرئيسي -->
<main class="container my-5">
<div class="row">
<div class="col-md-12 text-center">
<h1 class="display-4 mb-4">مرحباً بكم في ShubraVeil</h1>
<p class="lead">اكتشف مجموعتنا المميزة من الزيوت الطبيعية عالية الجودة</p>
<?php if (!isset($_SESSION['user_id'])): ?>
<div class="mt-4">
<a href="register.php" class="btn btn-primary mx-2">إنشاء حساب</a>
<a href="login.php" class="btn btn-outline-primary mx-2">تسجيل الدخول</a>
</div>
<?php endif; ?>
</div>
</div>
</main>
<!-- تذييل الصفحة -->
<footer class="bg-light py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-6">
<h5>روابط مهمة</h5>
<ul class="list-unstyled">
<li><a href="login.php">تسجيل الدخول</a></li>
<li><a href="register.php">حساب جديد</a></li>
<li><a href="forgot-password.php">نسيت كلمة المرور؟</a></li>
</ul>
</div>
<div class="col-md-6 text-md-end">
<p>&copy; <?php echo date('Y'); ?> ShubraVeil. جميع الحقوق محفوظة.</p>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

174
js/cart.js Normal file
View File

@ -0,0 +1,174 @@
class ShoppingCart {
constructor() {
this.cart = JSON.parse(localStorage.getItem('cart')) || [];
this.cartIcon = document.querySelector('.cart-icon');
this.cartCount = document.querySelector('.cart-count');
this.cartTotal = document.querySelector('.cart-total');
this.cartItems = document.querySelector('.cart-items');
this.cartDropdown = document.querySelector('.cart-dropdown');
this.initializeCart();
}
initializeCart() {
// تحديث عداد السلة
this.updateCartCount();
// إضافة مستمعي الأحداث
document.querySelectorAll('.add-to-cart').forEach(button => {
button.addEventListener('click', (e) => this.addToCart(e));
});
// تفعيل القائمة المنسدلة للسلة
this.cartIcon?.addEventListener('click', () => {
this.cartDropdown?.classList.toggle('active');
});
// إغلاق القائمة عند النقر خارجها
document.addEventListener('click', (e) => {
if (!e.target.closest('.cart-container') && this.cartDropdown?.classList.contains('active')) {
this.cartDropdown.classList.remove('active');
}
});
}
addToCart(e) {
const productCard = e.target.closest('.product-card');
const productId = productCard.dataset.id;
const productName = productCard.querySelector('h3').textContent;
const productPrice = parseFloat(productCard.querySelector('.product-price').textContent);
const productImage = productCard.querySelector('img').src;
const existingItem = this.cart.find(item => item.id === productId);
if (existingItem) {
existingItem.quantity += 1;
} else {
this.cart.push({
id: productId,
name: productName,
price: productPrice,
image: productImage,
quantity: 1
});
}
this.updateCart();
this.showNotification('تمت إضافة المنتج إلى السلة');
}
removeFromCart(productId) {
this.cart = this.cart.filter(item => item.id !== productId);
this.updateCart();
}
updateQuantity(productId, change) {
const item = this.cart.find(item => item.id === productId);
if (item) {
item.quantity = Math.max(1, item.quantity + change);
this.updateCart();
}
}
updateCart() {
// تحديث Local Storage
localStorage.setItem('cart', JSON.stringify(this.cart));
// تحديث عداد السلة
this.updateCartCount();
// تحديث عرض السلة
this.updateCartDisplay();
}
updateCartCount() {
const totalItems = this.cart.reduce((total, item) => total + item.quantity, 0);
if (this.cartCount) {
this.cartCount.textContent = totalItems;
this.cartCount.style.display = totalItems > 0 ? 'block' : 'none';
}
}
updateCartDisplay() {
if (!this.cartItems) return;
if (this.cart.length === 0) {
this.cartItems.innerHTML = '<p class="empty-cart">السلة فارغة</p>';
return;
}
this.cartItems.innerHTML = this.cart.map(item => `
<div class="cart-item" data-id="${item.id}">
<img src="${item.image}" alt="${item.name}">
<div class="item-details">
<h4>${item.name}</h4>
<div class="item-price">${item.price} جنيه</div>
<div class="quantity-controls">
<button onclick="cart.updateQuantity('${item.id}', -1)">-</button>
<span>${item.quantity}</span>
<button onclick="cart.updateQuantity('${item.id}', 1)">+</button>
</div>
</div>
<button class="remove-item" onclick="cart.removeFromCart('${item.id}')">
<i class="fas fa-times"></i>
</button>
</div>
`).join('');
// تحديث المجموع
const total = this.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
if (this.cartTotal) {
this.cartTotal.textContent = `المجموع: ${total.toFixed(2)} جنيه`;
}
}
showNotification(message) {
const notification = document.createElement('div');
notification.className = 'cart-notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 2000);
}
async checkout() {
if (this.cart.length === 0) {
alert('السلة فارغة');
return;
}
try {
const response = await fetch('/api/checkout.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
items: this.cart,
total: this.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0)
})
});
if (!response.ok) throw new Error('فشلت عملية الشراء');
// تفريغ السلة بعد نجاح عملية الشراء
this.cart = [];
this.updateCart();
alert('تمت عملية الشراء بنجاح!');
} catch (error) {
console.error('Checkout error:', error);
alert('حدث خطأ أثناء إتمام عملية الشراء');
}
}
}
// تهيئة سلة المشتريات
const cart = new ShoppingCart();

118
js/comments.js Normal file
View File

@ -0,0 +1,118 @@
// نظام التعليقات
class CommentSystem {
constructor() {
this.commentForm = document.getElementById('comment-form');
this.commentsContainer = document.getElementById('comments-container');
this.replyButtons = document.querySelectorAll('.reply-btn');
this.initializeEventListeners();
}
initializeEventListeners() {
// معالجة إضافة تعليق جديد
this.commentForm?.addEventListener('submit', (e) => this.handleNewComment(e));
// معالجة أزرار الرد
this.replyButtons.forEach(button => {
button.addEventListener('click', () => this.handleReply(button));
});
// معالجة أزرار الإعجاب
document.querySelectorAll('.like-btn').forEach(button => {
button.addEventListener('click', () => this.handleLike(button));
});
}
async handleNewComment(e) {
e.preventDefault();
const formData = new FormData(e.target);
try {
const response = await fetch('/api/comments.php', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('فشل إضافة التعليق');
const comment = await response.json();
this.addCommentToDOM(comment);
e.target.reset();
} catch (error) {
console.error('Comment error:', error);
alert('حدث خطأ أثناء إضافة التعليق');
}
}
handleReply(button) {
const commentId = button.dataset.commentId;
const replyForm = document.querySelector(`.reply-form[data-comment-id="${commentId}"]`);
// إظهار/إخفاء نموذج الرد
if (replyForm) {
replyForm.classList.toggle('active');
}
}
async handleLike(button) {
const commentId = button.dataset.commentId;
const likeCount = button.querySelector('.like-count');
try {
const response = await fetch('/api/like-comment.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ commentId })
});
if (!response.ok) throw new Error('فشل تسجيل الإعجاب');
const data = await response.json();
likeCount.textContent = data.likes;
button.classList.toggle('liked');
} catch (error) {
console.error('Like error:', error);
}
}
addCommentToDOM(comment) {
const commentElement = document.createElement('div');
commentElement.className = 'comment';
commentElement.innerHTML = `
<div class="comment-header">
<img src="${comment.userAvatar}" alt="${comment.userName}" class="user-avatar">
<div class="comment-info">
<h4>${comment.userName}</h4>
<span class="comment-date">${comment.date}</span>
</div>
</div>
<div class="comment-content">
<p>${comment.content}</p>
</div>
<div class="comment-actions">
<button class="like-btn" data-comment-id="${comment.id}">
<i class="far fa-heart"></i>
<span class="like-count">0</span>
</button>
<button class="reply-btn" data-comment-id="${comment.id}">
<i class="far fa-comment"></i> رد
</button>
</div>
<div class="reply-form" data-comment-id="${comment.id}">
<form>
<textarea placeholder="اكتب ردك هنا..."></textarea>
<button type="submit" class="btn">إرسال الرد</button>
</form>
</div>
`;
this.commentsContainer.prepend(commentElement);
}
}
// تهيئة نظام التعليقات
document.addEventListener('DOMContentLoaded', () => {
new CommentSystem();
});

239
js/main.js Normal file
View File

@ -0,0 +1,239 @@
document.addEventListener('DOMContentLoaded', function() {
// Mobile Menu Toggle
const mobileMenuBtn = document.querySelector('.mobile-menu');
const navLinks = document.querySelector('.nav-links');
const navbar = document.querySelector('.navbar');
mobileMenuBtn.addEventListener('click', function() {
navLinks.classList.toggle('active');
});
// Smooth Scrolling for Navigation Links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
const headerOffset = 100;
const elementPosition = target.getBoundingClientRect().top;
const offsetPosition = elementPosition - headerOffset;
window.scrollBy({
top: offsetPosition,
behavior: 'smooth'
});
// Close mobile menu if open
navLinks.classList.remove('active');
}
});
});
// تغيير الهيدر عند التمرير
window.addEventListener('scroll', function() {
if (window.scrollY > 50) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
});
// إضافة الكلاس عند تحميل الصفحة إذا كنا في منتصف الصفحة
if (window.scrollY > 50) {
navbar.classList.add('scrolled');
}
// تفعيل الأسئلة الشائعة
document.querySelectorAll('.faq-question').forEach(question => {
question.addEventListener('click', () => {
const faqItem = question.parentElement;
faqItem.classList.toggle('active');
});
});
// معالجة النشرة البريدية
document.getElementById('newsletter-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const email = e.target.email.value;
try {
const response = await fetch('/api/newsletter-subscribe.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email })
});
if (response.ok) {
alert('شكراً لاشتراكك في نشرتنا البريدية!');
e.target.reset();
} else {
throw new Error('حدث خطأ أثناء الاشتراك');
}
} catch (error) {
alert('عذراً، حدث خطأ. يرجى المحاولة مرة أخرى.');
console.error('Newsletter subscription error:', error);
}
});
// Enhanced Search Functionality
const searchBar = document.querySelector('.search-bar');
const searchIcon = document.querySelector('.search-icon');
const voiceSearchIcon = document.querySelector('.voice-search-icon');
const searchSuggestions = document.querySelector('.search-suggestions');
const filterTags = document.querySelectorAll('.filter-tag');
const searchHistory = document.querySelector('.search-history');
// Load search history from localStorage
let searchHistoryItems = JSON.parse(localStorage.getItem('searchHistory') || '[]');
updateSearchHistory();
// Search input handler
let searchTimeout;
searchBar.addEventListener('input', function() {
clearTimeout(searchTimeout);
const query = this.value.trim();
if (query.length > 0) {
searchIcon.classList.add('searching');
searchTimeout = setTimeout(() => {
fetchSearchSuggestions(query);
}, 300);
} else {
searchSuggestions.classList.remove('active');
searchIcon.classList.remove('searching');
}
});
// Voice search handler
voiceSearchIcon.addEventListener('click', function() {
if ('webkitSpeechRecognition' in window) {
const recognition = new webkitSpeechRecognition();
recognition.lang = 'ar';
recognition.continuous = false;
recognition.interimResults = false;
recognition.onstart = function() {
voiceSearchIcon.classList.add('recording');
searchBar.placeholder = 'جاري الاستماع...';
};
recognition.onresult = function(event) {
const transcript = event.results[0][0].transcript;
searchBar.value = transcript;
searchBar.dispatchEvent(new Event('input'));
};
recognition.onerror = function() {
voiceSearchIcon.classList.remove('recording');
searchBar.placeholder = 'ابحث عن المنتجات...';
};
recognition.onend = function() {
voiceSearchIcon.classList.remove('recording');
searchBar.placeholder = 'ابحث عن المنتجات...';
};
recognition.start();
} else {
alert('عذراً، البحث الصوتي غير مدعوم في متصفحك');
}
});
// Filter tags handler
filterTags.forEach(tag => {
tag.addEventListener('click', function() {
this.classList.toggle('active');
const activeFilters = Array.from(filterTags)
.filter(tag => tag.classList.contains('active'))
.map(tag => tag.textContent);
if (activeFilters.length > 0) {
searchBar.value = activeFilters.join(' ');
searchBar.dispatchEvent(new Event('input'));
}
});
});
// Mock function for search suggestions (replace with actual API call)
async function fetchSearchSuggestions(query) {
try {
// Simulate API call delay
await new Promise(resolve => setTimeout(resolve, 500));
// Mock suggestions (replace with actual API response)
const suggestions = [
'زيت اللافندر العطري',
'زيت الورد العطري',
'زيت اللوز الثابت',
'ماء الورد الطبيعي',
'كريم مرطب طبيعي'
].filter(item => item.includes(query));
// Update suggestions UI
searchSuggestions.innerHTML = suggestions.map(item =>
`<div class="suggestion-item">${item}</div>`
).join('');
if (suggestions.length > 0) {
searchSuggestions.classList.add('active');
} else {
searchSuggestions.classList.remove('active');
}
// Add click handlers to suggestions
document.querySelectorAll('.suggestion-item').forEach(item => {
item.addEventListener('click', function() {
searchBar.value = this.textContent;
addToSearchHistory(this.textContent);
searchSuggestions.classList.remove('active');
// Trigger search with selected suggestion
performSearch(this.textContent);
});
});
} catch (error) {
console.error('Error fetching suggestions:', error);
} finally {
searchIcon.classList.remove('searching');
}
}
// Add to search history
function addToSearchHistory(query) {
if (!searchHistoryItems.includes(query)) {
searchHistoryItems.unshift(query);
if (searchHistoryItems.length > 5) {
searchHistoryItems.pop();
}
localStorage.setItem('searchHistory', JSON.stringify(searchHistoryItems));
updateSearchHistory();
}
}
// Update search history UI
function updateSearchHistory() {
if (searchHistoryItems.length > 0) {
const historyHtml = searchHistoryItems
.map(item => `<span class="history-item">${item}</span>`)
.join('');
searchHistory.innerHTML = 'عمليات البحث السابقة: ' + historyHtml;
// Add click handlers to history items
document.querySelectorAll('.history-item').forEach(item => {
item.addEventListener('click', function() {
searchBar.value = this.textContent;
searchBar.dispatchEvent(new Event('input'));
});
});
} else {
searchHistory.style.display = 'none';
}
}
// Perform search (replace with actual search implementation)
function performSearch(query) {
console.log('Performing search for:', query);
// Implement actual search functionality here
}
});

278
js/reviews.js Normal file
View File

@ -0,0 +1,278 @@
class ReviewSystem {
constructor() {
this.reviewForm = document.getElementById('review-form');
this.reviewsContainer = document.getElementById('reviews-container');
this.ratingInput = document.getElementById('rating-input');
this.initializeReviews();
}
initializeReviews() {
// تهيئة نظام التقييم بالنجوم
this.initializeStarRating();
// معالجة إرسال المراجعة
this.reviewForm?.addEventListener('submit', (e) => this.handleReviewSubmit(e));
// تهيئة فلترة المراجعات
this.initializeReviewFilters();
// تحميل المراجعات الحالية
this.loadReviews();
}
initializeStarRating() {
const ratingStars = document.querySelectorAll('.rating-stars i');
ratingStars.forEach((star, index) => {
star.addEventListener('mouseover', () => {
this.updateStars(ratingStars, index);
});
star.addEventListener('click', () => {
this.ratingInput.value = index + 1;
star.classList.add('selected');
});
});
// إعادة تعيين النجوم عند إزالة المؤشر
document.querySelector('.rating-stars')?.addEventListener('mouseleave', () => {
this.updateStars(ratingStars, this.ratingInput.value - 1);
});
}
updateStars(stars, activeIndex) {
stars.forEach((star, index) => {
star.classList.toggle('active', index <= activeIndex);
});
}
async handleReviewSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
try {
const response = await fetch('/api/reviews.php', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('فشل إرسال المراجعة');
const review = await response.json();
this.addReviewToDOM(review);
this.showNotification('تم إضافة مراجعتك بنجاح');
e.target.reset();
this.updateStars(document.querySelectorAll('.rating-stars i'), -1);
} catch (error) {
console.error('Review submission error:', error);
this.showNotification('حدث خطأ أثناء إرسال المراجعة', 'error');
}
}
initializeReviewFilters() {
const filterButtons = document.querySelectorAll('.review-filter');
filterButtons.forEach(button => {
button.addEventListener('click', () => {
filterButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
this.filterReviews(button.dataset.rating);
});
});
// تهيئة الترتيب
document.getElementById('review-sort')?.addEventListener('change', (e) => {
this.sortReviews(e.target.value);
});
}
async loadReviews() {
try {
const productId = this.reviewsContainer?.dataset.productId;
const response = await fetch(`/api/reviews.php?product_id=${productId}`);
if (!response.ok) throw new Error('فشل تحميل المراجعات');
const reviews = await response.json();
this.displayReviews(reviews);
} catch (error) {
console.error('Reviews loading error:', error);
this.showNotification('حدث خطأ أثناء تحميل المراجعات', 'error');
}
}
displayReviews(reviews) {
if (!this.reviewsContainer) return;
if (reviews.length === 0) {
this.reviewsContainer.innerHTML = '<p class="no-reviews">لا توجد مراجعات بعد</p>';
return;
}
this.reviewsContainer.innerHTML = reviews.map(review => `
<div class="review-card" data-rating="${review.rating}">
<div class="review-header">
<div class="reviewer-info">
<img src="${review.userAvatar}" alt="${review.userName}" class="reviewer-avatar">
<div>
<h4>${review.userName}</h4>
<div class="review-rating">
${this.generateStars(review.rating)}
</div>
<span class="review-date">${review.date}</span>
</div>
</div>
<div class="review-verification">
${review.verified ? '<span class="verified-badge"><i class="fas fa-check-circle"></i> شراء موثق</span>' : ''}
</div>
</div>
<div class="review-content">
<p>${review.content}</p>
${review.images ? this.generateImageGallery(review.images) : ''}
</div>
<div class="review-actions">
<button class="helpful-btn" onclick="reviewSystem.markHelpful('${review.id}')">
<i class="far fa-thumbs-up"></i>
<span class="helpful-count">${review.helpfulCount}</span>
</button>
<button class="report-btn" onclick="reviewSystem.reportReview('${review.id}')">
<i class="far fa-flag"></i>
</button>
</div>
</div>
`).join('');
}
generateStars(rating) {
return Array(5).fill().map((_, index) => `
<i class="fas fa-star ${index < rating ? 'filled' : ''}"></i>
`).join('');
}
generateImageGallery(images) {
return `
<div class="review-images">
${images.map(image => `
<img src="${image}" alt="صورة المراجعة" onclick="reviewSystem.showImageModal('${image}')">
`).join('')}
</div>
`;
}
filterReviews(rating) {
const reviews = document.querySelectorAll('.review-card');
reviews.forEach(review => {
if (rating === 'all' || review.dataset.rating === rating) {
review.style.display = 'block';
} else {
review.style.display = 'none';
}
});
}
sortReviews(criteria) {
const reviews = Array.from(document.querySelectorAll('.review-card'));
const container = this.reviewsContainer;
if (!container) return;
reviews.sort((a, b) => {
switch (criteria) {
case 'newest':
return new Date(b.querySelector('.review-date').textContent) -
new Date(a.querySelector('.review-date').textContent);
case 'highest':
return b.dataset.rating - a.dataset.rating;
case 'lowest':
return a.dataset.rating - b.dataset.rating;
case 'helpful':
return parseInt(b.querySelector('.helpful-count').textContent) -
parseInt(a.querySelector('.helpful-count').textContent);
default:
return 0;
}
});
reviews.forEach(review => container.appendChild(review));
}
async markHelpful(reviewId) {
try {
const response = await fetch('/api/helpful-review.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ reviewId })
});
if (!response.ok) throw new Error('فشل تسجيل المساعدة');
const data = await response.json();
document.querySelector(`[data-review-id="${reviewId}"] .helpful-count`).textContent = data.helpfulCount;
this.showNotification('شكراً لتقييمك');
} catch (error) {
console.error('Helpful marking error:', error);
}
}
reportReview(reviewId) {
const reason = prompt('يرجى ذكر سبب الإبلاغ عن هذه المراجعة:');
if (!reason) return;
fetch('/api/report-review.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ reviewId, reason })
})
.then(response => {
if (response.ok) {
this.showNotification('شكراً لإبلاغك. سنراجع المحتوى.');
}
})
.catch(error => {
console.error('Review report error:', error);
this.showNotification('حدث خطأ أثناء الإبلاغ', 'error');
});
}
showImageModal(imageSrc) {
const modal = document.createElement('div');
modal.className = 'image-modal';
modal.innerHTML = `
<div class="modal-content">
<img src="${imageSrc}" alt="صورة المراجعة">
<button class="close-modal">&times;</button>
</div>
`;
document.body.appendChild(modal);
modal.querySelector('.close-modal').addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
}
showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `review-notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 2000);
}
}
// تهيئة نظام المراجعات
const reviewSystem = new ReviewSystem();

483
js/search.js Normal file
View File

@ -0,0 +1,483 @@
// متغيرات عامة
let selectedProducts = [];
const maxCompareItems = 3;
// تهيئة صفحة البحث
document.addEventListener('DOMContentLoaded', () => {
initializeSearch();
initializeComparison();
});
// تهيئة نظام البحث
function initializeSearch() {
const searchForm = document.getElementById('advanced-search');
const filters = document.querySelectorAll('select, input');
// معالجة نموذج البحث
searchForm.addEventListener('submit', async (e) => {
e.preventDefault();
await performSearch();
});
// تحديث النتائج عند تغيير الفلاتر
filters.forEach(filter => {
filter.addEventListener('change', debounce(performSearch, 500));
});
}
// تنفيذ البحث
async function performSearch() {
const searchQuery = document.getElementById('search-query').value;
const productType = document.getElementById('product-type').value;
const minPrice = document.getElementById('min-price').value;
const maxPrice = document.getElementById('max-price').value;
const sortBy = document.getElementById('sort-by').value;
try {
const response = await fetch('/api/search.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: searchQuery,
type: productType,
minPrice,
maxPrice,
sortBy
})
});
if (!response.ok) throw new Error('فشل البحث');
const results = await response.json();
displayResults(results);
} catch (error) {
console.error('Search error:', error);
showError('حدث خطأ أثناء البحث. يرجى المحاولة مرة أخرى.');
}
}
// عرض نتائج البحث
function displayResults(results) {
const resultsContainer = document.getElementById('search-results');
if (!results.length) {
resultsContainer.innerHTML = '<div class="no-results">لم يتم العثور على نتائج</div>';
return;
}
resultsContainer.innerHTML = results.map(product => `
<div class="product-card" data-id="${product.id}">
<div class="product-image">
<img src="${product.image}" alt="${product.name}">
${product.badge ? `<span class="badge">${product.badge}</span>` : ''}
</div>
<div class="product-info">
<h3>${product.name}</h3>
<p>${product.description}</p>
<div class="product-price">${product.price} جنيه</div>
<div class="product-actions">
<button class="btn add-to-cart">أضف للسلة</button>
<button class="btn-secondary add-to-compare" onclick="toggleCompare(${product.id})">
<i class="fas fa-exchange-alt"></i>
</button>
</div>
</div>
</div>
`).join('');
}
// نظام المقارنة
function initializeComparison() {
document.getElementById('compare-btn').addEventListener('click', () => {
if (selectedProducts.length > 1) {
window.location.href = `/compare.html?products=${selectedProducts.join(',')}`;
}
});
}
// إضافة/إزالة منتج من المقارنة
function toggleCompare(productId) {
const index = selectedProducts.indexOf(productId);
if (index === -1) {
if (selectedProducts.length >= maxCompareItems) {
alert(`يمكنك مقارنة ${maxCompareItems} منتجات كحد أقصى`);
return;
}
selectedProducts.push(productId);
} else {
selectedProducts.splice(index, 1);
}
updateComparisonUI();
}
// تحديث واجهة المقارنة
function updateComparisonUI() {
const compareBtn = document.getElementById('compare-btn');
const comparisonList = document.getElementById('comparison-list');
compareBtn.disabled = selectedProducts.length < 2;
// تحديث قائمة المنتجات المحددة للمقارنة
if (selectedProducts.length > 0) {
fetchProductsDetails(selectedProducts).then(products => {
comparisonList.innerHTML = products.map(product => `
<div class="comparison-item">
<img src="${product.image}" alt="${product.name}">
<span>${product.name}</span>
<button onclick="toggleCompare(${product.id})">
<i class="fas fa-times"></i>
</button>
</div>
`).join('');
});
} else {
comparisonList.innerHTML = '<p>اختر منتجات للمقارنة</p>';
}
}
// دالة مساعدة للحد من تكرار الطلبات
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// عرض رسائل الخطأ
function showError(message) {
const resultsContainer = document.getElementById('search-results');
resultsContainer.innerHTML = `<div class="error-message">${message}</div>`;
}
// جلب تفاصيل المنتجات للمقارنة
async function fetchProductsDetails(productIds) {
try {
const response = await fetch('/api/products.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids: productIds })
});
if (!response.ok) throw new Error('فشل جلب تفاصيل المنتجات');
return await response.json();
} catch (error) {
console.error('Error fetching product details:', error);
return [];
}
}
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.querySelector('.search-input');
const searchResults = document.querySelector('.search-results');
const searchIcon = document.querySelector('.search-icon');
const voiceSearchBtn = document.querySelector('.voice-search-btn');
const categoryFilters = document.querySelectorAll('input[name="category"]');
const minPrice = document.getElementById('min-price');
const maxPrice = document.getElementById('max-price');
const sortBy = document.getElementById('sort-by');
let searchTimeout;
const debounceDelay = 300;
// تفعيل البحث عند الكتابة
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
if (this.value.length > 0) {
searchTimeout = setTimeout(() => {
performSearch();
searchResults.classList.add('active');
}, debounceDelay);
} else {
searchResults.classList.remove('active');
}
});
// إخفاء نتائج البحث عند النقر خارجها
document.addEventListener('click', function(e) {
if (!searchResults.contains(e.target) && !searchInput.contains(e.target)) {
searchResults.classList.remove('active');
}
});
// تفعيل البحث الصوتي
voiceSearchBtn.addEventListener('click', function() {
if ('webkitSpeechRecognition' in window) {
const recognition = new webkitSpeechRecognition();
recognition.lang = 'ar-SA';
recognition.continuous = false;
recognition.interimResults = false;
recognition.onstart = function() {
voiceSearchBtn.classList.add('listening');
};
recognition.onresult = function(event) {
const transcript = event.results[0][0].transcript;
searchInput.value = transcript;
performSearch();
searchResults.classList.add('active');
};
recognition.onerror = function(event) {
console.error('Speech recognition error:', event.error);
voiceSearchBtn.classList.remove('listening');
};
recognition.onend = function() {
voiceSearchBtn.classList.remove('listening');
};
recognition.start();
} else {
alert('عذراً، البحث الصوتي غير مدعوم في متصفحك');
}
});
// تحديث نتائج البحث عند تغيير الفلاتر
categoryFilters.forEach(filter => {
filter.addEventListener('change', performSearch);
});
minPrice.addEventListener('input', performSearch);
maxPrice.addEventListener('input', performSearch);
sortBy.addEventListener('change', performSearch);
// دالة البحث الرئيسية
function performSearch() {
const query = searchInput.value;
const selectedCategories = Array.from(categoryFilters)
.filter(checkbox => checkbox.checked)
.map(checkbox => checkbox.value);
const priceRange = {
min: minPrice.value ? parseInt(minPrice.value) : null,
max: maxPrice.value ? parseInt(maxPrice.value) : null
};
const sortValue = sortBy.value;
// محاكاة طلب API
fetchSearchResults(query, selectedCategories, priceRange, sortValue)
.then(displayResults)
.catch(error => console.error('Search error:', error));
}
// دالة جلب نتائج البحث من الخادم
async function fetchSearchResults(query, categories, priceRange, sort) {
// هنا يمكنك استبدال هذا بطلب API حقيقي
const mockResults = [
{
id: 1,
title: 'زيت اللافندر العطري',
category: 'زيوت عطرية',
price: 120,
image: 'images/products/lavender.jpg'
},
{
id: 2,
title: 'زيت الجوجوبا',
category: 'زيوت ثابتة',
price: 85,
image: 'images/products/jojoba.jpg'
}
];
return new Promise(resolve => {
setTimeout(() => resolve(mockResults), 300);
});
}
// دالة عرض نتائج البحث
function displayResults(results) {
const resultsContainer = document.querySelector('.search-results-list');
resultsContainer.innerHTML = '';
if (results.length === 0) {
resultsContainer.innerHTML = '<div class="no-results">لا توجد نتائج</div>';
return;
}
results.forEach(result => {
const resultItem = document.createElement('div');
resultItem.className = 'result-item';
resultItem.innerHTML = `
<img src="${result.image}" alt="${result.title}" class="result-image">
<div class="result-info">
<div class="result-title">${result.title}</div>
<div class="result-category">${result.category}</div>
</div>
<div class="result-price">${result.price} ر.س</div>
`;
resultItem.addEventListener('click', () => {
window.location.href = `/product/${result.id}`;
});
resultsContainer.appendChild(resultItem);
});
}
});
class ProductSearch {
constructor() {
this.searchInput = document.querySelector('.search-input');
this.searchResults = document.querySelector('.search-results');
this.filterForm = document.querySelector('.search-filters');
this.initializeSearch();
}
initializeSearch() {
// إضافة مستمعي الأحداث
this.searchInput?.addEventListener('input', debounce(() => this.handleSearch(), 300));
this.filterForm?.addEventListener('change', () => this.handleSearch());
// إضافة مستمع لإغلاق نتائج البحث عند النقر خارجها
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-container')) {
this.hideResults();
}
});
}
async handleSearch() {
const query = this.searchInput?.value.trim();
if (!query) {
this.hideResults();
return;
}
try {
// جمع معايير التصفية
const filters = this.getFilters();
// إجراء البحث
const response = await fetch('/api/search.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query,
...filters
})
});
if (!response.ok) throw new Error('فشل البحث');
const results = await response.json();
this.displayResults(results);
} catch (error) {
console.error('Search error:', error);
this.showError('حدث خطأ أثناء البحث');
}
}
getFilters() {
const filters = {
category: [],
priceRange: {
min: null,
max: null
},
sortBy: 'relevance'
};
if (this.filterForm) {
// جمع الفئات المحددة
const categoryInputs = this.filterForm.querySelectorAll('input[name="category"]:checked');
filters.category = Array.from(categoryInputs).map(input => input.value);
// جمع نطاق السعر
const minPrice = this.filterForm.querySelector('#min-price')?.value;
const maxPrice = this.filterForm.querySelector('#max-price')?.value;
if (minPrice) filters.priceRange.min = parseFloat(minPrice);
if (maxPrice) filters.priceRange.max = parseFloat(maxPrice);
// جمع معيار الترتيب
filters.sortBy = this.filterForm.querySelector('#sort-by')?.value || 'relevance';
}
return filters;
}
displayResults(results) {
if (!this.searchResults) return;
if (results.length === 0) {
this.searchResults.innerHTML = `
<div class="no-results">
<i class="fas fa-search"></i>
<p>لم يتم العثور على نتائج</p>
</div>
`;
} else {
this.searchResults.innerHTML = `
<div class="results-list">
${results.map(product => `
<a href="${product.url}" class="result-item">
<img src="${product.image}" alt="${product.name}">
<div class="result-info">
<h4>${this.highlightQuery(product.name)}</h4>
<p>${this.highlightQuery(product.description)}</p>
<span class="price">${product.price} ريال</span>
</div>
</a>
`).join('')}
</div>
`;
}
this.showResults();
}
highlightQuery(text) {
if (!this.searchInput?.value.trim()) return text;
const query = this.searchInput.value.trim();
const regex = new RegExp(`(${query})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
showResults() {
this.searchResults?.classList.add('show');
}
hideResults() {
this.searchResults?.classList.remove('show');
}
showError(message) {
if (!this.searchResults) return;
this.searchResults.innerHTML = `
<div class="search-error">
<i class="fas fa-exclamation-circle"></i>
<p>${message}</p>
</div>
`;
this.showResults();
}
}
// دالة مساعدة للحد من تكرار استدعاء البحث
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// تهيئة نظام البحث
const productSearch = new ProductSearch();

179
js/voice-search.js Normal file
View File

@ -0,0 +1,179 @@
class VoiceSearch {
constructor() {
this.recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
this.searchInput = document.querySelector('.search-input input');
this.voiceButton = document.querySelector('.voice-search-btn');
this.initializeVoiceSearch();
}
initializeVoiceSearch() {
// إعداد خصائص التعرف على الصوت
this.recognition.lang = 'ar';
this.recognition.continuous = false;
this.recognition.interimResults = false;
// إضافة مستمعي الأحداث
this.voiceButton?.addEventListener('click', () => this.startVoiceSearch());
// معالجة نتائج التعرف على الصوت
this.recognition.onresult = (event) => {
const result = event.results[0][0].transcript;
if (this.searchInput) {
this.searchInput.value = result;
// تشغيل البحث تلقائياً
this.searchInput.form?.dispatchEvent(new Event('submit'));
}
};
// معالجة الأخطاء
this.recognition.onerror = (event) => {
console.error('Voice recognition error:', event.error);
this.showNotification('حدث خطأ في التعرف على الصوت', 'error');
};
// عند انتهاء التسجيل
this.recognition.onend = () => {
this.voiceButton?.classList.remove('listening');
};
}
startVoiceSearch() {
try {
this.recognition.start();
this.voiceButton?.classList.add('listening');
this.showNotification('جارٍ الاستماع... تحدث الآن');
} catch (error) {
console.error('Voice recognition error:', error);
this.showNotification('لا يمكن استخدام البحث الصوتي', 'error');
}
}
showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `voice-notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 3000);
}
}
// نظام مشاركة المنتجات
class ShareSystem {
constructor() {
this.initializeShare();
}
initializeShare() {
document.querySelectorAll('.share-btn').forEach(button => {
button.addEventListener('click', (e) => this.shareProduct(e));
});
}
async shareProduct(e) {
const productCard = e.target.closest('.product-card');
const productData = {
title: productCard.querySelector('h3').textContent,
text: productCard.querySelector('p').textContent,
url: window.location.href
};
try {
if (navigator.share) {
// استخدام Web Share API إذا كان متوفراً
await navigator.share(productData);
} else {
// عرض قائمة مشاركة مخصصة
this.showCustomShareMenu(e.target, productData);
}
} catch (error) {
console.error('Share error:', error);
}
}
showCustomShareMenu(target, data) {
const shareMenu = document.createElement('div');
shareMenu.className = 'share-menu';
const platforms = [
{ name: 'Facebook', icon: 'fab fa-facebook', url: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(data.url)}` },
{ name: 'Twitter', icon: 'fab fa-twitter', url: `https://twitter.com/intent/tweet?text=${encodeURIComponent(data.title)}&url=${encodeURIComponent(data.url)}` },
{ name: 'WhatsApp', icon: 'fab fa-whatsapp', url: `https://wa.me/?text=${encodeURIComponent(data.title + ' ' + data.url)}` },
{ name: 'نسخ الرابط', icon: 'fas fa-link', action: () => this.copyToClipboard(data.url) }
];
shareMenu.innerHTML = platforms.map(platform => `
<button class="share-option" data-url="${platform.url || ''}" data-action="${platform.action ? 'copy' : 'share'}">
<i class="${platform.icon}"></i>
<span>${platform.name}</span>
</button>
`).join('');
// إضافة مستمعي الأحداث للأزرار
shareMenu.querySelectorAll('.share-option').forEach(button => {
button.addEventListener('click', () => {
if (button.dataset.action === 'copy') {
this.copyToClipboard(data.url);
} else {
window.open(button.dataset.url, '_blank');
}
shareMenu.remove();
});
});
// إضافة القائمة للصفحة
document.body.appendChild(shareMenu);
// تحديد موقع القائمة
const rect = target.getBoundingClientRect();
shareMenu.style.top = rect.bottom + window.scrollY + 'px';
shareMenu.style.left = rect.left + window.scrollX + 'px';
// إغلاق القائمة عند النقر خارجها
document.addEventListener('click', (e) => {
if (!shareMenu.contains(e.target) && e.target !== target) {
shareMenu.remove();
}
});
}
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
this.showNotification('تم نسخ الرابط بنجاح');
} catch (error) {
console.error('Clipboard error:', error);
this.showNotification('فشل نسخ الرابط', 'error');
}
}
showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `share-notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 2000);
}
}
// تهيئة الأنظمة
const voiceSearch = new VoiceSearch();
const shareSystem = new ShareSystem();

129
js/wishlist.js Normal file
View File

@ -0,0 +1,129 @@
class Wishlist {
constructor() {
this.wishlist = JSON.parse(localStorage.getItem('wishlist')) || [];
this.wishlistIcon = document.querySelector('.wishlist-icon');
this.wishlistCount = document.querySelector('.wishlist-count');
this.initializeWishlist();
}
initializeWishlist() {
// تحديث عداد المفضلة
this.updateWishlistCount();
// إضافة مستمعي الأحداث لأزرار المفضلة
document.querySelectorAll('.add-to-wishlist').forEach(button => {
button.addEventListener('click', (e) => this.toggleWishlist(e));
});
}
toggleWishlist(e) {
e.preventDefault();
const productCard = e.target.closest('.product-card');
const productId = productCard.dataset.id;
const productName = productCard.querySelector('h3').textContent;
const productPrice = parseFloat(productCard.querySelector('.product-price').textContent);
const productImage = productCard.querySelector('img').src;
const index = this.wishlist.findIndex(item => item.id === productId);
if (index === -1) {
// إضافة المنتج للمفضلة
this.wishlist.push({
id: productId,
name: productName,
price: productPrice,
image: productImage
});
this.showNotification('تمت إضافة المنتج إلى المفضلة');
e.target.classList.add('active');
} else {
// إزالة المنتج من المفضلة
this.wishlist.splice(index, 1);
this.showNotification('تمت إزالة المنتج من المفضلة');
e.target.classList.remove('active');
}
this.updateWishlist();
}
updateWishlist() {
// تحديث Local Storage
localStorage.setItem('wishlist', JSON.stringify(this.wishlist));
// تحديث عداد المفضلة
this.updateWishlistCount();
// تحديث عرض المفضلة في الصفحة
this.updateWishlistDisplay();
}
updateWishlistCount() {
if (this.wishlistCount) {
this.wishlistCount.textContent = this.wishlist.length;
this.wishlistCount.style.display = this.wishlist.length > 0 ? 'block' : 'none';
}
}
updateWishlistDisplay() {
const wishlistContainer = document.querySelector('.wishlist-items');
if (!wishlistContainer) return;
if (this.wishlist.length === 0) {
wishlistContainer.innerHTML = '<p class="empty-wishlist">لا توجد منتجات في المفضلة</p>';
return;
}
wishlistContainer.innerHTML = this.wishlist.map(item => `
<div class="wishlist-item" data-id="${item.id}">
<img src="${item.image}" alt="${item.name}">
<div class="item-details">
<h4>${item.name}</h4>
<div class="item-price">${item.price} جنيه</div>
</div>
<div class="item-actions">
<button class="add-to-cart" onclick="cart.addToCart(event)">
<i class="fas fa-shopping-cart"></i>
</button>
<button class="remove-from-wishlist" onclick="wishlist.removeFromWishlist('${item.id}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
removeFromWishlist(productId) {
const index = this.wishlist.findIndex(item => item.id === productId);
if (index !== -1) {
this.wishlist.splice(index, 1);
this.updateWishlist();
this.showNotification('تمت إزالة المنتج من المفضلة');
}
}
showNotification(message) {
const notification = document.createElement('div');
notification.className = 'wishlist-notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 2000);
}
// التحقق من وجود منتج في المفضلة
isInWishlist(productId) {
return this.wishlist.some(item => item.id === productId);
}
}
// تهيئة نظام المفضلة
const wishlist = new Wishlist();

124
login.php Normal file
View File

@ -0,0 +1,124 @@
<?php
require_once 'includes/config.php';
require_once 'includes/Security.php';
session_start();
// إذا كان المستخدم مسجل دخوله بالفعل، قم بتحويله إلى الصفحة الرئيسية
if (isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$email = Security::sanitizeInput($_POST['email']);
$password = $_POST['password'];
if (empty($email) || empty($password)) {
throw new Exception('جميع الحقول مطلوبة');
}
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception('فشل الاتصال بقاعدة البيانات');
}
$stmt = $conn->prepare("SELECT id, username, password, role FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
throw new Exception('البريد الإلكتروني أو كلمة المرور غير صحيحة');
}
$user = $result->fetch_assoc();
if (!Security::verifyPassword($password, $user['password'])) {
throw new Exception('البريد الإلكتروني أو كلمة المرور غير صحيحة');
}
// تسجيل الدخول بنجاح
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
// تحديث وقت آخر تسجيل دخول
$stmt = $conn->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->bind_param("i", $user['id']);
$stmt->execute();
header('Location: index.php');
exit;
} catch (Exception $e) {
$error = $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تسجيل الدخول - ShubraVeil</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card shadow">
<div class="card-body">
<h2 class="text-center mb-4">تسجيل الدخول</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?php echo $success; ?></div>
<?php endif; ?>
<form method="POST" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
<div class="mb-3">
<label for="email" class="form-label">البريد الإلكتروني</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">كلمة المرور</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">تذكرني</label>
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">تسجيل الدخول</button>
</div>
</form>
<div class="text-center mt-3">
<p>ليس لديك حساب؟ <a href="register.php">إنشاء حساب جديد</a></p>
<p><a href="forgot-password.php">نسيت كلمة المرور؟</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

17
logout.php Normal file
View File

@ -0,0 +1,17 @@
<?php
session_start();
// حذف جميع متغيرات الجلسة
$_SESSION = array();
// حذف ملف الجلسة
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600, '/');
}
// إنهاء الجلسة
session_destroy();
// إعادة التوجيه إلى صفحة تسجيل الدخول
header('Location: login.php');
exit;

View File

@ -0,0 +1,169 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>الزيوت الأساسية - ShubraVeil</title>
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<header>
<nav class="navbar">
<div class="container">
<div class="logo">
<a href="../index.html">
<img src="../images/logo.jpg" alt="ShubraVeil" class="logo-img">
</a>
</div>
<ul class="nav-links">
<li><a href="../index.html">الرئيسية</a></li>
<li><a href="../index.html#about">من نحن</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle">منتجاتنا <i class="fas fa-chevron-down"></i></a>
<ul class="dropdown-menu">
<li><a href="essential-oils.html" class="active">الزيوت الأساسية</a></li>
<li><a href="fixed-oils.html">الزيوت الثابتة</a></li>
<li><a href="hydrosols.html">الهيدروسولات العطرية</a></li>
<li><a href="natural-cosmetics.html">مستحضرات تجميل طبيعية</a></li>
</ul>
</li>
<li><a href="../index.html#benefits">فوائد الزيوت</a></li>
<li><a href="../index.html#contact">تواصل معنا</a></li>
</ul>
<div class="mobile-menu">
<i class="fas fa-bars"></i>
</div>
</div>
</nav>
</header>
<main>
<section class="product-category-hero">
<div class="container">
<h1>الزيوت الأساسية</h1>
<p>مجموعة متميزة من الزيوت الأساسية النقية 100% مستخلصة بعناية من أجود النباتات العطرية</p>
</div>
</section>
<section class="products-grid-section">
<div class="container">
<div class="products-filter">
<div class="search-box">
<input type="text" placeholder="ابحث عن منتج...">
<i class="fas fa-search"></i>
</div>
<div class="sort-options">
<select>
<option value="newest">الأحدث</option>
<option value="price-low">السعر: من الأقل للأعلى</option>
<option value="price-high">السعر: من الأعلى للأقل</option>
<option value="name">الاسم: أ-ي</option>
</select>
</div>
</div>
<div class="products-grid" id="products-container">
<!-- سيتم تحميل المنتجات هنا عبر JavaScript -->
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h3>تواصل معنا</h3>
<p><i class="fas fa-phone"></i> +20 123 456 789</p>
<p><i class="fas fa-envelope"></i> info@shubraveil.com</p>
<div class="social-links">
<a href="#"><i class="fab fa-facebook"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-whatsapp"></i></a>
</div>
</div>
<div class="footer-section">
<h3>روابط سريعة</h3>
<ul>
<li><a href="../index.html#about">من نحن</a></li>
<li><a href="../index.html#products">منتجاتنا</a></li>
<li><a href="../index.html#benefits">فوائد الزيوت</a></li>
<li><a href="../index.html#contact">تواصل معنا</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2024 ShubraVeil. جميع الحقوق محفوظة</p>
</div>
</div>
</footer>
<script>
// تحميل المنتجات من قاعدة البيانات
fetch('../api/products.php?type=essential_oils')
.then(response => response.json())
.then(products => {
const container = document.getElementById('products-container');
products.forEach(product => {
container.innerHTML += `
<div class="product-card">
<div class="product-image">
<img src="../${product.image}" alt="${product.name}">
</div>
<div class="product-info">
<h3>${product.name}</h3>
<p>${product.description}</p>
<div class="product-price">${product.price} جنيه</div>
<button class="btn primary">اطلب الآن</button>
</div>
</div>
`;
});
});
// البحث في المنتجات
document.querySelector('.search-box input').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const products = document.querySelectorAll('.product-card');
products.forEach(product => {
const name = product.querySelector('h3').textContent.toLowerCase();
const description = product.querySelector('p').textContent.toLowerCase();
if (name.includes(searchTerm) || description.includes(searchTerm)) {
product.style.display = 'block';
} else {
product.style.display = 'none';
}
});
});
// ترتيب المنتجات
document.querySelector('.sort-options select').addEventListener('change', function(e) {
const products = Array.from(document.querySelectorAll('.product-card'));
const container = document.getElementById('products-container');
products.sort((a, b) => {
const valueA = a.querySelector('h3').textContent;
const valueB = b.querySelector('h3').textContent;
const priceA = parseFloat(a.querySelector('.product-price').textContent);
const priceB = parseFloat(b.querySelector('.product-price').textContent);
switch(e.target.value) {
case 'price-low':
return priceA - priceB;
case 'price-high':
return priceB - priceA;
case 'name':
return valueA.localeCompare(valueB);
default:
return 0;
}
});
container.innerHTML = '';
products.forEach(product => container.appendChild(product));
});
</script>
</body>
</html>

37
products/fixed-oils.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<!-- نفس محتوى صفحة essential-oils.html مع تغيير العنوان والوصف -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>الزيوت الثابتة - ShubraVeil</title>
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<!-- نفس الهيدر مع تغيير الرابط النشط -->
<header>
<!-- ... (نفس محتوى الهيدر) ... -->
</header>
<main>
<section class="product-category-hero">
<div class="container">
<h1>الزيوت الثابتة</h1>
<p>مجموعة مختارة من أجود الزيوت الثابتة الطبيعية المستخلصة من البذور والمكسرات</p>
</div>
</section>
<!-- نفس باقي المحتوى مع تغيير نوع المنتج في API call -->
<script>
fetch('../api/products.php?type=fixed_oils')
// ... باقي الكود
</script>
</main>
<footer>
<!-- ... (نفس محتوى الفوتر) ... -->
</footer>
</body>
</html>

198
register.php Normal file
View File

@ -0,0 +1,198 @@
<?php
require_once 'includes/config.php';
require_once 'includes/Security.php';
session_start();
// إذا كان المستخدم مسجل دخوله بالفعل، قم بتحويله إلى الصفحة الرئيسية
if (isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$username = Security::sanitizeInput($_POST['username']);
$email = Security::sanitizeInput($_POST['email']);
$password = $_POST['password'];
$confirm_password = $_POST['confirm_password'];
// التحقق من البيانات
if (empty($username) || empty($email) || empty($password) || empty($confirm_password)) {
throw new Exception('جميع الحقول مطلوبة');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new Exception('البريد الإلكتروني غير صالح');
}
if (strlen($password) < 8) {
throw new Exception('يجب أن تكون كلمة المرور 8 أحرف على الأقل');
}
if ($password !== $confirm_password) {
throw new Exception('كلمتا المرور غير متطابقتين');
}
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception('فشل الاتصال بقاعدة البيانات');
}
// التحقق من عدم وجود البريد الإلكتروني أو اسم المستخدم مسبقاً
$stmt = $conn->prepare("SELECT id FROM users WHERE email = ? OR username = ?");
$stmt->bind_param("ss", $email, $username);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
throw new Exception('البريد الإلكتروني أو اسم المستخدم مستخدم بالفعل');
}
// تشفير كلمة المرور
$hashed_password = Security::hashPassword($password);
// إنشاء المستخدم
$stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $email, $hashed_password);
if (!$stmt->execute()) {
throw new Exception('حدث خطأ أثناء إنشاء الحساب');
}
$success = 'تم إنشاء الحساب بنجاح! يمكنك الآن تسجيل الدخول.';
} catch (Exception $e) {
$error = $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>إنشاء حساب جديد - ShubraVeil</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card shadow">
<div class="card-body">
<h2 class="text-center mb-4">إنشاء حساب جديد</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success">
<?php echo $success; ?>
<br>
<a href="login.php" class="alert-link">اضغط هنا لتسجيل الدخول</a>
</div>
<?php endif; ?>
<form method="POST" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" class="needs-validation" novalidate>
<div class="mb-3">
<label for="username" class="form-label">اسم المستخدم</label>
<input type="text" class="form-control" id="username" name="username" required
pattern="[A-Za-z0-9_]{3,20}" title="يجب أن يحتوي على أحرف وأرقام وشرطة سفلية فقط (3-20 حرف)">
<div class="invalid-feedback">
يرجى اختيار اسم مستخدم صحيح
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">البريد الإلكتروني</label>
<input type="email" class="form-control" id="email" name="email" required>
<div class="invalid-feedback">
يرجى إدخال بريد إلكتروني صحيح
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">كلمة المرور</label>
<input type="password" class="form-control" id="password" name="password" required
minlength="8" title="يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل">
<div class="invalid-feedback">
يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل
</div>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">تأكيد كلمة المرور</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
<div class="invalid-feedback">
كلمتا المرور غير متطابقتين
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="terms" required>
<label class="form-check-label" for="terms">
أوافق على <a href="terms.php">الشروط والأحكام</a>
</label>
<div class="invalid-feedback">
يجب الموافقة على الشروط والأحكام
</div>
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">إنشاء حساب</button>
</div>
</form>
<div class="text-center mt-3">
<p>لديك حساب بالفعل؟ <a href="login.php">تسجيل الدخول</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// تفعيل التحقق من صحة النموذج
(function () {
'use strict'
var forms = document.querySelectorAll('.needs-validation')
Array.prototype.slice.call(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
// التحقق من تطابق كلمتي المرور
var password = document.getElementById('password')
var confirm_password = document.getElementById('confirm_password')
if (password.value !== confirm_password.value) {
confirm_password.setCustomValidity('كلمتا المرور غير متطابقتين')
event.preventDefault()
event.stopPropagation()
} else {
confirm_password.setCustomValidity('')
}
form.classList.add('was-validated')
}, false)
})
})()
</script>
</body>
</html>

166
reset-password.php Normal file
View File

@ -0,0 +1,166 @@
<?php
require_once 'includes/config.php';
require_once 'includes/Security.php';
session_start();
$error = '';
$success = '';
// التحقق من وجود الرمز
if (!isset($_GET['token'])) {
header('Location: login.php');
exit;
}
$token = Security::sanitizeInput($_GET['token']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$password = $_POST['password'];
$confirm_password = $_POST['confirm_password'];
if (empty($password) || empty($confirm_password)) {
throw new Exception('جميع الحقول مطلوبة');
}
if (strlen($password) < 8) {
throw new Exception('يجب أن تكون كلمة المرور 8 أحرف على الأقل');
}
if ($password !== $confirm_password) {
throw new Exception('كلمتا المرور غير متطابقتين');
}
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception('فشل الاتصال بقاعدة البيانات');
}
// التحقق من صلاحية الرمز
$stmt = $conn->prepare("SELECT user_id FROM password_resets WHERE token = ? AND expiry > NOW() AND used = 0");
$stmt->bind_param("s", $token);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
throw new Exception('رابط إعادة تعيين كلمة المرور غير صالح أو منتهي الصلاحية');
}
$reset = $result->fetch_assoc();
// تحديث كلمة المرور
$hashed_password = Security::hashPassword($password);
$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hashed_password, $reset['user_id']);
if (!$stmt->execute()) {
throw new Exception('حدث خطأ أثناء تحديث كلمة المرور');
}
// تعليم الرمز كمستخدم
$stmt = $conn->prepare("UPDATE password_resets SET used = 1 WHERE token = ?");
$stmt->bind_param("s", $token);
$stmt->execute();
$success = 'تم تحديث كلمة المرور بنجاح! يمكنك الآن تسجيل الدخول باستخدام كلمة المرور الجديدة.';
} catch (Exception $e) {
$error = $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>إعادة تعيين كلمة المرور - ShubraVeil</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card shadow">
<div class="card-body">
<h2 class="text-center mb-4">إعادة تعيين كلمة المرور</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success">
<?php echo $success; ?>
<br>
<a href="login.php" class="alert-link">اضغط هنا لتسجيل الدخول</a>
</div>
<?php else: ?>
<form method="POST" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'] . '?token=' . $token); ?>" class="needs-validation" novalidate>
<div class="mb-3">
<label for="password" class="form-label">كلمة المرور الجديدة</label>
<input type="password" class="form-control" id="password" name="password" required
minlength="8" title="يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل">
<div class="invalid-feedback">
يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل
</div>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">تأكيد كلمة المرور</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
<div class="invalid-feedback">
كلمتا المرور غير متطابقتين
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">تحديث كلمة المرور</button>
</div>
</form>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// تفعيل التحقق من صحة النموذج
(function () {
'use strict'
var forms = document.querySelectorAll('.needs-validation')
Array.prototype.slice.call(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
// التحقق من تطابق كلمتي المرور
var password = document.getElementById('password')
var confirm_password = document.getElementById('confirm_password')
if (password.value !== confirm_password.value) {
confirm_password.setCustomValidity('كلمتا المرور غير متطابقتين')
event.preventDefault()
event.stopPropagation()
} else {
confirm_password.setCustomValidity('')
}
form.classList.add('was-validated')
}, false)
})
})()
</script>
</body>
</html>

14
robots.txt Normal file
View File

@ -0,0 +1,14 @@
User-agent: *
Allow: /
Allow: /products/
Allow: /blog.html
Allow: /faq.html
Allow: /search.html
Disallow: /admin/
Disallow: /includes/
Disallow: /api/
Disallow: /backups/
Disallow: /cache/
Sitemap: https://shubraveil.com/sitemap.xml

88
search.html Normal file
View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>بحث المنتجات - ShubraVeil</title>
<meta name="description" content="ابحث عن الزيوت العطرية والطبيعية في موقع ShubraVeil">
<meta name="keywords" content="زيوت عطرية, زيوت طبيعية, بحث, شبرا بلولة">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<header>
<!-- نفس الهيدر السابق -->
</header>
<main>
<section class="search-section">
<div class="container">
<h1 class="section-title">بحث المنتجات</h1>
<div class="search-container">
<form id="advanced-search" class="search-form">
<div class="search-row">
<div class="search-input">
<input type="text" id="search-query" placeholder="ابحث عن المنتجات...">
<button type="submit" class="search-btn">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="filters">
<div class="filter-group">
<label>نوع المنتج</label>
<select id="product-type">
<option value="">الكل</option>
<option value="essential">زيوت عطرية</option>
<option value="fixed">زيوت ثابتة</option>
<option value="hydrosol">مياه عطرية</option>
<option value="natural">مستحضرات طبيعية</option>
</select>
</div>
<div class="filter-group">
<label>السعر</label>
<div class="price-range">
<input type="number" id="min-price" placeholder="من">
<span>-</span>
<input type="number" id="max-price" placeholder="إلى">
</div>
</div>
<div class="filter-group">
<label>الترتيب حسب</label>
<select id="sort-by">
<option value="relevance">الأكثر صلة</option>
<option value="price-asc">السعر: من الأقل للأعلى</option>
<option value="price-desc">السعر: من الأعلى للأقل</option>
<option value="name-asc">الاسم: أ-ي</option>
</select>
</div>
</div>
</form>
<div class="search-results" id="search-results">
<!-- سيتم ملء النتائج ديناميكياً -->
</div>
<div class="comparison-tool">
<h3>مقارنة المنتجات</h3>
<div id="comparison-list" class="comparison-list">
<!-- سيتم إضافة المنتجات للمقارنة هنا -->
</div>
<button id="compare-btn" class="btn" disabled>قارن المنتجات</button>
</div>
</div>
</div>
</section>
</main>
<footer>
<!-- نفس الفوتر السابق -->
</footer>
<script src="js/search.js"></script>
</body>
</html>

33
sitemap.xml Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://shubraveil.com/</loc>
<lastmod>2024-12-09</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://shubraveil.com/products</loc>
<lastmod>2024-12-09</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://shubraveil.com/blog.html</loc>
<lastmod>2024-12-09</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://shubraveil.com/faq.html</loc>
<lastmod>2024-12-09</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://shubraveil.com/search.html</loc>
<lastmod>2024-12-09</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
</urlset>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>تأكيد الطلب - ShubraVeil</title>
<style>
body { font-family: 'Tajawal', Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { text-align: center; padding: 20px 0; }
.content { background: #f9f9f9; padding: 20px; border-radius: 5px; }
.footer { text-align: center; padding: 20px 0; color: #666; }
.button { display: inline-block; padding: 10px 20px; background: #0c814a; color: white; text-decoration: none; border-radius: 5px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>شكراً لطلبك من ShubraVeil</h1>
</div>
<div class="content">
<h2>مرحباً {USER_NAME}،</h2>
<p>نشكرك على طلبك. تم استلام طلبك بنجاح وجاري تجهيزه.</p>
<h3>تفاصيل الطلب:</h3>
<p>رقم الطلب: {ORDER_ID}</p>
<p>إجمالي المبلغ: {ORDER_TOTAL} جنيه</p>
<p>يمكنك متابعة حالة طلبك من خلال حسابك على موقعنا.</p>
<div style="text-align: center; margin: 30px 0;">
<a href="{SITE_URL}/orders" class="button">متابعة طلبك</a>
</div>
</div>
<div class="footer">
<p>إذا كان لديك أي استفسار، يرجى التواصل معنا على البريد الإلكتروني: support@shubraveil.com</p>
<p>مع تحيات فريق ShubraVeil</p>
</div>
</div>
</body>
</html>

74
test_backup.php Normal file
View File

@ -0,0 +1,74 @@
<?php
require_once __DIR__ . '/includes/config.php';
echo "=== اختبار نظام النسخ الاحتياطي ===\n\n";
class DatabaseBackup {
private $db_host;
private $db_user;
private $db_pass;
private $db_name;
private $backup_dir;
public function __construct() {
$this->db_host = DB_SERVER;
$this->db_user = DB_USERNAME;
$this->db_pass = DB_PASSWORD;
$this->db_name = DB_NAME;
$this->backup_dir = __DIR__ . '/backups';
if (!is_dir($this->backup_dir)) {
mkdir($this->backup_dir, 0755, true);
}
}
public function createBackup() {
$date = date('Y-m-d_H-i-s');
$backup_file = $this->backup_dir . "/backup_" . $date . ".sql";
$command = sprintf(
'mysqldump --host=%s --user=%s --password=%s %s > %s',
escapeshellarg($this->db_host),
escapeshellarg($this->db_user),
escapeshellarg($this->db_pass),
escapeshellarg($this->db_name),
escapeshellarg($backup_file)
);
system($command, $return_var);
if ($return_var === 0) {
echo "تم إنشاء النسخة الاحتياطية بنجاح: " . basename($backup_file) . "\n";
return $backup_file;
} else {
throw new Exception("فشل إنشاء النسخة الاحتياطية");
}
}
public function listBackups() {
$backups = glob($this->backup_dir . "/*.sql");
echo "\nالنسخ الاحتياطية المتوفرة:\n";
foreach ($backups as $backup) {
echo "- " . basename($backup) . " (" . $this->formatSize(filesize($backup)) . ")\n";
}
}
private function formatSize($size) {
$units = ['B', 'KB', 'MB', 'GB'];
$power = $size > 0 ? floor(log($size, 1024)) : 0;
return number_format($size / pow(1024, $power), 2, '.', ',') . ' ' . $units[$power];
}
}
try {
$backup = new DatabaseBackup();
// إنشاء نسخة احتياطية
$backup_file = $backup->createBackup();
// عرض قائمة النسخ الاحتياطية
$backup->listBackups();
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage() . "\n";
}

37
test_connection.php Normal file
View File

@ -0,0 +1,37 @@
<?php
require_once __DIR__ . '/includes/config.php';
try {
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception("فشل الاتصال: " . $conn->connect_error);
}
echo "تم الاتصال بقاعدة البيانات بنجاح!\n\n";
// اختبار استعلام لعرض الجداول
$result = $conn->query("SHOW TABLES");
if ($result) {
echo "الجداول الموجودة في قاعدة البيانات:\n";
while ($row = $result->fetch_array()) {
echo "- " . $row[0] . "\n";
}
}
// اختبار إنشاء مستخدم تجريبي
$username = "test_user";
$email = "test@example.com";
$password = password_hash("test123", PASSWORD_DEFAULT);
$stmt = $conn->prepare("INSERT IGNORE INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $email, $password);
if ($stmt->execute()) {
echo "\nتم إنشاء مستخدم تجريبي بنجاح أو كان موجوداً مسبقاً\n";
}
$conn->close();
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage();
}

25
test_db.php Normal file
View File

@ -0,0 +1,25 @@
<?php
require_once __DIR__ . '/includes/config.php';
try {
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception("Connection failed: " . $conn->connect_error);
}
echo "Database connection successful!\n";
// Test query
$result = $conn->query("SHOW TABLES");
if ($result) {
echo "\nTables in database:\n";
while ($row = $result->fetch_array()) {
echo "- " . $row[0] . "\n";
}
}
$conn->close();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}

104
test_images.php Normal file
View File

@ -0,0 +1,104 @@
<?php
require_once __DIR__ . '/includes/config.php';
echo "=== اختبار نظام معالجة الصور ===\n\n";
class ImageProcessor {
private $upload_dir;
private $cache_dir;
private $allowed_types;
private $max_size;
public function __construct() {
$this->upload_dir = __DIR__ . '/uploads/images';
$this->cache_dir = __DIR__ . '/cache/images';
$this->allowed_types = ['image/jpeg', 'image/png', 'image/webp'];
$this->max_size = 5 * 1024 * 1024; // 5MB
// إنشاء المجلدات إذا لم تكن موجودة
foreach ([$this->upload_dir, $this->cache_dir] as $dir) {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
}
public function processTestImage() {
// إنشاء صورة اختبار
$width = 800;
$height = 600;
$image = imagecreatetruecolor($width, $height);
// تلوين الصورة
$bg_color = imagecolorallocate($image, 240, 240, 240);
$text_color = imagecolorallocate($image, 50, 50, 50);
imagefill($image, 0, 0, $bg_color);
// إضافة نص
$text = "ShubraVeil Test Image";
$font_size = 5;
// حساب موقع النص
$text_width = imagefontwidth($font_size) * strlen($text);
$text_height = imagefontheight($font_size);
$x = ($width - $text_width) / 2;
$y = ($height - $text_height) / 2;
imagestring($image, $font_size, $x, $y, $text, $text_color);
// حفظ الصورة الأصلية
$original_file = $this->upload_dir . '/test_image.png';
imagepng($image, $original_file);
echo "تم إنشاء صورة الاختبار: " . basename($original_file) . "\n";
echo "الحجم: " . $this->formatSize(filesize($original_file)) . "\n\n";
// إنشاء نسخة مصغرة
$thumb_width = 150;
$thumb_height = 150;
$thumbnail = imagecreatetruecolor($thumb_width, $thumb_height);
imagecopyresampled(
$thumbnail, $image,
0, 0, 0, 0,
$thumb_width, $thumb_height,
$width, $height
);
$thumb_file = $this->cache_dir . '/thumb_test_image.png';
imagepng($thumbnail, $thumb_file);
echo "تم إنشاء النسخة المصغرة: " . basename($thumb_file) . "\n";
echo "الحجم: " . $this->formatSize(filesize($thumb_file)) . "\n";
// تنظيف الذاكرة
imagedestroy($image);
imagedestroy($thumbnail);
return [
'original' => $original_file,
'thumbnail' => $thumb_file
];
}
private function formatSize($size) {
$units = ['B', 'KB', 'MB', 'GB'];
$power = $size > 0 ? floor(log($size, 1024)) : 0;
return number_format($size / pow(1024, $power), 2, '.', ',') . ' ' . $units[$power];
}
}
try {
$processor = new ImageProcessor();
$files = $processor->processTestImage();
echo "\nاختبار اكتمل بنجاح!\n";
echo "الملفات التي تم إنشاؤها:\n";
echo "- الصورة الأصلية: " . $files['original'] . "\n";
echo "- النسخة المصغرة: " . $files['thumbnail'] . "\n";
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage() . "\n";
}

105
test_orders.php Normal file
View File

@ -0,0 +1,105 @@
<?php
require_once __DIR__ . '/includes/config.php';
require_once __DIR__ . '/includes/Security.php';
echo "=== اختبار نظام إدارة الطلبات ===\n\n";
try {
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception("فشل الاتصال: " . $conn->connect_error);
}
// إنشاء مستخدم للاختبار
$stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$username = "test_user_" . time(); // اسم مستخدم فريد
$email = "test_" . time() . "@example.com";
$password = Security::hashPassword("test123");
$stmt->bind_param("sss", $username, $email, $password);
$stmt->execute();
$user_id = $conn->insert_id;
echo "تم إنشاء مستخدم للاختبار (ID: $user_id)\n";
// إنشاء منتج للاختبار
$stmt = $conn->prepare("INSERT INTO products (name, description, price, stock) VALUES (?, ?, ?, ?)");
$name = "زيت الورد الطبيعي";
$description = "زيت ورد طبيعي 100% للعناية بالبشرة";
$price = 199.99;
$stock = 30;
$stmt->bind_param("ssdi", $name, $description, $price, $stock);
$stmt->execute();
$product_id = $conn->insert_id;
echo "تم إنشاء منتج للاختبار (ID: $product_id)\n";
// إنشاء طلب جديد
$stmt = $conn->prepare("INSERT INTO orders (user_id, total_amount, status) VALUES (?, ?, ?)");
$total = 199.99;
$status = 'pending';
$stmt->bind_param("ids", $user_id, $total, $status);
$stmt->execute();
$order_id = $conn->insert_id;
echo "تم إنشاء طلب جديد (ID: $order_id)\n";
// إضافة عناصر الطلب
$stmt = $conn->prepare("INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)");
$quantity = 1;
$stmt->bind_param("iiid", $order_id, $product_id, $quantity, $price);
$stmt->execute();
echo "تم إضافة عناصر الطلب\n";
// استرجاع معلومات الطلب
$query = "
SELECT o.*, oi.quantity, oi.price as item_price, p.name as product_name, u.username
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
JOIN users u ON o.user_id = u.id
WHERE o.id = ?
";
$stmt = $conn->prepare($query);
$stmt->bind_param("i", $order_id);
$stmt->execute();
$result = $stmt->get_result();
$order = $result->fetch_assoc();
echo "\nتفاصيل الطلب:\n";
echo "رقم الطلب: " . $order['id'] . "\n";
echo "المستخدم: " . $order['username'] . "\n";
echo "المنتج: " . $order['product_name'] . "\n";
echo "الكمية: " . $order['quantity'] . "\n";
echo "السعر: " . $order['item_price'] . " جنيه\n";
echo "الإجمالي: " . $order['total_amount'] . " جنيه\n";
echo "الحالة: " . $order['status'] . "\n";
// تحديث حالة الطلب
$stmt = $conn->prepare("UPDATE orders SET status = ? WHERE id = ?");
$new_status = 'delivered';
$stmt->bind_param("si", $new_status, $order_id);
$stmt->execute();
echo "\nتم تحديث حالة الطلب إلى: delivered\n";
// تنظيف البيانات
$conn->query("DELETE FROM order_items WHERE order_id = $order_id");
$conn->query("DELETE FROM orders WHERE id = $order_id");
$conn->query("DELETE FROM products WHERE id = $product_id");
$conn->query("DELETE FROM users WHERE id = $user_id");
echo "\nتم تنظيف بيانات الاختبار بنجاح\n";
$conn->close();
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage() . "\n";
}

84
test_products.php Normal file
View File

@ -0,0 +1,84 @@
<?php
require_once __DIR__ . '/includes/config.php';
require_once __DIR__ . '/includes/Security.php';
echo "=== اختبار نظام إدارة المنتجات ===\n\n";
try {
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception("فشل الاتصال: " . $conn->connect_error);
}
// إضافة منتج جديد
$stmt = $conn->prepare("INSERT INTO products (name, description, price, stock, category) VALUES (?, ?, ?, ?, ?)");
$name = "زيت اللافندر الطبيعي";
$description = "زيت لافندر طبيعي 100% للعناية بالبشرة والشعر";
$price = 149.99;
$stock = 50;
$category = "essential_oils";
$stmt->bind_param("ssdis", $name, $description, $price, $stock, $category);
if ($stmt->execute()) {
$product_id = $conn->insert_id;
echo "تم إضافة المنتج بنجاح (ID: $product_id)\n";
}
// استرجاع المنتج
$stmt = $conn->prepare("SELECT * FROM products WHERE id = ?");
$stmt->bind_param("i", $product_id);
$stmt->execute();
$result = $stmt->get_result();
$product = $result->fetch_assoc();
echo "\nبيانات المنتج:\n";
echo "الاسم: " . $product['name'] . "\n";
echo "السعر: " . $product['price'] . " جنيه\n";
echo "المخزون: " . $product['stock'] . " قطعة\n";
// تحديث المنتج
$new_price = 139.99;
$new_stock = 45;
$stmt = $conn->prepare("UPDATE products SET price = ?, stock = ? WHERE id = ?");
$stmt->bind_param("dii", $new_price, $new_stock, $product_id);
if ($stmt->execute()) {
echo "\nتم تحديث المنتج بنجاح\n";
}
// التحقق من التحديث
$stmt = $conn->prepare("SELECT price, stock FROM products WHERE id = ?");
$stmt->bind_param("i", $product_id);
$stmt->execute();
$result = $stmt->get_result();
$updated_product = $result->fetch_assoc();
echo "\nبيانات المنتج بعد التحديث:\n";
echo "السعر الجديد: " . $updated_product['price'] . " جنيه\n";
echo "المخزون الجديد: " . $updated_product['stock'] . " قطعة\n";
// حذف المنتج
$stmt = $conn->prepare("DELETE FROM products WHERE id = ?");
$stmt->bind_param("i", $product_id);
if ($stmt->execute()) {
echo "\nتم حذف المنتج بنجاح\n";
}
// التحقق من الحذف
$stmt = $conn->prepare("SELECT COUNT(*) as count FROM products WHERE id = ?");
$stmt->bind_param("i", $product_id);
$stmt->execute();
$result = $stmt->get_result();
$count = $result->fetch_assoc()['count'];
echo "التحقق من الحذف: " . ($count == 0 ? "ناجح ✓" : "فشل ✗") . "\n";
$conn->close();
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage() . "\n";
}

33
test_security.php Normal file
View File

@ -0,0 +1,33 @@
<?php
require_once __DIR__ . '/includes/config.php';
require_once __DIR__ . '/includes/Security.php';
echo "=== اختبار وظائف الأمان ===\n\n";
// اختبار تشفير وفك تشفير البيانات
$testData = "بيانات حساسة للاختبار";
echo "البيانات الأصلية: " . $testData . "\n";
$encrypted = Security::encrypt($testData);
echo "البيانات المشفرة: " . $encrypted . "\n";
$decrypted = Security::decrypt($encrypted);
echo "البيانات بعد فك التشفير: " . $decrypted . "\n";
echo "نتيجة المقارنة: " . ($testData === $decrypted ? "ناجح ✓" : "فشل ✗") . "\n\n";
// اختبار تنظيف المدخلات
$dirtyInput = "<script>alert('XSS');</script>Hello' OR '1'='1";
echo "المدخلات غير النظيفة: " . $dirtyInput . "\n";
echo "المدخلات بعد التنظيف: " . Security::sanitizeInput($dirtyInput) . "\n\n";
// اختبار توليد والتحقق من رمز CSRF
$token = Security::generateCSRFToken();
echo "تم توليد رمز CSRF: " . $token . "\n";
echo "التحقق من الرمز: " . (Security::verifyCSRFToken($token) ? "ناجح ✓" : "فشل ✗") . "\n\n";
// اختبار تشفير كلمة المرور
$password = "TestPassword123";
$hashedPassword = Security::hashPassword($password);
echo "كلمة المرور الأصلية: " . $password . "\n";
echo "كلمة المرور المشفرة: " . $hashedPassword . "\n";
echo "التحقق من كلمة المرور: " . (Security::verifyPassword($password, $hashedPassword) ? "ناجح ✓" : "فشل ✗") . "\n";

102
test_user_system.php Normal file
View File

@ -0,0 +1,102 @@
<?php
require_once 'includes/config.php';
require_once 'includes/Security.php';
echo "=== اختبار نظام المستخدمين ===\n\n";
try {
// اختبار الاتصال بقاعدة البيانات
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception("فشل الاتصال بقاعدة البيانات: " . $conn->connect_error);
}
echo "✓ تم الاتصال بقاعدة البيانات بنجاح\n";
// التحقق من وجود جدول المستخدمين
$result = $conn->query("SHOW TABLES LIKE 'users'");
if ($result->num_rows > 0) {
echo "✓ جدول المستخدمين موجود\n";
} else {
throw new Exception("جدول المستخدمين غير موجود");
}
// التحقق من وجود جدول إعادة تعيين كلمات المرور
$result = $conn->query("SHOW TABLES LIKE 'password_resets'");
if ($result->num_rows > 0) {
echo "✓ جدول إعادة تعيين كلمات المرور موجود\n";
} else {
throw new Exception("جدول إعادة تعيين كلمات المرور غير موجود");
}
// اختبار إنشاء مستخدم جديد
$test_username = "test_user_" . time();
$test_email = "test_" . time() . "@example.com";
$test_password = "Test@123456";
$hashed_password = Security::hashPassword($test_password);
$stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $test_username, $test_email, $hashed_password);
if ($stmt->execute()) {
echo "✓ تم إنشاء مستخدم اختباري بنجاح\n";
$test_user_id = $stmt->insert_id;
} else {
throw new Exception("فشل في إنشاء مستخدم اختباري");
}
// اختبار تسجيل الدخول
$stmt = $conn->prepare("SELECT id, password FROM users WHERE email = ?");
$stmt->bind_param("s", $test_email);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
if (Security::verifyPassword($test_password, $row['password'])) {
echo "✓ تم التحقق من كلمة المرور بنجاح\n";
} else {
throw new Exception("فشل في التحقق من كلمة المرور");
}
} else {
throw new Exception("فشل في العثور على المستخدم");
}
// اختبار إنشاء رمز إعادة تعيين كلمة المرور
$token = bin2hex(random_bytes(32));
$expiry = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $conn->prepare("INSERT INTO password_resets (user_id, token, expiry) VALUES (?, ?, ?)");
$stmt->bind_param("iss", $test_user_id, $token, $expiry);
if ($stmt->execute()) {
echo "✓ تم إنشاء رمز إعادة تعيين كلمة المرور بنجاح\n";
} else {
throw new Exception("فشل في إنشاء رمز إعادة تعيين كلمة المرور");
}
// اختبار التحقق من رمز إعادة تعيين كلمة المرور
$stmt = $conn->prepare("SELECT user_id FROM password_resets WHERE token = ? AND expiry > NOW() AND used = 0");
$stmt->bind_param("s", $token);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
echo "✓ تم التحقق من رمز إعادة تعيين كلمة المرور بنجاح\n";
} else {
throw new Exception("فشل في التحقق من رمز إعادة تعيين كلمة المرور");
}
// تنظيف البيانات الاختبارية
$conn->query("DELETE FROM password_resets WHERE user_id = " . $test_user_id);
$conn->query("DELETE FROM users WHERE id = " . $test_user_id);
echo "✓ تم تنظيف البيانات الاختبارية بنجاح\n";
echo "\n=== تم اكتمال الاختبار بنجاح ===\n";
} catch (Exception $e) {
echo "\n❌ خطأ: " . $e->getMessage() . "\n";
} finally {
if (isset($conn)) {
$conn->close();
}
}

74
test_users.php Normal file
View File

@ -0,0 +1,74 @@
<?php
require_once __DIR__ . '/includes/config.php';
require_once __DIR__ . '/includes/Security.php';
echo "=== اختبار نظام إدارة المستخدمين ===\n\n";
try {
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
throw new Exception("فشل الاتصال: " . $conn->connect_error);
}
// تسجيل مستخدم جديد
$username = "test_user_" . time();
$email = "test_" . time() . "@example.com";
$password = "TestPass123!";
$hashed_password = Security::hashPassword($password);
$stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $email, $hashed_password);
if ($stmt->execute()) {
$user_id = $conn->insert_id;
echo "تم تسجيل المستخدم بنجاح (ID: $user_id)\n";
}
// محاولة تسجيل الدخول
$stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
if (Security::verifyPassword($password, $user['password'])) {
echo "تم تسجيل الدخول بنجاح\n";
} else {
echo "فشل تسجيل الدخول\n";
}
// تحديث معلومات المستخدم
$new_email = "updated_" . time() . "@example.com";
$stmt = $conn->prepare("UPDATE users SET email = ? WHERE id = ?");
$stmt->bind_param("si", $new_email, $user_id);
if ($stmt->execute()) {
echo "تم تحديث معلومات المستخدم بنجاح\n";
}
// التحقق من التحديث
$stmt = $conn->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$result = $stmt->get_result();
$updated_user = $result->fetch_assoc();
echo "\nمعلومات المستخدم المحدثة:\n";
echo "اسم المستخدم: " . $updated_user['username'] . "\n";
echo "البريد الإلكتروني: " . $updated_user['email'] . "\n";
echo "نوع المستخدم: " . $updated_user['role'] . "\n";
// تنظيف البيانات
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
if ($stmt->execute()) {
echo "\nتم حذف بيانات الاختبار بنجاح\n";
}
$conn->close();
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage() . "\n";
}

46
tests/SecurityTest.php Normal file
View File

@ -0,0 +1,46 @@
<?php
use PHPUnit\Framework\TestCase;
class SecurityTest extends TestCase {
private $security;
protected function setUp(): void {
$this->security = Security::getInstance();
}
public function testSanitizeInput() {
$input = "<script>alert('XSS')</script>";
$expected = "&lt;script&gt;alert(&#039;XSS&#039;)&lt;/script&gt;";
$this->assertEquals($expected, $this->security->sanitizeInput($input));
}
public function testCSRFToken() {
$token = $this->security->generateCSRFToken();
$this->assertTrue($this->security->validateCSRFToken($token));
$this->assertFalse($this->security->validateCSRFToken('invalid_token'));
}
public function testJWT() {
$payload = ['user_id' => 1, 'role' => 'admin'];
$token = $this->security->generateJWT($payload);
$decoded = $this->security->validateJWT($token);
$this->assertIsArray($decoded);
$this->assertEquals(1, $decoded['user_id']);
$this->assertEquals('admin', $decoded['role']);
}
public function testRateLimit() {
$ip = '127.0.0.1';
$endpoint = 'test_endpoint';
// Should allow first request
$this->assertTrue($this->security->checkRateLimit($ip, $endpoint, 2, 3600));
// Should allow second request
$this->assertTrue($this->security->checkRateLimit($ip, $endpoint, 2, 3600));
// Should block third request
$this->assertFalse($this->security->checkRateLimit($ip, $endpoint, 2, 3600));
}
}

1
uploads/.gitkeep Normal file
View File

@ -0,0 +1 @@