From f89d1364fd996d0af616493d6bbd8dc1978b73a3 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 25 Dec 2024 14:31:31 +0200 Subject: [PATCH] second commit --- .htaccess | 30 +++--- README.md | 120 ++++++++++++------------ composer.json | 32 ++++--- includes/Language.php | 112 ++++++++++++++++++++++ includes/Notification.php | 105 +++++++++++++++++++++ includes/Payment.php | 109 +++++++++++++++++++++ includes/Review.php | 193 ++++++++++++++++++++++++++++++++++++++ includes/Security.php | 118 +++++++++++++++++++++++ includes/config.php | 10 +- lang/ar.php | 107 +++++++++++++++++++++ lang/en.php | 107 +++++++++++++++++++++ 11 files changed, 955 insertions(+), 88 deletions(-) create mode 100644 includes/Language.php create mode 100644 includes/Notification.php create mode 100644 includes/Payment.php create mode 100644 includes/Review.php create mode 100644 lang/ar.php create mode 100644 lang/en.php diff --git a/.htaccess b/.htaccess index 8b13352..4c1abfb 100644 --- a/.htaccess +++ b/.htaccess @@ -7,36 +7,42 @@ RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # Protect sensitive files - + Order allow,deny Deny from all # Protect directories - + Order allow,deny Deny from all -# 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 - +# Cache control + Header set Cache-Control "max-age=31536000, public" -# 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 diff --git a/README.md b/README.md index 67bcffb..0b77ee9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/composer.json b/composer.json index edaa36e..b127b79 100644 --- a/composer.json +++ b/composer.json @@ -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, diff --git a/includes/Language.php b/includes/Language.php new file mode 100644 index 0000000..3777a2e --- /dev/null +++ b/includes/Language.php @@ -0,0 +1,112 @@ + $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); + } +} diff --git a/includes/Notification.php b/includes/Notification.php new file mode 100644 index 0000000..82e8004 --- /dev/null +++ b/includes/Notification.php @@ -0,0 +1,105 @@ +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; + } +} diff --git a/includes/Payment.php b/includes/Payment.php new file mode 100644 index 0000000..08840d9 --- /dev/null +++ b/includes/Payment.php @@ -0,0 +1,109 @@ +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); + } +} diff --git a/includes/Review.php b/includes/Review.php new file mode 100644 index 0000000..cd8ab3d --- /dev/null +++ b/includes/Review.php @@ -0,0 +1,193 @@ +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 + ] + ); + } + } +} diff --git a/includes/Security.php b/includes/Security.php index 436741c..0feac69 100644 --- a/includes/Security.php +++ b/includes/Security.php @@ -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(); + } + } } diff --git a/includes/config.php b/includes/config.php index e5ba658..0712f3d 100644 --- a/includes/config.php +++ b/includes/config.php @@ -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 diff --git a/lang/ar.php b/lang/ar.php new file mode 100644 index 0000000..394e6c6 --- /dev/null +++ b/lang/ar.php @@ -0,0 +1,107 @@ + 'شبرا فيل', + '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' => 'إرسال تذكرة', +]; diff --git a/lang/en.php b/lang/en.php new file mode 100644 index 0000000..83f916e --- /dev/null +++ b/lang/en.php @@ -0,0 +1,107 @@ + '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', +];