Created
August 30, 2019 11:50
-
-
Save kolomiec-valeriy/cd3525837bda60a481ae3ab8f2b3228a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Mcfedr\AwsPushBundle\Message; | |
use Mcfedr\AwsPushBundle\Exception\MessageTooLongException; | |
class Message implements \JsonSerializable | |
{ | |
/** | |
* @deprecated use NO_COLLAPSE | |
* @see NO_COLLAPSE | |
*/ | |
const GCM_NO_COLLAPSE = 'do_not_collapse'; | |
const NO_COLLAPSE = 'do_not_collapse'; | |
const APNS_MAX_LENGTH = 2048; | |
const GCM_MAX_LENGTH = 4096; | |
const ADM_MAX_LENGTH = 6144; | |
const PLATFORM_GCM = 'gcm'; | |
const PLATFORM_APNS = 'apns'; | |
const PLATFORM_ADM = 'adm'; | |
const PRIORITY_HIGH = 'high'; | |
const PRIORITY_NORMAL = 'normal'; | |
/** | |
* The text is automatically trimmed when sending to APNS and GCM | |
* The text will be sent to GCM and ADM as 'message' in the data field. | |
* | |
* @var ?string | |
*/ | |
private $text; | |
/** | |
* The text will be sent to FCM over GCM as 'title' in the notification field. | |
* | |
* @var ?string | |
*/ | |
private $title = null; | |
/** | |
* This is notification priority for GCM should be 'high' or 'normal'. High priority is default. | |
* | |
* @var string | |
*/ | |
private $priority = self::PRIORITY_HIGH; | |
/** | |
* The key of a localized string that will form the message displayed. | |
* | |
* @var string | |
*/ | |
private $localizedKey; | |
/** | |
* Arguments for the localized message | |
* If you are using iOS these should be strings only, plural localization doesn't work! | |
* | |
* @var array | |
*/ | |
private $localizedArguments; | |
/** | |
* If set then the text content of the message will be trimmed where necessary to fit in the length limits of each | |
* platform. | |
* | |
* @var bool | |
* | |
* @see Message::APNS_MAX_LENGTH | |
* @see Message::GCM_MAX_LENGTH | |
*/ | |
private $allowTrimming = true; | |
/** | |
* APNS only. | |
* | |
* @var int | |
*/ | |
private $badge; | |
/** | |
* APNS only | |
* Including this key means that when your app is launched in the background or resumed. | |
* | |
* @var bool | |
*/ | |
private $contentAvailable; | |
/** | |
* APNS only. | |
* | |
* @var string | |
*/ | |
private $sound; | |
/** | |
* This is the data to send to all services, it will be deep merged with the other data. | |
* | |
* @var array | |
*/ | |
private $custom = []; | |
/** | |
* If set, will be sent to GCM, deep merged with ['message' => $text] in the data field. | |
* | |
* @var array | |
*/ | |
private $gcmData; | |
/** | |
* If set, will be sent to ADM, deep merged with ['message' => $text] in the data field. | |
* | |
* @var array | |
*/ | |
private $admData; | |
/** | |
* If set, will be sent to APNS, deep merged as the top level. | |
* | |
* @var array | |
*/ | |
private $apnsData; | |
/** | |
* GCM and ADM only. | |
* | |
* @var string | |
*/ | |
private $collapseKey = self::NO_COLLAPSE; | |
/** | |
* GCM and ADM only. | |
* | |
* GCM: default is 4 weeks | |
* ADM: default is 1 week | |
* | |
* @var int number of seconds that the server should retain the message | |
*/ | |
private $ttl; | |
/** | |
* GCM only. | |
* | |
* @var bool | |
*/ | |
private $delayWhileIdle = false; | |
/** | |
* Platforms that this message will create JSON for, and throw errors for. | |
* | |
* @var array | |
* | |
* @see Message::PLATFORM_GCM | |
* @see Message::PLATFORM_APNS | |
* @see Message::PLATFORM_ADM | |
*/ | |
private $platforms = [self::PLATFORM_GCM, self::PLATFORM_APNS, self::PLATFORM_ADM]; | |
/** | |
* @param string $text | |
*/ | |
public function __construct($text = null) | |
{ | |
$this->text = $text; | |
} | |
/** | |
* Set the number on displayed badge | |
* APNS only. | |
* | |
* @param int $badge | |
*/ | |
public function setBadge($badge) | |
{ | |
$this->badge = $badge; | |
} | |
/** | |
* @return int | |
*/ | |
public function getBadge() | |
{ | |
return $this->badge; | |
} | |
/** | |
* @return bool | |
*/ | |
public function isContentAvailable() | |
{ | |
return $this->contentAvailable; | |
} | |
/** | |
* Including this key means that when your app is launched in the background or resumed | |
* APNS only. | |
* | |
* @param bool $contentAvailable | |
*/ | |
public function setContentAvailable($contentAvailable) | |
{ | |
$this->contentAvailable = $contentAvailable; | |
} | |
/** | |
* This is the data to send to all services, it will be deep merged with the other data. | |
* | |
* @param array $custom | |
*/ | |
public function setCustom(array $custom) | |
{ | |
$this->custom = $custom; | |
} | |
/** | |
* @return array | |
*/ | |
public function getCustom() | |
{ | |
return $this->custom; | |
} | |
/** | |
* Name of sound file to use | |
* APNS only. | |
* | |
* @param string $sound | |
*/ | |
public function setSound($sound) | |
{ | |
$this->sound = $sound; | |
} | |
/** | |
* @return string | |
*/ | |
public function getSound() | |
{ | |
return $this->sound; | |
} | |
/** | |
* @return string | |
*/ | |
public function getTitle() | |
{ | |
return $this->title; | |
} | |
/** | |
* The text will be sent to FCM over GCM as 'title' in the data field. | |
* | |
* @param string $title | |
*/ | |
public function setTitle($title) | |
{ | |
$this->title = $title; | |
} | |
/** | |
* The text is automatically trimmed when sending to APNS and GCM | |
* The text will be sent to GCM and ADM as 'message' in the data field. | |
* | |
* @param string $text | |
*/ | |
public function setText($text) | |
{ | |
$this->text = $text; | |
} | |
/** | |
* @return string | |
*/ | |
public function getText() | |
{ | |
return $this->text; | |
} | |
/** | |
* Set Priority. | |
* | |
* @param string $priority | |
* | |
* @return $this | |
*/ | |
public function setPriority($priority) | |
{ | |
$this->priority = $priority; | |
return $this; | |
} | |
/** | |
* Get Priority. | |
* | |
* @return string | |
*/ | |
public function getPriority() | |
{ | |
return $this->priority; | |
} | |
/** | |
* @return string | |
*/ | |
public function getLocalizedKey() | |
{ | |
return $this->localizedKey; | |
} | |
/** | |
* The key of a localized string that will form the message displayed. | |
* | |
* @param string $localizedKey | |
* | |
* @return Message | |
*/ | |
public function setLocalizedKey($localizedKey) | |
{ | |
$this->localizedKey = $localizedKey; | |
return $this; | |
} | |
/** | |
* @return array | |
*/ | |
public function getLocalizedArguments() | |
{ | |
return $this->localizedArguments; | |
} | |
/** | |
* Arguments for the localized message | |
* If you are using iOS these should be strings only, plural localization doesn't work! | |
* | |
* @param array $localizedArguments | |
* | |
* @return Message | |
*/ | |
public function setLocalizedArguments(array $localizedArguments = null) | |
{ | |
$this->localizedArguments = $localizedArguments; | |
return $this; | |
} | |
/** | |
* Convenience to set localized text. | |
* | |
* @param string $key | |
* @param array|null $arguments | |
*/ | |
public function setLocalizedText($key, array $arguments = null) | |
{ | |
$this->setLocalizedKey($key); | |
$this->setLocalizedArguments($arguments); | |
} | |
/** | |
* @return bool | |
*/ | |
public function getAllowTrimming() | |
{ | |
return $this->allowTrimming; | |
} | |
/** | |
* If set then the text content of the message will be trimmed where necessary to fit in the length limits of each | |
* platform. | |
* | |
* @param bool $allowTrimming | |
*/ | |
public function setAllowTrimming($allowTrimming) | |
{ | |
$this->allowTrimming = $allowTrimming; | |
} | |
/** | |
* GCM and ADM only. | |
* | |
* @param string $collapseKey | |
*/ | |
public function setCollapseKey($collapseKey) | |
{ | |
$this->collapseKey = $collapseKey ?: static::NO_COLLAPSE; | |
} | |
/** | |
* @return string | |
*/ | |
public function getCollapseKey() | |
{ | |
return $this->collapseKey; | |
} | |
/** | |
* GCM only. | |
* | |
* @param bool $delayWhileIdle | |
*/ | |
public function setDelayWhileIdle($delayWhileIdle) | |
{ | |
$this->delayWhileIdle = $delayWhileIdle; | |
} | |
/** | |
* @return bool | |
*/ | |
public function getDelayWhileIdle() | |
{ | |
return $this->delayWhileIdle; | |
} | |
/** | |
* number of seconds that the server should retain the message. | |
* | |
* GCM and ADM only | |
* | |
* @param int $ttl | |
*/ | |
public function setTtl($ttl) | |
{ | |
$this->ttl = $ttl; | |
} | |
/** | |
* @return int | |
*/ | |
public function getTtl() | |
{ | |
return $this->ttl; | |
} | |
/** | |
* If set, will be sent to GCM, deep merged with ['message' => $text] in the data field. | |
* | |
* @param array $gcmData | |
*/ | |
public function setGcmData(array $gcmData) | |
{ | |
$this->gcmData = $gcmData; | |
} | |
/** | |
* @return array | |
*/ | |
public function getGcmData() | |
{ | |
return $this->gcmData; | |
} | |
/** | |
* If set, will be sent to ADM, deep merged with ['message' => $text] in the data field. | |
* | |
* @param array $admData | |
* | |
* @return Message | |
*/ | |
public function setAdmData(array $admData) | |
{ | |
$this->admData = $admData; | |
return $this; | |
} | |
/** | |
* @return array | |
*/ | |
public function getAdmData() | |
{ | |
return $this->admData; | |
} | |
/** | |
* If set, will be sent to APNS, deep merged as the top level, meaning you can add extra data to 'aps'. | |
* | |
* @param array $apnsData | |
* | |
* @return Message | |
*/ | |
public function setApnsData(array $apnsData) | |
{ | |
$this->apnsData = $apnsData; | |
return $this; | |
} | |
/** | |
* @return array | |
*/ | |
public function getApnsData() | |
{ | |
return $this->apnsData; | |
} | |
/** | |
* @return array | |
*/ | |
public function getPlatforms() | |
{ | |
return $this->platforms; | |
} | |
/** | |
* Platforms that this message will create JSON for, and throw errors for (for long messages). | |
* | |
* @param array $platforms | |
* | |
* @see Message::PLATFORM_GCM | |
* @see Message::PLATFORM_APNS | |
* @see Message::PLATFORM_ADM | |
*/ | |
public function setPlatforms(array $platforms) | |
{ | |
$this->platforms = $platforms; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function jsonSerialize() | |
{ | |
$data = [ | |
'default' => $this->text | |
]; | |
if (in_array(self::PLATFORM_APNS, $this->platforms)) { | |
$data['APNS'] = $data['APNS_SANDBOX'] = $this->getApnsJson(); | |
} | |
if (in_array(self::PLATFORM_GCM, $this->platforms)) { | |
$data['FCM'] = $data['GCM'] = $this->getGcmJson(); | |
} | |
if (in_array(self::PLATFORM_ADM, $this->platforms)) { | |
$data['ADM'] = $this->getAdmJson(); | |
} | |
return $data; | |
} | |
public function __toString() | |
{ | |
return json_encode($this); | |
} | |
/** | |
* Get the json to send via Apple Push Notification Server | |
* For APNS the max length applies to the whole message. | |
* | |
* @return string | |
* | |
* @throws MessageTooLongException | |
*/ | |
private function getApnsJson() | |
{ | |
return json_encode($this->getTrimmedJson([$this, 'getApnsJsonInner'], static::APNS_MAX_LENGTH, 'You message for APNS is too long'), JSON_UNESCAPED_UNICODE); | |
} | |
/** | |
* Get the correct apple push notification server data. | |
* | |
* @param string $text | |
* | |
* @return array | |
*/ | |
private function getApnsJsonInner($text) | |
{ | |
$apns = [ | |
'aps' => [] | |
]; | |
if (!is_null($this->localizedKey)) { | |
$apns['aps']['alert'] = [ | |
'loc-key' => $this->localizedKey | |
]; | |
if ($this->localizedArguments) { | |
$apns['aps']['alert']['loc-args'] = $this->localizedArguments; | |
} | |
} elseif (!is_null($text)) { | |
$apns['aps']['alert']['body'] = $text; | |
if (!is_null($this->title)) { | |
$apns['aps']['alert']['title'] = $this->title; | |
} | |
} | |
if ($this->isContentAvailable()) { | |
$apns['aps']['content-available'] = 1; | |
} | |
if (!is_null($this->badge)) { | |
$apns['aps']['badge'] = $this->badge; | |
} | |
if (!is_null($this->sound)) { | |
$apns['aps']['sound'] = $this->sound; | |
} | |
$merged = $this->arrayMergeDeep($apns, $this->custom, $this->apnsData ? $this->apnsData : []); | |
// Force aps to be an object, because it shouldnt get encoded as [] but as {} | |
if (!count($merged['aps'])) { | |
$merged['aps'] = new \stdClass(); | |
} | |
return $merged; | |
} | |
/** | |
* Get the json to send via Amazon Device Messaging. | |
* | |
* @return string | |
*/ | |
private function getAdmJson() | |
{ | |
$adm = [ | |
'data' => $this->getTrimmedJson([$this, 'getAdmJsonInner'], static::ADM_MAX_LENGTH, 'You message for ADM is too long'), | |
'expiresAfter' => $this->ttl | |
]; | |
foreach ($adm['data'] as $key => $value) { | |
if (!is_string($value)) { | |
$adm['data']["{$key}_json"] = json_encode($value); | |
unset($adm['data'][$key]); | |
} | |
} | |
if ($this->collapseKey != static::NO_COLLAPSE) { | |
$adm['consolidationKey'] = $this->collapseKey; | |
} | |
return json_encode($adm, JSON_UNESCAPED_UNICODE); | |
} | |
/** | |
* Gets the data part of the GCM message. | |
* | |
* @param $text | |
* | |
* @return array | |
*/ | |
private function getAdmJsonInner($text) | |
{ | |
$data = $this->getAndroidJsonInner($text); | |
$merged = $this->arrayMergeDeep($data, $this->custom, $this->admData ? $this->admData : []); | |
// Force to be an object, because it shouldnt get encoded as [] but as {} | |
if (!count($merged)) { | |
$merged = new \stdClass(); | |
} | |
return $merged; | |
} | |
/** | |
* Get the json to send via Google Cloud Messaging | |
* For GCM the max length is for the data field only. | |
* | |
* @return string | |
* | |
* @throws MessageTooLongException | |
*/ | |
private function getGcmJson() | |
{ | |
$payload = [ | |
'collapse_key' => $this->collapseKey, | |
'time_to_live' => $this->ttl, | |
'delay_while_idle' => $this->delayWhileIdle, | |
'priority' => $this->priority, | |
]; | |
if (!is_null($this->title)) { | |
$payload = array_merge($payload, $this->getTrimmedJson([$this, 'getFcmJsonInner'], static::GCM_MAX_LENGTH, 'You message for GCM is too long')); | |
} else { | |
$payload['data'] = $this->getTrimmedJson([$this, 'getGcmJsonInner'], static::GCM_MAX_LENGTH, 'You message for GCM is too long'); | |
} | |
return json_encode($payload, JSON_UNESCAPED_UNICODE); | |
} | |
// 'data' => $this->getTrimmedJson([$this, 'getGcmJsonInner'], static::GCM_MAX_LENGTH, 'You message for GCM is too long') | |
/** | |
* Gets the data part of the GCM message. | |
* | |
* @param $text | |
* | |
* @return array | |
*/ | |
private function getGcmJsonInner($text) | |
{ | |
$data = $this->getAndroidJsonInner($text); | |
$merged = $this->arrayMergeDeep($data, $this->custom, $this->gcmData ? $this->gcmData : []); | |
// Force to be an object, because it shouldnt get encoded as [] but as {} | |
if (!count($merged)) { | |
$merged = new \stdClass(); | |
} | |
return $merged; | |
} | |
/** | |
* Gets the data part of the FCM message. | |
* | |
* @param $text | |
* | |
* @return array | |
*/ | |
private function getFcmJsonInner($text) | |
{ | |
$notification = []; | |
if (!is_null($text)) { | |
$notification['body'] = $text; | |
} | |
if (!is_null($this->localizedKey)) { | |
$notification['body_loc_key'] = $this->localizedKey; | |
if ($this->localizedArguments) { | |
$notification['body_loc_args'] = $this->localizedArguments; | |
} | |
} | |
if (!is_null($this->title)) { | |
$notification['title'] = $this->title; | |
} | |
if (!is_null($this->sound)) { | |
$notification['sound'] = $this->sound; | |
} | |
return [ | |
'notification' => $notification, | |
'data' => $this->arrayMergeDeep($this->custom, $this->gcmData ? $this->gcmData : []) | |
]; | |
} | |
/** | |
* Gets the base of the data for the android platforms, with text and localization keys. | |
* | |
* @param $text | |
* | |
* @return array | |
*/ | |
private function getAndroidJsonInner($text) | |
{ | |
$data = []; | |
if (!is_null($text)) { | |
$data['message'] = $text; | |
} | |
if (!is_null($this->localizedKey)) { | |
$data['message-loc-key'] = $this->localizedKey; | |
if ($this->localizedArguments) { | |
$data['message-loc-args'] = $this->localizedArguments; | |
} | |
} | |
return $data; | |
} | |
/** | |
* Using a inner function gets the data, and trys again if its too long by trimming the text. | |
* | |
* @param callable $inner | |
* @param int $limit | |
* @param string $error | |
* | |
* @return array | |
* | |
* @throws MessageTooLongException | |
*/ | |
private function getTrimmedJson(callable $inner, $limit, $error) | |
{ | |
$gcmInner = $inner($this->text); | |
$gcmInnerJson = json_encode($gcmInner, JSON_UNESCAPED_UNICODE); | |
if (($gcmInnerJsonLength = strlen($gcmInnerJson)) > $limit) { | |
$cut = $gcmInnerJsonLength - $limit; | |
//Note that strlen returns the byte length of the string | |
$textLength = strlen($this->text); | |
if ($textLength > $cut && $this->allowTrimming) { | |
$gcmInner = $inner(mb_strcut($this->text, 0, $textLength - $cut - 3, 'utf8') . '...'); | |
} else { | |
throw new MessageTooLongException("$error $gcmInnerJson"); | |
} | |
} | |
return $gcmInner; | |
} | |
/** | |
* Merge arrays, deeply. | |
* | |
* @param array $array1 | |
* @param array $array2 | |
* | |
* @return array | |
*/ | |
private function arrayMergeDeep(array $array1, array $array2) | |
{ | |
$result = []; | |
foreach (func_get_args() as $array) { | |
foreach ($array as $key => $value) { | |
// Renumber integer keys as array_merge_recursive() does. Note that PHP | |
// automatically converts array keys that are integer strings (e.g., '1') | |
// to integers. | |
if (is_integer($key)) { | |
$result[] = $value; | |
} elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) { | |
// Recurse when both values are arrays. | |
$result[$key] = $this->arrayMergeDeep($result[$key], $value); | |
} else { | |
// Otherwise, use the latter value, overriding any previous value. | |
$result[$key] = $value; | |
} | |
} | |
} | |
return $result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment