Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
392 lines (355 sloc) 11.5 KB
<?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;
}
}