[
  {
    "path": ".gitignore",
    "content": "vendor"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\nbefore_install:\n  # If PHP >= 5.6, download & install PHPunit 5.7 to avoid builds failures\n  - 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\n\nphp:\n  - 5.3\n  - 5.4\n  - 5.5\n  - 5.6\n  - 7.0\n  - 7.1\nenv:\n  - SYMFONY_VERSION=origin/master\n\nbefore_script:\n  - wget http://getcomposer.org/composer.phar\n  - php composer.phar install\n\nscript: phpunit --configuration phpunit.travis.xml\n"
  },
  {
    "path": "Command/TestPushCommand.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Command;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand;\n\nuse Symfony\\Component\\Console\\Input\\InputArgument,\n    Symfony\\Component\\Console\\Input\\InputInterface,\n    Symfony\\Component\\Console\\Input\\InputOption,\n    Symfony\\Component\\Console\\Output\\OutputInterface;\nuse RMS\\PushNotificationsBundle\\Message as PushMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\n\nclass TestPushCommand extends ContainerAwareCommand\n{\n    /**\n     * @var \\Doctrine\\ORM\\EntityManager\n     */\n    protected $em;\n\n    /**\n     * Configures the console commnad\n     *\n     * @return void\n     */\n    protected function configure()\n    {\n        $this\n            ->setName(\"rms:test-push\")\n            ->setDescription(\"Sends a push command to a supplied push token'd device\")\n            ->addOption(\"badge\", \"b\", InputOption::VALUE_OPTIONAL, \"Badge number (for iOS devices)\", 0)\n            ->addOption(\"text\", \"t\", InputOption::VALUE_OPTIONAL, \"Text message\")\n            ->addArgument(\"service\", InputArgument::REQUIRED, \"One of 'ios', 'c2dm', 'gcm', 'mac', 'blackberry' or 'windowsphone'\")\n            ->addArgument(\"token\", InputArgument::REQUIRED, \"Authentication token for the service\")\n            ->addArgument(\"payload\", InputArgument::OPTIONAL, \"The payload data to send (JSON)\", '{\"data\": \"test\"}')\n        ;\n    }\n\n    /**\n     * Main command execution.\n     *\n     * @param  InputInterface  $input  An InputInterface instance\n     * @param  OutputInterface $output An OutputInterface instance\n     * @return void\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        $token = $input->getArgument(\"token\");\n        $service = strtolower($input->getArgument(\"service\"));\n        $json_payload = $input->getArgument(\"payload\");\n        $payload = json_decode($json_payload, true);\n\n        $tokenLengths = array(\n            \"ios\" => 64,\n            \"c2dm\" => 162,\n        );\n\n        if (isset($tokenLengths[$service]) && strlen($token) != $tokenLengths[$service]) {\n            $output->writeln(\"<error>Token should be \" . $tokenLengths[$service] . \"chars long, not \" . strlen($token) . \"</error>\");\n\n            return;\n        }\n\n        if ($payload == null) {\n            throw new \\InvalidArgumentException(\"Invalid JSON payload \" . $json_payload);\n        }\n\n        $msg = $this->getMessageClass($service);\n\n        if (method_exists($msg, \"setAPSBadge\")) {\n            // Set badge on iOS\n            $msg->setAPSBadge((int) $input->getOption(\"badge\"));\n        }\n        if (method_exists($msg, \"setAPSSound\")) {\n            // Set sound on iOS\n            $msg->setAPSSound(\"default\");\n        }\n\n        $msg->setDeviceIdentifier($token);\n        $msg->setData($payload);\n\n        if ($input->getOption(\"text\")) {\n            $msg->setMessage($input->getOption(\"text\"));\n        }\n\n        $result = $this->getContainer()->get(\"rms_push_notifications\")->send($msg);\n        if ($result) {\n            $output->writeln(\"<comment>Send successful</comment>\");\n        } else {\n            $output->writeln(\"<error>Send failed</error>\");\n        }\n\n        $output->writeln(\"<comment>done</comment>\");\n    }\n\n    /**\n     * Returns a message class based on the supplied os\n     *\n     * @param  string                    $service The name of the service to return a message for\n     * @throws \\InvalidArgumentException\n     * @return MessageInterface\n     */\n    protected function getMessageClass($service)\n    {\n        switch ($service) {\n            case \"ios\":\n                return new PushMessage\\iOSMessage();\n            case \"c2dm\":\n                return new PushMessage\\AndroidMessage();\n            case \"gcm\":\n                $message = new PushMessage\\AndroidMessage();\n                $message->setGCM(true);\n\n                return $message;\n            case \"blackberry\":\n                return new PushMessage\\BlackberryMessage();\n            case \"mac\":\n                return new PushMessage\\MacMessage();\n            case \"windowsphone\":\n                return new PushMessage\\WindowsphoneMessage();\n            default:\n                throw new \\InvalidArgumentException(\"Service '{$service}' not supported presently\");\n        }\n    }\n}\n"
  },
  {
    "path": "DependencyInjection/Compiler/AddHandlerPass.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\DependencyInjection\\Compiler;\n\nuse Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface,\n    Symfony\\Component\\DependencyInjection\\ContainerBuilder,\n    Symfony\\Component\\DependencyInjection\\Definition,\n    Symfony\\Component\\DependencyInjection\\Reference,\n    RMS\\PushNotificationsBundle\\Device\\Types,\n    Symfony\\Component\\DependencyInjection\\Exception\\ParameterNotFoundException;\n\nclass AddHandlerPass implements CompilerPassInterface\n{\n    /**\n     * Processes any handlers tagged accordingly\n     *\n     * @param  ContainerBuilder $container\n     * @return void\n     */\n    public function process(ContainerBuilder $container)\n    {\n        $service = $container->getDefinition(\"rms_push_notifications\");\n\n        foreach ($container->findTaggedServiceIds(\"rms_push_notifications.handler\") as $id => $attributes) {\n            if (!isset($attributes[0][\"osType\"])) {\n                throw new \\LogicException(\"Handler {$id} requires an osType attribute\");\n            }\n\n            $definition = $container->getDefinition($id);\n\n            // Get reflection class for validate handler\n            try {\n                $class = $definition->getClass();\n\n                // Class is parameter\n                if (strpos($class, '%') === 0) {\n                    $class = $container->getParameter(trim($class, '%'));\n                }\n\n                $refClass = new \\ReflectionClass($class);\n            } catch (\\ReflectionException $ref) {\n                // Class not found or other reflection error\n                throw new \\RuntimeException(sprintf(\n                    'Can\\'t compile notification handler by service id \"%s\".',\n                    $id\n                ), 0, $ref);\n            } catch (ParameterNotFoundException $paramNotFound) {\n                // Parameter not found in service container\n                throw new \\RuntimeException(sprintf(\n                    'Can\\'t compile notification handler by service id \"%s\".',\n                    $id\n                ), 0, $paramNotFound);\n            }\n\n            // Required interface\n            $requiredInterface = 'RMS\\\\PushNotificationsBundle\\\\Service\\\\OS\\\\OSNotificationServiceInterface';\n            if (!$refClass->implementsInterface($requiredInterface)) {\n                throw new \\UnexpectedValueException(sprintf(\n                   'Notification service \"%s\" by id \"%s\" must be implements \"%s\" interface!' ,\n                   $refClass->getName(), $id, $requiredInterface\n                ));\n            }\n\n            // Add handler to service notifications storage\n            $service->addMethodCall(\"addHandler\", array($attributes[0][\"osType\"], new Reference($id)));\n        }\n    }\n}\n"
  },
  {
    "path": "DependencyInjection/Configuration.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\DependencyInjection;\n\nuse Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder;\nuse Symfony\\Component\\Config\\Definition\\ConfigurationInterface;\n\nclass Configuration implements ConfigurationInterface\n{\n    /**\n     * @var \\Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition\n     */\n    protected $root;\n\n    /**\n     * Generates the configuration tree builder.\n     *\n     * @return \\Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder The tree builder\n     */\n    public function getConfigTreeBuilder()\n    {\n        $treeBuilder = new TreeBuilder();\n        $this->root = $treeBuilder->root(\"rms_push_notifications\");\n\n        $this->addAndroid();\n        $this->addiOS();\n        $this->addMac();\n        $this->addBlackberry();\n        $this->addWindowsphone();\n\n        return $treeBuilder;\n    }\n\n    /**\n     * Android configuration\n     */\n    protected function addAndroid()\n    {\n        $this->root->\n            children()->\n                arrayNode(\"android\")->\n                    canBeUnset()->\n                    children()->\n\n                        scalarNode(\"timeout\")->defaultValue(5)->end()->\n\n                        // WARNING: These 3 fields as they are, outside of the c2dm array\n                        // are deprecrated in favour of using the c2dm array configuration\n                        // At present these will be overriden by anything supplied\n                        // in the c2dm array\n                        scalarNode(\"username\")->defaultValue(\"\")->end()->\n                        scalarNode(\"password\")->defaultValue(\"\")->end()->\n                        scalarNode(\"source\")->defaultValue(\"\")->end()->\n\n                        arrayNode(\"c2dm\")->\n                            canBeUnset()->\n                            children()->\n                                scalarNode(\"username\")->isRequired()->end()->\n                                scalarNode(\"password\")->isRequired()->end()->\n                                scalarNode(\"source\")->defaultValue(\"\")->end()->\n                            end()->\n                        end()->\n                        arrayNode(\"gcm\")->\n                            canBeUnset()->\n                            children()->\n                                scalarNode(\"api_key\")->isRequired()->cannotBeEmpty()->end()->\n                                booleanNode(\"use_multi_curl\")->defaultValue(true)->end()->\n                                booleanNode(\"dry_run\")->defaultFalse()->end()->\n                            end()->\n                        end()->\n                    end()->\n                end()->\n            end()\n        ;\n    }\n\n    /**\n     * iOS configuration\n     */\n    protected function addiOS()\n    {\n        $this->addApple(\"ios\");\n    }\n\n    /**\n     * Mac configuration\n     */\n    protected function addMac()\n    {\n        $this->addApple(\"mac\");\n    }\n\n    /**\n     * Generic Apple Configuration\n     */\n    private function addApple($os)\n    {\n        $config = $this->root->\n            children()->\n                arrayNode($os)->\n                    children()->\n                        scalarNode(\"timeout\")->defaultValue(60)->end()->\n                        booleanNode(\"sandbox\")->defaultFalse()->end()->\n                        scalarNode(\"pem\")->cannotBeEmpty()->end()->\n                        scalarNode(\"passphrase\")->defaultValue(\"\")->end()->\n                        scalarNode('json_unescaped_unicode')->defaultFalse();\n                        if (method_exists($config,'info')) {\n                            $config = $config->info('PHP >= 5.4.0 and each messaged must be UTF-8 encoding');\n                        }\n                        $config->end()->\n                    end()->\n                end()->\n            end()\n        ;\n    }\n\n    /**\n     * Blackberry configuration\n     */\n    protected function addBlackberry()\n    {\n        $this->root->\n            children()->\n                arrayNode(\"blackberry\")->\n                    children()->\n                        scalarNode(\"timeout\")->defaultValue(5)->end()->\n                        booleanNode(\"evaluation\")->defaultFalse()->end()->\n                        scalarNode(\"app_id\")->isRequired()->cannotBeEmpty()->end()->\n                        scalarNode(\"password\")->isRequired()->cannotBeEmpty()->end()->\n                    end()->\n                end()->\n            end()\n        ;\n    }\n\n    /**\n     * Windows Phone configuration\n     */\n    protected function addWindowsphone()\n    {\n        $this->root->\n            children()->\n                arrayNode('windowsphone')->\n                    children()->\n                        scalarNode(\"timeout\")->defaultValue(5)->end()->\n                    end()->\n                end()->\n            end()\n        ;\n    }\n}\n"
  },
  {
    "path": "DependencyInjection/RMSPushNotificationsExtension.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\DependencyInjection;\n\nuse Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension,\n    Symfony\\Component\\DependencyInjection\\ContainerBuilder,\n    Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader,\n    Symfony\\Component\\Config\\FileLocator;\n\nclass RMSPushNotificationsExtension extends Extension\n{\n    /**\n     * @var ContainerBuilder\n     */\n    protected $container;\n\n    /**\n     * @var string\n     */\n    protected $kernelRootDir;\n\n    /**\n     * Loads any resources/services we need\n     *\n     * @param  array                                                   $configs\n     * @param  \\Symfony\\Component\\DependencyInjection\\ContainerBuilder $container\n     * @return void\n     */\n    public function load(array $configs, ContainerBuilder $container)\n    {\n        $this->container = $container;\n        $this->kernelRootDir = $container->getParameterBag()->get(\"kernel.root_dir\");\n\n        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));\n        $loader->load('services.xml');\n\n        $configuration = new Configuration();\n        $config = $this->processConfiguration($configuration, $configs);\n\n        $this->setInitialParams();\n        if (isset($config[\"android\"])) {\n            $this->setAndroidConfig($config);\n            $loader->load('android.xml');\n        }\n        if (isset($config[\"ios\"])) {\n            $this->setiOSConfig($config);\n            $loader->load('ios.xml');\n        }\n        if (isset($config[\"mac\"])) {\n            $this->setMacConfig($config);\n            $loader->load('mac.xml');\n        }\n        if (isset($config[\"blackberry\"])) {\n            $this->setBlackberryConfig($config);\n            $loader->load('blackberry.xml');\n        }\n        if (isset($config['windowsphone'])) {\n            $this->setWindowsphoneConfig($config);\n            $loader->load('windowsphone.xml');\n        }\n    }\n\n    /**\n     * Initial enabling\n     */\n    protected function setInitialParams()\n    {\n        $this->container->setParameter(\"rms_push_notifications.android.enabled\", false);\n        $this->container->setParameter(\"rms_push_notifications.ios.enabled\", false);\n        $this->container->setParameter(\"rms_push_notifications.mac.enabled\", false);\n    }\n\n    /**\n     * Sets Android config into container\n     *\n     * @param array $config\n     */\n    protected function setAndroidConfig(array $config)\n    {\n        $this->container->setParameter(\"rms_push_notifications.android.enabled\", true);\n        $this->container->setParameter(\"rms_push_notifications.android.c2dm.enabled\", true);\n\n        // C2DM\n        $username = $config[\"android\"][\"username\"];\n        $password = $config[\"android\"][\"password\"];\n        $source = $config[\"android\"][\"source\"];\n        $timeout = $config[\"android\"][\"timeout\"];\n        if (isset($config[\"android\"][\"c2dm\"])) {\n            $username = $config[\"android\"][\"c2dm\"][\"username\"];\n            $password = $config[\"android\"][\"c2dm\"][\"password\"];\n            $source = $config[\"android\"][\"c2dm\"][\"source\"];\n        }\n        $this->container->setParameter(\"rms_push_notifications.android.timeout\", $timeout);\n        $this->container->setParameter(\"rms_push_notifications.android.c2dm.username\", $username);\n        $this->container->setParameter(\"rms_push_notifications.android.c2dm.password\", $password);\n        $this->container->setParameter(\"rms_push_notifications.android.c2dm.source\", $source);\n\n        // GCM\n        $this->container->setParameter(\"rms_push_notifications.android.gcm.enabled\", isset($config[\"android\"][\"gcm\"]));\n        if (isset($config[\"android\"][\"gcm\"])) {\n            $this->container->setParameter(\"rms_push_notifications.android.gcm.api_key\", $config[\"android\"][\"gcm\"][\"api_key\"]);\n            $this->container->setParameter(\"rms_push_notifications.android.gcm.use_multi_curl\", $config[\"android\"][\"gcm\"][\"use_multi_curl\"]);\n            $this->container->setParameter('rms_push_notifications.android.gcm.dry_run', $config[\"android\"][\"gcm\"][\"dry_run\"]);\n        }\n    }\n\n    /**\n     * Sets iOS config into container\n     *\n     * @param array $config\n     */\n    protected function setiOSConfig(array $config)\n    {\n        $this->setAppleConfig($config, \"ios\");\n    }\n\n    /**\n     * Sets Mac config into container\n     *\n     * @param array $config\n     */\n    protected function setMacConfig(array $config)\n    {\n        $this->setAppleConfig($config, \"mac\");\n    }\n\n    /**\n     * Sets Apple config into container\n     *\n     * @param  array             $config\n     * @param $os\n     * @throws \\RuntimeException\n     * @throws \\LogicException\n     */\n    protected function setAppleConfig(array $config, $os)\n    {\n        $supportedAppleOS = array(\"mac\", \"ios\");\n        //Check if the OS is supported\n        if (!in_array($os, $supportedAppleOS, true)) {\n            throw new \\RuntimeException(sprintf('This Apple OS \"%s\" is not supported', $os));\n        }\n\n        $pemFile = null;\n        if (isset($config[$os][\"pem\"])) {\n            // If PEM is set, it must be a real file\n            if (realpath($config[$os][\"pem\"])) {\n                // Absolute path\n                $pemFile = $config[$os][\"pem\"];\n            } elseif (realpath($this->kernelRootDir.DIRECTORY_SEPARATOR.$config[$os][\"pem\"]) ) {\n                // Relative path\n                $pemFile = $this->kernelRootDir.DIRECTORY_SEPARATOR.$config[$os][\"pem\"];\n            } else {\n                // path isn't valid\n                throw new \\RuntimeException(sprintf('Pem file \"%s\" not found.', $config[$os][\"pem\"]));\n            }\n        }\n\n        if ($config[$os]['json_unescaped_unicode']) {\n            // Not support JSON_UNESCAPED_UNICODE option\n            if (!version_compare(PHP_VERSION, '5.4.0', '>=')) {\n                throw new \\LogicException(sprintf(\n                    'Can\\'t use JSON_UNESCAPED_UNICODE option. This option can use only PHP Version >= 5.4.0. Your version: %s',\n                    PHP_VERSION\n                ));\n            }\n        }\n\n        $this->container->setParameter(sprintf('rms_push_notifications.%s.enabled', $os), true);\n        $this->container->setParameter(sprintf('rms_push_notifications.%s.timeout', $os), $config[$os][\"timeout\"]);\n        $this->container->setParameter(sprintf('rms_push_notifications.%s.sandbox', $os), $config[$os][\"sandbox\"]);\n        $this->container->setParameter(sprintf('rms_push_notifications.%s.pem', $os), $pemFile);\n        $this->container->setParameter(sprintf('rms_push_notifications.%s.passphrase', $os), $config[$os][\"passphrase\"]);\n        $this->container->setParameter(sprintf('rms_push_notifications.%s.json_unescaped_unicode', $os), (bool) $config[$os]['json_unescaped_unicode']);\n    }\n\n    /**\n     * Sets Blackberry config into container\n     *\n     * @param array $config\n     */\n    protected function setBlackberryConfig(array $config)\n    {\n        $this->container->setParameter(\"rms_push_notifications.blackberry.enabled\", true);\n        $this->container->setParameter(\"rms_push_notifications.blackberry.timeout\", $config[\"blackberry\"][\"timeout\"]);\n        $this->container->setParameter(\"rms_push_notifications.blackberry.evaluation\", $config[\"blackberry\"][\"evaluation\"]);\n        $this->container->setParameter(\"rms_push_notifications.blackberry.app_id\", $config[\"blackberry\"][\"app_id\"]);\n        $this->container->setParameter(\"rms_push_notifications.blackberry.password\", $config[\"blackberry\"][\"password\"]);\n    }\n\n    protected function setWindowsphoneConfig(array $config)\n    {\n        $this->container->setParameter(\"rms_push_notifications.windowsphone.enabled\", true);\n        $this->container->setParameter(\"rms_push_notifications.windowsphone.timeout\", $config[\"windowsphone\"][\"timeout\"]);\n    }\n}\n"
  },
  {
    "path": "Device/Types.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Device;\n\nclass Types\n{\n    const OS_ANDROID_C2DM = \"rms_push_notifications.os.android.c2dm\";\n    const OS_ANDROID_GCM = \"rms_push_notifications.os.android.gcm\";\n    const OS_IOS = \"rms_push_notifications.os.ios\";\n    const OS_MAC = \"rms_push_notifications.os.mac\";\n    const OS_BLACKBERRY = \"rms_push_notifications.os.blackberry\";\n    const OS_WINDOWSMOBILE = \"rms_push_notifications.os.windowsmobile\";\n    const OS_WINDOWSPHONE = \"rms_push_notifications.os.windowsphone\";\n}\n"
  },
  {
    "path": "Device/iOS/Feedback.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Device\\iOS;\n\nclass Feedback\n{\n    public $timestamp;\n    public $tokenLength;\n    public $uuid;\n\n    /**\n     * Unpacks the APNS data into the required fields\n     *\n     * @param $data\n     * @return \\RMS\\PushNotificationsBundle\\Device\\iOS\\Feedback\n     */\n    public function unpack($data)\n    {\n        $token = unpack(\"N1timestamp/n1length/H*token\", $data);\n        $this->timestamp = $token[\"timestamp\"];\n        $this->tokenLength = $token[\"length\"];\n        $this->uuid = $token[\"token\"];\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "Exception/InvalidMessageTypeException.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Exception;\n\nclass InvalidMessageTypeException extends \\RuntimeException\n{\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012-2015 Rich Sage\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Message/AndroidMessage.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types;\n\nclass AndroidMessage implements MessageInterface\n{\n    const DEFAULT_COLLAPSE_KEY = 1;\n\n    /**\n     * String message\n     *\n     * @var string\n     */\n    protected $message = \"\";\n\n    /**\n     * The data to send in the message\n     *\n     * @var array\n     */\n    protected $data = array();\n\n    /**\n     * Identifier of the target device\n     *\n     * @var string\n     */\n    protected $identifier = \"\";\n\n    /**\n     * Collapse key for data\n     *\n     * @var int\n     */\n    protected $collapseKey = self::DEFAULT_COLLAPSE_KEY;\n\n    /**\n     * Whether this is a GCM message\n     *\n     * @var bool\n     */\n    protected $isGCM = false;\n\n    /**\n     * A collection of device identifiers that the message\n     * is intended for. GCM use only\n     *\n     * @var array\n     */\n    protected $allIdentifiers = array();\n\n    /**\n     * Options for GCM messages\n     *\n     * @var array\n     */\n    protected $gcmOptions = array();\n\n    /**\n     * Sets the string message\n     *\n     * @param $message\n     */\n    public function setMessage($message)\n    {\n        $this->message = $message;\n    }\n\n    /**\n     * Returns the string message\n     *\n     * @return string\n     */\n    public function getMessage()\n    {\n        return $this->message;\n    }\n\n    /**\n     * Sets the data. For Android, this is any custom data to use\n     *\n     * @param array $data The custom data to send\n     */\n    public function setData($data)\n    {\n        $this->data = (is_array($data) ? $data : array($data));\n    }\n\n    /**\n     * Returns any custom data\n     *\n     * @return array\n     */\n    public function getData()\n    {\n        return array_merge(array('message' => $this->getMessage()), $this->data);\n    }\n\n    /**\n     * Gets the message body to send\n     * This is primarily used in C2DM\n     *\n     * @return array\n     */\n    public function getMessageBody()\n    {\n        $data = array(\n            \"registration_id\" => $this->identifier,\n            \"collapse_key\"    => $this->collapseKey,\n            \"data.message\"    => $this->message,\n        );\n        if (!empty($this->data)) {\n            $data = array_merge($data, $this->data);\n        }\n\n        return $data;\n    }\n\n    /**\n     * Sets the identifier of the target device, eg UUID or similar\n     *\n     * @param $identifier\n     */\n    public function setDeviceIdentifier($identifier)\n    {\n        $this->identifier = $identifier;\n        $this->allIdentifiers = array($identifier => $identifier);\n    }\n\n    /**\n     * Returns the target OS for this message\n     *\n     * @return string\n     */\n    public function getTargetOS()\n    {\n        return ($this->isGCM ? Types::OS_ANDROID_GCM : Types::OS_ANDROID_C2DM);\n    }\n\n    /**\n     * Returns the target device identifier\n     *\n     * @return string\n     */\n    public function getDeviceIdentifier()\n    {\n        return $this->identifier;\n    }\n\n    /**\n     * Android-specific\n     * Returns the collapse key\n     *\n     * @return int\n     */\n    public function getCollapseKey()\n    {\n        return $this->collapseKey;\n    }\n\n    /**\n     * Android-specific\n     * Sets the collapse key\n     *\n     * @param $collapseKey\n     */\n    public function setCollapseKey($collapseKey)\n    {\n        $this->collapseKey = $collapseKey;\n    }\n\n    /**\n     * Set whether this is a GCM message\n     * (default false)\n     *\n     * @param $gcm\n     */\n    public function setGCM($gcm)\n    {\n        $this->isGCM = !!$gcm;\n    }\n\n    /**\n     * Returns whether this is a GCM message\n     *\n     * @return mixed\n     */\n    public function isGCM()\n    {\n        return $this->isGCM;\n    }\n\n    /**\n     * Returns an array of device identifiers\n     * Not used in C2DM\n     *\n     * @return mixed\n     */\n    public function getGCMIdentifiers()\n    {\n        return array_values($this->allIdentifiers);\n    }\n\n    /**\n     * Adds a device identifier to the GCM list\n     * @param string $identifier\n     */\n    public function addGCMIdentifier($identifier)\n    {\n        $this->allIdentifiers[$identifier] = $identifier;\n    }\n\n    /**\n     * Sets the GCM list\n     * @param array $allIdentifiers\n     */\n    public function setAllIdentifiers($allIdentifiers) {\n        $this->allIdentifiers = array_combine($allIdentifiers, $allIdentifiers);\n    }\n\n    /**\n     * Sets GCM options\n     * @param array $options\n     */\n    public function setGCMOptions($options)\n    {\n        $this->gcmOptions = $options;\n    }\n\n    /**\n     * Returns GCM options\n     *\n     * @return array\n     */\n    public function getGCMOptions()\n    {\n        return $this->gcmOptions;\n    }\n}\n"
  },
  {
    "path": "Message/AppleMessage.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Message;\n\nclass AppleMessage implements MessageInterface\n{\n    /**\n     * Custom data for the APS body\n     *\n     * @var array\n     */\n    protected $customData = array();\n\n    /**\n     * Device identifier\n     *\n     * @var null\n     */\n    protected $identifier = null;\n\n    /**\n     * The APS core body\n     *\n     * @var array\n     */\n    protected $apsBody = array();\n\n    /**\n     * Expiration date (UTC)\n     *\n     * A fixed UNIX epoch date expressed in seconds (UTC) that identifies when the notification is no longer valid and can be discarded.\n     * If the expiry value is non-zero, APNs tries to deliver the notification at least once.\n     * Specify zero to request that APNs not store the notification at all.\n     *\n     * @var int\n     */\n    protected $expiry = 0;\n\n    /**\n     * Device push magic token\n     *\n     * @var string\n     */\n    protected $pushMagicToken = '';\n\n    /**\n     * Device token\n     *\n     * @var string\n     */\n    protected $token = '';\n\n    /**\n     * Whether this is a MDM message or not\n     *\n     * @var bool\n     */\n    protected $isMdmMessage = false;\n\n    /**\n     * Class constructor\n     */\n    public function __construct($identifier = NULL)\n    {\n        $this->apsBody = array(\n            \"aps\" => array(\n            ),\n        );\n\n        if ($identifier !== NULL) {\n            $this->identifier = $identifier;\n        }\n    }\n\n    /**\n     * Sets the message. For iOS, this is the APS alert message\n     *\n     * @param $message\n     */\n    public function setMessage($message)\n    {\n        $this->apsBody[\"aps\"][\"alert\"] = $message;\n    }\n\n    /**\n     * Sets any custom data for the APS body\n     *\n     * @param array $data\n     */\n    public function setData($data)\n    {\n        if (!is_array($data)) {\n            throw new \\InvalidArgumentException(sprintf('Messages custom data must be array, \"%s\" given.', gettype($data)));\n        }\n\n        if (array_key_exists(\"aps\", $data)) {\n            unset($data[\"aps\"]);\n        }\n\n        foreach ($data as $key => $value) {\n            $this->addCustomData($key, $value);\n        }\n\n        return $this;\n    }\n\n    /**\n     * Add custom data\n     *\n     * @param string $key\n     * @param mixed  $value\n     */\n    public function addCustomData($key, $value)\n    {\n        if ($key == 'aps') {\n            throw new \\LogicException('Can\\'t replace \"aps\" data. Please call to setMessage, if your want replace message text.');\n        }\n\n        if (is_object($value)) {\n            if (interface_exists('JsonSerializable') && !$value instanceof \\stdClass && !$value instanceof \\JsonSerializable) {\n                throw new \\InvalidArgumentException(sprintf(\n                    'Object %s::%s must be implements JsonSerializable interface for next serialize data.',\n                    get_class($value), spl_object_hash($value)\n                ));\n            }\n        }\n\n        $this->customData[$key] = $value;\n\n        return $this;\n    }\n\n    /**\n     * Sets the identifier of the target device, eg UUID or similar\n     *\n     * @param $identifier\n     */\n    public function setDeviceIdentifier($identifier)\n    {\n        $this->identifier = $identifier;\n    }\n\n    /**\n     * Gets the full message body to send to APN\n     *\n     * @return array\n     */\n    public function getMessageBody()\n    {\n        $payloadBody = $this->apsBody;\n        if (!empty($this->customData)) {\n            $payloadBody = array_merge($payloadBody, $this->customData);\n        }\n\n        return $payloadBody;\n    }\n\n    /**\n     * Returns the device identifier\n     *\n     * @return null|string\n     */\n    public function getDeviceIdentifier()\n    {\n        return $this->identifier;\n    }\n\n    /**\n     * Returns the target OS for this message\n     * Must be implemented by subclass\n     *\n     * @return string\n     */\n    public function getTargetOS()\n    {\n        return \"\";\n    }\n\n    /**\n     * iOS-specific\n     * Sets the APS sound\n     *\n     * @param string $sound The sound to use. Use 'default' to use the built-in default\n     */\n    public function setAPSSound($sound)\n    {\n        $this->apsBody[\"aps\"][\"sound\"] = $sound;\n    }\n\n    /**\n     * iOS-specific\n     * Sets the APS badge count\n     *\n     * @param integer $badge The badge count to display\n     */\n    public function setAPSBadge($badge)\n    {\n        $this->apsBody[\"aps\"][\"badge\"] = (int) $badge;\n    }\n\n    /**\n     * iOS-specific\n     * Sets the APS content available flag, used to transform the notification into remote-notification\n     * and trigger the \"didReceiveRemoteNotification: fetchCompletionHandler:\" method on iOS apps\n     *\n     * @param string $contentAvailable The flag to set the content-available option, for example set it to 1.\n     */\n    public function setAPSContentAvailable($contentAvailable)\n    {\n        $this->apsBody[\"aps\"][\"content-available\"] = $contentAvailable;\n    }\n\n    /**\n     * iOS-specific\n     * Sets the APS category\n     *\n     * @param string $category The notification category\n     */\n    public function setCategory($category)\n    {\n        $this->apsBody[\"aps\"][\"category\"] = $category;\n    }\n\n    /**\n     * iOS-specific\n     * Sets the APS mutable-content attribute\n     *\n     * @param bool $mutableContent \n     */\n    public function setMutableContent($mutableContent)\n    {\n        $this->apsBody[\"aps\"][\"mutable-content\"] = $mutableContent ? 1 : 0;\n    }\n\n    /**\n     * Set expiry of message\n     *\n     * @param int $expiry\n     */\n    public function setExpiry($expiry)\n    {\n        $this->expiry = $expiry;\n    }\n\n    /**\n     * Get expiry of message\n     *\n     * @return int\n     */\n    public function getExpiry()\n    {\n        return $this->expiry;\n    }\n\n    /**\n     * @param string $pushMagicToken\n     */\n    public function setPushMagicToken($pushMagicToken)\n    {\n        $this->pushMagicToken = $pushMagicToken;\n    }\n\n    /**\n     * @return string\n     */\n    public function getPushMagicToken()\n    {\n        return $this->pushMagicToken;\n    }\n\n    /**\n     * @return string\n     */\n    public function getToken()\n    {\n        return $this->token;\n    }\n\n    /**\n     * @param string $token\n     */\n    public function setToken($token)\n    {\n        $this->token = $token;\n    }\n\n    /**\n     * @param null|bool $isMdmMessage\n     *\n     * @return bool|null\n     */\n    public function isMdmMessage($isMdmMessage = null)\n    {\n        if ($isMdmMessage === null) {\n            return $this->isMdmMessage;\n        }\n\n        $this->isMdmMessage = (bool) $isMdmMessage;\n    }\n}\n"
  },
  {
    "path": "Message/BlackberryMessage.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types;\n\nclass BlackberryMessage implements MessageInterface\n{\n    /**\n     * The data to send in the message\n     *\n     * @var mixed\n     */\n    protected $data = null;\n\n    /**\n     * Identifier of the target device\n     *\n     * @var string\n     */\n    protected $identifier = \"\";\n\n    /**\n     * Sets the message\n     * For Blackberry, this is the same as the data\n     *\n     * @param $message\n     */\n    public function setMessage($message)\n    {\n        $this->setData($message);\n    }\n\n    /**\n     * Sets the data. For Blackberry, this is any data required\n     *\n     * @param array $data The custom data to send\n     */\n    public function setData($data)\n    {\n        $this->data = $data;\n    }\n\n    /**\n     * Gets the message body to send\n     * For Blackberry, this is just our data object\n     *\n     * @return array\n     */\n    public function getMessageBody()\n    {\n        return $this->data;\n    }\n\n    /**\n     * Sets the identifier of the target device, eg UUID or similar\n     *\n     * @param $identifier\n     */\n    public function setDeviceIdentifier($identifier)\n    {\n        $this->identifier = $identifier;\n    }\n\n    /**\n     * Returns the target OS for this message\n     *\n     * @return string\n     */\n    public function getTargetOS()\n    {\n        return Types::OS_BLACKBERRY;\n    }\n\n    /**\n     * Returns the target device identifier\n     *\n     * @return string\n     */\n    public function getDeviceIdentifier()\n    {\n        return $this->identifier;\n    }\n}\n"
  },
  {
    "path": "Message/MacMessage.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types;\n\nclass MacMessage extends AppleMessage\n{\n    /**\n     * Returns the target OS for this message\n     *\n     * @return string\n     */\n    public function getTargetOS()\n    {\n        return Types::OS_MAC;\n    }\n}\n"
  },
  {
    "path": "Message/MessageInterface.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Message;\n\ninterface MessageInterface\n{\n    public function setMessage($message);\n\n    public function setData($data);\n\n    public function setDeviceIdentifier($identifier);\n\n    public function getMessageBody();\n\n    public function getDeviceIdentifier();\n\n    public function getTargetOS();\n}\n"
  },
  {
    "path": "Message/WindowsphoneMessage.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types;\n\nclass WindowsphoneMessage implements MessageInterface\n{\n    const TYPE_TOAST = 'toast';\n\n    protected static $notificationClass = array(\n        self::TYPE_TOAST => 2\n    );\n\n    protected $identifier;\n\n    protected $text1 = '';\n\n    protected $text2 = '';\n\n    protected $target;\n\n    public function __construct()\n    {\n        $this->target = self::TYPE_TOAST;\n    }\n\n    public function getTargetOS()\n    {\n        return Types::OS_WINDOWSPHONE;\n    }\n\n    public function getDeviceIdentifier()\n    {\n        return $this->identifier;\n    }\n\n    public function setDeviceIdentifier($identifier)\n    {\n        $this->identifier = $identifier;\n    }\n\n    public function getMessageBody()\n    {\n        return array(\n            'text1' => $this->text1,\n            'text2' => $this->text2\n        );\n    }\n\n    public function setMessage($message)\n    {\n        $this->text2 = $message;\n    }\n\n    public function setData($data)\n    {\n        // Not implemented yet\n    }\n\n    public function getTarget()\n    {\n        return $this->target;\n    }\n\n    public function getNotificationClass()\n    {\n        return static::$notificationClass[$this->getTarget()];\n    }\n}\n"
  },
  {
    "path": "Message/iOSMessage.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types;\n\nclass iOSMessage extends AppleMessage\n{\n    /**\n     * Returns the target OS for this message\n     *\n     * @return string\n     */\n    public function getTargetOS()\n    {\n        return Types::OS_IOS;\n    }\n}\n"
  },
  {
    "path": "README.md",
    "content": "# RMSPushNotificationsBundle ![](https://secure.travis-ci.org/richsage/RMSPushNotificationsBundle.png)\n\nA bundle to allow sending of push notifications to mobile devices.  Currently supports Android (C2DM, GCM), Blackberry and iOS devices.\n\n## Installation\n\nTo use this bundle in your Symfony2 project add the following to your `composer.json`:\n\n    {\n        \"require\": {\n            // ...\n            \"richsage/rms-push-notifications-bundle\": \"dev-master\"\n        }\n    }\n\nand enable it in your kernel:\n\n    <?php\n    // app/AppKernel.php\n\n    public function registerBundles()\n    {\n        $bundles = array(\n            // ...\n            new RMS\\PushNotificationsBundle\\RMSPushNotificationsBundle(),\n        );\n    }\n\nNOTE: If you are still using Symfony 2.0, please use the `symfony2.0` branch.\n\n## Configuration\n\nConfiguration options available are as follows. Note that the specific services will\nonly be available if you provide configuration respectively for them.\n\n    rms_push_notifications:\n      android:\n          timeout: 5 # Seconds to wait for connection timeout, default is 5\n          c2dm:\n              username: <string_android_c2dm_username>\n              password: <string_android_c2dm_password>\n              source: <string_android_c2dm_source>\n          gcm:\n              api_key: <string_android_gcm_api_key> # This is titled \"Server Key\" when creating it\n              use_multi_curl: <boolean_android_gcm_use_multi_curl> # default is true\n              dry_run: <bool_use_gcm_dry_run>\n      ios:\n          timeout: 60 # Seconds to wait for connection timeout, default is 60\n          sandbox: <bool_use_apns_sandbox>\n          pem: <path_apns_certificate> # can be absolute or relative path (from app directory)\n          passphrase: <string_apns_certificate_passphrase>\n      mac:\n          timeout: 60 # Seconds to wait for connection timeout, default is 60\n          sandbox: <bool_use_apns_sandbox>\n          pem: <path_apns_certificate>\n          passphrase: <string_apns_certificate_passphrase>\n      blackberry:\n          timeout: 5 # Seconds to wait for connection timeout, default is 5\n          evaluation: <bool_bb_evaluation_mode>\n          app_id: <string_bb_app_id>\n          password: <string_bb_password>\n      windowsphone:\n          timeout: 5 # Seconds to wait for connection timeout, default is 5\n\nNOTE: 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.\n\nTimeout defaults are the defaults from prior to the introduction of this configuration value.\n\n## Usage\n\nA little example of how to push your first message to an iOS device, we'll assume that you've set up the configuration correctly:\n\n    use RMS\\PushNotificationsBundle\\Message\\iOSMessage;\n\n    class PushDemoController extends Controller\n    {\n        public function pushAction()\n        {\n            $message = new iOSMessage();\n            $message->setMessage('Oh my! A push notification!');\n            $message->setDeviceIdentifier('test012fasdf482asdfd63f6d7bc6d4293aedd5fb448fe505eb4asdfef8595a7');\n\n            $this->container->get('rms_push_notifications')->send($message);\n\n            return new Response('Push notification send!');\n        }\n    }\n\nThe 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.\n\n## Android messages\n\nSince 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:\n\n    use RMS\\PushNotificationsBundle\\Message\\AndroidMessage;\n\n    $message = new AndroidMessage();\n    $message->setGCM(true);\n\nto send as a GCM message rather than C2DM.\n\n## iOS Feedback service\n\nThe 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.\n\nThis service is available within the bundle.  The following code demonstrates how you can retrieve data from the service:\n\n    $feedbackService = $container->get(\"rms_push_notifications.ios.feedback\");\n    $uuids = $feedbackService->getDeviceUUIDs();\n\nHere, `$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.\n\nApple recommend you poll this service daily.\n\n## Windows Phone - Toast support\n\nThe bundle has beta support for Windows Phone, and supports the Toast notification. Use the `WindowsphoneMessage` message class to send accordingly.\n\n# Thanks\n\nFirstly, thanks to all contributors to this bundle!\n\n![](https://www.jetbrains.com/phpstorm/documentation/docs/logo_phpstorm.png)\n\nSecondly, thanks to [JetBrains](http://www.jetbrains.com) for their sponsorship of an open-source [PhpStorm](https://www.jetbrains.com/phpstorm/) licence for this project.\n"
  },
  {
    "path": "RMSPushNotificationsBundle.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle;\n\nuse Symfony\\Component\\HttpKernel\\Bundle\\Bundle;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse RMS\\PushNotificationsBundle\\DependencyInjection\\Compiler\\AddHandlerPass;\n\nclass RMSPushNotificationsBundle extends Bundle\n{\n    public function build(ContainerBuilder $container)\n    {\n        $container->addCompilerPass(new AddHandlerPass());\n    }\n}\n"
  },
  {
    "path": "Resources/config/android.xml",
    "content": "<?xml version=\"1.0\" ?>\n<container xmlns=\"http://symfony.com/schema/dic/services\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd\">\n\n    <parameters>\n        <parameter key=\"rms_push_notifications.android.c2dm.class\">RMS\\PushNotificationsBundle\\Service\\OS\\AndroidNotification</parameter>\n        <parameter key=\"rms_push_notifications.android.gcm.class\">RMS\\PushNotificationsBundle\\Service\\OS\\AndroidGCMNotification</parameter>\n    </parameters>\n\n    <services>\n\n        <!-- Android (C2DM) -->\n        <service id=\"rms_push_notifications.android.c2dm\" class=\"%rms_push_notifications.android.c2dm.class%\" public=\"false\">\n            <argument>%rms_push_notifications.android.c2dm.username%</argument>\n            <argument>%rms_push_notifications.android.c2dm.password%</argument>\n            <argument>%rms_push_notifications.android.c2dm.source%</argument>\n            <argument>%rms_push_notifications.android.timeout%</argument>\n            <tag name=\"rms_push_notifications.handler\" osType=\"rms_push_notifications.os.android.c2dm\" />\n        </service>\n\n        <!-- Android (GCM) -->\n        <service id=\"rms_push_notifications.android.gcm\" class=\"%rms_push_notifications.android.gcm.class%\" public=\"false\">\n            <argument>%rms_push_notifications.android.gcm.api_key%</argument>\n            <argument>%rms_push_notifications.android.gcm.use_multi_curl%</argument>\n            <argument>%rms_push_notifications.android.timeout%</argument>\n            <argument type=\"service\" id=\"logger\" />\n            <argument>null</argument>\n            <argument>%rms_push_notifications.android.gcm.dry_run%</argument>\n            <tag name=\"rms_push_notifications.handler\" osType=\"rms_push_notifications.os.android.gcm\" />\n        </service>\n\n    </services>\n\n</container>\n"
  },
  {
    "path": "Resources/config/blackberry.xml",
    "content": "<?xml version=\"1.0\" ?>\n<container xmlns=\"http://symfony.com/schema/dic/services\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd\">\n\n    <parameters>\n        <parameter key=\"rms_push_notifications.blackberry.class\">RMS\\PushNotificationsBundle\\Service\\OS\\BlackberryNotification</parameter>\n    </parameters>\n\n    <services>\n\n        <!-- Blackberry -->\n        <service id=\"rms_push_notifications.blackberry\" class=\"%rms_push_notifications.blackberry.class%\" public=\"false\">\n            <argument>%rms_push_notifications.blackberry.evaluation%</argument>\n            <argument>%rms_push_notifications.blackberry.app_id%</argument>\n            <argument>%rms_push_notifications.blackberry.password%</argument>\n            <argument>%rms_push_notifications.blackberry.timeout%</argument>\n            <argument type=\"service\" id=\"logger\" />\n            <tag name=\"rms_push_notifications.handler\" osType=\"rms_push_notifications.os.blackberry\" />\n        </service>\n\n    </services>\n\n</container>\n"
  },
  {
    "path": "Resources/config/ios.xml",
    "content": "<?xml version=\"1.0\" ?>\n<container xmlns=\"http://symfony.com/schema/dic/services\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd\">\n\n    <parameters>\n        <parameter key=\"rms_push_notifications.ios.class\">RMS\\PushNotificationsBundle\\Service\\OS\\AppleNotification</parameter>\n    </parameters>\n\n    <services>\n\n        <!-- iOS-->\n        <service id=\"rms_push_notifications.ios\" class=\"%rms_push_notifications.ios.class%\" public=\"false\">\n            <argument>%rms_push_notifications.ios.sandbox%</argument>\n            <argument>%rms_push_notifications.ios.pem%</argument>\n            <argument>%rms_push_notifications.ios.passphrase%</argument>\n            <argument>%rms_push_notifications.ios.json_unescaped_unicode%</argument>\n            <argument>%rms_push_notifications.ios.timeout%</argument>\n            <argument>%kernel.cache_dir%</argument>\n            <argument type=\"service\" id=\"rms_push_notifications.event_listener\" />\n            <argument type=\"service\" id=\"logger\" />\n            <tag name=\"rms_push_notifications.handler\" osType=\"rms_push_notifications.os.ios\" />\n        </service>\n\n        <!-- iOS Feedback requests -->\n        <service id=\"rms_push_notifications.ios.feedback\" class=\"%rms_push_notifications.ios.feedback.class%\">\n            <argument>%rms_push_notifications.ios.sandbox%</argument>\n            <argument>%rms_push_notifications.ios.pem%</argument>\n            <argument>%rms_push_notifications.ios.passphrase%</argument>\n            <argument>%rms_push_notifications.ios.timeout%</argument>\n        </service>\n\n\n    </services>\n\n</container>\n"
  },
  {
    "path": "Resources/config/mac.xml",
    "content": "<?xml version=\"1.0\" ?>\n<container xmlns=\"http://symfony.com/schema/dic/services\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd\">\n\n    <parameters>\n        <parameter key=\"rms_push_notifications.mac.class\">RMS\\PushNotificationsBundle\\Service\\OS\\AppleNotification</parameter>\n    </parameters>\n\n    <services>\n\n        <!-- Mac-->\n        <service id=\"rms_push_notifications.mac\" class=\"%rms_push_notifications.mac.class%\" public=\"false\">\n            <argument>%rms_push_notifications.mac.sandbox%</argument>\n            <argument>%rms_push_notifications.mac.pem%</argument>\n            <argument>%rms_push_notifications.mac.passphrase%</argument>\n            <argument>%rms_push_notifications.mac.json_unescaped_unicode%</argument>\n            <argument>%rms_push_notifications.mac.timeout%</argument>\n            <argument>%kernel.cache_dir%</argument>\n            <argument type=\"service\" id=\"rms_push_notifications.event_listener\" />\n            <argument type=\"service\" id=\"logger\" />\n            <tag name=\"rms_push_notifications.handler\" osType=\"rms_push_notifications.os.mac\" />\n        </service>\n    </services>\n\n</container>\n"
  },
  {
    "path": "Resources/config/services.xml",
    "content": "<?xml version=\"1.0\" ?>\n<container xmlns=\"http://symfony.com/schema/dic/services\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd\">\n\n    <parameters>\n        <parameter key=\"rms_push_notifications.class\">RMS\\PushNotificationsBundle\\Service\\Notifications</parameter>\n        <parameter key=\"rms_push_notifications.android.class\">RMS\\PushNotificationsBundle\\Service\\OS\\AndroidNotification</parameter>\n        <parameter key=\"rms_push_notifications.ios.class\">RMS\\PushNotificationsBundle\\Service\\OS\\AppleNotification</parameter>\n        <parameter key=\"rms_push_notifications.ios.feedback.class\">RMS\\PushNotificationsBundle\\Service\\iOSFeedback</parameter>\n        <parameter key=\"rms_push_notifications.mac.class\">RMS\\PushNotificationsBundle\\Service\\OS\\AppleNotification</parameter>\n        <parameter key=\"rms_push_notifications.event_listener.class\">RMS\\PushNotificationsBundle\\Service\\EventListener</parameter>\n    </parameters>\n\n    <services>\n\n        <!-- main notification service -->\n        <service id=\"rms_push_notifications\" class=\"%rms_push_notifications.class%\">\n        </service>\n\n        <service id=\"rms_push_notifications.event_listener\" class=\"%rms_push_notifications.event_listener.class%\">\n            <tag name=\"kernel.event_listener\" event=\"kernel.terminate\" method=\"onKernelTerminate\" />\n        </service>\n    </services>\n\n</container>\n"
  },
  {
    "path": "Resources/config/windowsphone.xml",
    "content": "<?xml version=\"1.0\" ?>\n<container xmlns=\"http://symfony.com/schema/dic/services\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd\">\n\n    <parameters>\n        <parameter key=\"rms_push_notifications.windowsphone.class\">RMS\\PushNotificationsBundle\\Service\\OS\\MicrosoftNotification</parameter>\n    </parameters>\n\n    <services>\n\n        <!-- Windows Phone -->\n        <service id=\"rms_push_notifications.windowsphone\" class=\"%rms_push_notifications.windowsphone.class%\" public=\"false\">\n            <argument>%rms_push_notifications.windowsphone.timeout%</argument>\n            <argument type=\"service\" id=\"logger\" />\n            <tag name=\"rms_push_notifications.handler\" osType=\"rms_push_notifications.os.windowsphone\" />\n        </service>\n\n    </services>\n\n</container>\n"
  },
  {
    "path": "Service/EventListener.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service;\n\n\nclass EventListener {\n\n    /**\n     * @var EventListenerInterface[]\n     */\n    protected $listeners = array();\n\n    /**\n     * @param EventListenerInterface $listener\n     */\n    public function addListener (EventListenerInterface $listener) {\n        $this->listeners[] = $listener;\n    }\n\n    /**\n     * Call onKernelTerminate on every listener\n     */\n    public function onKernelTerminate () {\n        foreach ($this->listeners as $listener) {\n            $listener->onKernelTerminate();\n        }\n    }\n}\n"
  },
  {
    "path": "Service/EventListenerInterface.php",
    "content": "<?php\nnamespace RMS\\PushNotificationsBundle\\Service;\n\n\ninterface EventListenerInterface {\n\n    public function onKernelTerminate ();\n}"
  },
  {
    "path": "Service/Notifications.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types;\nuse RMS\\PushNotificationsBundle\\Message\\MessageInterface;\nuse RMS\\PushNotificationsBundle\\Service\\OS\\AppleNotification;\n\nclass Notifications\n{\n    /**\n     * Array of handlers\n     *\n     * @var array\n     */\n    protected $handlers = array();\n\n    /**\n     * Constructor\n     */\n    public function __construct()\n    {\n    }\n\n    /**\n     * Sends a message to a device, identified by\n     * the OS and the supplied device token\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\MessageInterface $message\n     * @throws \\RuntimeException\n     * @return bool\n     */\n    public function send(MessageInterface $message)\n    {\n        if (!$this->supports($message->getTargetOS())) {\n            throw new \\RuntimeException(\"OS type {$message->getTargetOS()} not supported\");\n        }\n\n        return $this->handlers[$message->getTargetOS()]->send($message);\n    }\n\n    /**\n     * Adds a handler\n     *\n     * @param $osType\n     * @param $service\n     */\n    public function addHandler($osType, $service)\n    {\n        if (!isset($this->handlers[$osType])) {\n            $this->handlers[$osType] = $service;\n        }\n    }\n\n    /**\n     * Get responses from handler\n     *\n     * @param  string            $osType\n     * @return array\n     * @throws \\RuntimeException\n     */\n    public function getResponses($osType)\n    {\n        if (!isset($this->handlers[$osType])) {\n            throw new \\RuntimeException(\"OS type {$osType} not supported\");\n        }\n\n        if (!method_exists($this->handlers[$osType], 'getResponses')) {\n            throw new \\RuntimeException(\"Handler for OS type {$osType} not supported getResponses() method\");\n        }\n\n        return $this->handlers[$osType]->getResponses();\n    }\n\n    /**\n     * Check if target OS is supported\n     *\n     * @param $targetOS\n     *\n     * @return bool\n     */\n    public function supports($targetOS)\n    {\n        return isset($this->handlers[$targetOS]);\n    }\n\n\n    /**\n     * Set Apple Push Notification Service pem as string.\n     * Service won't use pem file passed by config anymore.\n     *\n     * @param $pemContent string\n     * @param $passphrase\n     */\n    public function setAPNSPemAsString($pemContent, $passphrase) {\n        if (isset($this->handlers[Types::OS_IOS]) && $this->handlers[Types::OS_IOS] instanceof AppleNotification) {\n            /** @var AppleNotification $appleNotification */\n            $appleNotification = $this->handlers[Types::OS_IOS];\n            $appleNotification->setPemAsString($pemContent, $passphrase);\n        }\n    }\n}\n"
  },
  {
    "path": "Service/OS/AndroidGCMNotification.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service\\OS;\n\nuse Psr\\Log\\LoggerInterface;\nuse RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException,\n    RMS\\PushNotificationsBundle\\Message\\AndroidMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\nuse Buzz\\Browser,\n    Buzz\\Client\\AbstractCurl,\n    Buzz\\Client\\Curl,\n    Buzz\\Client\\MultiCurl;\n\nclass AndroidGCMNotification implements OSNotificationServiceInterface\n{\n\n    /**\n     * Whether or not to use the dry run GCM\n     *\n     * @var bool\n     */\n    protected $useDryRun = false;\n\n    /**\n     * GCM endpoint\n     *\n     * @var string\n     */\n    protected $apiURL = \"https://android.googleapis.com/gcm/send\";\n\n    /**\n     * Google GCM API key\n     *\n     * @var string\n     */\n    protected $apiKey;\n\n    /**\n     * Max registration count\n     *\n     * @var integer\n     */\n    protected $registrationIdMaxCount = 1000;\n\n    /**\n     * Browser object\n     *\n     * @var \\Buzz\\Browser\n     */\n    protected $browser;\n\n    /**\n     * Collection of the responses from the GCM communication\n     *\n     * @var array\n     */\n    protected $responses;\n\n    /**\n     * Monolog logger\n     *\n     * @var LoggerInterface\n     */\n    protected $logger;\n\n    /**\n     * Constructor\n     *\n     * @param string       $apiKey\n     * @param bool         $useMultiCurl\n     * @param int          $timeout\n     * @param LoggerInterface $logger\n     * @param AbstractCurl $client (optional)\n     * @param bool         $dryRun\n     */\n    public function __construct($apiKey, $useMultiCurl, $timeout, $logger, AbstractCurl $client = null, $dryRun = false)\n    {\n        $this->useDryRun = $dryRun;\n        $this->apiKey = $apiKey;\n        if (!$client) {\n            $client = ($useMultiCurl ? new MultiCurl() : new Curl());\n        }\n        $client->setTimeout($timeout);\n\n        $this->browser = new Browser($client);\n        $this->browser->getClient()->setVerifyPeer(false);\n        $this->logger = $logger;\n    }\n\n    /**\n     * Sends the data to the given registration IDs via the GCM server\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\MessageInterface              $message\n     * @throws \\RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException\n     * @return bool\n     */\n    public function send(MessageInterface $message)\n    {\n        if (!$message instanceof AndroidMessage) {\n            throw new InvalidMessageTypeException(sprintf(\"Message type '%s' not supported by GCM\", get_class($message)));\n        }\n        if (!$message->isGCM()) {\n            throw new InvalidMessageTypeException(\"Non-GCM messages not supported by the Android GCM sender\");\n        }\n\n        $headers = array(\n            \"Authorization: key=\" . $this->apiKey,\n            \"Content-Type: application/json\",\n        );\n        $data = array_merge(\n            $message->getGCMOptions(),\n            array(\"data\" => $message->getData())\n        );\n\n        if ($this->useDryRun) {\n            $data['dry_run'] = true;\n        }\n\n        // Perform the calls (in parallel)\n        $this->responses = array();\n        $gcmIdentifiers = $message->getGCMIdentifiers();\n\n        if (count($message->getGCMIdentifiers()) == 1) {\n            $data['to'] = $gcmIdentifiers[0];\n            $this->responses[] = $this->browser->post($this->apiURL, $headers, json_encode($data));\n        } else {\n            // Chunk number of registration IDs according to the maximum allowed by GCM\n            $chunks = array_chunk($message->getGCMIdentifiers(), $this->registrationIdMaxCount);\n\n            foreach ($chunks as $registrationIDs) {\n                $data['registration_ids'] = $registrationIDs;\n                $this->responses[] = $this->browser->post($this->apiURL, $headers, json_encode($data));\n            }\n        }\n\n        // If we're using multiple concurrent connections via MultiCurl\n        // then we should flush all requests\n        if ($this->browser->getClient() instanceof MultiCurl) {\n            $this->browser->getClient()->flush();\n        }\n\n        // Determine success\n        foreach ($this->responses as $response) {\n            $message = json_decode($response->getContent());\n            if ($message === null || $message->success == 0 || $message->failure > 0) {\n                if ($message == null) {\n                    $this->logger->error($response->getContent());\n                } else {\n                    foreach ($message->results as $result) {\n                        if (isset($result->error)) {\n                            $this->logger->error($result->error);\n                        }\n                    }\n                }\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Returns responses\n     *\n     * @return array\n     */\n    public function getResponses()\n    {\n        return $this->responses;\n    }\n}\n"
  },
  {
    "path": "Service/OS/AndroidNotification.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service\\OS;\n\nuse RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException,\n    RMS\\PushNotificationsBundle\\Message\\AndroidMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\nuse Buzz\\Browser;\n\nclass AndroidNotification implements OSNotificationServiceInterface\n{\n    /**\n     * Username for auth\n     *\n     * @var string\n     */\n    protected $username;\n\n    /**\n     * Password for auth\n     *\n     * @var string\n     */\n    protected $password;\n\n    /**\n     * The source of the notification\n     * eg com.example.myapp\n     *\n     * @var string\n     */\n    protected $source;\n\n    /**\n     * Timeout in seconds for the connecting client\n     *\n     * @var int\n     */\n    protected $timeout;\n\n    /**\n     * Authentication token\n     *\n     * @var string\n     */\n    protected $authToken;\n\n    /**\n     * Constructor\n     *\n     * @param $username\n     * @param $password\n     * @param $source\n     * @param $timeout\n     */\n    public function __construct($username, $password, $source, $timeout)\n    {\n        $this->username = $username;\n        $this->password = $password;\n        $this->source = $source;\n        $this->timeout = $timeout;\n        $this->authToken = \"\";\n    }\n\n    /**\n     * Sends a C2DM message\n     * This assumes that a valid auth token can be obtained\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\MessageInterface              $message\n     * @throws \\RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException\n     * @return bool\n     */\n    public function send(MessageInterface $message)\n    {\n        if (!$message instanceof AndroidMessage) {\n            throw new InvalidMessageTypeException(sprintf(\"Message type '%s' not supported by C2DM\", get_class($message)));\n        }\n\n        if ($this->getAuthToken()) {\n            $headers[] = \"Authorization: GoogleLogin auth=\" . $this->authToken;\n            $data = $message->getMessageBody();\n\n            $buzz = new Browser();\n            $buzz->getClient()->setVerifyPeer(false);\n            $buzz->getClient()->setTimeout($this->timeout);\n            $response = $buzz->post(\"https://android.apis.google.com/c2dm/send\", $headers, http_build_query($data));\n\n            return preg_match(\"/^id=/\", $response->getContent()) > 0;\n        }\n\n        return false;\n    }\n\n    /**\n     * Gets a valid authentication token\n     *\n     * @return bool\n     */\n    protected function getAuthToken()\n    {\n        $data = array(\n            \"Email\"         => $this->username,\n            \"Passwd\"        => $this->password,\n            \"accountType\"   => \"HOSTED_OR_GOOGLE\",\n            \"source\"        => $this->source,\n            \"service\"       => \"ac2dm\"\n        );\n\n        $buzz = new Browser();\n        $buzz->getClient()->setVerifyPeer(false);\n        $buzz->getClient()->setTimeout($this->timeout);\n        $response = $buzz->post(\"https://www.google.com/accounts/ClientLogin\", array(), http_build_query($data));\n        if ($response->getStatusCode() !== 200) {\n            return false;\n        }\n\n        preg_match(\"/Auth=([a-z0-9_\\-]+)/i\", $response->getContent(), $matches);\n        $this->authToken = $matches[1];\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "Service/OS/AppleNotification.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service\\OS;\n\nuse Psr\\Log\\LoggerInterface;\nuse RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException,\n    RMS\\PushNotificationsBundle\\Message\\AppleMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface,\n    Symfony\\Component\\Filesystem\\Filesystem,\n    RMS\\PushNotificationsBundle\\Service\\EventListenerInterface;\nuse RMS\\PushNotificationsBundle\\Service\\EventListener;\n\nclass AppleNotification implements OSNotificationServiceInterface, EventListenerInterface\n{\n\n    /**\n     * Whether or not to use the sandbox APNS\n     *\n     * @var bool\n     */\n    protected $useSandbox = false;\n\n    /**\n     * Path to PEM file\n     *\n     * @var string\n     */\n    protected $pemPath;\n\n    /**\n     * Passphrase for PEM file\n     *\n     * @var string\n     */\n    protected $passphrase;\n\n    /**\n     * Content of PEM\n     *\n     * @var string\n     */\n    protected $pemContent;\n\n    /**\n     * Passphrase for PEM content\n     *\n     * @var string\n     */\n    protected $pemContentPassphrase;\n\n    /**\n     * Array for streams to APN\n     *\n     * @var array\n     */\n    protected $apnStreams;\n\n    /**\n     * Array for messages to APN\n     *\n     * @var array\n     */\n    protected $messages;\n\n    /**\n     * Last used message ID\n     *\n     * @var int\n     */\n    protected $lastMessageId;\n\n    /**\n     * JSON_UNESCAPED_UNICODE\n     *\n     * @var boolean\n     */\n    protected $jsonUnescapedUnicode = FALSE;\n\n    /**\n     * Connection timeout\n     *\n     * @var int\n     */\n    protected $timeout;\n\n    /**\n     * Collection of the responses from the APN\n     *\n     * @var array\n     */\n    protected $responses = array();\n\n    /**\n     * Cache dir used for cache pem file\n     *\n     * @var string\n     */\n    protected $cachedir;\n\n    /**\n     * Monolog logger\n     *\n     * @var LoggerInterface\n     */\n    protected $logger;\n\n    /**\n     * Cache pem filename\n     */\n    const APNS_CERTIFICATE_FILE = '/rms_push_notifications/apns.pem';\n\n    /**\n     * Status code retrieve when APNS server closed the connection\n     */\n    const APNS_SHUTDOWN_CODE = 10;\n\n    /**\n     * Constructor\n     *\n     * @param bool          $sandbox\n     * @param string        $pem\n     * @param string        $passphrase\n     * @param bool          $jsonUnescapedUnicode\n     * @param int           $timeout\n     * @param string        $cachedir\n     * @param EventListener $eventListener\n     * @param LoggerInterface $logger\n     */\n    public function __construct($sandbox, $pem, $passphrase = \"\", $jsonUnescapedUnicode = FALSE, $timeout = 60, $cachedir = \"\", EventListener $eventListener = null, $logger = null)\n    {\n        $this->useSandbox = $sandbox;\n        $this->pemPath = $pem;\n        $this->passphrase = $passphrase;\n        $this->apnStreams = array();\n        $this->messages = array();\n        $this->lastMessageId = -1;\n        $this->jsonUnescapedUnicode = $jsonUnescapedUnicode;\n        $this->timeout = $timeout;\n        $this->cachedir = $cachedir;\n        $this->logger = $logger;\n\n        if ($eventListener != null) {\n            $eventListener->addListener($this);\n        }\n    }\n\n    /**\n     * Set option JSON_UNESCAPED_UNICODE to json encoders\n     *\n     * @param boolean $jsonUnescapedUnicode\n     */\n    public function setJsonUnescapedUnicode($jsonUnescapedUnicode)\n    {\n        $this->jsonUnescapedUnicode = (bool) $jsonUnescapedUnicode;\n\n        return $this;\n    }\n\n    /**\n     * Send a MDM or notification message\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\MessageInterface|\\RMS\\PushNotificationsBundle\\Service\\OS\\MessageInterface $message\n     * @throws \\RuntimeException\n     * @throws \\RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException\n     * @return bool\n     */\n    public function send(MessageInterface $message)\n    {\n        if (!$message instanceof AppleMessage) {\n            throw new InvalidMessageTypeException(sprintf(\"Message type '%s' not supported by APN\", get_class($message)));\n        }\n\n        $apnURL = \"ssl://gateway.push.apple.com:2195\";\n        if ($this->useSandbox) {\n            $apnURL = \"ssl://gateway.sandbox.push.apple.com:2195\";\n        }\n\n        $messageId = ++$this->lastMessageId;\n\n        if ($message->isMdmMessage()) {\n            if ($message->getToken() == '') {\n                throw new InvalidMessageTypeException(sprintf(\"Message type '%s' is a MDM message but 'token' is missing\", get_class($message)));\n            }\n\n            if ($message->getPushMagicToken() == '') {\n                throw new InvalidMessageTypeException(sprintf(\"Message type '%s' is a MDM message but 'pushMagicToken' is missing\", get_class($message)));\n            }\n\n            $this->messages[$messageId] = $this->createMdmPayload($message->getToken(), $message->getPushMagicToken());\n        } else {\n            $this->messages[$messageId] = $this->createPayload($messageId, $message->getExpiry(), $message->getDeviceIdentifier(), $message->getMessageBody());\n        }\n\n        $errors = $this->sendMessages($messageId, $apnURL);\n\n        return !$errors;\n    }\n\n    /**\n     * Send all notification messages starting from the given ID\n     *\n     * @param  int                                                                $firstMessageId\n     * @param  string                                                             $apnURL\n     * @throws \\RuntimeException\n     * @throws \\RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException\n     * @return int\n     */\n    protected function sendMessages($firstMessageId, $apnURL)\n    {\n        $errors = array();\n        // Loop through all messages starting from the given ID\n        $messagesCount = count($this->messages);\n        for ($currentMessageId = $firstMessageId; $currentMessageId < $messagesCount; $currentMessageId++) {\n            // Send the message\n            $result = $this->writeApnStream($apnURL, $this->messages[$currentMessageId]);\n\n            // Check if there is an error result\n            if (is_array($result)) {\n\n                // Close the apn stream in case of Shutdown status code.\n                if ($result['status'] === self::APNS_SHUTDOWN_CODE) {\n                    $this->closeApnStream($apnURL);\n                }\n\n                $this->responses[] = $result;\n                // Resend all messages that were sent after the failed message\n                $this->sendMessages($result['identifier']+1, $apnURL);\n                $errors[] = $result;\n                if ($this->logger) {\n                    $this->logger->error(json_encode($result));\n                }\n            } else {\n                $this->responses[] = true;\n            }\n        }\n\n        return $errors;\n    }\n\n    /**\n     * Write data to the apn stream that is associated with the given apn URL\n     *\n     * @param  string            $apnURL\n     * @param  string            $payload\n     * @throws \\RuntimeException\n     * @return mixed\n     */\n    protected function writeApnStream($apnURL, $payload)\n    {\n        // Get the correct Apn stream and send data\n        $fp = $this->getApnStream($apnURL);\n        $response = (strlen($payload) === @fwrite($fp, $payload, strlen($payload)));\n\n        // Check if there is responsedata to read\n        $readStreams = array($fp);\n        $null = NULL;\n        $streamsReadyToRead = @stream_select($readStreams, $null, $null, 1, 0);\n        if ($streamsReadyToRead > 0) {\n            // Unpack error response data and set as the result\n            $response = @unpack(\"Ccommand/Cstatus/Nidentifier\", fread($fp, 6));\n            $this->closeApnStream($apnURL);\n        }\n\n        // Will contain true if writing succeeded and no error is returned yet\n        return $response;\n    }\n\n    /**\n     * Get an apn stream associated with the given apn URL, create one if necessary\n     *\n     * @param  string            $apnURL\n     * @throws \\RuntimeException\n     * @return resource\n     */\n    protected function getApnStream($apnURL)\n    {\n        if (!isset($this->apnStreams[$apnURL])) {\n            // No stream found, setup a new stream\n            $ctx = $this->getStreamContext();\n            $this->apnStreams[$apnURL] = stream_socket_client($apnURL, $err, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $ctx);\n            if (!$this->apnStreams[$apnURL]) {\n                throw new \\RuntimeException(\"Couldn't connect to APN server. Error no $err: $errstr\");\n            }\n\n            // Reduce buffering and blocking\n            if (function_exists(\"stream_set_read_buffer\")) {\n                stream_set_read_buffer($this->apnStreams[$apnURL], 6);\n            }\n            stream_set_write_buffer($this->apnStreams[$apnURL], 0);\n            stream_set_blocking($this->apnStreams[$apnURL], 0);\n        }\n\n        return $this->apnStreams[$apnURL];\n    }\n\n    /**\n     * Close the apn stream associated with the given apn URL\n     *\n     * @param string $apnURL\n     */\n    protected function closeApnStream($apnURL)\n    {\n        if (isset($this->apnStreams[$apnURL])) {\n            // Stream found, close the stream\n            fclose($this->apnStreams[$apnURL]);\n            unset($this->apnStreams[$apnURL]);\n        }\n    }\n\n    /**\n     * Gets a stream context set up for SSL\n     * using our PEM file and passphrase\n     *\n     * @return resource\n     */\n    protected function getStreamContext()\n    {\n        $pem = $this->pemPath;\n        $passphrase = $this->passphrase;\n\n        // Create cache pem file if needed\n        if (!empty($this->pemContent)) {\n            $filename = $this->cachedir . self::APNS_CERTIFICATE_FILE;\n\n            $fs = new Filesystem();\n            $fs->mkdir(dirname($filename));\n            file_put_contents($filename, $this->pemContent);\n\n            // Now we use this file as pem\n            $pem = $filename;\n            $passphrase = $this->pemContentPassphrase;\n        }\n\n        $ctx = stream_context_create();\n        stream_context_set_option($ctx, \"ssl\", \"local_cert\", $pem);\n        if (strlen($passphrase)) {\n            stream_context_set_option($ctx, \"ssl\", \"passphrase\", $passphrase);\n        }\n\n        return $ctx;\n    }\n\n    /**\n     * Creates the full payload for the notification\n     *\n     * @param int    $messageId\n     * @param string $expiry\n     * @param string $token\n     * @param array  $message\n     *\n     * @return string\n     *\n     * @throws \\LogicException\n     * @throws \\InvalidArgumentException\n     */\n    protected function createPayload($messageId, $expiry, $token, $message)\n    {\n        if ($this->jsonUnescapedUnicode) {\n            // Validate PHP version\n            if (!version_compare(PHP_VERSION, '5.4.0', '>=')) {\n                throw new \\LogicException(sprintf(\n                    'Can\\'t use JSON_UNESCAPED_UNICODE option on PHP %s. Support PHP >= 5.4.0',\n                    PHP_VERSION\n                ));\n            }\n\n            // WARNING:\n            // Set otpion JSON_UNESCAPED_UNICODE is violation\n            // of RFC 4627\n            // Because required validate charsets (Must be UTF-8)\n\n            $encoding = mb_detect_encoding($message['aps']['alert']);\n            if ($encoding != 'UTF-8' && $encoding != 'ASCII') {\n                throw new \\InvalidArgumentException(sprintf(\n                    'Message must be UTF-8 encoding, \"%s\" given.',\n                    mb_detect_encoding($message['aps']['alert'])\n                ));\n            }\n\n            $jsonBody = json_encode($message, JSON_UNESCAPED_UNICODE);\n        } else {\n            $jsonBody = json_encode($message);\n        }\n\n        $token = preg_replace(\"/[^0-9A-Fa-f]/\", \"\", $token);\n        $payload = chr(1) . pack(\"N\", $messageId) . pack(\"N\", $expiry) . pack(\"n\", 32) . pack(\"H*\", $token) . pack(\"n\", strlen($jsonBody)) . $jsonBody;\n\n        return $payload;\n    }\n\n    /**\n     * Creates a MDM payload\n     *\n     * @param string $token\n     * @param string $magicPushToken\n     *\n     * @return string\n     */\n    public function createMdmPayload($token, $magicPushToken)\n    {\n        $jsonPayload = json_encode(array('mdm' => $magicPushToken));\n\n        $payload = chr(0) . chr(0) . chr(32) . base64_decode($token) . chr(0)  . chr(strlen($jsonPayload)) . $jsonPayload;\n\n        return $payload;\n    }\n\n    /**\n     * Returns responses\n     *\n     * @return array\n     */\n    public function getResponses()\n    {\n        return $this->responses;\n    }\n\n    /**\n     * @param $pemContent\n     * @param $passphrase\n     */\n    public function setPemAsString($pemContent, $passphrase) {\n        if ($this->pemContent === $pemContent && $this->pemContentPassphrase === $passphrase) {\n            return;\n        }\n\n        $this->pemContent = $pemContent;\n        $this->pemContentPassphrase = $passphrase;\n\n        // for new pem will take affect we need to close existing streams which use cached pem\n        $this->closeStreams();\n    }\n\n    /**\n     * Called on kernel terminate\n     */\n    public function onKernelTerminate()\n    {\n        $this->removeCachedPemFile();\n        $this->closeStreams();\n    }\n\n    /**\n     * Remove cache pem file\n     */\n    private function removeCachedPemFile()\n    {\n        $fs = new Filesystem();\n        $filename = $this->cachedir . self::APNS_CERTIFICATE_FILE;\n        if ($fs->exists(dirname($filename))) {\n            $fs->remove(dirname($filename));\n        }\n    }\n\n    /**\n     * Close existing streams\n     */\n    private function closeStreams()\n    {\n        foreach ($this->apnStreams as $stream) {\n            fclose($stream);\n        }\n\n        $this->apnStreams = array();\n    }\n}\n"
  },
  {
    "path": "Service/OS/BlackberryNotification.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service\\OS;\n\nuse Psr\\Log\\LoggerInterface;\nuse RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException,\n    RMS\\PushNotificationsBundle\\Message\\BlackberryMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\nuse Buzz\\Browser,\n    Buzz\\Listener\\BasicAuthListener,\n    Buzz\\Client\\Curl;\n\nclass BlackberryNotification implements OSNotificationServiceInterface\n{\n    /**\n     * Evaluation mode or not\n     *\n     * @var string\n     */\n    protected $evaluation;\n\n    /**\n     * App ID\n     *\n     * @var string\n     */\n    protected $appID;\n\n    /**\n     * Password for auth\n     *\n     * @var string\n     */\n    protected $password;\n\n    /**\n     * Timeout in seconds for the connecting client\n     *\n     * @var int\n     */\n    protected $timeout;\n\n    /**\n     * Monolog logger\n     *\n     * @var LoggerInterface\n     */\n    protected $logger;\n\n    /**\n     * Constructor\n     *\n     * @param $evaluation\n     * @param $appID\n     * @param $password\n     * @param $timeout\n     * @param $logger\n     */\n    public function __construct($evaluation, $appID, $password, $timeout, $logger)\n    {\n        $this->evaluation = $evaluation;\n        $this->appID = $appID;\n        $this->password = $password;\n        $this->timeout = $timeout;\n        $this->logger = $logger;\n    }\n\n    /**\n     * Sends a Blackberry Push message\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\MessageInterface              $message\n     * @throws \\RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException\n     * @return bool\n     */\n    public function send(MessageInterface $message)\n    {\n        if (!$message instanceof BlackberryMessage) {\n            throw new InvalidMessageTypeException(sprintf(\"Message type '%s' not supported by Blackberry\", get_class($message)));\n        }\n\n        return $this->doSend($message);\n    }\n\n    /**\n     * Does the actual sending\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\BlackberryMessage $message\n     * @return bool\n     */\n    protected function doSend(BlackberryMessage $message)\n    {\n        $separator = \"mPsbVQo0a68eIL3OAxnm\";\n        $body = $this->constructMessageBody($message, $separator);\n        $browser = new Browser(new Curl());\n        $browser->getClient()->setTimeout($this->timeout);\n        $listener = new BasicAuthListener($this->appID, $this->password);\n        $browser->addListener($listener);\n\n        $url = \"https://pushapi.na.blackberry.com/mss/PD_pushRequest\";\n        if ($this->evaluation) {\n            $url = \"https://pushapi.eval.blackberry.com/mss/PD_pushRequest\";\n        }\n        $headers = array();\n        $headers[] = \"Content-Type: multipart/related; boundary={$separator}; type=application/xml\";\n        $headers[] = \"Accept: text/html, *\";\n        $headers[] = \"Connection: Keep-Alive\";\n\n        $response = $browser->post($url, $headers, $body);\n\n        return $this->parseResponse($response);\n    }\n\n    /**\n     * Builds the actual body of the message\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\BlackberryMessage $message\n     * @param $separator\n     * @return string\n     */\n    protected function constructMessageBody(BlackberryMessage $message, $separator)\n    {\n        $data = \"\";\n        $messageID = microtime(true);\n\n        $data .= \"--\" . $separator . \"\\r\\n\";\n        $data .= \"Content-Type: application/xml; charset=UTF-8\\r\\n\\r\\n\";\n        $data .= $this->getXMLBody($message, $messageID) . \"\\r\\n\";\n        $data .= \"--\" . $separator . \"\\r\\n\";\n        $data .= \"Content-Type: text/plain\\r\\n\";\n        $data .= \"Push-Message-ID: {$messageID}\\r\\n\\r\\n\";\n        if (is_array($message->getMessageBody())) {\n            $data .= json_encode($message->getMessageBody());\n        } else {\n            $data .= $message->getMessageBody();\n        }\n        $data .= \"\\r\\n\";\n        $data .= \"--\" . $separator . \"--\\r\\n\";\n\n        return $data;\n    }\n\n    /**\n     * Handles and parses the response\n     * Returns a value indicating success/fail\n     *\n     * @param  \\Buzz\\Message\\Response $response\n     * @return bool\n     */\n    protected function parseResponse(\\Buzz\\Message\\Response $response)\n    {\n        if (null !== $response->getStatusCode() && $response->getStatusCode() != 200) {\n            return false;\n        }\n        $doc = new \\DOMDocument();\n        $doc->loadXML($response->getContent());\n        $elems = $doc->getElementsByTagName(\"response-result\");\n        if (!$elems->length) {\n            $this->logger->error('Response is empty');\n            return false;\n        }\n        $responseElement = $elems->item(0);\n        if ($responseElement->getAttribute(\"code\") != \"1001\") {\n            $this->logger->error($responseElement->getAttribute(\"code\"). ' : '. $responseElement->getAttribute(\"desc\"));\n        }\n\n        return ($responseElement->getAttribute(\"code\") == \"1001\");\n    }\n\n    /**\n     * Create the XML body that accompanies the actual push data\n     *\n     * @param $messageID\n     * @return string\n     */\n    private function getXMLBody(BlackberryMessage $message, $messageID)\n    {\n        $deliverBefore = gmdate('Y-m-d\\TH:i:s\\Z', strtotime('+5 minutes'));\n        $impl = new \\DOMImplementation();\n        $dtd = $impl->createDocumentType(\n            \"pap\",\n            \"-//WAPFORUM//DTD PAP 2.1//EN\",\n            \"http://www.openmobilealliance.org/tech/DTD/pap_2.1.dtd\"\n        );\n        $doc = $impl->createDocument(\"\", \"\", $dtd);\n\n        // Build it centre-out\n        $pm = $doc->createElement(\"push-message\");\n        $pm->setAttribute(\"push-id\", $messageID);\n        $pm->setAttribute(\"deliver-before-timestamp\", $deliverBefore);\n        $pm->setAttribute(\"source-reference\", $this->appID);\n\n        $qos = $doc->createElement(\"quality-of-service\");\n        $qos->setAttribute(\"delivery-method\", \"unconfirmed\");\n        $add = $doc->createElement(\"address\");\n        $add->setAttribute(\"address-value\", $message->getDeviceIdentifier());\n\n        $pm->appendChild($add);\n        $pm->appendChild($qos);\n        $pap = $doc->createElement(\"pap\");\n        $pap->appendChild($pm);\n        $doc->appendChild($pap);\n\n        return $doc->saveXML();\n    }\n}\n"
  },
  {
    "path": "Service/OS/MicrosoftNotification.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service\\OS;\n\nuse Psr\\Log\\LoggerInterface;\nuse RMS\\PushNotificationsBundle\\Exception\\InvalidMessageTypeException;\nuse RMS\\PushNotificationsBundle\\Message\\WindowsphoneMessage;\nuse RMS\\PushNotificationsBundle\\Message\\MessageInterface;\nuse Buzz\\Browser,\n    Buzz\\Client\\Curl;\n\nclass MicrosoftNotification implements OSNotificationServiceInterface\n{\n    /**\n     * Browser object\n     *\n     * @var \\Buzz\\Browser\n     */\n    protected $browser;\n\n    /**\n     * Monolog logger\n     *\n     * @var LoggerInterface\n     */\n    protected $logger;\n\n    /**\n     * @param $timeout\n     * @param $logger\n     */\n    public function __construct($timeout, $logger)\n    {\n        $this->browser = new Browser(new Curl());\n        $this->browser->getClient()->setVerifyPeer(false);\n        $this->browser->getClient()->setTimeout($timeout);\n        $this->logger = $logger;\n    }\n\n    public function send(MessageInterface $message)\n    {\n        if (!$message instanceof WindowsphoneMessage) {\n            throw new InvalidMessageTypeException(sprintf(\"Message type '%s' not supported by MPNS\", get_class($message)));\n        }\n\n        $headers = array(\n            'Content-Type: text/xml',\n            'X-WindowsPhone-Target: ' . $message->getTarget(),\n            'X-NotificationClass: ' . $message->getNotificationClass()\n        );\n\n        $xml = new \\SimpleXMLElement('<?xml version=\"1.0\" encoding=\"utf-8\"?><wp:Notification xmlns:wp=\"WPNotification\" />');\n\n        $msgBody = $message->getMessageBody();\n\n        if ($message->getTarget() == WindowsphoneMessage::TYPE_TOAST) {\n            $toast = $xml->addChild('wp:Toast');\n            $toast->addChild('wp:Text1', htmlspecialchars($msgBody['text1'], ENT_XML1|ENT_QUOTES));\n            $toast->addChild('wp:Text2', htmlspecialchars($msgBody['text2'], ENT_XML1|ENT_QUOTES));\n        }\n\n        $response = $this->browser->post($message->getDeviceIdentifier(), $headers, $xml->asXML());\n\n        if (!$response->isSuccessful()) {\n            $this->logger->error($response->getStatusCode(). ' : '. $response->getReasonPhrase());\n        }\n\n        return $response->isSuccessful();\n    }\n}\n"
  },
  {
    "path": "Service/OS/OSNotificationServiceInterface.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service\\OS;\n\nuse RMS\\PushNotificationsBundle\\Message\\MessageInterface;\n\ninterface OSNotificationServiceInterface\n{\n    /**\n     * Send a notification message\n     *\n     * @param  \\RMS\\PushNotificationsBundle\\Message\\MessageInterface $message\n     * @return mixed\n     */\n    public function send(MessageInterface $message);\n}\n"
  },
  {
    "path": "Service/iOSFeedback.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Service;\n\nuse RMS\\PushNotificationsBundle\\Device\\iOS\\Feedback;\n\nclass iOSFeedback\n{\n    /**\n     * Sandbox mode or not\n     *\n     * @var bool\n     */\n    protected $sandbox;\n\n    /**\n     * Path to PEM file\n     *\n     * @var string\n     */\n    protected $pem;\n\n    /**\n     * Passphrase for PEM file\n     *\n     * @var string\n     */\n    protected $passphrase;\n\n    /**\n     * Connection timeout\n     *\n     * @var int\n     */\n    protected $timeout;\n\n    /**\n     * Constructor\n     *\n     * @param $sandbox\n     * @param $pem\n     * @param $passphrase\n     * @param $timeout\n     */\n    public function __construct($sandbox, $pem, $passphrase, $timeout)\n    {\n        $this->sandbox = $sandbox;\n        $this->pem = $pem;\n        $this->passphrase = $passphrase;\n        $this->timeout = $timeout;\n    }\n\n    /**\n     * Gets an array of device UUID unregistration details\n     * from the APN feedback service\n     *\n     * @throws \\RuntimeException\n     * @return array\n     */\n    public function getDeviceUUIDs()\n    {\n        if (!strlen($this->pem)) {\n            throw new \\RuntimeException(\"PEM not provided\");\n        }\n\n        $feedbackURL = \"ssl://feedback.push.apple.com:2196\";\n        if ($this->sandbox) {\n            $feedbackURL = \"ssl://feedback.sandbox.push.apple.com:2196\";\n        }\n        $data = \"\";\n\n        $ctx = $this->getStreamContext();\n        $fp = stream_socket_client($feedbackURL, $err, $errstr, $this->timeout, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);\n        if (!$fp) {\n            throw new \\RuntimeException(\"Couldn't connect to APNS Feedback service. Error no $err: $errstr\");\n        }\n        while (!feof($fp)) {\n            $data .= fread($fp, 4096);\n        }\n        fclose($fp);\n        if (!strlen($data)) {\n            return array();\n        }\n\n        $feedbacks = array();\n        $items = str_split($data, 38);\n        foreach ($items as $item) {\n            $feedback = new Feedback();\n            $feedbacks[] = $feedback->unpack($item);\n        }\n\n        return $feedbacks;\n    }\n\n    /**\n     * Gets a stream context set up for SSL\n     * using our PEM file and passphrase\n     *\n     * @return resource\n     */\n    protected function getStreamContext()\n    {\n        $ctx = stream_context_create();\n\n        stream_context_set_option($ctx, \"ssl\", \"local_cert\", $this->pem);\n        if (strlen($this->passphrase)) {\n            stream_context_set_option($ctx, \"ssl\", \"passphrase\", $this->passphrase);\n        }\n\n        return $ctx;\n    }\n\n}\n"
  },
  {
    "path": "Tests/DependencyInjection/ConfigurationTest.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Tests\\DependencyInjection;\n\nuse RMS\\PushNotificationsBundle\\DependencyInjection\\Configuration;\nuse Symfony\\Component\\Config\\Definition\\Processor;\n\nclass ConfigurationTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testDefaults()\n    {\n        $config = $this->process(array());\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testAddingAndroidKeyRequiresValues()\n    {\n        $arr = array(\n            array(\"android\" => \"~\"),\n        );\n        $config = $this->process($arr);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testAndroidRequiresUsername()\n    {\n        $arr = array(\n            array(\n                \"android\" => array(\"c2dm\" => array(\"password\" => \"foo\"))\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testAndroidRequiresPassword()\n    {\n        $arr = array(\n            array(\n                \"android\" => array(\"c2dm\" => array(\"username\" => \"foo\"))\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    public function testOldFullAndroid()\n    {\n        // NB - this is the deprecated version\n        $arr = array(\n            array(\n                \"android\" => array(\"username\" => \"foo\", \"password\" => \"bar\", \"source\" => \"123\")\n            ),\n        );\n        $config = $this->process($arr);\n        $this->assertArrayHasKey(\"android\", $config);\n        $this->assertEquals(5, $config[\"android\"][\"timeout\"]);\n        $this->assertEquals(\"foo\", $config[\"android\"][\"username\"]);\n        $this->assertEquals(\"bar\", $config[\"android\"][\"password\"]);\n        $this->assertEquals(\"123\", $config[\"android\"][\"source\"]);\n    }\n\n    public function testNewC2DMIsAllowedWithoutOldBits()\n    {\n        $arr = array(\n            array(\n                \"android\" => array(\n                    \"c2dm\" => array(\n                        \"username\" => \"foo\",\n                        \"password\" => \"bar\",\n                        \"source\" => \"123\"\n                    )\n                )\n            ),\n        );\n        $config = $this->process($arr);\n        $this->assertArrayHasKey(\"android\", $config);\n        $this->assertEquals(5, $config[\"android\"][\"timeout\"]);\n        $this->assertArrayHasKey(\"c2dm\", $config[\"android\"]);\n        $this->assertEquals(\"foo\", $config[\"android\"][\"c2dm\"][\"username\"]);\n        $this->assertEquals(\"bar\", $config[\"android\"][\"c2dm\"][\"password\"]);\n        $this->assertEquals(\"123\", $config[\"android\"][\"c2dm\"][\"source\"]);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testGCMRequiresAPIKey()\n    {\n        $arr = array(\n            array(\n                \"android\" => array(\n                    \"gcm\" => array(\n                    )\n                )\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    public function testGCMIsOK()\n    {\n        $arr = array(\n            array(\n                \"android\" => array(\n                    \"gcm\" => array(\n                        \"api_key\" => \"foo\",\n                        \"use_multi_curl\" => true,\n                        \"dry_run\" => false,\n                    )\n                )\n            ),\n        );\n        $config = $this->process($arr);\n        $this->assertEquals(\"foo\", $config[\"android\"][\"gcm\"][\"api_key\"]);\n        $this->assertFalse($config[\"android\"][\"gcm\"][\"dry_run\"]);\n\n        $arr[0][\"android\"][\"gcm\"][\"dry_run\"] = true;\n        $config = $this->process($arr);\n        $this->assertTrue($config[\"android\"][\"gcm\"][\"dry_run\"]);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testAddingiOsKeyRequiresValues()\n    {\n        $arr = array(\n            array(\"ios\" => \"~\"),\n        );\n        $config = $this->process($arr);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testiOSRequiresPEM()\n    {\n        $arr = array(\n            array(\n                \"ios\" => array(\"pem\" => \"\")\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    public function testFulliOS()\n    {\n        $arr = array(\n            array(\n                \"ios\" => array(\"sandbox\" => false, \"pem\" => \"foo/bar.pem\", \"passphrase\" => \"foo\")\n            ),\n        );\n        $config = $this->process($arr);\n        $this->assertArrayHasKey(\"ios\", $config);\n        $this->assertEquals(60, $config[\"ios\"][\"timeout\"]);\n        $this->assertEquals(false, $config[\"ios\"][\"sandbox\"]);\n        $this->assertEquals(\"foo/bar.pem\", $config[\"ios\"][\"pem\"]);\n        $this->assertEquals(\"foo\", $config[\"ios\"][\"passphrase\"]);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testAddingMacKeyRequiresValues()\n    {\n        $arr = array(\n            array(\"mac\" => \"~\"),\n        );\n        $config = $this->process($arr);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testMacRequiresPEM()\n    {\n        $arr = array(\n            array(\n                \"mac\" => array(\"pem\" => \"\")\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    public function testFullMac()\n    {\n        $arr = array(\n            array(\n                \"mac\" => array(\"sandbox\" => false, \"pem\" => \"foo/bar.pem\", \"passphrase\" => \"foo\")\n            ),\n        );\n        $config = $this->process($arr);\n        $this->assertArrayHasKey(\"mac\", $config);\n        $this->assertEquals(60, $config[\"mac\"][\"timeout\"]);\n        $this->assertEquals(false, $config[\"mac\"][\"sandbox\"]);\n        $this->assertEquals(\"foo/bar.pem\", $config[\"mac\"][\"pem\"]);\n        $this->assertEquals(\"foo\", $config[\"mac\"][\"passphrase\"]);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testBlackberryRequiresAppID()\n    {\n        $arr = array(\n            array(\n                \"blackberry\" => array(\"password\" => \"foo\")\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testBlackberryRequiresPassword()\n    {\n        $arr = array(\n            array(\n                \"blackberry\" => array(\"app_id\" => \"foo\")\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    public function testFullBlackberry()\n    {\n        $arr = array(\n            array(\n                \"blackberry\" => array(\"evaluation\" => false, \"app_id\" => \"foo\", \"password\" => \"bar\")\n            ),\n        );\n        $config = $this->process($arr);\n        $this->assertArrayHasKey(\"blackberry\", $config);\n        $this->assertFalse($config[\"blackberry\"][\"evaluation\"]);\n        $this->assertEquals(5, $config[\"blackberry\"][\"timeout\"]);\n        $this->assertEquals(\"foo\", $config[\"blackberry\"][\"app_id\"]);\n        $this->assertEquals(\"bar\", $config[\"blackberry\"][\"password\"]);\n    }\n\n    /**\n     * @expectedException \\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException\n     */\n    public function testAddingWindowsKeyRequiresValues()\n    {\n        $arr = array(\n            array(\n                \"windowsphone\" => \"~\"\n            ),\n        );\n        $config = $this->process($arr);\n    }\n\n    public function testFullWindows()\n    {\n        $arr = array(\n            array(\n                \"windowsphone\" => array(\"timeout\" => 5)\n            ),\n        );\n        $config = $this->process($arr);\n        $this->assertArrayHasKey(\"windowsphone\", $config);\n        $this->assertEquals(5, $config[\"windowsphone\"][\"timeout\"]);\n    }\n\n    /**\n     * Takes in an array of configuration values and returns the processed version\n     *\n     * @param  array $config\n     * @return array\n     */\n    protected function process($config)\n    {\n        $processor = new Processor();\n\n        return $processor->processConfiguration(new Configuration(), $config);\n    }\n}\n"
  },
  {
    "path": "Tests/Message/AndroidMessageTest.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Tests\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types,\n    RMS\\PushNotificationsBundle\\Message\\AndroidMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\n\nclass AndroidMessageTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testCreation()\n    {\n        $msg = new AndroidMessage();\n        $this->assertInstanceOf(\"RMS\\PushNotificationsBundle\\Message\\MessageInterface\", $msg);\n        $this->assertEquals(Types::OS_ANDROID_C2DM, $msg->getTargetOS());\n    }\n\n    public function testCoreBodyGeneratedOK()\n    {\n        $expected = array(\n            \"registration_id\" => \"\",\n            \"collapse_key\"    => AndroidMessage::DEFAULT_COLLAPSE_KEY,\n            \"data.message\"    => \"\",\n        );\n        $msg = new AndroidMessage();\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testMessageAddedOK()\n    {\n        $expected = array(\n            \"registration_id\" => \"\",\n            \"collapse_key\"    => AndroidMessage::DEFAULT_COLLAPSE_KEY,\n            \"data.message\"    => \"Foo\",\n        );\n        $msg = new AndroidMessage();\n        $msg->setMessage(\"Foo\");\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testNewCollapseKey()\n    {\n        $expected = array(\n            \"registration_id\" => \"\",\n            \"collapse_key\"    => 123,\n            \"data.message\"    => \"\",\n        );\n        $msg = new AndroidMessage();\n        $msg->setCollapseKey(123);\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testRegistrationIDAddedToBody()\n    {\n        $expected = array(\n            \"registration_id\" => \"ABC123\",\n            \"collapse_key\"    => AndroidMessage::DEFAULT_COLLAPSE_KEY,\n            \"data.message\"    => \"\",\n        );\n        $msg = new AndroidMessage();\n        $msg->setDeviceIdentifier(\"ABC123\");\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testCustomData()\n    {\n        $expected = array(\n            \"registration_id\" => \"\",\n            \"collapse_key\"    => AndroidMessage::DEFAULT_COLLAPSE_KEY,\n            \"data.message\"    => \"\",\n            \"custom\"          => array(\"foo\" => \"bar\"),\n        );\n        $msg = new AndroidMessage();\n        $msg->setData(array(\"custom\" => array(\"foo\" => \"bar\")));\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testTypeChangesBasedOnGCM()\n    {\n        $msg = new AndroidMessage();\n        $this->assertEquals(Types::OS_ANDROID_C2DM, $msg->getTargetOS());\n        $msg->setGCM(true);\n        $this->assertEquals(Types::OS_ANDROID_GCM, $msg->getTargetOS());\n    }\n\n    public function testSetIdentifierIsSingleEntryInGCMArray()\n    {\n        $msg = new AndroidMessage();\n        $msg->setDeviceIdentifier(\"foo\");\n        $this->assertCount(1, $msg->getGCMIdentifiers());\n    }\n\n    public function testAddingGCMIdentifiers()\n    {\n        $msg = new AndroidMessage();\n        $msg->addGCMIdentifier(\"foo\");\n        $msg->addGCMIdentifier(\"bar\");\n        $this->assertCount(2, $msg->getGCMIdentifiers());\n    }\n\n    public function testSetMessageIsReturnedInGetData()\n    {\n        $msg = new AndroidMessage();\n        $message = 'Test message';\n        $msg->setMessage($message);\n        $this->assertEquals(array('message' => $message), $msg->getData());\n\n        $msg->setData(array('id' => 10));\n        $this->assertEquals(array('id' => 10, 'message' => $message), $msg->getData());\n\n        $msg->setData(array('message' => 'Other message'));\n        $this->assertEquals(array('message' => 'Other message'), $msg->getData());\n    }\n}\n"
  },
  {
    "path": "Tests/Message/BlackberryMessageTest.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Tests\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types,\n    RMS\\PushNotificationsBundle\\Message\\BlackberryMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\n\nclass BlackberryMessageTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testCreation()\n    {\n        $msg = new BlackberryMessage();\n        $this->assertInstanceOf(\"RMS\\PushNotificationsBundle\\Message\\MessageInterface\", $msg);\n        $this->assertEquals(Types::OS_BLACKBERRY, $msg->getTargetOS());\n    }\n\n    public function testDefaultBody()\n    {\n        $expected = null;\n        $msg = new BlackberryMessage();\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testSettingBody()\n    {\n        $expected = \"Foo\";\n        $msg = new BlackberryMessage();\n        $msg->setMessage(\"Foo\");\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n}\n"
  },
  {
    "path": "Tests/Message/MacMessageTest.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Tests\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types,\n    RMS\\PushNotificationsBundle\\Message\\MacMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\n\nclass MacMessageTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testCreation()\n    {\n        $msg = new MacMessage();\n        $this->assertInstanceOf(\"RMS\\PushNotificationsBundle\\Message\\MessageInterface\", $msg);\n        $this->assertEquals(Types::OS_MAC, $msg->getTargetOS());\n    }\n}\n"
  },
  {
    "path": "Tests/Message/WindowsphoneMessageTest.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Tests\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types,\n    RMS\\PushNotificationsBundle\\Message\\WindowsphoneMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\n\nclass WindowsphoneMessageTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testCreation()\n    {\n        $msg = new WindowsphoneMessage();\n        $this->assertInstanceOf(\"RMS\\PushNotificationsBundle\\Message\\MessageInterface\", $msg);\n        $this->assertEquals(Types::OS_WINDOWSPHONE, $msg->getTargetOS());\n    }\n\n    public function testDefaultBody()\n    {\n        $msg = new WindowsphoneMessage();\n        $this->assertArrayHasKey(\"text1\", $msg->getMessageBody());\n        $this->assertArrayHasKey(\"text2\", $msg->getMessageBody());\n    }\n\n    public function testSettingBody()\n    {\n        $expected = \"Foo\";\n        $msg = new WindowsphoneMessage();\n        $msg->setMessage(\"Foo\");\n        $msgBody = $msg->getMessageBody();\n        $this->assertEquals($expected, $msgBody['text2']);\n    }\n\n    public function testDefaultTarget()\n    {\n        $msg = new WindowsphoneMessage();\n        $this->assertEquals(WindowsphoneMessage::TYPE_TOAST, $msg->getTarget());\n    }\n}\n"
  },
  {
    "path": "Tests/Message/iOSMessageTest.php",
    "content": "<?php\n\nnamespace RMS\\PushNotificationsBundle\\Tests\\Message;\n\nuse RMS\\PushNotificationsBundle\\Device\\Types,\n    RMS\\PushNotificationsBundle\\Message\\iOSMessage,\n    RMS\\PushNotificationsBundle\\Message\\MessageInterface;\n\nclass iOSMessageTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testCreation()\n    {\n        $msg = new iOSMessage();\n        $this->assertInstanceOf(\"RMS\\PushNotificationsBundle\\Message\\MessageInterface\", $msg);\n        $this->assertEquals(Types::OS_IOS, $msg->getTargetOS());\n    }\n\n    public function testCoreBodyGeneratedOK()\n    {\n        $expected = array(\n            \"aps\" => array(),\n        );\n        $msg = new iOSMessage();\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testAPSAlertAddedOK()\n    {\n        $expected = array(\n            \"aps\" => array(\n                \"alert\" => \"Foo\",\n            ),\n        );\n        $msg = new iOSMessage();\n        $msg->setMessage(\"Foo\");\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testAPSBadgeAddedOK()\n    {\n        $expected = array(\n            \"aps\" => array(\n                \"badge\" => 1,\n            ),\n        );\n        $msg = new iOSMessage();\n        $msg->setAPSBadge(1);\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testAPSSoundAddedOK()\n    {\n        $expected = array(\n            \"aps\" => array(\n                \"sound\" => \"default\",\n            ),\n        );\n        $msg = new iOSMessage();\n        $msg->setAPSSound(\"default\");\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testCustomDataAddedOK()\n    {\n        $expected = array(\n            \"aps\" => array(),\n            \"custom\" => array(\"foo\" => \"bar\"),\n        );\n        $msg = new iOSMessage();\n        $msg->setData(array(\"custom\" => array(\"foo\" => \"bar\")));\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n    public function testMutableContentAddOk()\n    {\n        $expected = array(\n            \"aps\" => array(\n                \"mutable-content\" => 1,\n            ),\n        );\n        $msg = new iOSMessage();\n        $msg->setMutableContent(true);\n        $this->assertEquals($expected, $msg->getMessageBody());\n    }\n\n}\n"
  },
  {
    "path": "Tests/autoload.php.dist",
    "content": "<?php\n\n$vendorDir = __DIR__ . '/../vendor';\n \nif (!@include($vendorDir . '/autoload.php')) {\n    die(\"You must set up the project dependencies, run the following commands:\nwget http://getcomposer.org/composer.phar\nphp composer.phar install\n    \");\n}\n"
  },
  {
    "path": "Tests/bootstrap.php",
    "content": "<?php\n\nif (file_exists($file = __DIR__.'/autoload.php')) {\n    require_once $file;\n} elseif (file_exists($file = __DIR__.'/autoload.php.dist')) {\n    require_once $file;\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"richsage/rms-push-notifications-bundle\",\n    \"type\": \"symfony-bundle\",\n    \"description\": \"Push notifications/messages for mobile devices\",\n    \"keywords\": [\"push\", \"notification\", \"bundle\", \"gcm\", \"c2dm\", \"ios\", \"apns\", \"blackberry\", \"mpns\", \"windowsphone\"],\n    \"homepage\": \"https://github.com/richsage/RMSPushNotificationsBundle\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Rich Sage\",\n            \"email\": \"rich.sage@gmail.com\",\n            \"homepage\": \"http://www.richsage.co.uk/\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=5.3.0\",\n        \"kriswallsmith/buzz\": \"*\",\n        \"psr/log\": \"^1.0\"\n    },\n    \"require-dev\": {\n        \"symfony/symfony\": \"^2.0 || ^3.0\"\n    },\n    \"suggest\": {\n        \"symfony/symfony\": \"To use as a bundle\"\n    },\n    \"autoload\": {\n        \"psr-0\": { \"RMS\\\\PushNotificationsBundle\": \"\" }\n    },\n    \"target-dir\": \"RMS/PushNotificationsBundle\",\n    \"extra\": {\n        \"branch-alias\": {\n            \"dev-master\": \"0.2.x-dev\"\n        }\n    }\n}\n"
  },
  {
    "path": "phpunit.travis.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         colors=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\"\n         syntaxCheck=\"false\"\n         bootstrap=\"./Tests/bootstrap.php\"\n>\n\n    <testsuites>\n        <testsuite name=\"PushNotificationsBundle Test Suite\">\n            <directory>./Tests</directory>\n        </testsuite>\n    </testsuites>\n</phpunit>\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<phpunit backupGlobals=\"false\"\r\n         backupStaticAttributes=\"false\"\r\n         colors=\"true\"\r\n         convertErrorsToExceptions=\"true\"\r\n         convertNoticesToExceptions=\"true\"\r\n         convertWarningsToExceptions=\"true\"\r\n         processIsolation=\"false\"\r\n         stopOnFailure=\"false\"\r\n         syntaxCheck=\"false\"\r\n         bootstrap=\"./Tests/bootstrap.php\"\r\n>\r\n         \r\n    <testsuites>\r\n        <testsuite name=\"PushNotificationsBundle Test Suite\">\r\n            <directory>./Tests</directory>\r\n        </testsuite>\r\n    </testsuites>\r\n</phpunit>\r\n"
  }
]