Bläddra i källkod

Added Zend_Mobile component

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@24687 44c647ce-9c0f-0410-b52a-842ac1e357ba
digitalstruct 14 år sedan
förälder
incheckning
07b3376e9e
49 ändrade filer med 5726 tillägg och 0 borttagningar
  1. 21 0
      demos/Zend/Mobile/Push/ApnsFeedback.php
  2. 34 0
      demos/Zend/Mobile/Push/ApnsServer.php
  3. 43 0
      demos/Zend/Mobile/Push/C2dmServer.php
  4. 39 0
      demos/Zend/Mobile/Push/MpnsServer.php
  5. 24 0
      documentation/manual/en/manual.xml.in
  6. 228 0
      documentation/manual/en/module_specs/Zend_Mobile_Push-Apns.xml
  7. 178 0
      documentation/manual/en/module_specs/Zend_Mobile_Push-C2dm.xml
  8. 112 0
      documentation/manual/en/module_specs/Zend_Mobile_Push-Introduction.xml
  9. 205 0
      documentation/manual/en/module_specs/Zend_Mobile_Push-Mpns.xml
  10. 33 0
      library/Zend/Mobile/Exception.php
  11. 112 0
      library/Zend/Mobile/Push/Abstract.php
  12. 389 0
      library/Zend/Mobile/Push/Apns.php
  13. 213 0
      library/Zend/Mobile/Push/C2dm.php
  14. 35 0
      library/Zend/Mobile/Push/Exception.php
  15. 35 0
      library/Zend/Mobile/Push/Exception/DeviceQuotaExceeded.php
  16. 35 0
      library/Zend/Mobile/Push/Exception/InvalidAuthToken.php
  17. 35 0
      library/Zend/Mobile/Push/Exception/InvalidPayload.php
  18. 35 0
      library/Zend/Mobile/Push/Exception/InvalidRegistration.php
  19. 35 0
      library/Zend/Mobile/Push/Exception/InvalidToken.php
  20. 35 0
      library/Zend/Mobile/Push/Exception/InvalidTopic.php
  21. 35 0
      library/Zend/Mobile/Push/Exception/QuotaExceeded.php
  22. 35 0
      library/Zend/Mobile/Push/Exception/ServerUnavailable.php
  23. 64 0
      library/Zend/Mobile/Push/Interface.php
  24. 134 0
      library/Zend/Mobile/Push/Message/Abstract.php
  25. 284 0
      library/Zend/Mobile/Push/Message/Apns.php
  26. 150 0
      library/Zend/Mobile/Push/Message/C2dm.php
  27. 35 0
      library/Zend/Mobile/Push/Message/Exception.php
  28. 79 0
      library/Zend/Mobile/Push/Message/Interface.php
  29. 117 0
      library/Zend/Mobile/Push/Message/Mpns.php
  30. 149 0
      library/Zend/Mobile/Push/Message/Mpns/Raw.php
  31. 365 0
      library/Zend/Mobile/Push/Message/Mpns/Tile.php
  32. 225 0
      library/Zend/Mobile/Push/Message/Mpns/Toast.php
  33. 152 0
      library/Zend/Mobile/Push/Mpns.php
  34. 103 0
      library/Zend/Mobile/Push/Test/ApnsProxy.php
  35. 57 0
      tests/Zend/Mobile/AllTests.php
  36. 74 0
      tests/Zend/Mobile/Push/AbstractTest.php
  37. 65 0
      tests/Zend/Mobile/Push/AllTests.php
  38. 228 0
      tests/Zend/Mobile/Push/ApnsTest.php
  39. 226 0
      tests/Zend/Mobile/Push/C2dmTest.php
  40. 105 0
      tests/Zend/Mobile/Push/Message/AbstractTest.php
  41. 63 0
      tests/Zend/Mobile/Push/Message/AllTests.php
  42. 212 0
      tests/Zend/Mobile/Push/Message/ApnsTest.php
  43. 115 0
      tests/Zend/Mobile/Push/Message/C2dmTest.php
  44. 60 0
      tests/Zend/Mobile/Push/Message/Mpns/AllTests.php
  45. 139 0
      tests/Zend/Mobile/Push/Message/Mpns/RawTest.php
  46. 234 0
      tests/Zend/Mobile/Push/Message/Mpns/TileTest.php
  47. 161 0
      tests/Zend/Mobile/Push/Message/Mpns/ToastTest.php
  48. 184 0
      tests/Zend/Mobile/Push/MpnsTest.php
  49. 0 0
      tests/Zend/Mobile/Push/certificate.pem

+ 21 - 0
demos/Zend/Mobile/Push/ApnsFeedback.php

@@ -0,0 +1,21 @@
+<?php
+require_once 'Zend/Mobile/Push/Apns.php';
+
+$apns = new Zend_Mobile_Push_Apns();
+$apns->setCertificate('/path/to/provisioning-certificate.pem');
+ 
+try {
+    $apns->connect(Zend_Mobile_Push_Apns::SERVER_FEEDBACK_SANDBOX_URI);
+} catch (Zend_Mobile_Push_Exception_ServerUnavailable $e) {
+    // you can either attempt to reconnect here or try again later
+    exit(1);
+} catch (Zend_Mobile_Push_Exception $e) {
+    echo 'APNS Connection Error:' . $e->getMessage();
+    exit(1);
+}
+ 
+$tokens = $apns->feedback();
+while(list($token, $time) = each($tokens)) {
+    echo $time . "\t" . $token . PHP_EOL;
+}
+$apns->close();

+ 34 - 0
demos/Zend/Mobile/Push/ApnsServer.php

@@ -0,0 +1,34 @@
+<?php
+require_once 'Zend/Mobile/Push/Apns.php';
+require_once 'Zend/Mobile/Push/Message/Apns.php';
+
+$message = new Zend_Mobile_Push_Message_Apns();
+$message->setAlert('Zend Mobile Push Example');
+$message->setBadge(1);
+$message->setSound('default');
+$message->setId(time());
+$message->setToken('ABCDEF0123456789');
+ 
+$apns = new Zend_Mobile_Push_Apns();
+$apns->setCertificate('/path/to/provisioning-certificate.pem');
+ 
+try {
+    $apns->connect(Zend_Mobile_Push_Apns::SERVER_SANDBOX_URI);
+} catch (Zend_Mobile_Push_Exception_ServerUnavailable $e) {
+    // you can either attempt to reconnect here or try again later
+    exit(1);
+} catch (Zend_Mobile_Push_Exception $e) {
+    echo 'APNS Connection Error:' . $e->getMessage();
+    exit(1);
+}
+ 
+try {
+    $apns->send($message);
+} catch (Zend_Mobile_Push_Exception_InvalidToken $e) {
+    // you would likely want to remove the token from being sent to again
+    echo $e->getMessage();
+} catch (Zend_Mobile_Push_Exception $e) {
+    // all other exceptions only require action to be sent
+    echo $e->getMessage();
+}
+$apns->close();

+ 43 - 0
demos/Zend/Mobile/Push/C2dmServer.php

@@ -0,0 +1,43 @@
+<?php
+require_once 'Zend/Mobile/Push/C2dm.php';
+require_once 'Zend/Mobile/Push/Message/C2dm.php';
+require_once 'Zend/Gdata/ClientLogin.php';
+
+try {
+    $client = Zend_Gdata_ClientLogin::getHttpClient(
+        'my@gmail.com', // REPLACE WITH YOUR GOOGLE ACCOUNT
+        'myPassword', // REPLACE WITH YOUR PASSWORD
+        Zend_Mobile_Push_C2dm::AUTH_SERVICE_NAME,
+        null,
+        'myAppName' // REPLACE WITH YOUR APP NAME
+    );
+} catch (Zend_Gdata_App_CaptchaRequiredException $cre) {
+    // manual login is required
+    echo 'URL of CAPTCHA image: ' . $cre->getCaptchaUrl() . PHP_EOL;
+    echo 'Token ID: ' . $cre->getCaptchaToken() . PHP_EOL;
+    exit(1);
+} catch (Zend_Gdata_App_AuthException $ae) {
+    echo 'Problem authenticating: ' . $ae->exception() . PHP_EOL;
+    exit(1);
+}
+ 
+$message = new Zend_Mobile_Push_Message_C2dm();
+$message->setId(time());
+$message->setToken('ABCDEF0123456789');
+$message->setData(array(
+    'foo' => 'bar',
+    'bar' => 'foo',
+));
+ 
+$c2dm = new Zend_Mobile_Push_C2dm();
+$c2dm->setLoginToken($client->getClientLoginToken());
+ 
+try {
+    $c2dm->send($message);
+} catch (Zend_Mobile_Push_Exception_InvalidToken $e) {
+    // you would likely want to remove the token from being sent to again
+    echo $e->getMessage();
+} catch (Zend_Mobile_Push_Exception $e) {
+    // all other exceptions only require action to be sent or implementation of exponential backoff.
+    echo $e->getMessage();
+}

+ 39 - 0
demos/Zend/Mobile/Push/MpnsServer.php

@@ -0,0 +1,39 @@
+<?php
+require_once 'Zend/Mobile/Push/Mpns.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/Raw.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/Tile.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/Toast.php';
+
+$mpns = new Zend_Mobile_Push_Mpns();
+$messages = array();
+ 
+// raw notification
+$message = new Zend_Mobile_Push_Message_Mpns_Raw();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN');
+$message->setMessage('<notification><foo id="bar" /></notification>');
+$messages[] = $message;
+ 
+// toast message
+$message = new Zend_Mobile_Push_Message_Mpns_Toast();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN');
+$message->setTitle('Foo');
+$message->setMessage('Bar');
+$messages[] = $message;
+ 
+// tile message
+$message = new Zend_Mobile_Push_Mpns_Tile();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN');
+$message->setBackgroundImage('foo.bar');
+$message->setCount(1);
+$message->setTitle('Bar Foo');
+$messages[] = $message;
+ 
+foreach ($messages as $m) {
+    try {
+        $mpns->send($m);
+    } catch (Zend_Mobile_Push_Exception_InvalidToken $e) {
+        echo 'Remove token: ' . $m->getToken() . PHP_EOL;
+    } catch (Zend_Mobile_Push_Exception $e) {
+        echo 'Error occurred, token: ' . $m->getToken() . ' - ' . $e->getMessage() . PHP_EOL;
+    }
+}

+ 24 - 0
documentation/manual/en/manual.xml.in

@@ -1544,6 +1544,30 @@
             </xi:include>
         </chapter>
 
+        <chapter id="zend.mobile.push">
+            <title>Zend_Mobile_Push</title>
+            <xi:include href="module_specs/Zend_Mobile_Push-Introduction.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Mobile_Push-Introduction.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Mobile_Push-Apns.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Mobile_Push-Apns.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Mobile_Push-C2dm.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Mobile_Push-C2dm.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Mobile_Push-Mpns.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Mobile_Push-Mpns.xml" />
+                </xi:fallback>
+            </xi:include>
+        </chapter>
+
         <chapter id="zend.navigation">
             <title>Zend_Navigation</title>
             <xi:include href="module_specs/Zend_Navigation-Introduction.xml">

+ 228 - 0
documentation/manual/en/module_specs/Zend_Mobile_Push-Apns.xml

@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.mobile.push.apns">
+    <title>Zend_Mobile_Push_Apns</title>
+
+    <para>
+        <classname>Zend_Mobile_Push_Apns</classname> provides the ability to
+        send push notifications to <acronym>APNS</acronym> generally in
+        conjunction with <classname>Zend_Mobile_Push_Message_Apns</classname>;
+        however there is a case when it would not be utilized is when getting
+        feedback from the APNS server.
+    </para>
+
+    <sect2 id="zend.mobile.push.apns.server">
+        <title>Pushing Messages</title>
+
+        <note>
+            <para>Prior to pushing messages; you must follow the
+                <ulink
+                    url="http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ProvisioningDevelopment/ProvisioningDevelopment.html">provisioning and deployment steps outlined by Apple</ulink>.</para>
+        </note>
+
+        <para>
+            When implementing APNS; you have a few components that
+            you will utilize.  <classname>Zend_Mobile_Push_Apns</classname>
+            which contains the server components and
+            <classname>Zend_Mobile_Push_Message_Apns</classname> which contains
+            the message that you would like to send.  Generally when sending
+            push notifications to Apple you should do so in a batch.
+        </para>
+
+        <para>
+            The actual implementation of the code is fairly minimal; however,
+            considerations to error handling must be taken.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$message = new Zend_Mobile_Push_Message_Apns();
+$message->setAlert('Zend Mobile Push Example');
+$message->setBadge(1);
+$message->setSound('default');
+$message->setId(time());
+$message->setToken('ABCDEF0123456789');
+
+$apns = new Zend_Mobile_Push_Apns();
+$apns->setCertificate('/path/to/provisioning-certificate.pem');
+// if you have a passphrase on your certificate:
+// $apns->setCertificatePassphrase('foobar');
+
+try {
+    $apns->connect(Zend_Mobile_Push_Apns::SERVER_SANDBOX_URI);
+} catch (Zend_Mobile_Push_Exception_ServerUnavailable $e) {
+    // you can either attempt to reconnect here or try again later
+    exit(1);
+} catch (Zend_Mobile_Push_Exception $e) {
+    echo 'APNS Connection Error:' . $e->getMessage();
+    exit(1);
+}
+
+try {
+    $apns->send($message);
+} catch (Zend_Mobile_Push_Exception_InvalidToken $e) {
+    // you would likely want to remove the token from being sent to again
+    echo $e->getMessage();
+} catch (Zend_Mobile_Push_Exception $e) {
+    // all other exceptions only require action to be sent
+    echo $e->getMessage();
+}
+$apns->close();
+]]></programlisting>
+
+        <table id="zend.mobile.push.apns.server.exceptions">
+            <title>Exceptions and Remediation Techniques</title>
+            <tgroup cols="3" align="left" colsep="1" rowsep="1">
+                <thead>
+                    <row>
+                        <entry>Exception</entry>
+                        <entry>Meaning</entry>
+                        <entry>Handling</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception</entry>
+                        <entry>These types of exceptions are more generic in nature
+                        and are thrown either from APNS or internally on input
+                        validation</entry>
+                        <entry>Read the message and determine remediation steps.</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidPayload</entry>
+                        <entry>Generally the payload will not throw an exception
+                        unless the size of the payload is too large or it is missing
+                        required content.</entry>
+                        <entry>Check the size of the payload is within the
+                        requirements of APNS</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidToken</entry>
+                        <entry>Any form of an invalid token will be if the token is
+                        no longer registered; you are missing a token or it is in an
+                        invalid format.</entry>
+                        <entry>You should remove the token and not attempt to send
+                        to it again.</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidTopic</entry>
+                        <entry>An invalid topic simply means that the message id was
+                        too long or not an integer.</entry>
+                        <entry>Ensure that the message ID is an integer.</entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+
+        <warning>
+            <para>
+                When sending in batches and you are sending a large amount of push
+                notifications out; you should ensure to usleep from time to time.  This
+                will ensure that your messages will be delivered and APNS will not simply
+                hang up on you.
+        </para>
+        </warning>
+    </sect2>
+
+    <sect2 id="zend.mobile.push.apns.feedback">
+        <title>Getting Feedback</title>
+        <para>
+            APNS has a feedback service that you <emphasis>must</emphasis>
+            listen to.  Apple states that they monitor providers to ensure that they
+            are listening to this service.
+        </para>
+
+        <para>
+            The feedback service simply returns an array of device tokens and
+            the time.  You can use the time to ensure that the device has not
+            re-registered for push notifications since the last send.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$apns = new Zend_Mobile_Push_Apns();
+$apns->setCertificate('/path/to/provisioning-certificate.pem');
+
+try {
+    $apns->connect(Zend_Mobile_Push_Apns::SERVER_FEEDBACK_SANDBOX_URI);
+} catch (Zend_Mobile_Push_Exception_ServerUnavailable $e) {
+    // you can either attempt to reconnect here or try again later
+    exit(1);
+} catch (Zend_Mobile_Push_Exception $e) {
+    echo 'APNS Connection Error:' . $e->getMessage();
+    exit(1);
+}
+
+$tokens = $apns->feedback();
+while(list($token, $time) = each($tokens)) {
+    echo $time . "\t" . $token . PHP_EOL;
+}
+$apns->close();
+]]></programlisting>
+        
+    </sect2>
+
+    <sect2 id="zend.mobile.push.apns.message">
+
+        <title>Advanced Messages</title>
+
+        <para>
+            APNS provides the ability for sending more advanced messages; for
+            instance the examples above show the most basic implementation of a
+            message.  <classname>Zend_Mobile_Push_Message_Apns</classname>
+            allows you to do far more advanced messaging outlined below.
+        </para>
+
+        <sect3 id="zend.mobile.push.apns.message.alerts">
+
+            <title>Alerts</title>
+
+            <para>
+                Alerts can contain anything from a simple body message to having an
+                action key and a launch image (iOS 4).  You may only want to provide
+                an action key when only a confirmation is necessary OR you are
+                looking to localize the button with non-standard text (aka not
+                "View").
+            </para>
+
+            <para>
+                The following code example shows alerts from the <ulink
+                url="http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html">APNS
+                payload examples</ulink>.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+    $message = new Zend_Mobile_Push_Message_Apns();
+
+    // message with different button
+    $message->setAlert('Bob wants to play poker', 'PLAY');
+    // message using apps localized strings w/ string replacements
+    $message->setAlert(null, null, 'GAME_PLAY_REQUEST_FORMAT', array('Jenna', 'Frank'));
+    ]]></programlisting>
+
+        </sect3>
+
+        <sect3 id="zend.mobile.push.apns.message.custom-data">
+
+            <title>Custom Data</title>
+
+            <para>
+                You can send your app custom data which allows you to make decisions
+                based on the notifications; such as synchronizing data.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+                $message = new Zend_Mobile_Push_Message_Apns();
+                $message->addCustomData('foo', 'bar');
+                $message->addCustomData('foo', array('bar' => 1));
+                $message->addCustomData('bar', 'foo');
+]]></programlisting>
+
+            <warning>
+                <para>
+                    You may not use a custom key of 'aps' as it is reserved by Apple and
+                    leveraged for the main push data.
+                </para>
+            </warning>
+
+        </sect3>
+    </sect2>
+</sect1>

