diff --git a/.travis.yml b/.travis.yml index cca78b8..f248c95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,16 @@ language: php + +before_install: + # If PHP >= 5.6, download & install PHPunit 5.7 to avoid builds failures + - if php -r "exit( (int)! version_compare( '$TRAVIS_PHP_VERSION', '5.6', '>=' ) );"; then wget -O phpunit https://phar.phpunit.de/phpunit-5.7.phar && chmod +x phpunit && mkdir ~/bin && mv -v phpunit ~/bin; fi + php: - 5.3 - 5.4 - 5.5 - 5.6 + - 7.0 + - 7.1 env: - SYMFONY_VERSION=origin/master diff --git a/DependencyInjection/Compiler/AddHandlerPass.php b/DependencyInjection/Compiler/AddHandlerPass.php index 57294fd..3151328 100644 --- a/DependencyInjection/Compiler/AddHandlerPass.php +++ b/DependencyInjection/Compiler/AddHandlerPass.php @@ -38,7 +38,7 @@ public function process(ContainerBuilder $container) } $refClass = new \ReflectionClass($class); - } catch (\ReflectionClass $ref) { + } catch (\ReflectionException $ref) { // Class not found or other reflection error throw new \RuntimeException(sprintf( 'Can\'t compile notification handler by service id "%s".', diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index ce1022e..9d8f30f 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -65,6 +65,7 @@ protected function addAndroid() children()-> scalarNode("api_key")->isRequired()->cannotBeEmpty()->end()-> booleanNode("use_multi_curl")->defaultValue(true)->end()-> + booleanNode("dry_run")->defaultFalse()->end()-> end()-> end()-> end()-> diff --git a/DependencyInjection/RMSPushNotificationsExtension.php b/DependencyInjection/RMSPushNotificationsExtension.php index c00f476..a0c4745 100644 --- a/DependencyInjection/RMSPushNotificationsExtension.php +++ b/DependencyInjection/RMSPushNotificationsExtension.php @@ -100,6 +100,7 @@ protected function setAndroidConfig(array $config) if (isset($config["android"]["gcm"])) { $this->container->setParameter("rms_push_notifications.android.gcm.api_key", $config["android"]["gcm"]["api_key"]); $this->container->setParameter("rms_push_notifications.android.gcm.use_multi_curl", $config["android"]["gcm"]["use_multi_curl"]); + $this->container->setParameter('rms_push_notifications.android.gcm.dry_run', $config["android"]["gcm"]["dry_run"]); } } diff --git a/Message/AppleMessage.php b/Message/AppleMessage.php index 00e329b..62e2866 100644 --- a/Message/AppleMessage.php +++ b/Message/AppleMessage.php @@ -210,6 +210,28 @@ public function setAPSContentAvailable($contentAvailable) $this->apsBody["aps"]["content-available"] = $contentAvailable; } + /** + * iOS-specific + * Sets the APS category + * + * @param string $category The notification category + */ + public function setCategory($category) + { + $this->apsBody["aps"]["category"] = $category; + } + + /** + * iOS-specific + * Sets the APS mutable-content attribute + * + * @param bool $mutableContent + */ + public function setMutableContent($mutableContent) + { + $this->apsBody["aps"]["mutable-content"] = $mutableContent ? 1 : 0; + } + /** * Set expiry of message * diff --git a/README.md b/README.md index ceca8ad..e3cc8af 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ only be available if you provide configuration respectively for them. gcm: api_key: # This is titled "Server Key" when creating it use_multi_curl: # default is true + dry_run: ios: timeout: 60 # Seconds to wait for connection timeout, default is 60 sandbox: @@ -111,6 +112,10 @@ Here, `$uuids` contains an array of [Feedback](https://github.com/richsage/RMSPu Apple recommend you poll this service daily. +## Windows Phone - Toast support + +The bundle has beta support for Windows Phone, and supports the Toast notification. Use the `WindowsphoneMessage` message class to send accordingly. + # Thanks Firstly, thanks to all contributors to this bundle! diff --git a/Resources/config/android.xml b/Resources/config/android.xml index 03d5849..5802e00 100644 --- a/Resources/config/android.xml +++ b/Resources/config/android.xml @@ -24,6 +24,9 @@ %rms_push_notifications.android.gcm.api_key% %rms_push_notifications.android.gcm.use_multi_curl% %rms_push_notifications.android.timeout% + + null + %rms_push_notifications.android.gcm.dry_run% diff --git a/Resources/config/blackberry.xml b/Resources/config/blackberry.xml index 374a04e..eb9b5a0 100644 --- a/Resources/config/blackberry.xml +++ b/Resources/config/blackberry.xml @@ -15,6 +15,7 @@ %rms_push_notifications.blackberry.app_id% %rms_push_notifications.blackberry.password% %rms_push_notifications.blackberry.timeout% + diff --git a/Resources/config/ios.xml b/Resources/config/ios.xml index 8ca6789..3ca7bec 100644 --- a/Resources/config/ios.xml +++ b/Resources/config/ios.xml @@ -18,6 +18,7 @@ %rms_push_notifications.ios.timeout% %kernel.cache_dir% + diff --git a/Resources/config/mac.xml b/Resources/config/mac.xml index 20322c5..e4e4c0e 100644 --- a/Resources/config/mac.xml +++ b/Resources/config/mac.xml @@ -16,6 +16,9 @@ %rms_push_notifications.mac.passphrase% %rms_push_notifications.mac.json_unescaped_unicode% %rms_push_notifications.mac.timeout% + %kernel.cache_dir% + + diff --git a/Resources/config/windowsphone.xml b/Resources/config/windowsphone.xml index 8659821..6703a17 100644 --- a/Resources/config/windowsphone.xml +++ b/Resources/config/windowsphone.xml @@ -12,6 +12,7 @@ %rms_push_notifications.windowsphone.timeout% + diff --git a/Service/EventListener.php b/Service/EventListener.php index f745bab..bfe8ba2 100644 --- a/Service/EventListener.php +++ b/Service/EventListener.php @@ -8,7 +8,7 @@ class EventListener { /** * @var EventListenerInterface[] */ - protected $listeners = []; + protected $listeners = array(); /** * @param EventListenerInterface $listener @@ -25,4 +25,4 @@ public function onKernelTerminate () { $listener->onKernelTerminate(); } } -} \ No newline at end of file +} diff --git a/Service/OS/AndroidGCMNotification.php b/Service/OS/AndroidGCMNotification.php index 9fade96..69a1c1a 100644 --- a/Service/OS/AndroidGCMNotification.php +++ b/Service/OS/AndroidGCMNotification.php @@ -2,6 +2,7 @@ namespace RMS\PushNotificationsBundle\Service\OS; +use Psr\Log\LoggerInterface; use RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException, RMS\PushNotificationsBundle\Message\AndroidMessage, RMS\PushNotificationsBundle\Message\MessageInterface; @@ -12,6 +13,14 @@ class AndroidGCMNotification implements OSNotificationServiceInterface { + + /** + * Whether or not to use the dry run GCM + * + * @var bool + */ + protected $useDryRun = false; + /** * GCM endpoint * @@ -47,16 +56,26 @@ class AndroidGCMNotification implements OSNotificationServiceInterface */ protected $responses; + /** + * Monolog logger + * + * @var LoggerInterface + */ + protected $logger; + /** * Constructor * - * @param $apiKey + * @param string $apiKey * @param bool $useMultiCurl * @param int $timeout - * @param AbstractCurl $client (optional) + * @param LoggerInterface $logger + * @param AbstractCurl $client (optional) + * @param bool $dryRun */ - public function __construct($apiKey, $useMultiCurl, $timeout, AbstractCurl $client = null) + public function __construct($apiKey, $useMultiCurl, $timeout, $logger, AbstractCurl $client = null, $dryRun = false) { + $this->useDryRun = $dryRun; $this->apiKey = $apiKey; if (!$client) { $client = ($useMultiCurl ? new MultiCurl() : new Curl()); @@ -65,6 +84,7 @@ public function __construct($apiKey, $useMultiCurl, $timeout, AbstractCurl $clie $this->browser = new Browser($client); $this->browser->getClient()->setVerifyPeer(false); + $this->logger = $logger; } /** @@ -92,14 +112,25 @@ public function send(MessageInterface $message) array("data" => $message->getData()) ); - // Chunk number of registration IDs according to the maximum allowed by GCM - $chunks = array_chunk($message->getGCMIdentifiers(), $this->registrationIdMaxCount); + if ($this->useDryRun) { + $data['dry_run'] = true; + } // Perform the calls (in parallel) $this->responses = array(); - foreach ($chunks as $registrationIDs) { - $data["registration_ids"] = $registrationIDs; + $gcmIdentifiers = $message->getGCMIdentifiers(); + + if (count($message->getGCMIdentifiers()) == 1) { + $data['to'] = $gcmIdentifiers[0]; $this->responses[] = $this->browser->post($this->apiURL, $headers, json_encode($data)); + } else { + // Chunk number of registration IDs according to the maximum allowed by GCM + $chunks = array_chunk($message->getGCMIdentifiers(), $this->registrationIdMaxCount); + + foreach ($chunks as $registrationIDs) { + $data['registration_ids'] = $registrationIDs; + $this->responses[] = $this->browser->post($this->apiURL, $headers, json_encode($data)); + } } // If we're using multiple concurrent connections via MultiCurl @@ -112,6 +143,15 @@ public function send(MessageInterface $message) foreach ($this->responses as $response) { $message = json_decode($response->getContent()); if ($message === null || $message->success == 0 || $message->failure > 0) { + if ($message == null) { + $this->logger->error($response->getContent()); + } else { + foreach ($message->results as $result) { + if (isset($result->error)) { + $this->logger->error($result->error); + } + } + } return false; } } diff --git a/Service/OS/AppleNotification.php b/Service/OS/AppleNotification.php index e50b883..d19aa14 100644 --- a/Service/OS/AppleNotification.php +++ b/Service/OS/AppleNotification.php @@ -2,6 +2,7 @@ namespace RMS\PushNotificationsBundle\Service\OS; +use Psr\Log\LoggerInterface; use RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException, RMS\PushNotificationsBundle\Message\AppleMessage, RMS\PushNotificationsBundle\Message\MessageInterface, @@ -96,23 +97,36 @@ class AppleNotification implements OSNotificationServiceInterface, EventListener */ protected $cachedir; + /** + * Monolog logger + * + * @var LoggerInterface + */ + protected $logger; + /** * Cache pem filename */ const APNS_CERTIFICATE_FILE = '/rms_push_notifications/apns.pem'; + /** + * Status code retrieve when APNS server closed the connection + */ + const APNS_SHUTDOWN_CODE = 10; + /** * Constructor * - * @param $sandbox - * @param $pem - * @param string $passphrase - * @param bool $jsonUnescapedUnicode - * @param int $timeout - * @param string $cachedir + * @param bool $sandbox + * @param string $pem + * @param string $passphrase + * @param bool $jsonUnescapedUnicode + * @param int $timeout + * @param string $cachedir * @param EventListener $eventListener + * @param LoggerInterface $logger */ - public function __construct($sandbox, $pem, $passphrase = "", $jsonUnescapedUnicode = FALSE, $timeout = 60, $cachedir = "", EventListener $eventListener = null) + public function __construct($sandbox, $pem, $passphrase = "", $jsonUnescapedUnicode = FALSE, $timeout = 60, $cachedir = "", EventListener $eventListener = null, $logger = null) { $this->useSandbox = $sandbox; $this->pemPath = $pem; @@ -123,9 +137,11 @@ public function __construct($sandbox, $pem, $passphrase = "", $jsonUnescapedUnic $this->jsonUnescapedUnicode = $jsonUnescapedUnicode; $this->timeout = $timeout; $this->cachedir = $cachedir; + $this->logger = $logger; - if ($eventListener != null) + if ($eventListener != null) { $eventListener->addListener($this); + } } /** @@ -193,18 +209,26 @@ protected function sendMessages($firstMessageId, $apnURL) { $errors = array(); // Loop through all messages starting from the given ID - for ($currentMessageId = $firstMessageId; $currentMessageId < count($this->messages); $currentMessageId++) { + $messagesCount = count($this->messages); + for ($currentMessageId = $firstMessageId; $currentMessageId < $messagesCount; $currentMessageId++) { // Send the message $result = $this->writeApnStream($apnURL, $this->messages[$currentMessageId]); - $errors = array(); - // Check if there is an error result if (is_array($result)) { + + // Close the apn stream in case of Shutdown status code. + if ($result['status'] === self::APNS_SHUTDOWN_CODE) { + $this->closeApnStream($apnURL); + } + $this->responses[] = $result; // Resend all messages that were sent after the failed message $this->sendMessages($result['identifier']+1, $apnURL); $errors[] = $result; + if ($this->logger) { + $this->logger->error(json_encode($result)); + } } else { $this->responses[] = true; } @@ -396,26 +420,47 @@ public function getResponses() * @param $passphrase */ public function setPemAsString($pemContent, $passphrase) { + if ($this->pemContent === $pemContent && $this->pemContentPassphrase === $passphrase) { + return; + } + $this->pemContent = $pemContent; $this->pemContentPassphrase = $passphrase; + + // for new pem will take affect we need to close existing streams which use cached pem + $this->closeStreams(); } /** * Called on kernel terminate */ - public function onKernelTerminate() { + public function onKernelTerminate() + { + $this->removeCachedPemFile(); + $this->closeStreams(); + } - // Remove cache pem file + /** + * Remove cache pem file + */ + private function removeCachedPemFile() + { $fs = new Filesystem(); $filename = $this->cachedir . self::APNS_CERTIFICATE_FILE; if ($fs->exists(dirname($filename))) { $fs->remove(dirname($filename)); } + } - // Close streams + /** + * Close existing streams + */ + private function closeStreams() + { foreach ($this->apnStreams as $stream) { fclose($stream); } + $this->apnStreams = array(); } } diff --git a/Service/OS/BlackberryNotification.php b/Service/OS/BlackberryNotification.php index a80aa63..acc4b4c 100644 --- a/Service/OS/BlackberryNotification.php +++ b/Service/OS/BlackberryNotification.php @@ -2,6 +2,7 @@ namespace RMS\PushNotificationsBundle\Service\OS; +use Psr\Log\LoggerInterface; use RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException, RMS\PushNotificationsBundle\Message\BlackberryMessage, RMS\PushNotificationsBundle\Message\MessageInterface; @@ -39,6 +40,13 @@ class BlackberryNotification implements OSNotificationServiceInterface */ protected $timeout; + /** + * Monolog logger + * + * @var LoggerInterface + */ + protected $logger; + /** * Constructor * @@ -46,13 +54,15 @@ class BlackberryNotification implements OSNotificationServiceInterface * @param $appID * @param $password * @param $timeout + * @param $logger */ - public function __construct($evaluation, $appID, $password, $timeout) + public function __construct($evaluation, $appID, $password, $timeout, $logger) { $this->evaluation = $evaluation; $this->appID = $appID; $this->password = $password; $this->timeout = $timeout; + $this->logger = $logger; } /** @@ -145,9 +155,13 @@ protected function parseResponse(\Buzz\Message\Response $response) $doc->loadXML($response->getContent()); $elems = $doc->getElementsByTagName("response-result"); if (!$elems->length) { + $this->logger->error('Response is empty'); return false; } $responseElement = $elems->item(0); + if ($responseElement->getAttribute("code") != "1001") { + $this->logger->error($responseElement->getAttribute("code"). ' : '. $responseElement->getAttribute("desc")); + } return ($responseElement->getAttribute("code") == "1001"); } diff --git a/Service/OS/MicrosoftNotification.php b/Service/OS/MicrosoftNotification.php index 35db37c..204b1f0 100644 --- a/Service/OS/MicrosoftNotification.php +++ b/Service/OS/MicrosoftNotification.php @@ -2,6 +2,7 @@ namespace RMS\PushNotificationsBundle\Service\OS; +use Psr\Log\LoggerInterface; use RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException; use RMS\PushNotificationsBundle\Message\WindowsphoneMessage; use RMS\PushNotificationsBundle\Message\MessageInterface; @@ -17,14 +18,23 @@ class MicrosoftNotification implements OSNotificationServiceInterface */ protected $browser; + /** + * Monolog logger + * + * @var LoggerInterface + */ + protected $logger; + /** * @param $timeout + * @param $logger */ - public function __construct($timeout) + public function __construct($timeout, $logger) { $this->browser = new Browser(new Curl()); $this->browser->getClient()->setVerifyPeer(false); $this->browser->getClient()->setTimeout($timeout); + $this->logger = $logger; } public function send(MessageInterface $message) @@ -51,6 +61,10 @@ public function send(MessageInterface $message) $response = $this->browser->post($message->getDeviceIdentifier(), $headers, $xml->asXML()); + if (!$response->isSuccessful()) { + $this->logger->error($response->getStatusCode(). ' : '. $response->getReasonPhrase()); + } + return $response->isSuccessful(); } } diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index 10ef8bd..b6e64aa 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -111,12 +111,18 @@ public function testGCMIsOK() "gcm" => array( "api_key" => "foo", "use_multi_curl" => true, + "dry_run" => false, ) ) ), ); $config = $this->process($arr); $this->assertEquals("foo", $config["android"]["gcm"]["api_key"]); + $this->assertFalse($config["android"]["gcm"]["dry_run"]); + + $arr[0]["android"]["gcm"]["dry_run"] = true; + $config = $this->process($arr); + $this->assertTrue($config["android"]["gcm"]["dry_run"]); } /** diff --git a/Tests/Message/iOSMessageTest.php b/Tests/Message/iOSMessageTest.php index 7c3b5e6..49226d3 100644 --- a/Tests/Message/iOSMessageTest.php +++ b/Tests/Message/iOSMessageTest.php @@ -70,4 +70,17 @@ public function testCustomDataAddedOK() $msg->setData(array("custom" => array("foo" => "bar"))); $this->assertEquals($expected, $msg->getMessageBody()); } + + public function testMutableContentAddOk() + { + $expected = array( + "aps" => array( + "mutable-content" => 1, + ), + ); + $msg = new iOSMessage(); + $msg->setMutableContent(true); + $this->assertEquals($expected, $msg->getMessageBody()); + } + } diff --git a/composer.json b/composer.json index d061b4d..2dfd3fa 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,14 @@ ], "require": { "php": ">=5.3.0", - "symfony/symfony": "2.*", - "kriswallsmith/buzz": "*" + "kriswallsmith/buzz": "*", + "psr/log": "^1.0" + }, + "require-dev": { + "symfony/symfony": "^2.0 || ^3.0" + }, + "suggest": { + "symfony/symfony": "To use as a bundle" }, "autoload": { "psr-0": { "RMS\\PushNotificationsBundle": "" } @@ -23,7 +29,7 @@ "target-dir": "RMS/PushNotificationsBundle", "extra": { "branch-alias": { - "dev-master": "0.1.x-dev" + "dev-master": "0.2.x-dev" } } }