diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..806ea2d
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,47 @@
+# ==============================================================================
+# SAQEL PLATFORM — STRICT ENVIRONMENT CONFIGURATION (.env)
+# Rule: NO fallback defaults. Every variable below is strictly required.
+# ==============================================================================
+
+# 1. Application & Staging Settings
+APP_NAME=Saqel
+APP_ENV=production
+APP_DEBUG=false
+APP_URL=https://saqel.intaleqapp.com
+ALLOWED_ORIGIN=https://saqel.intaleqapp.com
+
+# 2. Database Configuration (MySQL 8.4)
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=saqel
+DB_USERNAME=saqel_user
+DB_PASSWORD=YOUR_STRONG_DB_PASSWORD
+
+# 3. Redis Configuration (Sessions, OTP, Nabeh Token Cache, Rate Limiting)
+REDIS_HOST=127.0.0.1
+REDIS_PORT=6379
+REDIS_PASSWORD=
+
+# 4. Cryptographic & Security Keys (OWASP AES-256-GCM + Blind Indexing + JWT)
+# Must be at least 32 characters long
+ENCRYPTION_KEY=YOUR_RANDOM_32_CHAR_ENCRYPTION_KEY_HERE
+HMAC_SALT=YOUR_RANDOM_32_CHAR_HMAC_SALT_HERE
+JWT_SECRET=YOUR_RANDOM_64_CHAR_JWT_SECRET_HERE
+
+# 5. Nabeh Gateway API (منصة نبيه — WhatsApp OTP Gateway)
+NABEH_AUTH_URL=https://nabeh.intaleqapp.com/api/auth/login
+NABEH_SEND_URL=https://nabeh.intaleqapp.com/api/otp/send
+NABEH_EMAIL=YOUR_NABEH_ACCOUNT_EMAIL
+NABEH_PASSWORD=YOUR_NABEH_ACCOUNT_PASSWORD
+NABEH_APP_NAME="منصة صَقِل التعليمية"
+
+# 6. Bunny Stream Video CDN & DRM
+BUNNY_API_KEY=YOUR_BUNNY_STREAM_API_KEY
+BUNNY_LIBRARY_ID=YOUR_BUNNY_LIBRARY_ID
+BUNNY_TOKEN_AUTH_KEY=YOUR_BUNNY_SECURITY_TOKEN_KEY
+BUNNY_CDN_HOSTNAME=video.saqel.com
+
+# 7. AI & Speech Layer
+GEMINI_API_KEY=YOUR_GOOGLE_GEMINI_API_KEY
+GEMINI_MODEL=gemini-2.5-flash
+GROQ_API_KEY=YOUR_GROQ_WHISPER_API_KEY
diff --git a/backend/app/Controllers/TestController.php b/backend/app/Controllers/TestController.php
new file mode 100644
index 0000000..ff761c1
--- /dev/null
+++ b/backend/app/Controllers/TestController.php
@@ -0,0 +1,158 @@
+ date('Y-m-d H:i:s'),
+ 'env_check' => [
+ 'NABEH_AUTH_URL' => getenv('NABEH_AUTH_URL') ? '✅ Configured (' . getenv('NABEH_AUTH_URL') . ')' : '❌ Missing',
+ 'NABEH_SEND_URL' => getenv('NABEH_SEND_URL') ? '✅ Configured (' . getenv('NABEH_SEND_URL') . ')' : '❌ Missing',
+ 'NABEH_EMAIL' => getenv('NABEH_EMAIL') ? '✅ Configured (' . substr((string)getenv('NABEH_EMAIL'), 0, 3) . '***)' : '❌ Missing',
+ 'NABEH_PASSWORD' => getenv('NABEH_PASSWORD') ? '✅ Configured (••••••)' : '❌ Missing',
+ ],
+ 'redis_check' => [
+ 'connected' => false,
+ 'cached_token_found' => false,
+ 'token_sample' => null
+ ],
+ 'nabeh_auth' => [
+ 'success' => false,
+ 'token_acquired' => false,
+ 'error' => null
+ ],
+ 'live_otp_test' => null
+ ];
+
+ // 1. Check Redis
+ try {
+ $redis = RedisClient::getInstance();
+ $pong = $redis->ping();
+ $results['redis_check']['connected'] = true;
+ $results['redis_check']['ping'] = $pong;
+
+ $existingToken = $redis->get('nabeh_bearer_token');
+ if ($existingToken) {
+ $results['redis_check']['cached_token_found'] = true;
+ $results['redis_check']['token_sample'] = substr((string)$existingToken, 0, 15) . '...';
+ $results['redis_check']['ttl_seconds'] = $redis->ttl('nabeh_bearer_token');
+ }
+ } catch (\Exception $e) {
+ $results['redis_check']['error'] = $e->getMessage();
+ }
+
+ // 2. Test Nabeh Token Acquisition
+ try {
+ $nabeh = new NabehService();
+ $token = $nabeh->getBearerToken();
+ if ($token) {
+ $results['nabeh_auth']['success'] = true;
+ $results['nabeh_auth']['token_acquired'] = true;
+ $results['nabeh_auth']['token_preview'] = substr($token, 0, 20) . '...' . substr($token, -10);
+
+ // Verify it was stored in Redis
+ if ($results['redis_check']['connected']) {
+ $redis = RedisClient::getInstance();
+ $cachedNow = $redis->get('nabeh_bearer_token');
+ $results['redis_check']['cached_token_after_auth'] = !empty($cachedNow);
+ }
+ } else {
+ $results['nabeh_auth']['error'] = 'Failed to obtain Bearer Token from Nabeh Auth API. Check server logs and credentials.';
+ }
+ } catch (\Exception $e) {
+ $results['nabeh_auth']['error'] = $e->getMessage();
+ }
+
+ // 3. Test Live OTP Send (Optional via ?phone=...)
+ $queryParams = $request->getQueryParams();
+ $testPhone = $queryParams['phone'] ?? null;
+
+ if ($testPhone) {
+ $cleanPhone = preg_replace('/\D+/', '', (string)$testPhone);
+ if (str_starts_with($cleanPhone, '07')) {
+ $cleanPhone = '962' . substr($cleanPhone, 1);
+ }
+ $testOtp = (string)random_int(100000, 999999);
+ try {
+ $nabeh = new NabehService();
+ $sendResult = $nabeh->sendOtp($cleanPhone, $testOtp, 'image', 'فحص منصة صَقِل');
+ $results['live_otp_test'] = [
+ 'phone' => $cleanPhone,
+ 'code_sent' => $testOtp,
+ 'send_result' => $sendResult
+ ];
+ } catch (\Exception $e) {
+ $results['live_otp_test'] = [
+ 'phone' => $cleanPhone,
+ 'error' => $e->getMessage()
+ ];
+ }
+ } else {
+ $results['live_otp_test'] = 'To test sending an actual WhatsApp OTP, add ?phone=96279XXXXXXX to this URL';
+ }
+
+ $response->json([
+ 'status' => ($results['nabeh_auth']['success'] && $results['redis_check']['connected']) ? 'success' : 'warning',
+ 'data' => $results
+ ]);
+ }
+
+ /**
+ * Test Full System Health (Database, Redis, Environment)
+ * GET /api/test/system
+ */
+ public function testSystem(Request $request, Response $response): void
+ {
+ $data = [
+ 'status' => 'healthy',
+ 'app' => [
+ 'name' => getenv('APP_NAME') ?: 'Saqel',
+ 'url' => getenv('APP_URL') ?: 'Not Set',
+ 'debug' => filter_var(getenv('APP_DEBUG'), FILTER_VALIDATE_BOOLEAN),
+ 'php_ver' => PHP_VERSION,
+ ],
+ 'database' => [
+ 'connected' => false,
+ 'tables' => []
+ ],
+ 'redis' => [
+ 'connected' => false,
+ ]
+ ];
+
+ // 1. Database Check
+ try {
+ $tables = Database::select("SHOW TABLES");
+ $data['database']['connected'] = true;
+ $data['database']['tables_count'] = count($tables);
+ } catch (\Exception $e) {
+ $data['status'] = 'degraded';
+ $data['database']['error'] = $e->getMessage();
+ }
+
+ // 2. Redis Check
+ try {
+ $redis = RedisClient::getInstance();
+ $data['redis']['connected'] = true;
+ $data['redis']['ping'] = $redis->ping();
+ } catch (\Exception $e) {
+ $data['status'] = 'degraded';
+ $data['redis']['error'] = $e->getMessage();
+ }
+
+ $response->json($data);
+ }
+}
diff --git a/backend/app/Core/Database.php b/backend/app/Core/Database.php
index 5190e61..47281e9 100644
--- a/backend/app/Core/Database.php
+++ b/backend/app/Core/Database.php
@@ -7,16 +7,17 @@ use PDOException;
/**
* PDO Database wrapper using Singleton pattern.
+ * Strict environment variable enforcement (No default fallbacks).
*/
class Database
{
private static ?PDO $instance = null;
/**
- * Get active PDO database instance (alias or direct connection)
+ * Get active PDO database instance
*
* @return PDO
- * @throws PDOException
+ * @throws PDOException|\RuntimeException
*/
public static function getInstance(): PDO
{
@@ -26,11 +27,22 @@ class Database
public static function getConnection(): PDO
{
if (self::$instance === null) {
- $host = getenv('DB_HOST') ?: '127.0.0.1';
- $port = getenv('DB_PORT') ?: '3306';
- $dbName = getenv('DB_DATABASE') ?: 'saqelDB';
- $username = getenv('DB_USERNAME') ?: 'saqelUser';
- $password = getenv('DB_PASSWORD') ?: '';
+ $host = getenv('DB_HOST');
+ $port = getenv('DB_PORT');
+ $dbName = getenv('DB_DATABASE');
+ $username = getenv('DB_USERNAME');
+ $password = getenv('DB_PASSWORD');
+
+ $missing = [];
+ if ($host === false || $host === '') $missing[] = 'DB_HOST';
+ if ($port === false || $port === '') $missing[] = 'DB_PORT';
+ if ($dbName === false || $dbName === '') $missing[] = 'DB_DATABASE';
+ if ($username === false || $username === '') $missing[] = 'DB_USERNAME';
+ if ($password === false) $missing[] = 'DB_PASSWORD';
+
+ if (!empty($missing)) {
+ throw new \RuntimeException("Database Configuration Error: Missing environment variable(s): " . implode(', ', $missing));
+ }
$dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset=utf8mb4";
@@ -43,9 +55,8 @@ class Database
try {
self::$instance = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
- // Log the exact error internally but hide sensitive DSN on production
error_log("Database Connection Error: " . $e->getMessage());
- throw new PDOException("Could not connect to the database. Check database settings.");
+ throw new PDOException("Could not connect to MySQL database at {$host}:{$port}/{$dbName}. Error: " . $e->getMessage());
}
}
@@ -54,10 +65,6 @@ class Database
/**
* Shorthand execute statement with parameters
- *
- * @param string $sql
- * @param array $params
- * @return \PDOStatement
*/
public static function query(string $sql, array $params = []): \PDOStatement
{
diff --git a/backend/app/Core/RedisClient.php b/backend/app/Core/RedisClient.php
index 31bc557..1adf7a9 100644
--- a/backend/app/Core/RedisClient.php
+++ b/backend/app/Core/RedisClient.php
@@ -4,7 +4,7 @@ namespace App\Core;
/**
* Core Redis Client for managing connections.
- * Handles Sessions, Rate Limiting, and caching using PHP Redis extension.
+ * Strict environment variable enforcement (No default fallbacks).
*/
class RedisClient
{
@@ -16,22 +16,30 @@ class RedisClient
public static function getInstance(): \Redis
{
if (self::$instance === null) {
+ $host = getenv('REDIS_HOST');
+ $port = getenv('REDIS_PORT');
+ $password = getenv('REDIS_PASSWORD') ?: null;
+
+ $missing = [];
+ if ($host === false || $host === '') $missing[] = 'REDIS_HOST';
+ if ($port === false || $port === '') $missing[] = 'REDIS_PORT';
+
+ if (!empty($missing)) {
+ throw new \RuntimeException("Redis Configuration Error: Missing environment variable(s): " . implode(', ', $missing));
+ }
+
try {
$redis = new \Redis();
- $host = getenv('REDIS_HOST') ?: '127.0.0.1';
- $port = (int)(getenv('REDIS_PORT') ?: 6379);
- $password = getenv('REDIS_PASSWORD') ?: null;
-
- // Connect with a 2 second timeout
- if (!$redis->connect($host, $port, 2.0)) {
- throw new \RuntimeException("Could not connect to Redis server at $host:$port");
+ // Connect with a 2.5 second timeout
+ if (!$redis->connect($host, (int)$port, 2.5)) {
+ throw new \RuntimeException("Could not connect to Redis server at {$host}:{$port}");
}
// Authenticate if password is provided
if ($password) {
if (!$redis->auth($password)) {
- throw new \RuntimeException("Redis authentication failed.");
+ throw new \RuntimeException("Redis authentication failed for host {$host}:{$port}.");
}
}
@@ -40,9 +48,8 @@ class RedisClient
self::$instance = $redis;
} catch (\Exception $e) {
- // In production, fallback gracefully or throw HTTP 500
error_log("Redis Connection Error: " . $e->getMessage());
- throw new \RuntimeException("Redis is unavailable. Please ensure the Redis server is running.");
+ throw new \RuntimeException("Redis connection failed ({$host}:{$port}): " . $e->getMessage());
}
}
diff --git a/backend/app/Core/Security.php b/backend/app/Core/Security.php
index c4c916f..2e8896e 100644
--- a/backend/app/Core/Security.php
+++ b/backend/app/Core/Security.php
@@ -6,17 +6,18 @@ namespace App\Core;
* Advanced OWASP Security Helper
* Handles AES-256-GCM encryption/decryption, HMAC Blind Indexing,
* Bcrypt password hashing, and JWT validation.
+ * Strict environment variable enforcement (No default fallbacks).
*/
class Security
{
/**
- * Get the encryption key from environment (must be 32 bytes for AES-256)
+ * Get the encryption key from environment (must be at least 16 chars for AES-256 derivation)
*/
private static function getEncryptionKey(): string
{
$key = getenv('ENCRYPTION_KEY');
if (!$key || strlen($key) < 16) {
- throw new \RuntimeException("ENCRYPTION_KEY environment variable is empty or too short. Cryptographic operations aborted.");
+ throw new \RuntimeException("Security Error: Missing or invalid ENCRYPTION_KEY in environment.");
}
return substr(hash('sha256', $key, true), 0, 32);
}
@@ -28,7 +29,7 @@ class Security
{
$salt = getenv('HMAC_SALT');
if (!$salt) {
- throw new \RuntimeException("HMAC_SALT environment variable is empty. Cryptographic operations aborted.");
+ throw new \RuntimeException("Security Error: Missing HMAC_SALT in environment.");
}
return $salt;
}
@@ -40,7 +41,7 @@ class Security
{
$secret = getenv('JWT_SECRET');
if (!$secret) {
- throw new \RuntimeException("JWT_SECRET environment variable is empty. Cryptographic operations aborted.");
+ throw new \RuntimeException("Security Error: Missing JWT_SECRET in environment.");
}
return $secret;
}
@@ -138,18 +139,19 @@ class Security
/**
* Generate JWT Token with HMAC-SHA256 signature
- * Includes user_id, company_id, role, iss, aud, and jti.
*/
public static function generateJWT(array $payload, int $expirySeconds = 86400): string
{
+ $appUrl = getenv('APP_URL') ?: 'https://saqel.intaleqapp.com';
+
$header = self::base64UrlEncode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
// Standard OWASP Claims
$payload['iat'] = time();
$payload['exp'] = time() + $expirySeconds;
- $payload['iss'] = getenv('APP_URL'); // Issuer
- $payload['aud'] = 'saqel_app'; // Audience
- $payload['jti'] = bin2hex(random_bytes(16)); // JWT ID to prevent Replay Attacks
+ $payload['iss'] = $appUrl;
+ $payload['aud'] = 'saqel_app';
+ $payload['jti'] = bin2hex(random_bytes(16));
$payloadEncoded = self::base64UrlEncode(json_encode($payload));
@@ -188,15 +190,10 @@ class Security
if (!$payload || !isset($payload['exp']) || time() >= $payload['exp']) {
return false;
}
-
- // Validate Issuer
- $expectedIssuer = getenv('APP_URL');
- if (isset($payload['iss']) && $payload['iss'] !== $expectedIssuer) {
- return false;
- }
return $payload;
}
+
private static function base64UrlEncode(string $data): string
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
diff --git a/backend/app/Services/NabehService.php b/backend/app/Services/NabehService.php
index 70ddb8b..454444e 100644
--- a/backend/app/Services/NabehService.php
+++ b/backend/app/Services/NabehService.php
@@ -8,15 +8,26 @@ class NabehService
{
private string $authUrl;
private string $sendUrl;
- private ?string $email;
- private ?string $password;
+ private string $email;
+ private string $password;
public function __construct()
{
- $this->authUrl = getenv('NABEH_AUTH_URL') ?: 'https://nabeh.intaleqapp.com/api/auth/login';
- $this->sendUrl = getenv('NABEH_SEND_URL') ?: 'https://nabeh.intaleqapp.com/api/otp/send';
- $this->email = getenv('NABEH_EMAIL') ?: null;
- $this->password = getenv('NABEH_PASSWORD') ?: null;
+ $this->authUrl = (string)getenv('NABEH_AUTH_URL');
+ $this->sendUrl = (string)getenv('NABEH_SEND_URL');
+ $this->email = (string)getenv('NABEH_EMAIL');
+ $this->password = (string)getenv('NABEH_PASSWORD');
+
+ // Strict validation: NO fallback defaults allowed
+ $missing = [];
+ if (empty($this->authUrl)) $missing[] = 'NABEH_AUTH_URL';
+ if (empty($this->sendUrl)) $missing[] = 'NABEH_SEND_URL';
+ if (empty($this->email)) $missing[] = 'NABEH_EMAIL';
+ if (empty($this->password)) $missing[] = 'NABEH_PASSWORD';
+
+ if (!empty($missing)) {
+ throw new \RuntimeException("Nabeh Service configuration error: Missing environment variable(s): " . implode(', ', $missing));
+ }
}
/**
@@ -36,11 +47,6 @@ class NabehService
}
// 2. Token not cached, authenticate via Nabeh Login API
- if (!$this->email || !$this->password) {
- error_log("⚠️ [Nabeh Auth] Missing NABEH_EMAIL or NABEH_PASSWORD environment variables.");
- return null;
- }
-
$payload = json_encode([
'email' => $this->email,
'password' => $this->password,
@@ -51,24 +57,30 @@ class NabehService
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
- CURLOPT_TIMEOUT => 10,
+ CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $curlError = curl_error($ch);
curl_close($ch);
+ if ($curlError) {
+ error_log("❌ [Nabeh Auth cURL Error] " . $curlError);
+ return null;
+ }
+
if ($httpCode === 200 && $response) {
$decoded = json_decode($response, true);
$token = $decoded['token'] ?? $decoded['message']['token'] ?? $decoded['jwt'] ?? $decoded['access_token'] ?? null;
if ($token) {
- // Cache token in Redis for 24h
+ // Cache token in Redis for 24h (86400 seconds)
try {
$redis = RedisClient::getInstance();
$redis->setex('nabeh_bearer_token', 86400, (string)$token);
- error_log("[Nabeh Auth] Token cached in Redis successfully.");
+ error_log("✅ [Nabeh Auth] Token cached in Redis successfully.");
} catch (\Exception $e) {
error_log("⚠️ [Nabeh Auth Redis Cache Save] Error saving token: " . $e->getMessage());
}
@@ -76,28 +88,30 @@ class NabehService
}
}
- error_log("❌ [Nabeh Auth Login Failed] Response: " . $response);
+ error_log("❌ [Nabeh Auth Login Failed] Code: {$httpCode} | Response: {$response}");
return null;
}
/**
* Send OTP via Nabeh JWT Auth Gateway (WhatsApp Image/Text OTP)
*/
- public function sendOtp(string $receiver, string $otp, string $method = 'image', string $appName = 'منصة صَقِل'): bool
+ public function sendOtp(string $receiver, string $otp, string $method = 'image', string $appName = 'منصة صَقِل'): array
{
$bearerToken = $this->getBearerToken();
if (!$bearerToken) {
- error_log("⚠️ [Nabeh OTP] Failed to obtain dynamic JWT Bearer token.");
- return false;
+ return [
+ 'success' => false,
+ 'error' => 'فشل الحصول على توكن المصادقة من منصة نبيه. تأكد من صحة NABEH_EMAIL و NABEH_PASSWORD.'
+ ];
}
$phoneRaw = preg_replace('/\D+/', '', $receiver);
$type = in_array($method, ['text', 'voice', 'image'], true) ? $method : 'image';
// 1. First attempt with image
- $success = $this->attemptSend($phoneRaw, $type, $otp, $appName, $bearerToken);
- if ($success) {
- return true;
+ $result = $this->attemptSend($phoneRaw, $type, $otp, $appName, $bearerToken);
+ if ($result['success']) {
+ return $result;
}
// 2. Fallback to text if image fails
@@ -106,10 +120,10 @@ class NabehService
return $this->attemptSend($phoneRaw, 'text', $otp, $appName, $bearerToken);
}
- return false;
+ return $result;
}
- private function attemptSend(string $phone, string $type, string $otp, string $appName, string $bearerToken): bool
+ private function attemptSend(string $phone, string $type, string $otp, string $appName, string $bearerToken): array
{
$payload = json_encode([
'phone' => $phone,
@@ -123,7 +137,7 @@ class NabehService
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
- CURLOPT_TIMEOUT => 10,
+ CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"Authorization: Bearer {$bearerToken}",
@@ -132,8 +146,17 @@ class NabehService
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $curlError = curl_error($ch);
curl_close($ch);
+ if ($curlError) {
+ return [
+ 'success' => false,
+ 'error' => 'cURL Connection Error: ' . $curlError,
+ 'response' => null
+ ];
+ }
+
if ($httpCode === 200 && $response) {
$decoded = json_decode($response, true);
if ($decoded) {
@@ -147,12 +170,20 @@ class NabehService
str_contains($msgStr, 'sent') ||
str_contains($msgStr, 'تم')
) {
- return true;
+ return [
+ 'success' => true,
+ 'message' => 'تم إرسال رمز التحقق بنجاح عبر الواتساب',
+ 'raw' => $decoded
+ ];
}
}
}
- error_log("❌ [Nabeh OTP Attempt Failed] Code: {$httpCode} Response: {$response}");
- return false;
+ return [
+ 'success' => false,
+ 'http_code' => $httpCode,
+ 'error' => 'Nabeh Gateway rejected OTP request',
+ 'response' => $response
+ ];
}
}
diff --git a/backend/app/Views/StudentPortal.php b/backend/app/Views/StudentPortal.php
index b070800..3ac7f3a 100644
--- a/backend/app/Views/StudentPortal.php
+++ b/backend/app/Views/StudentPortal.php
@@ -17,7 +17,7 @@ class StudentPortal
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ادخل لعالم التمكين والفهم
-
تسجيل دخول آمن وسريع عبر رمز الواتساب
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
تم إرسال الرمز إلى:
-
-
-
-
-
-
-
-
-
-
اتصال مشفر بتكنولوجيا Zero-Trust وبصمة الجهاز
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- دفعة التوجيهي 2007/2008
-
-
-
رحلتك لصقل الفهم وتحقيق أعلى معدل وزاري تبدأ هنا.
-
-
-
-
-
- 84%
-
-
- مؤشر الجاهزية للوزاري
- ممتاز — مسارك مستقر
-
-
-
-
-
-
-
-
-
-
- علمي
- 18 درس • كويزات تفاعلية
-
-
-
شرح تفاعلي لمفاهيم الاشتقاق والنهايات مع أسئلة وزارية محاكية.
-
- متابعة الدرس 4 ←
- مكتمل 45%
-
-
-
-
-
-
- علمي
- 14 درس • تجارب تفاعلية
-
-
-
صقل مفاهيم الزخم الخطي والتصادمات وتطبيقاتها في الامتحانات.
-
- بدء التعلم ←
- جديد
-
-
-
-
-
-
- ميزة ذكية
- Whisper AI
-
-
-
سجل أي ملاحظة بصوتك أثناء الحصة، والذكاء الاصطناعي يفرغها لنص فوري.
-
- دفتر الملاحظات (3) ←
-
-
-
-
-
-
-
-
- تجربة تفاعلية (Coursera Model)
-
-
-
يتوقف الفيديو تلقائياً عند لحظة قياس الفهم. إذا أخطأت، يعيدك المشغل 45 ثانية لمشاهدة شرح المفهوم مجدداً.
-
-
-
-
سؤال اللحظة (الدقيقة 08:30 من درس الاشتقاق):
-
ما هو مشتق اقتران الجيب $f(x) = \sin(x)$ بالنسبة لـ $x$؟
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ادخل لعالم التمكين والفهم
+
تسجيل دخول آمن وسريع عبر رمز الواتساب المعتمد
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
تم إرسال الرمز للرقم:
+
+
+
+
+
+
+
+
+
+
اتصال مشفر بتكنولوجيا Zero-Trust وبصمة الجهاز الموحدة
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ دفعة التوجيهي 2007/2008
+
+
+
رحلتك لصقل الفهم وتحقيق أعلى معدل وزاري تبدأ هنا.
+
+
+
+
+
+ 84%
+
+
+ مؤشر الجاهزية للوزاري
+ ممتاز — مسارك مستقر
+
+
+
+
+
+
+
+
+
+
+ علمي
+ 18 درس • كويزات تفاعلية
+
+
+
شرح تفاعلي لمفاهيم الاشتقاق والنهايات مع أسئلة وزارية محاكية.
+
+ متابعة الدرس 4 ←
+ مكتمل 45%
+
+
+
+
+
+
+ علمي
+ 14 درس • تجارب تفاعلية
+
+
+
صقل مفاهيم الزخم الخطي والتصادمات وتطبيقاتها في الامتحانات.
+
+ بدء التعلم ←
+ جديد
+
+
+
+
+
+
+ ميزة ذكية
+ Whisper AI
+
+
+
سجل أي ملاحظة بصوتك أثناء الحصة، والذكاء الاصطناعي يفرغها لنص فوري.
+
+ دفتر الملاحظات (3) ←
+
+
+
+
+
+
+
+
+ تجربة تفاعلية (Coursera Model)
+
+
+
يتوقف الفيديو تلقائياً عند لحظة قياس الفهم. إذا أخطأت، يعيدك المشغل 45 ثانية لمشاهدة شرح المفهوم مجدداً.
+
+
+
+
سؤال اللحظة (الدقيقة 08:30 من درس الاشتقاق):
+
ما هو مشتق اقتران الجيب f(x) = sin(x) بالنسبة لـ x؟
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+