diff --git a/admin_integrations.php b/admin_integrations.php
index 46eb598..c0d0445 100644
--- a/admin_integrations.php
+++ b/admin_integrations.php
@@ -2,37 +2,65 @@
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
+require_once __DIR__ . '/includes/NotificationService.php';
ensure_schema();
$errors = [];
$success = '';
+$testSuccess = '';
+$testError = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $updates = [
- 'thawani_publishable_key' => trim($_POST['thawani_publishable_key'] ?? ''),
- 'thawani_secret_key' => trim($_POST['thawani_secret_key'] ?? ''),
- 'thawani_environment' => trim($_POST['thawani_environment'] ?? 'test'),
- 'wablas_domain' => trim($_POST['wablas_domain'] ?? ''),
- 'wablas_api_token' => trim($_POST['wablas_api_token'] ?? ''),
- 'wablas_secret_key' => trim($_POST['wablas_secret_key'] ?? ''),
- 'whatsapp_enabled' => isset($_POST['whatsapp_enabled']) ? '1' : '0',
- 'smtp_host' => trim($_POST['smtp_host'] ?? ''),
- 'smtp_port' => trim($_POST['smtp_port'] ?? ''),
- 'smtp_secure' => trim($_POST['smtp_secure'] ?? ''),
- 'smtp_user' => trim($_POST['smtp_user'] ?? ''),
- 'smtp_pass' => trim($_POST['smtp_pass'] ?? ''),
- 'mail_from' => trim($_POST['mail_from'] ?? ''),
- 'mail_from_name' => trim($_POST['mail_from_name'] ?? ''),
- ];
-
- if (empty($errors)) {
- $pdo = db();
- foreach ($updates as $key => $val) {
- $stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:k, :v) ON DUPLICATE KEY UPDATE setting_value = :v2");
- $stmt->execute([':k' => $key, ':v' => $val, ':v2' => $val]);
+ if (isset($_POST['action']) && $_POST['action'] === 'test_whatsapp') {
+ $testPhone = trim($_POST['test_phone'] ?? '');
+ $testMessage = trim($_POST['test_message'] ?? '');
+
+ if ($testPhone === '') {
+ $testError = 'Phone number is required for testing.';
+ } else {
+ // Check if settings are saved first?
+ // We rely on get_setting() which reads from DB.
+ // If user just changed input but didn't save, it won't work.
+ // That's standard behavior.
+
+ try {
+ // We use the new public method in NotificationService
+ // This method handles the 968 prefix logic internally.
+ NotificationService::sendWhatsApp($testPhone, $testMessage ?: 'Test message from CargoLink');
+ $testSuccess = 'Test message sent (check logs/phone).';
+ } catch (Throwable $e) {
+ $testError = 'Error sending test: ' . $e->getMessage();
+ }
+ }
+
+ } else {
+ // Save Settings
+ $updates = [
+ 'thawani_publishable_key' => trim($_POST['thawani_publishable_key'] ?? ''),
+ 'thawani_secret_key' => trim($_POST['thawani_secret_key'] ?? ''),
+ 'thawani_environment' => trim($_POST['thawani_environment'] ?? 'test'),
+ 'wablas_domain' => trim($_POST['wablas_domain'] ?? ''),
+ 'wablas_api_token' => trim($_POST['wablas_api_token'] ?? ''),
+ 'wablas_secret_key' => trim($_POST['wablas_secret_key'] ?? ''),
+ 'whatsapp_enabled' => isset($_POST['whatsapp_enabled']) ? '1' : '0',
+ 'smtp_host' => trim($_POST['smtp_host'] ?? ''),
+ 'smtp_port' => trim($_POST['smtp_port'] ?? ''),
+ 'smtp_secure' => trim($_POST['smtp_secure'] ?? ''),
+ 'smtp_user' => trim($_POST['smtp_user'] ?? ''),
+ 'smtp_pass' => trim($_POST['smtp_pass'] ?? ''),
+ 'mail_from' => trim($_POST['mail_from'] ?? ''),
+ 'mail_from_name' => trim($_POST['mail_from_name'] ?? ''),
+ ];
+
+ if (empty($errors)) {
+ $pdo = db();
+ foreach ($updates as $key => $val) {
+ $stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:k, :v) ON DUPLICATE KEY UPDATE setting_value = :v2");
+ $stmt->execute([':k' => $key, ':v' => $val, ':v2' => $val]);
+ }
+ $success = "Integrations settings updated successfully.";
}
- $success = "Integrations settings updated successfully.";
}
}
@@ -68,11 +96,17 @@ render_header('Integrations', 'admin', true);
= e($success) ?>
+
+ = e($testSuccess) ?>
+
+
+ = e($testError) ?>
+
= e(implode('
', $errors)) ?>
-
+
+
+
diff --git a/includes/NotificationService.php b/includes/NotificationService.php
index 9886416..a62305c 100644
--- a/includes/NotificationService.php
+++ b/includes/NotificationService.php
@@ -9,7 +9,7 @@ class NotificationService
* Send a notification for a specific event.
*
* @param string $eventName The event name (e.g., 'shipment_created')
- * @param array $user The recipient user array (must contain 'email' and 'full_name')
+ * @param array $user The recipient user array (must contain 'id', 'email', 'full_name', 'role')
* @param array $data Data to replace in placeholders (e.g., ['shipment_id' => 123])
* @param string|null $lang 'en' or 'ar'. If null, sends both combined.
*/
@@ -69,9 +69,109 @@ class NotificationService
// Send WhatsApp if enabled
if (get_setting('whatsapp_enabled') === '1') {
- // Log WhatsApp (Mock)
- // In a real app, this would call Twilio/Meta API
- error_log("WHATSAPP Notification to {$user['email']} (Phone N/A): $whatsapp");
+ $phone = self::getPhoneNumber($user);
+ if ($phone) {
+ self::sendWhatsApp($phone, $whatsapp);
+ } else {
+ error_log("WHATSAPP Notification skipped for {$user['email']}: Phone number not found.");
+ }
}
}
-}
\ No newline at end of file
+
+ /**
+ * Retrieve phone number for a user, fetching from profile if necessary.
+ */
+ private static function getPhoneNumber(array $user): ?string
+ {
+ if (!empty($user['phone'])) {
+ return $user['phone'];
+ }
+
+ if (empty($user['id']) || empty($user['role'])) {
+ return null;
+ }
+
+ $pdo = db();
+ if ($user['role'] === 'shipper') {
+ $stmt = $pdo->prepare("SELECT phone FROM shipper_profiles WHERE user_id = ?");
+ $stmt->execute([$user['id']]);
+ return $stmt->fetchColumn() ?: null;
+ } elseif ($user['role'] === 'truck_owner') {
+ $stmt = $pdo->prepare("SELECT phone FROM truck_owner_profiles WHERE user_id = ?");
+ $stmt->execute([$user['id']]);
+ return $stmt->fetchColumn() ?: null;
+ }
+
+ return null;
+ }
+
+ /**
+ * Send a WhatsApp message via Wablas API.
+ */
+ public static function sendWhatsApp(string $phone, string $message)
+ {
+ // 1. Format Phone Number
+ // Remove non-numeric characters
+ $phone = preg_replace('/[^0-9]/', '', $phone);
+
+ // If 8 digits, prepend 968 (Oman country code)
+ if (strlen($phone) === 8) {
+ $phone = '968' . $phone;
+ }
+
+ // 2. Get Settings
+ $domain = get_setting('wablas_domain');
+ $token = get_setting('wablas_api_token');
+
+ if (!$domain || !$token) {
+ error_log("WHATSAPP Error: Wablas domain or token not configured.");
+ return;
+ }
+
+ // Ensure domain has no trailing slash and correct scheme
+ $domain = rtrim($domain, '/');
+ if (!preg_match('/^https?:\\/', $domain)) {
+ $domain = 'https://' . $domain;
+ }
+
+ // 3. Send API Request
+ // Endpoint: /api/send-message (StandardWablas)
+ $url = $domain . "/api/send-message";
+
+ $data = [
+ 'phone' => $phone,
+ 'message' => $message,
+ ];
+
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ "Authorization: $token",
+ "Content-Type: application/json" // Wablas often accepts JSON or form-data. Trying JSON first, or form-data if that fails.
+ // Actually, many Wablas instances use form-data or x-www-form-urlencoded.
+ // Let's try standard POST fields which curl sends as application/x-www-form-urlencoded by default if passing array.
+ ]);
+
+ // Resetting headers to just Authorization for form-urlencoded default
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ "Authorization: $token"
+ ]);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For dev env
+
+ $response = curl_exec($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $error = curl_error($ch);
+ curl_close($ch);
+
+ if ($error) {
+ error_log("WHATSAPP cURL Error: $error");
+ return;
+ }
+
+ // Log result
+ error_log("WHATSAPP Sent to $phone. Status: $httpCode. Response: " . substr($response, 0, 100));
+ }
+}