+ 178 - 0
documentation/manual/en/module_specs/Zend_Mobile_Push-C2dm.xml

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.mobile.push.c2dm">
+    <title>Zend_Mobile_Push_C2dm</title>
+
+    <para>
+        <classname>Zend_Mobile_Push_C2dm</classname> provides the ability to
+        send push notifications to Android devices that contain Google Services. 
+        A message will always be constructed with
+        <classname>Zend_Mobile_Push_Message_C2dm</classname>.
+    </para>
+
+    <sect2 id="zend.mobile.push.c2dm.server">
+        <title>Pushing Messages</title>
+
+        <note>
+            <para>Prior to pushing messages; you must
+                <ulink
+                    url="http://code.google.com/android/c2dm/signup.html"> sign
+                    up for a c2dm account</ulink>.  In order to get your application
+                    to recieve push notifications, you should follow: <ulink
+                    url="http://code.google.com/android/c2dm/index.html#writing_apps">Writing
+                    Android Applications that Use C2DM</ulink>.</para>
+        </note>
+
+        <para>
+            When implementing C2DM; you have a few components that
+            you will utilize.  <classname>Zend_Mobile_Push_C2dm</classname>
+            which contains the server components and
+            <classname>Zend_Mobile_Push_Message_C2dm</classname> which contains
+            the message that you would like to send.  Each message sent must do
+            an HTTP request; so remember this when sending in large batches.
+        </para>
+
+        <para>
+            The actual implementation of the code is fairly minimal; however,
+            considerations to error handling must be taken.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+try {
+    $client = Zend_Gdata_ClientLogin::getHttpClient(
+        'my@gmail.com', // REPLACE WITH YOUR GOOGLE ACCOUNT
+        'myPassword', // REPLACE WITH YOUR PASSWORD
+        Zend_Mobile_Push_C2dm::AUTH_SERVICE_NAME,
+        null,
+        'myAppName' // REPLACE WITH YOUR APP NAME
+    );
+} catch (Zend_Gdata_App_CaptchaRequiredException $cre) {
+    // manual login is required
+    echo 'URL of CAPTCHA image: ' . $cre->getCaptchaUrl() . PHP_EOL;
+    echo 'Token ID: ' . $cre->getCaptchaToken() . PHP_EOL;
+    exit(1);
+} catch (Zend_Gdata_App_AuthException $ae) {
+    echo 'Problem authenticating: ' . $ae->exception() . PHP_EOL;
+    exit(1);
+}
+
+$message = new Zend_Mobile_Push_Message_C2dm();
+$message->setId(time());
+$message->setToken('ABCDEF0123456789');
+$message->setData(array(
+    'foo' => 'bar',
+    'bar' => 'foo',
+));
+
+$c2dm = new Zend_Mobile_Push_C2dm();
+$c2dm->setLoginToken($client->getClientLoginToken());
+
+try {
+    $c2dm->send($message);
+} catch (Zend_Mobile_Push_Exception_InvalidToken $e) {
+    // you would likely want to remove the token from being sent to again
+    echo $e->getMessage();
+} catch (Zend_Mobile_Push_Exception $e) {
+    // all other exceptions only require action to be sent or implementation of exponential backoff.
+    echo $e->getMessage();
+}
+]]></programlisting>
+
+        <table id="zend.mobile.push.c2dm.server.exceptions">
+            <title>Exceptions and Remediation Techniques</title>
+            <tgroup cols="3" align="left" colsep="1" rowsep="1">
+                <thead>
+                    <row>
+                        <entry>Exception</entry>
+                        <entry>Meaning</entry>
+                        <entry>Handling</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception</entry>
+                        <entry>These types of exceptions are more generic in nature
+                        and are thrown either from C2DM when there was an unknown exception
+                        or internally on input validation.</entry>
+                        <entry>Read the message and determine remediation steps.</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidPayload</entry>
+                        <entry>Generally the payload will not throw an exception
+                        unless the size of the payload is too large or it is missing
+                        required content.</entry>
+                        <entry>Check the size of the payload is within the
+                        requirements of C2DM</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidToken</entry>
+                        <entry>Any form of an invalid token will be if the token is
+                        no longer registered; you are missing a token or it is in an
+                        invalid format.</entry>
+                        <entry>You should remove the token and not attempt to send
+                        to it again.</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidTopic</entry>
+                        <entry>An invalid topic simply means that the message id was
+                        too long or not an integer.</entry>
+                        <entry>Ensure that the message ID is an integer.</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_DeviceQuotaExceeded</entry>
+                        <entry>You have sent too many messages to this device;
+                            you may retry again later.</entry>
+                        <entry>Grab the HTTP client and check to see if there
+                            was a retry-after header; otherwise implement
+                            <ulink
+                                url="http://en.wikipedia.org/wiki/Exponential_backoff">Exponential
+                                Backoff</ulink></entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_QuotaExceeded</entry>
+                        <entry>You have sent too many messages and have gone
+                            over the quota for the day; you may try again later.</entry>
+                        <entry>Grab the HTTP client and check to see if there
+                            was a retry-after header; like above implement
+                            Exponential Backoff.  Secondly; if you are
+                            continually going over your limit; you may request
+                            <ulink
+                                url="http://code.google.com/android/c2dm/quotas.html">Request
+                            a higher quota</ulink>.  See the link on the bottom of
+                            the page.</entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+    </sect2>
+
+    <sect2 id="zend.mobile.push.c2dm.message">
+
+        <title>Advanced Messages</title>
+
+        <para>
+            C2DM provides the ability for sending more advanced messages; for
+            instance the examples above show the most basic implementation of a
+            message.  <classname>Zend_Mobile_Push_Message_C2dm</classname>
+            allows you to do far more advanced messaging outlined below.
+        </para>
+
+        <sect3 id="zend.mobile.push.c2dm.message.delay-while-idle">
+
+            <title>Delay While Idle</title>
+
+            <para>
+                If included, indicates that the message should not be sent
+                immediately if the device is idle. The server will wait for the
+                device to become active, and then only the last message for each
+                collapse_key value will be sent.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+    $message = new Zend_Mobile_Push_Message_C2dm();
+    $message->setDelayWhileIdle(true);
+    ]]></programlisting>
+
+        </sect3>
+    </sect2>
+</sect1>

+ 112 - 0
documentation/manual/en/module_specs/Zend_Mobile_Push-Introduction.xml

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.mobile.push.introduction">
+    <title>Zend_Mobile_Push Introduction</title>
+
+    <para>
+        Zend_Mobile_Push provides the ability for sending push notifications to
+        the vendor specific notification servers.  Currently this list includes
+        APNS (iTouch/iPad/iPhone), C2DM (Google Android) and MPNS (Windows Phone). 
+    </para>
+
+    <para>
+        Since many of the underlying implementations vary; please see each
+        specific notification class for usage details.  Outlined below are
+        details on the common functionality that exists between the
+        implementations.
+    </para>
+
+    <sect2 id="zend.mobile.push.common-functionality">
+        <title>Common Functionality</title>
+
+        <para>
+            While each individual push notification adapter contains specific
+            functionality; there are commonalities that have been attempted to
+            make implementation of the adapters easier.
+        </para>
+
+        <table id="zend.mobile.push.common-functionality.adapters">
+            <title>Adapters</title>
+            <tgroup cols="2" align="left" colsep="1" rowsep="1">
+                <thead>
+                    <row>
+                        <entry>Method</entry>
+                        <entry>Description</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>close</entry>
+                        <entry>Allows you to close an already open connection;
+                            if no connection is open will simply default the
+                            state.</entry>
+                    </row>
+                    <row>
+                        <entry>connect</entry>
+                        <entry>Generally only utilized for APNS; however,
+                            assists in maintaining the connection state.</entry>
+                    </row>
+                    <row>
+                        <entry>isConnected</entry>
+                        <entry>Allows you to see if you are already connected.</entry>
+                    </row>
+                    <row>
+                        <entry>send</entry>
+                        <entry>Allows you to pass in a message to send.  This
+                            will send the message to the corresponding system.
+                            Note that the send method for APNS will lazily connect
+                            if you are not already connected.</entry>
+                    </row>
+                    <row>
+                        <entry>setOptions</entry>
+                        <entry>Allows you to set options in bulk for the
+                            adapters.</entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+
+        <para>
+            Each individual message varies in implementation; again like
+            adapters there are some commonalities that have been addressed to
+            ensure consistency between messages.
+        </para>
+
+
+        <table id="zend.mobile.push.common-functionality.messages">
+            <title>Messages</title>
+            <tgroup cols="2" align="left" colsep="1" rowsep="1">
+                <thead>
+                    <row>
+                        <entry>Method</entry>
+                        <entry>Description</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>(set|get)Token</entry>
+                        <entry>Allows you to set or get the token or device id /
+                        unique id that is for any specific device.  For APNS
+                        this is the device ID, for C2DM it is the registration
+                        id and for MPNS it equates to the push url.</entry>
+                    </row>
+                    <row>
+                        <entry>(set|get)Id</entry>
+                        <entry>Sets the distict message id that you would give a
+                        message.</entry>
+                    </row>
+                    <row>
+                        <entry>setOptions</entry>
+                        <entry>Allows you to set a messages options in bulk.</entry>
+                    </row>
+                    <row>
+                        <entry>validate</entry>
+                        <entry>Does a <emphasis>very</emphasis> simple test on
+                            the message to ensure that it can be sent through
+                            the adapter.</entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+    </sect2>
+</sect1>

+ 205 - 0
documentation/manual/en/module_specs/Zend_Mobile_Push-Mpns.xml

@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.mobile.push.mpns">
+    <title>Zend_Mobile_Push_Mpns</title>
+
+    <para>
+        <classname>Zend_Mobile_Push_Mpns</classname> provides the ability to
+        send push notifications to Windows Phone.  MPNS allows the sending of
+        3 different types of notifications; which common behavior is in the
+        <classname>Zend_Mobile_Push_Message_Mpns</classname> base.  Followed by
+        specific classes for Raw, Toast and Tile notifications.
+    </para>
+
+    <sect2 id="zend.mobile.push.mpns.server">
+        <title>Pushing Messages</title>
+
+        <note>
+            <para>Prior to pushing messages; you must implement the practices
+                outlined on <ulink
+                url="http://msdn.microsoft.com/en-us/library/hh202940(v=VS.92).aspx">Receiving
+                Push Notifications for Windows Phone</ulink>. 
+            </para>
+        </note>
+
+        <para>
+            When implementing MPNS; you have several components that
+            you will utilize.  <classname>Zend_Mobile_Push_Mpns</classname>
+            which contains the server components and
+            <classname>Zend_Mobile_Push_Message_Mpns_Raw</classname> which
+            allows you to send <ulink
+            url="http://msdn.microsoft.com/en-us/library/hh202977(v=VS.92).aspx">raw
+            notifications</ulink>,
+            <classname>Zend_Mobile_Push_Message_Mpns_Toast</classname> which
+            allows you to send <ulink
+            url="http://msdn.microsoft.com/en-us/library/hh202967(v=VS.92).aspx">toast
+            notifications</ulink>, and
+            <classname>Zend_Mobile_Push_Message_Mpns_Tile</classname> which
+            allows you to send <ulink
+            url="http://msdn.microsoft.com/en-us/library/hh202970(v=VS.92).aspx">tile
+            notifications</ulink>.  Each message sent must do an HTTP request;
+            so remember this when sending in large batches.
+        </para>
+
+        <para>
+            The actual implementation of the code is fairly minimal; however,
+            considerations to error handling must be taken.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$mpns = new Zend_Mobile_Push_Mpns();
+$messages = array();
+
+// raw notification
+$message = new Zend_Mobile_Push_Message_Mpns_Raw();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN');
+$message->setMessage('<notification><foo id="bar" /></notification>');
+$messages[] = $message;
+
+// toast message
+$message = new Zend_Mobile_Push_Message_Mpns_Toast();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN');
+$message->setTitle('Foo');
+$message->setMessage('Bar');
+$messages[] = $message;
+
+// tile message
+$message = new Zend_Mobile_Push_Mpns_Tile();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN');
+$message->setBackgroundImage('foo.bar');
+$message->setCount(1);
+$message->setTitle('Bar Foo');
+$messages[] = $message;
+
+foreach ($messages as $m) {
+    try {
+        $mpns->send($m);
+    } catch (Zend_Mobile_Push_Exception_InvalidToken $e) {
+        echo 'Remove token: ' . $m->getToken() . PHP_EOL;
+    } catch (Zend_Mobile_Push_Exception $e) {
+        echo 'Error occurred, token: ' . $m->getToken() . ' - ' . $e->getMessage() . PHP_EOL;
+    }
+}
+]]></programlisting>
+
+        <table id="zend.mobile.push.mpns.server.exceptions">
+            <title>Exceptions and Remediation Techniques</title>
+            <tgroup cols="3" align="left" colsep="1" rowsep="1">
+                <thead>
+                    <row>
+                        <entry>Exception</entry>
+                        <entry>Meaning</entry>
+                        <entry>Handling</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception</entry>
+                        <entry>These types of exceptions are more generic in nature
+                        and are thrown either from MPNS or internally on input
+                        validation</entry>
+                        <entry>Read the message and determine remediation steps.</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_DeviceQuotaExceeded</entry>
+                        <entry>You have sent too many messages to this device;
+                            you may retry again later.</entry>
+                        <entry>Try again later or implement <ulink
+                                url="http://en.wikipedia.org/wiki/Exponential_backoff">
+                            Exponential Backoff
+                        </ulink>.</entry>
+                    </row>
+                    <row>
+                        
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidPayload</entry>
+                        <entry>Generally the payload will not throw an exception
+                        unless the size of the payload is too large or it is missing
+                        required content.</entry>
+                        <entry>Check the size of the payload is within the
+                        requirements of MPNS</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_InvalidToken</entry>
+                        <entry>Any form of an invalid token will be if the
+                            device is no longer subscribed, inactive or not
+                            valid.</entry>
+                        <entry>In some cases you may attempt to resend in an
+                            hour; this will be stated in the exception.
+                            Otherwise you will want to remove the token from
+                            being sent to again.</entry>
+                    </row>
+                    <row>
+                        <entry>Zend_Mobile_Push_Exception_QuotaExceeded</entry>
+                        <entry>You have reached the per-day throttling.</entry>
+                        <entry>Per-day throttling is only on unauthenticated web
+                        services; you will need to <ulink
+                        url="http://msdn.microsoft.com/en-us/library/ff941099(v=vs.92).aspx">register
+                        your application for notifications</ulink>.</entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+    </sect2>
+
+    <sect2 id="zend.mobile.push.mpns.message">
+
+        <title>Advanced Messages</title>
+
+        <para>
+            MPNS provides the ability for sending more advanced messages; for
+            instance the examples above show the most basic implementation of a
+            message.  Zend_Mobile_Push_Message_Mpns_* allows you to do far more
+            advanced messaging outlined below.
+        </para>
+
+        <sect3 id="zend.mobile.push.mpns.message.tile">
+
+            <title>Tile Messages</title>
+
+            <para>
+                Tile messages have additional optional attributes for Windows
+                Phone 7.1+; you must ensure that you are sending to a device
+                with the proper version otherwise your notification will fail.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+$message = new Zend_Mobile_Push_Message_Mpns_Tile();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN'); // REPLACE WITH NOTIFICATION URI FROM MPNS
+$message->setBackgroundImage('foo.jpg');
+$message->setCount(1);
+$message->setTitle('Bar');
+
+// other optional attributes for wp7.1+
+$message->setTileId('/Foo.xaml');
+$message->setBackBackgroundImage('blue.jpg');
+$message->setBackTitle('Bar');
+$message->setBackContent('Foo Bar');
+    ]]></programlisting>
+
+        </sect3>
+
+        <sect3 id="zend.mobile.push.mpns.message.toast">
+
+            <title>Toast Messages</title>
+
+            <para>
+                Toast messages have additional optional attributes for Windows
+                Phone 7.1+; you must ensure that you are sending to a device
+                with the proper version otherwise your notification will fail.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+$message = new Zend_Mobile_Push_Message_Mpns_Toast();
+$message->setToken('http://sn1.notify.live.net/throttledthirdparty/01.00/THETOKEN'); // REPLACE WITH NOTIFICATION URI FROM MPNS
+$message->setTitle('Foo');
+$message->setMessage('Bar');
+
+// optional attributes for wp7.1+
+$message->setParams('?bar=foo'); //optional parameters
+]]></programlisting>
+
+        </sect3>
+    </sect2>
+</sect1>

