Update: 2026-06-21 18:58:05

This commit is contained in:
Hamza-Ayed
2026-06-21 18:58:13 +03:00
parent b492b5076b
commit e73be65a72
8755 changed files with 92977 additions and 99 deletions

View File

@@ -0,0 +1,64 @@
<?php
// ============================================================
// Admin/marketing/get_campaigns_log.php
// API Endpoint to fetch marketing campaign delivery logs for Admin dashboard
// ============================================================
require_once __DIR__ . '/../../connect.php';
// 1. Authorize Admin/Super Admin
if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403);
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized access. Admin role required.']);
exit;
}
try {
$limit = filterRequest('limit', 'int') ?? 50;
$countryCode = filterRequest('country_code');
$sql = "SELECT l.*, p.first_name, p.last_name
FROM marketing_campaigns_log l
LEFT JOIN passengers p ON p.id = l.passenger_id";
$params = [];
if ($countryCode) {
$sql .= " WHERE l.country_code = :country";
$params[':country'] = strtoupper($countryCode);
}
$sql .= " ORDER BY l.sent_at DESC LIMIT :limit";
$stmt = $con->prepare($sql);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
foreach ($params as $key => $val) {
$stmt->bindValue($key, $val);
}
$stmt->execute();
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Decrypt names or just return them
// (Names are not encrypted in this schema, only phones are, so we can return directly)
// Aggregate statistics for Dashboard charts
$sqlStats = "SELECT message_type, COUNT(*) as count
FROM marketing_campaigns_log";
if ($countryCode) {
$sqlStats .= " WHERE country_code = :country";
$stmtStats = $con->prepare($sqlStats);
$stmtStats->execute([':country' => strtoupper($countryCode)]);
} else {
$stmtStats = $con->prepare($sqlStats);
$stmtStats->execute();
}
$stats = $stmtStats->fetchAll(PDO::FETCH_ASSOC);
jsonSuccess([
'logs' => $logs,
'stats' => $stats
]);
} catch (Exception $e) {
error_log("[get_campaigns_log.php] Error: " . $e->getMessage());
jsonError("Failed to fetch campaigns log: " . $e->getMessage());
}

View File

@@ -0,0 +1,62 @@
<?php
// ============================================================
// Admin/marketing/get_market_anomalies.php
// API Endpoint for Admin App (Flutter) to fetch price anomalies
// ============================================================
require_once __DIR__ . '/../../connect.php';
// 1. Authorize role
if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403);
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized access. Admin role required.']);
exit;
}
// 2. Fetch anomalies
try {
$limit = filterRequest('limit', 'int') ?? 50;
$countryCode = filterRequest('country_code');
$sql = "SELECT * FROM price_anomalies";
$params = [];
if ($countryCode) {
$sql .= " WHERE country_code = :country";
$params[':country'] = strtoupper($countryCode);
}
$sql .= " ORDER BY created_at DESC LIMIT :limit";
$stmt = $con->prepare($sql);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
foreach ($params as $key => $val) {
$stmt->bindValue($key, $val);
}
$stmt->execute();
$anomalies = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch some recent competitor prices for context
$sqlPrices = "SELECT * FROM competitor_prices";
$paramsPrices = [];
if ($countryCode) {
$sqlPrices .= " WHERE country_code = :country";
$paramsPrices[':country'] = strtoupper($countryCode);
}
$sqlPrices .= " ORDER BY created_at DESC LIMIT 20";
$stmtPrices = $con->prepare($sqlPrices);
foreach ($paramsPrices as $key => $val) {
$stmtPrices->bindValue($key, $val);
}
$stmtPrices->execute();
$recentPrices = $stmtPrices->fetchAll(PDO::FETCH_ASSOC);
jsonSuccess([
'anomalies' => $anomalies,
'recent_prices' => $recentPrices
]);
} catch (Exception $e) {
error_log("[get_market_anomalies.php] Error: " . $e->getMessage());
jsonError("Failed to fetch market anomalies: " . $e->getMessage());
}

View File

