Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

11 changed files with 88 additions and 955 deletions

View File

@ -7,42 +7,36 @@ RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Protect sensitive files
<FilesMatch "^\.env|composer\.json|composer\.lock|package\.json|package-lock\.json|README\.md|\.gitignore">
<FilesMatch "^\.env">
Order allow,deny
Deny from all
</FilesMatch>
# Protect directories
<DirectoryMatch "^/.*/(?:logs|backups|cache)/|^/.git/|/.github/|/vendor/|/node_modules/">
<DirectoryMatch "^/.*/(?:logs|backups|cache)/">
Order allow,deny
Deny from all
</DirectoryMatch>
# Security headers
# 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 Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'"
Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Cache control
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|ico|webp)$">
# Cache Control
<FilesMatch "\.(jpg|jpeg|png|gif|ico|css|js)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
# PHP settings
php_flag display_errors off
php_value upload_max_filesize 5M
php_value post_max_size 6M
php_value max_execution_time 30
php_value max_input_time 60
php_value memory_limit 128M
php_value error_reporting E_ALL
php_value error_log logs/php_errors.log
# Directory protection
# Prevent directory listing
Options -Indexes
ServerSignature Off
# Custom error pages
ErrorDocument 404 /404.html

120
README.md
View File

@ -2,32 +2,62 @@
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 المطلوبة:
- تمكين امتدادات PHP التالية:
- GD
- MySQLi
- ZIP
- JSON
- OpenSSL
## التثبيت
### التثبيت
1. استنساخ المستودع:
```bash
git clone https://github.com/yourusername/shubraveil.git
@ -39,7 +69,7 @@ cd shubraveil
composer install
```
3. إنشاء وتكوين ملف .env:
3. إنشاء ملف .env:
```bash
cp .env.example .env
```
@ -49,61 +79,27 @@ cp .env.example .env
```bash
mysql -u root -p
CREATE DATABASE shubraveil_db;
mysql -u root -p shubraveil_db < database/schema.sql
```
5. تهيئة المجلدات وضبط الصلاحيات:
5. تهيئة المجلدات:
```bash
mkdir -p uploads/products cache backups
mkdir -p uploads/products
mkdir -p cache
mkdir -p backups
chmod -R 755 uploads cache backups
```
## الأمان
### التكوين
#### الإعدادات الأساسية
1. تكوين قاعدة البيانات في ملف .env
2. تكوين SMTP لإرسال البريد الإلكتروني
3. تكوين مفاتيح reCAPTCHA
- تم تفعيل HTTPS إجبارياً
- حماية الملفات والمجلدات الحساسة
- استخدام CSRF tokens لحماية النماذج
- تشفير كلمات المرور باستخدام password_hash
- استخدام Prepared Statements لمنع SQL Injection
- تصفية وتنظيف جميع المدخلات
- رسائل خطأ آمنة لا تكشف معلومات حساسة
#### الأمان
- تأكد من تعيين كلمات مرور قوية
- قم بتحديث مفتاح JWT_SECRET
- قم بتكوين HTTPS
## هيكل المشروع
## Development
```
shubraveil/
├── admin/ # لوحة التحكم
├── api/ # واجهة برمجة التطبيقات
├── backups/ # النسخ الاحتياطية
├── cache/ # التخزين المؤقت
├── css/ # ملفات CSS
├── database/ # ملفات قاعدة البيانات
├── images/ # الصور الثابتة
├── includes/ # ملفات PHP المشتركة
├── js/ # ملفات JavaScript
├── products/ # صفحات المنتجات
├── templates/ # قوالب الصفحات
├── uploads/ # الملفات المرفوعة
└── tests/ # اختبارات الوحدة
```
## الاختبارات
يمكن تشغيل الاختبارات باستخدام:
```bash
composer test
```
## النسخ الاحتياطي
يتم إنشاء نسخة احتياطية تلقائياً كل يوم في الساعة 12 صباحاً في مجلد `backups/`.
## المساهمة
1. Fork المستودع
2. إنشاء فرع للميزة الجديدة
3. إرسال pull request
## الترخيص
جميع الحقوق محفوظة © 2024 ShubraVeil
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.

View File