+ 33 - 0
library/Zend/Mobile/Exception.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Exception */
+require_once 'Zend/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Exception extends Zend_Exception
+{}

+ 112 - 0
library/Zend/Mobile/Push/Abstract.php

@@ -0,0 +1,112 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Interface **/
+require_once 'Zend/Mobile/Push/Interface.php';
+
+/** Zend_Mobile_Push_Exception **/
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * Push Abstract
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+abstract class Zend_Mobile_Push_Abstract implements Zend_Mobile_Push_Interface
+{
+    /**
+     * Is Connected
+     *
+     * @var boolean
+     */
+    protected $_isConnected = false;
+
+    /**
+     * Connect to the Push Server
+     * 
+     * @return Zend_Mobile_Push_Abstract
+     */
+    public function connect()
+    {
+        $this->_isConnected = true;
+        return $this;
+    }
+
+    /**
+     * Send a Push Message
+     *
+     * @param Zend_Mobile_Push_Message_Abstract $message
+     * @return boolean
+     * @throws DomainException
+     */
+    public function send(Zend_Mobile_Push_Message_Abstract $message)
+    {
+        if (!$this->_isConnected) {
+            $this->connect();
+        }
+        return true;
+    }
+
+    /**
+     * Close the Connection to the Push Server
+     *
+     * @return void
+     */
+    public function close()
+    {
+        $this->_isConnected = false;
+    }
+
+    /**
+     * Is Connected
+     *
+     * @return boolean
+     */
+    public function isConnected()
+    {
+        return $this->_isConnected;
+    }
+
+    /**
+     * Set Options
+     *
+     * @param array $options
+     * @return Zend_Mobile_Push_Abstract
+     * @throws Zend_Mobile_Push_Exception
+     */
+    public function setOptions(array $options)
+    {
+        foreach ($options as $k => $v) {
+            $method = 'set' . ucwords($k);
+            if (!method_exists($this, $method)) {
+                throw new Zend_Mobile_Push_Exception('The method "' . $method . "' does not exist.");
+            }
+            $this->$method($v);
+        }
+        return $this;
+    }
+}

+ 389 - 0
library/Zend/Mobile/Push/Apns.php

@@ -0,0 +1,389 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Abstract **/
+require_once 'Zend/Mobile/Push/Abstract.php';
+
+/** Zend_Mobile_Push_Message_Apns **/
+require_once 'Zend/Mobile/Push/Message/Apns.php';
+
+/**
+ * APNS Push
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Apns extends Zend_Mobile_Push_Abstract
+{
+
+    /**
+     * @const int apple server uri constants
+     */
+    const SERVER_SANDBOX_URI = 0;
+    const SERVER_PRODUCTION_URI = 1;
+    const SERVER_FEEDBACK_SANDBOX_URI = 2;
+    const SERVER_FEEDBACK_PRODUCTION_URI = 3;
+
+    /**
+     * Apple Server URI's
+     *
+     * @var array
+     */
+    protected $_serverUriList = array(
+        'ssl://gateway.sandbox.push.apple.com:2195',
+        'ssl://gateway.push.apple.com:2195',
+        'ssl://feedback.push.apple.com:2196',
+        'ssl://feedback.sandbox.push.apple.com:2196'
+    );
+
+    /**
+     * Current Environment
+     *
+     * @var int
+     */
+    protected $_currentEnv;
+
+    /**
+     * Socket
+     *
+     * @var resource
+     */
+    protected $_socket;
+
+    /**
+     * Certificate
+     *
+     * @var string
+     */
+    protected $_certificate;
+
+    /**
+     * Certificate Passphrase
+     *
+     * @var string
+     */
+    protected $_certificatePassphrase;
+
+    /**
+     * Get Certficiate
+     *
+     * @return string
+     */
+    public function getCertificate()
+    {
+        return $this->_certificate;
+    }
+
+    /**
+     * Set Certificate
+     *
+     * @param  string $cert
+     * @return Zend_Mobile_Push_Apns
+     * @throws Zend_Mobile_Push_Exception
+     */
+    public function setCertificate($cert)
+    {
+        if (!is_string($cert)) {
+            throw new Zend_Mobile_Push_Exception('$cert must be a string');
+        }
+        if (!file_exists($cert)) {
+            throw new Zend_Mobile_Push_Exception('$cert must be a valid path to the certificate');
+        }
+        $this->_certificate = $cert;
+        return $this;
+    }
+
+    /**
+     * Get Certificate Passphrase
+     *
+     * @return string
+     */
+    public function getCertificatePassphrase()
+    {
+        return $this->_certificatePassphrase;
+    }
+
+    /**
+     * Set Certificate Passphrase
+     *
+     * @param  string $passphrase
+     * @return Zend_Mobile_Push_Apns
+     * @throws Zend_Mobile_Push_Exception
+     */
+    public function setCertificatePassphrase($passphrase)
+    {
+        if (!is_string($passphrase)) {
+            throw new Zend_Mobile_Push_Exception('$passphrase must be a string');
+        }
+        $this->_certificatePassphrase = $passphrase;
+        return $this;
+    }
+
+    /**
+     * Connect to Socket
+     *
+     * @param  string $uri
+     * @return bool
+     * @throws Zend_Mobile_Push_Exception_ServerUnavailable
+     */
+    protected function _connect($uri)
+    {
+        $ssl = array(
+            'local_cert' => $this->_certificate,
+        );
+        if ($this->_certificatePassphrase) {
+            $ssl['passphrase'] = $this->_certificatePassphrase;
+        }
+
+        $this->_socket = stream_socket_client($uri,
+            $errno,
+            $errstr,
+            ini_get('default_socket_timeout'),
+            STREAM_CLIENT_CONNECT,
+            stream_context_create(array(
+                'ssl' => $ssl,
+            ))
+        );
+
+        if (!is_resource($this->_socket)) {
+            require_once 'Zend/Mobile/Push/Exception/ServerUnavailable.php';
+            throw new Zend_Mobile_Push_Exception_ServerUnavailable(sprintf('Unable to connect: %s: %d (%s)',
+                $uri,
+                $errno,
+                $errstr
+            ));
+        }
+
+        stream_set_blocking($this->_socket, 0);
+        stream_set_write_buffer($this->_socket, 0);
+        return true;
+    }
+
+    /**
+    * Read from the Socket Server
+    * 
+    * @param int $length
+    * @return string
+    */
+    protected function _read($length) {
+        $data = false;
+        if (!feof($this->_socket)) {
+            $data = fread($this->_socket, $length);
+        }
+        return $data;
+    }
+
+    /**
+    * Write to the Socket Server
+    * 
+    * @param string $payload
+    * @return int
+    */
+    protected function _write($payload) {
+        return @fwrite($this->_socket, $payload);
+    }
+
+    /**
+     * Connect to the Push Server
+     *
+     * @param string $env
+     * @return Zend_Mobile_Push_Abstract
+     * @throws Zend_Mobile_Push_Exception
+     * @throws Zend_Mobile_Push_Exception_ServerUnavailable
+     */
+    public function connect($env = self::SERVER_PRODUCTION_URI)
+    {
+        if ($this->_isConnected) {
+            if ($this->_currentEnv == self::SERVER_PRODUCTION_URI) {
+                return $this;
+            }
+            $this->close();
+        }
+
+        if (!isset($this->_serverUriList[$env])) {
+            throw new Zend_Mobile_Push_Exception('$env is not a valid environment');
+        }
+
+        if (!$this->_certificate) {
+            throw new Zend_Mobile_Push_Exception('A certificate must be set prior to calling ::connect');
+        }
+
+        $this->_connect($this->_serverUriList[$env]);
+
+        $this->_currentEnv = $env;
+        $this->_isConnected = true;
+        return $this;
+    }
+
+
+
+    /**
+     * Feedback
+     *
+     * @return array array w/ key = token and value = time
+     * @throws Zend_Mobile_Push_Exception
+     * @throws Zend_Mobile_Push_Exception_ServerUnavailable
+     */
+    public function feedback()
+    {
+        if (!$this->_isConnected ||
+            !in_array($this->_currentEnv,
+                array(self::SERVER_FEEDBACK_SANDBOX_URI, self::SERVER_FEEDBACK_PRODUCTION_URI))) {
+            $this->connect(self::SERVER_FEEDBACK_PRODUCTION_URI);
+        }
+
+        $tokens = array();
+        while ($token = $this->_read(38)) {
+            if (strlen($token) < 38) {
+                continue;
+            }
+            $token = unpack('Ntime/ntokenLength/H*token', $token);
+            if (!isset($tokens[$token['token']]) || $tokens[$token['token']] < $token['time']) {
+                $tokens[$token['token']] = $token['time'];
+            }
+        }
+        return $tokens;
+    }
+
+    /**
+     * Send Message
+     *
+     * @param Zend_Mobile_Push_Message_Apns $message
+     * @return boolean
+     * @throws Zend_Mobile_Push_Exception
+     * @throws Zend_Mobile_Push_Exception_ServerUnavailable
+     * @throws Zend_Mobile_Push_Exception_InvalidToken
+     * @throws Zend_Mobile_Push_Exception_InvalidTopic
+     * @throws Zend_Mobile_Push_Exception_InvalidPayload
+     */
+    public function send(Zend_Mobile_Push_Message_Abstract $message)
+    {
+        if (!$message->validate()) {
+            throw new Zend_Mobile_Push_Exception('The message is not valid.');
+        }
+
+        if (!$this->_isConnected || !in_array($this->_currentEnv, array(
+            self::SERVER_SANDBOX_URI,
+            self::SERVER_PRODUCTION_URI))) {
+            $this->connect(self::SERVER_PRODUCTION_URI);
+        }
+
+        $payload = array('aps' => array());
+
+        $alert = $message->getAlert();
+        foreach ($alert as $k => $v) {
+            if ($v == null) {
+                unset($alert[$k]);
+            }
+        }
+        if (!empty($alert)) {
+            $payload['aps']['alert'] = $alert;
+        }
+        $payload['aps']['badge'] = $message->getBadge();
+        $payload['aps']['sound'] = $message->getSound();
+
+        foreach($message->getCustomData() as $k => $v) {
+            $payload[$k] = $v;
+        }
+        $payload = json_encode($payload);
+
+        $expire = $message->getExpire();
+        if ($expire > 0) {
+            $expire += time();
+        }
+        $id = $message->getId();
+        if (empty($id)) {
+            $id = time();
+        }
+
+        $payload = pack('CNNnH*', 1, $id, $expire, 32, $message->getToken())
+            . pack('n', strlen($payload))
+            . $payload;
+        $ret = $this->_write($payload);
+        if ($ret === false) {
+            require_once 'Zend/Mobile/Push/Exception/ServerUnavailable.php';
+            throw new Zend_Mobile_Push_Exception_ServerUnavailable('Unable to send message');
+        }
+        // check for errors from apple
+        $err = $this->_read(1024);
+        if (strlen($err) > 0) {
+            $err = unpack('Ccmd/Cerrno/Nid', $err);
+            switch ($err['errno']) {
+                case 0:
+                    return true;
+                    break;
+                case 1:
+                    throw new Zend_Mobile_Push_Exception('A processing error has occurred on the apple push notification server.');
+                    break;
+                case 2:
+                    require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                    throw new Zend_Mobile_Push_Exception_InvalidToken('Missing token; you must set a token for the message.');
+                    break;
+                case 3:
+                    require_once 'Zend/Mobile/Push/Exception/InvalidTopic.php';
+                    throw new Zend_Mobile_Push_Exception_InvalidTopic('Missing id; you must set an id for the message.');
+                    break;
+                case 4:
+                    require_once 'Zend/Mobile/Push/Exception/InvalidPayload.php';
+                    throw new Zend_Mobile_Push_Exception_InvalidPayload('Missing message; the message must always have some content.');
+                    break;
+                case 5:
+                    require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                    throw new Zend_Mobile_Push_Exception_InvalidToken('Bad token.  This token is too big and is not a regular apns token.');
+                    break;
+                case 6:
+                    require_once 'Zend/Mobile/Push/Exception/InvalidTopic.php';
+                    throw new Zend_Mobile_Push_Exception_InvalidTopic('The message id is too big; reduce the size of the id.');
+                    break;
+                case 7:
+                    require_once 'Zend/Mobile/Push/Exception/InvalidPayload.php';
+                    throw new Zend_Mobile_Push_Exception_InvalidPayload('The message is too big; reduce the size of the message.');
+                    break;
+                case 8:
+                    require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                    throw new Zend_Mobile_Push_Exception_InvalidToken('Bad token.  Remove this token from being sent to again.');
+                    break;
+                default:
+                    throw new Zend_Mobile_Push_Exception(sprintf('An unknown error occurred: %d', $err['errno']));
+                    break;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Close Connection
+     *
+     * @return void
+     */
+    public function close()
+    {
+        if ($this->_isConnected && is_resource($this->_socket)) {
+            fclose($this->_socket);
+        }
+        $this->_isConnected = false;
+    }
+}

+ 213 - 0
library/Zend/Mobile/Push/C2dm.php

@@ -0,0 +1,213 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Http_Client **/
+require_once 'Zend/Http/Client.php';
+
+/** Zend_Mobile_Push_Abstract **/
+require_once 'Zend/Mobile/Push/Abstract.php';
+
+/** Zend_Mobile_Push_Message_C2dm **/
+require_once 'Zend/Mobile/Push/Message/C2dm.php';
+
+/**
+ * C2DM Push
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_C2dm extends Zend_Mobile_Push_Abstract
+{
+
+    /**
+     * @const string Server URI
+     */
+    const SERVER_URI = 'https://android.apis.google.com/c2dm/send';
+
+    /**
+     * @const string ClientLogin auth service name
+     */
+    const AUTH_SERVICE_NAME = 'ac2dm';
+
+    /**
+     * Http Client
+     *
+     * @var Client
+     */
+    protected $_httpClient;
+
+    /**
+     * Login Token
+     *
+     * @var string
+     */
+    protected $_loginToken;
+
+    /**
+     * Get Login Token
+     *
+     * @return string
+     */
+    public function getLoginToken()
+    {
+        return $this->_loginToken;
+    }
+
+    /**
+     * Set Login Token
+     *
+     * @param  string $token
+     * @return Zend_Mobile_Push_C2dm
+     * @throws Zend_Mobile_Push_Exception
+     */
+    public function setLoginToken($token)
+    {
+        if (!is_string($token) || empty($token)) {
+            throw new Zend_Mobile_Push_Exception('The login token must be a string and not empty');
+        }
+        $this->_loginToken = $token;
+        return $this;
+    }
+
+    /**
+     * Get Http Client
+     *
+     * @return Zend_Http_Client
+     */
+    public function getHttpClient()
+    {
+        if (!$this->_httpClient) {
+            $this->_httpClient = new Zend_Http_Client();
+            $this->_httpClient->setConfig(array(
+                'strictredirects' => true,
+            ));
+        }
+        return $this->_httpClient;
+    }
+
+    /**
+     * Set Http Client
+     *
+     * @return Zend_Mobile_Push_C2dm
+     */
+    public function setHttpClient(Zend_Http_Client $client)
+    {
+        $this->_httpClient = $client;
+        return $this;
+    }
+
+    /**
+     * Send Message
+     *
+     * @param Zend_Mobile_Push_Message_C2dm $message
+     * @return boolean
+     * @throws Zend_Mobile_Push_Exception
+     */
+    public function send(Zend_Mobile_Push_Message_Abstract $message)
+    {
+        if (!$message->validate()) {
+            throw new Zend_Mobile_Push_Exception('The message is not valid.');
+        }
+
+        $this->connect();
+
+        $client = $this->getHttpClient();
+        $client->setUri(self::SERVER_URI);
+        $client->setHeaders('Authorization', 'GoogleLogin auth=' . $this->getLoginToken());
+        $client->setParameterPost('delay_while_idle', (int) $message->getDelayWhileIdle());
+        $client->setParameterPost('registration_id', $message->getToken());
+        $client->setParameterPost('collapse_key', $message->getId());
+        foreach ($message->getData() as $k => $v) {
+            $client->setParameterPost('data.' . $k, $v);
+        }
+        $response = $client->request('POST');
+        $this->close();
+
+
+        switch ($response->getStatus())
+        {
+            case 500:
+            case 503:
+                require_once 'Zend/Mobile/Push/Exception/ServerUnavailable.php';
+                throw new Zend_Mobile_Push_Exception_ServerUnavailable('The server was unavailable, check Retry-After header');
+                break;
+            case 401:
+                require_once 'Zend/Mobile/Push/Exception/InvalidAuthToken.php';
+                throw new Zend_Mobile_Push_Exception_InvalidAuthToken('The auth token is invalid');
+                break;
+            default:
+                $body = $response->getBody();
+                $body = preg_split('/=/', $body);
+                if (!isset($body[0]) || !isset($body[1])) {
+                    require_once 'Zend/Mobile/Push/Exception/ServerUnavailable.php';
+                    throw new Zend_Mobile_Push_Exception_ServerUnavailable('The server gave us an invalid response, try again later');
+                }
+                if (strtolower($body[0]) == 'error') {
+                    $err = strtolower($body[1]);
+                    switch ($err) {
+                        case 'quotaexceeded':
+                            require_once 'Zend/Mobile/Push/Exception/QuotaExceeded.php';
+                            throw new Zend_Mobile_Push_Exception_QuotaExceeded('Too many messages sent by the sender. Retry after a while.');
+                            break;
+                        case 'devicequotaexceeded':
+                            require_once 'Zend/Mobile/Push/Exception/DeviceQuotaExceeded.php';
+                            throw new Zend_Mobile_Push_Exception_DeviceQuotaExceeded('Too many messages sent by the sender to a specific device. Retry after a while.');
+                            break;
+                        case 'missingregistration':
+                            require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                            throw new Zend_Mobile_Push_Exception_InvalidToken('Missing token.  The message must always have a token.');
+                            break;
+                        case 'invalidregistration':
+                            require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                            throw new Zend_Mobile_Push_Exception_InvalidToken('Bad token.  Remove this token from being sent to again.');
+                            break;
+                        case 'mismatchsenderid':
+                            require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                            throw new Zend_Mobile_Push_Exception_InvalidToken('Bad token.  This token is not registered with the current login');
+                            break;
+                        case 'notregistered':
+                            require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                            throw new Zend_Mobile_Push_Exception_InvalidToken('Bad token.  This token is not registered.');
+                            break;
+                        case 'messagetoobig':
+                            require_once 'Zend/Mobile/Push/Exception/InvalidPayload.php';
+                            throw new Zend_Mobile_Push_Exception_InvalidPayload('The message is too big; reduce the size of the message.');
+                            break;
+                        case 'missingcollapsekey':
+                            require_once 'Zend/Mobile/Push/Exception/InvalidTopic.php';
+                            throw new Zend_Mobile_Push_Exception_InvalidTopic('The message id must be set; include one in the message.');
+                            break;
+                        default:
+                            $err = strip_tags($body[1]);
+                            throw new Zend_Mobile_Push_Exception(sprintf('An unknown error occurred: %s', $err));
+
+                    }
+                }
+                break;
+        }
+        return true;
+    }
+}

+ 35 - 0
library/Zend/Mobile/Push/Exception.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Exception */
+require_once 'Zend/Mobile/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception extends Zend_Mobile_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/DeviceQuotaExceeded.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_DeviceQuotaExceeded extends Zend_Mobile_Push_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/InvalidAuthToken.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_InvalidAuthToken extends Zend_Mobile_Push_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/InvalidPayload.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_InvalidPayload extends Zend_Mobile_Push_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/InvalidRegistration.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_InvalidRegistration extends Zend_Mobile_Push_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/InvalidToken.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_InvalidToken extends Zend_Mobile_Push_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/InvalidTopic.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_InvalidTopic extends Zend_Mobile_Push_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/QuotaExceeded.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_QuotaExceeded extends Zend_Mobile_Push_Exception
+{}

+ 35 - 0
library/Zend/Mobile/Push/Exception/ServerUnavailable.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Exception_ServerUnavailable extends Zend_Mobile_Push_Exception
+{}

+ 64 - 0
library/Zend/Mobile/Push/Interface.php

@@ -0,0 +1,64 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/**
+ * Push Interface
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+interface Zend_Mobile_Push_Interface
+{
+    /**
+     * Connect to the Push Server
+     *
+     * @return Push
+     */
+    public function connect();
+
+    /**
+     * Send a Push Message
+     *
+     * @param Zend_Mobile_Push_Message_Interface $message
+     * @return boolean
+     */
+    public function send(Zend_Mobile_Push_Message_Abstract $message);
+
+    /**
+     * Close the Connection to the Push Server
+     *
+     * @return void
+     */
+    public function close();
+
+    /**
+     * Set Options
+     *
+     * @param array $options
+     * @return Zend_Mobile_Push_Abstract
+     */
+    public function setOptions(array $options);
+}