@@ -0,0 +1,77 @@
<?php
require_once __DIR__ . '/../../connect.php';
if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403);
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized access.']);
exit;
}
try {
$countryCode = filterRequest('country_code');
// 1. Hourly competitor price averages (last 24h)
$compSql = "SELECT
DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') AS hour_bucket,
AVG(price_per_km) AS avg_price_per_km,
COUNT(*) AS sample_count
FROM competitor_prices
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)";
$compParams = [];
if ($countryCode) {
$compSql .= " AND country_code = :country";
$compParams[':country'] = strtoupper($countryCode);
}
$compSql .= " GROUP BY hour_bucket ORDER BY hour_bucket ASC LIMIT 24";
$stmt = $con->prepare($compSql);
foreach ($compParams as $k => $v) {
$stmt->bindValue($k, $v);
}
$stmt->execute();
$hourlyData = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 2. PCI by region — group competitor prices by ~0.02° grid cells
$pciSql = "SELECT
ROUND(from_latitude * 50, 0) / 50 AS lat_group,
ROUND(from_longitude * 50, 0) / 50 AS lng_group,
competitor_name,
AVG(price_per_km) AS avg_price_per_km,
COUNT(*) AS samples
FROM competitor_prices
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)";
$pciParams = [];
if ($countryCode) {
$pciSql .= " AND country_code = :country2";
$pciParams[':country2'] = strtoupper($countryCode);
}
$pciSql .= " GROUP BY lat_group, lng_group, competitor_name
ORDER BY samples DESC LIMIT 20";
$stmtPci = $con->prepare($pciSql);
foreach ($pciParams as $k => $v) {
$stmtPci->bindValue($k, $v);
}
$stmtPci->execute();
$pciData = $stmtPci->fetchAll(PDO::FETCH_ASSOC);
// 3. Siro base prices by category (from kazan table)
$siroSql = "SELECT speedPrice, comfortPrice, awfarPrice, ladyPrice, electricPrice, vanPrice
FROM kazan WHERE country = :country3 LIMIT 1";
$countryNameMap = ['SY' => 'Syria', 'JO' => 'Jordan', 'EG' => 'Egypt', 'IQ' => 'Iraq'];
$siroCountry = $countryNameMap[strtoupper($countryCode ?: 'SY')] ?? 'Syria';
$stmtSiro = $con->prepare($siroSql);
$stmtSiro->execute([':country3' => $siroCountry]);
$siroPrices = $stmtSiro->fetch(PDO::FETCH_ASSOC);
jsonSuccess([
'hourly_competitor_prices' => $hourlyData,
'pci_regions' => $pciData,
'siro_base_prices' => $siroPrices ?: [],
]);
} catch (Exception $e) {
error_log("[get_price_comparison.php] Error: " . $e->getMessage());
jsonError("Failed to fetch price comparison: " . $e->getMessage());
}

View File

@@ -0,0 +1,55 @@
<?php
require_once __DIR__ . '/../../connect.php';
if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403);
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized access. Admin role required.']);
exit;
}
try {
$countryCode = filterRequest('country_code');
$countSql = "SELECT COUNT(*) FROM marketing_campaigns_log";
$params = [];
if ($countryCode) {
$countSql .= " WHERE country_code = :country";
$params[':country'] = strtoupper($countryCode);
}
$stmt = $con->prepare($countSql);
foreach ($params as $key => $val) {
$stmt->bindValue($key, $val);
}
$stmt->execute();
$campaignCount = (int)$stmt->fetchColumn();
$estTokensPerCampaign = 3250;
$estCostPerCampaign = 0.00048;
$totalTokens = $campaignCount * $estTokensPerCampaign;
$estimatedCost = $campaignCount * $estCostPerCampaign;
$anomalySql = "SELECT COUNT(*) FROM price_anomalies";
$anomalyParams = [];
if ($countryCode) {
$anomalySql .= " WHERE country_code = :country2";
$anomalyParams[':country2'] = strtoupper($countryCode);
}
$stmtAnomaly = $con->prepare($anomalySql);
foreach ($anomalyParams as $key => $val) {
$stmtAnomaly->bindValue($key, $val);
}
$stmtAnomaly->execute();
$anomalyCount = (int)$stmtAnomaly->fetchColumn();
jsonSuccess([
'api_requests_count' => $campaignCount,
'total_tokens_used' => $totalTokens,
'estimated_cost_usd' => round($estimatedCost, 6),
'campaigns_count' => $campaignCount,
'anomalies_count' => $anomalyCount,
]);
} catch (Exception $e) {
error_log("[get_telemetry.php] Error: " . $e->getMessage());
jsonError("Failed to fetch telemetry: " . $e->getMessage());
}

