1
|
|
|
<?php
|
2
|
|
|
|
3
|
|
|
namespace Toplan\PhpSms;
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
class Constants
|
7
|
|
|
{
|
8
|
|
|
const GMT_DATE_FORMAT = "D, d M Y H:i:s \\G\\M\\T";
|
9
|
|
|
|
10
|
|
|
const MNS_VERSION_HEADER = "x-mns-version";
|
11
|
|
|
const MNS_HEADER_PREFIX = "x-mns";
|
12
|
|
|
const MNS_XML_NAMESPACE = "http://mns.aliyuncs.com/doc/v1/";
|
13
|
|
|
|
14
|
|
|
const MNS_VERSION = "2015-06-06";
|
15
|
|
|
const AUTHORIZATION = "Authorization";
|
16
|
|
|
const MNS = "MNS";
|
17
|
|
|
|
18
|
|
|
const CONTENT_LENGTH = "Content-Length";
|
19
|
|
|
const CONTENT_TYPE = "Content-Type";
|
20
|
|
|
const SECURITY_TOKEN = "security-token";
|
21
|
|
|
const DIRECT_MAIL = "DirectMail";
|
22
|
|
|
const DIRECT_SMS = "DirectSMS";
|
23
|
|
|
const WEBSOCKET = "WebSocket";
|
24
|
|
|
|
25
|
|
|
// XML Tag
|
26
|
|
|
const ERROR = "Error";
|
27
|
|
|
const ERRORS = "Errors";
|
28
|
|
|
const DELAY_SECONDS = "DelaySeconds";
|
29
|
|
|
const MAXIMUM_MESSAGE_SIZE = "MaximumMessageSize";
|
30
|
|
|
const MESSAGE_RETENTION_PERIOD = "MessageRetentionPeriod";
|
31
|
|
|
const VISIBILITY_TIMEOUT = "VisibilityTimeout";
|
32
|
|
|
const POLLING_WAIT_SECONDS = "PollingWaitSeconds";
|
33
|
|
|
const MESSAGE_BODY = "MessageBody";
|
34
|
|
|
const PRIORITY = "Priority";
|
35
|
|
|
const MESSAGE_ID = "MessageId";
|
36
|
|
|
const MESSAGE_BODY_MD5 = "MessageBodyMD5";
|
37
|
|
|
const ENQUEUE_TIME = "EnqueueTime";
|
38
|
|
|
const NEXT_VISIBLE_TIME = "NextVisibleTime";
|
39
|
|
|
const FIRST_DEQUEUE_TIME = "FirstDequeueTime";
|
40
|
|
|
const RECEIPT_HANDLE = "ReceiptHandle";
|
41
|
|
|
const RECEIPT_HANDLES = "ReceiptHandles";
|
42
|
|
|
const DEQUEUE_COUNT = "DequeueCount";
|
43
|
|
|
const ERROR_CODE = "ErrorCode";
|
44
|
|
|
const ERROR_MESSAGE = "ErrorMessage";
|
45
|
|
|
const ENDPOINT = "Endpoint";
|
46
|
|
|
const STRATEGY = "NotifyStrategy";
|
47
|
|
|
const CONTENT_FORMAT = "NotifyContentFormat";
|
48
|
|
|
const LOGGING_BUCKET = "LoggingBucket";
|
49
|
|
|
const LOGGING_ENABLED = "LoggingEnabled";
|
50
|
|
|
const MESSAGE_ATTRIBUTES = "MessageAttributes";
|
51
|
|
|
const SUBJECT = "Subject";
|
52
|
|
|
const ACCOUNT_NAME = "AccountName";
|
53
|
|
|
const ADDRESS_TYPE = "AddressType";
|
54
|
|
|
const REPLY_TO_ADDRESS = "ReplyToAddress";
|
55
|
|
|
const IS_HTML = "IsHtml";
|
56
|
|
|
const FREE_SIGN_NAME = "FreeSignName";
|
57
|
|
|
const TEMPLATE_CODE = "TemplateCode";
|
58
|
|
|
const RECEIVER = "Receiver";
|
59
|
|
|
const SMS_PARAMS = "SmsParams";
|
60
|
|
|
const IMPORTANCE_LEVEL = "ImportanceLevel";
|
61
|
|
|
|
62
|
|
|
// some MNS ErrorCodes
|
63
|
|
|
const INVALID_ARGUMENT = "InvalidArgument";
|
64
|
|
|
const QUEUE_ALREADY_EXIST = "QueueAlreadyExist";
|
65
|
|
|
const QUEUE_NOT_EXIST = "QueueNotExist";
|
66
|
|
|
const MALFORMED_XML = "MalformedXML";
|
67
|
|
|
const MESSAGE_NOT_EXIST = "MessageNotExist";
|
68
|
|
|
const RECEIPT_HANDLE_ERROR = "ReceiptHandleError";
|
69
|
|
|
const BATCH_SEND_FAIL = "BatchSendFail";
|
70
|
|
|
const BATCH_DELETE_FAIL = "BatchDeleteFail";
|
71
|
|
|
|
72
|
|
|
const TOPIC_ALREADY_EXIST = "TopicAlreadyExist";
|
73
|
|
|
const TOPIC_NOT_EXIST = "TopicNotExist";
|
74
|
|
|
const SUBSCRIPTION_ALREADY_EXIST = "SubscriptionAlreadyExist";
|
75
|
|
|
const SUBSCRIPTION_NOT_EXIST = "SubscriptionNotExist";
|
76
|
|
|
}
|
77
|
|
|
|
78
|
|
|
|
79
|
|
|
/**
|
80
|
|
|
* Class AliyunAgent
|
81
|
|
|
*
|
82
|
|
|
* @property string $accessKeyId
|
83
|
|
|
* @property string $accessKeySecret
|
84
|
|
|
* @property string $signName
|
85
|
|
|
* @property string $endPoint
|
86
|
|
|
* @property string $topicName
|
87
|
|
|
*/
|
88
|
|
|
class Aliyun_MNSAgent extends Agent implements TemplateSms
|
|
|
|
|
89
|
|
|
{
|
90
|
|
|
|
91
|
|
|
private $tempId, $smsParams;
|
|
|
|
|
92
|
|
|
|
93
|
|
|
public function sendTemplateSms($to, $tempId, array $data)
|
94
|
|
|
{
|
95
|
|
|
$this->smsParams = [$to=>$data];
|
96
|
|
|
$this->tempId = $tempId;
|
97
|
|
|
$this->request();
|
98
|
|
|
|
99
|
|
|
}
|
100
|
|
|
|
101
|
|
|
protected function request()
|
102
|
|
|
{
|
103
|
|
|
$sendurl = $this->endPoint . '/topics/' . $this->topicName . '/messages';
|
104
|
|
|
$result = $this->curlPost($sendurl, [], [
|
105
|
|
|
CURLOPT_HTTPHEADER => $this->createHeaders(),
|
106
|
|
|
CURLOPT_POSTFIELDS => $this->generateBody(),
|
107
|
|
|
]);
|
108
|
|
|
$this->setResult($result);
|
109
|
|
|
}
|
110
|
|
|
|
111
|
|
|
protected function writeMessagePropertiesForPublishXML(\XMLWriter $xmlWriter)
|
112
|
|
|
{
|
113
|
|
|
|
114
|
|
|
$xmlWriter->writeElement(Constants::MESSAGE_BODY, 'smsmessage');
|
115
|
|
|
$xmlWriter->startElement(Constants::MESSAGE_ATTRIBUTES);
|
116
|
|
|
$jsonArray = array("Type" => "multiContent");
|
117
|
|
|
$jsonArray[Constants::FREE_SIGN_NAME] = $this->signName;
|
118
|
|
|
$jsonArray[Constants::TEMPLATE_CODE] = $this->tempId;
|
119
|
|
|
|
120
|
|
|
if ($this->smsParams != null) {
|
121
|
|
|
if (!is_array($this->smsParams)) {
|
122
|
|
|
throw new PhpSmsException("SmsParams should be an array!",400);
|
123
|
|
|
}
|
124
|
|
|
if (!empty($this->smsParams)) {
|
125
|
|
|
$jsonArray['SmsParams'] = json_encode($this->smsParams,JSON_FORCE_OBJECT);
|
126
|
|
|
}
|
127
|
|
|
}
|
128
|
|
|
|
129
|
|
|
if (!empty($jsonArray)) {
|
130
|
|
|
$xmlWriter->writeElement(Constants::DIRECT_SMS, json_encode($jsonArray));
|
131
|
|
|
}
|
132
|
|
|
$xmlWriter->endElement();
|
133
|
|
|
}
|
134
|
|
|
|
135
|
|
|
|
136
|
|
|
protected function generateBody()
|
137
|
|
|
{
|
138
|
|
|
$xmlWriter = new \XMLWriter;
|
139
|
|
|
$xmlWriter->openMemory();
|
140
|
|
|
$xmlWriter->startDocument("1.0", "UTF-8");
|
141
|
|
|
$xmlWriter->startElementNS(NULL, "Message", Constants::MNS_XML_NAMESPACE);
|
142
|
|
|
$this->writeMessagePropertiesForPublishXML($xmlWriter);
|
143
|
|
|
$xmlWriter->endElement();
|
144
|
|
|
$xmlWriter->endDocument();
|
145
|
|
|
|
146
|
|
|
return $xmlWriter->outputMemory();
|
147
|
|
|
}
|
148
|
|
|
|
149
|
|
|
protected function createHeaders()
|
150
|
|
|
{
|
151
|
|
|
$params = [
|
152
|
|
|
Constants::CONTENT_TYPE => 'text/xml',
|
153
|
|
|
Constants::MNS_VERSION_HEADER => Constants::MNS_VERSION,
|
154
|
|
|
'Date' => gmdate(Constants::GMT_DATE_FORMAT),
|
155
|
|
|
];
|
156
|
|
|
$params[Constants::AUTHORIZATION] = Constants::MNS . " " . $this->accessKeyId . ":" . $this->computeSignature($params);
|
157
|
|
|
|
158
|
|
|
$p = [];
|
159
|
|
|
foreach ($params as $k => $v) {
|
160
|
|
|
$p[] = "$k:$v";
|
161
|
|
|
}
|
162
|
|
|
return $p;
|
163
|
|
|
}
|
164
|
|
|
|
165
|
|
|
|
166
|
|
|
private function computeSignature($parameters)
|
167
|
|
|
{
|
168
|
|
|
$contentMd5 = '';
|
169
|
|
|
$contentType = $parameters[Constants::CONTENT_TYPE];
|
170
|
|
|
$date = $parameters['Date'];
|
171
|
|
|
$canonicalizedMNSHeaders = Constants::MNS_VERSION_HEADER.':' . $parameters[Constants::MNS_VERSION_HEADER];
|
172
|
|
|
$canonicalizedResource = '/topics/' . $this->topicName . '/messages';
|
173
|
|
|
$stringToSign = strtoupper('POST') . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n" . $canonicalizedMNSHeaders . "\n" . $canonicalizedResource;
|
174
|
|
|
|
175
|
|
|
return base64_encode(hash_hmac('sha1', $stringToSign, $this->accessKeySecret, true));
|
176
|
|
|
}
|
177
|
|
|
|
178
|
|
|
|
179
|
|
|
protected function loadXmlContent($content)
|
180
|
|
|
{
|
181
|
|
|
$xmlReader = new \XMLReader();
|
182
|
|
|
$isXml = $xmlReader->XML($content);
|
183
|
|
|
if ($isXml === FALSE) {
|
|
|
|
|
184
|
|
|
|
185
|
|
|
}
|
186
|
|
|
try {
|
187
|
|
|
while ($xmlReader->read()) {}
|
|
|
|
|
188
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
189
|
|
|
|
190
|
|
|
}
|
191
|
|
|
$xmlReader->XML($content);
|
192
|
|
|
return $xmlReader;
|
193
|
|
|
}
|
194
|
|
|
|
195
|
|
|
protected function setResult($result)
|
196
|
|
|
{
|
197
|
|
|
|
198
|
|
|
if ($result['request']) {
|
199
|
|
|
$this->result(Agent::INFO, $result['response']);
|
200
|
|
|
|
201
|
|
|
|
202
|
|
|
$messageId = NULL;
|
203
|
|
|
$messageBodyMD5 = NULL;
|
|
|
|
|
204
|
|
|
$errorCode = NULL;
|
205
|
|
|
$errorMessage = NULL;
|
|
|
|
|
206
|
|
|
|
207
|
|
|
$xmlReader= $this->loadXmlContent($result['response']);
|
208
|
|
|
while ($xmlReader->read())
|
209
|
|
|
{
|
210
|
|
|
switch ($xmlReader->nodeType)
|
211
|
|
|
{
|
212
|
|
|
case \XMLReader::ELEMENT:
|
213
|
|
|
switch ($xmlReader->name) {
|
214
|
|
View Code Duplication |
case Constants::MESSAGE_ID:
|
|
|
|
|
215
|
|
|
$xmlReader->read();
|
216
|
|
|
if ($xmlReader->nodeType == \XMLReader::TEXT)
|
217
|
|
|
{
|
218
|
|
|
$messageId = $xmlReader->value;
|
219
|
|
|
}
|
220
|
|
|
break;
|
221
|
|
View Code Duplication |
case Constants::MESSAGE_BODY_MD5:
|
|
|
|
|
222
|
|
|
$xmlReader->read();
|
223
|
|
|
if ($xmlReader->nodeType == \XMLReader::TEXT)
|
224
|
|
|
{
|
225
|
|
|
$messageBodyMD5 = $xmlReader->value;
|
|
|
|
|
226
|
|
|
}
|
227
|
|
|
break;
|
228
|
|
View Code Duplication |
case Constants::ERROR_CODE:
|
|
|
|
|
229
|
|
|
$xmlReader->read();
|
230
|
|
|
if ($xmlReader->nodeType == \XMLReader::TEXT)
|
231
|
|
|
{
|
232
|
|
|
$errorCode = $xmlReader->value;
|
233
|
|
|
}
|
234
|
|
|
break;
|
235
|
|
View Code Duplication |
case Constants::ERROR_MESSAGE:
|
|
|
|
|
236
|
|
|
$xmlReader->read();
|
237
|
|
|
if ($xmlReader->nodeType == \XMLReader::TEXT)
|
238
|
|
|
{
|
239
|
|
|
$errorMessage = $xmlReader->value;
|
|
|
|
|
240
|
|
|
}
|
241
|
|
|
break;
|
242
|
|
|
}
|
243
|
|
|
break;
|
244
|
|
|
case \XMLReader::END_ELEMENT:
|
245
|
|
|
if ($xmlReader->name == 'Message')
|
246
|
|
|
{
|
247
|
|
View Code Duplication |
if ($messageId != NULL)
|
|
|
|
|
248
|
|
|
{
|
249
|
|
|
$this->result(Agent::SUCCESS, true);
|
250
|
|
|
}
|
251
|
|
|
else
|
252
|
|
|
{
|
253
|
|
|
$this->result(Agent::SUCCESS, false);
|
254
|
|
|
}
|
255
|
|
|
}
|
256
|
|
|
break;
|
257
|
|
|
}
|
258
|
|
|
}
|
259
|
|
|
|
260
|
|
View Code Duplication |
if ($messageId != NULL)
|
|
|
|
|
261
|
|
|
{
|
262
|
|
|
$this->result(Agent::SUCCESS, true);
|
263
|
|
|
}
|
264
|
|
|
else
|
265
|
|
|
{
|
266
|
|
|
$this->result(Agent::SUCCESS, false);
|
267
|
|
|
}
|
268
|
|
|
|
269
|
|
|
if ($errorCode!=NULL)
|
|
|
|
|
270
|
|
|
$this->result(Agent::CODE, $errorCode);
|
271
|
|
|
|
272
|
|
|
|
273
|
|
|
} else {
|
274
|
|
|
$this->result(Agent::INFO, 'request failed');
|
275
|
|
|
}
|
276
|
|
|
}
|
277
|
|
|
|
278
|
|
|
|
279
|
|
|
}
|
280
|
|
|
|
Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.