+ 134 - 0
library/Zend/Mobile/Push/Message/Abstract.php

@@ -0,0 +1,134 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Message_Interface **/
+require_once 'Zend/Mobile/Push/Message/Interface.php';
+
+/** Zend_Mobile_Push_Message_Exception **/
+require_once 'Zend/Mobile/Push/Message/Exception.php';
+
+/**
+ * Message Abstract
+ *
+ * @category   Zend
+ * @package    Zend\Mobile
+ * @subpackage Zend\Mobile\Push\Message
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+abstract class Zend_Mobile_Push_Message_Abstract implements Zend_Mobile_Push_Message_Interface
+{
+    /**
+     * Token
+     *
+     * @var string
+     */
+    protected $_token;
+
+    /**
+     * Id
+     *
+     * @var scalar
+     */
+    protected $_id;
+
+    /**
+     * Get Token
+     *
+     * @return string
+     */
+    public function getToken()
+    {
+        return $this->_token;
+    }
+
+    /**
+     * Set Token
+     *
+     * @return MessageAbstract
+     */
+    public function setToken($token)
+    {
+        if (!is_string($token)) {
+            throw new Zend_Mobile_Push_Message_Exception('$token must be a string');
+        }
+        $this->_token = $token;
+        return $this;
+    }
+
+    /**
+     * Get Message ID
+     * 
+     * @return scalar
+     */
+    public function getId()
+    {
+        return $this->_id;
+    }
+
+    /**
+     * Set Message ID
+     *
+     * @param scalar $id
+     * @return MessageAbstract
+     * @throws Exception
+     */
+    public function setId($id)
+    {
+        if (!is_scalar($id)) {
+            throw new Zend_Mobile_Push_Message_Exception('$id must be a scalar');
+        }
+        $this->_id = $id;
+        return $this;
+    }
+
+    /**
+     * Set Options
+     *
+     * @param array $options
+     * @return Zend_Mobile_Push_Message_Abstract
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setOptions(array $options)
+    {
+        foreach ($options as $k => $v) {
+            $method = 'set' . ucwords($k);
+            if (!method_exists($this, $method)) {
+                throw new Zend_Mobile_Push_Message_Exception('The method "' . $method . "' does not exist.");
+            }
+            $this->$method($v);
+        }
+        return $this;
+    }
+
+
+    /**
+     * Validate Message format
+     *
+     * @return boolean
+     */
+    public function validate()
+    {
+        return true;
+    }
+}

+ 284 - 0
library/Zend/Mobile/Push/Message/Apns.php

@@ -0,0 +1,284 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Message_Abstract **/
+require_once 'Zend/Mobile/Push/Message/Abstract.php';
+
+/**
+ * Apns Message
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Message_Apns extends Zend_Mobile_Push_Message_Abstract
+{
+    /**
+     * Badge Number
+     *
+     * @var int
+     */
+    protected $_badge  = 0;
+
+    /**
+     * Alert
+     *
+     * @var array
+     */
+    protected $_alert  = array();
+
+    /**
+     * Expiration
+     *
+     * @var int
+     */
+    protected $_expire;
+
+    /**
+     * Sound
+     *
+     * @var string
+     */
+    protected $_sound = 'default';
+
+    /**
+     * Custom Data
+     *
+     * @var array
+     */
+    protected $_custom = array();
+
+    /**
+     * Get Alert
+     *
+     * @return array
+     */
+    public function getAlert()
+    {
+        return $this->_alert;
+    }
+
+    /**
+     * Set Alert
+     *
+     * @param string $text
+     * @param string $actionLocKey
+     * @param string $locKey
+     * @param array $locArgs
+     * @param string $launchImage
+     * @return Zend_Mobile_Push_Message_Apns
+     */
+    public function setAlert($text, $actionLocKey=null, $locKey=null, $locArgs=null, $launchImage=null)
+    {
+        if ($text !== null && !is_string($text)) {
+            throw new Zend_Mobile_Push_Message_Exception('$text must be a string');
+        }
+
+        if ($actionLocKey !== null && !is_string($actionLocKey)) {
+            throw new Zend_Mobile_Push_Message_Exception('$actionLocKey must be a string');
+        }
+
+        if ($locKey !== null && !is_string($locKey)) {
+            throw new Zend_Mobile_Push_Message_Exception('$locKey must be a string');
+        }
+
+        if ($locArgs !== null) {
+            if (!is_array($locArgs)) {
+                throw new Zend_Mobile_Push_Message_Exception('$locArgs must be an array of strings');
+            } else {
+                foreach ($locArgs as $str) {
+                    if (!is_string($str)) {
+                        throw new Zend_Mobile_Push_Message_Exception('$locArgs contains an item that is not a string');
+                    }
+                }
+            }
+        }
+
+        if (null !== $launchImage && !is_string($launchImage)) {
+            throw new Zend_Mobile_Push_Message_Exception('$launchImage must be a string');
+        }
+
+        $this->_alert = array(
+            'body'           => $text,
+            'action-loc-key' => $actionLocKey,
+            'loc-key'        => $locKey,
+            'loc-args'       => $locArgs,
+            'launch-image'   => $launchImage,
+        );
+        return $this;
+    }
+
+    /**
+     * Get Badge
+     *
+     * @return int
+     */
+    public function getBadge()
+    {
+        return (int) $this->_badge;
+    }
+
+    /**
+     * Set Badge
+     *
+     * @param int $badge
+     * @return Zend_Mobile_Push_Message_Apns
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setBadge($badge)
+    {
+        if (!is_numeric($badge)) {
+            throw new Zend_Mobile_Push_Message_Exception('$badge must be an integer');
+        }
+        if ($badge < 0) {
+            throw new Zend_Mobile_Push_Message_Exception('$badge must be greater or equal to 0');
+        }
+        $this->_badge = (int) $badge;
+    }
+
+    /**
+     * Get Expire
+     *
+     * @return int
+     */
+    public function getExpire()
+    {
+        return $this->_expire;
+    }
+
+    /**
+     * Set Expire
+     *
+     * @param int $expire
+     * @return Zend_Mobile_Push_Message_Apns
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setExpire($expire)
+    {
+        if (!is_numeric($expire)) {
+            throw new Zend_Mobile_Push_Message_Exception('$expire must be an integer');
+        }
+        $this->_expire = (int) $expire;
+        return $this;
+    }
+
+    /**
+     * Get Sound
+     *
+     * @return string
+     */
+    public function getSound()
+    {
+        return $this->_sound;
+    }
+
+    /**
+     * Set Sound
+     *
+     * @param string $sound
+     * @return Zend_Mobile_Push_Message_Apns
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setSound($sound)
+    {
+        if (!is_string($sound)) {
+            throw new Zend_Mobile_Push_Message_Exception('$sound must be a string');
+        }
+        $this->_sound = $sound;
+        return $this;
+    }
+
+    /**
+     * Add Custom Data
+     *
+     * @param string $key
+     * @param mixed $value
+     * @return Zend_Mobile_Push_Message_Apns
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function addCustomData($key, $value)
+    {
+        if (!is_string($key)) {
+            throw new Zend_Mobile_Push_Message_Exception('$key is not a string');
+        }
+        if ($key == 'aps') {
+            throw new Zend_Mobile_Push_Message_Exception('$key must not be aps as it is reserved by apple');
+        }
+        $this->_custom[$key] = $value;
+    }
+
+    /**
+     * Clear Custom Data
+     *
+     * @return throw new Zend_Mobile_Push_Message_Apns
+     */
+    public function clearCustomData()
+    {
+        $this->_custom = array();
+        return $this;
+    }
+
+    /**
+     * Set Custom Data
+     *
+     * @param array $data
+     * @return Zend_Mobile_Push_Message_Apns
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setCustomData($array)
+    {
+        $this->_custom = array();
+        foreach ($array as $k => $v) {
+            $this->addCustomData($k, $v);
+        }
+        return $this;
+    }
+
+    /**
+     * Get Custom Data
+     *
+     * @return array
+     */
+    public function getCustomData()
+    {
+        return $this->_custom;
+    }
+
+    /**
+     * Validate this is a proper Apns message
+     *
+     * @return boolean
+     */
+    public function validate()
+    {
+        if (!is_string($this->_token) || strlen($this->_token) === 0) {
+            return false;
+        }
+        if (null != $this->_id && !is_numeric($this->_id)) {
+            return false;
+        }
+        return true;
+    }
+}

+ 150 - 0
library/Zend/Mobile/Push/Message/C2dm.php

@@ -0,0 +1,150 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Message_Abstract **/
+require_once 'Zend/Mobile/Push/Message/Abstract.php';
+
+/**
+ * C2dm Message
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Message_C2dm extends Zend_Mobile_Push_Message_Abstract
+{
+    /**
+     * Data key value pairs
+     * 
+     * @var array
+     */
+    protected $_data = array();
+
+    /**
+     * Delay While Idle
+     *
+     * @var boolean
+     */
+    protected $_delay = false;
+
+    /**
+     * Add Data
+     *
+     * @param string $key
+     * @param string $value
+     * @return Zend_Mobile_Push_Message_C2dm
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function addData($key, $value)
+    {
+        if (!is_string($key)) {
+            throw new Zend_Mobile_Push_Message_Exception('$key is not a string');
+        }
+        if (!is_scalar($value)) {
+            throw new Zend_Mobile_Push_Message_Exception('$value is not a string');
+        }
+        $this->_data[$key] = $value;
+        return $this;
+    }
+
+    /**
+     * Set Data
+     *
+     * @param array $data
+     * @return Zend_Mobile_Push_Message_C2dm
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setData(array $data)
+    {
+        $this->clearData();
+        foreach ($data as $k => $v) {
+            $this->addData($k, $v);
+        }
+        return $this;
+    }
+
+    /**
+     * Clear Data
+     *
+     * @return Zend_Mobile_Push_Message_C2dm
+     */
+    public function clearData()
+    {
+        $this->_data = array();
+    }
+
+    /**
+     * Get Data
+     *
+     * @return array
+     */
+    public function getData()
+    {
+        return $this->_data;
+    }
+
+    /**
+     * Set Delay While Idle
+     *
+     * @param boolean $delay
+     * @return Zend_Mobile_Push_Message_C2dm
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setDelayWhileIdle($delay)
+    {
+        if (!is_bool($delay)) {
+            throw new Zend_Mobile_Push_Message_Exception('$delay must be boolean');
+        }
+        $this->_delay = $delay;
+        return $this;
+    }
+
+    /**
+     * Get Delay While Idle
+     *
+     * @return boolean
+     */
+    public function getDelayWhileIdle()
+    {
+        return $this->_delay;
+    }
+
+    /**
+     * Validate this is a proper C2dm message
+     * Does not validate size.
+     *
+     * @return boolean
+     */
+    public function validate()
+    {
+        if (!is_string($this->_token) || strlen($this->_token) === 0) {
+            return false;
+        }
+        if (!is_scalar($this->_id) || strlen($this->_id) === 0) {
+            return false;
+        }
+        return true;
+    }
+}

+ 35 - 0
library/Zend/Mobile/Push/Message/Exception.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Mobile_Push_Exception */
+require_once 'Zend/Mobile/Push/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Message_Exception extends Zend_Mobile_Exception
+{}

+ 79 - 0
library/Zend/Mobile/Push/Message/Interface.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/**
+ * Push Message Interface
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+interface Zend_Mobile_Push_Message_Interface
+{
+    /**
+     * Get Token
+     *
+     * @return string
+     */
+    public function getToken();
+
+    /**
+     * Set Token
+     *
+     * @param string $token
+     * @return Zend_Mobile_Push_Message_Abstract
+     */
+    public function setToken($token);
+
+    /**
+     * Get Id
+     *
+     * @return scalar
+     */
+    public function getId();
+
+    /**
+     * Set Id
+     *
+     * @param scalar $id
+     * @return Zend_Mobile_Push_Message_Abstract
+     */
+    public function setId($id);
+
+    /**
+     * Set Options
+     *
+     * @param array $options
+     * @return Zend_Mobile_Push_Message_Abstract
+     */
+    public function setOptions(array $options);
+
+    /**
+     * Validate Message
+     *
+     * @return boolean
+     */
+    public function validate();
+}

+ 117 - 0
library/Zend/Mobile/Push/Message/Mpns.php

@@ -0,0 +1,117 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Mobile_Push_Message_Abstract **/
+require_once 'Zend/Mobile/Push/Message/Abstract.php';
+
+/** Zend_Uri **/
+require_once 'Zend/Uri.php';
+
+/**
+ * Mpns Message
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Mobile_Push_Message_Mpns extends Zend_Mobile_Push_Message_Abstract
+{
+    /**
+     * Mpns types
+     *
+     * @var string
+     */
+    const TYPE_RAW = 'raw';
+    const TYPE_TILE = 'token';
+    const TYPE_TOAST = 'toast';
+
+    /**
+     * Delay
+     *
+     * @var int
+     */
+    protected $_delay;
+
+    /**
+     * Get Delay
+     *
+     * @return int
+     */
+    abstract public function getDelay();
+
+    /**
+     * Set Delay
+     *
+     * @param int $delay one of const DELAY_* of implementing classes
+     * @return Zend_Mobile_Push_Message_Mpns
+     */
+    abstract public function setDelay($delay);
+
+    /**
+     * Get Notification Type
+     *
+     * @return string
+     */
+    public static function getNotificationType()
+    {
+        return "";
+    }
+
+    /**
+     * Set Token
+     *
+     * @param string $token
+     * @return Zend_Mobile_Push_Message_Mpns
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setToken($token)
+    {
+        if (!is_string($token)) {
+            throw new Zend_Mobile_Push_Message_Exception('$token is not a string');
+        }
+        if (!Zend_Uri::check($token)) {
+            throw new Zend_Mobile_Push_Message_Exception('$token is not a valid URI');
+        }
+        return parent::setToken($token);
+    }
+
+    /**
+     * Get XML Payload
+     *
+     * @return string
+     */
+    abstract public function getXmlPayload();
+
+    /**
+     * Validate proper mpns message
+     *
+     * @return boolean
+     */
+    public function validate()
+    {
+        if (!isset($this->_token) || strlen($this->_token) === 0) {
+            return false;
+        }
+        return parent::validate();
+    }
+}

+ 149 - 0
library/Zend/Mobile/Push/Message/Mpns/Raw.php

@@ -0,0 +1,149 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Mobile_Push_Message_Mpns **/
+require_once 'Zend/Mobile/Push/Message/Mpns.php';
+
+/**
+ * Mpns Raw Message
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Mobile_Push_Message_Mpns_Raw extends Zend_Mobile_Push_Message_Mpns
+{
+    /**
+     * Mpns delays
+     *
+     * @var int
+     */
+    const DELAY_IMMEDIATE = 3;
+    const DELAY_450S = 13;
+    const DELAY_900S = 23;
+
+    /**
+     * Message
+     *
+     * @var string
+     */
+    protected $_msg;
+
+    /**
+     * Get Delay
+     *
+     * @return int
+     */
+    public function getDelay()
+    {
+        if (!$this->_delay) {
+            return self::DELAY_IMMEDIATE;
+        }
+        return $this->_delay;
+    }
+
+    /**
+     * Set Delay
+     *
+     * @param int $delay
+     * @return Zend_Mobile_Push_Message_Mpns_Raw
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setDelay($delay)
+    {
+        if (!in_array($delay, array(
+            self::DELAY_IMMEDIATE,
+            self::DELAY_450S,
+            self::DELAY_900S
+        ))) {
+            throw new Zend_Mobile_Push_Message_Exception('$delay must be one of the DELAY_* constants');
+        }
+        $this->_delay = $delay;
+        return $this;
+    }
+
+    /**
+     * Set Message
+     *
+     * @param string $msg XML string
+     * @return Zend_Mobile_Push_Message_Mpns_Raw
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setMessage($msg)
+    {
+        if (!is_string($msg)) {
+            throw new Zend_Mobile_Push_Message_Exception('$msg is not a string');
+        }
+        if (!simplexml_load_string($msg)) {
+            throw new Zend_Mobile_Push_Message_Exception('$msg is not valid xml');
+        }
+        $this->_msg = $msg;
+        return $this;
+    }
+
+    /**
+     * Get Message
+     *
+     * @return string
+     */
+    public function getMessage()
+    {
+        return $this->_msg;
+    }
+
+    /**
+     * Get Notification Type
+     *
+     * @return string
+     */
+    public static function getNotificationType()
+    {
+        return 'raw';
+    }
+
+    /**
+     * Get XML Payload
+     *
+     * @return string
+     */
+    public function getXmlPayload()
+    {
+        return $this->_msg;
+    }
+
+    /**
+     * Validate proper mpns message
+     *
+     * @return boolean
+     */
+    public function validate()
+    {
+        if (!isset($this->_token) || strlen($this->_token) === 0) {
+            return false;
+        }
+        if (empty($this->_msg)) {
+            return false;
+        }
+        return parent::validate();
+    }
+}

+ 365 - 0
library/Zend/Mobile/Push/Message/Mpns/Tile.php

@@ -0,0 +1,365 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Mobile_Push_Message_Mpns **/
+require_once 'Zend/Mobile/Push/Message/Mpns.php';
+
+/**
+ * Mpns Tile Message
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Mobile_Push_Message_Mpns_Tile extends Zend_Mobile_Push_Message_Mpns
+{
+    /**
+     * Mpns delays
+     *
+     * @var int
+     */
+    const DELAY_IMMEDIATE = 1;
+    const DELAY_450S = 11;
+    const DELAY_900S = 21;
+
+    /**
+     * Background Image
+     *
+     * @var string
+     */
+    protected $_backgroundImage;
+
+    /**
+     * Count
+     *
+     * @var int
+     */
+    protected $_count = 0;
+
+    /**
+     * Title
+     *
+     * @var string
+     */
+    protected $_title;
+
+    /**
+     * Back Background Image
+     *
+     * @var string
+     */
+    protected $_backBackgroundImage;
+
+    /**
+     * Back Title
+     *
+     * @var string
+     */
+    protected $_backTitle;
+
+    /**
+     * Back Content
+     *
+     * @var string
+     */
+    protected $_backContent;
+
+    /**
+     * Tile ID
+     *
+     * @var string
+     */
+    protected $_tileId;
+
+    /**
+     * Get Background Image
+     *
+     * @return string
+     */
+    public function getBackgroundImage()
+    {
+        return $this->_backgroundImage;
+    }
+
+    /**
+     * Set Background Image
+     *
+     * @param string $bgImg
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setBackgroundImage($bgImg)
+    {
+        if (!is_string($bgImg)) {
+            throw new Zend_Mobile_Push_Message_Exception('$bgImg must be a string');
+        }
+        $this->_backgroundImage = $bgImg;
+        return $this;
+    }
+
+    /**
+     * Get Count
+     *
+     * @return int
+     */
+    public function getCount()
+    {
+        return $this->_count;
+    }
+
+    /**
+     * Set Count
+     *
+     * @param int $count
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setCount($count)
+    {
+        if (!is_numeric($count)) {
+            throw new Zend_Mobile_Push_Message_Exception('$count is not numeric');
+        }
+        $this->_count = (int) $count;
+        return $this;
+    }
+
+    /**
+     * Get Title
+     *
+     * @return string
+     */
+    public function getTitle()
+    {
+        return $this->_title;
+    }
+
+    /**
+     * Set Title
+     *
+     * @param string $title
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setTitle($title)
+    {
+        if (!is_string($title)) {
+            throw new Zend_Mobile_Push_Message_Exception('$title must be a string');
+        }
+        $this->_title = $title;
+        return $this;
+    }
+
+    /**
+     * Get Back Background Image
+     *
+     * @return string
+     */
+    public function getBackBackgroundImage()
+    {
+        return $this->_backBackgroundImage;
+    }
+
+    /**
+     * Set Back Background Image
+     *
+     * @param string $bgImg
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setBackBackgroundImage($bgImg)
+    {
+        if (!is_string($bgImg)) {
+            throw new Zend_Mobile_Push_Message_Exception('$bgImg must be a string');
+        }
+        $this->_backBackgroundImage = $bgImg;
+        return $this;
+    }
+
+    /**
+     * Get Back Title
+     *
+     * @return string
+     */
+    public function getBackTitle()
+    {
+        return $this->_backTitle;
+    }
+
+    /**
+     * Set Back Title
+     *
+     * @param string $title
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setBackTitle($title)
+    {
+        if (!is_string($title)) {
+            throw new Zend_Mobile_Push_Message_Exception('$title must be a string');
+        }
+        $this->_backTitle = $title;
+        return $this;
+    }
+
+    /**
+     * Get Back Content
+     *
+     * @return string
+     */
+    public function getBackContent()
+    {
+        return $this->_backContent;
+    }
+
+    /**
+     * Set Back Content
+     * 
+     * @param string $content
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setBackContent($content)
+    {
+        if (!is_string($content)) {
+            throw new Zend_Mobile_Push_Message_Exception('$content must be a string');
+        }
+        $this->_backContent = $content;
+    }
+
+    /**
+     * Get Tile Id
+     *
+     * @return string
+     */
+    public function getTileId()
+    {
+        return $this->_tileId;
+    }
+
+    /**
+     * Set Tile Id
+     *
+     * @param string $tileId
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setTileId($tileId)
+    {
+        if (!is_string($tileId)) {
+            throw new Zend_Mobile_Push_Message_Exception('$tileId is not a string');
+        }
+        $this->_tileId = $tileId;
+        return $this;
+    }
+
+    /**
+     * Get Delay
+     *
+     * @return int
+     */
+    public function getDelay()
+    {
+        if (!$this->_delay) {
+            return self::DELAY_IMMEDIATE;
+        }
+        return $this->_delay;
+    }
+
+    /**
+     * Set Delay
+     *
+     * @param int $delay
+     * @return Zend_Mobile_Push_Message_Mpns_Tile
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setDelay($delay)
+    {
+        if (!in_array($delay, array(
+            self::DELAY_IMMEDIATE,
+            self::DELAY_450S,
+            self::DELAY_900S
+        ))) {
+            throw new Zend_Mobile_Push_Message_Exception('$delay must be one of the DELAY_* constants');
+        }
+        $this->_delay = $delay;
+        return $this;
+    }
+
+    /**
+     * Get Notification Type
+     *
+     * @return string
+     */
+    public static function getNotificationType()
+    {
+        return 'token';
+    }
+
+    /**
+     * Get XML Payload
+     *
+     * @return string
+     */
+    public function getXmlPayload()
+    {
+        $ret = '<?xml version="1.0" encoding="utf-8"?>'
+            . '<wp:Notification xmlns:wp="WPNotification">'
+            . '<wp:Tile' . (($this->_tileId) ? 'Id="' . htmlspecialchars($this->_tileId) . '"' : '') . '>'
+            . '<wp:BackgroundImage>' . htmlspecialchars($this->_backgroundImage) . '</wp:BackgroundImage>'
+            . '<wp:Count>' . (int) $this->_count . '</wp:Count>'
+            . '<wp:Title>' . htmlspecialchars($this->_title) . '</wp:Title>';
+
+        if ($this->_backBackgroundImage) {
+            $ret .= '<wp:BackBackgroundImage>' . htmlspecialchars($this->_backBackgroundImage) . '</wp:BackBackgroundImage>';
+        }
+        if ($this->_backTitle) {
+            $ret .= '<wp:BackTitle>' . htmlspecialchars($this->_backTitle) . '</wp:BackTitle>';
+        }
+        if ($this->_backContent) {
+            $ret .= '<wp:BackContent>' . htmlspecialchars($this->_backContent) . '</wp:BackContent>';
+        }
+
+        $ret .= '</wp:Tile>'
+            . '</wp:Notification>';
+        return $ret;
+    }
+
+    /**
+     * Validate proper mpns message
+     *
+     * @return boolean
+     */
+    public function validate()
+    {
+        if (!isset($this->_token) || strlen($this->_token) === 0) {
+            return false;
+        }
+        if (empty($this->_backgroundImage)) {
+            return false;
+        }
+        if (empty($this->_title)) {
+            return false;
+        }
+        return parent::validate();
+    }
+}

