self::$sessionKey, 'phone' => $cleanPhone ]); $ch = curl_init(self::$gatewayUrl . '/api/contacts/check'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'X-Webhook-Secret: ' . self::$secret ]); curl_setopt($ch, CURLOPT_TIMEOUT, 5); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode === 200 && $response) { $data = json_decode($response, true); if (isset($data['status']) && $data['status'] === 'success') { return isset($data['data']['exists']) && $data['data']['exists'] === true; } } return false; } /** * Send OTP message via WhatsApp Gateway. * * @param string $phone Destination phone number * @param string $message Message text * @param string|null $imageBase64 Optional base64 image data * @return bool True if successfully sent */ public static function sendMessage($phone, $message, $imageBase64 = null) { self::init(); $cleanPhone = preg_replace('/[^\d]/', '', $phone); $payloadData = [ 'session_key' => self::$sessionKey, 'phone' => $cleanPhone, 'message' => $message ]; if ($imageBase64) { $payloadData['image'] = $imageBase64; } $payload = json_encode($payloadData); $ch = curl_init(self::$gatewayUrl . '/api/messages/send'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'X-Webhook-Secret: ' . self::$secret ]); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode === 200 && $response) { $data = json_decode($response, true); return isset($data['status']) && $data['status'] === 'success'; } return false; } /** * Ensure the TTF fonts exist in the backend/fonts directory. * If they don't, try to download them. */ private static function ensureFontsExist() { $fontDir = __DIR__ . '/../fonts'; if (!is_dir($fontDir)) { @mkdir($fontDir, 0755, true); } $fontDir = realpath($fontDir); if (!$fontDir) { return; } // Use Fontsource dedicated jsDelivr CDN endpoint for direct TTF files $fonts = [ 'Roboto-Bold.ttf' => 'https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-700-normal.ttf', 'Lora-Bold.ttf' => 'https://cdn.jsdelivr.net/fontsource/fonts/lora@latest/latin-700-normal.ttf', 'Cairo-Bold.ttf' => 'https://cdn.jsdelivr.net/fontsource/fonts/cairo@latest/arabic-700-normal.ttf' ]; foreach ($fonts as $filename => $url) { $path = $fontDir . '/' . $filename; // Validate if file exists and starts with valid TTF signatures $isValid = false; if (file_exists($path) && filesize($path) > 20000) { $fp = @fopen($path, 'rb'); if ($fp) { $sig = fread($fp, 4); fclose($fp); // Match TTF signatures: 0x00010000, 'OTTO', 'true' if ($sig === "\x00\x01\x00\x00" || $sig === "OTTO" || $sig === "true") { $isValid = true; } } } if (!$isValid) { @unlink($path); // Delete invalid/corrupted file // Try file_get_contents first $content = @file_get_contents($url); if ($content !== false && strlen($content) > 20000) { @file_put_contents($path, $content); } elseif (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 15); $content = curl_exec($ch); curl_close($ch); if ($content && strlen($content) > 20000) { @file_put_contents($path, $content); } } } } } /** * Generate dynamic base64-encoded image containing OTP code using GD library. * * @param string $otp 3-digit OTP code * @return string Base64 encoded PNG image */ public static function generateOtpImageBase64($otp) { // Ensure fonts exist (downloads them if missing) self::ensureFontsExist(); // Create a 300x100 image $im = imagecreatetruecolor(300, 100); if (!$im) { return ''; } // Colors $bgColor = imagecolorallocate($im, 240, 244, 248); // Soft grey-blue $textColor = imagecolorallocate($im, 33, 37, 41); // Dark charcoal $noiseColor = imagecolorallocate($im, 200, 210, 220); // Light noise $otpFg = imagecolorallocate($im, 13, 110, 253); // Royal blue for OTP // Fill background imagefill($im, 0, 0, $bgColor); // --- 1. Check if TTF is available and fonts are loaded --- $useTtf = false; $availableFonts = []; $arabicFont = ''; if (function_exists('imagettftext')) { $fontDir = realpath(__DIR__ . '/../fonts'); if ($fontDir) { if (file_exists($fontDir . '/Roboto-Bold.ttf') && @filesize($fontDir . '/Roboto-Bold.ttf') > 20000) { $availableFonts[] = $fontDir . '/Roboto-Bold.ttf'; } if (file_exists($fontDir . '/Lora-Bold.ttf') && @filesize($fontDir . '/Lora-Bold.ttf') > 20000) { $availableFonts[] = $fontDir . '/Lora-Bold.ttf'; } if (file_exists($fontDir . '/Cairo-Bold.ttf') && @filesize($fontDir . '/Cairo-Bold.ttf') > 20000) { $arabicFont = $fontDir . '/Cairo-Bold.ttf'; // We can also use Cairo for drawing OTP digits for extra variety! $availableFonts[] = $fontDir . '/Cairo-Bold.ttf'; } if (!empty($availableFonts)) { $useTtf = true; } } } $chars = str_split($otp); if ($useTtf) { // Render smooth OTP using TTF fonts // Center the text block roughly. A 3-digit OTP with size 30px needs ~120px width. $startX = random_int(85, 115); $yBase = 65; // Baseline of TTF text foreach ($chars as $char) { // Alternating / random font selection per digit $fontFile = $availableFonts[array_rand($availableFonts)]; $angle = random_int(-15, 15); // Dynamic tilts to thwart OCR $size = random_int(28, 34); // Dynamic size $yOffset = random_int(-6, 6); // Vertical jitter // Draw character imagettftext($im, $size, $angle, $startX, $yBase + $yOffset, $otpFg, $fontFile, $char); $startX += 42; // Spacing to next char (larger spacing to handle angle/size variations) } } else { // Fallback: draw scaled bitmap fonts if TTF not supported $otpWidth = 45; // 3 chars * 15px width roughly $otpHeight = 20; $otpIm = imagecreatetruecolor($otpWidth, $otpHeight); $otpBg = imagecolorallocate($otpIm, 240, 244, 248); $otpFgTemp = imagecolorallocate($otpIm, 13, 110, 253); imagefill($otpIm, 0, 0, $otpBg); $x = 2; foreach ($chars as $char) { $y = random_int(0, 5); // Slight vertical jitter imagestring($otpIm, 5, $x, $y, $char, $otpFgTemp); $x += 14; } // Scale it up by 3x onto the main image $scale = 3; $dstWidth = $otpWidth * $scale; $dstHeight = $otpHeight * $scale; $dstX = random_int(80, 140); $dstY = random_int(30, 40); imagecopyresampled($im, $otpIm, $dstX, $dstY, 0, 0, $dstWidth, $dstHeight, $otpWidth, $otpHeight); imagedestroy($otpIm); } // --- 2. Add Background Noise (Lines & Dots) --- for ($i = 0; $i < 6; $i++) { imageline($im, random_int(0, 300), random_int(0, 100), random_int(0, 300), random_int(0, 100), $noiseColor); } for ($i = 0; $i < 100; $i++) { imagesetpixel($im, random_int(0, 300), random_int(0, 100), $noiseColor); } // --- 3. Draw Random Header Label with Variable Font --- $labels = [ // English labels of varying word count ['text' => 'Verification Code:', 'lang' => 'en'], ['text' => 'Your OTP:', 'lang' => 'en'], ['text' => 'Security Key:', 'lang' => 'en'], ['text' => 'Access Number:', 'lang' => 'en'], ['text' => 'Auth Code:', 'lang' => 'en'], ['text' => 'Login Pin:', 'lang' => 'en'], ['text' => 'Secret Key:', 'lang' => 'en'], ['text' => 'Your Number:', 'lang' => 'en'], ['text' => 'One Time Pass:', 'lang' => 'en'], ['text' => 'Code:', 'lang' => 'en'], ['text' => 'App Verification PIN:', 'lang' => 'en'], ['text' => 'System Access Key:', 'lang' => 'en'], ['text' => 'Dynamic Access PIN:', 'lang' => 'en'], ['text' => 'Secure Verification Code:', 'lang' => 'en'], ['text' => 'Account Code:', 'lang' => 'en'], ['text' => 'Secure Passcode:', 'lang' => 'en'], // Pre-shaped Arabic labels (written RTL for GD engine) ['text' => 'ﻖﻘﺤﺘﻠﺎ ﺰﻣﺮ', 'lang' => 'ar'], // رمز التحقق ['text' => 'ﻥﺎﻣﺃﻟﺍ ﺩﻮﻛ', 'lang' => 'ar'], // كود الأمان ['text' => 'ﺪﻴﻜﺃﺘﻠﺍ ﺰﻣﺮ', 'lang' => 'ar'], // رمز التأكيد ['text' => 'ﻒﻳﺮﻌﺘﻟﺍ ﻢﻗﺭ', 'lang' => 'ar'] // رقم التعريف ]; $selectedLabel = $labels[array_rand($labels)]; $label = $selectedLabel['text']; if ($useTtf) { // Select font: Cairo-Bold for Arabic, random for English $labelFont = ($selectedLabel['lang'] === 'ar' && $arabicFont) ? $arabicFont : $availableFonts[array_rand($availableFonts)]; // Draw smooth TTF label (y=28 is baseline position, roughly equivalent to y=10 box) $size = ($selectedLabel['lang'] === 'ar') ? 14 : 12; // Adjust size for Arabic readability imagettftext($im, $size, 0, 20, 28, $textColor, $labelFont, $label); } else { // Fallback: draw using built-in font (convert Arabic to English fallback to avoid garbage text) $fallbackLabel = $label; if ($selectedLabel['lang'] === 'ar') { $fallbackLabel = 'Verification Code:'; } $labelFont = random_int(3, 5); imagestring($im, $labelFont, 20, 10, $fallbackLabel, $textColor); } // Draw a bounding border imagerectangle($im, 0, 0, 299, 99, $noiseColor); // Capture output ob_start(); imagepng($im); $imageData = ob_get_clean(); imagedestroy($im); return base64_encode($imageData); } }