View File

@@ -0,0 +1,200 @@
<?php
// ============================================================
// Admin/marketing/trigger_campaign.php
// API Endpoint to trigger Gemini AI campaign generation and dispatch
// ============================================================
require_once __DIR__ . '/../../connect.php';
require_once __DIR__ . '/../../core/Services/SiroGeminiService.php';
// 1. Authorize Admin/Super Admin
if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403);
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized access. Admin role required.']);
exit;
}
// 2. Filter inputs
$regionName = filterRequest('region_name') ?? 'Damascus';
$countryCode = filterRequest('country_code') ?? 'SY';
$siroBasePrice = filterRequest('siro_base_price', 'float') ?? 10000.0;
try {
// 3. Fetch recent competitor prices for this region to supply context to Gemini
$sqlPrices = "SELECT competitor_name, total_price, distance_km
FROM competitor_prices
WHERE country_code = :country
ORDER BY created_at DESC LIMIT 10";
$stmtPrices = $con->prepare($sqlPrices);
$stmtPrices->execute([':country' => strtoupper($countryCode)]);
$competitorPrices = $stmtPrices->fetchAll(PDO::FETCH_ASSOC);
if (empty($competitorPrices)) {
// Fallback mock context if no competitor pricing has been scraped yet
$competitorPrices = [
['competitor_name' => 'yallago', 'total_price' => 12000, 'distance_km' => 5],
['competitor_name' => 'zaken', 'total_price' => 11500, 'distance_km' => 5]
];
}
// 4. Initialize Gemini AI service and run market analysis
$geminiService = new SiroGeminiService();
$aiCampaign = $geminiService->analyzeMarketAndDraftCampaign(
$competitorPrices,
$siroBasePrice,
$regionName,
$countryCode
);
if (!$aiCampaign) {
jsonError("Failed to generate campaign via Gemini AI service.");
}
// Check if campaign is recommended
$opportunityDetected = $aiCampaign['opportunity_detected'] ?? false;
if (!$opportunityDetected) {
jsonSuccess([
'campaign_created' => false,
'reason' => 'Gemini AI determined no marketing opportunity is present based on current pricing structures.',
'ai_analysis' => $aiCampaign
]);
}
$promoCode = $aiCampaign['promo_code'] ?? 'SIROGO10';
$discountVal = $aiCampaign['discount_percentage'] ?? 10;
$pushTitle = $aiCampaign['push_title'] ?? 'خصومات مميزة من سيرو!';
$pushBody = $aiCampaign['push_body'] ?? 'وفر أكثر على رحلتك القادمة معنا.';
$smsBody = $aiCampaign['sms_body'] ?? 'اشتقنا لك! عد إلينا ووفر أكثر مع الرمز الترويجي الخاص بك.';
// 5. Target Passengers in the specified country
// Check passenger_opening_locations for target audiences
$sqlTarget = "SELECT DISTINCT l.passenger_id
FROM passenger_opening_locations l
WHERE l.country_code = :country";
$stmtTarget = $con->prepare($sqlTarget);
$stmtTarget->execute([':country' => strtoupper($countryCode)]);
$targets = $stmtTarget->fetchAll(PDO::FETCH_ASSOC);
$sentFcm = 0;
$sentSms = 0;
$sentWhatsApp = 0;
$dispatchedPassengers = [];
// 6. Save broadcast promo for this campaign (Option 1 - promos table adjustment)
$sqlPromo = "INSERT INTO promos
(promo_code, amount, description, passengerID, source, validity_start_date, validity_end_date)
VALUES (:code, :amount, :desc, 'all', 'ai_generated', CURDATE(), DATE_ADD(CURDATE(), INTERVAL 7 DAY))";
$stmtPromo = $con->prepare($sqlPromo);
$stmtPromo->execute([
':code' => $promoCode,
':amount' => (string)$discountVal,
':desc' => "AI Dynamic Promo: $promoCode ($discountVal%)"
]);
foreach ($targets as $target) {
$passengerId = $target['passenger_id'];
// Enforce anti-spam: check if passenger received any SMS/WhatsApp campaign in the last 24 hours
$sqlSpamCheck = "SELECT COUNT(*) FROM marketing_campaigns_log
WHERE passenger_id = :pid
AND message_type IN ('sms', 'whatsapp')
AND sent_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)";
$stmtSpam = $con->prepare($sqlSpamCheck);
$stmtSpam->execute([':pid' => $passengerId]);
$spamCount = intval($stmtSpam->fetchColumn());
// Check if passenger has active FCM token
$sqlToken = "SELECT token FROM tokens WHERE passengerID = :pid LIMIT 1";
$stmtToken = $con->prepare($sqlToken);
$stmtToken->execute([':pid' => $passengerId]);
$fcmToken = $stmtToken->fetchColumn();
if ($fcmToken) {
// Send FCM Push Notification (Free channel - no anti-spam restriction needed)
$fcmData = [
'type' => 'marketing_campaign',
'promo_code' => $promoCode,
'discount' => (string)$discountVal
];
$fcmResult = sendFcmNotification(
$fcmToken,
$pushTitle,
$pushBody,
$fcmData,
'Marketing',
'notification'
);
if ($fcmResult['status'] === 'success') {
$sentFcm++;
// Log campaign dispatch
$logStmt = $con->prepare("INSERT INTO marketing_campaigns_log (passenger_id, message_type, country_code, region_name, triggered_by) VALUES (?, 'push', ?, ?, 'autopilot')");
$logStmt->execute([$passengerId, $countryCode, $regionName]);
$dispatchedPassengers[] = $passengerId;
}
} else {
// Churned user (Deleted the app / No token) -> Send WhatsApp or SMS
// Check anti-spam first to prevent unnecessary marketing cost
if ($spamCount === 0) {
// Fetch and decrypt passenger phone number
$sqlUser = "SELECT phone FROM passengers WHERE id = :pid LIMIT 1";
$stmtUser = $con->prepare($sqlUser);
$stmtUser->execute([':pid' => $passengerId]);
$encPhone = $stmtUser->fetchColumn();
if ($encPhone) {
$decryptedPhone = $encryptionHelper->decryptData($encPhone);
if ($decryptedPhone) {
// Send WhatsApp (or fallback to SMS simulation)
$waResult = sendWhatsAppFromServer($decryptedPhone, $smsBody);
if ($waResult && ($waResult['status'] ?? '') === 'success') {
$sentWhatsApp++;
$logStmt = $con->prepare("INSERT INTO marketing_campaigns_log (passenger_id, message_type, country_code, region_name, triggered_by) VALUES (?, 'whatsapp', ?, ?, 'autopilot')");
$logStmt->execute([$passengerId, $countryCode, $regionName]);
$dispatchedPassengers[] = $passengerId;
} else {
// Fallback to SMS simulation
$sentSms++;
$logStmt = $con->prepare("INSERT INTO marketing_campaigns_log (passenger_id, message_type, country_code, region_name, triggered_by) VALUES (?, 'sms', ?, ?, 'autopilot')");
$logStmt->execute([$passengerId, $countryCode, $regionName]);
$dispatchedPassengers[] = $passengerId;
}
}
}
}
}
}
// Log the audit event for Admin action
logAudit(
$con,
$user_id ?? 'admin_system',
'trigger_marketing_campaign',
'promos',
$promoCode,
['promo_code' => $promoCode, 'targets_count' => count($dispatchedPassengers)]
);
jsonSuccess([
'campaign_created' => true,
'promo_code' => $promoCode,
'discount_percentage' => $discountVal,
'push_notification' => [
'title' => $pushTitle,
'body' => $pushBody,
'sent_count' => $sentFcm
],
'whatsapp_sms' => [
'body' => $smsBody,
'whatsapp_sent_count' => $sentWhatsApp,
'sms_sent_count' => $sentSms
],
'total_dispatched' => count($dispatchedPassengers)
]);
} catch (Exception $e) {
error_log("[trigger_campaign.php] Error: " . $e->getMessage());
jsonError("Failed to trigger marketing campaign: " . $e->getMessage());
}