+ 225 - 0
library/Zend/Mobile/Push/Message/Mpns/Toast.php

@@ -0,0 +1,225 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Mobile_Push_Message_Mpns **/
+require_once 'Zend/Mobile/Push/Message/Mpns.php';
+
+/**
+ * Mpns Toast Message
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Mobile_Push_Message_Mpns_Toast extends Zend_Mobile_Push_Message_Mpns
+{
+    /**
+     * Mpns delays
+     *
+     * @var int
+     */
+    const DELAY_IMMEDIATE = 2;
+    const DELAY_450S = 12;
+    const DELAY_900S = 22;
+
+    /**
+     * Title
+     *
+     * @var string
+     */
+    protected $_title;
+
+    /**
+     * Message
+     *
+     * @var string
+     */
+    protected $_msg;
+
+    /**
+     * Params
+     *
+     * @var string
+     */
+    protected $_params;
+
+    /**
+     * Get Title
+     *
+     * @return string
+     */
+    public function getTitle()
+    {
+        return $this->_title;
+    }
+
+    /**
+     * Set Title
+     *
+     * @param string $title
+     * @return Zend_Mobile_Push_Message_Mpns_Toast
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setTitle($title)
+    {
+        if (!is_string($title)) {
+            throw new Zend_Mobile_Push_Message_Exception('$title must be a string');
+        }
+        $this->_title = $title;
+        return $this;
+    }
+
+    /**
+     * Get Message
+     *
+     * @return string
+     */
+    public function getMessage()
+    {
+        return $this->_msg;
+    }
+
+    /**
+     * Set Message
+     *
+     * @param string $msg
+     * @return Zend_Mobile_Push_Message_Mpns_Toast
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setMessage($msg)
+    {
+        if (!is_string($msg)) {
+            throw new Zend_Mobile_Push_Message_Exception('$msg must be a string');
+        }
+        $this->_msg = $msg;
+        return $this;
+    }
+
+    /**
+     * Get Params
+     *
+     * @return string
+     */
+    public function getParams()
+    {
+        return $this->_params;
+    }
+
+    /**
+     * Set Params
+     *
+     * @param string $params
+     * @return Zend_Mobile_Push_Message_Mpns_Toast
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setParams($params)
+    {
+        if (!is_string($params)) {
+            throw new Zend_Mobile_Push_Message_Exception('$params must be a string');
+        }
+        $this->_params = $params;
+        return $this;
+    }
+
+    /**
+     * Get Delay
+     *
+     * @return int
+     */
+    public function getDelay()
+    {
+        if (!$this->_delay) {
+            return self::DELAY_IMMEDIATE;
+        }
+        return $this->_delay;
+    }
+
+    /**
+     * Set Delay
+     *
+     * @param int $delay
+     * @return Zend_Mobile_Push_Message_Mpns_Toast
+     * @throws Zend_Mobile_Push_Message_Exception
+     */
+    public function setDelay($delay)
+    {
+        if (!in_array($delay, array(
+            self::DELAY_IMMEDIATE,
+            self::DELAY_450S,
+            self::DELAY_900S
+        ))) {
+            throw new Zend_Mobile_Push_Message_Exception('$delay must be one of the DELAY_* constants');
+        }
+        $this->_delay = $delay;
+        return $this;
+    }
+
+    /**
+     * Get Notification Type
+     *
+     * @return string
+     */
+    public static function getNotificationType()
+    {
+        return 'toast';
+    }
+
+    /**
+     * Get XML Payload
+     *
+     * @return string
+     */
+    public function getXmlPayload()
+    {
+        $ret = '<?xml version="1.0" encoding="utf-8"?>'
+            . '<wp:Notification xmlns:wp="WPNotification">'
+            . '<wp:Toast>'
+            . '<wp:Text1>' . htmlspecialchars($this->_title) . '</wp:Text1>'
+            . '<wp:Text2>' . htmlspecialchars($this->_msg) . '</wp:Text2>';
+        if (!empty($this->_params)) {
+            $ret .= '<wp:Param>' . htmlspecialchars($this->_params) . '</wp:Param>';
+        }
+        $ret .= '</wp:Toast>'
+            . '</wp:Notification>';
+        return $ret;
+    }
+
+    /**
+     * Validate proper mpns message
+     *
+     * @return boolean
+     */
+    public function validate()
+    {
+        if (!isset($this->_token) || strlen($this->_token) === 0) {
+            return false;
+        }
+        if (empty($this->_title)) {
+            return false;
+        }
+        if (empty($this->_msg)) {
+            return false;
+        }
+        return parent::validate();
+    }
+}

+ 152 - 0
library/Zend/Mobile/Push/Mpns.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/** Zend_Http_Client **/
+require_once 'Zend/Http/Client.php';
+
+/** Zend_Mobile_Push_Abstract **/
+require_once 'Zend/Mobile/Push/Abstract.php';
+
+/** Zend_Mobile_Push_Message_Mpns **/
+require_once 'Zend/Mobile/Push/Message/Mpns.php';
+
+/**
+ * Mpns Push
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Zend_Mobile_Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+class Zend_Mobile_Push_Mpns extends Zend_Mobile_Push_Abstract
+{
+    /**
+     * Http Client
+     *
+     * @var Client
+     */
+    protected $_httpClient;
+
+    /**
+     * Get Http Client
+     *
+     * @return Zend_Http_Client
+     */
+    public function getHttpClient()
+    {
+        if (!$this->_httpClient) {
+            $this->_httpClient = new Zend_Http_Client();
+            $this->_httpClient->setConfig(array(
+                'strictredirects' => true,
+            ));
+        }
+        return $this->_httpClient;
+    }
+
+    /**
+     * Set Http Client
+     *
+     * @return Zend_Mobile_Push_C2dm
+     */
+    public function setHttpClient(Zend_Http_Client $client)
+    {
+        $this->_httpClient = $client;
+        return $this;
+    }
+
+    /**
+     * Send Message
+     *
+     * @param Zend_Mobile_Push_Message_Mpns $message
+     * @return boolean
+     * @throws Zend_Mobile_Push_Exception
+     */
+    public function send(Zend_Mobile_Push_Message_Abstract $message)
+    {
+        if (!$message->validate()) {
+            throw new Zend_Mobile_Push_Exception('The message is not valid.');
+        }
+
+        $this->connect();
+
+        $client = $this->getHttpClient();
+        $client->setUri($message->getToken());
+        $client->setHeaders(array(
+            'Context-Type' => 'text/xml',
+            'Accept' => 'application/*',
+            'X-NotificationClass' => $message->getDelay()
+        ));
+        if ($message->getId()) {
+            $client->setHeaders('X-MessageID', $message->getId());
+        }
+        if ($message->getNotificationType() != Zend_Mobile_Push_Message_Mpns::TYPE_RAW) {
+            $client->setHeaders('X-WindowsPhone-Target', $message->getNotificationType());
+        }
+        $client->setRawData($message->getXmlPayload(), 'text/xml');
+        $response = $client->request('POST');
+        $this->close();
+
+
+        switch ($response->getStatus())
+        {
+            case 200:
+                // check headers for response?  need to test how this actually works to correctly handle different states.
+                if ($response->getHeader('NotificationStatus') == 'QueueFull') {
+                    require_once 'Zend/Mobile/Push/Exception/DeviceQuotaExceeded.php';
+                    throw new Zend_Mobile_Push_Exception_DeviceQuotaExceeded('The devices push notification queue is full, use exponential backoff');
+                }
+                break;
+            case 400:
+                require_once 'Zend/Mobile/Push/Exception/InvalidPayload.php';
+                throw new Zend_Mobile_Push_Exception_InvalidPayload('The message xml was invalid');
+                break;
+            case 401:
+                require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                throw new Zend_Mobile_Push_Exception_InvalidToken('The device token is not valid or there is a mismatch between certificates');
+                break;
+            case 404:
+                require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                throw new Zend_Mobile_Push_Exception_InvalidToken('The device subscription is invalid, stop sending notifications to this device');
+                break;
+            case 405:
+                throw new Zend_Mobile_Push_Exception('Invalid method, only POST is allowed'); // will never be hit unless overwritten
+                break;
+            case 406:
+                require_once 'Zend/Mobile/Push/Exception/QuotaExceeded.php';
+                throw new Zend_Mobile_Push_Exception_QuotaExceeded('The unauthenticated web service has reached the per-day throttling limit');
+                break;
+            case 412:
+                require_once 'Zend/Mobile/Push/Exception/InvalidToken.php';
+                throw new Zend_Mobile_Push_Exception_InvalidToken('The device is in an inactive state.  You may retry once per hour');
+                break;
+            case 503:
+                require_once 'Zend/Mobile/Push/Exception/ServerUnavailable.php';
+                throw new Zend_Mobile_Push_Exception_ServerUnavailable('The server was unavailable.');
+                break;
+            default:
+                break;
+        }
+        return true;
+    }
+}

+ 103 - 0
library/Zend/Mobile/Push/Test/ApnsProxy.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+/** Zend_Mobile_Push_Apns **/
+require_once 'Zend/Mobile/Push/Apns.php';
+
+/**
+ * Apns Test Proxy
+ * This class is utilized for unit testing purposes
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ */
+class Zend_Mobile_Push_Test_ApnsProxy extends Zend_Mobile_Push_Apns 
+{
+    /**
+     * Read Response
+     *
+     * @var string
+     */
+    protected $_readResponse;
+
+    /**
+     * Write Response
+     *
+     * @var mixed
+     */
+    protected $_writeResponse;
+
+    /**
+     * Set the Response
+     *
+     * @param string $str
+     * @return Zend_Mobile_Push_ApnsProxy
+     */
+    public function setReadResponse($str) {
+        $this->_readResponse = $str;
+    }
+
+    /**
+     * Set the write response
+     *
+     * @param mixed $resp
+     * @return void
+     */
+    public function setWriteResponse($resp)
+    {
+        $this->_writeResponse = $resp;
+    }
+
+    /**
+     * Connect
+     *
+     * @return true
+     */
+    protected function _connect($uri) {
+        return true;
+    }
+
+    /**
+     * Return Response
+     *
+     * @param string $length
+     * @return string
+     */
+    protected function _read($length) {
+        $ret = substr($this->_readResponse, 0, $length);
+        $this->_readResponse = null;
+        return $ret;
+    }
+
+    /**
+     * Write and Return Length
+     *
+     * @param string $payload
+     * @return int
+     */
+    protected function _write($payload) {
+        $ret = $this->_writeResponse;
+        $this->_writeResponse = null;
+        return (null === $ret) ? strlen($payload) : $ret;
+    }
+}

+ 57 - 0
tests/Zend/Mobile/AllTests.php

@@ -0,0 +1,57 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: AllTests.php 24593 2012-01-05 20:35:02Z matthew $
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Mobile_AllTests::main');
+}
+
+require_once 'Zend/Mobile/Push/AllTests.php';
+
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ */
+class Zend_Mobile_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Mobile');
+
+        $suite->addTest(Zend_Mobile_Push_AllTests::suite());
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Mobile_AllTests::main') {
+    Zend_Mobile_AllTests::main();
+}

+ 74 - 0
tests/Zend/Mobile/Push/AbstractTest.php

@@ -0,0 +1,74 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Abstract.php';
+require_once 'Zend/Mobile/Push/Message/Abstract.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_Abstract
+ */
+
+class Zend_Mobile_Push_AbstractTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->adapter = new Zend_Mobile_Push_AbstractProxy();
+    }
+
+    public function testConnect()
+    {
+        $ret = $this->adapter->connect();
+        $this->assertEquals($this->adapter, $ret);
+        $this->assertTrue($this->adapter->isConnected());
+    }
+
+    public function testSend()
+    {
+        $msg = new Zend_Mobile_Push_AbstractProxy_Message();
+        $this->assertTrue($this->adapter->send($msg));
+    }
+
+    public function testClose()
+    {
+        $this->adapter->connect();
+        $ret = $this->adapter->close();
+        $this->assertNull($ret);
+        $this->assertFalse($this->adapter->isConnected());
+    }
+}
+
+class Zend_Mobile_Push_AbstractProxy extends Zend_Mobile_Push_Abstract
+{
+    
+}
+
+class Zend_Mobile_Push_AbstractProxy_Message extends Zend_Mobile_Push_Message_Abstract
+{
+
+}

+ 65 - 0
tests/Zend/Mobile/Push/AllTests.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile_Push
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: AllTests.php $
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Mobile_Push_AllTests::main');
+}
+
+require_once 'Zend/Mobile/Push/Message/AllTests.php';
+require_once 'Zend/Mobile/Push/AbstractTest.php';
+require_once 'Zend/Mobile/Push/ApnsTest.php';
+require_once 'Zend/Mobile/Push/C2dmTest.php';
+require_once 'Zend/Mobile/Push/MpnsTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile_Push
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ */
+class Zend_Mobile_Push_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Mobile_Push');
+
+        $suite->addTest(Zend_Mobile_Push_Message_AllTests::suite());
+         
+        $suite->addTestSuite('Zend_Mobile_Push_AbstractTest');
+        $suite->addTestSuite('Zend_Mobile_Push_ApnsTest');
+        $suite->addTestSuite('Zend_Mobile_Push_C2dmTest');
+        $suite->addTestSuite('Zend_Mobile_Push_MpnsTest');
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Mobile_Push_AllTests::main') {
+    Zend_Mobile_Push_AllTests::main();
+}

+ 228 - 0
tests/Zend/Mobile/Push/ApnsTest.php