@ -1,32 +1,24 @@
{
"name": "shubraveil/essential-oils",
"description": "ShubraVeil Essential Oils E-commerce Platform",
"name": "shubraveil/website",
"description": "ShubraVeil - نظام إدارة متجر الحجاب",
"type": "project",
"require": {
"php": "^7.4|^8.0",
"stripe/stripe-php": "^10.0",
"paypal/rest-api-sdk-php": "^1.14",
"kreait/firebase-php": "^5.0",
"php": ">=7.4",
"phpmailer/phpmailer": "^6.8",
"firebase/php-jwt": "^6.4",
"vlucas/phpdotenv": "^5.5",
"monolog/monolog": "^2.9",
"intervention/image": "^2.7",
"guzzlehttp/guzzle": "^7.7",
"ext-json": "*",
"ext-pdo": "*",
"ext-mysqli": "*",
"ext-gd": "*",
"ext-intl": "*"
"guzzlehttp/guzzle": "^7.0",
"monolog/monolog": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6",
"phpunit/phpunit": "^9.5",
"phpstan/phpstan": "^1.10",
"squizlabs/php_codesniffer": "^3.7",
"fakerphp/faker": "^1.23"
"squizlabs/php_codesniffer": "^3.7"
},
"autoload": {
"psr-4": {
"ShubraVeil\\": "src/"
"ShubraVeil\\": "includes/"
}
},
"autoload-dev": {
@ -36,9 +28,11 @@
},
"scripts": {
"test": "phpunit",
"phpstan": "phpstan analyse",
"analyse": "phpstan analyse",
"cs": "phpcs",
"cs-fix": "phpcbf"
"post-install-cmd": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
]
},
"config": {
"optimize-autoloader": true,

View File

@ -1,112 +0,0 @@
<?php
class Language {
private static $translations = [];
private static $currentLocale = 'ar';
private static $fallbackLocale = 'en';
private static $supportedLocales = ['ar', 'en'];
public static function init($locale = null) {
if ($locale && in_array($locale, self::$supportedLocales)) {
self::$currentLocale = $locale;
} elseif (isset($_SESSION['locale'])) {
self::$currentLocale = $_SESSION['locale'];
} else {
// Try to detect from browser
$browserLang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
self::$currentLocale = in_array($browserLang, self::$supportedLocales) ? $browserLang : self::$fallbackLocale;
}
$_SESSION['locale'] = self::$currentLocale;
self::loadTranslations();
}
public static function setLocale($locale) {
if (in_array($locale, self::$supportedLocales)) {
self::$currentLocale = $locale;
$_SESSION['locale'] = $locale;
self::loadTranslations();
return true;
}
return false;
}
public static function getCurrentLocale() {
return self::$currentLocale;
}
public static function isRTL() {
return in_array(self::$currentLocale, ['ar']);
}
public static function getDirection() {
return self::isRTL() ? 'rtl' : 'ltr';
}
public static function translate($key, $params = []) {
$translation = self::$translations[self::$currentLocale][$key] ??
self::$translations[self::$fallbackLocale][$key] ??
$key;
if (!empty($params)) {
foreach ($params as $param => $value) {
$translation = str_replace(':' . $param, $value, $translation);
}
}
return $translation;
}
private static function loadTranslations() {
// Load current locale
$localePath = __DIR__ . '/../lang/' . self::$currentLocale . '.php';
if (file_exists($localePath)) {
self::$translations[self::$currentLocale] = require $localePath;
}
// Load fallback locale if different
if (self::$currentLocale !== self::$fallbackLocale) {
$fallbackPath = __DIR__ . '/../lang/' . self::$fallbackLocale . '.php';
if (file_exists($fallbackPath)) {
self::$translations[self::$fallbackLocale] = require $fallbackPath;
}
}
}
public static function getAllTranslations() {
return self::$translations;
}
public static function getSupportedLocales() {
return self::$supportedLocales;
}
// Helper function for date formatting
public static function formatDate($timestamp, $format = 'full') {
$locale = self::$currentLocale . '_' . strtoupper(self::$currentLocale);
setlocale(LC_TIME, $locale . '.UTF-8');
$formats = [
'full' => '%A %d %B %Y',
'long' => '%d %B %Y',
'medium' => '%d %b %Y',
'short' => '%d/%m/%Y'
];
return strftime($formats[$format] ?? $formats['full'], $timestamp);
}
// Helper function for number formatting
public static function formatNumber($number, $decimals = 0) {
$locale = self::$currentLocale . '_' . strtoupper(self::$currentLocale);
$formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL);
$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $decimals);
return $formatter->format($number);
}
// Helper function for currency formatting
public static function formatCurrency($amount, $currency = 'EGP') {
$locale = self::$currentLocale . '_' . strtoupper(self::$currentLocale);
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
return $formatter->formatCurrency($amount, $currency);
}
}

