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!

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