184 lines
4.9 KiB
PHP
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]);
|
|
}
|
|
}
|