@@ -0,0 +1,228 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+/** Zend_Mobile_Push_Test_ApnsProxy **/
+require_once 'Zend/Mobile/Push/Test/ApnsProxy.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_Apns
+ */
+class Zend_Mobile_Push_ApnsTest extends PHPUnit_Framework_TestCase
+{
+
+    public function setUp()
+    {
+        $this->apns = new Zend_Mobile_Push_Test_ApnsProxy();
+        $this->message = new Zend_Mobile_Push_Message_Apns();
+    }
+
+    protected function _setupValidBase()
+    {
+        $this->message->setToken('AF0123DE');
+        $this->message->setId(time());
+        $this->message->setAlert('bar');
+        $this->apns->setCertificate('Zend/Mobile/Push/certificate.pem');
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testConnectThrowsExceptionOnInvalidEnvironment()
+    {
+        $this->apns->connect(5);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testConnectThrowsExceptionOnMissingCertificate()
+    {
+        $this->apns->connect();
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSetCertificateThrowsExceptionOnNonString()
+    {
+        $this->apns->setCertificate(array('foo'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSetCertificateThrowsExceptionOnMissingFile()
+    {
+        $this->apns->setCertificate('bar');
+    }
+
+    public function testSetCertificateReturnsInstance()
+    {
+        $ret = $this->apns->setCertificate('Zend/Mobile/Push/certificate.pem');
+        $this->assertEquals($this->apns, $ret);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSetCertificatePassphraseThrowsExceptionOnNonString()
+    {
+        $this->apns->setCertificatePassphrase(array('foo'));
+    }
+
+    public function testSetCertificatePassphraseReturnsInstance()
+    {
+        $ret = $this->apns->setCertificatePassphrase('foobar');
+        $this->assertEquals($this->apns, $ret);
+    }
+
+    public function testSetCertificatePassphraseSetsPassphrase()
+    {
+        $this->apns->setCertificatePassphrase('foobar');
+        $this->assertEquals('foobar', $this->apns->getCertificatePassphrase());
+    }
+
+    public function testConnectReturnsThis()
+    {
+        $this->apns->setCertificate('Zend/Mobile/Push/certificate.pem');
+        $ret = $this->apns->connect();
+        $this->assertEquals($this->apns, $ret);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSendThrowsExceptionOnInvalidMessage()
+    {
+        $this->apns->setCertificate('Zend/Mobile/Push/certificate.pem');
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_ServerUnavailable
+     */
+    public function testSendThrowsServerUnavailableExceptionOnFalseReturn()
+    {
+        $this->_setupValidBase();
+        $this->apns->setWriteResponse(false);
+        $this->apns->send($this->message);
+    }
+
+    public function testSendReturnsTrueOnSuccess()
+    {
+        $this->_setupValidBase();
+        $this->assertTrue($this->apns->send($this->message));
+    }
+
+    public function testSendReturnsTrueOnErr0()
+    {
+        $this->_setupValidBase();
+        $this->assertTrue($this->apns->send($this->message));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSendThrowsExceptionOnProcessingError()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 1, 1, 012345));
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionOnInvalidToken()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 2, 2, 012345));
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidTopic
+     */
+    public function testSendThrowsExceptionOnInvalidTopic()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 3, 3, 012345));
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidPayload
+     */
+    public function testSendThrowsExceptionOnInvalidPayload()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 4, 4, 012345));
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionOnInvalidToken2()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 5, 5, 012345));
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidTopic
+     */
+    public function testSendThrowsExceptionOnInvalidTopic2()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 6, 6, 012345));
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidPayload
+     */
+    public function testSendThrowsExceptionOnMessageTooBig()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 7, 7, 012345));
+        $this->apns->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionOnInvalidToken3()
+    {
+        $this->_setupValidBase();
+        $this->apns->setReadResponse(pack('CCN*', 8, 8, 012345));
+        $this->apns->send($this->message);
+    }
+}

+ 226 - 0
tests/Zend/Mobile/Push/C2dmTest.php

@@ -0,0 +1,226 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/C2dm.php';
+require_once 'Zend/Http/Client.php';
+require_once 'Zend/Http/Client/Adapter/Test.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_C2dm
+ */
+class Zend_Mobile_Push_C2dmTest extends PHPUnit_Framework_TestCase
+{
+
+    public function setUp()
+    {
+        $this->adapter = new Zend_Http_Client_Adapter_Test();
+        $this->client = new Zend_Http_Client();
+        $this->client->setAdapter($this->adapter);
+        $this->c2dm = new Zend_Mobile_Push_C2dm();
+        $this->c2dm->setLoginToken('testing');
+        $this->c2dm->setHttpClient($this->client);
+        $this->message = new Zend_Mobile_Push_Message_C2dm();
+        $this->message->setId('testing');
+        $this->message->setToken('testing');
+        $this->message->addData('testKey', 'testValue');
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSetLoginTokenThrowsExceptionOnNonString()
+    {
+        $this->c2dm->setLoginToken(array());
+    }
+
+    public function testSetLoginToken()
+    {
+        $loginToken = 'a-login-token';
+        $this->c2dm->setLoginToken($loginToken);
+        $this->assertEquals($loginToken, $this->c2dm->getLoginToken());
+    }
+
+    public function testGetHttpClientReturnsDefault()
+    {
+        $c2dm = new Zend_Mobile_Push_C2dm();
+        $this->assertEquals('Zend_Http_Client', get_class($c2dm->getHttpClient()));
+        $this->assertTrue($c2dm->getHttpClient() instanceof Zend_Http_Client);
+    }
+
+    public function testSetHttpClient()
+    {
+        $client = new Zend_Http_Client();
+        $this->c2dm->setHttpClient($client);
+        $this->assertEquals($client, $this->c2dm->getHttpClient());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSendThrowsExceptionWithNonValidMessage()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $this->c2dm->send($msg);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_ServerUnavailable
+     */
+    public function testSendThrowsExceptionWhenServerUnavailable()
+    {
+        $this->adapter->setResponse('HTTP/1.1 500 Internal Server Error' . "\r\n\r\n");
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidAuthToken
+     */
+    public function testSendThrowsExceptionWhenInvalidAuthToken()
+    {
+        $this->adapter->setResponse('HTTP/1.1 401 Unauthorized' . "\r\n\r\n");
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_QuotaExceeded
+     */
+    public function testSendThrowsExceptionWhenQuotaExceeded()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=QuotaExceeded'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_DeviceQuotaExceeded
+     */
+    public function testSendThrowsExceptionWhenDeviceQuotaExceeded()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=DeviceQuotaExceeded'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionWhenMissingRegistration()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=MissingRegistration'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionWhenInvalidRegistration()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=InvalidRegistration'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionWhenMismatchSenderId()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=MismatchSenderId'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionWhenNotRegistered()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=NotRegistered'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidPayload
+     */
+    public function testSendThrowsExceptionWhenMessageTooBig()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" . 
+            'Error=MessageTooBig'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidTopic
+     */
+    public function testSendThrowsExceptionWhenMissingCollapseKey()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=MissingCollapseKey'
+        );
+        $this->c2dm->send($this->message);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSendThrowsExceptionWhenUnknownError()
+    {
+        $this->adapter->setResponse(
+            'HTTP/1.1 200 OK' . "\r\n" .
+            'Context-Type: text/html' . "\r\n\r\n" .
+            'Error=somethinghappened'
+        );
+        $this->c2dm->send($this->message);
+    }
+}

+ 105 - 0
tests/Zend/Mobile/Push/Message/AbstractTest.php

@@ -0,0 +1,105 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile_Push_Message
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Message/Abstract.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile_Push_Message
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ */
+class Zend_Mobile_Push_Message_AbstractTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->msg = new Zend_Mobile_Push_Message_AbstractProxy();
+    }
+
+    public function testSetToken()
+    {
+        $token = 'a-token!';
+        $ret = $this->msg->setToken($token);
+        $this->assertEquals($this->msg, $ret);
+        $this->assertEquals($token, $this->msg->getToken());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTokenThrowsExceptionOnNonStringToken()
+    {
+        $this->msg->setToken(array('dummy'));
+    }
+
+    public function testSetId()
+    {
+        $id = 'wahooo';
+        $ret = $this->msg->setId($id);
+        $this->assertEquals($this->msg, $ret);
+        $this->assertEquals($id, $this->msg->getId());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetIdThrowsExceptionOnNonScalar()
+    {
+        $this->msg->setId(array('foo'));
+    }
+
+    public function testSetOptions()
+    {
+        $token = 'token';
+        $id = 'id';
+
+        $ret = $this->msg->setOptions(array(
+            'id' => $id,
+            'token' => $token
+        ));
+        $this->assertEquals($this->msg, $ret);
+        $this->assertEquals($token, $this->msg->getToken());
+        $this->assertEquals($id, $this->msg->getId());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetOptionsThrowsExceptionOnMissingMethod()
+    {
+        $this->msg->setOptions(array(
+            'thisMethodDoesNotExist' => 'value'
+        ));
+    }
+
+    public function testValidateReturnsTrue()
+    {
+        $this->assertTrue($this->msg->validate());
+    }
+}
+
+class Zend_Mobile_Push_Message_AbstractProxy extends Zend_Mobile_Push_Message_Abstract
+{
+    
+}

+ 63 - 0
tests/Zend/Mobile/Push/Message/AllTests.php

@@ -0,0 +1,63 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: AllTests.php $
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Mobile_Push_Message_AllTests::main');
+}
+
+require_once 'Zend/Mobile/Push/Message/Mpns/AllTests.php';
+require_once 'Zend/Mobile/Push/Message/AbstractTest.php';
+require_once 'Zend/Mobile/Push/Message/ApnsTest.php';
+require_once 'Zend/Mobile/Push/Message/C2dmTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile_Push_Message
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ */
+class Zend_Mobile_Push_Message_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Mobile_Push_Message');
+
+        $suite->addTest(Zend_Mobile_Push_Message_Mpns_AllTests::suite());
+         
+        $suite->addTestSuite('Zend_Mobile_Push_Message_AbstractTest');
+        $suite->addTestSuite('Zend_Mobile_Push_Message_ApnsTest');
+        $suite->addTestSuite('Zend_Mobile_Push_Message_C2dmTest');
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Mobile_Push_Message_AllTests::main') {
+    Zend_Mobile_Push_Message_AllTests::main();
+}

+ 212 - 0
tests/Zend/Mobile/Push/Message/ApnsTest.php

@@ -0,0 +1,212 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Message/Apns.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_Apns
+ */
+class Zend_Mobile_Push_Message_ApnsTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->message = new Zend_Mobile_Push_Message_Apns();
+    }
+
+    public function testSetAlertTextReturnsCorrectly()
+    {
+        $text = 'my alert';
+        $ret = $this->message->setAlert($text);
+        $this->assertTrue($ret instanceof Zend_Mobile_Push_Message_Apns);
+        $checkText = $this->message->getAlert();
+        $this->assertTrue(is_array($checkText));
+        $this->assertEquals($checkText['body'], $text);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetAlertThrowsExceptionOnTextNonString()
+    {
+        $this->message->setAlert(array());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetAlertThrowsExceptionOnActionLocKeyNonString()
+    {
+        $this->message->setAlert('text', array());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetAlertThrowsExceptionOnLocKeyNonString()
+    {
+        $this->message->setAlert('text', 'button', array());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetAlertThrowsExceptionOnLocArgsNonArray()
+    {
+        $this->message->setAlert('text', 'button', 'action', 'whoa');
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetAlertThrowsExceptionOnLaunchImageNonString()
+    {
+        $this->message->setAlert('text', 'button', 'action', array('locale'), array());
+    }
+
+    public function testSetBadgeReturnsCorrectNumber()
+    {
+        $num = 5;
+        $this->message->setBadge($num);
+        $this->assertEquals($this->message->getBadge(), $num);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetBadgeNonNumericThrowsException()
+    {
+        $this->message->setBadge('string!');
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetBadgeNegativeNumberThrowsException()
+    {
+        $this->message->setBadge(-5);
+    }
+
+    public function testSetExpireReturnsInteger()
+    {
+        $expire = 100;
+        $this->message->setExpire($expire);
+        $this->assertEquals($this->message->getExpire(), $expire);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetExpireNonNumericThrowsException()
+    {
+        $this->message->setExpire('sting!');
+    }
+
+    public function testSetSoundReturnsString()
+    {
+        $sound = 'test';
+        $this->message->setSound($sound);
+        $this->assertEquals($this->message->getSound(), $sound);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetSoundThrowsExceptionOnNonString()
+    {
+        $this->message->setSound(array());
+    }
+
+    public function testAddCustomDataReturnsSetData()
+    {
+        $addKey1 = 'test1';
+        $addValue1 = array('val', 'ue', '1');
+
+        $addKey2 = 'test2';
+        $addValue2 = 'value2';
+
+        $expected = array($addKey1 => $addValue1);
+        $this->message->addCustomData($addKey1, $addValue1);
+        $this->assertEquals($this->message->getCustomData(), $expected);
+
+        $expected[$addKey2] = $addValue2;
+        $this->message->addCustomData($addKey2, $addValue2);
+        $this->assertEquals($this->message->getCustomData(), $expected);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testAddCustomDataThrowsExceptionOnNonStringKey()
+    {
+        $this->message->addCustomData(array('key'), 'val');
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testAddCustomDataThrowsExceptionOnReservedKeyAps()
+    {
+        $this->message->addCustomData('aps', 'val');
+    }
+
+    public function testClearCustomDataClearsData()
+    {
+        $this->message->addCustomData('key', 'val');
+        $this->message->clearCustomData();
+        $this->assertEquals($this->message->getCustomData(), array());
+    }
+
+    public function testSetCustomData()
+    {
+        $data = array('key' => 'val', 'key2' => array(1, 2, 3, 4, 5));
+        $this->message->setCustomData($data);
+        $this->assertEquals($this->message->getCustomData(), $data);
+    }
+
+    public function testValidateReturnsFalseWithoutToken()
+    {
+        $this->assertFalse($this->message->validate());
+    }
+
+    public function testValidateReturnsFalseIdNotNumeric()
+    {
+        $this->message->setToken('abc');
+        $this->message->setId('def');
+        $this->assertFalse($this->message->validate());
+    }
+
+    public function testValidateReturnsTrueWhenProperlySet()
+    {
+        $this->message->setToken('abc');
+        $this->assertTrue($this->message->validate());
+
+        $this->message->setId(12345);
+        $this->assertTrue($this->message->validate());
+    }
+}

+ 115 - 0
tests/Zend/Mobile/Push/Message/C2dmTest.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Message/C2dm.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_C2dm
+ */
+class Zend_Mobile_Push_Message_C2dmTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testAddDataThrowsExceptionOnNonStringKey()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->addData(array(), 'value');
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testAddDataThrowsExceptionOnNonScalarValue()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->addData('key', new stdClass);
+    }
+
+    public function testSetData()
+    {
+        $data = array('key' => 'value');
+        $data2 = array('key2' => 'value2');
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+
+        $msg->setData($data);
+        $this->assertEquals($data, $msg->getData());
+
+        $msg->setData($data2);
+        $this->assertEquals($data2, $msg->getData());
+    }
+
+    public function testDelayWhileIdle()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->setDelayWhileIdle(true);
+        $this->assertTrue($msg->getDelayWhileIdle());
+        $msg->setDelayWhileIdle(false);
+        $this->assertFalse($msg->getDelayWhileIdle());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testDelayWhileIdleThrowsExceptionOnInvalidValue()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->setDelayWhileIdle('true');
+    }
+
+    public function testValidateWithoutTokenReturnsFalse()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->setId('collapseKey');
+        $this->assertFalse($msg->validate());
+    }
+
+    public function testValidateWithoutIdReturnsFalse()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->setToken('a-token!');
+        $this->assertFalse($msg->validate());
+    }
+
+    public function testValidateWithIdAndTokenReturnsTrue()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->setId('collapseKey');
+        $msg->setToken('a-token!');
+        $this->assertTrue($msg->validate());
+    }
+
+    public function testValidateWithIdAsIntAndTokenReturnsTrue()
+    {
+        $msg = new Zend_Mobile_Push_Message_C2dm();
+        $msg->setId(time());
+        $msg->setToken('da-token');
+        $this->assertTrue($msg->validate());
+    }
+}

+ 60 - 0
tests/Zend/Mobile/Push/Message/Mpns/AllTests.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile_Push_Message_Mpns
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: AllTests.php $
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Mobile_Push_Message_Mpns_AllTests::main');
+}
+
+require_once 'Zend/Mobile/Push/Message/Mpns/RawTest.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/TileTest.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/ToastTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile_Push_Message_Mpns
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ */
+class Zend_Mobile_Push_Message_Mpns_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Mobile_Push_Message_Mpns');
+        
+        $suite->addTestSuite('Zend_Mobile_Push_Message_Mpns_RawTest');
+        $suite->addTestSuite('Zend_Mobile_Push_Message_Mpns_TileTest');
+        $suite->addTestSuite('Zend_Mobile_Push_Message_Mpns_ToastTest');
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Mobile_Push_Message_Mpns_AllTests::main') {
+    Zend_Mobile_Push_Message_Mpns_AllTests::main();
+}

+ 139 - 0
tests/Zend/Mobile/Push/Message/Mpns/RawTest.php

@@ -0,0 +1,139 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Message/Mpns/Raw.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_Mpns
+ */
+class Zend_Mobile_Push_Message_Mpns_RawTest extends PHPUnit_Framework_TestCase
+{
+    private $_msg;
+
+    public function setUp()
+    {
+        $this->_msg = new Zend_Mobile_Push_Message_Mpns_Raw();
+    }
+
+    public function testSetToken()
+    {
+        $token = 'http://sn1.notify.live.net/throttledthirdparty/bogusdata';
+        $this->_msg->setToken($token);
+        $this->assertEquals($token, $this->_msg->getToken());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTokenNonStringThrowsException()
+    {
+        $token = array('foo' => 'bar');
+        $this->_msg->setToken($token);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTokenInvalidUrlThrowsException()
+    {
+        $token = 'notaurl';
+        $this->_msg->setToken($token);
+    }
+
+    public function testGetNotificationType()
+    {
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns::TYPE_RAW, $this->_msg->getNotificationType());
+    }
+
+    public function testSetMessage()
+    {
+        $msg = '<root><foo /></root>';
+        $this->_msg->setMessage($msg);
+        $this->assertEquals($msg, $this->_msg->getMessage());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetMessageThrowsExceptionOnNonString()
+    {
+        $msg = array('foo' => 'bar');
+        $this->_msg->setMessage($msg);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testSetMessageThrowsExceptionOnNonXml()
+    {
+        $msg = 'foo';
+        $this->_msg->setMessage($msg);
+    }
+
+    public function testGetDelayHasDefaultOfImmediate()
+    {
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Raw::DELAY_IMMEDIATE, $this->_msg->getDelay());
+    }
+
+    public function testSetDelay()
+    {
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Raw::DELAY_450S);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Raw::DELAY_450S, $this->_msg->getDelay());
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Raw::DELAY_900S);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Raw::DELAY_900S, $this->_msg->getDelay());
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Raw::DELAY_IMMEDIATE);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Raw::DELAY_IMMEDIATE, $this->_msg->getDelay());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetDelayThrowsExceptionOnInvalidDelay()
+    {
+        $delay = 'foo';
+        $this->_msg->setDelay($delay);
+    }
+
+    public function testValidate()
+    {
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setToken('http://sn1.notify.live.net/throttledthirdparty/bogusdata');
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setMessage('<root><bar>foo</bar></root>');
+        $this->assertTrue($this->_msg->validate());
+    }
+
+    public function testGetXmlPayload()
+    {
+        $raw = '<root><bar>foo</bar></root>';
+        $this->_msg->setToken('http://sn1.notify.live.net/throttledthirdparty/abcdef1234567890');
+        $this->_msg->setMessage($raw);
+        $this->assertEquals($this->_msg->getXmlPayload(), $raw);
+    }
+}

