second commit

This commit is contained in:
Mohamed Elsheikh 2024-12-25 14:31:31 +02:00
parent f1f424e6ff
commit f89d1364fd
11 changed files with 955 additions and 88 deletions

View File

@ -7,36 +7,42 @@ RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Protect sensitive files
<FilesMatch "^\.env">
<FilesMatch "^\.env|composer\.json|composer\.lock|package\.json|package-lock\.json|README\.md|\.gitignore">
Order allow,deny
Deny from all
</FilesMatch>
# Protect directories
<DirectoryMatch "^/.*/(?:logs|backups|cache)/">
<DirectoryMatch "^/.*/(?:logs|backups|cache)/|^/.git/|/.github/|/vendor/|/node_modules/">
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
# 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=()"
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'"
# Cache Control
<FilesMatch "\.(jpg|jpeg|png|gif|ico|css|js)$">
# Cache control
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|ico|webp)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
# Prevent directory listing
# 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
Options -Indexes
ServerSignature Off
# Custom error pages
ErrorDocument 404 /404.html

120
README.md
View File

@ -2,62 +2,32 @@
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
@ -69,7 +39,7 @@ cd shubraveil
composer install
```
3. إنشاء ملف .env:
3. إنشاء وتكوين ملف .env:
```bash
cp .env.example .env
```
@ -79,27 +49,61 @@ 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
mkdir -p cache
mkdir -p backups
mkdir -p uploads/products cache backups
chmod -R 755 uploads cache backups
```
### التكوين
#### الإعدادات الأساسية
1. تكوين قاعدة البيانات في ملف .env
2. تكوين SMTP لإرسال البريد الإلكتروني
3. تكوين مفاتيح reCAPTCHA
## الأمان
#### الأمان
- تأكد من تعيين كلمات مرور قوية
- قم بتحديث مفتاح JWT_SECRET
- قم بتكوين HTTPS
- تم تفعيل HTTPS إجبارياً
- حماية الملفات والمجلدات الحساسة
- استخدام CSRF tokens لحماية النماذج
- تشفير كلمات المرور باستخدام password_hash
- استخدام Prepared Statements لمنع SQL Injection
- تصفية وتنظيف جميع المدخلات
- رسائل خطأ آمنة لا تكشف معلومات حساسة
## 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.
```
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

View File

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

112
includes/Language.php Normal file
View File

@ -0,0 +1,112 @@
<?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);
}
}

105
includes/Notification.php Normal file
View File

@ -0,0 +1,105 @@
<?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;
}
}

109
includes/Payment.php Normal file
View File

@ -0,0 +1,109 @@
<?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);
}
}

193
includes/Review.php Normal file
View File

@ -0,0 +1,193 @@
<?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,4 +113,122 @@ 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', 'localhost');
define('DB_USERNAME', 'momaher');
define('DB_PASSWORD', 'Mohamed@9498#');
define('DB_NAME', 'shubraveil_db');
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');
// Site configuration
define('SITE_NAME', 'ShubraVeil');
define('SITE_URL', $env['SITE_URL']);
define('SITE_URL', getenv('SITE_URL') ?: 'https://localhost/shubraveil');
define('UPLOAD_PATH', __DIR__ . '/../uploads');
define('ALLOWED_IMAGE_TYPES', ['image/jpeg', 'image/png', 'image/webp']);
define('MAX_IMAGE_SIZE', 5 * 1024 * 1024); // 5MB

107
lang/ar.php Normal file
View File

@ -0,0 +1,107 @@
<?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' => 'إرسال تذكرة',
];

107
lang/en.php Normal file
View File

@ -0,0 +1,107 @@
<?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',
];