View File

@ -1,105 +0,0 @@
<?php
class Notification {
private $db;
private $mailer;
public function __construct($db, $mailer) {
$this->db = $db;
$this->mailer = $mailer;
}
public function createNotification($user_id, $type, $message, $data = null) {
$stmt = $this->db->prepare("INSERT INTO notifications (user_id, type, message, data, created_at) VALUES (?, ?, ?, ?, NOW())");
$data_json = $data ? json_encode($data) : null;
$stmt->bind_param('isss', $user_id, $type, $message, $data_json);
$stmt->execute();
// Send email notification if enabled
$this->sendEmailNotification($user_id, $type, $message);
// Send push notification if enabled
$this->sendPushNotification($user_id, $type, $message);
return $stmt->insert_id;
}
public function getUserNotifications($user_id, $limit = 10, $offset = 0) {
$stmt = $this->db->prepare("SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?");
$stmt->bind_param('iii', $user_id, $limit, $offset);
$stmt->execute();
return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}
public function markAsRead($notification_id, $user_id) {
$stmt = $this->db->prepare("UPDATE notifications SET read_at = NOW() WHERE id = ? AND user_id = ?");
$stmt->bind_param('ii', $notification_id, $user_id);
return $stmt->execute();
}
public function markAllAsRead($user_id) {
$stmt = $this->db->prepare("UPDATE notifications SET read_at = NOW() WHERE user_id = ? AND read_at IS NULL");
$stmt->bind_param('i', $user_id);
return $stmt->execute();
}
public function getUnreadCount($user_id) {
$stmt = $this->db->prepare("SELECT COUNT(*) as count FROM notifications WHERE user_id = ? AND read_at IS NULL");
$stmt->bind_param('i', $user_id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
return $result['count'];
}
private function sendEmailNotification($user_id, $type, $message) {
// Get user email
$stmt = $this->db->prepare("SELECT email, notification_preferences FROM users WHERE id = ?");
$stmt->bind_param('i', $user_id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
if ($user && $this->shouldSendEmail($user['notification_preferences'], $type)) {
$this->mailer->sendEmail(
$user['email'],
"New Notification from ShubraVeil",
$message
);
}
}
private function sendPushNotification($user_id, $type, $message) {
// Get user's push notification token
$stmt = $this->db->prepare("SELECT push_token, notification_preferences FROM users WHERE id = ?");
$stmt->bind_param('i', $user_id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
if ($user && $this->shouldSendPush($user['notification_preferences'], $type) && $user['push_token']) {
// Initialize Firebase
$firebase = new \Kreait\Firebase\Factory();
$messaging = $firebase->createMessaging();
$message = \Kreait\Firebase\Messaging\CloudMessage::withTarget('token', $user['push_token'])
->withNotification([
'title' => 'ShubraVeil',
'body' => $message
]);
try {
$messaging->send($message);
} catch (\Exception $e) {
// Log error but don't throw
error_log("Push notification failed: " . $e->getMessage());
}
}
}
private function shouldSendEmail($preferences, $type) {
$prefs = json_decode($preferences, true);
return isset($prefs['email'][$type]) ? $prefs['email'][$type] : true;
}
private function shouldSendPush($preferences, $type) {
$prefs = json_decode($preferences, true);
return isset($prefs['push'][$type]) ? $prefs['push'][$type] : true;
}
}

View File

@ -1,109 +0,0 @@
<?php
class Payment {
private $db;
private $stripe;
private $paypal;
public function __construct($db) {
$this->db = $db;
// Initialize Stripe
if (getenv('STRIPE_SECRET_KEY')) {
\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
$this->stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET_KEY'));
}
// Initialize PayPal
if (getenv('PAYPAL_CLIENT_ID') && getenv('PAYPAL_CLIENT_SECRET')) {
$this->paypal = new \PayPal\Rest\ApiContext(
new \PayPal\Auth\OAuthTokenCredential(
getenv('PAYPAL_CLIENT_ID'),
getenv('PAYPAL_CLIENT_SECRET')
)
);
}
}
public function processStripePayment($amount, $currency, $token, $description) {
try {
$charge = \Stripe\Charge::create([
'amount' => $amount * 100, // Convert to cents
'currency' => $currency,
'source' => $token,
'description' => $description,
]);
$this->saveTransaction($charge->id, 'stripe', $amount, $currency, $charge->status);
return ['success' => true, 'transaction_id' => $charge->id];
} catch (\Stripe\Exception\CardException $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
public function processPayPalPayment($amount, $currency, $returnUrl, $cancelUrl) {
$payer = new \PayPal\Api\Payer();
$payer->setPaymentMethod('paypal');
$amount = new \PayPal\Api\Amount();
$amount->setTotal($amount);
$amount->setCurrency($currency);
$transaction = new \PayPal\Api\Transaction();
$transaction->setAmount($amount);
$redirectUrls = new \PayPal\Api\RedirectUrls();
$redirectUrls->setReturnUrl($returnUrl)
->setCancelUrl($cancelUrl);
$payment = new \PayPal\Api\Payment();
$payment->setIntent('sale')
->setPayer($payer)
->setTransactions(array($transaction))
->setRedirectUrls($redirectUrls);
try {
$payment->create($this->paypal);
return ['success' => true, 'approval_url' => $payment->getApprovalLink()];
} catch (\PayPal\Exception\PayPalConnectionException $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
public function executePayPalPayment($paymentId, $payerId) {
$payment = \PayPal\Api\Payment::get($paymentId, $this->paypal);
$execution = new \PayPal\Api\PaymentExecution();
$execution->setPayerId($payerId);
try {
$result = $payment->execute($execution, $this->paypal);
$this->saveTransaction($result->getId(), 'paypal', $result->getTransactions()[0]->getAmount()->getTotal(), $result->getTransactions()[0]->getAmount()->getCurrency(), $result->getState());
return ['success' => true, 'transaction_id' => $result->getId()];
} catch (\PayPal\Exception\PayPalConnectionException $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
private function saveTransaction($transaction_id, $provider, $amount, $currency, $status) {
$stmt = $this->db->prepare("INSERT INTO transactions (transaction_id, provider, amount, currency, status, created_at) VALUES (?, ?, ?, ?, ?, NOW())");
$stmt->bind_param('ssdss', $transaction_id, $provider, $amount, $currency, $status);
$stmt->execute();
}
public function getTransaction($transaction_id) {
$stmt = $this->db->prepare("SELECT * FROM transactions WHERE transaction_id = ?");
$stmt->bind_param('s', $transaction_id);
$stmt->execute();
return $stmt->get_result()->fetch_assoc();
}
public function getUserTransactions($user_id) {
$stmt = $this->db->prepare("SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC");
$stmt->bind_param('i', $user_id);
$stmt->execute();
return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}
}

View File

@ -1,193 +0,0 @@
<?php
class Review {
private $db;
private $notification;
public function __construct($db, $notification) {
$this->db = $db;
$this->notification = $notification;
}
public function addReview($user_id, $product_id, $rating, $comment, $images = []) {
// Start transaction
$this->db->begin_transaction();
try {
// Add review
$stmt = $this->db->prepare("INSERT INTO reviews (user_id, product_id, rating, comment, created_at) VALUES (?, ?, ?, ?, NOW())");
$stmt->bind_param('iiis', $user_id, $product_id, $rating, $comment);
$stmt->execute();
$review_id = $stmt->insert_id;
// Add review images if any
if (!empty($images)) {
$stmt = $this->db->prepare("INSERT INTO review_images (review_id, image_path) VALUES (?, ?)");
foreach ($images as $image) {
$stmt->bind_param('is', $review_id, $image);
$stmt->execute();
}
}
// Update product rating
$this->updateProductRating($product_id);
// Notify product owner
$this->notifyProductOwner($product_id, $user_id, $rating);
$this->db->commit();
return $review_id;
} catch (\Exception $e) {
$this->db->rollback();
throw $e;
}
}
public function getProductReviews($product_id, $limit = 10, $offset = 0) {
$stmt = $this->db->prepare("
SELECT r.*, u.username, u.avatar,
GROUP_CONCAT(ri.image_path) as images
FROM reviews r
LEFT JOIN users u ON r.user_id = u.id
LEFT JOIN review_images ri ON r.id = ri.review_id
WHERE r.product_id = ?
GROUP BY r.id
ORDER BY r.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->bind_param('iii', $product_id, $limit, $offset);
$stmt->execute();
return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}
public function getUserReviews($user_id, $limit = 10, $offset = 0) {
$stmt = $this->db->prepare("
SELECT r.*, p.name as product_name, p.image as product_image,
GROUP_CONCAT(ri.image_path) as images
FROM reviews r
LEFT JOIN products p ON r.product_id = p.id
LEFT JOIN review_images ri ON r.id = ri.review_id
WHERE r.user_id = ?
GROUP BY r.id
ORDER BY r.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->bind_param('iii', $user_id, $limit, $offset);
$stmt->execute();
return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}
public function updateReview($review_id, $user_id, $rating, $comment, $images = []) {
$this->db->begin_transaction();
try {
// Update review
$stmt = $this->db->prepare("UPDATE reviews SET rating = ?, comment = ?, updated_at = NOW() WHERE id = ? AND user_id = ?");
$stmt->bind_param('isii', $rating, $comment, $review_id, $user_id);
$stmt->execute();
// Update images
if (!empty($images)) {
// Delete old images
$stmt = $this->db->prepare("DELETE FROM review_images WHERE review_id = ?");
$stmt->bind_param('i', $review_id);
$stmt->execute();
// Add new images
$stmt = $this->db->prepare("INSERT INTO review_images (review_id, image_path) VALUES (?, ?)");
foreach ($images as $image) {
$stmt->bind_param('is', $review_id, $image);
$stmt->execute();
}
}
// Update product rating
$stmt = $this->db->prepare("SELECT product_id FROM reviews WHERE id = ?");
$stmt->bind_param('i', $review_id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
$this->updateProductRating($result['product_id']);
$this->db->commit();
return true;
} catch (\Exception $e) {
$this->db->rollback();
throw $e;
}
}
public function deleteReview($review_id, $user_id) {
$this->db->begin_transaction();
try {
// Get product_id before deletion
$stmt = $this->db->prepare("SELECT product_id FROM reviews WHERE id = ? AND user_id = ?");
$stmt->bind_param('ii', $review_id, $user_id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if (!$result) {
throw new \Exception('Review not found or unauthorized');
}
// Delete review images
$stmt = $this->db->prepare("DELETE FROM review_images WHERE review_id = ?");
$stmt->bind_param('i', $review_id);
$stmt->execute();
// Delete review
$stmt = $this->db->prepare("DELETE FROM reviews WHERE id = ? AND user_id = ?");
$stmt->bind_param('ii', $review_id, $user_id);
$stmt->execute();
// Update product rating
$this->updateProductRating($result['product_id']);
$this->db->commit();
return true;
} catch (\Exception $e) {
$this->db->rollback();
throw $e;
}
}
private function updateProductRating($product_id) {
$stmt = $this->db->prepare("
UPDATE products p
SET rating = (
SELECT AVG(rating)
FROM reviews
WHERE product_id = ?
)
WHERE id = ?
");
$stmt->bind_param('ii', $product_id, $product_id);
$stmt->execute();
}
private function notifyProductOwner($product_id, $reviewer_id, $rating) {
$stmt = $this->db->prepare("SELECT user_id FROM products WHERE id = ?");
$stmt->bind_param('i', $product_id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if ($result) {
$message = $rating >= 4
? "Someone left a positive review on your product!"
: "You received a new review on your product.";
$this->notification->createNotification(
$result['user_id'],
'product_review',
$message,
[
'product_id' => $product_id,
'reviewer_id' => $reviewer_id,
'rating' => $rating
]
);
}
}
}

View File

@ -113,122 +113,4 @@ class Security {
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
public static function sanitize_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
return $data;
}
public static function generate_csrf_token() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function verify_csrf_token($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
public static function check_rate_limit($key, $limit = 5, $period = 300) {
$cache_key = "rate_limit:{$key}";
$current = isset($_SESSION[$cache_key]) ? $_SESSION[$cache_key] : ['count' => 0, 'timestamp' => time()];
if (time() - $current['timestamp'] > $period) {
$current = ['count' => 1, 'timestamp' => time()];
} else {
$current['count']++;
}
$_SESSION[$cache_key] = $current;
return $current['count'] <= $limit;
}
public static function is_password_strong($password) {
// Minimum 8 characters
if (strlen($password) < 8) return false;
// Must contain at least one uppercase letter
if (!preg_match('/[A-Z]/', $password)) return false;
// Must contain at least one lowercase letter
if (!preg_match('/[a-z]/', $password)) return false;
// Must contain at least one number
if (!preg_match('/[0-9]/', $password)) return false;
// Must contain at least one special character
if (!preg_match('/[!@#$%^&*()\-_=+{};:,<.>]/', $password)) return false;
return true;
}
public static function is_file_upload_safe($file) {
$allowed_types = ['image/jpeg', 'image/png', 'image/webp'];
$max_size = 5 * 1024 * 1024; // 5MB
// Check file type
if (!in_array($file['type'], $allowed_types)) {
return false;
}
// Check file size
if ($file['size'] > $max_size) {
return false;
}
// Check if file is actually an image
if (!getimagesize($file['tmp_name'])) {
return false;
}
return true;
}
public static function set_secure_headers() {
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'");
}
public static function secure_session_start() {
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_samesite', 'Strict');
session_start();
}
public static function log_security_event($event_type, $details) {
$log_file = __DIR__ . '/../logs/security.log';
$timestamp = date('Y-m-d H:i:s');
$ip = $_SERVER['REMOTE_ADDR'];
$user = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 'anonymous';
$log_entry = sprintf(
"[%s] %s - IP: %s, User: %s, Details: %s\n",
$timestamp,
$event_type,
$ip,
$user,
json_encode($details)
);
error_log($log_entry, 3, $log_file);
}
public static function init_security() {
self::secure_session_start();
self::set_secure_headers();
// Force HTTPS
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
exit();
}
}
}

View File

@ -15,14 +15,14 @@ if (getenv('DEBUG_MODE') === 'true') {
$env = parse_ini_file(__DIR__ . '/../.env');
// Database configuration
define('DB_SERVER', getenv('DB_SERVER') ?: 'localhost');
define('DB_USERNAME', getenv('DB_USERNAME') ?: 'root');
define('DB_PASSWORD', getenv('DB_PASSWORD') ?: '');
define('DB_NAME', getenv('DB_NAME') ?: 'shubraveil_db');
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', getenv('SITE_URL') ?: 'https://localhost/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

View File

@ -1,107 +0,0 @@
<?php
return [
// العامة
'site_name' => 'شبرا فيل',
'welcome' => 'مرحباً بك في شبرا فيل',
'home' => 'الرئيسية',
'products' => 'المنتجات',
'about' => 'عن الشركة',
'contact' => 'اتصل بنا',
'search' => 'بحث',
'cart' => 'سلة التسوق',
'account' => 'حسابي',
// المصادقة
'login' => 'تسجيل الدخول',
'register' => 'تسجيل جديد',
'logout' => 'تسجيل الخروج',
'email' => 'البريد الإلكتروني',
'password' => 'كلمة المرور',
'confirm_password' => 'تأكيد كلمة المرور',
'forgot_password' => 'نسيت كلمة المرور؟',
'remember_me' => 'تذكرني',
// المنتجات
'product_name' => 'اسم المنتج',
'price' => 'السعر',
'quantity' => 'الكمية',
'description' => 'الوصف',
'add_to_cart' => 'أضف إلى السلة',
'out_of_stock' => 'نفذت الكمية',
'reviews' => 'التقييمات',
'write_review' => 'اكتب تقييماً',
// سلة التسوق
'shopping_cart' => 'سلة التسوق',
'total' => 'المجموع',
'checkout' => 'إتمام الشراء',
'continue_shopping' => 'مواصلة التسوق',
'empty_cart' => 'السلة فارغة',
// الدفع
'payment' => 'الدفع',
'payment_method' => 'طريقة الدفع',
'card_number' => 'رقم البطاقة',
'expiry_date' => 'تاريخ الانتهاء',
'cvv' => 'رمز التحقق',
'pay_now' => 'ادفع الآن',
// الطلبات
'orders' => 'الطلبات',
'order_number' => 'رقم الطلب',
'order_date' => 'تاريخ الطلب',
'order_status' => 'حالة الطلب',
'order_total' => 'إجمالي الطلب',
// الإشعارات
'notifications' => 'الإشعارات',
'no_notifications' => 'لا توجد إشعارات',
'mark_as_read' => 'تعليم كمقروء',
'mark_all_as_read' => 'تعليم الكل كمقروء',
// الملف الشخصي
'profile' => 'الملف الشخصي',
'edit_profile' => 'تعديل الملف الشخصي',
'name' => 'الاسم',
'phone' => 'رقم الهاتف',
'address' => 'العنوان',
'save_changes' => 'حفظ التغييرات',
// رسائل
'success' => 'تم بنجاح',
'error' => 'حدث خطأ',
'confirm' => 'تأكيد',
'cancel' => 'إلغاء',
'loading' => 'جاري التحميل...',
// الأخطاء
'required_field' => 'هذا الحقل مطلوب',
'invalid_email' => 'البريد الإلكتروني غير صحيح',
'password_mismatch' => 'كلمات المرور غير متطابقة',
'invalid_credentials' => 'بيانات الدخول غير صحيحة',
// التقييمات
'rating' => 'التقييم',
'comment' => 'التعليق',
'submit_review' => 'إرسال التقييم',
'edit_review' => 'تعديل التقييم',
'delete_review' => 'حذف التقييم',
// الشحن
'shipping' => 'الشحن',
'shipping_address' => 'عنوان الشحن',
'shipping_method' => 'طريقة الشحن',
'shipping_cost' => 'تكلفة الشحن',
// الفواتير
'invoice' => 'الفاتورة',
'invoice_number' => 'رقم الفاتورة',
'invoice_date' => 'تاريخ الفاتورة',
'download_invoice' => 'تحميل الفاتورة',
// الدعم
'support' => 'الدعم الفني',
'faq' => 'الأسئلة الشائعة',
'contact_support' => 'اتصل بالدعم',
'submit_ticket' => 'إرسال تذكرة',
];

View File

@ -1,107 +0,0 @@
<?php
return [
// General
'site_name' => 'ShubraVeil',
'welcome' => 'Welcome to ShubraVeil',
'home' => 'Home',
'products' => 'Products',
'about' => 'About',
'contact' => 'Contact',
'search' => 'Search',
'cart' => 'Cart',
'account' => 'My Account',
// Authentication
'login' => 'Login',
'register' => 'Register',
'logout' => 'Logout',
'email' => 'Email',
'password' => 'Password',
'confirm_password' => 'Confirm Password',
'forgot_password' => 'Forgot Password?',
'remember_me' => 'Remember Me',
// Products
'product_name' => 'Product Name',
'price' => 'Price',
'quantity' => 'Quantity',
'description' => 'Description',
'add_to_cart' => 'Add to Cart',
'out_of_stock' => 'Out of Stock',
'reviews' => 'Reviews',
'write_review' => 'Write a Review',
// Shopping Cart
'shopping_cart' => 'Shopping Cart',
'total' => 'Total',
'checkout' => 'Checkout',
'continue_shopping' => 'Continue Shopping',
'empty_cart' => 'Your cart is empty',
// Payment
'payment' => 'Payment',
'payment_method' => 'Payment Method',
'card_number' => 'Card Number',
'expiry_date' => 'Expiry Date',
'cvv' => 'CVV',
'pay_now' => 'Pay Now',
// Orders
'orders' => 'Orders',
'order_number' => 'Order Number',
'order_date' => 'Order Date',
'order_status' => 'Order Status',
'order_total' => 'Order Total',
// Notifications
'notifications' => 'Notifications',
'no_notifications' => 'No notifications',
'mark_as_read' => 'Mark as Read',
'mark_all_as_read' => 'Mark All as Read',
// Profile
'profile' => 'Profile',
'edit_profile' => 'Edit Profile',
'name' => 'Name',
'phone' => 'Phone',
'address' => 'Address',
'save_changes' => 'Save Changes',
// Messages
'success' => 'Success',
'error' => 'Error',
'confirm' => 'Confirm',
'cancel' => 'Cancel',
'loading' => 'Loading...',
// Errors
'required_field' => 'This field is required',
'invalid_email' => 'Invalid email address',
'password_mismatch' => 'Passwords do not match',
'invalid_credentials' => 'Invalid credentials',
// Reviews
'rating' => 'Rating',
'comment' => 'Comment',
'submit_review' => 'Submit Review',
'edit_review' => 'Edit Review',
'delete_review' => 'Delete Review',
// Shipping
'shipping' => 'Shipping',
'shipping_address' => 'Shipping Address',
'shipping_method' => 'Shipping Method',
'shipping_cost' => 'Shipping Cost',
// Invoices
'invoice' => 'Invoice',
'invoice_number' => 'Invoice Number',
'invoice_date' => 'Invoice Date',
'download_invoice' => 'Download Invoice',
// Support
'support' => 'Support',
'faq' => 'FAQ',
'contact_support' => 'Contact Support',
'submit_ticket' => 'Submit Ticket',
];