Verified Commit c6d2fd4e authored by Richard Weinhold's avatar Richard Weinhold 🔨
Browse files

some code cleanup and minor refactoring for php 7.2

- cleanup of docblock comments
- adds @method hints for PushNotification magic __call methods to internal Handlers
parent 06c6b705
/.idea
/.build
/vendor
composer.lock
#### joe made this: http://goel.io/joe
#### linux ####
*~
......
path: ./src/
jobs: 10
cache: .build/phplint.cache
extensions:
- php
exclude:
- vendor
The MIT License (MIT)
Copyright (c) 2017 Richard Weinhold
Copyright (c) 2020 Richard Weinhold
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
{
"name": "ricwein/push-notifications",
"description": "Push Notifications for iOS (APNS), Android (GCM/FCM) and Windows (WNS)",
"type": "library",
"license": "MIT",
"authors": [{
"name": "Richard Weinhold",
"email": "git@ricwein.com"
}],
"require": {
"php": ">= 7.0.0",
"ext-json": "*"
},
"autoload": {
"psr-4": {
"ricwein\\PushNotification\\": "src/"
}
"name": "ricwein/push-notifications",
"description": "Push Notifications for iOS (APNS), Android (GCM/FCM) and Windows (WNS)",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Richard Weinhold",
"email": "git@ricwein.com"
}
],
"autoload": {
"psr-4": {
"ricwein\\PushNotification\\": "src/"
}
},
"require": {
"php": ">= 7.2.0",
"ext-json": "*",
"ext-curl": "*"
},
"require-dev": {
"overtrue/phplint": "^2.0"
}
}
......@@ -2,65 +2,69 @@
/**
* @author Richard Weinhold
*/
namespace ricwein\PushNotification\Handler;
use ricwein\PushNotification\PushHandler;
use RuntimeException;
use UnexpectedValueException;
/**
* PushHandler for Apple Push Notification Service
*/
class APNSHandler extends PushHandler {
class APNSHandler extends PushHandler
{
/**
* @var array
*/
protected $_server = [
'token' => '',
'url' => 'ssl://gateway.push.apple.com:2195',
'token' => '',
'url' => 'ssl://gateway.push.apple.com:2195',
'passphrase' => null,
];
/**
* build binary notification-package
*
* @param string $deviceToken
* @param string $payload json
* @param array $arbitrary additional settings
* @param int $version push-version (1/2)
*
* @throws \UnexpectedValueException
*
* @param string $payload json
* @param array $arbitrary additional settings
* @param int $version push-version (1/2)
* @return string
* @throws UnexpectedValueException
*/
protected function _buildNotification(string $deviceToken, string $payload, array $arbitrary = [], int $version = 1): string {
protected function _buildNotification(string $deviceToken, string $payload, array $arbitrary = [], int $version = 1): string
{
// set default arbitrary settings
$arbitrary = array_merge([
'expire' => 0,
'expire' => 0,
'messageID' => 0,
'priority' => 10,
'priority' => 10,
], $arbitrary);
// cleanup device tokens
$deviceToken = str_replace(' ', '', trim($deviceToken, '<> '));
// build notification
if ((int) $version === 1) {
if ((int)$version === 1) {
$notification = pack('C', 1); // Command 1
$notification .= pack('N', (int) $arbitrary['messageID']); // notification id
$notification .= pack('N', (int)$arbitrary['messageID']); // notification id
$notification .= pack('N', ($arbitrary['expire'] > 0 ? time() + $arbitrary['expire'] : 0)); // expiration timestamps
$notification .= pack('nH*', 32, $deviceToken); // device-token
$notification .= pack('n', strlen($payload)) . $payload; // payload
return $notification;
} elseif ((int) $version === 2) {
}
if ((int)$version === 2) {
// build notification
$notification = pack('CnH*', 1, 32, $deviceToken); // device-token
$notification .= pack('CnA*', 2, strlen($payload), $payload); // payload
$notification .= pack('CnN', 3, 4, (int) $arbitrary['messageID']); // notification id
$notification .= pack('CnN', 3, 4, (int)$arbitrary['messageID']); // notification id
$notification .= pack('CnN', 4, 4, ($arbitrary['expire'] > 0 ? time() + $arbitrary['expire'] : 0)); // expiration timestamps
$notification .= pack('CnC', 5, 1, (int) $arbitrary['priority']); // notification priority
$notification .= pack('CnC', 5, 1, (int)$arbitrary['priority']); // notification priority
// pack notification into frame
$frame = pack('C', 2); // Command 2
......@@ -69,22 +73,22 @@ class APNSHandler extends PushHandler {
return $frame;
}
throw new \UnexpectedValueException('Unknown Command Version', 500);
throw new UnexpectedValueException('Unknown Command Version', 500);
}
/**
* send notification to Apples APNS servers
*
* @param string $message
* @param string $message
* @param string|null $title
* @param array $payload
* @param array $devices
*
* @throws \UnexpectedValueException|\RuntimeException
*
* @param array $payload
* @param array $devices
* @return bool
* @throws UnexpectedValueException
* @throws RuntimeException
*
*/
public function send(string $message, string $title = null, array $payload, array $devices): bool {
public function send(string $message, ?string $title, array $payload, array $devices): bool
{
$message = trim(stripslashes($message));
// build payload
......@@ -99,27 +103,26 @@ class APNSHandler extends PushHandler {
/**
* build and send Notification from raw payload
*
* @param array $payload
* @param array $devices
*
* @throws \UnexpectedValueException|\RuntimeException
*
* @return bool
* @throws UnexpectedValueException
* @throws RuntimeException
*/
public function sendRaw(array $payload, array $devices): bool {
public function sendRaw(array $payload, array $devices): bool
{
// set default values
$result = true;
$result = true;
$arbitrary = ['command' => 1];
// extract arbitrary settings
foreach (['expire', 'messageID', 'priority', 'command'] as $key) {
if (isset($payload[$key])) {
$arbitrary[$key] = (int) abs($payload[$key]);
$arbitrary[$key] = (int)abs($payload[$key]);
unset($payload[$key]);
} elseif (isset($this->_server[$key])) {
$arbitrary[$key] = (int) abs($this->_server[$key]);
$arbitrary[$key] = (int)abs($this->_server[$key]);
}
}
......@@ -129,11 +132,11 @@ class APNSHandler extends PushHandler {
// check and set cert-path
$certpath = realpath($this->_server['token']);
if (empty($certpath) || $certpath === DIRECTORY_SEPARATOR || !is_file($certpath)) {
throw new \UnexpectedValueException('Invalid cert-file: ' . $certpath, 500);
throw new UnexpectedValueException("Invalid cert-file: {$certpath}", 500);
}
if (!stream_context_set_option($ctx, 'ssl', 'local_cert', $certpath)) {
throw new \UnexpectedValueException('unable to set cert-file', 500);
throw new UnexpectedValueException('unable to set cert-file', 500);
}
// set cert passphrase if given
......@@ -145,22 +148,22 @@ class APNSHandler extends PushHandler {
$stream = @stream_socket_client($this->_server['url'], $errno, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx);
if (!$stream) {
throw new \RuntimeException('Error connecting to APNS-Server [' . $errno . ']: ' . $errstr, 500);
throw new RuntimeException('Error connecting to APNS-Server [' . $errno . ']: ' . $errstr, 500);
}
$payload = json_encode($payload);
$encodedPayload = json_encode($payload);
// create and write notification for each single device
foreach ($devices as $device) {
// build binary notification
$notification = $this->_buildNotification($device, $payload, $arbitrary, $arbitrary['command']);
$notification = $this->_buildNotification($device, $encodedPayload, $arbitrary, $arbitrary['command']);
// write into stream and apply result onto previous results
$result = $result && (bool) fwrite($stream, $notification);
$result = $result && (bool)@fwrite($stream, $notification);
}
// remeber to close the stream when finished
// remember to close the stream when finished
@fclose($stream);
return $result;
......
......@@ -2,46 +2,49 @@
/**
* @author Richard Weinhold
*/
namespace ricwein\PushNotification\Handler;
use ricwein\PushNotification\PushHandler;
use RuntimeException;
use UnexpectedValueException;
/**
* PushHandler for Firebase Cloud Messaging (Google)
*/
class FCMHandler extends PushHandler {
class FCMHandler extends PushHandler
{
/**
* @var array
*/
protected $_server = [
'token' => '',
'url' => 'https://fcm.googleapis.com/fcm/send',
'url' => 'https://fcm.googleapis.com/fcm/send',
];
/**
* send notification to Googles FCM servers
*
* @param string $message
* @param string $message
* @param string|null $title
* @param array $payload
* @param array $devices
*
* @throws \UnexpectedValueException|\RuntimeException
*
* @param array $payload
* @param array $devices
* @return bool
* @throws UnexpectedValueException
* @throws RuntimeException
*/
public function send(string $message, string $title = null, array $payload, array $devices): bool {
public function send(string $message, ?string $title, array $payload, array $devices): bool
{
$message = trim(stripslashes($message));
// build payload
$payload = [
'priority' => 'high',
'priority' => 'high',
'notification' => [
'title' => $title ?? (strlen($message) > 64 ? substr($message, 0, 61) . '...' : $message),
'body' => $message,
'body' => $message,
],
'data' => array_merge([
'data' => array_merge([
'message' => $message,
], $payload),
];
......@@ -55,11 +58,12 @@ class FCMHandler extends PushHandler {
* @param array $payload
* @param array $devices
*
* @throws \RuntimeException
*
* @return bool
* @throws RuntimeException
*
*/
public function sendRaw(array $payload, array $devices): bool {
public function sendRaw(array $payload, array $devices): bool
{
if (count($devices) <= 1) {
$payload = array_merge([
'to' => current($devices),
......@@ -102,7 +106,7 @@ class FCMHandler extends PushHandler {
if ($result === false) {
$error = curl_error($curl);
curl_close($curl);
throw new \RuntimeException('error processing FCM: ' . $error, 500);
throw new RuntimeException('error processing FCM: ' . $error, 500);
}
// remeber to close the connection when finished
......@@ -110,6 +114,6 @@ class FCMHandler extends PushHandler {
// decode response and check if sending to all devices succeeded
$result = @json_decode($result, true);
return isset($result['success']) && (int) $result['success'] === count($devices);
return isset($result['success']) && (int)$result['success'] === count($devices);
}
}
......@@ -2,45 +2,48 @@
/**
* @author Richard Weinhold
*/
namespace ricwein\PushNotification\Handler;
use ricwein\PushNotification\PushHandler;
use RuntimeException;
use UnexpectedValueException;
/**
* PushHandler for Google Cloud Messaging
*/
class GCMHandler extends PushHandler {
class GCMHandler extends PushHandler
{
/**
* @var array
*/
protected $_server = [
'token' => '',
'url' => 'https://gcm-http.googleapis.com/gcm/send',
'url' => 'https://gcm-http.googleapis.com/gcm/send',
];
/**
* send notification to Googles GCM servers
*
* @param string $message
* @param string $message
* @param string|null $title
* @param array $payload
* @param array $devices
*
* @throws \UnexpectedValueException|\RuntimeException
*
* @param array $payload
* @param array $devices
* @return bool
* @throws UnexpectedValueException
* @throws RuntimeException
*/
public function send(string $message, string $title = null, array $payload, array $devices): bool {
public function send(string $message, ?string $title, array $payload, array $devices): bool
{
$message = trim(stripslashes($message));
// build payload
$payload = [
'notification' => [
'title' => $title ?? (strlen($message) > 64 ? substr($message, 0, 61) . '...' : $message),
'body' => $message,
'body' => $message,
],
'data' => array_merge([
'data' => array_merge([
'message' => $message,
], $payload),
];
......@@ -50,15 +53,13 @@ class GCMHandler extends PushHandler {
/**
* build and send Notification from raw payload
*
* @param array $payload
* @param array $devices
*
* @throws \RuntimeException
*
* @return bool
* @throws RuntimeException
*/
public function sendRaw(array $payload, array $devices): bool {
public function sendRaw(array $payload, array $devices): bool
{
if (count($devices) <= 1) {
$payload = array_merge([
'to' => current($devices),
......@@ -101,7 +102,7 @@ class GCMHandler extends PushHandler {
if ($result === false) {
$error = curl_error($curl);
curl_close($curl);
throw new \RuntimeException('error processing GCM: ' . $error, 500);
throw new RuntimeException('error processing GCM: ' . $error, 500);
}
// remeber to close the connection when finished
......@@ -109,6 +110,6 @@ class GCMHandler extends PushHandler {
// decode response and check if sending to all devices succeeded
$result = @json_decode($result, true);
return isset($result['success']) && (int) $result['success'] === count($devices);
return isset($result['success']) && (int)$result['success'] === count($devices);
}
}
......@@ -2,33 +2,36 @@
/**
* @author Richard Weinhold
*/
namespace ricwein\PushNotification\Handler;
use ricwein\PushNotification\PushHandler;
use RuntimeException;
use UnexpectedValueException;
/**
* PushHandler for Windows push Notification Services
*/
class WNSHandler extends PushHandler {
class WNSHandler extends PushHandler
{
/**
* @var array
*/
protected $_server = [
'token' => '',
'url' => '',
'token' => '',
'url' => '',
'auth-url' => 'https://login.live.com/accesstoken.srf',
];
/**
* @param int $clientID
* @param int $clientID
* @param string $clientSecret
*
* @throws \Exception
*
* @return string
* @throws RuntimeException
*/
public function requestOAuthToken(int $clientID, string $clientSecret): string {
public function requestOAuthToken(int $clientID, string $clientSecret): string
{
// init http-headers
$headers = [
'Content-Type: application/x-www-form-urlencoded',
......@@ -58,93 +61,67 @@ class WNSHandler extends PushHandler {
curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
// send request
$result = curl_exec($curl);
$response = curl_exec($curl);
if ($result === false) {
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new \Exception('error processing WPN: ' . $error, 500);
throw new RuntimeException('error processing WPN: ' . $error, 500);
}
$result = json_decode($result);
$result = json_decode($response, true);
// remeber to close the connection when finished
curl_close($curl);
// handle errors
if (isset($output->error)) {
throw new \Exception($output->error_description, 500);
} elseif (!isset($result->access_token)) {
throw new \Exception('access_token not found', 500);
if (isset($result['error'], $result['error_description'])) {
throw new RuntimeException("[{$result['error']}] - {$result['error_description']}", 500);
}
if (!isset($result['access_token'])) {
throw new RuntimeException('access_token not found', 500);
}
return $result->access_token;
return $result['access_token'];
}
/**
* @param string $message
* @param array $data
*
* @param array $data
* @return string
*/
protected static function _buildPayloadXML(string $message, array $data = []): string {
protected static function _buildPayloadXML(string $message, array $data = []): string
{
$message = trim(stripslashes($message));
if (isset($data['title']) && !isset($data['image'])) {
return '<?xml version="1.0" encoding="utf-8"?>' .
'<toast>' .
'<visual>' .
'<binding template="ToastText02">' .
'<text id="1">' . $message . '</text>' .
'<text id="2">' . $data['title'] . '</text>' .
'</binding>' .
'</visual>' .
'</toast>';
} elseif (isset($data['title'])) {
return '<?xml version="1.0" encoding="utf-8"?>' .
'<toast>' .
'<visual>' .
'<binding template="ToastImageAndText02">' .
'<image id="1" src="' . $data['image'] . '" alt="' . $data['image'] . '"/>' .
'<text id="1">' . $message . '</text>' .
'<text id="2">' . $data['title'] . '</text>' .
'</binding>' .
'</visual>' .
'</toast>';
} elseif (isset($data['image'])) {
return '<?xml version="1.0" encoding="utf-8"?>' .
'<toast>' .
'<visual>' .
'<binding template="ToastImageAndText01">' .
'<image id="1" src="' . $data['image'] . '" alt="' . $data['image'] . '"/>' .
'<text id="1">' . $message . '</text>' .
'</binding>' .
'</visual>' .
'</toast>';
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><toast><visual><binding template=\"ToastText02\"><text id=\"1\">{$message}</text><text id=\"2\">{$data['title']}</text></binding></visual></toast>";
}
if (isset($data['title'])) {
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><toast><visual><binding template=\"ToastImageAndText02\"><image id=\"1\" src=\"{$data['image']}\" alt=\"{$data['image']}\"/><text id=\"1\">{$message}</text><text id=\"2\">{$data['title']}</text></binding></visual></toast>";
}
return '<?xml version="1.0" encoding="utf-8"?>' .
'<toast>' .
'<visual>' .
'<binding template="ToastText01">' .
'<text id="1">' . $message . '</text>' .
'</binding>' .
'</visual>' .
'</toast>';
if (isset($data['image'])) {
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><toast><visual><binding template=\"ToastImageAndText01\"><image id=\"1\" src=\"{$data['image']}\" alt=\"{$data['image']}\"/><text id=\"1\">{$message}</text></binding></visual></toast>";
}
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><toast><visual><binding template=\"ToastText01\"><text id=\"1\">{$message}</text></binding></visual></toast>";
}
/**
* send notification to Microsofts live.com WNS servers
*
* @param string $message
* send notification to Microsoft live.com WNS servers
* @param string $message
* @param string|null $title
* @param array $payload
* @param array $devices
*
* @throws \UnexpectedValueException|\RuntimeException
*
* @param array $payload
* @param array $devices
* @return bool
* @throws UnexpectedValueException