Files
2026-03-05 10:25:39 +01:00

184 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/../../includes/bootstrap_global.php';
require_once __DIR__ . '/../vendor/autoload.php';
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$cfg = require __DIR__ . '/config.php';
$auth = [
'VAPID' => [
'subject' => $cfg['vapid_subject'],
'publicKey' => $cfg['vapid_public'],
'privateKey' => $cfg['vapid_private'],
],
];
$webPush = new WebPush($auth);
$webPush->setDefaultOptions([
'TTL' => (int)($cfg['ttl'] ?? 86400),
]);
/**
* badgeCount = msg_unread + rdv_unread
* table push_badge_counters est indexée par compte_id (PK)
*/
function computeBadgeCount(PDO $pdo, array $n): int
{
$cid = (int)($n['target_compte_id'] ?? 0);
if ($cid <= 0) return 0;
$st = $pdo->prepare("SELECT msg_unread, rdv_unread FROM push_badge_counters WHERE compte_id = ? LIMIT 1");
$st->execute([$cid]);
$row = $st->fetch(PDO::FETCH_ASSOC);
if (!$row) return 0;
$total = (int)($row['msg_unread'] ?? 0) + (int)($row['rdv_unread'] ?? 0);
return max(0, min(99, $total));
}
// 1) lot pending
$notifs = $pdo->query("
SELECT *
FROM push_notifications
WHERE status = 'pending'
ORDER BY id ASC
LIMIT 50
")->fetchAll(PDO::FETCH_ASSOC);
if (!$notifs) exit(0);
foreach ($notifs as $n) {
$nid = (int)$n['id'];
// 1bis) lock
$lock = $pdo->prepare("
UPDATE push_notifications
SET status='sending'
WHERE id=? AND status='pending'
");
$lock->execute([$nid]);
if ($lock->rowCount() === 0) continue;
// 2) subscriptions cibles
$where = ["is_active = 1"];
$params = [];
if (!empty($n['target_compte_id'])) {
$where[] = "compte_id = :cid";
$params[':cid'] = (int)$n['target_compte_id'];
} else {
// fallback (normalement tu as toujours target_compte_id)
$where[] = "role = :role";
$params[':role'] = (string)$n['target_role'];
if (!empty($n['target_reference_id'])) {
$where[] = "reference_id = :ref";
$params[':ref'] = (int)$n['target_reference_id'];
}
}
$stmt = $pdo->prepare("
SELECT id, endpoint, p256dh, auth
FROM push_subscriptions
WHERE " . implode(' AND ', $where) . "
");
$stmt->execute($params);
$subs = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$subs) {
$pdo->prepare("
UPDATE push_notifications
SET status='failed', attempts=attempts+1, last_error='no_subscriptions'
WHERE id=?
")->execute([$nid]);
continue;
}
// 3) payload
$badgeCount = computeBadgeCount($pdo, $n);
$payload = [
'type' => $n['type'],
'title' => $n['title'],
'body' => $n['body'],
'url' => $n['url'],
'data' => $n['data_json'] ? json_decode((string)$n['data_json'], true) : null,
'nid' => $nid,
'badgeCount' => $badgeCount,
];
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// map endpoint => subscription_id
$subIdByEndpoint = [];
foreach ($subs as $s) {
$endpoint = (string)$s['endpoint'];
$subIdByEndpoint[$endpoint] = (int)$s['id'];
$subscription = Subscription::create([
'endpoint' => $endpoint,
'publicKey' => (string)$s['p256dh'],
'authToken' => (string)$s['auth'],
]);
$webPush->queueNotification($subscription, $payloadJson, ['urgency' => 'normal']);
}
// 4) flush + delivery log
$successCount = 0;
$lastErr = null;
$insLog = $pdo->prepare("
INSERT INTO push_delivery_log
(notification_id, subscription_id, http_status, error_code, error_message, created_at)
VALUES
(:nid, :sid, :http, :ecode, :emsg, NOW())
");
foreach ($webPush->flush() as $report) {
$ok = $report->isSuccess();
$endpoint = (string)$report->getRequest()->getUri();
$sid = $subIdByEndpoint[$endpoint] ?? null;
$statusCode = $report->getResponse() ? $report->getResponse()->getStatusCode() : null;
$reason = $ok ? null : (string)$report->getReason();
if ($ok) $successCount++;
if ($sid) {
$insLog->execute([
':nid' => $nid,
':sid' => $sid,
':http' => $statusCode,
':ecode' => (!$ok && in_array((int)$statusCode, [404, 410], true)) ? 'gone' : null,
':emsg' => $reason ? mb_substr($reason, 0, 255) : null,
]);
}
if (!$ok && in_array((int)$statusCode, [404, 410], true)) {
$pdo->prepare("UPDATE push_subscriptions SET is_active=0 WHERE endpoint=?")->execute([$endpoint]);
}
if (!$ok && $reason) $lastErr = $reason;
}
// 5) statut notification
if ($successCount > 0) {
$pdo->prepare("
UPDATE push_notifications
SET status='sent', sent_at=NOW(), attempts=attempts+1, last_error=NULL
WHERE id=?
")->execute([$nid]);
} else {
$pdo->prepare("
UPDATE push_notifications
SET status='failed', attempts=attempts+1, last_error=?
WHERE id=?
")->execute([mb_substr((string)$lastErr, 0, 255), $nid]);
}
}