db = Database::getInstance(); $this->logger = Logger::getInstance(); $config = App::getConfig(); $this->serverKey = $config['fcm']['server_key'] ?? $_ENV['FCM_SERVER_KEY'] ?? ''; } public function isConfigured(): bool { return !empty($this->serverKey); } public function sendToUser(int $userId, string $title, string $body, string $type = 'info', ?int $notificationId = null): bool { if (!$this->isConfigured()) { return false; } $tokens = $this->db->fetchAll( 'SELECT token FROM user_fcm_tokens WHERE user_id = ?', [$userId] ); if (empty($tokens)) { return false; } $success = true; foreach ($tokens as $row) { $result = $this->sendToDevice($row['token'], $title, $body, $type, $notificationId); if (!$result) $success = false; } return $success; } public function sendToAll(string $title, string $body, string $type = 'info', ?int $notificationId = null): int { if (!$this->isConfigured()) { return 0; } $tokens = $this->db->fetchAll('SELECT DISTINCT token FROM user_fcm_tokens'); if (empty($tokens)) return 0; $sent = 0; foreach ($tokens as $row) { if ($this->sendToDevice($row['token'], $title, $body, $type, $notificationId)) { $sent++; } } return $sent; } private function sendToDevice(string $token, string $title, string $body, string $type, ?int $notificationId): bool { $payload = [ 'to' => $token, 'priority' => 'high', 'notification' => [ 'title' => $title, 'body' => $body, 'sound' => 'default', ], 'data' => [ 'type' => $type, 'notification_id' => (string) ($notificationId ?? 0), 'click_action' => 'OPEN_NOTIFICATIONS', ], 'android' => [ 'priority' => 'high', 'notification' => [ 'channel_id' => match ($type) { 'error', 'warning' => 'notifications_alerts', default => 'notifications_general', }, 'priority' => match ($type) { 'error' => 'high', default => 'default', }, 'sound' => 'default', ], ], ]; $ch = curl_init('https://fcm.googleapis.com/fcm/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: key=' . $this->serverKey, ], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { $this->logger->error('FCM send failed', [ 'http_code' => $httpCode, 'response' => substr($response, 0, 500), ]); return false; } return true; } }