├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE-MIT ├── README.md ├── SMSCounter.php ├── Tests └── SMSCounterTest.php ├── composer.json └── phpunit.xml.dist /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | indent_size = 4 13 | 14 | [*.php] 15 | indent_size = 4 16 | 17 | [*.md] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | test/vendor 3 | composer.phar 4 | composer.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | - nightly 8 | 9 | matrix: 10 | allow_failures: 11 | - php: nightly 12 | 13 | before_script: 14 | - composer install 15 | 16 | script: vendor/bin/phpunit 17 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Instasent - SMS Counter for PHP 2 | 3 | Character counter for SMS Messages 4 | 5 | [![Build Status](https://img.shields.io/travis/instasent/sms-counter-php.svg?style=flat-square)](https://travis-ci.org/instasent/sms-counter-php) 6 | [![SensioLabsInsight](https://img.shields.io/sensiolabs/i/0a2fa87a-0287-46f6-b8b5-818b44a2b9f9.svg?style=flat-square)](https://insight.sensiolabs.com/projects/0a2fa87a-0287-46f6-b8b5-818b44a2b9f9) 7 | 8 | ## Usage 9 | 10 | ```php 11 | use Instasent\SMSCounter\SMSCounter; 12 | 13 | $smsCounter = new SMSCounter(); 14 | $smsCounter->count('some-string-to-be-counted'); 15 | $smsCounter->countWithShiftTables('some-string-to-be-counted'); 16 | ``` 17 | 18 | which returns 19 | ``` 20 | stdClass Object 21 | ( 22 | [encoding] => GSM_7BIT 23 | [length] => 25 24 | [per_message] => 160 25 | [remaining] => 135 26 | [messages] => 1 27 | ) 28 | ``` 29 | 30 | ##### UTF16 notice 31 | 32 | When using unicode chars over U+10000 (mainly emoticons 😎) on messages larger than 70 chars the _remaining_ value will actually be the **remaining chars in last message part only**, this is due to how those chars are encoded using two 16bit chars and max part length being an odd number (67) 33 | 34 | #### Sanitization 35 | 36 | You can sanitize your text to be a valid strict GSM 03.38 charset 37 | 38 | ```php 39 | use Instasent\SMSCounter\SMSCounter; 40 | 41 | $smsCounter = new SMSCounter(); 42 | $smsCounter->sanitizeToGSM('dadáó'); //return dadao 43 | ``` 44 | 45 | #### National Language Shift Tables 46 | 47 | Starting release 8 of GSM 03.38 some additional charsets are allowed. This is the list of such National Language Shift Tables currently supported 48 | 49 | - [Turkish](https://en.wikipedia.org/wiki/GSM_03.38#Turkish_language_(Latin_script)) 50 | - [Spanish](https://en.wikipedia.org/wiki/GSM_03.38#Spanish_language_(Latin_script)) 51 | - [Portuguese](https://en.wikipedia.org/wiki/GSM_03.38#Portuguese_language_(Latin_script)) 52 | 53 | 54 | ## Installation 55 | 56 | `sms-counter-php` is available via [composer](http://getcomposer.org) on [packagist](https://packagist.org/packages/instasent/sms-counter-php). 57 | 58 | ```json 59 | { 60 | "require": { 61 | "instasent/sms-counter-php": "^0.4" 62 | } 63 | } 64 | ``` 65 | 66 | ## License 67 | 68 | SMS Counter (PHP) is released under the [MIT License](LICENSE-MIT.md) 69 | 70 | ### Mentions 71 | 72 | * Original idea : [danxexe/sms-counter](https://github.com/danxexe/sms-counter) 73 | * Fork Idea from: [acpmasquerade/sms-counter-php](https://github.com/acpmasquerade/sms-counter-php) 74 | -------------------------------------------------------------------------------- /SMSCounter.php: -------------------------------------------------------------------------------- 1 | getGsm7bitMap(), 104 | $this->getAddedGsm7bitExMap() 105 | ); 106 | } 107 | 108 | public function getTurkishGsm7bitMap() 109 | { 110 | return [ 111 | 10, 12, 13, 32, 33, 34, 35, 36, 112 | 37, 38, 39, 40, 41, 42, 43, 44, 113 | 45, 46, 47, 48, 49, 50, 51, 52, 114 | 53, 54, 55, 56, 57, 58, 59, 60, 115 | 61, 62, 63, 64, 65, 66, 67, 68, 116 | 69, 70, 71, 72, 73, 74, 75, 76, 117 | 77, 78, 79, 80, 81, 82, 83, 84, 118 | 85, 86, 87, 88, 89, 90, 91, 92, 119 | 93, 94, 95, 97, 98, 99, 100, 101, 120 | 102, 103, 104, 105, 106, 107, 108, 121 | 109, 110, 111, 112, 113, 114, 115, 122 | 116, 117, 118, 119, 120, 121, 122, 123 | 123, 124, 125, 126, 163, 164, 165, 124 | 167, 196, 197, 199, 201, 209, 214, 125 | 220, 223, 224, 228, 229, 231, 233, 126 | 241, 242, 246, 249, 252, 286, 287, 127 | 304, 305, 350, 351, 915, 916, 920, 128 | 923, 926, 928, 931, 934, 936, 937, 129 | 8364, 130 | ]; 131 | } 132 | 133 | public function getAddedTurkishGsm7bitExMap() 134 | { 135 | return [12, 91, 92, 93, 94, 123, 124, 125, 126, 286, 287, 304, 305, 350, 351, 8364]; 136 | } 137 | 138 | public function getAddedSpanishGsm7bitExMap() 139 | { 140 | return [12, 91, 92, 93, 94, 123, 124, 125, 126, 193, 205, 211, 218, 225, 231, 237, 243, 250, 8364]; 141 | } 142 | 143 | public function getPortugueseGsm7bitMap() 144 | { 145 | return [ 146 | 10, 12, 13, 32, 33, 34, 35, 36, 147 | 37, 38, 39, 40, 41, 42, 43, 44, 148 | 45, 46, 47, 48, 49, 50, 51, 52, 149 | 53, 54, 55, 56, 57, 58, 59, 60, 150 | 61, 62, 63, 64, 65, 66, 67, 68, 151 | 69, 70, 71, 72, 73, 74, 75, 76, 152 | 77, 78, 79, 80, 81, 82, 83, 84, 153 | 85, 86, 87, 88, 89, 90, 91, 92, 154 | 93, 94, 95, 96, 97, 98, 99, 100, 155 | 101, 102, 103, 104, 105, 106, 107, 108, 156 | 109, 110, 111, 112, 113, 114, 115, 116, 157 | 117, 118, 119, 120, 121, 122, 123, 124, 158 | 125, 126, 163, 165, 167, 170, 186, 192, 159 | 193, 194, 195, 199, 201, 202, 205, 211, 160 | 212, 213, 218, 220, 224, 225, 226, 227, 161 | 231, 233, 234, 237, 242, 243, 244, 245, 162 | 250, 252, 915, 916, 920, 928, 931, 934, 163 | 936, 937, 8364, 8734, 164 | ]; 165 | } 166 | 167 | public function getAddedPortugueseGsm7bitExMap() 168 | { 169 | return [ 170 | 12, 91, 92, 93, 94, 123, 124, 125, 171 | 126, 193, 194, 195, 202, 205, 211, 212, 172 | 213, 218, 225, 226, 227, 231, 234, 237, 173 | 242, 243, 245, 250, 915, 920, 928, 931, 174 | 934, 936, 937, 8364, 175 | ]; 176 | } 177 | 178 | /** 179 | * Detects the encoding, Counts the characters, message length, remaining characters. 180 | * 181 | * @return \stdClass Object with params encoding,length, per_message, remaining, messages 182 | */ 183 | public function count($text) 184 | { 185 | return $this->doCount($text, false); 186 | } 187 | 188 | /** 189 | * Detects the encoding, Counts the characters, message length, remaining characters. 190 | * Supports language shift tables characters. 191 | * 192 | * @return \stdClass Object with params encoding,length, per_message, remaining, messages 193 | */ 194 | public function countWithShiftTables($text) 195 | { 196 | return $this->doCount($text, true); 197 | } 198 | 199 | /** 200 | * @return \stdClass Object with params encoding,length, per_message, remaining, messages 201 | */ 202 | private function doCount($text, $supportShiftTables) 203 | { 204 | $unicodeArray = $this->utf8ToUnicode($text); 205 | 206 | // variable to catch if any ex chars while encoding detection. 207 | $exChars = []; 208 | $encoding = $supportShiftTables 209 | ? $this->detectEncodingWithShiftTables($text, $exChars) 210 | : $this->detectEncoding($text, $exChars); 211 | 212 | $length = count($unicodeArray); 213 | 214 | if ($encoding === self::GSM_7BIT_EX) { 215 | $lengthExchars = count($exChars); 216 | // Each exchar in the GSM 7 Bit encoding takes one more space 217 | // Hence the length increases by one char for each of those Ex chars. 218 | $length += $lengthExchars; 219 | } elseif ($encoding === self::UTF16) { 220 | // Unicode chars over U+10000 occupy an extra byte 221 | $lengthExtra = array_reduce( 222 | $unicodeArray, 223 | function ($carry, $char) { 224 | if ($char >= 65536) { 225 | $carry++; 226 | } 227 | 228 | return $carry; 229 | }, 230 | 0 231 | ); 232 | 233 | $length += $lengthExtra; 234 | } 235 | 236 | // Select the per message length according to encoding and the message length 237 | switch ($encoding) { 238 | case self::GSM_7BIT: 239 | $perMessage = self::GSM_7BIT_LEN; 240 | if ($length > self::GSM_7BIT_LEN) { 241 | $perMessage = self::GSM_7BIT_LEN_MULTIPART; 242 | } 243 | break; 244 | 245 | case self::GSM_7BIT_EX: 246 | $perMessage = self::GSM_7BIT_EX_LEN; 247 | if ($length > self::GSM_7BIT_EX_LEN) { 248 | $perMessage = self::GSM_7BIT_EX_LEN_MULTIPART; 249 | } 250 | break; 251 | 252 | default: 253 | $perMessage = self::UTF16_LEN; 254 | if ($length > self::UTF16_LEN) { 255 | $perMessage = self::UTF16_LEN_MULTIPART; 256 | } 257 | 258 | break; 259 | } 260 | 261 | $messages = (int) ceil($length / $perMessage); 262 | 263 | if ($encoding === self::UTF16 && $length > $perMessage) { 264 | $count = 0; 265 | foreach ($unicodeArray as $char) { 266 | if ($count === $perMessage) { 267 | $count = 0; 268 | } elseif ($count > $perMessage) { 269 | $count = 2; 270 | } 271 | 272 | $count += $char >= 65536 ? 2 : 1; 273 | } 274 | 275 | $remaining = $perMessage - ($count > $perMessage ? 2 : $count); 276 | } else { 277 | $remaining = ($perMessage * $messages) - $length; 278 | } 279 | 280 | $returnset = new \stdClass(); 281 | 282 | $returnset->encoding = $encoding; 283 | $returnset->length = $length; 284 | $returnset->per_message = $perMessage; 285 | $returnset->remaining = $remaining; 286 | $returnset->messages = $messages; 287 | 288 | return $returnset; 289 | } 290 | 291 | /** 292 | * Detects the encoding of a particular text. 293 | * 294 | * @return string (GSM_7BIT|GSM_7BIT_EX|UTF16) 295 | */ 296 | public function detectEncoding($text, &$exChars) 297 | { 298 | if (!is_array($text)) { 299 | $text = $this->utf8ToUnicode($text); 300 | } 301 | 302 | $utf16Chars = array_diff($text, $this->getGsm7bitExMap()); 303 | if (count($utf16Chars)) { 304 | return self::UTF16; 305 | } 306 | 307 | $exChars = array_intersect($text, $this->getAddedGsm7bitExMap()); 308 | if (count($exChars)) { 309 | return self::GSM_7BIT_EX; 310 | } 311 | 312 | return self::GSM_7BIT; 313 | } 314 | 315 | /** 316 | * Detects the encoding of a particular text. 317 | * Supports language shift tables characters. 318 | * 319 | * @return string (GSM_7BIT|GSM_7BIT_EX|UTF16) 320 | */ 321 | public function detectEncodingWithShiftTables($text, &$exChars) 322 | { 323 | if (!is_array($text)) { 324 | $text = $this->utf8ToUnicode($text); 325 | } 326 | 327 | $gsmCharMap = array_merge( 328 | $this->getGsm7bitExMap(), 329 | $this->getTurkishGsm7bitMap(), 330 | $this->getAddedTurkishGsm7bitExMap(), 331 | $this->getAddedSpanishGsm7bitExMap(), 332 | $this->getPortugueseGsm7bitMap(), 333 | $this->getAddedPortugueseGsm7bitExMap() 334 | ); 335 | 336 | $utf16Chars = array_diff($text, $gsmCharMap); 337 | if (count($utf16Chars)) { 338 | return self::UTF16; 339 | } 340 | 341 | $addedGsmCharMap = array_merge( 342 | $this->getAddedGsm7bitExMap(), 343 | $this->getAddedTurkishGsm7bitExMap(), 344 | $this->getAddedSpanishGsm7bitExMap(), 345 | $this->getAddedPortugueseGsm7bitExMap() 346 | ); 347 | 348 | $exChars = array_intersect($text, $addedGsmCharMap); 349 | if (count($exChars)) { 350 | return self::GSM_7BIT_EX; 351 | } 352 | 353 | return self::GSM_7BIT; 354 | } 355 | 356 | /** 357 | * Generates array of unicode points for the utf8 string. 358 | * 359 | * @return array 360 | */ 361 | public function utf8ToUnicode($str) 362 | { 363 | $unicode = []; 364 | $values = []; 365 | $lookingFor = 1; 366 | $len = strlen($str); 367 | 368 | for ($i = 0; $i < $len; $i++) { 369 | $thisValue = ord($str[$i]); 370 | 371 | if ($thisValue < 128) { 372 | $unicode[] = $thisValue; 373 | } 374 | 375 | if ($thisValue >= 128) { 376 | if (count($values) === 0) { 377 | $lookingFor = 2; 378 | 379 | if ($thisValue >= 240) { 380 | $lookingFor = 4; 381 | } elseif ($thisValue >= 224) { 382 | $lookingFor = 3; 383 | } 384 | } 385 | 386 | $values[] = $thisValue; 387 | 388 | if (count($values) === $lookingFor) { 389 | switch ($lookingFor) { 390 | case 4: 391 | $number = (($values[0] % 16) * 262144) + (($values[1] % 64) * 4096) + (($values[2] % 64) * 64) + ($values[3] % 64); 392 | break; 393 | 394 | case 3: 395 | $number = (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64); 396 | break; 397 | 398 | case 2: 399 | $number = (($values[0] % 32) * 64) + ($values[1] % 64); 400 | break; 401 | } 402 | 403 | $unicode[] = $number; 404 | $values = []; 405 | $lookingFor = 1; 406 | } 407 | } 408 | } 409 | 410 | return $unicode; 411 | } 412 | 413 | /** 414 | * Unicode equivalent chr() function. 415 | * 416 | * @return array characters 417 | */ 418 | public function utf8Chr($unicode) 419 | { 420 | $unicode = intval($unicode); 421 | 422 | $utf8char = chr(240 | ($unicode >> 18)); 423 | $utf8char .= chr(128 | (($unicode >> 12) & 0x3F)); 424 | $utf8char .= chr(128 | (($unicode >> 6) & 0x3F)); 425 | $utf8char .= chr(128 | ($unicode & 0x3F)); 426 | 427 | if ($unicode < 128) { 428 | $utf8char = chr($unicode); 429 | } elseif ($unicode >= 128 && $unicode < 2048) { 430 | $utf8char = chr(192 | ($unicode >> 6)).chr(128 | ($unicode & 0x3F)); 431 | } elseif ($unicode >= 2048 && $unicode < 65536) { 432 | $utf8char = chr(224 | ($unicode >> 12)).chr(128 | (($unicode >> 6) & 0x3F)).chr(128 | ($unicode & 0x3F)); 433 | } 434 | 435 | return $utf8char; 436 | } 437 | 438 | /** 439 | * Converts unicode code points array to a utf8 str. 440 | * 441 | * @param array $array unicode codepoints array 442 | * 443 | * @return string utf8 encoded string 444 | */ 445 | public function unicodeToUtf8($array) 446 | { 447 | $str = ''; 448 | foreach ($array as $a) { 449 | $str .= $this->utf8Chr($a); 450 | } 451 | 452 | return $str; 453 | } 454 | 455 | /** 456 | * Removes non GSM characters from a string. 457 | * 458 | * @return string 459 | */ 460 | public function removeNonGsmChars($str) 461 | { 462 | return $this->replaceNonGsmChars($str, null); 463 | } 464 | 465 | /** 466 | * Replaces non GSM characters from a string. 467 | * 468 | * @param string $str String to be replaced 469 | * @param string $replacement String of characters to be replaced with 470 | * 471 | * @return (string|false) if replacement string is more than 1 character 472 | * in length 473 | */ 474 | public function replaceNonGsmChars($str, $replacement = null) 475 | { 476 | $validChars = $this->getGsm7bitExMap(); 477 | $allChars = $this->utf8ToUnicode($str); 478 | 479 | if (strlen($replacement) > 1) { 480 | return false; 481 | } 482 | 483 | $replacementArray = []; 484 | $unicodeArray = $this->utf8ToUnicode($replacement); 485 | $replacementUnicode = array_pop($unicodeArray); 486 | 487 | foreach ($allChars as $key => $char) { 488 | if (!in_array($char, $validChars)) { 489 | $replacementArray[] = $key; 490 | } 491 | } 492 | 493 | if ($replacement) { 494 | foreach ($replacementArray as $key) { 495 | $allChars[$key] = $replacementUnicode; 496 | } 497 | } 498 | 499 | if (!$replacement) { 500 | foreach ($replacementArray as $key) { 501 | unset($allChars[$key]); 502 | } 503 | } 504 | 505 | return $this->unicodeToUtf8($allChars); 506 | } 507 | 508 | public function sanitizeToGSM($str) 509 | { 510 | $str = $this->removeAccents($str); 511 | $str = $this->removeNonGsmChars($str); 512 | 513 | return $str; 514 | } 515 | 516 | /** 517 | * @param string $str Message text 518 | * 519 | * @return string Sanitized message text 520 | */ 521 | public function removeAccents($str) 522 | { 523 | if (!preg_match('/[\x80-\xff]/', $str)) { 524 | return $str; 525 | } 526 | 527 | $chars = [ 528 | // Decompositions for Latin-1 Supplement 529 | 'ª' => 'a', 'º' => 'o', 530 | 'À' => 'A', 'Á' => 'A', 531 | 'Â' => 'A', 'Ã' => 'A', 532 | 'È' => 'E', 533 | 'Ê' => 'E', 'Ë' => 'E', 534 | 'Ì' => 'I', 'Í' => 'I', 535 | 'Î' => 'I', 'Ï' => 'I', 536 | 'Ð' => 'D', 537 | 'Ò' => 'O', 'Ó' => 'O', 538 | 'Ô' => 'O', 'Õ' => 'O', 539 | 'Ù' => 'U', 540 | 'Ú' => 'U', 'Û' => 'U', 541 | 'Ý' => 'Y', 542 | 'Þ' => 'TH', 543 | 'á' => 'a', 544 | 'â' => 'a', 'ã' => 'a', 545 | 'ç' => 'c', 546 | 'ê' => 'e', 'ë' => 'e', 547 | 'í' => 'i', 548 | 'î' => 'i', 'ï' => 'i', 549 | 'ð' => 'd', 550 | 'ó' => 'o', 551 | 'ô' => 'o', 'õ' => 'o', 552 | 'ú' => 'u', 553 | 'û' => 'u', 554 | 'ý' => 'y', 'þ' => 'th', 555 | 'ÿ' => 'y', 556 | // Decompositions for Latin Extended-A 557 | 'Ā' => 'A', 'ā' => 'a', 558 | 'Ă' => 'A', 'ă' => 'a', 559 | 'Ą' => 'A', 'ą' => 'a', 560 | 'Ć' => 'C', 'ć' => 'c', 561 | 'Ĉ' => 'C', 'ĉ' => 'c', 562 | 'Ċ' => 'C', 'ċ' => 'c', 563 | 'Č' => 'C', 'č' => 'c', 564 | 'Ď' => 'D', 'ď' => 'd', 565 | 'Đ' => 'D', 'đ' => 'd', 566 | 'Ē' => 'E', 'ē' => 'e', 567 | 'Ĕ' => 'E', 'ĕ' => 'e', 568 | 'Ė' => 'E', 'ė' => 'e', 569 | 'Ę' => 'E', 'ę' => 'e', 570 | 'Ě' => 'E', 'ě' => 'e', 571 | 'Ĝ' => 'G', 'ĝ' => 'g', 572 | 'Ğ' => 'G', 'ğ' => 'g', 573 | 'Ġ' => 'G', 'ġ' => 'g', 574 | 'Ģ' => 'G', 'ģ' => 'g', 575 | 'Ĥ' => 'H', 'ĥ' => 'h', 576 | 'Ħ' => 'H', 'ħ' => 'h', 577 | 'Ĩ' => 'I', 'ĩ' => 'i', 578 | 'Ī' => 'I', 'ī' => 'i', 579 | 'Ĭ' => 'I', 'ĭ' => 'i', 580 | 'Į' => 'I', 'į' => 'i', 581 | 'İ' => 'I', 'ı' => 'i', 582 | 'IJ' => 'IJ', 'ij' => 'ij', 583 | 'Ĵ' => 'J', 'ĵ' => 'j', 584 | 'Ķ' => 'K', 'ķ' => 'k', 585 | 'ĸ' => 'k', 'Ĺ' => 'L', 586 | 'ĺ' => 'l', 'Ļ' => 'L', 587 | 'ļ' => 'l', 'Ľ' => 'L', 588 | 'ľ' => 'l', 'Ŀ' => 'L', 589 | 'ŀ' => 'l', 'Ł' => 'L', 590 | 'ł' => 'l', 'Ń' => 'N', 591 | 'ń' => 'n', 'Ņ' => 'N', 592 | 'ņ' => 'n', 'Ň' => 'N', 593 | 'ň' => 'n', 'ʼn' => 'n', 594 | 'Ŋ' => 'N', 'ŋ' => 'n', 595 | 'Ō' => 'O', 'ō' => 'o', 596 | 'Ŏ' => 'O', 'ŏ' => 'o', 597 | 'Ő' => 'O', 'ő' => 'o', 598 | 'Œ' => 'OE', 'œ' => 'oe', 599 | 'Ŕ' => 'R', 'ŕ' => 'r', 600 | 'Ŗ' => 'R', 'ŗ' => 'r', 601 | 'Ř' => 'R', 'ř' => 'r', 602 | 'Ś' => 'S', 'ś' => 's', 603 | 'Ŝ' => 'S', 'ŝ' => 's', 604 | 'Ş' => 'S', 'ş' => 's', 605 | 'Š' => 'S', 'š' => 's', 606 | 'Ţ' => 'T', 'ţ' => 't', 607 | 'Ť' => 'T', 'ť' => 't', 608 | 'Ŧ' => 'T', 'ŧ' => 't', 609 | 'Ũ' => 'U', 'ũ' => 'u', 610 | 'Ū' => 'U', 'ū' => 'u', 611 | 'Ŭ' => 'U', 'ŭ' => 'u', 612 | 'Ů' => 'U', 'ů' => 'u', 613 | 'Ű' => 'U', 'ű' => 'u', 614 | 'Ų' => 'U', 'ų' => 'u', 615 | 'Ŵ' => 'W', 'ŵ' => 'w', 616 | 'Ŷ' => 'Y', 'ŷ' => 'y', 617 | 'Ÿ' => 'Y', 'Ź' => 'Z', 618 | 'ź' => 'z', 'Ż' => 'Z', 619 | 'ż' => 'z', 'Ž' => 'Z', 620 | 'ž' => 'z', 'ſ' => 's', 621 | // Decompositions for Latin Extended-B 622 | 'Ș' => 'S', 'ș' => 's', 623 | 'Ț' => 'T', 'ț' => 't', 624 | // Vowels with diacritic (Vietnamese) 625 | // unmarked 626 | 'Ơ' => 'O', 'ơ' => 'o', 627 | 'Ư' => 'U', 'ư' => 'u', 628 | // grave accent 629 | 'Ầ' => 'A', 'ầ' => 'a', 630 | 'Ằ' => 'A', 'ằ' => 'a', 631 | 'Ề' => 'E', 'ề' => 'e', 632 | 'Ồ' => 'O', 'ồ' => 'o', 633 | 'Ờ' => 'O', 'ờ' => 'o', 634 | 'Ừ' => 'U', 'ừ' => 'u', 635 | 'Ỳ' => 'Y', 'ỳ' => 'y', 636 | // hook 637 | 'Ả' => 'A', 'ả' => 'a', 638 | 'Ẩ' => 'A', 'ẩ' => 'a', 639 | 'Ẳ' => 'A', 'ẳ' => 'a', 640 | 'Ẻ' => 'E', 'ẻ' => 'e', 641 | 'Ể' => 'E', 'ể' => 'e', 642 | 'Ỉ' => 'I', 'ỉ' => 'i', 643 | 'Ỏ' => 'O', 'ỏ' => 'o', 644 | 'Ổ' => 'O', 'ổ' => 'o', 645 | 'Ở' => 'O', 'ở' => 'o', 646 | 'Ủ' => 'U', 'ủ' => 'u', 647 | 'Ử' => 'U', 'ử' => 'u', 648 | 'Ỷ' => 'Y', 'ỷ' => 'y', 649 | // tilde 650 | 'Ẫ' => 'A', 'ẫ' => 'a', 651 | 'Ẵ' => 'A', 'ẵ' => 'a', 652 | 'Ẽ' => 'E', 'ẽ' => 'e', 653 | 'Ễ' => 'E', 'ễ' => 'e', 654 | 'Ỗ' => 'O', 'ỗ' => 'o', 655 | 'Ỡ' => 'O', 'ỡ' => 'o', 656 | 'Ữ' => 'U', 'ữ' => 'u', 657 | 'Ỹ' => 'Y', 'ỹ' => 'y', 658 | // acute accent 659 | 'Ấ' => 'A', 'ấ' => 'a', 660 | 'Ắ' => 'A', 'ắ' => 'a', 661 | 'Ế' => 'E', 'ế' => 'e', 662 | 'Ố' => 'O', 'ố' => 'o', 663 | 'Ớ' => 'O', 'ớ' => 'o', 664 | 'Ứ' => 'U', 'ứ' => 'u', 665 | // dot below 666 | 'Ạ' => 'A', 'ạ' => 'a', 667 | 'Ậ' => 'A', 'ậ' => 'a', 668 | 'Ặ' => 'A', 'ặ' => 'a', 669 | 'Ẹ' => 'E', 'ẹ' => 'e', 670 | 'Ệ' => 'E', 'ệ' => 'e', 671 | 'Ị' => 'I', 'ị' => 'i', 672 | 'Ọ' => 'O', 'ọ' => 'o', 673 | 'Ộ' => 'O', 'ộ' => 'o', 674 | 'Ợ' => 'O', 'ợ' => 'o', 675 | 'Ụ' => 'U', 'ụ' => 'u', 676 | 'Ự' => 'U', 'ự' => 'u', 677 | 'Ỵ' => 'Y', 'ỵ' => 'y', 678 | // Vowels with diacritic (Chinese, Hanyu Pinyin) 679 | 'ɑ' => 'a', 680 | // macron 681 | 'Ǖ' => 'U', 'ǖ' => 'u', 682 | // acute accent 683 | 'Ǘ' => 'U', 'ǘ' => 'u', 684 | // caron 685 | 'Ǎ' => 'A', 'ǎ' => 'a', 686 | 'Ǐ' => 'I', 'ǐ' => 'i', 687 | 'Ǒ' => 'O', 'ǒ' => 'o', 688 | 'Ǔ' => 'U', 'ǔ' => 'u', 689 | 'Ǚ' => 'U', 'ǚ' => 'u', 690 | // grave accent 691 | 'Ǜ' => 'U', 'ǜ' => 'u', 692 | // spaces 693 | ' ' => ' ', ' ' => ' ', 694 | ]; 695 | 696 | $str = strtr($str, $chars); 697 | 698 | return $str; 699 | } 700 | 701 | /** 702 | * Truncated to the limit of chars allowed by number of SMS. It will detect 703 | * the encoding an multipart limits to apply the truncate. 704 | * 705 | * @param string $str Message text 706 | * @param int $limitSms Number of SMS allowed 707 | * 708 | * @return string Truncated message 709 | */ 710 | public function truncate($str, $limitSms) 711 | { 712 | return $this->doTruncate($str, $limitSms, false); 713 | } 714 | 715 | /** 716 | * Truncated to the limit of chars allowed by number of SMS. It will detect 717 | * the encoding an multipart limits to apply the truncate. 718 | * Supports language shift tables characters. 719 | * 720 | * @param string $str Message text 721 | * @param int $limitSms Number of SMS allowed 722 | * 723 | * @return string Truncated message 724 | */ 725 | public function truncateWithShiftTables($str, $limitSms) 726 | { 727 | return $this->doTruncate($str, $limitSms, true); 728 | } 729 | 730 | /** 731 | * @return string Truncated message 732 | */ 733 | private function doTruncate($str, $limitSms, $supportShiftTables) 734 | { 735 | $count = $supportShiftTables 736 | ? $this->countWithShiftTables($str) 737 | : $this->count($str); 738 | 739 | if ($count->messages <= $limitSms) { 740 | return $str; 741 | } 742 | 743 | if ($count->encoding === 'UTF16') { 744 | $limit = self::UTF16_LEN; 745 | 746 | if ($limitSms > 2) { 747 | $limit = self::UTF16_LEN_MULTIPART; 748 | } 749 | } else { 750 | $limit = self::GSM_7BIT_LEN; 751 | 752 | if ($limitSms > 2) { 753 | $limit = self::GSM_7BIT_LEN_MULTIPART; 754 | } 755 | } 756 | 757 | do { 758 | $str = mb_substr($str, 0, $limit * $limitSms); 759 | $count = $supportShiftTables 760 | ? $this->countWithShiftTables($str) 761 | : $this->count($str); 762 | 763 | $limit = $limit - 1; 764 | } while ($count->messages > $limitSms); 765 | 766 | return $str; 767 | } 768 | } 769 | -------------------------------------------------------------------------------- /Tests/SMSCounterTest.php: -------------------------------------------------------------------------------- 1 | count($text); 16 | 17 | $expected = new \stdClass(); 18 | $expected->encoding = SMSCounter::GSM_7BIT; 19 | $expected->length = 10; 20 | $expected->per_message = 160; 21 | $expected->remaining = 150; 22 | $expected->messages = 1; 23 | 24 | $this->assertEquals($expected, $count); 25 | } 26 | 27 | public function testGSM_TR() 28 | { 29 | $text = 'a GSM TR ç Text'; 30 | 31 | $smsCounter = new SMSCounter(); 32 | $count = $smsCounter->countWithShiftTables($text); 33 | 34 | $expected = new \stdClass(); 35 | $expected->encoding = SMSCounter::GSM_7BIT_EX; 36 | $expected->length = 16; 37 | $expected->per_message = 160; 38 | $expected->remaining = 144; 39 | $expected->messages = 1; 40 | 41 | $this->assertEquals($expected, $count); 42 | } 43 | 44 | public function testGSM_ES() 45 | { 46 | $text = 'a GSM ES Ú Text'; 47 | 48 | $smsCounter = new SMSCounter(); 49 | $count = $smsCounter->countWithShiftTables($text); 50 | 51 | $expected = new \stdClass(); 52 | $expected->encoding = SMSCounter::GSM_7BIT_EX; 53 | $expected->length = 16; 54 | $expected->per_message = 160; 55 | $expected->remaining = 144; 56 | $expected->messages = 1; 57 | 58 | $this->assertEquals($expected, $count); 59 | } 60 | 61 | public function testGSM_PT() 62 | { 63 | $text = 'a GSM PT à Text'; 64 | 65 | $smsCounter = new SMSCounter(); 66 | $count = $smsCounter->countWithShiftTables($text); 67 | 68 | $expected = new \stdClass(); 69 | $expected->encoding = SMSCounter::GSM_7BIT_EX; 70 | $expected->length = 16; 71 | $expected->per_message = 160; 72 | $expected->remaining = 144; 73 | $expected->messages = 1; 74 | 75 | $this->assertEquals($expected, $count); 76 | } 77 | 78 | public function testGSMSymbols() 79 | { 80 | $text = 'a GSM +Text'; 81 | $smsCounter = new SMSCounter(); 82 | $count = $smsCounter->count($text); 83 | 84 | $expected = new \stdClass(); 85 | $expected->encoding = SMSCounter::GSM_7BIT; 86 | $expected->length = 11; 87 | $expected->per_message = 160; 88 | $expected->remaining = 149; 89 | $expected->messages = 1; 90 | 91 | $this->assertEquals($expected, $count); 92 | } 93 | 94 | public function testGSMMultiPage() 95 | { 96 | $text = '1234567890'; 97 | $text .= '1234567890'; 98 | $text .= '1234567890'; 99 | $text .= '1234567890'; 100 | $text .= '1234567890'; 101 | $text .= '1234567890'; 102 | $text .= '1234567890'; 103 | $text .= '1234567890'; 104 | $text .= '1234567890'; 105 | $text .= '1234567890'; 106 | $text .= '1234567890'; 107 | $text .= '1234567890'; 108 | $text .= '1234567890'; 109 | $text .= '1234567890'; 110 | $text .= '1234567890'; 111 | $text .= '1234567890'; 112 | $text .= '1234567890'; 113 | 114 | $smsCounter = new SMSCounter(); 115 | $count = $smsCounter->count($text); 116 | 117 | $expected = new \stdClass(); 118 | $expected->encoding = SMSCounter::GSM_7BIT; 119 | $expected->length = 170; 120 | $expected->per_message = 153; 121 | $expected->remaining = 153 * 2 - 170; 122 | $expected->messages = 2; 123 | 124 | $this->assertEquals($expected, $count); 125 | } 126 | 127 | public function testUnicodeMultiPage() 128 | { 129 | $text = '`'; 130 | $text .= '1234567890'; 131 | $text .= '1234567890'; 132 | $text .= '1234567890'; 133 | $text .= '1234567890'; 134 | $text .= '1234567890'; 135 | $text .= '1234567890'; 136 | $text .= '1234567890'; 137 | 138 | $smsCounter = new SMSCounter(); 139 | $count = $smsCounter->count($text); 140 | 141 | $expected = new \stdClass(); 142 | $expected->encoding = SMSCounter::UTF16; 143 | $expected->length = 71; 144 | $expected->per_message = 67; 145 | $expected->remaining = 67 * 2 - 71; 146 | $expected->messages = 2; 147 | 148 | $this->assertEquals($expected, $count); 149 | } 150 | 151 | public function testCarriageReturn() 152 | { 153 | $text = "\n\r"; 154 | $smsCounter = new SMSCounter(); 155 | $count = $smsCounter->count($text); 156 | 157 | $expected = new \stdClass(); 158 | $expected->encoding = SMSCounter::GSM_7BIT; 159 | $expected->length = 2; 160 | $expected->per_message = 160; 161 | $expected->remaining = 158; 162 | $expected->messages = 1; 163 | 164 | $this->assertEquals($expected, $count); 165 | } 166 | 167 | public function testUnicodeEncodingAndLength() 168 | { 169 | $smsCounter = new SMSCounter(); 170 | 171 | // 1 byte UTF8 172 | $this->assertEquals([33], $smsCounter->utf8ToUnicode('!')); // U+0021 => 0x21 173 | $this->assertEquals(1, $smsCounter->count('!')->length); 174 | 175 | if (version_compare(PHP_VERSION, '7.0.0') >= 0) { 176 | $this->assertEquals([127], $smsCounter->utf8ToUnicode("\u{007F}")); // U+007F => 0x7F 177 | $this->assertEquals(1, $smsCounter->count("\u{007F}")->length); 178 | } 179 | 180 | // 2 bytes UTF8 181 | if (version_compare(PHP_VERSION, '7.0.0') >= 0) { 182 | $this->assertEquals([128], $smsCounter->utf8ToUnicode("\u{0080}")); // U+0080 => 0xC2 0x80 183 | $this->assertEquals(1, $smsCounter->count("\u{0080}")->length); 184 | } 185 | 186 | $this->assertEquals([2047], $smsCounter->utf8ToUnicode('߿')); // U+07FF => 0xDF 0xBF 187 | $this->assertEquals(1, $smsCounter->count('߿')->length); 188 | 189 | // 3 bytes UTF8 190 | $this->assertEquals([2048], $smsCounter->utf8ToUnicode('ࠀ')); // U+0800 => 0xE0 0xA0 0x80 191 | $this->assertEquals(1, $smsCounter->count('ࠀ')->length); 192 | 193 | $this->assertEquals([65535], $smsCounter->utf8ToUnicode('￿')); // U+FFFF => 0xEF 0xBF 0xBF 194 | $this->assertEquals(1, $smsCounter->count('￿')->length); 195 | 196 | // 4 bytes UTF8 197 | $this->assertEquals([65536], $smsCounter->utf8ToUnicode('𐀀')); // U+10000 => 0xF0 0x90 0x80 0x80 198 | $this->assertEquals(2, $smsCounter->count('𐀀')->length); 199 | 200 | $this->assertEquals([983295], $smsCounter->utf8ToUnicode('󰃿')); // U+F00FF => 0xF3 0xB0 0x83 0xBF 201 | $this->assertEquals(2, $smsCounter->count('󰃿')->length); 202 | } 203 | 204 | public function testUnicode() 205 | { 206 | $text = '`'; 207 | $smsCounter = new SMSCounter(); 208 | $count = $smsCounter->count($text); 209 | 210 | $expected = new \stdClass(); 211 | $expected->encoding = SMSCounter::UTF16; 212 | $expected->length = 1; 213 | $expected->per_message = 70; 214 | $expected->remaining = 69; 215 | $expected->messages = 1; 216 | 217 | $this->assertEquals($expected, $count); 218 | } 219 | 220 | public function testUnicodeEmojiSingleMessage() 221 | { 222 | $text = '😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎'; 223 | $smsCounter = new SMSCounter(); 224 | $count = $smsCounter->count($text); 225 | 226 | $expected = new \stdClass(); 227 | $expected->encoding = SMSCounter::UTF16; 228 | $expected->length = 70; 229 | $expected->per_message = 70; 230 | $expected->remaining = 0; 231 | $expected->messages = 1; 232 | 233 | $this->assertEquals($expected, $count); 234 | } 235 | 236 | public function testUnicodeEmojiMultiPartMessage() 237 | { 238 | // A char is lost at the end of first part 239 | $text = '😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎'; 240 | $smsCounter = new SMSCounter(); 241 | $count = $smsCounter->count($text); 242 | 243 | $expected = new \stdClass(); 244 | $expected->encoding = SMSCounter::UTF16; 245 | $expected->length = 72; 246 | $expected->per_message = 67; 247 | $expected->remaining = 61; 248 | $expected->messages = 2; 249 | 250 | $this->assertEquals($expected, $count); 251 | 252 | // First part is completed with a dash char (-) 253 | $text = '😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎-😎😎😎'; 254 | $smsCounter = new SMSCounter(); 255 | $count = $smsCounter->count($text); 256 | 257 | $expected = new \stdClass(); 258 | $expected->encoding = SMSCounter::UTF16; 259 | $expected->length = 73; 260 | $expected->per_message = 67; 261 | $expected->remaining = 61; 262 | $expected->messages = 2; 263 | 264 | $this->assertEquals($expected, $count); 265 | 266 | // Both parts are completed with dash chars (-) 267 | $text = '😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎-😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎-'; 268 | $smsCounter = new SMSCounter(); 269 | $count = $smsCounter->count($text); 270 | 271 | $expected = new \stdClass(); 272 | $expected->encoding = SMSCounter::UTF16; 273 | $expected->length = 134; 274 | $expected->per_message = 67; 275 | $expected->remaining = 0; 276 | $expected->messages = 2; 277 | 278 | $this->assertEquals($expected, $count); 279 | } 280 | 281 | public function testRemoveNonGSMChars() 282 | { 283 | $text = 'áno-unicode-remaining` ñ'; 284 | $expectedTExt = 'no-unicode-remaining ñ'; 285 | 286 | $smsCounter = new SMSCounter(); 287 | $output = $smsCounter->removeNonGsmChars($text); 288 | 289 | $this->assertEquals($expectedTExt, $output); 290 | } 291 | 292 | /** 293 | * @dataProvider dataProvider 294 | */ 295 | public function testSanitizeToGSM($text, $expectedText) 296 | { 297 | $smsCounter = new SMSCounter(); 298 | $output = $smsCounter->sanitizeToGSM($text); 299 | 300 | $this->assertEquals($expectedText, $output); 301 | } 302 | 303 | public function testTruncate1SmsGSM7() 304 | { 305 | $text = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.'; 306 | $expectedTExt = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient '; 307 | 308 | $smsCounter = new SMSCounter(); 309 | $output = $smsCounter->truncate($text, 1); 310 | 311 | $this->assertEquals($expectedTExt, $output); 312 | } 313 | 314 | public function testTruncate1SmsGSM7ShiftTable() 315 | { 316 | $text = 'ÚLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.'; 317 | $expectedTExt = 'ÚLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturien'; 318 | 319 | $smsCounter = new SMSCounter(); 320 | $output = $smsCounter->truncateWithShiftTables($text, 1); 321 | 322 | $this->assertEquals($expectedTExt, $output); 323 | } 324 | 325 | public function testTruncate2SmsGSM7() 326 | { 327 | $text = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient'; 328 | $expectedTExt = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis d'; 329 | 330 | $smsCounter = new SMSCounter(); 331 | $output = $smsCounter->truncate($text, 2); 332 | 333 | $this->assertEquals($expectedTExt, $output); 334 | } 335 | 336 | public function testTruncate2SmsGSM7ShiftTable() 337 | { 338 | $text = 'çLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturie'; 339 | $expectedTExt = 'çLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magni'; 340 | 341 | $smsCounter = new SMSCounter(); 342 | $output = $smsCounter->truncateWithShiftTables($text, 2); 343 | 344 | $this->assertEquals($expectedTExt, $output); 345 | } 346 | 347 | public function testTruncate1SmsUnicode() 348 | { 349 | $text = 'Snowman shows off! ☃ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa'; 350 | $expectedTExt = 'Snowman shows off! ☃ Lorem ipsum dolor sit amet, consectetuer adipisci'; 351 | 352 | $smsCounter = new SMSCounter(); 353 | $output = $smsCounter->truncate($text, 1); 354 | 355 | $this->assertEquals($expectedTExt, $output); 356 | } 357 | 358 | public function testTruncate2SmsUnicode() 359 | { 360 | $text = 'Snowman shows off! ☃ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula e ☃get dolor. Aenean massa Lorem ipsum dolor sit amet, consectetuer adip eg'; 361 | $expectedTExt = 'Snowman shows off! ☃ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula e ☃get dolor. Aenean massa Lorem '; 362 | 363 | $smsCounter = new SMSCounter(); 364 | $output = $smsCounter->truncate($text, 2); 365 | 366 | $this->assertEquals($expectedTExt, $output); 367 | } 368 | 369 | public function dataProvider() 370 | { 371 | return [ 372 | ['@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞ^{}\[~]|€ÆæßÉ!\"#¤%&\'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà', '@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞ^{}\[~]|€ÆæßÉ!\"#¤%&\'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà'], 373 | ['Lhg jjjo fx 382 64237 12299 qmecb. Ç éæ+! -[Å*_ (¡)| ?Λ^ ~£;ΩΠ¿ ÑΔ #ΓüΘ¥ñ,É øΨì] ò= Ü. @å<: ö%\'Ƥ"Ö> اΦ{ }/&Ä ùß\€ èà Ξ$äΣ.', 'Lhg jjjo fx 382 64237 12299 qmecb. Ç éæ+! -[Å*_ (¡)| ?Λ^ ~£;ΩΠ¿ ÑΔ #ΓüΘ¥ñ,É øΨì] ò= Ü. @å<: ö%\'Ƥ"Ö> اΦ{ }/&Ä ùß\€ èà Ξ$äΣ.'], 374 | ['dadáó', 'dadao'], 375 | ["\xc2\xa0|\xe2\x80\x87|\xef\xbb\xbf", ' | |'], 376 | ]; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "instasent/sms-counter-php", 3 | "type": "library", 4 | "description": "SMS Counter PHP Class Library which detects encoding of an SMS message text, counts the characters as per the encoding and gives page limit information.", 5 | "keywords": ["SMS", "counter", "GSM", "Unicode", "Mobile"], 6 | "homepage": "http://github.com/instasent/sms-counter-php", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Luis Hdez", 11 | "email": "luis.munoz.hdez@gmail.com", 12 | "homepage": "http://www.instasent.com" 13 | }, 14 | { 15 | "name": "Marcos Gómez", 16 | "email": "marcos@instasent.com", 17 | "homepage": "http://www.instasent.com" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=5.6.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Instasent\\SMSCounter\\": "" 26 | } 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^5.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ./Tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./ 22 | 23 | ./Resources 24 | ./Tests 25 | ./vendor 26 | 27 | 28 | 29 | 30 | --------------------------------------------------------------------------------