+ 234 - 0
tests/Zend/Mobile/Push/Message/Mpns/TileTest.php

@@ -0,0 +1,234 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Message/Mpns/Tile.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_Mpns
+ */
+class Zend_Mobile_Push_Message_Mpns_TileTest extends PHPUnit_Framework_TestCase
+{
+    private $_msg;
+
+    public function setUp()
+    {
+        $this->_msg = new Zend_Mobile_Push_Message_Mpns_Tile();
+    }
+
+    public function testSetToken()
+    {
+        $token = 'http://sn1.notify.live.net/throttledthirdparty/bogusdata';
+        $this->_msg->setToken($token);
+        $this->assertEquals($token, $this->_msg->getToken());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTokenNonStringThrowsException()
+    {
+        $token = array('foo' => 'bar');
+        $this->_msg->setToken($token);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTokenInvalidUrlThrowsException()
+    {
+        $token = 'notaurl';
+        $this->_msg->setToken($token);
+    }
+
+    public function testGetNotificationType()
+    {
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns::TYPE_TILE, $this->_msg->getNotificationType());
+    }
+
+    public function testGetDelayHasDefaultOfImmediate()
+    {
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Tile::DELAY_IMMEDIATE, $this->_msg->getDelay());
+    }
+
+    public function testSetBackgroundImage()
+    {
+        $image = 'foo.bar';
+        $this->_msg->setBackgroundImage($image);
+        $this->assertEquals($image, $this->_msg->getBackgroundImage());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetBackgroundImageThrowsExceptionOnNonString()
+    {
+        $image = array('foo' => 'bar');
+        $this->_msg->setBackgroundImage($image);
+    }
+
+    public function testSetCount()
+    {
+        $negCount = -1;
+        $posCount = 1;
+        $this->_msg->setCount($negCount);
+        $this->assertEquals($negCount, $this->_msg->getCount());
+        $this->_msg->setCount($posCount);
+        $this->assertEquals($posCount, $this->_msg->getCount());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetCountThrowsExceptionOnNonNumeric()
+    {
+        $count = 'five';
+        $this->_msg->setCount($count);
+    }
+
+    public function testSetTitle()
+    {
+        $title = 'foo';
+        $this->_msg->setTitle($title);
+        $this->assertEquals($title, $this->_msg->getTitle());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTitleThrowsExceptionOnNonString()
+    {
+        $title = array('foo' => 'bar');
+        $this->_msg->setTitle($title);
+    }
+
+    public function testSetBackBackgroundImage()
+    {
+        $image = 'foo.bar';
+        $this->_msg->setBackBackgroundImage($image);
+        $this->assertEquals($image, $this->_msg->getBackBackgroundImage());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetBackBackgroundImageThrowsExceptionOnNonString()
+    {
+        $image = array('foo' => 'bar');
+        $this->_msg->setBackBackgroundImage($image);
+    }
+
+    public function testSetBackTitle()
+    {
+        $title = 'foo';
+        $this->_msg->setBackTitle($title);
+        $this->assertEquals($title, $this->_msg->getBackTitle());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetBackTitleThrowsExceptionOnNonString()
+    {
+        $title = array('foo' => 'bar');
+        $this->_msg->setBackTitle($title);
+    }
+
+    public function testSetBackContent()
+    {
+        $content = 'foo';
+        $this->_msg->setBackContent($content);
+        $this->assertEquals($content, $this->_msg->getBackContent());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetBackContentThrowsExceptionOnNonString()
+    {
+        $content = array('foo' => 'bar');
+        $this->_msg->setBackContent($content);
+        $this->assertEquals($content, $this->_msg->getBackContent());
+    }
+
+    public function testSetTileId()
+    {
+        $id = '?foo.bar';
+        $this->_msg->setTileId($id);
+        $this->assertEquals($id, $this->_msg->getTileId());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTileIdThrowsExceptionOnNonString()
+    {
+        $id = array('foo' => 'bar');
+        $this->_msg->setTileId($id);
+    }
+
+
+    public function testSetDelay()
+    {
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Tile::DELAY_450S);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Tile::DELAY_450S, $this->_msg->getDelay());
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Tile::DELAY_900S);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Tile::DELAY_900S, $this->_msg->getDelay());
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Tile::DELAY_IMMEDIATE);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Tile::DELAY_IMMEDIATE, $this->_msg->getDelay());
+    }
+
+    public function testValidate()
+    {
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setToken('http://sn1.notify.live.net/throttledthirdparty/bogusdata');
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setBackgroundImage('foo.bar');
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setTitle('foo');
+        $this->assertTrue($this->_msg->validate());
+    }
+
+    public function testGetXmlPayload()
+    {
+        $title = 'foo';
+        $backgroundImage = 'bar.jpg';
+        $count = 5;
+        $this->_msg->setToken('http://sn1.notify.live.net/throttledthirdparty/abcdef1234567890');
+        $this->_msg->setTitle($title);
+        $this->_msg->setBackgroundImage($backgroundImage);
+        $this->_msg->setCount($count);
+
+        $xml = new SimpleXMLElement($this->_msg->getXmlPayload(), 0, false, 'wp', true);
+
+        $this->assertEquals($title, (string) $xml->Tile->Title);
+        $this->assertEquals($backgroundImage, (string) $xml->Tile->BackgroundImage);
+        $this->assertEquals($count, (int) $xml->Tile->Count);
+    }
+
+}

+ 161 - 0
tests/Zend/Mobile/Push/Message/Mpns/ToastTest.php

@@ -0,0 +1,161 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Message/Mpns/Toast.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_Mpns
+ */
+class Zend_Mobile_Push_Message_Mpns_ToastTest extends PHPUnit_Framework_TestCase
+{
+    private $_msg;
+
+    public function setUp()
+    {
+        $this->_msg = new Zend_Mobile_Push_Message_Mpns_Toast();
+    }
+
+    public function testSetToken()
+    {
+        $token = 'http://sn1.notify.live.net/throttledthirdparty/bogusdata';
+        $this->_msg->setToken($token);
+        $this->assertEquals($token, $this->_msg->getToken());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTokenNonStringThrowsException()
+    {
+        $token = array('foo' => 'bar');
+        $this->_msg->setToken($token);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTokenInvalidUrlThrowsException()
+    {
+        $token = 'notaurl';
+        $this->_msg->setToken($token);
+    }
+
+    public function testGetNotificationType()
+    {
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns::TYPE_TOAST, $this->_msg->getNotificationType());
+    }
+
+    public function testSetTitle()
+    {
+        $title = 'foo';
+        $this->_msg->setTitle($title);
+        $this->assertEquals($title, $this->_msg->getTitle());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetTitleThrowsExceptionOnNonString()
+    {
+        $title = array('foo' => 'bar');
+        $this->_msg->setTitle($title);
+    }
+
+    public function testSetMessage()
+    {
+        $msg = 'foo';
+        $this->_msg->setMessage($msg);
+        $this->assertEquals($msg, $this->_msg->getMessage());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetMessageThrowsExceptionOnNonString()
+    {
+        $msg = array('foo' => 'bar');
+        $this->_msg->setMessage($msg);
+    }
+
+    public function testSetParams()
+    {
+        $params = '?foo=bar';
+        $this->_msg->setParams($params);
+        $this->assertEquals($params, $this->_msg->getParams());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Message_Exception
+     */
+    public function testSetParamsThrowsExceptionOnNonString()
+    {
+        $params = array('foo' => 'bar');
+        $this->_msg->setParams($params);
+    }
+
+    public function testGetDelayHasDefaultOfImmediate()
+    {
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Toast::DELAY_IMMEDIATE, $this->_msg->getDelay());
+    }
+
+    public function testSetDelay()
+    {
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Toast::DELAY_450S);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Toast::DELAY_450S, $this->_msg->getDelay());
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Toast::DELAY_900S);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Toast::DELAY_900S, $this->_msg->getDelay());
+        $this->_msg->setDelay(Zend_Mobile_Push_Message_Mpns_Toast::DELAY_IMMEDIATE);
+        $this->assertEquals(Zend_Mobile_Push_Message_Mpns_Toast::DELAY_IMMEDIATE, $this->_msg->getDelay());
+    }
+
+    public function testValidate()
+    {
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setToken('http://sn1.notify.live.net/throttledthirdparty/bogusdata');
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setTitle('foo');
+        $this->assertFalse($this->_msg->validate());
+        $this->_msg->setMessage('bar');
+        $this->assertTrue($this->_msg->validate());
+    }
+
+    public function testGetXmlPayload()
+    {
+        $title = 'Foo';
+        $message = 'Bar';
+        $this->_msg->setToken('http://sn1.notify.live.net/throttledthirdparty/abcdef1234567890');
+        $this->_msg->setTitle($title);
+        $this->_msg->setMessage($message);
+
+        $xml = new SimpleXMLElement($this->_msg->getXmlPayload(), 0, false, 'wp', true);
+
+        $this->assertEquals($title, (string) $xml->Toast->Text1);
+        $this->assertEquals($message, (string) $xml->Toast->Text2);
+    }
+}

+ 184 - 0
tests/Zend/Mobile/Push/MpnsTest.php

@@ -0,0 +1,184 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id $
+ */
+
+require_once 'Zend/Mobile/Push/Mpns.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/Raw.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/Tile.php';
+require_once 'Zend/Mobile/Push/Message/Mpns/Toast.php';
+require_once 'Zend/Http/Client.php';
+require_once 'Zend/Http/Client/Adapter/Test.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Mobile
+ * @subpackage Push
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Mobile
+ * @group      Zend_Mobile_Push
+ * @group      Zend_Mobile_Push_Mpns
+ */
+class Zend_Mobile_Push_MpnsTest extends PHPUnit_Framework_TestCase
+{
+
+    public function setUp()
+    {
+        $this->adapter = new Zend_Http_Client_Adapter_Test();
+        $this->client = new Zend_Http_Client();
+        $this->client->setAdapter($this->adapter);
+        $this->mpns = new Zend_Mobile_Push_Mpns();
+        $this->mpns->setHttpClient($this->client);
+    }
+
+    public function getMessage($type) {
+        switch ($type) {
+            case 'tile':
+                $message = new Zend_Mobile_Push_Message_Mpns_Tile();
+                break;
+            case 'toast':
+                $message = new Zend_Mobile_Push_Message_Mpns_Toast();
+                break;
+            default:
+                $message = new Zend_Mobile_Push_Message_Mpns_Raw();
+                $message->setMessage('<w><oa h="" /></w>');
+        }
+        $message->setToken('http://this.is.a.url.com');
+        return $message;
+    }
+
+    public function testGetHttpClientReturnsDefault()
+    {
+        $mpns = new Zend_Mobile_Push_Mpns();
+        $this->assertEquals('Zend_Http_Client', get_class($mpns->getHttpClient()));
+        $this->assertTrue($mpns->getHttpClient() instanceof Zend_Http_Client);
+    }
+
+    public function testSetHttpClient()
+    {
+        $client = new Zend_Http_Client();
+        $this->mpns->setHttpClient($client);
+        $this->assertEquals($client, $this->mpns->getHttpClient());
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSendThrowsExceptionWithNonValidMessage()
+    {
+        $msg = new Zend_Mobile_Push_Message_Mpns_Tile();
+        $this->mpns->send($msg);
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_DeviceQuotaExceeded
+     */
+    public function testSendThrowsExceptionWhenDeviceQuotaExceeded()
+    {
+        $this->adapter->setResponse('HTTP/1.1 200 OK' . "\r\n" . 'NotificationStatus: QueueFull' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidPayload
+     */
+    public function testSendThrowsExceptionWhenInvalidPayload()
+    {
+        $this->adapter->setResponse('HTTP/1.1 400 Bad Request' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionWhenInvalidToken()
+    {
+        $this->adapter->setResponse('HTTP/1.1 401 Unauthorized' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionWhenDeviceNotRegistered()
+    {
+        $this->adapter->setResponse('HTTP/1.1 404 Not Found' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception
+     */
+    public function testSendThrowsExceptionWhenMethodNotPost()
+    {
+        $this->adapter->setResponse('HTTP/1.1 405 Method Not Allowed' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_QuotaExceeded
+     */
+    public function testSendThrowsExceptionWhenServiceQuotaExceeded()
+    {
+        $this->adapter->setResponse('HTTP/1.1 406 Not Acceptable');
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_InvalidToken
+     */
+    public function testSendThrowsExceptionWhenInvalidToken2()
+    {
+        $this->adapter->setResponse('HTTP/1.1 412 Precondition Failed' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    /**
+     * @expectedException Zend_Mobile_Push_Exception_ServerUnavailable
+     */
+    public function testSendThrowsExceptionWhenServerUnavailable()
+    {
+        $this->adapter->setResponse('HTTP/1.1 503 Service Unavailable' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+    }
+
+    public function testAllOk()
+    {
+        $this->adapter->setResponse('HTTP/1.1 200 OK' . "\r\n\r\n");
+        $this->mpns->send($this->getMessage('raw'));
+
+        $toast = $this->getMessage('toast');
+        $toast->setTitle('Foo');
+        $toast->setMessage('Bar');
+        $this->mpns->send($toast);
+
+        $tile = $this->getMessage('tile');
+        $tile->setBackgroundImage('red.jpg');
+        $tile->setCount(1);
+        $tile->setTitle('Foo Bar');
+
+        // other optional attributes for wp7.1+
+        $tile->setTileId('/SomeAction.xaml');
+        $tile->setBackBackgroundImage('blue.jpg');
+        $tile->setBackTitle('Bar');
+        $tile->setBackContent('Foo');
+    }
+}

+ 0 - 0
tests/Zend/Mobile/Push/certificate.pem