Repository: richsage/RMSPushNotificationsBundle Branch: master Commit: 3432bca679f9 Files: 46 Total size: 105.3 KB Directory structure: gitextract_uz_kv258/ ├── .gitignore ├── .travis.yml ├── Command/ │ └── TestPushCommand.php ├── DependencyInjection/ │ ├── Compiler/ │ │ └── AddHandlerPass.php │ ├── Configuration.php │ └── RMSPushNotificationsExtension.php ├── Device/ │ ├── Types.php │ └── iOS/ │ └── Feedback.php ├── Exception/ │ └── InvalidMessageTypeException.php ├── LICENSE ├── Message/ │ ├── AndroidMessage.php │ ├── AppleMessage.php │ ├── BlackberryMessage.php │ ├── MacMessage.php │ ├── MessageInterface.php │ ├── WindowsphoneMessage.php │ └── iOSMessage.php ├── README.md ├── RMSPushNotificationsBundle.php ├── Resources/ │ └── config/ │ ├── android.xml │ ├── blackberry.xml │ ├── ios.xml │ ├── mac.xml │ ├── services.xml │ └── windowsphone.xml ├── Service/ │ ├── EventListener.php │ ├── EventListenerInterface.php │ ├── Notifications.php │ ├── OS/ │ │ ├── AndroidGCMNotification.php │ │ ├── AndroidNotification.php │ │ ├── AppleNotification.php │ │ ├── BlackberryNotification.php │ │ ├── MicrosoftNotification.php │ │ └── OSNotificationServiceInterface.php │ └── iOSFeedback.php ├── Tests/ │ ├── DependencyInjection/ │ │ └── ConfigurationTest.php │ ├── Message/ │ │ ├── AndroidMessageTest.php │ │ ├── BlackberryMessageTest.php │ │ ├── MacMessageTest.php │ │ ├── WindowsphoneMessageTest.php │ │ └── iOSMessageTest.php │ ├── autoload.php.dist │ └── bootstrap.php ├── composer.json ├── phpunit.travis.xml └── phpunit.xml.dist ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ vendor ================================================ FILE: .travis.yml ================================================ 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 before_script: - wget http://getcomposer.org/composer.phar - php composer.phar install script: phpunit --configuration phpunit.travis.xml ================================================ FILE: Command/TestPushCommand.php ================================================ setName("rms:test-push") ->setDescription("Sends a push command to a supplied push token'd device") ->addOption("badge", "b", InputOption::VALUE_OPTIONAL, "Badge number (for iOS devices)", 0) ->addOption("text", "t", InputOption::VALUE_OPTIONAL, "Text message") ->addArgument("service", InputArgument::REQUIRED, "One of 'ios', 'c2dm', 'gcm', 'mac', 'blackberry' or 'windowsphone'") ->addArgument("token", InputArgument::REQUIRED, "Authentication token for the service") ->addArgument("payload", InputArgument::OPTIONAL, "The payload data to send (JSON)", '{"data": "test"}') ; } /** * Main command execution. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * @return void */ protected function execute(InputInterface $input, OutputInterface $output) { $token = $input->getArgument("token"); $service = strtolower($input->getArgument("service")); $json_payload = $input->getArgument("payload"); $payload = json_decode($json_payload, true); $tokenLengths = array( "ios" => 64, "c2dm" => 162, ); if (isset($tokenLengths[$service]) && strlen($token) != $tokenLengths[$service]) { $output->writeln("Token should be " . $tokenLengths[$service] . "chars long, not " . strlen($token) . ""); return; } if ($payload == null) { throw new \InvalidArgumentException("Invalid JSON payload " . $json_payload); } $msg = $this->getMessageClass($service); if (method_exists($msg, "setAPSBadge")) { // Set badge on iOS $msg->setAPSBadge((int) $input->getOption("badge")); } if (method_exists($msg, "setAPSSound")) { // Set sound on iOS $msg->setAPSSound("default"); } $msg->setDeviceIdentifier($token); $msg->setData($payload); if ($input->getOption("text")) { $msg->setMessage($input->getOption("text")); } $result = $this->getContainer()->get("rms_push_notifications")->send($msg); if ($result) { $output->writeln("Send successful"); } else { $output->writeln("Send failed"); } $output->writeln("done"); } /** * Returns a message class based on the supplied os * * @param string $service The name of the service to return a message for * @throws \InvalidArgumentException * @return MessageInterface */ protected function getMessageClass($service) { switch ($service) { case "ios": return new PushMessage\iOSMessage(); case "c2dm": return new PushMessage\AndroidMessage(); case "gcm": $message = new PushMessage\AndroidMessage(); $message->setGCM(true); return $message; case "blackberry": return new PushMessage\BlackberryMessage(); case "mac": return new PushMessage\MacMessage(); case "windowsphone": return new PushMessage\WindowsphoneMessage(); default: throw new \InvalidArgumentException("Service '{$service}' not supported presently"); } } } ================================================ FILE: DependencyInjection/Compiler/AddHandlerPass.php ================================================ getDefinition("rms_push_notifications"); foreach ($container->findTaggedServiceIds("rms_push_notifications.handler") as $id => $attributes) { if (!isset($attributes[0]["osType"])) { throw new \LogicException("Handler {$id} requires an osType attribute"); } $definition = $container->getDefinition($id); // Get reflection class for validate handler try { $class = $definition->getClass(); // Class is parameter if (strpos($class, '%') === 0) { $class = $container->getParameter(trim($class, '%')); } $refClass = new \ReflectionClass($class); } catch (\ReflectionException $ref) { // Class not found or other reflection error throw new \RuntimeException(sprintf( 'Can\'t compile notification handler by service id "%s".', $id ), 0, $ref); } catch (ParameterNotFoundException $paramNotFound) { // Parameter not found in service container throw new \RuntimeException(sprintf( 'Can\'t compile notification handler by service id "%s".', $id ), 0, $paramNotFound); } // Required interface $requiredInterface = 'RMS\\PushNotificationsBundle\\Service\\OS\\OSNotificationServiceInterface'; if (!$refClass->implementsInterface($requiredInterface)) { throw new \UnexpectedValueException(sprintf( 'Notification service "%s" by id "%s" must be implements "%s" interface!' , $refClass->getName(), $id, $requiredInterface )); } // Add handler to service notifications storage $service->addMethodCall("addHandler", array($attributes[0]["osType"], new Reference($id))); } } } ================================================ FILE: DependencyInjection/Configuration.php ================================================ root = $treeBuilder->root("rms_push_notifications"); $this->addAndroid(); $this->addiOS(); $this->addMac(); $this->addBlackberry(); $this->addWindowsphone(); return $treeBuilder; } /** * Android configuration */ protected function addAndroid() { $this->root-> children()-> arrayNode("android")-> canBeUnset()-> children()-> scalarNode("timeout")->defaultValue(5)->end()-> // WARNING: These 3 fields as they are, outside of the c2dm array // are deprecrated in favour of using the c2dm array configuration // At present these will be overriden by anything supplied // in the c2dm array scalarNode("username")->defaultValue("")->end()-> scalarNode("password")->defaultValue("")->end()-> scalarNode("source")->defaultValue("")->end()-> arrayNode("c2dm")-> canBeUnset()-> children()-> scalarNode("username")->isRequired()->end()-> scalarNode("password")->isRequired()->end()-> scalarNode("source")->defaultValue("")->end()-> end()-> end()-> arrayNode("gcm")-> canBeUnset()-> children()-> scalarNode("api_key")->isRequired()->cannotBeEmpty()->end()-> booleanNode("use_multi_curl")->defaultValue(true)->end()-> booleanNode("dry_run")->defaultFalse()->end()-> end()-> end()-> end()-> end()-> end() ; } /** * iOS configuration */ protected function addiOS() { $this->addApple("ios"); } /** * Mac configuration */ protected function addMac() { $this->addApple("mac"); } /** * Generic Apple Configuration */ private function addApple($os) { $config = $this->root-> children()-> arrayNode($os)-> children()-> scalarNode("timeout")->defaultValue(60)->end()-> booleanNode("sandbox")->defaultFalse()->end()-> scalarNode("pem")->cannotBeEmpty()->end()-> scalarNode("passphrase")->defaultValue("")->end()-> scalarNode('json_unescaped_unicode')->defaultFalse(); if (method_exists($config,'info')) { $config = $config->info('PHP >= 5.4.0 and each messaged must be UTF-8 encoding'); } $config->end()-> end()-> end()-> end() ; } /** * Blackberry configuration */ protected function addBlackberry() { $this->root-> children()-> arrayNode("blackberry")-> children()-> scalarNode("timeout")->defaultValue(5)->end()-> booleanNode("evaluation")->defaultFalse()->end()-> scalarNode("app_id")->isRequired()->cannotBeEmpty()->end()-> scalarNode("password")->isRequired()->cannotBeEmpty()->end()-> end()-> end()-> end() ; } /** * Windows Phone configuration */ protected function addWindowsphone() { $this->root-> children()-> arrayNode('windowsphone')-> children()-> scalarNode("timeout")->defaultValue(5)->end()-> end()-> end()-> end() ; } } ================================================ FILE: DependencyInjection/RMSPushNotificationsExtension.php ================================================ container = $container; $this->kernelRootDir = $container->getParameterBag()->get("kernel.root_dir"); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $this->setInitialParams(); if (isset($config["android"])) { $this->setAndroidConfig($config); $loader->load('android.xml'); } if (isset($config["ios"])) { $this->setiOSConfig($config); $loader->load('ios.xml'); } if (isset($config["mac"])) { $this->setMacConfig($config); $loader->load('mac.xml'); } if (isset($config["blackberry"])) { $this->setBlackberryConfig($config); $loader->load('blackberry.xml'); } if (isset($config['windowsphone'])) { $this->setWindowsphoneConfig($config); $loader->load('windowsphone.xml'); } } /** * Initial enabling */ protected function setInitialParams() { $this->container->setParameter("rms_push_notifications.android.enabled", false); $this->container->setParameter("rms_push_notifications.ios.enabled", false); $this->container->setParameter("rms_push_notifications.mac.enabled", false); } /** * Sets Android config into container * * @param array $config */ protected function setAndroidConfig(array $config) { $this->container->setParameter("rms_push_notifications.android.enabled", true); $this->container->setParameter("rms_push_notifications.android.c2dm.enabled", true); // C2DM $username = $config["android"]["username"]; $password = $config["android"]["password"]; $source = $config["android"]["source"]; $timeout = $config["android"]["timeout"]; if (isset($config["android"]["c2dm"])) { $username = $config["android"]["c2dm"]["username"]; $password = $config["android"]["c2dm"]["password"]; $source = $config["android"]["c2dm"]["source"]; } $this->container->setParameter("rms_push_notifications.android.timeout", $timeout); $this->container->setParameter("rms_push_notifications.android.c2dm.username", $username); $this->container->setParameter("rms_push_notifications.android.c2dm.password", $password); $this->container->setParameter("rms_push_notifications.android.c2dm.source", $source); // GCM $this->container->setParameter("rms_push_notifications.android.gcm.enabled", isset($config["android"]["gcm"])); 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"]); } } /** * Sets iOS config into container * * @param array $config */ protected function setiOSConfig(array $config) { $this->setAppleConfig($config, "ios"); } /** * Sets Mac config into container * * @param array $config */ protected function setMacConfig(array $config) { $this->setAppleConfig($config, "mac"); } /** * Sets Apple config into container * * @param array $config * @param $os * @throws \RuntimeException * @throws \LogicException */ protected function setAppleConfig(array $config, $os) { $supportedAppleOS = array("mac", "ios"); //Check if the OS is supported if (!in_array($os, $supportedAppleOS, true)) { throw new \RuntimeException(sprintf('This Apple OS "%s" is not supported', $os)); } $pemFile = null; if (isset($config[$os]["pem"])) { // If PEM is set, it must be a real file if (realpath($config[$os]["pem"])) { // Absolute path $pemFile = $config[$os]["pem"]; } elseif (realpath($this->kernelRootDir.DIRECTORY_SEPARATOR.$config[$os]["pem"]) ) { // Relative path $pemFile = $this->kernelRootDir.DIRECTORY_SEPARATOR.$config[$os]["pem"]; } else { // path isn't valid throw new \RuntimeException(sprintf('Pem file "%s" not found.', $config[$os]["pem"])); } } if ($config[$os]['json_unescaped_unicode']) { // Not support JSON_UNESCAPED_UNICODE option if (!version_compare(PHP_VERSION, '5.4.0', '>=')) { throw new \LogicException(sprintf( 'Can\'t use JSON_UNESCAPED_UNICODE option. This option can use only PHP Version >= 5.4.0. Your version: %s', PHP_VERSION )); } } $this->container->setParameter(sprintf('rms_push_notifications.%s.enabled', $os), true); $this->container->setParameter(sprintf('rms_push_notifications.%s.timeout', $os), $config[$os]["timeout"]); $this->container->setParameter(sprintf('rms_push_notifications.%s.sandbox', $os), $config[$os]["sandbox"]); $this->container->setParameter(sprintf('rms_push_notifications.%s.pem', $os), $pemFile); $this->container->setParameter(sprintf('rms_push_notifications.%s.passphrase', $os), $config[$os]["passphrase"]); $this->container->setParameter(sprintf('rms_push_notifications.%s.json_unescaped_unicode', $os), (bool) $config[$os]['json_unescaped_unicode']); } /** * Sets Blackberry config into container * * @param array $config */ protected function setBlackberryConfig(array $config) { $this->container->setParameter("rms_push_notifications.blackberry.enabled", true); $this->container->setParameter("rms_push_notifications.blackberry.timeout", $config["blackberry"]["timeout"]); $this->container->setParameter("rms_push_notifications.blackberry.evaluation", $config["blackberry"]["evaluation"]); $this->container->setParameter("rms_push_notifications.blackberry.app_id", $config["blackberry"]["app_id"]); $this->container->setParameter("rms_push_notifications.blackberry.password", $config["blackberry"]["password"]); } protected function setWindowsphoneConfig(array $config) { $this->container->setParameter("rms_push_notifications.windowsphone.enabled", true); $this->container->setParameter("rms_push_notifications.windowsphone.timeout", $config["windowsphone"]["timeout"]); } } ================================================ FILE: Device/Types.php ================================================ timestamp = $token["timestamp"]; $this->tokenLength = $token["length"]; $this->uuid = $token["token"]; return $this; } } ================================================ FILE: Exception/InvalidMessageTypeException.php ================================================ message = $message; } /** * Returns the string message * * @return string */ public function getMessage() { return $this->message; } /** * Sets the data. For Android, this is any custom data to use * * @param array $data The custom data to send */ public function setData($data) { $this->data = (is_array($data) ? $data : array($data)); } /** * Returns any custom data * * @return array */ public function getData() { return array_merge(array('message' => $this->getMessage()), $this->data); } /** * Gets the message body to send * This is primarily used in C2DM * * @return array */ public function getMessageBody() { $data = array( "registration_id" => $this->identifier, "collapse_key" => $this->collapseKey, "data.message" => $this->message, ); if (!empty($this->data)) { $data = array_merge($data, $this->data); } return $data; } /** * Sets the identifier of the target device, eg UUID or similar * * @param $identifier */ public function setDeviceIdentifier($identifier) { $this->identifier = $identifier; $this->allIdentifiers = array($identifier => $identifier); } /** * Returns the target OS for this message * * @return string */ public function getTargetOS() { return ($this->isGCM ? Types::OS_ANDROID_GCM : Types::OS_ANDROID_C2DM); } /** * Returns the target device identifier * * @return string */ public function getDeviceIdentifier() { return $this->identifier; } /** * Android-specific * Returns the collapse key * * @return int */ public function getCollapseKey() { return $this->collapseKey; } /** * Android-specific * Sets the collapse key * * @param $collapseKey */ public function setCollapseKey($collapseKey) { $this->collapseKey = $collapseKey; } /** * Set whether this is a GCM message * (default false) * * @param $gcm */ public function setGCM($gcm) { $this->isGCM = !!$gcm; } /** * Returns whether this is a GCM message * * @return mixed */ public function isGCM() { return $this->isGCM; } /** * Returns an array of device identifiers * Not used in C2DM * * @return mixed */ public function getGCMIdentifiers() { return array_values($this->allIdentifiers); } /** * Adds a device identifier to the GCM list * @param string $identifier */ public function addGCMIdentifier($identifier) { $this->allIdentifiers[$identifier] = $identifier; } /** * Sets the GCM list * @param array $allIdentifiers */ public function setAllIdentifiers($allIdentifiers) { $this->allIdentifiers = array_combine($allIdentifiers, $allIdentifiers); } /** * Sets GCM options * @param array $options */ public function setGCMOptions($options) { $this->gcmOptions = $options; } /** * Returns GCM options * * @return array */ public function getGCMOptions() { return $this->gcmOptions; } } ================================================ FILE: Message/AppleMessage.php ================================================ apsBody = array( "aps" => array( ), ); if ($identifier !== NULL) { $this->identifier = $identifier; } } /** * Sets the message. For iOS, this is the APS alert message * * @param $message */ public function setMessage($message) { $this->apsBody["aps"]["alert"] = $message; } /** * Sets any custom data for the APS body * * @param array $data */ public function setData($data) { if (!is_array($data)) { throw new \InvalidArgumentException(sprintf('Messages custom data must be array, "%s" given.', gettype($data))); } if (array_key_exists("aps", $data)) { unset($data["aps"]); } foreach ($data as $key => $value) { $this->addCustomData($key, $value); } return $this; } /** * Add custom data * * @param string $key * @param mixed $value */ public function addCustomData($key, $value) { if ($key == 'aps') { throw new \LogicException('Can\'t replace "aps" data. Please call to setMessage, if your want replace message text.'); } if (is_object($value)) { if (interface_exists('JsonSerializable') && !$value instanceof \stdClass && !$value instanceof \JsonSerializable) { throw new \InvalidArgumentException(sprintf( 'Object %s::%s must be implements JsonSerializable interface for next serialize data.', get_class($value), spl_object_hash($value) )); } } $this->customData[$key] = $value; return $this; } /** * Sets the identifier of the target device, eg UUID or similar * * @param $identifier */ public function setDeviceIdentifier($identifier) { $this->identifier = $identifier; } /** * Gets the full message body to send to APN * * @return array */ public function getMessageBody() { $payloadBody = $this->apsBody; if (!empty($this->customData)) { $payloadBody = array_merge($payloadBody, $this->customData); } return $payloadBody; } /** * Returns the device identifier * * @return null|string */ public function getDeviceIdentifier() { return $this->identifier; } /** * Returns the target OS for this message * Must be implemented by subclass * * @return string */ public function getTargetOS() { return ""; } /** * iOS-specific * Sets the APS sound * * @param string $sound The sound to use. Use 'default' to use the built-in default */ public function setAPSSound($sound) { $this->apsBody["aps"]["sound"] = $sound; } /** * iOS-specific * Sets the APS badge count * * @param integer $badge The badge count to display */ public function setAPSBadge($badge) { $this->apsBody["aps"]["badge"] = (int) $badge; } /** * iOS-specific * Sets the APS content available flag, used to transform the notification into remote-notification * and trigger the "didReceiveRemoteNotification: fetchCompletionHandler:" method on iOS apps * * @param string $contentAvailable The flag to set the content-available option, for example set it to 1. */ 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 * * @param int $expiry */ public function setExpiry($expiry) { $this->expiry = $expiry; } /** * Get expiry of message * * @return int */ public function getExpiry() { return $this->expiry; } /** * @param string $pushMagicToken */ public function setPushMagicToken($pushMagicToken) { $this->pushMagicToken = $pushMagicToken; } /** * @return string */ public function getPushMagicToken() { return $this->pushMagicToken; } /** * @return string */ public function getToken() { return $this->token; } /** * @param string $token */ public function setToken($token) { $this->token = $token; } /** * @param null|bool $isMdmMessage * * @return bool|null */ public function isMdmMessage($isMdmMessage = null) { if ($isMdmMessage === null) { return $this->isMdmMessage; } $this->isMdmMessage = (bool) $isMdmMessage; } } ================================================ FILE: Message/BlackberryMessage.php ================================================ setData($message); } /** * Sets the data. For Blackberry, this is any data required * * @param array $data The custom data to send */ public function setData($data) { $this->data = $data; } /** * Gets the message body to send * For Blackberry, this is just our data object * * @return array */ public function getMessageBody() { return $this->data; } /** * Sets the identifier of the target device, eg UUID or similar * * @param $identifier */ public function setDeviceIdentifier($identifier) { $this->identifier = $identifier; } /** * Returns the target OS for this message * * @return string */ public function getTargetOS() { return Types::OS_BLACKBERRY; } /** * Returns the target device identifier * * @return string */ public function getDeviceIdentifier() { return $this->identifier; } } ================================================ FILE: Message/MacMessage.php ================================================ 2 ); protected $identifier; protected $text1 = ''; protected $text2 = ''; protected $target; public function __construct() { $this->target = self::TYPE_TOAST; } public function getTargetOS() { return Types::OS_WINDOWSPHONE; } public function getDeviceIdentifier() { return $this->identifier; } public function setDeviceIdentifier($identifier) { $this->identifier = $identifier; } public function getMessageBody() { return array( 'text1' => $this->text1, 'text2' => $this->text2 ); } public function setMessage($message) { $this->text2 = $message; } public function setData($data) { // Not implemented yet } public function getTarget() { return $this->target; } public function getNotificationClass() { return static::$notificationClass[$this->getTarget()]; } } ================================================ FILE: Message/iOSMessage.php ================================================ password: source: 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: pem: # can be absolute or relative path (from app directory) passphrase: mac: timeout: 60 # Seconds to wait for connection timeout, default is 60 sandbox: pem: passphrase: blackberry: timeout: 5 # Seconds to wait for connection timeout, default is 5 evaluation: app_id: password: windowsphone: timeout: 5 # Seconds to wait for connection timeout, default is 5 NOTE: If you are using Windows, you may need to set the Android GCM `use_multi_curl` flag to false for GCM messages to be sent correctly. Timeout defaults are the defaults from prior to the introduction of this configuration value. ## Usage A little example of how to push your first message to an iOS device, we'll assume that you've set up the configuration correctly: use RMS\PushNotificationsBundle\Message\iOSMessage; class PushDemoController extends Controller { public function pushAction() { $message = new iOSMessage(); $message->setMessage('Oh my! A push notification!'); $message->setDeviceIdentifier('test012fasdf482asdfd63f6d7bc6d4293aedd5fb448fe505eb4asdfef8595a7'); $this->container->get('rms_push_notifications')->send($message); return new Response('Push notification send!'); } } The send method will detect the type of message so if you'll pass it an `AndroidMessage` it will automatically send it through the C2DM/GCM servers, and likewise for Mac and Blackberry. ## Android messages Since both C2DM and GCM are still available, the `AndroidMessage` class has a small flag on it to toggle which service to send it to. Use as follows: use RMS\PushNotificationsBundle\Message\AndroidMessage; $message = new AndroidMessage(); $message->setGCM(true); to send as a GCM message rather than C2DM. ## iOS Feedback service The Apple Push Notification service also exposes a Feedback service where you can get information about failed push notifications - see [here](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3) for further details. This service is available within the bundle. The following code demonstrates how you can retrieve data from the service: $feedbackService = $container->get("rms_push_notifications.ios.feedback"); $uuids = $feedbackService->getDeviceUUIDs(); Here, `$uuids` contains an array of [Feedback](https://github.com/richsage/RMSPushNotificationsBundle/blob/master/Device/iOS/Feedback.php) objects, with timestamp, token length and the device UUID all populated. 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! ![](https://www.jetbrains.com/phpstorm/documentation/docs/logo_phpstorm.png) Secondly, thanks to [JetBrains](http://www.jetbrains.com) for their sponsorship of an open-source [PhpStorm](https://www.jetbrains.com/phpstorm/) licence for this project. ================================================ FILE: RMSPushNotificationsBundle.php ================================================ addCompilerPass(new AddHandlerPass()); } } ================================================ FILE: Resources/config/android.xml ================================================ RMS\PushNotificationsBundle\Service\OS\AndroidNotification RMS\PushNotificationsBundle\Service\OS\AndroidGCMNotification %rms_push_notifications.android.c2dm.username% %rms_push_notifications.android.c2dm.password% %rms_push_notifications.android.c2dm.source% %rms_push_notifications.android.timeout% %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% ================================================ FILE: Resources/config/blackberry.xml ================================================ RMS\PushNotificationsBundle\Service\OS\BlackberryNotification %rms_push_notifications.blackberry.evaluation% %rms_push_notifications.blackberry.app_id% %rms_push_notifications.blackberry.password% %rms_push_notifications.blackberry.timeout% ================================================ FILE: Resources/config/ios.xml ================================================ RMS\PushNotificationsBundle\Service\OS\AppleNotification %rms_push_notifications.ios.sandbox% %rms_push_notifications.ios.pem% %rms_push_notifications.ios.passphrase% %rms_push_notifications.ios.json_unescaped_unicode% %rms_push_notifications.ios.timeout% %kernel.cache_dir% %rms_push_notifications.ios.sandbox% %rms_push_notifications.ios.pem% %rms_push_notifications.ios.passphrase% %rms_push_notifications.ios.timeout% ================================================ FILE: Resources/config/mac.xml ================================================ RMS\PushNotificationsBundle\Service\OS\AppleNotification %rms_push_notifications.mac.sandbox% %rms_push_notifications.mac.pem% %rms_push_notifications.mac.passphrase% %rms_push_notifications.mac.json_unescaped_unicode% %rms_push_notifications.mac.timeout% %kernel.cache_dir% ================================================ FILE: Resources/config/services.xml ================================================ RMS\PushNotificationsBundle\Service\Notifications RMS\PushNotificationsBundle\Service\OS\AndroidNotification RMS\PushNotificationsBundle\Service\OS\AppleNotification RMS\PushNotificationsBundle\Service\iOSFeedback RMS\PushNotificationsBundle\Service\OS\AppleNotification RMS\PushNotificationsBundle\Service\EventListener ================================================ FILE: Resources/config/windowsphone.xml ================================================ RMS\PushNotificationsBundle\Service\OS\MicrosoftNotification %rms_push_notifications.windowsphone.timeout% ================================================ FILE: Service/EventListener.php ================================================ listeners[] = $listener; } /** * Call onKernelTerminate on every listener */ public function onKernelTerminate () { foreach ($this->listeners as $listener) { $listener->onKernelTerminate(); } } } ================================================ FILE: Service/EventListenerInterface.php ================================================ supports($message->getTargetOS())) { throw new \RuntimeException("OS type {$message->getTargetOS()} not supported"); } return $this->handlers[$message->getTargetOS()]->send($message); } /** * Adds a handler * * @param $osType * @param $service */ public function addHandler($osType, $service) { if (!isset($this->handlers[$osType])) { $this->handlers[$osType] = $service; } } /** * Get responses from handler * * @param string $osType * @return array * @throws \RuntimeException */ public function getResponses($osType) { if (!isset($this->handlers[$osType])) { throw new \RuntimeException("OS type {$osType} not supported"); } if (!method_exists($this->handlers[$osType], 'getResponses')) { throw new \RuntimeException("Handler for OS type {$osType} not supported getResponses() method"); } return $this->handlers[$osType]->getResponses(); } /** * Check if target OS is supported * * @param $targetOS * * @return bool */ public function supports($targetOS) { return isset($this->handlers[$targetOS]); } /** * Set Apple Push Notification Service pem as string. * Service won't use pem file passed by config anymore. * * @param $pemContent string * @param $passphrase */ public function setAPNSPemAsString($pemContent, $passphrase) { if (isset($this->handlers[Types::OS_IOS]) && $this->handlers[Types::OS_IOS] instanceof AppleNotification) { /** @var AppleNotification $appleNotification */ $appleNotification = $this->handlers[Types::OS_IOS]; $appleNotification->setPemAsString($pemContent, $passphrase); } } } ================================================ FILE: Service/OS/AndroidGCMNotification.php ================================================ useDryRun = $dryRun; $this->apiKey = $apiKey; if (!$client) { $client = ($useMultiCurl ? new MultiCurl() : new Curl()); } $client->setTimeout($timeout); $this->browser = new Browser($client); $this->browser->getClient()->setVerifyPeer(false); $this->logger = $logger; } /** * Sends the data to the given registration IDs via the GCM server * * @param \RMS\PushNotificationsBundle\Message\MessageInterface $message * @throws \RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException * @return bool */ public function send(MessageInterface $message) { if (!$message instanceof AndroidMessage) { throw new InvalidMessageTypeException(sprintf("Message type '%s' not supported by GCM", get_class($message))); } if (!$message->isGCM()) { throw new InvalidMessageTypeException("Non-GCM messages not supported by the Android GCM sender"); } $headers = array( "Authorization: key=" . $this->apiKey, "Content-Type: application/json", ); $data = array_merge( $message->getGCMOptions(), array("data" => $message->getData()) ); if ($this->useDryRun) { $data['dry_run'] = true; } // Perform the calls (in parallel) $this->responses = array(); $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 // then we should flush all requests if ($this->browser->getClient() instanceof MultiCurl) { $this->browser->getClient()->flush(); } // Determine success 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; } } return true; } /** * Returns responses * * @return array */ public function getResponses() { return $this->responses; } } ================================================ FILE: Service/OS/AndroidNotification.php ================================================ username = $username; $this->password = $password; $this->source = $source; $this->timeout = $timeout; $this->authToken = ""; } /** * Sends a C2DM message * This assumes that a valid auth token can be obtained * * @param \RMS\PushNotificationsBundle\Message\MessageInterface $message * @throws \RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException * @return bool */ public function send(MessageInterface $message) { if (!$message instanceof AndroidMessage) { throw new InvalidMessageTypeException(sprintf("Message type '%s' not supported by C2DM", get_class($message))); } if ($this->getAuthToken()) { $headers[] = "Authorization: GoogleLogin auth=" . $this->authToken; $data = $message->getMessageBody(); $buzz = new Browser(); $buzz->getClient()->setVerifyPeer(false); $buzz->getClient()->setTimeout($this->timeout); $response = $buzz->post("https://android.apis.google.com/c2dm/send", $headers, http_build_query($data)); return preg_match("/^id=/", $response->getContent()) > 0; } return false; } /** * Gets a valid authentication token * * @return bool */ protected function getAuthToken() { $data = array( "Email" => $this->username, "Passwd" => $this->password, "accountType" => "HOSTED_OR_GOOGLE", "source" => $this->source, "service" => "ac2dm" ); $buzz = new Browser(); $buzz->getClient()->setVerifyPeer(false); $buzz->getClient()->setTimeout($this->timeout); $response = $buzz->post("https://www.google.com/accounts/ClientLogin", array(), http_build_query($data)); if ($response->getStatusCode() !== 200) { return false; } preg_match("/Auth=([a-z0-9_\-]+)/i", $response->getContent(), $matches); $this->authToken = $matches[1]; return true; } } ================================================ FILE: Service/OS/AppleNotification.php ================================================ useSandbox = $sandbox; $this->pemPath = $pem; $this->passphrase = $passphrase; $this->apnStreams = array(); $this->messages = array(); $this->lastMessageId = -1; $this->jsonUnescapedUnicode = $jsonUnescapedUnicode; $this->timeout = $timeout; $this->cachedir = $cachedir; $this->logger = $logger; if ($eventListener != null) { $eventListener->addListener($this); } } /** * Set option JSON_UNESCAPED_UNICODE to json encoders * * @param boolean $jsonUnescapedUnicode */ public function setJsonUnescapedUnicode($jsonUnescapedUnicode) { $this->jsonUnescapedUnicode = (bool) $jsonUnescapedUnicode; return $this; } /** * Send a MDM or notification message * * @param \RMS\PushNotificationsBundle\Message\MessageInterface|\RMS\PushNotificationsBundle\Service\OS\MessageInterface $message * @throws \RuntimeException * @throws \RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException * @return bool */ public function send(MessageInterface $message) { if (!$message instanceof AppleMessage) { throw new InvalidMessageTypeException(sprintf("Message type '%s' not supported by APN", get_class($message))); } $apnURL = "ssl://gateway.push.apple.com:2195"; if ($this->useSandbox) { $apnURL = "ssl://gateway.sandbox.push.apple.com:2195"; } $messageId = ++$this->lastMessageId; if ($message->isMdmMessage()) { if ($message->getToken() == '') { throw new InvalidMessageTypeException(sprintf("Message type '%s' is a MDM message but 'token' is missing", get_class($message))); } if ($message->getPushMagicToken() == '') { throw new InvalidMessageTypeException(sprintf("Message type '%s' is a MDM message but 'pushMagicToken' is missing", get_class($message))); } $this->messages[$messageId] = $this->createMdmPayload($message->getToken(), $message->getPushMagicToken()); } else { $this->messages[$messageId] = $this->createPayload($messageId, $message->getExpiry(), $message->getDeviceIdentifier(), $message->getMessageBody()); } $errors = $this->sendMessages($messageId, $apnURL); return !$errors; } /** * Send all notification messages starting from the given ID * * @param int $firstMessageId * @param string $apnURL * @throws \RuntimeException * @throws \RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException * @return int */ protected function sendMessages($firstMessageId, $apnURL) { $errors = array(); // Loop through all messages starting from the given ID $messagesCount = count($this->messages); for ($currentMessageId = $firstMessageId; $currentMessageId < $messagesCount; $currentMessageId++) { // Send the message $result = $this->writeApnStream($apnURL, $this->messages[$currentMessageId]); // 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; } } return $errors; } /** * Write data to the apn stream that is associated with the given apn URL * * @param string $apnURL * @param string $payload * @throws \RuntimeException * @return mixed */ protected function writeApnStream($apnURL, $payload) { // Get the correct Apn stream and send data $fp = $this->getApnStream($apnURL); $response = (strlen($payload) === @fwrite($fp, $payload, strlen($payload))); // Check if there is responsedata to read $readStreams = array($fp); $null = NULL; $streamsReadyToRead = @stream_select($readStreams, $null, $null, 1, 0); if ($streamsReadyToRead > 0) { // Unpack error response data and set as the result $response = @unpack("Ccommand/Cstatus/Nidentifier", fread($fp, 6)); $this->closeApnStream($apnURL); } // Will contain true if writing succeeded and no error is returned yet return $response; } /** * Get an apn stream associated with the given apn URL, create one if necessary * * @param string $apnURL * @throws \RuntimeException * @return resource */ protected function getApnStream($apnURL) { if (!isset($this->apnStreams[$apnURL])) { // No stream found, setup a new stream $ctx = $this->getStreamContext(); $this->apnStreams[$apnURL] = stream_socket_client($apnURL, $err, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $ctx); if (!$this->apnStreams[$apnURL]) { throw new \RuntimeException("Couldn't connect to APN server. Error no $err: $errstr"); } // Reduce buffering and blocking if (function_exists("stream_set_read_buffer")) { stream_set_read_buffer($this->apnStreams[$apnURL], 6); } stream_set_write_buffer($this->apnStreams[$apnURL], 0); stream_set_blocking($this->apnStreams[$apnURL], 0); } return $this->apnStreams[$apnURL]; } /** * Close the apn stream associated with the given apn URL * * @param string $apnURL */ protected function closeApnStream($apnURL) { if (isset($this->apnStreams[$apnURL])) { // Stream found, close the stream fclose($this->apnStreams[$apnURL]); unset($this->apnStreams[$apnURL]); } } /** * Gets a stream context set up for SSL * using our PEM file and passphrase * * @return resource */ protected function getStreamContext() { $pem = $this->pemPath; $passphrase = $this->passphrase; // Create cache pem file if needed if (!empty($this->pemContent)) { $filename = $this->cachedir . self::APNS_CERTIFICATE_FILE; $fs = new Filesystem(); $fs->mkdir(dirname($filename)); file_put_contents($filename, $this->pemContent); // Now we use this file as pem $pem = $filename; $passphrase = $this->pemContentPassphrase; } $ctx = stream_context_create(); stream_context_set_option($ctx, "ssl", "local_cert", $pem); if (strlen($passphrase)) { stream_context_set_option($ctx, "ssl", "passphrase", $passphrase); } return $ctx; } /** * Creates the full payload for the notification * * @param int $messageId * @param string $expiry * @param string $token * @param array $message * * @return string * * @throws \LogicException * @throws \InvalidArgumentException */ protected function createPayload($messageId, $expiry, $token, $message) { if ($this->jsonUnescapedUnicode) { // Validate PHP version if (!version_compare(PHP_VERSION, '5.4.0', '>=')) { throw new \LogicException(sprintf( 'Can\'t use JSON_UNESCAPED_UNICODE option on PHP %s. Support PHP >= 5.4.0', PHP_VERSION )); } // WARNING: // Set otpion JSON_UNESCAPED_UNICODE is violation // of RFC 4627 // Because required validate charsets (Must be UTF-8) $encoding = mb_detect_encoding($message['aps']['alert']); if ($encoding != 'UTF-8' && $encoding != 'ASCII') { throw new \InvalidArgumentException(sprintf( 'Message must be UTF-8 encoding, "%s" given.', mb_detect_encoding($message['aps']['alert']) )); } $jsonBody = json_encode($message, JSON_UNESCAPED_UNICODE); } else { $jsonBody = json_encode($message); } $token = preg_replace("/[^0-9A-Fa-f]/", "", $token); $payload = chr(1) . pack("N", $messageId) . pack("N", $expiry) . pack("n", 32) . pack("H*", $token) . pack("n", strlen($jsonBody)) . $jsonBody; return $payload; } /** * Creates a MDM payload * * @param string $token * @param string $magicPushToken * * @return string */ public function createMdmPayload($token, $magicPushToken) { $jsonPayload = json_encode(array('mdm' => $magicPushToken)); $payload = chr(0) . chr(0) . chr(32) . base64_decode($token) . chr(0) . chr(strlen($jsonPayload)) . $jsonPayload; return $payload; } /** * Returns responses * * @return array */ public function getResponses() { return $this->responses; } /** * @param $pemContent * @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() { $this->removeCachedPemFile(); $this->closeStreams(); } /** * 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 existing streams */ private function closeStreams() { foreach ($this->apnStreams as $stream) { fclose($stream); } $this->apnStreams = array(); } } ================================================ FILE: Service/OS/BlackberryNotification.php ================================================ evaluation = $evaluation; $this->appID = $appID; $this->password = $password; $this->timeout = $timeout; $this->logger = $logger; } /** * Sends a Blackberry Push message * * @param \RMS\PushNotificationsBundle\Message\MessageInterface $message * @throws \RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException * @return bool */ public function send(MessageInterface $message) { if (!$message instanceof BlackberryMessage) { throw new InvalidMessageTypeException(sprintf("Message type '%s' not supported by Blackberry", get_class($message))); } return $this->doSend($message); } /** * Does the actual sending * * @param \RMS\PushNotificationsBundle\Message\BlackberryMessage $message * @return bool */ protected function doSend(BlackberryMessage $message) { $separator = "mPsbVQo0a68eIL3OAxnm"; $body = $this->constructMessageBody($message, $separator); $browser = new Browser(new Curl()); $browser->getClient()->setTimeout($this->timeout); $listener = new BasicAuthListener($this->appID, $this->password); $browser->addListener($listener); $url = "https://pushapi.na.blackberry.com/mss/PD_pushRequest"; if ($this->evaluation) { $url = "https://pushapi.eval.blackberry.com/mss/PD_pushRequest"; } $headers = array(); $headers[] = "Content-Type: multipart/related; boundary={$separator}; type=application/xml"; $headers[] = "Accept: text/html, *"; $headers[] = "Connection: Keep-Alive"; $response = $browser->post($url, $headers, $body); return $this->parseResponse($response); } /** * Builds the actual body of the message * * @param \RMS\PushNotificationsBundle\Message\BlackberryMessage $message * @param $separator * @return string */ protected function constructMessageBody(BlackberryMessage $message, $separator) { $data = ""; $messageID = microtime(true); $data .= "--" . $separator . "\r\n"; $data .= "Content-Type: application/xml; charset=UTF-8\r\n\r\n"; $data .= $this->getXMLBody($message, $messageID) . "\r\n"; $data .= "--" . $separator . "\r\n"; $data .= "Content-Type: text/plain\r\n"; $data .= "Push-Message-ID: {$messageID}\r\n\r\n"; if (is_array($message->getMessageBody())) { $data .= json_encode($message->getMessageBody()); } else { $data .= $message->getMessageBody(); } $data .= "\r\n"; $data .= "--" . $separator . "--\r\n"; return $data; } /** * Handles and parses the response * Returns a value indicating success/fail * * @param \Buzz\Message\Response $response * @return bool */ protected function parseResponse(\Buzz\Message\Response $response) { if (null !== $response->getStatusCode() && $response->getStatusCode() != 200) { return false; } $doc = new \DOMDocument(); $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"); } /** * Create the XML body that accompanies the actual push data * * @param $messageID * @return string */ private function getXMLBody(BlackberryMessage $message, $messageID) { $deliverBefore = gmdate('Y-m-d\TH:i:s\Z', strtotime('+5 minutes')); $impl = new \DOMImplementation(); $dtd = $impl->createDocumentType( "pap", "-//WAPFORUM//DTD PAP 2.1//EN", "http://www.openmobilealliance.org/tech/DTD/pap_2.1.dtd" ); $doc = $impl->createDocument("", "", $dtd); // Build it centre-out $pm = $doc->createElement("push-message"); $pm->setAttribute("push-id", $messageID); $pm->setAttribute("deliver-before-timestamp", $deliverBefore); $pm->setAttribute("source-reference", $this->appID); $qos = $doc->createElement("quality-of-service"); $qos->setAttribute("delivery-method", "unconfirmed"); $add = $doc->createElement("address"); $add->setAttribute("address-value", $message->getDeviceIdentifier()); $pm->appendChild($add); $pm->appendChild($qos); $pap = $doc->createElement("pap"); $pap->appendChild($pm); $doc->appendChild($pap); return $doc->saveXML(); } } ================================================ FILE: Service/OS/MicrosoftNotification.php ================================================ browser = new Browser(new Curl()); $this->browser->getClient()->setVerifyPeer(false); $this->browser->getClient()->setTimeout($timeout); $this->logger = $logger; } public function send(MessageInterface $message) { if (!$message instanceof WindowsphoneMessage) { throw new InvalidMessageTypeException(sprintf("Message type '%s' not supported by MPNS", get_class($message))); } $headers = array( 'Content-Type: text/xml', 'X-WindowsPhone-Target: ' . $message->getTarget(), 'X-NotificationClass: ' . $message->getNotificationClass() ); $xml = new \SimpleXMLElement(''); $msgBody = $message->getMessageBody(); if ($message->getTarget() == WindowsphoneMessage::TYPE_TOAST) { $toast = $xml->addChild('wp:Toast'); $toast->addChild('wp:Text1', htmlspecialchars($msgBody['text1'], ENT_XML1|ENT_QUOTES)); $toast->addChild('wp:Text2', htmlspecialchars($msgBody['text2'], ENT_XML1|ENT_QUOTES)); } $response = $this->browser->post($message->getDeviceIdentifier(), $headers, $xml->asXML()); if (!$response->isSuccessful()) { $this->logger->error($response->getStatusCode(). ' : '. $response->getReasonPhrase()); } return $response->isSuccessful(); } } ================================================ FILE: Service/OS/OSNotificationServiceInterface.php ================================================ sandbox = $sandbox; $this->pem = $pem; $this->passphrase = $passphrase; $this->timeout = $timeout; } /** * Gets an array of device UUID unregistration details * from the APN feedback service * * @throws \RuntimeException * @return array */ public function getDeviceUUIDs() { if (!strlen($this->pem)) { throw new \RuntimeException("PEM not provided"); } $feedbackURL = "ssl://feedback.push.apple.com:2196"; if ($this->sandbox) { $feedbackURL = "ssl://feedback.sandbox.push.apple.com:2196"; } $data = ""; $ctx = $this->getStreamContext(); $fp = stream_socket_client($feedbackURL, $err, $errstr, $this->timeout, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx); if (!$fp) { throw new \RuntimeException("Couldn't connect to APNS Feedback service. Error no $err: $errstr"); } while (!feof($fp)) { $data .= fread($fp, 4096); } fclose($fp); if (!strlen($data)) { return array(); } $feedbacks = array(); $items = str_split($data, 38); foreach ($items as $item) { $feedback = new Feedback(); $feedbacks[] = $feedback->unpack($item); } return $feedbacks; } /** * Gets a stream context set up for SSL * using our PEM file and passphrase * * @return resource */ protected function getStreamContext() { $ctx = stream_context_create(); stream_context_set_option($ctx, "ssl", "local_cert", $this->pem); if (strlen($this->passphrase)) { stream_context_set_option($ctx, "ssl", "passphrase", $this->passphrase); } return $ctx; } } ================================================ FILE: Tests/DependencyInjection/ConfigurationTest.php ================================================ process(array()); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testAddingAndroidKeyRequiresValues() { $arr = array( array("android" => "~"), ); $config = $this->process($arr); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testAndroidRequiresUsername() { $arr = array( array( "android" => array("c2dm" => array("password" => "foo")) ), ); $config = $this->process($arr); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testAndroidRequiresPassword() { $arr = array( array( "android" => array("c2dm" => array("username" => "foo")) ), ); $config = $this->process($arr); } public function testOldFullAndroid() { // NB - this is the deprecated version $arr = array( array( "android" => array("username" => "foo", "password" => "bar", "source" => "123") ), ); $config = $this->process($arr); $this->assertArrayHasKey("android", $config); $this->assertEquals(5, $config["android"]["timeout"]); $this->assertEquals("foo", $config["android"]["username"]); $this->assertEquals("bar", $config["android"]["password"]); $this->assertEquals("123", $config["android"]["source"]); } public function testNewC2DMIsAllowedWithoutOldBits() { $arr = array( array( "android" => array( "c2dm" => array( "username" => "foo", "password" => "bar", "source" => "123" ) ) ), ); $config = $this->process($arr); $this->assertArrayHasKey("android", $config); $this->assertEquals(5, $config["android"]["timeout"]); $this->assertArrayHasKey("c2dm", $config["android"]); $this->assertEquals("foo", $config["android"]["c2dm"]["username"]); $this->assertEquals("bar", $config["android"]["c2dm"]["password"]); $this->assertEquals("123", $config["android"]["c2dm"]["source"]); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testGCMRequiresAPIKey() { $arr = array( array( "android" => array( "gcm" => array( ) ) ), ); $config = $this->process($arr); } public function testGCMIsOK() { $arr = array( array( "android" => array( "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"]); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testAddingiOsKeyRequiresValues() { $arr = array( array("ios" => "~"), ); $config = $this->process($arr); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testiOSRequiresPEM() { $arr = array( array( "ios" => array("pem" => "") ), ); $config = $this->process($arr); } public function testFulliOS() { $arr = array( array( "ios" => array("sandbox" => false, "pem" => "foo/bar.pem", "passphrase" => "foo") ), ); $config = $this->process($arr); $this->assertArrayHasKey("ios", $config); $this->assertEquals(60, $config["ios"]["timeout"]); $this->assertEquals(false, $config["ios"]["sandbox"]); $this->assertEquals("foo/bar.pem", $config["ios"]["pem"]); $this->assertEquals("foo", $config["ios"]["passphrase"]); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testAddingMacKeyRequiresValues() { $arr = array( array("mac" => "~"), ); $config = $this->process($arr); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testMacRequiresPEM() { $arr = array( array( "mac" => array("pem" => "") ), ); $config = $this->process($arr); } public function testFullMac() { $arr = array( array( "mac" => array("sandbox" => false, "pem" => "foo/bar.pem", "passphrase" => "foo") ), ); $config = $this->process($arr); $this->assertArrayHasKey("mac", $config); $this->assertEquals(60, $config["mac"]["timeout"]); $this->assertEquals(false, $config["mac"]["sandbox"]); $this->assertEquals("foo/bar.pem", $config["mac"]["pem"]); $this->assertEquals("foo", $config["mac"]["passphrase"]); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testBlackberryRequiresAppID() { $arr = array( array( "blackberry" => array("password" => "foo") ), ); $config = $this->process($arr); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testBlackberryRequiresPassword() { $arr = array( array( "blackberry" => array("app_id" => "foo") ), ); $config = $this->process($arr); } public function testFullBlackberry() { $arr = array( array( "blackberry" => array("evaluation" => false, "app_id" => "foo", "password" => "bar") ), ); $config = $this->process($arr); $this->assertArrayHasKey("blackberry", $config); $this->assertFalse($config["blackberry"]["evaluation"]); $this->assertEquals(5, $config["blackberry"]["timeout"]); $this->assertEquals("foo", $config["blackberry"]["app_id"]); $this->assertEquals("bar", $config["blackberry"]["password"]); } /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testAddingWindowsKeyRequiresValues() { $arr = array( array( "windowsphone" => "~" ), ); $config = $this->process($arr); } public function testFullWindows() { $arr = array( array( "windowsphone" => array("timeout" => 5) ), ); $config = $this->process($arr); $this->assertArrayHasKey("windowsphone", $config); $this->assertEquals(5, $config["windowsphone"]["timeout"]); } /** * Takes in an array of configuration values and returns the processed version * * @param array $config * @return array */ protected function process($config) { $processor = new Processor(); return $processor->processConfiguration(new Configuration(), $config); } } ================================================ FILE: Tests/Message/AndroidMessageTest.php ================================================ assertInstanceOf("RMS\PushNotificationsBundle\Message\MessageInterface", $msg); $this->assertEquals(Types::OS_ANDROID_C2DM, $msg->getTargetOS()); } public function testCoreBodyGeneratedOK() { $expected = array( "registration_id" => "", "collapse_key" => AndroidMessage::DEFAULT_COLLAPSE_KEY, "data.message" => "", ); $msg = new AndroidMessage(); $this->assertEquals($expected, $msg->getMessageBody()); } public function testMessageAddedOK() { $expected = array( "registration_id" => "", "collapse_key" => AndroidMessage::DEFAULT_COLLAPSE_KEY, "data.message" => "Foo", ); $msg = new AndroidMessage(); $msg->setMessage("Foo"); $this->assertEquals($expected, $msg->getMessageBody()); } public function testNewCollapseKey() { $expected = array( "registration_id" => "", "collapse_key" => 123, "data.message" => "", ); $msg = new AndroidMessage(); $msg->setCollapseKey(123); $this->assertEquals($expected, $msg->getMessageBody()); } public function testRegistrationIDAddedToBody() { $expected = array( "registration_id" => "ABC123", "collapse_key" => AndroidMessage::DEFAULT_COLLAPSE_KEY, "data.message" => "", ); $msg = new AndroidMessage(); $msg->setDeviceIdentifier("ABC123"); $this->assertEquals($expected, $msg->getMessageBody()); } public function testCustomData() { $expected = array( "registration_id" => "", "collapse_key" => AndroidMessage::DEFAULT_COLLAPSE_KEY, "data.message" => "", "custom" => array("foo" => "bar"), ); $msg = new AndroidMessage(); $msg->setData(array("custom" => array("foo" => "bar"))); $this->assertEquals($expected, $msg->getMessageBody()); } public function testTypeChangesBasedOnGCM() { $msg = new AndroidMessage(); $this->assertEquals(Types::OS_ANDROID_C2DM, $msg->getTargetOS()); $msg->setGCM(true); $this->assertEquals(Types::OS_ANDROID_GCM, $msg->getTargetOS()); } public function testSetIdentifierIsSingleEntryInGCMArray() { $msg = new AndroidMessage(); $msg->setDeviceIdentifier("foo"); $this->assertCount(1, $msg->getGCMIdentifiers()); } public function testAddingGCMIdentifiers() { $msg = new AndroidMessage(); $msg->addGCMIdentifier("foo"); $msg->addGCMIdentifier("bar"); $this->assertCount(2, $msg->getGCMIdentifiers()); } public function testSetMessageIsReturnedInGetData() { $msg = new AndroidMessage(); $message = 'Test message'; $msg->setMessage($message); $this->assertEquals(array('message' => $message), $msg->getData()); $msg->setData(array('id' => 10)); $this->assertEquals(array('id' => 10, 'message' => $message), $msg->getData()); $msg->setData(array('message' => 'Other message')); $this->assertEquals(array('message' => 'Other message'), $msg->getData()); } } ================================================ FILE: Tests/Message/BlackberryMessageTest.php ================================================ assertInstanceOf("RMS\PushNotificationsBundle\Message\MessageInterface", $msg); $this->assertEquals(Types::OS_BLACKBERRY, $msg->getTargetOS()); } public function testDefaultBody() { $expected = null; $msg = new BlackberryMessage(); $this->assertEquals($expected, $msg->getMessageBody()); } public function testSettingBody() { $expected = "Foo"; $msg = new BlackberryMessage(); $msg->setMessage("Foo"); $this->assertEquals($expected, $msg->getMessageBody()); } } ================================================ FILE: Tests/Message/MacMessageTest.php ================================================ assertInstanceOf("RMS\PushNotificationsBundle\Message\MessageInterface", $msg); $this->assertEquals(Types::OS_MAC, $msg->getTargetOS()); } } ================================================ FILE: Tests/Message/WindowsphoneMessageTest.php ================================================ assertInstanceOf("RMS\PushNotificationsBundle\Message\MessageInterface", $msg); $this->assertEquals(Types::OS_WINDOWSPHONE, $msg->getTargetOS()); } public function testDefaultBody() { $msg = new WindowsphoneMessage(); $this->assertArrayHasKey("text1", $msg->getMessageBody()); $this->assertArrayHasKey("text2", $msg->getMessageBody()); } public function testSettingBody() { $expected = "Foo"; $msg = new WindowsphoneMessage(); $msg->setMessage("Foo"); $msgBody = $msg->getMessageBody(); $this->assertEquals($expected, $msgBody['text2']); } public function testDefaultTarget() { $msg = new WindowsphoneMessage(); $this->assertEquals(WindowsphoneMessage::TYPE_TOAST, $msg->getTarget()); } } ================================================ FILE: Tests/Message/iOSMessageTest.php ================================================ assertInstanceOf("RMS\PushNotificationsBundle\Message\MessageInterface", $msg); $this->assertEquals(Types::OS_IOS, $msg->getTargetOS()); } public function testCoreBodyGeneratedOK() { $expected = array( "aps" => array(), ); $msg = new iOSMessage(); $this->assertEquals($expected, $msg->getMessageBody()); } public function testAPSAlertAddedOK() { $expected = array( "aps" => array( "alert" => "Foo", ), ); $msg = new iOSMessage(); $msg->setMessage("Foo"); $this->assertEquals($expected, $msg->getMessageBody()); } public function testAPSBadgeAddedOK() { $expected = array( "aps" => array( "badge" => 1, ), ); $msg = new iOSMessage(); $msg->setAPSBadge(1); $this->assertEquals($expected, $msg->getMessageBody()); } public function testAPSSoundAddedOK() { $expected = array( "aps" => array( "sound" => "default", ), ); $msg = new iOSMessage(); $msg->setAPSSound("default"); $this->assertEquals($expected, $msg->getMessageBody()); } public function testCustomDataAddedOK() { $expected = array( "aps" => array(), "custom" => array("foo" => "bar"), ); $msg = new iOSMessage(); $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()); } } ================================================ FILE: Tests/autoload.php.dist ================================================ =5.3.0", "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": "" } }, "target-dir": "RMS/PushNotificationsBundle", "extra": { "branch-alias": { "dev-master": "0.2.x-dev" } } } ================================================ FILE: phpunit.travis.xml ================================================ ./Tests ================================================ FILE: phpunit.xml.dist ================================================ ./Tests