PHP Source
Before you get started: You can download the source code files here:
Once you have installed the MySQL Tables and added the appropriate code to your Apple Delegate file, you need to get PHP ready to receive device registrations.
STEP 1: Make a new file named class_APNS.php
This is the main APNs file that does all the dirty work.
You will need to change the following variables in this file:
- (line 55) $logPath: Must be the absolute path to your log file.
- (line 72) $certificate: Must be the absolute path to your Apple Production Certificate
- (line 96) $sandboxCertificate: Must be the absolute path to your Apple Development Certificate
<?PHP
/**
* @category Apple Push Notification Service using PHP & MySQL
* @package APNS
* @author Peter Schmalfeldt <manifestinteractive@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0
* @link http://code.google.com/p/easyapns/
*/
/**
* Begin Document
*/
class APNS {
/**
* Connection to MySQL
*
* @var string
* @access private
*/
private $db;
/**
* Array of APNS Connection Settings
*
* @var array
* @access private
*/
private $apnsData;
/**
* Whether to trigger errors
*
* @var bool
* @access private
*/
private $showErrors = true;
/**
* Whether APNS should log errors
*
* @var bool
* @access private
*/
private $logErrors = true;
/**
* Log path for APNS errors
*
* @var string
* @access private
*/
private $logPath = '/usr/local/apns/apns.log';
/**
* Max files size of log before it is truncated. 1048576 = 1MB. Added incase you do not add to a log
* rotator so this script will not accidently make gigs of error logs if there are issues with install
*
* @var int
* @access private
*/
private $logMaxSize = 1048576; // max log size before it is truncated
/**
* Absolute path to your Production Certificate
*
* @var string
* @access private
*/
private $certificate = '/usr/local/apns/apns.pem';
/**
* Apples Production APNS Gateway
*
* @var string
* @access private
*/
private $ssl = 'ssl://gateway.push.apple.com:2195';
/**
* Apples Production APNS Feedback Service
*
* @var string
* @access private
*/
private $feedback = 'ssl://feedback.push.apple.com:2196';
/**
* Absolute path to your Development Certificate
*
* @var string
* @access private
*/
private $sandboxCertificate = '/usr/local/apns/apns-dev.pem'; // change this to your development certificate absolute path
/**
* Apples Sandbox APNS Gateway
*
* @var string
* @access private
*/
private $sandboxSsl = 'ssl://gateway.sandbox.push.apple.com:2195';
/**
* Apples Sandbox APNS Feedback Service
*
* @var string
* @access private
*/
private $sandboxFeedback = 'ssl://feedback.sandbox.push.apple.com:2196';
/**
* Message to push to user
*
* @var string
* @access private
*/
private $message;
/**
* Constructor.
*
* Initializes a database connection and perfoms any tasks that have been assigned.
*
* Create a new PHP file named apns.php on your website...
*
* <code>
* <?php
* $db = new DbConnect();
* $db->show_errors();
* $apns = new APNS($db);
* ?>
* </code>
*
* Your iPhone App Delegate.m file will point to a PHP file with this APNS Object. The url will end up looking something like:
* https://secure.yourwebsite.com/apns.php?task=register&appname=My%20App&appversion=1.0.1&deviceuid=e018c2e46efe185d6b1107aa942085a59bb865d9&devicetoken=43df9e97b09ef464a6cf7561f9f339cb1b6ba38d8dc946edd79f1596ac1b0f66&devicename=My%20Awesome%20iPhone&devicemodel=iPhone&deviceversion=3.1.2&pushbadge=enabled&pushalert=disabled&pushsound=enabled
*
* @param object $db Database Object
* @param array $args Optional arguments passed through $argv or $_GET
* @access public
*/
function __construct($db, $args=NULL) {
$this->db = $db;
$this->checkSetup();
$this->apnsData = array(
'production'=>array(
'certificate'=>$this->certificate,
'ssl'=>$this->ssl,
'feedback'=>$this->feedback
),
'sandbox'=>array(
'certificate'=>$this->sandboxCertificate,
'ssl'=>$this->sandboxSsl,
'feedback'=>$this->sandboxFeedback
)
);
if(!empty($args)){
switch($args['task']){
case "register":
$this->_registerDevice(
$args['appname'],
$args['appversion'],
$args['deviceuid'],
$args['devicetoken'],
$args['devicename'],
$args['devicemodel'],
$args['deviceversion'],
$args['pushbadge'],
$args['pushalert'],
$args['pushsound']
);
break;
case "fetch";
$this->_fetchMessages();
break;
default:
echo "No APNS Task Provided...\n";
break;
}
}
}
/**
* Check Setup
*
* Check to make sure that the certificates are available and also provide a notice if they are not as secure as they could be.
*
* @access private
*/
private function checkSetup(){
if(!file_exists($this->certificate)) $this->_triggerError('ERROR: Missing Production Certificate.', E_USER_ERROR);
if(!file_exists($this->sandboxCertificate)) $this->_triggerError('ERROR: Missing Sandbox Certificate.', E_USER_ERROR);
clearstatcache();
$certificateMod = substr(sprintf('%o', fileperms($this->certificate)), -3);
$sandboxCertificateMod = substr(sprintf('%o', fileperms($this->sandboxCertificate)), -3);
if($certificateMod>644) $this->_triggerError('NOTICE: Production Certificate is insecure! Suggest chmod 644.');
if($sandboxCertificateMod>644) $this->_triggerError('NOTICE: Sandbox Certificate is insecure! Suggest chmod 644.');
}
/**
* Register Apple device
*
* Using your Delegate file to auto register the device on application launch. This will happen automatically from the Delegate.m file in your iPhone Application using our code.
*
* @param sting $appname Application Name
* @param sting $appversion Application Version
* @param sting $deviceuid 40 charater unique user id of Apple device
* @param sting $devicetoken 64 character unique device token tied to device id
* @param sting $devicename User selected device name
* @param sting $devicemodel Modle of device 'iPhone' or 'iPod'
* @param sting $deviceversion Current version of device
* @param sting $pushbadge Whether Badge Pushing is Enabled or Disabled
* @param sting $pushalert Whether Alert Pushing is Enabled or Disabled
* @param sting $pushsound Whether Sound Pushing is Enabled or Disabled
* @access private
*/
private function _registerDevice($appname, $appversion, $deviceuid, $devicetoken, $devicename, $devicemodel, $deviceversion, $pushbadge, $pushalert, $pushsound){
if(strlen($appname)==0) $this->_triggerError('ERROR: Application Name must not be blank.', E_USER_ERROR);
else if(strlen($appversion)==0) $this->_triggerError('ERROR: Application Version must not be blank.', E_USER_ERROR);
else if(strlen($deviceuid)!=40) $this->_triggerError('ERROR: Device ID must be 40 characters in length.', E_USER_ERROR);
else if(strlen($devicetoken)!=64) $this->_triggerError('ERROR: Device Token must be 64 characters in length.', E_USER_ERROR);
else if(strlen($devicename)==0) $this->_triggerError('ERROR: Device Name must not be blank.', E_USER_ERROR);
else if(strlen($devicemodel)==0) $this->_triggerError('ERROR: Device Model must not be blank.', E_USER_ERROR);
else if(strlen($deviceversion)==0) $this->_triggerError('ERROR: Device Version must not be blank.', E_USER_ERROR);
else if($pushbadge!='disabled' && $pushbadge!='enabled') $this->_triggerError('ERROR: Push Badge must be either Enabled or Disabled.', E_USER_ERROR);
else if($pushalert!='disabled' && $pushalert!='enabled') $this->_triggerError('ERROR: Push Alert must be either Enabled or Disabled.', E_USER_ERROR);
else if($pushsound!='disabled' && $pushsound!='enabled') $this->_triggerError('ERROR: Push Sount must be either Enabled or Disabled.', E_USER_ERROR);
$appname = $this->db->prepare($appname);
$appversion = $this->db->prepare($appversion);
$deviceuid = $this->db->prepare($deviceuid);
$devicetoken = $this->db->prepare($devicetoken);
$devicename = $this->db->prepare($devicename);
$devicemodel = $this->db->prepare($devicemodel);
$deviceversion = $this->db->prepare($deviceversion);
$pushbadge = $this->db->prepare($pushbadge);
$pushalert = $this->db->prepare($pushalert);
$pushsound = $this->db->prepare($pushsound);
// store device for push notifications
$this->db->query("SET NAMES 'utf8';"); // force utf8 encoding if not your default
$sql = "INSERT INTO `apns_devices`
VALUES (
NULL,
'{$appname}',
'{$appversion}',
'{$deviceuid}',
'{$devicetoken}',
'{$devicename}',
'{$devicemodel}',
'{$deviceversion}',
'{$pushbadge}',
'{$pushalert}',
'{$pushsound}',
'production',
'active',
NOW(),
NOW()
)
ON DUPLICATE KEY UPDATE
`devicetoken`='{$devicetoken}',
`devicename`='{$devicename}',
`devicemodel`='{$devicemodel}',
`deviceversion`='{$deviceversion}',
`pushbadge`='{$pushbadge}',
`pushalert`='{$pushalert}',
`pushsound`='{$pushsound}',
`status`='active',
`modified`=NOW();";
$this->db->query($sql);
}
/**
* Unregister Apple device
*
* This gets called automatically when Apple's Feedback Service responds with an invalid token.
*
* @param sting $token 64 character unique device token tied to device id
* @access private
*/
private function _unregisterDevice($token){
$sql = "UPDATE `apns_devices`
SET `status`='uninstalled'
WHERE `devicetoken`='{$token}'
LIMIT 1;";
$this->db->query($sql);
}
/**
* Fetch Messages
*
* This gets called by a cron job that runs as often as you want. You might want to set it for every minute.
*
* @param sting $token 64 character unique device token tied to device id
* @access private
*/
private function _fetchMessages(){
// only send one message per user... oldest message first
$sql = "SELECT
`apns_messages`.`pid`,
`apns_messages`.`message`,
`apns_devices`.`devicetoken`,
`apns_devices`.`development`
FROM `apns_messages`
LEFT JOIN `apns_devices` ON
`apns_devices`.`pid` = `apns_messages`.`fk_device`
WHERE `apns_messages`.`status`='queued'
AND `apns_messages`.`delivery` <= NOW()
AND `apns_devices`.`status`='active'
GROUP BY `apns_messages`.`fk_device`
ORDER BY `apns_messages`.`created` ASC
LIMIT 100;";
if($result = $this->db->query($sql)){
if($result->num_rows){
while($row = $result->fetch_array(MYSQLI_ASSOC)){
$pid = $this->db->prepare($row['pid']);
$message = stripslashes($this->db->prepare($row['message']));
$token = $this->db->prepare($row['devicetoken']);
$development = $this->db->prepare($row['development']);
$this->_pushMessage($pid, $message, $token, $development);
}
}
}
}
/**
* Push APNS Messages
*
* This gets called automatically by _fetchMessages. This is what actually deliveres the message.
*
* @param int $pid
* @param sting $message JSON encoded string
* @param sting $token 64 character unique device token tied to device id
* @param sting $development Which SSL to connect to, Sandbox or Production
* @access private
*/
private function _pushMessage($pid, $message, $token, $development){
if(strlen($pid)==0) $this->_triggerError('ERROR: Missing message pid.', E_USER_ERROR);
if(strlen($message)==0) $this->_triggerError('ERROR: Missing message.', E_USER_ERROR);
if(strlen($token)==0) $this->_triggerError('ERROR: Missing message token.', E_USER_ERROR);
if(strlen($development)==0) $this->_triggerError('ERROR: Missing development status.', E_USER_ERROR);
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $this->apnsData[$development]['certificate']);
$fp = stream_socket_client($this->apnsData[$development]['ssl'], $error, $errorString, 60, STREAM_CLIENT_CONNECT, $ctx);
if(!$fp){
$this->_messageFailed($pid);
$this->_triggerError("NOTICE: Failed to connect to APNS: {$error} {$errorString}.");
}
else {
$msg = chr(0).pack("n",32).pack('H*',$token).pack("n",strlen($message)).$message;
$fwrite = fwrite($fp, $msg);
if(!$fwrite) {
$this->_pushFailed($pid);
$this->_triggerError("ERROR: Failed writing to stream.", E_USER_ERROR);
}
else {
$this->_pushSuccess($pid);
}
}
fclose($fp);
$this->_checkFeedback($development);
}
/**
* Fetch APNS Messages
*
* This gets called automatically by _pushMessage. This will check with APNS for any invalid tokens and disable them from receiving further notifications.
*
* @param sting $development Which SSL to connect to, Sandbox or Production
* @access private
*/
private function _checkFeedback($development){
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $this->apnsData[$development]['certificate']);
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
$fp = stream_socket_client($this->apnsData[$development]['feedback'], $error, $errorString, 60, STREAM_CLIENT_CONNECT, $ctx);
if(!$fp) $this->_triggerError("NOTICE: Failed to connect to device: {$error} {$errorString}.");
while ($devcon = fread($fp, 38)){
$arr = unpack("H*", $devcon);
$rawhex = trim(implode("", $arr));
$token = substr($rawhex, 12, 64);
if(!empty($token)){
$this->_unregisterDevice($token);
$this->_triggerError("NOTICE: Unregistering Device Token: {$token}.");
}
}
fclose($fp);
}
/**
* APNS Push Success
*
* This gets called automatically by _pushMessage. When no errors are present, then the message was delivered.
*
* @param int $pid Primary ID of message that was delivered
* @access private
*/
private function _pushSuccess($pid){
$sql = "UPDATE `apns_messages`
SET `status`='delivered'
WHERE `pid`={$pid}
LIMIT 1;";
$this->db->query($sql);
}
/**
* APNS Push Failed
*
* This gets called automatically by _pushMessage. If an error is present, then the message was NOT delivered.
*
* @param int $pid Primary ID of message that was delivered
* @access private
*/
private function _pushFailed($pid){
$sql = "UPDATE `apns_messages`
SET `status`='failed'
WHERE `pid`={$pid}
LIMIT 1;";
$this->db->query($sql);
}
/**
* Trigger Error
*
* Use PHP error handling to trigger User Errors or Notices. If logging is enabled, errors will be written to the log as well.
* Disable on screen errors by setting showErrors to false;
*
* @param string $error Error String
* @param int $type Type of Error to Trigger
* @access private
*/
private function _triggerError($error, $type=E_USER_NOTICE){
$backtrace = debug_backtrace();
$backtrace = array_reverse($backtrace);
$error .= "\n";
$i=1;
foreach($backtrace as $errorcode){
$file = ($errorcode['file']!='') ? "-> File: ".basename($errorcode['file'])." (line ".$errorcode['line'].")":"";
$error .= "\n\t".$i.") ".$errorcode['class']."::".$errorcode['function']." {$file}";
$i++;
}
$error .= "\n\n";
if($this->logErrors && file_exists($this->logPath)){
if(filesize($this->logPath) > $this->logMaxSize) $fh = fopen($this->logPath, 'w');
else $fh = fopen($this->logPath, 'a');
fwrite($fh, $error);
fclose($fh);
}
if($this->showErrors) trigger_error($error, $type);
}
/**
* JSON Encode
*
* Some servers do not have json_encode, so use this instead.
*
* @param array $array Data to convert to JSON string.
* @access private
* @return string
*/
private function _jsonEncode($array=false){
if(is_null($array)) return 'null';
if($array === false) return 'false';
if($array === true) return 'true';
if(is_scalar($array)){
if(is_float($array)){
return floatval(str_replace(",", ".", strval($array)));
}
if(is_string($array)){
static $jsonReplaces = array(array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'), array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'));
return '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $array) . '"';
}
else return $array;
}
$isList = true;
for($i=0, reset($array); $i<count($array); $i++, next($array)){
if(key($array) !== $i){
$isList = false;
break;
}
}
$result = array();
if($isList){
foreach($array as $v) $result[] = json_encode($v);
return '[' . join(',', $result) . ']';
}
else {
foreach ($array as $k => $v) $result[] = json_encode($k).':'.json_encode($v);
return '{' . join(',', $result) . '}';
}
}
/**
* Start a New Message
*
* <code>
* <?php
* $db = new DbConnect();
* $db->show_errors();
* $apns = new APNS($db); // CREATE THE OBJECT
* $apns->newMessage(1, '2010-01-01 00:00:00'); // START A MESSAGE... SECOND ARGUMENT ACCEPTS ANY DATETIME STRING
* $apns->addMessageAlert('You got your emails.'); // ALERTS ARE TRICKY... SEE EXAMPLES
* $apns->addMessageBadge(9); // PASS A NUMBER
* $apns->addMessageSound('bingbong.aiff'); // ADD A SOUND
* $apns->queueMessage(); // AND SEND IT ON IT'S WAY
* ?>
* </code>
*
* @param int $fk_device Foreign Key to the device you want to send a message to.
* @param string $delivery Possible future date to send the message.
* @access public
*/
public function newMessage($fk_device, $delivery=NULL){
if(strlen($fk_device)==0) $this->_triggerError('ERROR: Missing message fk_device.', E_USER_ERROR);
if(isset($this->message)){
unset($this->message);
$this->_triggerError('NOTICE: An existing message already created but not delivered. The previous message has been removed. Use queueMessage() to complete a message.');
}
$this->message = array();
$this->message['aps'] = array();
$this->message['send']['to'] = $fk_device;
$this->message['send']['when'] = $delivery;
}
/**
* Queue Message for Delivery
*
* <code>
* <?php
* $db = new DbConnect();
* $db->show_errors();
* $apns = new APNS($db);
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageAlert('You got your emails.');
* $apns->addMessageBadge(9);
* $apns->addMessageSound('bingbong.aiff');
* $apns->queueMessage(); // ADD THE MESSAGE TO QUEUE
* ?>
* </code>
*
* @access public
*/
public function queueMessage(){
// check to make sure a message was created
if(!isset($this->message)) $this->_triggerError('NOTICE: You cannot Queue a message that has not been created. Use newMessage() to create a new message.');
// fetch the users id and check to make sure they have certain notifications enabled before trying to send anything to them.
$deliver = false;
$sql = "SELECT `pushbadge`, `pushalert`, `pushsound` FROM `apns_devices` WHERE `pid`={$this->message['send']['to']} AND `status`='active' LIMIT 1;";
if($result = $this->db->query($sql)){
if($result->num_rows){
while($row = $result->fetch_array(MYSQLI_ASSOC)){
$pushbadge = $this->db->prepare($row['pushbadge']);
$pushalert = $this->db->prepare($row['pushalert']);
$pushsound = $this->db->prepare($row['pushsound']);
}
$deliver = true;
}
}
// has user disabled messages?
if($pushbadge=='disabled' && $pushalert=='disabled' && $pushsound=='disabled') $deliver = false;
if(!$deliver) {
$this->_triggerError('NOTICE: This user has either disabled push notifications, or does not exist in the database.');
unset($this->message);
}
else {
// get sending information
$to = $this->message['send']['to'];
$when = $this->message['send']['when'];
unset($this->message['send']);
// remove notifications that user will not recieve.
if($pushbadge=='disabled'){
$this->_triggerError('NOTICE: This user has disabled Push Badge Notifications, Badge will not be delivered.');
unset($this->message['aps']['badge']);
}
if($pushalert=='disabled'){
$this->_triggerError('NOTICE: This user has disabled Push Alert Notifications, Alert will not be delivered.');
unset($this->message['aps']['alert']);
}
if($pushsound=='disabled'){
$this->_triggerError('NOTICE: This user has disabled Push Sound Notifications, Sound will not be delivered.');
unset($this->message['aps']['sound']);
}
$fk_device = $this->db->prepare($to);
$message = $this->_jsonEncode($this->message);
$message = $this->db->prepare($message);
$delivery = (!empty($when)) ? "'{$when}'":'NOW()';
$this->db->query("SET NAMES 'utf8';"); // force utf8 encoding if not your default
$sql = "INSERT INTO `apns_messages`
VALUES (
NULL,
'{$fk_device}',
'{$message}',
{$delivery},
'queued',
NOW(),
NOW()
);";
$this->db->query($sql);
unset($this->message);
}
}
/**
* Add Message Alert
*
* <code>
* <?php
* $db = new DbConnect();
* $db->show_errors();
* $apns = new APNS($db);
*
* // SIMPLE ALERT
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageAlert('Message received from Bob'); // MAKES DEFAULT BUTTON WITH BOTH 'Close' AND 'View' BUTTONS
* $apns->queueMessage();
*
* // CUSTOM 'View' BUTTON
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageAlert('Bob wants to play poker', 'PLAY'); // MAKES THE 'View' BUTTON READ 'PLAY'
* $apns->queueMessage();
*
* // NO 'View' BUTTON
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageAlert('Bob wants to play poker', ''); // MAKES AN ALERT WITH JUST AN 'OK' BUTTON
* $apns->queueMessage();
*
* // CUSTOM LOCALIZATION STRING FOR YOUR APP
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageAlert(NULL, NULL, 'GAME_PLAY_REQUEST_FORMAT', array('Jenna', 'Frank'));
* $apns->queueMessage();
* ?>
* </code>
*
* @param int $number
* @access public
*/
public function addMessageAlert($alert=NULL, $actionlockey=NULL, $lockey=NULL, $locargs=NULL){
if(!$this->message) $this->_triggerError('ERROR: Must use newMessage() before calling this method.', E_USER_ERROR);
if(isset($this->message['aps']['alert'])){
unset($this->message['aps']['alert']);
$this->_triggerError('NOTICE: An existing alert was already created but not delivered. The previous alert has been removed.');
}
switch(true){
case (!empty($alert) && empty($actionlockey) && empty($lockey) && empty($locargs)):
if(!is_string($alert)) $this->_triggerError('ERROR: Invalid Alert Format. See documentation for correct procedure.', E_USER_ERROR);
$this->message['aps']['alert'] = (string)$alert;
break;
case (!empty($alert) && !empty($actionlockey) && empty($lockey) && empty($locargs)):
if(!is_string($alert)) $this->_triggerError('ERROR: Invalid Alert Format. See documentation for correct procedure.', E_USER_ERROR);
else if(!is_string($actionlockey)) $this->_triggerError('ERROR: Invalid Action Loc Key Format. See documentation for correct procedure.', E_USER_ERROR);
$this->message['aps']['alert']['body'] = (string)$alert;
$this->message['aps']['alert']['action-loc-key'] = (string)$actionlockey;
break;
case (empty($alert) && empty($actionlockey) && !empty($lockey) && !empty($locargs)):
if(!is_string($lockey)) $this->_triggerError('ERROR: Invalid Loc Key Format. See documentation for correct procedure.', E_USER_ERROR);
$this->message['aps']['alert']['loc-key'] = (string)$lockey;
$this->message['aps']['alert']['loc-args'] = $locargs;
break;
default:
$this->_triggerError('ERROR: Invalid Alert Format. See documentation for correct procedure.', E_USER_ERROR);
break;
}
}
/**
* Add Message Badge
*
* <code>
* <?php
* $db = new DbConnect();
* $db->show_errors();
* $apns = new APNS($db);
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageBadge(9); // HAS TO BE A NUMBER
* $apns->queueMessage();
* ?>
* </code>
*
* @param int $number
* @access public
*/
public function addMessageBadge($number=NULL){
if(!$this->message) $this->_triggerError('ERROR: Must use newMessage() before calling this method.', E_USER_ERROR);
if($number) {
if(isset($this->message['aps']['badge'])) $this->_triggerError('NOTICE: Message Badge has already been created. Overwriting with '.$number.'.');
$this->message['aps']['badge'] = (int)$number;
}
}
/**
* Add Message Custom
*
* <code>
* <?php
* $db = new DbConnect();
* $db->show_errors();
* $apns = new APNS($db);
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageCustom('acme1', 42); // CAN BE NUMBER...
* $apns->addMessageCustom('acme2', 'foo'); // ... STRING
* $apns->addMessageCustom('acme3', array('bang', 'whiz')); // OR ARRAY
* $apns->queueMessage();
* ?>
* </code>
*
* @param string $key Name of Custom Object you want to pass back to your iPhone App
* @param mixed $value Mixed Value you want to pass back. Can be int, bool, string, or array.
* @access public
*/
public function addMessageCustom($key=NULL, $value=NULL){
if(!$this->message) $this->_triggerError('ERROR: Must use newMessage() before calling this method.', E_USER_ERROR);
if(!empty($key) && !empty($value)) {
if(isset($this->message[$key])){
unset($this->message[$key]);
$this->_triggerError('NOTICE: This same Custom Key already exists and has not been delivered. The previous values have been removed.');
}
if(!is_string($key)) $this->_triggerError('ERROR: Invalid Key Format. Key must be a string. See documentation for correct procedure.', E_USER_ERROR);
$this->message[$key] = $value;
}
}
/**
* Add Message Sound
*
* <code>
* <?php
* $db = new DbConnect();
* $db->show_errors();
* $apns = new APNS($db);
* $apns->newMessage(1, '2010-01-01 00:00:00');
* $apns->addMessageSound('bingbong.aiff'); // STRING OF FILE NAME
* $apns->queueMessage();
* ?>
* </code>
*
* @param string $sound Name of sound file in your Resources Directory
* @access public
*/
public function addMessageSound($sound=NULL){
if(!$this->message) $this->_triggerError('ERROR: Must use newMessage() before calling this method.', E_USER_ERROR);
if($sound) {
if(isset($this->message['aps']['sound'])) $this->_triggerError('NOTICE: Message Sound has already been created. Overwriting with '.$sound.'.');
$this->message['aps']['sound'] = (string)$sound;
}
}
}
?>
STEP 2: Make a new file named class_DbConnect.php
This is a database wrapper that works well with the APNS Class. Read the comments for settings that changed.
You will need to change the following variables in this file:
- (line 109) $this->DB_HOST: Usually "localhost", but change it if not
- (line 110) $this->DB_USERNAME: Your MySQL username
- (line 111) $this->DB_PASSWORD: Your MySQL Password
- (line 112) $this->DB_DATABASE: Your MySQL Database
<?PHP
/**
* @category Apple Push Notification Service using PHP & MySQL
* @package APNS
* @author Peter Schmalfeldt <manifestinteractive@gmail.com>
* @author John Kramlich <me@johnkramlich.com>
* @license http://www.apache.org/licenses/LICENSE-2.0
* @link http://code.google.com/p/easyapns/
*/
/**
* Begin Document
*/
class DbConnect
{
/**
* Connection to MySQL.
*
* @var string
*/
var $link;
/**
* Holds the most recent connection.
*
* @var string
*/
var $recent_link = null;
/**
* Holds the contents of the most recent SQL query.
*
* @var string
*/
var $sql = '';
/**
* Holds the number of queries executed.
*
* @var integer
*/
var $query_count = 0;
/**
* The text of the most recent database error message.
*
* @var string
*/
var $error = '';
/**
* The error number of the most recent database error message.
*
* @var integer
*/
var $errno = '';
/**
* Do we currently have a lock in place?
*
* @var boolean
*/
var $is_locked = false;
/**
* Show errors? If set to true, the error message/sql is displayed.
*
* @var boolean
*/
var $show_errors = false;
/**
* Log errors? If set to true, the error message/sql is logged.
*
* @var boolean
*/
public $log_errors = false;
/**
* The Database.
*
* @var string
*/
public $DB_DATABASE;
/**
* The variable used to contain a singleton instance of the database connection.
*
* @var string
*/
static $instance;
/**
* The number of rows affected by the most recent query.
*
* @var string
*/
public $affected_rows;
public $insert_id;
/**
* Constructor. Initializes a database connection and selects our database.
*/
function __construct()
{
$this->DB_HOST = 'localhost';
$this->DB_USERNAME = 'MYUSERNAME'; // !!! CHANGE ME
$this->DB_PASSWORD = 'MYPASSWORD'; // !!! CHANGE ME
$this->DB_DATABASE = 'MYDATABASE'; // !!! CHANGE ME
}
/**
* Singleton pattern to retrieve database connection.
*
* @return mixed MySQL database connection
*/
function _get($property)
{
if(self::$instance == NULL)
{
self::$instance = $this->connect();
}
return self::$instance->$property;
}
/**
* Singleton pattern to retrieve database connection.
*
* @return mixed MySQL database connection
*/
function Connection()
{
if(self::$instance == NULL)
{
self::$instance = $this->connect();
}
return self::$instance;
}
/**
* Connect to the Database.
*
*/
function connect()
{
self::$instance = new mysqli($this->DB_HOST, $this->DB_USERNAME, $this->DB_PASSWORD, $this->DB_DATABASE);
if (mysqli_connect_errno()) {
$this->raise_error(printf("Connect failed: %s\n", mysqli_connect_error()));
}
return self::$instance;
}
/**
* Executes a sql query. If optional $only_first is set to true, it will
* return the first row of the result as an array.
*
* @param string Query to run
* @param bool Return only the first row, as an array?
* @return mixed
*/
function query($sql, $only_first = false)
{
if(self::$instance == NULL)
{
self::$instance = $this->connect();
}
$this->recent_link =& self::$instance;
$this->sql =& $sql;
if(!$result = self::$instance->query($sql))
{
$this->raise_error(printf("Connect failed: %s\n", self::$instance->error));
}
$this->affected_rows = self::$instance->affected_rows;
$this->insert_id = self::$instance->insert_id;
$this->query_count++;
if ($only_first)
{
$return = $result->fetch_array(MYSQLI_ASSOC);
$this->free_result($result);
return $return;
}
return $result;
}
/**
* Fetches a row from a query result and returns the values from that row as an array.
*
* @param string The query result we are dealing with.
* @return array
*/
function fetch_array($result)
{
return @mysql_fetch_assoc($result);
}
/**
* Returns the number of rows in a result set.
*
* @param string The query result we are dealing with.
* @return integer
*/
function num_rows($result)
{
return self::$instance->num_rows;
}
/**
* Retuns the number of rows affected by the most recent query
*
* @return integer
*/
function affected_rows()
{
return self::$instance->affected_rows;
}
/**
* Returns the number of queries executed.
*
* @param none
* @return integer
*/
function num_queries()
{
return $this->query_count;
}
/**
* Lock database tables
*
* @param array Array of table => lock type
* @return void
*/
function lock($tables)
{
if (is_array($tables) AND count($tables))
{
$sql = '';
foreach ($tables AS $name => $type)
{
$sql .= (!empty($sql) ? ', ' : '') . "$name $type";
}
$this->query("LOCK TABLES $sql");
$this->is_locked = true;
}
}
/**
* Unlock tables
*/
function unlock()
{
if ($this->is_locked)
{
$this->query("UNLOCK TABLES");
}
}
/**
* Returns the ID of the most recently inserted item in an auto_increment field
*
* @return integer
*/
function insert_id()
{
return self::$instance->insert_id;
}
/**
* Escapes a value to make it safe for using in queries.
*
* @param string Value to be escaped
* @param bool Do we need to escape this string for a LIKE statement?
* @return string
*/
function prepare($value, $do_like = false)
{
if(self::$instance == NULL)
{
self::$instance = $this->connect();
}
if ($do_like)
{
$value = str_replace(array('%', '_'), array('\%', '\_'), $value);
}
return self::$instance->real_escape_string($value);
}
/**
* Frees memory associated with a query result.
*
* @param string The query result we are dealing with.
* @return boolean
*/
function free_result($result)
{
return @mysql_free_result($result);
}
/**
* Turns database error reporting on
*/
function show_errors()
{
$this->show_errors = true;
}
/**
* Turns database error reporting off
*/
function hide_errors()
{
$this->show_errors = false;
}
/**
* Closes our connection to MySQL.
*
* @param none
* @return boolean
*/
function close()
{
$this->sql = '';
return self::$instance->close();
}
/**
* Returns the MySQL error message.
*
* @param none
* @return string
*/
function error()
{
$this->error = (is_null($this->recent_link)) ? '' : self::$instance->error;
return $this->error;
}
/**
* Returns the MySQL error number.
*
* @param none
* @return string
*/
function errno()
{
$this->errno = (is_null($this->recent_link)) ? 0 : self::$instance->errno ;
return $this->errno;
}
/**
* Gets the url/path of where we are when a MySQL error occurs.
*
* @access private
* @param none
* @return string
*/
function _get_error_path()
{
if ($_SERVER['REQUEST_URI'])
{
$errorpath = $_SERVER['REQUEST_URI'];
}
else
{
if ($_SERVER['PATH_INFO'])
{
$errorpath = $_SERVER['PATH_INFO'];
}
else
{
$errorpath = $_SERVER['PHP_SELF'];
}
if ($_SERVER['QUERY_STRING'])
{
$errorpath .= '?' . $_SERVER['QUERY_STRING'];
}
}
if (($pos = strpos($errorpath, '?')) !== false)
{
$errorpath = urldecode(substr($errorpath, 0, $pos)) . substr($errorpath, $pos);
}
else
{
$errorpath = urldecode($errorpath);
}
return $_SERVER['HTTP_HOST'] . $errorpath;
}
/**
* If there is a database error, the script will be stopped and an error message displayed.
*
* @param string The error message. If empty, one will be built with $this->sql.
* @return string
*/
function raise_error($error_message = '')
{
if ($this->recent_link)
{
$this->error = $this->error($this->recent_link);
$this->errno = $this->errno($this->recent_link);
}
if ($error_message == '')
{
$this->sql = "Error in SQL query:\n\n" . rtrim($this->sql) . ';';
$error_message =& $this->sql;
}
else
{
$error_message = $error_message . ($this->sql != '' ? "\n\nSQL:" . rtrim($this->sql) . ';' : '');
}
$message = "<textarea rows=\"10\" cols=\"80\">MySQL Error:\n\n\n$error_message\n\nError: {$this->error}\nError #: {$this->errno}\nFilename: " . $this->_get_error_path() . "\n</textarea>";
if (!$this->show_errors)
{
$message = "<!--\n\n$message\n\n-->";
}
else die("There seems to have been a slight problem with our database, please try again later.<br /><br />\n$message");
}
}
?>
STEP 3: Make a new file named apns.php
This file will be used to register devices and push notifications.
#!/usr/bin/php
<?PHP
/**
* @category Apple Push Notification Service using PHP & MySQL
* @package APNS
* @author Peter Schmalfeldt <manifestinteractive@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0
* @link http://code.google.com/p/easyapns/
*/
/**
* Begin Document
*/
// AUTOLOAD CLASS OBJECTS... YOU CAN USE INCLUDES IF YOU PREFER
if(!function_exists("__autoload")){
function __autoload($class_name){
require_once('classes/class_'.$class_name.'.php');
}
}
// CREATE DATABASE OBJECT ( MAKE SURE TO CHANGE LOGIN INFO IN CLASS FILE )
$db = new DbConnect();
$db->show_errors();
// FETCH $_GET OR CRON ARGUMENTS TO AUTOMATE TASKS
$args = (!empty($_GET)) ? $_GET:array('task'=>$argv[1]);
// CREATE APNS OBJECT, WITH DATABASE OBJECT AND ARGUMENTS
$apns = new APNS($db, $args);
?>
The first three steps take care of all the backend work for your server. But all of this would be for nothing, if you cannot send a message to your user... so here is how to do that:
<?PHP
/**
* @category Apple Push Notification Service using PHP & MySQL
* @package APNS
* @author Peter Schmalfeldt <manifestinteractive@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0
* @link http://code.google.com/p/easyapns/
*/
/**
* Begin Document
*/
// AUTOLOAD CLASS OBJECTS... YOU CAN USE INCLUDES IF YOU PREFER
if(!function_exists("__autoload")){
function __autoload($class_name){
require_once('classes/class_'.$class_name.'.php');
}
}
// CREATE DATABASE OBJECT ( MAKE SURE TO CHANGE LOGIN INFO IN CLASS FILE )
$db = new DbConnect();
$db->show_errors();
// FETCH $_GET OR CRON ARGUMENTS TO AUTOMATE TASKS
$apns = new APNS($db);
/**
/* ACTUAL SAMPLES USING THE 'Examples of JSON Payloads' EXAMPLES (1-5) FROM APPLE'S WEBSITE.
* LINK: http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW15
*/
// APPLE APNS EXAMPLE 1
$apns->newMessage(1);
$apns->addMessageAlert('Message received from Bob');
$apns->addMessageCustom('acme2', array('bang', 'whiz'));
$apns->queueMessage();
// APPLE APNS EXAMPLE 2
$apns->newMessage(1, '2010-01-01 00:00:00'); // FUTURE DATE NOT APART OF APPLE EXAMPLE
$apns->addMessageAlert('Bob wants to play poker', 'PLAY');
$apns->addMessageBadge(5);
$apns->addMessageCustom('acme1', 'bar');
$apns->addMessageCustom('acme2', array('bang', 'whiz'));
$apns->queueMessage();
// APPLE APNS EXAMPLE 3
$apns->newMessage(1);
$apns->addMessageAlert('You got your emails.');
$apns->addMessageBadge(9);
$apns->addMessageSound('bingbong.aiff');
$apns->addMessageCustom('acme1', 'bar');
$apns->addMessageCustom('acme2', 42);
$apns->queueMessage();
// APPLE APNS EXAMPLE 4
$apns->newMessage(1, '2010-01-01 00:00:00'); // FUTURE DATE NOT APART OF APPLE EXAMPLE
$apns->addMessageAlert(NULL, NULL, 'GAME_PLAY_REQUEST_FORMAT', array('Jenna', 'Frank'));
$apns->addMessageSound('chime');
$apns->addMessageCustom('acme', 'foo');
$apns->queueMessage();
// APPLE APNS EXAMPLE 5
$apns->newMessage(1);
$apns->addMessageCustom('acme2', array(5, 8));
$apns->queueMessage();
?>
Sending Messages:
Now that you have some messages added to your queue, you can use a cron job to automatically send your messages for you.
