/lib/Zend/Mobile/Push/Apns.php
PHP | 391 lines | 209 code | 36 blank | 146 comment | 30 complexity | 8a9b4c310458933c7af0d067b1da1640 MD5 | raw file
- <?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;
- }
- }