File indexing completed on 2025-03-02 05:29:36
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Mobile 0017 * @subpackage Zend_Mobile_Push 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** Zend_Mobile_Push_Abstract **/ 0024 // require_once 'Zend/Mobile/Push/Abstract.php'; 0025 0026 /** Zend_Mobile_Push_Message_Apns **/ 0027 // require_once 'Zend/Mobile/Push/Message/Apns.php'; 0028 0029 /** 0030 * APNS Push 0031 * 0032 * @category Zend 0033 * @package Zend_Mobile 0034 * @subpackage Zend_Mobile_Push 0035 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0036 * @license http://framework.zend.com/license/new-bsd New BSD License 0037 * @version $Id$ 0038 */ 0039 class Zend_Mobile_Push_Apns extends Zend_Mobile_Push_Abstract 0040 { 0041 0042 /** 0043 * @const int apple server uri constants 0044 */ 0045 const SERVER_SANDBOX_URI = 0; 0046 const SERVER_PRODUCTION_URI = 1; 0047 const SERVER_FEEDBACK_SANDBOX_URI = 2; 0048 const SERVER_FEEDBACK_PRODUCTION_URI = 3; 0049 0050 /** 0051 * Apple Server URI's 0052 * 0053 * @var array 0054 */ 0055 protected $_serverUriList = array( 0056 'ssl://gateway.sandbox.push.apple.com:2195', 0057 'ssl://gateway.push.apple.com:2195', 0058 'ssl://feedback.sandbox.push.apple.com:2196', 0059 'ssl://feedback.push.apple.com:2196' 0060 ); 0061 0062 /** 0063 * Current Environment 0064 * 0065 * @var int 0066 */ 0067 protected $_currentEnv; 0068 0069 /** 0070 * Socket 0071 * 0072 * @var resource 0073 */ 0074 protected $_socket; 0075 0076 /** 0077 * Certificate 0078 * 0079 * @var string 0080 */ 0081 protected $_certificate; 0082 0083 /** 0084 * Certificate Passphrase 0085 * 0086 * @var string 0087 */ 0088 protected $_certificatePassphrase; 0089 0090 /** 0091 * Get Certficiate 0092 * 0093 * @return string 0094 */ 0095 public function getCertificate() 0096 { 0097 return $this->_certificate; 0098 } 0099 0100 /** 0101 * Set Certificate 0102 * 0103 * @param string $cert 0104 * @return Zend_Mobile_Push_Apns 0105 * @throws Zend_Mobile_Push_Exception 0106 */ 0107 public function setCertificate($cert) 0108 { 0109 if (!is_string($cert)) { 0110 throw new Zend_Mobile_Push_Exception('$cert must be a string'); 0111 } 0112 if (!file_exists($cert)) { 0113 throw new Zend_Mobile_Push_Exception('$cert must be a valid path to the certificate'); 0114 } 0115 $this->_certificate = $cert; 0116 return $this; 0117 } 0118 0119 /** 0120 * Get Certificate Passphrase 0121 * 0122 * @return string 0123 */ 0124 public function getCertificatePassphrase() 0125 { 0126 return $this->_certificatePassphrase; 0127 } 0128 0129 /** 0130 * Set Certificate Passphrase 0131 * 0132 * @param string $passphrase 0133 * @return Zend_Mobile_Push_Apns 0134 * @throws Zend_Mobile_Push_Exception 0135 */ 0136 public function setCertificatePassphrase($passphrase) 0137 { 0138 if (!is_string($passphrase)) { 0139 throw new Zend_Mobile_Push_Exception('$passphrase must be a string'); 0140 } 0141 $this->_certificatePassphrase = $passphrase; 0142 return $this; 0143 } 0144 0145 /** 0146 * Connect to Socket 0147 * 0148 * @param string $uri 0149 * @return bool 0150 * @throws Zend_Mobile_Push_Exception_ServerUnavailable 0151 */ 0152 protected function _connect($uri) 0153 { 0154 $ssl = array( 0155 'local_cert' => $this->_certificate, 0156 ); 0157 if ($this->_certificatePassphrase) { 0158 $ssl['passphrase'] = $this->_certificatePassphrase; 0159 } 0160 0161 $this->_socket = stream_socket_client($uri, 0162 $errno, 0163 $errstr, 0164 ini_get('default_socket_timeout'), 0165 STREAM_CLIENT_CONNECT, 0166 stream_context_create(array( 0167 'ssl' => $ssl, 0168 )) 0169 ); 0170 0171 if (!is_resource($this->_socket)) { 0172 // require_once 'Zend/Mobile/Push/Exception/ServerUnavailable.php'; 0173 throw new Zend_Mobile_Push_Exception_ServerUnavailable(sprintf('Unable to connect: %s: %d (%s)', 0174 $uri, 0175 $errno, 0176 $errstr 0177 )); 0178 } 0179 0180 stream_set_blocking($this->_socket, 0); 0181 stream_set_write_buffer($this->_socket, 0); 0182 return true; 0183 } 0184 0185 /** 0186 * Read from the Socket Server 0187 * 0188 * @param int $length 0189 * @return string 0190 */ 0191 protected function _read($length) { 0192 $data = false; 0193 if (!feof($this->_socket)) { 0194 $data = fread($this->_socket, $length); 0195 } 0196 return $data; 0197 } 0198 0199 /** 0200 * Write to the Socket Server 0201 * 0202 * @param string $payload 0203 * @return int 0204 */ 0205 protected function _write($payload) { 0206 return @fwrite($this->_socket, $payload); 0207 } 0208 0209 /** 0210 * Connect to the Push Server 0211 * 0212 * @param int|string $env 0213 * @throws Zend_Mobile_Push_Exception 0214 * @throws Zend_Mobile_Push_Exception_ServerUnavailable 0215 * @return Zend_Mobile_Push_Abstract 0216 */ 0217 public function connect($env = self::SERVER_PRODUCTION_URI) 0218 { 0219 if ($this->_isConnected) { 0220 if ($this->_currentEnv == self::SERVER_PRODUCTION_URI) { 0221 return $this; 0222 } 0223 $this->close(); 0224 } 0225 0226 if (!isset($this->_serverUriList[$env])) { 0227 throw new Zend_Mobile_Push_Exception('$env is not a valid environment'); 0228 } 0229 0230 if (!$this->_certificate) { 0231 throw new Zend_Mobile_Push_Exception('A certificate must be set prior to calling ::connect'); 0232 } 0233 0234 $this->_connect($this->_serverUriList[$env]); 0235 0236 $this->_currentEnv = $env; 0237 $this->_isConnected = true; 0238 return $this; 0239 } 0240 0241 0242 0243 /** 0244 * Feedback 0245 * 0246 * @return array array w/ key = token and value = time 0247 * @throws Zend_Mobile_Push_Exception 0248 * @throws Zend_Mobile_Push_Exception_ServerUnavailable 0249 */ 0250 public function feedback() 0251 { 0252 if (!$this->_isConnected || 0253 !in_array($this->_currentEnv, 0254 array(self::SERVER_FEEDBACK_SANDBOX_URI, self::SERVER_FEEDBACK_PRODUCTION_URI))) { 0255 $this->connect(self::SERVER_FEEDBACK_PRODUCTION_URI); 0256 } 0257 0258 $tokens = array(); 0259 while ($token = $this->_read(38)) { 0260 if (strlen($token) < 38) { 0261 continue; 0262 } 0263 $token = unpack('Ntime/ntokenLength/H*token', $token); 0264 if (!isset($tokens[$token['token']]) || $tokens[$token['token']] < $token['time']) { 0265 $tokens[$token['token']] = $token['time']; 0266 } 0267 } 0268 return $tokens; 0269 } 0270 0271 /** 0272 * Send Message 0273 * 0274 * @param Zend_Mobile_Push_Message_Abstract $message 0275 * @throws Zend_Mobile_Push_Exception 0276 * @throws Zend_Mobile_Push_Exception_InvalidPayload 0277 * @throws Zend_Mobile_Push_Exception_InvalidToken 0278 * @throws Zend_Mobile_Push_Exception_InvalidTopic 0279 * @throws Zend_Mobile_Push_Exception_ServerUnavailable 0280 * @return bool 0281 */ 0282 public function send(Zend_Mobile_Push_Message_Abstract $message) 0283 { 0284 if (!$message->validate()) { 0285 throw new Zend_Mobile_Push_Exception('The message is not valid.'); 0286 } 0287 0288 if (!$this->_isConnected || !in_array($this->_currentEnv, array( 0289 self::SERVER_SANDBOX_URI, 0290 self::SERVER_PRODUCTION_URI))) { 0291 $this->connect(self::SERVER_PRODUCTION_URI); 0292 } 0293 0294 $payload = array('aps' => array()); 0295 0296 $alert = $message->getAlert(); 0297 foreach ($alert as $k => $v) { 0298 if ($v == null) { 0299 unset($alert[$k]); 0300 } 0301 } 0302 if (!empty($alert)) { 0303 $payload['aps']['alert'] = $alert; 0304 } 0305 if (!is_null($message->getBadge())) { 0306 $payload['aps']['badge'] = $message->getBadge(); 0307 } 0308 $sound = $message->getSound(); 0309 if (!empty($sound)) { 0310 $payload['aps']['sound'] = $sound; 0311 } 0312 0313 foreach($message->getCustomData() as $k => $v) { 0314 $payload[$k] = $v; 0315 } 0316 0317 if (version_compare(PHP_VERSION, '5.4.0') >= 0) { 0318 $payload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 0319 } else { 0320 $payload = json_encode($payload); 0321 } 0322 0323 $expire = $message->getExpire(); 0324 if ($expire > 0) { 0325 $expire += time(); 0326 } 0327 $id = $message->getId(); 0328 if (empty($id)) { 0329 $id = time(); 0330 } 0331 0332 $payload = pack('CNNnH*', 1, $id, $expire, 32, $message->getToken()) 0333 . pack('n', strlen($payload)) 0334 . $payload; 0335 $ret = $this->_write($payload); 0336 if ($ret === false) { 0337 // require_once 'Zend/Mobile/Push/Exception/ServerUnavailable.php'; 0338 throw new Zend_Mobile_Push_Exception_ServerUnavailable('Unable to send message'); 0339 } 0340 // check for errors from apple 0341 $err = $this->_read(1024); 0342 if (strlen($err) > 0) { 0343 $err = unpack('Ccmd/Cerrno/Nid', $err); 0344 switch ($err['errno']) { 0345 case 0: 0346 return true; 0347 break; 0348 case 1: 0349 throw new Zend_Mobile_Push_Exception('A processing error has occurred on the apple push notification server.'); 0350 break; 0351 case 2: 0352 // require_once 'Zend/Mobile/Push/Exception/InvalidToken.php'; 0353 throw new Zend_Mobile_Push_Exception_InvalidToken('Missing token; you must set a token for the message.'); 0354 break; 0355 case 3: 0356 // require_once 'Zend/Mobile/Push/Exception/InvalidTopic.php'; 0357 throw new Zend_Mobile_Push_Exception_InvalidTopic('Missing id; you must set an id for the message.'); 0358 break; 0359 case 4: 0360 // require_once 'Zend/Mobile/Push/Exception/InvalidPayload.php'; 0361 throw new Zend_Mobile_Push_Exception_InvalidPayload('Missing message; the message must always have some content.'); 0362 break; 0363 case 5: 0364 // require_once 'Zend/Mobile/Push/Exception/InvalidToken.php'; 0365 throw new Zend_Mobile_Push_Exception_InvalidToken('Bad token. This token is too big and is not a regular apns token.'); 0366 break; 0367 case 6: 0368 // require_once 'Zend/Mobile/Push/Exception/InvalidTopic.php'; 0369 throw new Zend_Mobile_Push_Exception_InvalidTopic('The message id is too big; reduce the size of the id.'); 0370 break; 0371 case 7: 0372 // require_once 'Zend/Mobile/Push/Exception/InvalidPayload.php'; 0373 throw new Zend_Mobile_Push_Exception_InvalidPayload('The message is too big; reduce the size of the message.'); 0374 break; 0375 case 8: 0376 // require_once 'Zend/Mobile/Push/Exception/InvalidToken.php'; 0377 throw new Zend_Mobile_Push_Exception_InvalidToken('Bad token. Remove this token from being sent to again.'); 0378 break; 0379 default: 0380 throw new Zend_Mobile_Push_Exception(sprintf('An unknown error occurred: %d', $err['errno'])); 0381 break; 0382 } 0383 } 0384 return true; 0385 } 0386 0387 /** 0388 * Close Connection 0389 * 0390 * @return void 0391 */ 0392 public function close() 0393 { 0394 if ($this->_isConnected && is_resource($this->_socket)) { 0395 fclose($this->_socket); 0396 } 0397 $this->_isConnected = false; 0398 } 0399 }