216 lines
7.4 KiB
PHP
216 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Core\Request;
|
|
use App\Core\Response;
|
|
use App\Models\Campaign;
|
|
|
|
class CampaignController extends BaseController
|
|
{
|
|
/**
|
|
* List all campaigns for the company
|
|
*/
|
|
public function index(Request $request, Response $response)
|
|
{
|
|
$campaignModel = new Campaign();
|
|
$campaigns = $campaignModel->findAllByCompany($request->company_id);
|
|
|
|
// Count sent messages per campaign from database
|
|
foreach ($campaigns as &$cmp) {
|
|
$counts = \App\Core\Database::selectOne(
|
|
"SELECT COUNT(*) as total FROM messages_log WHERE campaign_id = ? AND status = 'sent'",
|
|
[$cmp['id']]
|
|
);
|
|
$cmp['sent_count'] = $counts['total'] ?? 0;
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'data' => $campaigns
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Create a new broadcast campaign and launch it
|
|
*/
|
|
public function store(Request $request, Response $response)
|
|
{
|
|
$errors = $this->validate($request, [
|
|
'name' => 'required',
|
|
'group_id' => 'required',
|
|
'session_id' => 'required',
|
|
'template_id' => 'required'
|
|
]);
|
|
|
|
if (!empty($errors)) {
|
|
$response->status(400)->json(['status' => 'error', 'errors' => $errors]);
|
|
return;
|
|
}
|
|
|
|
$body = $request->getBody();
|
|
$campaignModel = new Campaign();
|
|
|
|
$id = $campaignModel->create([
|
|
'company_id' => $request->company_id,
|
|
'name' => $body['name'],
|
|
'group_id' => $body['group_id'],
|
|
'session_id' => $body['session_id'],
|
|
'template_id' => $body['template_id'],
|
|
'status' => 'pending',
|
|
'scheduled_at' => $body['scheduled_at'] ?? null
|
|
]);
|
|
|
|
// Launch campaign in background using PHP fastcgi_finish_request
|
|
if (function_exists('fastcgi_finish_request')) {
|
|
$response->status(201);
|
|
$response->setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
$allowedOrigin = getenv('ALLOWED_ORIGIN') ?: '*';
|
|
$response->setHeader('Access-Control-Allow-Origin', $allowedOrigin);
|
|
$response->setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
$response->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
|
|
$response->sendHeaders();
|
|
http_response_code(201);
|
|
echo json_encode([
|
|
'status' => 'success',
|
|
'message' => 'Campaign started in background',
|
|
'id' => $id
|
|
], JSON_UNESCAPED_UNICODE);
|
|
fastcgi_finish_request();
|
|
|
|
$this->dispatchCampaign($id, $request->company_id);
|
|
exit;
|
|
} else {
|
|
// Fallback for environment without PHP-FPM
|
|
$this->dispatchCampaign($id, $request->company_id);
|
|
$response->status(201)->json([
|
|
'status' => 'success',
|
|
'message' => 'Campaign completed successfully (synchronous fallback)',
|
|
'id' => $id
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch campaign messages in sequence with rate limiting
|
|
*/
|
|
private function dispatchCampaign(int $campaignId, int $companyId)
|
|
{
|
|
set_time_limit(0);
|
|
ignore_user_abort(true);
|
|
|
|
$campaign = \App\Core\Database::selectOne(
|
|
"SELECT * FROM campaigns WHERE id = ? AND company_id = ? LIMIT 1",
|
|
[$campaignId, $companyId]
|
|
);
|
|
|
|
if (!$campaign) return;
|
|
|
|
// Set status to running
|
|
\App\Core\Database::execute(
|
|
"UPDATE campaigns SET status = 'running' WHERE id = ?",
|
|
[$campaignId]
|
|
);
|
|
|
|
// Fetch template
|
|
$template = \App\Models\Template::findByIdAndCompany($campaign['template_id'], $companyId);
|
|
if (!$template) {
|
|
\App\Core\Database::execute(
|
|
"UPDATE campaigns SET status = 'failed' WHERE id = ?",
|
|
[$campaignId]
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Fetch whatsapp session
|
|
$session = \App\Core\Database::selectOne(
|
|
"SELECT * FROM whatsapp_sessions WHERE id = ? AND company_id = ? LIMIT 1",
|
|
[$campaign['session_id'], $companyId]
|
|
);
|
|
if (!$session || $session['status'] !== 'connected') {
|
|
\App\Core\Database::execute(
|
|
"UPDATE campaigns SET status = 'failed' WHERE id = ?",
|
|
[$campaignId]
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Get contacts in group
|
|
$contacts = \App\Models\ContactGroup::getRawContacts($campaign['group_id']);
|
|
if (empty($contacts)) {
|
|
\App\Core\Database::execute(
|
|
"UPDATE campaigns SET status = 'completed' WHERE id = ?",
|
|
[$campaignId]
|
|
);
|
|
return;
|
|
}
|
|
|
|
$gatewayUrl = getenv('WHATSAPP_GATEWAY_URL') ?: 'http://localhost:3722';
|
|
$sendUrl = $gatewayUrl . '/api/messages/send';
|
|
|
|
foreach ($contacts as $rawContact) {
|
|
// Decrypt contact data
|
|
$contact = \App\Models\Contact::findByPhone($companyId, \App\Core\Security::decrypt($rawContact['phone']));
|
|
if (!$contact) continue;
|
|
|
|
// Replace template variables
|
|
$messageBody = str_replace('{{name}}', $contact['name'], $template['body']);
|
|
|
|
// Send via cURL to Node.js Gateway
|
|
$payload = json_encode([
|
|
'session_key' => $session['session_key'],
|
|
'phone' => $contact['phone'],
|
|
'message' => $messageBody,
|
|
'media_url' => $template['media_url']
|
|
]);
|
|
|
|
$ch = curl_init($sendUrl);
|
|
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: ' . getenv('WEBHOOK_SECRET')
|
|
]);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
$status = 'failed';
|
|
$errorMsg = null;
|
|
|
|
if ($httpCode === 200) {
|
|
$status = 'sent';
|
|
} else {
|
|
$resData = json_decode($response, true);
|
|
$errorMsg = $resData['error'] ?? 'HTTP Code ' . $httpCode;
|
|
}
|
|
|
|
// Log message securely
|
|
\App\Models\MessageLog::logMessage([
|
|
'company_id' => $companyId,
|
|
'session_id' => $session['id'],
|
|
'campaign_id' => $campaignId,
|
|
'contact_phone' => $contact['phone'],
|
|
'direction' => 'outbound',
|
|
'message_type' => $template['type'],
|
|
'message_body' => $messageBody,
|
|
'media_url' => $template['media_url'],
|
|
'status' => $status,
|
|
'error_message' => $errorMsg
|
|
]);
|
|
|
|
// Wait 2 seconds between messages
|
|
sleep(2);
|
|
}
|
|
|
|
// Set status to completed
|
|
\App\Core\Database::execute(
|
|
"UPDATE campaigns SET status = 'completed' WHERE id = ?",
|
|
[$campaignId]
|
|
);
|
|
}
|
|
}
|