├── .gitignore ├── README.md ├── composer.json └── lib └── SMSCenter └── SMSCenter.php /.gitignore: -------------------------------------------------------------------------------- 1 | .log 2 | .old 3 | .temp 4 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SMSCenter 2 | ========= 3 | 4 | Класс для работы с сервисом smsc.ru (SMS-Центр) 5 | 6 | Функции: 7 | * отправка одного/нескольких сообщений на один/несколько номеров одним запросом 8 | * проверка статуса сообщений 9 | * получение стоимости рассылки 10 | * проверка баланса 11 | * получение информации об операторе по номеру 12 | 13 | Минимальные требования — **PHP 5.4**+ 14 | 15 | *** 16 | 17 | Допустимые ключи массива настроек (в скобках значения по-умолчанию): 18 | ```php 19 | $default = [ 20 | 'sender', // имя отправителя 21 | 'translit', // кодировать ли сообщения в транслит (self::TRANSLIT_NONE) 22 | 'charset', // кодировка запроса и ответа (self::CHARSET_UTF8) 23 | 'fmt', // формат ответа сервера (self::FMT_JSON) 24 | 'type', // тип сообщения (self::MSG_SMS), замена push, ping, hlr и прочих 25 | 'cost', // запрашивать ли стоимость (self::COST_NO) 26 | 'time', // время отправки сообщения (null) 27 | 'tz', // часовой пояс параметра time (null) 28 | 'period', // (null) 29 | 'freq', // (null) 30 | 'maxsms', // (null) 31 | 'err' // (null) 32 | ]; 33 | ``` 34 | 35 | *** 36 | 37 | Примеры использования: 38 | ```php 39 | SMSCenter::CHARSET_UTF8, 43 | 'fmt' => SMSCenter::FMT_XML 44 | ]); 45 | 46 | // Отправка сообщения 47 | $smsc->send('+7991111111', 'Превед, медведы!', 'SuperIvan'); 48 | 49 | // Отправка сообщения на 2 номера 50 | $smsc->send(['+7(999)1111111', '+7(999)222-22-22'], 'Превед, медведы!', 'SuperIvan'); 51 | $smsc->send('+7(999)1111111,+7(999)222-22-22', 'Превед, медведы!', 'SuperIvan'); 52 | 53 | // Отправка разных сообщений на разные номера 54 | $sms->sendMulti([ 55 | ['+79991111111', "Text 1\nnew line"], 56 | '+79992222222' => 'Text 2', 57 | ]); 58 | 59 | // Получение стоимости рассылки 60 | $smsc->getCost('7991111111,79992222222', 'Начало около 251 млн лет, конец — 201 млн лет назад.'); 61 | 62 | // Получение стоимости рассылки разных сообщений на разные номера 63 | $sms->getCostMulti([ 64 | '79991111111' => 'Text 1', 65 | '79992222222' => 'Text 2', 66 | ]); 67 | 68 | // Получение баланса 69 | echo $smsc->getBalance(), ' руб.'; // "72.2 руб." 70 | 71 | // Получение информации об операторе 72 | $smsc->getOperatorInfo('7991111111'); 73 | 74 | // Получения статуса сообщения 75 | $smsc->getStatus('+7991111111', 6, SMSCenter::STATUS_INFO_EXT); 76 | 77 | // Проверка тарифной зоны 78 | if ($sms->getChargingZone('+79991111111') === self::ZONE_RU) { 79 | ... 80 | } 81 | ``` 82 | 83 | *** 84 | 85 | Лицензия: Apache License, Version 2.0 86 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jhaoda/smscenter", 3 | "type": "library", 4 | "description": "Класс для работы с API сервиса smsc.ru", 5 | "keywords": ["smsc", "smscenter", "smsc.ru"], 6 | "homepage": "https://github.com/jhaoda/SMSCenter", 7 | "license": "Apache-2.0", 8 | "authors": [ 9 | { 10 | "name": "JhaoDa", 11 | "email": "jhaoda@gmail.com" 12 | } 13 | ], 14 | "support": { 15 | "source": "https://github.com/jhaoda/SMSCenter", 16 | "issues": "https://github.com/jhaoda/SMSCenter/issues" 17 | }, 18 | "require": { 19 | "php": ">=5.4.0" 20 | }, 21 | "autoload": { 22 | "psr-0": { 23 | "SMSCenter\\": "lib/" 24 | } 25 | }, 26 | "minimum-stability": "stable", 27 | "notify-batch": "http://jhaoda.ru/install-notify" 28 | } 29 | -------------------------------------------------------------------------------- /lib/SMSCenter/SMSCenter.php: -------------------------------------------------------------------------------- 1 | 8 | * @link https://github.com/jhaoda/SMSCenter 9 | * @license http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Copyright 2013 JhaoDa 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | namespace SMSCenter; 25 | 26 | class SMSCenter 27 | { 28 | const VERSION = '2.0.1'; 29 | 30 | const MSG_SMS = 0; 31 | const MSG_FLASH = 1; 32 | const MSG_WAP = 2; 33 | const MSG_HLR = 3; 34 | const MSG_BIN = 4; 35 | const MSG_HEX = 5; 36 | const MSG_PING = 6; 37 | 38 | const COST_NO = 0; 39 | const COST_ONLY = 1; 40 | const COST_TOTAL = 2; 41 | const COST_BALANCE = 3; 42 | 43 | const TRANSLIT_NONE = 0; 44 | const TRANSLIT_YES = 1; 45 | const TRANSLIT_ALT = 2; 46 | 47 | const FMT_PLAIN = 0; 48 | const FMT_PLAIN_ALT = 1; 49 | const FMT_XML = 2; 50 | const FMT_JSON = 3; 51 | 52 | const STATUS_PLAIN = 0; 53 | const STATUS_INFO = 1; 54 | const STATUS_INFO_EXT = 2; 55 | 56 | const CHARSET_UTF8 = 'utf-8'; 57 | const CHARSET_KOI8 = 'koi8-r'; 58 | const CHARSET_1251 = 'windows-1251'; 59 | 60 | const ZONE_RU = 10; 61 | const ZONE_UA = 20; 62 | const ZONE_SNG = 30; 63 | const ZONE_1 = 1; 64 | const ZONE_2 = 2; 65 | const ZONE_3 = 3; 66 | 67 | private $login; 68 | private $password; 69 | private $useSSL; 70 | private $options = []; 71 | private $types = ['', 'flash=1', 'push=1', 'hlr=1', 'bin=1', 'bin=2', 'ping=1']; 72 | 73 | private static $chargingZonePatterns = [ 74 | self::ZONE_RU => '~^\+?(79|73|74|78)~', 75 | self::ZONE_UA => '~^\+?380~', 76 | self::ZONE_SNG => '~^\+?(7940|374|375|995|77|996|370|992|993|998)~', 77 | self::ZONE_1 => '~^\+?(994|213|244|376|54|93|880|973|591|387|58|84|241|233|502|852|299|20|972|91|92|62|962|964|98|353|354|855|237|1|254|357|57|242|506|965|856|231|423|352|261|389|60|960|356|52|976|971|595|503|966|381|65|421|386|66|255|216|598|63|385|382|56|94|593|372|27|1876|81)~', 78 | self::ZONE_2 => '~^\+?(44|359|30|45|86|53|371|373|48|886|358|420|82)~' 79 | ]; 80 | 81 | private static $curl; 82 | 83 | /** 84 | * Инициализация. 85 | * 86 | * @param string $login логин 87 | * @param string $password пароль 88 | * @param bool $useSSL использовать HTTPS или нет 89 | * @param array $options прочие параметры 90 | */ 91 | public function __construct($login, $password, $useSSL = false, array $options = []) 92 | { 93 | $this->login = $login; 94 | $this->password = $password; 95 | $this->useSSL = $useSSL; 96 | 97 | $default = [ 98 | 'charset' => self::CHARSET_UTF8, 99 | 'fmt' => self::FMT_JSON 100 | ]; 101 | 102 | $this->options = array_merge($default, $options); 103 | } 104 | 105 | /** 106 | * Отправка сообщения. 107 | * 108 | * @param string|array $phones номера телефонов 109 | * @param string $message текст сообщения 110 | * @param string $sender имя отправителя 111 | * @param array $options дополнительные параметры 112 | * 113 | * @throws \InvalidArgumentException если список телефонов пуст или длина сообщения больше 1000 символов 114 | * 115 | * @return bool|string|\stdClass результат выполнения запроса в виде строки, объекта (FMT_JSON) или false в случае ошибки. 116 | */ 117 | public function send($phones, $message, $sender = null, array $options = []) 118 | { 119 | if (empty($phones)) { 120 | throw new \InvalidArgumentException("The 'phones' parameter is empty."); 121 | } 122 | 123 | if (is_array($phones)) { 124 | $phones = array_map(__CLASS__.'::clearPhone', $phones); 125 | $phones = implode(';', $phones); 126 | } else { 127 | $phones = self::clearPhone($phones); 128 | } 129 | 130 | if ($message !== null && empty($message)) { 131 | throw new \InvalidArgumentException('The message is empty.'); 132 | } 133 | 134 | if (mb_strlen($message, 'UTF-8') > 800) { 135 | throw new \InvalidArgumentException('The maximum length of a message is 800 symbols.'); 136 | } 137 | 138 | $options['phones'] = $phones; 139 | $options['mes'] = $message; 140 | 141 | if ($sender !== null) { 142 | $options['sender'] = $sender; 143 | } 144 | 145 | return $this->sendRequest('send', $options); 146 | } 147 | 148 | /** 149 | * Отправка разных сообщений на несколько номеров. 150 | * 151 | * @param array $list массив [номер => сообщение] или [номер, сообщение] 152 | * @param string $sender имя отправителя 153 | * @param array $options дополнительные параметры 154 | * 155 | * @return bool|string|\stdClass результат выполнения запроса в виде строки, объекта (FMT_JSON) или false в случае ошибки. 156 | */ 157 | public function sendMulti(array $list, $sender = null, array $options = []) 158 | { 159 | foreach ($list as $key => $value) { 160 | if (is_array($value)) { 161 | list($key, $value) = $value; 162 | } 163 | 164 | $options['list'][] = self::clearPhone($key).':'.str_replace("\n", '\n', $value); 165 | } 166 | 167 | $options['list'] = implode("\n", $options['list']); 168 | 169 | if ($sender !== null) { 170 | $options['sender'] = $sender; 171 | } 172 | 173 | return $this->sendRequest('send', $options); 174 | } 175 | 176 | /** 177 | * Проверка номеров на доступность в реальном времени. 178 | * 179 | * @param string|array $phones номера телефонов 180 | * 181 | * @return bool|string|\stdClass результат выполнения запроса в виде строки, объекта (FMT_JSON) или false в случае ошибки. 182 | */ 183 | public function pingPhone($phones) 184 | { 185 | return $this->send($phones, null, null, ['type' => self::MSG_PING]); 186 | } 187 | 188 | /** 189 | * Получение стоимости рассылки. 190 | * 191 | * @param string|array $phones номера телефонов 192 | * @param string $message текст сообщения 193 | * @param array $options дополнительные опции 194 | * 195 | * @return bool|string|\stdClass стоимость рассылки в виде строки, объекта (FMT_JSON) или FALSE в случае ошибки. 196 | */ 197 | public function getCost($phones, $message, array $options = []) 198 | { 199 | $options['cost'] = self::COST_ONLY; 200 | 201 | return $this->send($phones, $message, null, $options); 202 | } 203 | 204 | /** 205 | * Получение стоимости рассылки разные сообщения на несколько номеров. 206 | * 207 | * @param array $list массив [номер => сообщение] или [номер, сообщение] 208 | * @param array $options дополнительные опции 209 | * 210 | * @return bool|string|\stdClass стоимость рассылки в виде строки, объекта (FMT_JSON) или FALSE в случае ошибки. 211 | */ 212 | public function getCostMulti(array $list, array $options = []) 213 | { 214 | $options['cost'] = self::COST_ONLY; 215 | 216 | return $this->sendMulti($list, null, $options); 217 | } 218 | 219 | /** 220 | * Получение статуса сообщения. 221 | * 222 | * @param string $phone номер телефона 223 | * @param int|string $id идентификатор сообщения 224 | * @param int $mode вид ответа: обычный, полный, расширеный 225 | * 226 | * @return bool|string|\stdClass статус сообщения в виде строки, объекта (FMT_JSON) или false в случае ошибки. 227 | */ 228 | public function getStatus($phone, $id, $mode = self::STATUS_PLAIN) 229 | { 230 | return $this->sendRequest('status', [ 231 | 'phone' => $phone, 232 | 'id' => (int) $id, 233 | 'all' => (int) $mode, 234 | ]); 235 | } 236 | 237 | /** 238 | * Получение информации об операторе: название и регион регистрации номера абонента. 239 | * 240 | * @param string $phone номер телефона 241 | * 242 | * @return bool|string|\stdClass информация об операторе в виде строки, объекта (FMT_JSON) или false в случае ошибки. 243 | */ 244 | public function getOperatorInfo($phone) 245 | { 246 | return $this->sendRequest('info', [ 247 | 'get_operator' => '1', 248 | 'phone' => $phone, 249 | ]); 250 | } 251 | 252 | /** 253 | * Запрос баланса. 254 | * 255 | * @param int $format формат ответа сервера (self::FMT_JSON) 256 | * 257 | * @return string баланс в виде строки или false в случае ошибки. 258 | */ 259 | public function getBalance($format = self::FMT_JSON) 260 | { 261 | $response = $this->sendRequest('balance', ['fmt' => $format]); 262 | 263 | if ($format === self::FMT_JSON) { 264 | return json_decode($response)->balance; 265 | } 266 | 267 | if ($format === self::FMT_XML) { 268 | return preg_replace('~~', '', $response); 269 | } 270 | 271 | return $response; 272 | } 273 | 274 | /** 275 | * Определение тарифной зоны. 276 | * 277 | * @param string $phone номер телефона 278 | * 279 | * @return int номер тарифной зоны (константы self::ZONE_*) 280 | */ 281 | public function getChargingZone($phone) 282 | { 283 | $phone = self::clearPhone($phone); 284 | 285 | foreach (self::$chargingZonePatterns as $key => $value) { 286 | if (preg_match($value, $phone)) { 287 | return $key; 288 | } 289 | } 290 | 291 | return self::ZONE_3; 292 | } 293 | 294 | /** 295 | * Самая умная функция. 296 | * 297 | * @param string $resource 298 | * @param array $options 299 | * 300 | * @throws \InvalidArgumentException 301 | * 302 | * @return bool|string|\stdClass 303 | */ 304 | private function sendRequest($resource, array $options) 305 | { 306 | $options = array_merge($this->options, $options); 307 | 308 | if (in_array($resource, ['status', 'info'])) { 309 | if (isset($options['phone']) && !empty($options['phone'])) { 310 | $options['phone'] = self::clearPhone($options['phone']); 311 | } else { 312 | throw new \InvalidArgumentException("The 'phone' parameter is empty."); 313 | } 314 | } 315 | 316 | $params = [ 317 | 'login='.urlencode($this->login), 318 | 'psw='.urlencode($this->password), 319 | ]; 320 | 321 | foreach ($options as $key => $value) { 322 | switch ($key) { 323 | case 'type': 324 | if ($value > 0 && $value < count($this->types)) { 325 | $params[] = $this->types[$value]; 326 | } 327 | break; 328 | default: 329 | if (!empty($value)) { 330 | $params[] = $key.'='.urlencode($value); 331 | } 332 | } 333 | } 334 | 335 | $i = 0; 336 | 337 | do { 338 | (!$i) || sleep(2); 339 | $ret = $this->execRequest($resource, $params); 340 | } while ($ret == '' && ++$i < 3); 341 | 342 | if (($resource == 'info' || $resource == 'status') && $options['fmt'] == self::FMT_JSON) { 343 | if ($options['charset'] === self::CHARSET_1251) { 344 | $ret = mb_convert_encoding($ret, 'UTF-8', 'WINDOWS-1251'); 345 | } elseif ($options['charset'] === self::CHARSET_KOI8) { 346 | $ret = mb_convert_encoding($ret, 'UTF-8', 'KOI8-R'); 347 | } 348 | } 349 | 350 | return !empty($ret) ? $ret : false; 351 | } 352 | 353 | /** 354 | * Непосредственно выполнение запроса. 355 | * 356 | * @param string $resource 357 | * @param array $params 358 | * 359 | * @return string ответ сервера 360 | */ 361 | private function execRequest($resource, array $params) 362 | { 363 | $url = ($this->useSSL ? 'https' : 'http').'://smsc.ru/sys/'.$resource.'.php'; 364 | $query = implode('&', $params); 365 | $isPOST = $resource === 'send'; 366 | 367 | if (function_exists('curl_init')) { 368 | if (!self::$curl) { 369 | self::$curl = curl_init(); 370 | curl_setopt_array(self::$curl, [ 371 | CURLOPT_RETURNTRANSFER => true, 372 | CURLOPT_SSL_VERIFYPEER => false, 373 | CURLOPT_CONNECTTIMEOUT => 5, 374 | CURLOPT_TIMEOUT => 10, 375 | ]); 376 | } 377 | 378 | if ($isPOST) { 379 | curl_setopt_array(self::$curl, [ 380 | CURLOPT_URL => $url, 381 | CURLOPT_POST => true, 382 | CURLOPT_POSTFIELDS => $query, 383 | ]); 384 | } else { 385 | curl_setopt(self::$curl, CURLOPT_URL, $url.'?'.$query); 386 | } 387 | 388 | $response = curl_exec(self::$curl); 389 | } else { 390 | $options = ['timeout' => 5]; 391 | 392 | if ($isPOST) { 393 | $options = array_merge($options, [ 394 | 'method' => 'POST', 395 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 396 | 'content' => $query, 397 | ]); 398 | } else { 399 | $url .= '?'.$query; 400 | } 401 | 402 | $response = file_get_contents($url, false, stream_context_create(['http' => $options])); 403 | } 404 | 405 | return $response; 406 | } 407 | 408 | /** 409 | * Удаляет из номера любые символы, кроме цифр. 410 | * 411 | * @param string $phone номер телефона 412 | * 413 | * @return string «чистый» номер телефона 414 | */ 415 | public static function clearPhone($phone) 416 | { 417 | return preg_replace('~[^\d+]~', '', $phone); 418 | } 419 | } 420 | --------------------------------------------------------------------------------