chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل
نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق». نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا `cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules · .dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore. هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ. ⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// core/bootstrap.php
|
||||
// البوابة الرئيسية الموحدة لكل التطبيق
|
||||
// ============================================================
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// 1. إعدادات الأخطاء والـ Headers الأساسية
|
||||
// اجعل القيمة true لتفعيل عرض الأخطاء (التطوير)، أو false لإخفائها (التشغيل الفعلي)
|
||||
$debugMode = getenv('APP_DEBUG') === 'true';
|
||||
|
||||
if ($debugMode || php_sapi_name() === 'cli') {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
} else {
|
||||
error_reporting(0);
|
||||
ini_set('display_errors', '0');
|
||||
}
|
||||
ini_set('log_errors', '1');
|
||||
|
||||
// تحديد مسار اللوج بشكل ديناميكي (محلياً أو سيرفر)
|
||||
$logPath = getenv('ERROR_LOG_PATH') ?: (__DIR__ . '/../logs/php_errors.log');
|
||||
ini_set('error_log', $logPath);
|
||||
|
||||
// تعريف الدولة أو البيئة الحالية للسيرفر (مثلاً: syria, egypt, jordan)
|
||||
// تُستخدم لتوجيه الروابط أو لتحديد السيرفر
|
||||
$globalCountry = getenv('GLOBAL_COUNTRY') ?: 'syria';
|
||||
if (!defined('GLOBAL_COUNTRY')) {
|
||||
define('GLOBAL_COUNTRY', $globalCountry);
|
||||
}
|
||||
|
||||
header_remove('X-Powered-By');
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('X-Frame-Options: DENY');
|
||||
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
||||
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'");
|
||||
header("Referrer-Policy: strict-origin-when-cross-origin");
|
||||
header("Permissions-Policy: geolocation=(), microphone=(), camera=()");
|
||||
header("X-XSS-Protection: 1; mode=block");
|
||||
|
||||
|
||||
// CORS مع التحقق من المصدر المسموح
|
||||
$envOrigins = array_map('trim', explode(',', getenv('CORS_ALLOWED_ORIGINS') ?: ''));
|
||||
$defaultOrigins = ['https://siromove.com', 'https://admin.siromove.com', 'https://jordan-siro.intaleqapp.com', 'http://localhost', 'http://127.0.0.1'];
|
||||
$allowedOrigins = array_unique(array_merge($envOrigins, $defaultOrigins));
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if (in_array($origin, $allowedOrigins)) {
|
||||
header("Access-Control-Allow-Origin: $origin");
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
}
|
||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Device-FP, X-HMAC-Auth, X-Internal-Key');
|
||||
|
||||
// REQUEST_METHOD غير معرّف عند التشغيل من سطر الأوامر (سكربتات الترحيل)
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Autoload
|
||||
$vendorPath = realpath(__DIR__ . '/../../vendor/autoload.php');
|
||||
if (!$vendorPath) {
|
||||
$vendorPath = realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
}
|
||||
if ($vendorPath) require_once $vendorPath;
|
||||
|
||||
// 3. Helpers & Env
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
|
||||
// تحديد مسار الـ .env بشكل ديناميكي
|
||||
if (preg_match('#^(/home/[^/]+)#', __DIR__, $matches)) {
|
||||
$homeDir = $matches[1];
|
||||
} else {
|
||||
$homeDir = dirname($_SERVER['DOCUMENT_ROOT'] ?? __DIR__);
|
||||
}
|
||||
|
||||
$envFile = getenv('ENV_FILE_PATH') ?: ($homeDir . '/.env');
|
||||
if (!file_exists($envFile)) {
|
||||
$envFile = __DIR__ . '/../.env';
|
||||
}
|
||||
loadEnvironment($envFile);
|
||||
|
||||
// تعيين مسارات المفاتيح تلقائياً إذا لم تكن معرفة في الـ .env
|
||||
if (!getenv('ENCRYPTION_KEY_PATH')) {
|
||||
$encKeyDefault = "$homeDir/.enckey";
|
||||
putenv("ENCRYPTION_KEY_PATH=$encKeyDefault");
|
||||
$_ENV['ENCRYPTION_KEY_PATH'] = $encKeyDefault;
|
||||
}
|
||||
if (!getenv('SECRET_KEY_PATH')) {
|
||||
$secKeyDefault = "$homeDir/.secret_key";
|
||||
putenv("SECRET_KEY_PATH=$secKeyDefault");
|
||||
$_ENV['SECRET_KEY_PATH'] = $secKeyDefault;
|
||||
}
|
||||
if (!getenv('SECRET_KEY_PAY_PATH')) {
|
||||
$secPayKeyDefault = "$homeDir/.secret_key_pay";
|
||||
putenv("SECRET_KEY_PAY_PATH=$secPayKeyDefault");
|
||||
$_ENV['SECRET_KEY_PAY_PATH'] = $secPayKeyDefault;
|
||||
}
|
||||
if (!getenv('INTERNAL_SOCKET_KEY_PATH')) {
|
||||
$sockKeyDefault = "$homeDir/.internal_socket_key";
|
||||
putenv("INTERNAL_SOCKET_KEY_PATH=$sockKeyDefault");
|
||||
$_ENV['INTERNAL_SOCKET_KEY_PATH'] = $sockKeyDefault;
|
||||
}
|
||||
if (!getenv('SERVICE_ACCOUNT_FILE_PATH')) {
|
||||
$svcAcctDefault = "$homeDir/service-account.json";
|
||||
putenv("SERVICE_ACCOUNT_FILE_PATH=$svcAcctDefault");
|
||||
$_ENV['SERVICE_ACCOUNT_FILE_PATH'] = $svcAcctDefault;
|
||||
}
|
||||
|
||||
// 4. Redis Connections (Dual Architecture)
|
||||
$redis = null;
|
||||
$redisLocation = null;
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
// --- Main Server Redis ---
|
||||
$redis = new Redis();
|
||||
$redisHost = getenv('REDIS_MAIN_HOST') ?: getenv('REDIS_HOST') ?: '127.0.0.1';
|
||||
$redisPort = (int)(getenv('REDIS_MAIN_PORT') ?: getenv('REDIS_PORT') ?: 6379);
|
||||
$redisPass = getenv('REDIS_MAIN_PASSWORD') ?: getenv('REDIS_MAIN_AUTH') ?: getenv('REDIS_PASSWORD') ?: getenv('REDIS_AUTH');
|
||||
|
||||
if ($redis->connect($redisHost, $redisPort, 1.5)) {
|
||||
if ($redisPass) $redis->auth($redisPass);
|
||||
$redis->setOption(Redis::OPT_PREFIX, 'siro:');
|
||||
} else {
|
||||
$redis = null;
|
||||
}
|
||||
|
||||
// --- Location Server Redis ---
|
||||
$redisLocation = new Redis();
|
||||
// 🔥 [Fix Silent Fallback] إذا لم تُضبط REDIS_LOCATION_HOST صراحة، نسقط
|
||||
// على Redis الرئيسي — وهذا يجعل استعلامات كثافة السائقين (geo:drivers:*)
|
||||
// ترجع فارغة بصمت لأن تلك المفاتيح تُكتب فقط على Redis الخاص بلوكيشن
|
||||
// سيرفر. نسجّل تحذيراً واضحاً حتى لا يمر هذا دون ملاحظة في اللوجز.
|
||||
$locHostConfigured = getenv('REDIS_LOCATION_HOST');
|
||||
if (!$locHostConfigured) {
|
||||
error_log('[REDIS] ⚠️ REDIS_LOCATION_HOST is not set — $redisLocation is falling back to the MAIN redis host (' . $redisHost . '). ' .
|
||||
'geo:drivers:available / driver:profile:* / driver:public:* keys live only on the location-server Redis, ' .
|
||||
'so driver-density lookups (getSpeed.php, heatmap_live.php, pricing/get.php) will silently return empty results ' .
|
||||
'unless REDIS_LOCATION_HOST/PORT/PASSWORD are configured correctly in .env.');
|
||||
}
|
||||
$locHost = $locHostConfigured ?: $redisHost;
|
||||
$locPort = (int)(getenv('REDIS_LOCATION_PORT') ?: $redisPort);
|
||||
$locPass = getenv('REDIS_LOCATION_PASSWORD') ?: $redisPass;
|
||||
|
||||
if ($redisLocation->connect($locHost, $locPort, 1.5)) {
|
||||
if ($locPass) $redisLocation->auth($locPass);
|
||||
// No prefix for location server
|
||||
} else {
|
||||
error_log("[REDIS] ⚠️ Failed to connect \$redisLocation to $locHost:$locPort — driver-density features will be degraded.");
|
||||
$redisLocation = null;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
error_log("[REDIS] Connection failed: " . $e->getMessage());
|
||||
$redis = null;
|
||||
$redisLocation = null;
|
||||
}
|
||||
|
||||
// 5. تحميل الـ Services الأساسية
|
||||
require_once __DIR__ . '/Security/EncryptionHelper.php';
|
||||
require_once __DIR__ . '/Security/BlindIndex.php';
|
||||
|
||||
// فهرس البحث الأعمى — اختياري: إن لم يُضبط BLIND_INDEX_PEPPER تبقى نقاط
|
||||
// البحث تعمل بأسلوبها القديم بدل أن تفشل.
|
||||
$blindIndex = null;
|
||||
try {
|
||||
$blindIndex = new BlindIndex();
|
||||
} catch (Throwable $e) {
|
||||
error_log('[BlindIndex] disabled: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/Database/Database.php';
|
||||
require_once __DIR__ . '/Auth/RateLimiter.php';
|
||||
require_once __DIR__ . '/Auth/JwtService.php';
|
||||
// لا نحمّل OtpService و FcmService إلا عند الحاجة (Lazy)
|
||||
|
||||
// 6. تهيئة Encryption Helper العام (للتوافقية)
|
||||
// يتم استخدام .enckey (32 بايت) لتشفير البيانات
|
||||
$encKeyPath = getenv('ENCRYPTION_KEY_PATH');
|
||||
$encKey = '';
|
||||
if ($encKeyPath && file_exists($encKeyPath)) {
|
||||
$encKey = trim(@file_get_contents($encKeyPath) ?: '');
|
||||
}
|
||||
if (!$encKey) {
|
||||
$encKey = getenv('ENC_KEY') ?: '';
|
||||
}
|
||||
|
||||
if (!$encKey || strlen($encKey) !== 32) {
|
||||
error_log("[FATAL] Encryption key (.enckey) is missing or invalid length (must be 32 bytes).");
|
||||
http_response_code(500);
|
||||
exit(json_encode(['error' => 'Server configuration error: Encryption key issue']));
|
||||
}
|
||||
|
||||
$encryptionHelper = new EncryptionHelper($encKey);
|
||||
Reference in New Issue
Block a user