web/lib/Zend/Mobile/Push/Apns.php
changeset 808 6b6c2214f778
child 1230 68c69c656a2c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/Zend/Mobile/Push/Apns.php	Thu Mar 21 19:52:38 2013 +0100
@@ -0,0 +1,391 @@
+<?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.sandbox.push.apple.com:2196',
+        'ssl://feedback.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;
+        }
+        if (!is_null($message->getBadge())) {
+            $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;
+    }
+}