1<?php 2 3namespace Emojione; 4 5/** 6 * Client for Emojione 7 */ 8 9class Client implements ClientInterface 10{ 11 public $ascii = false; // convert ascii smileys? 12 public $shortcodes = true; // convert shortcodes? 13 public $unicodeAlt = true; // use the unicode char as the alt attribute (makes copy and pasting the resulting text better) 14 public $imageType = 'png'; // or svg 15 public $cacheBustParam = '?v=2.2.7'; 16 public $sprites = false; 17 public $imagePathPNG = 'https://cdn.jsdelivr.net/emojione/assets/png/'; 18 public $imagePathSVG = 'https://cdn.jsdelivr.net/emojione/assets/svg/'; 19 public $imagePathSVGSprites = './../../assets/sprites/emojione.sprites.svg'; 20 public $imageTitleTag = true; 21 public $unicode_replaceWith = false; 22 public $ignoredRegexp = '<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>'; 23 public $unicodeRegexp = '([*#0-9](?>\\xEF\\xB8\\x8F)?\\xE2\\x83\\xA3|\\xC2[\\xA9\\xAE]|\\xE2..(\\xF0\\x9F\\x8F[\\xBB-\\xBF])?(?>\\xEF\\xB8\\x8F)?|\\xE3(?>\\x80[\\xB0\\xBD]|\\x8A[\\x97\\x99])(?>\\xEF\\xB8\\x8F)?|\\xF0\\x9F(?>[\\x80-\\x86].(?>\\xEF\\xB8\\x8F)?|\\x87.\\xF0\\x9F\\x87.|..((\\xE2\\x80\\x8D\\xF0\\x9F\\x97\\xA8)|(\\xF0\\x9F\\x8F[\\xBB-\\xBF])|(\\xE2\\x80\\x8D\\xF0\\x9F\\x91[\\xA6-\\xA9]){2,3}|(\\xE2\\x80\\x8D\\xE2\\x9D\\xA4\\xEF\\xB8\\x8F\\xE2\\x80\\x8D\\xF0\\x9F..(\\xE2\\x80\\x8D\\xF0\\x9F\\x91[\\xA6-\\xA9])?))?))'; 24 25 public $shortcodeRegexp = ':([-+\\w]+):'; 26 27 protected $ruleset = null; 28 29 public function __construct(RulesetInterface $ruleset = null) 30 { 31 if ( ! is_null($ruleset) ) 32 { 33 $this->ruleset = $ruleset; 34 } 35 } 36 37 // ########################################## 38 // ######## core methods 39 // ########################################## 40 41 /** 42 * First pass changes unicode characters into emoji markup. 43 * Second pass changes any shortnames into emoji markup. 44 * 45 * @param string $string The input string. 46 * @return string String with appropriate html for rendering emoji. 47 */ 48 public function toImage($string) 49 { 50 $string = $this->unicodeToImage($string); 51 $string = $this->shortnameToImage($string); 52 return $string; 53 } 54 55 /** 56 * Uses toShort to transform all unicode into a standard shortname 57 * then transforms the shortname into unicode. 58 * This is done for standardization when converting several unicode types. 59 * 60 * @param string $string The input string. 61 * @return string String with standardized unicode. 62 */ 63 public function unifyUnicode($string) 64 { 65 $string = $this->toShort($string); 66 $string = $this->shortnameToUnicode($string); 67 return $string; 68 } 69 70 /** 71 * This will output unicode from shortname input. 72 * If Client/$ascii is true it will also output unicode from ascii. 73 * This is useful for sending emojis back to mobile devices. 74 * 75 * @param string $string The input string. 76 * @return string String with unicode replacements. 77 */ 78 public function shortnameToUnicode($string) 79 { 80 if ($this->shortcodes) 81 { 82 $string = preg_replace_callback('/'.$this->ignoredRegexp.'|('.$this->shortcodeRegexp.')/Si', array($this, 'shortnameToUnicodeCallback'), $string); 83 } 84 85 if ($this->ascii) 86 { 87 $ruleset = $this->getRuleset(); 88 $asciiRegexp = $ruleset->getAsciiRegexp(); 89 90 $string = preg_replace_callback('/'.$this->ignoredRegexp.'|((\\s|^)'.$asciiRegexp.'(?=\\s|$|[!,.?]))/S', array($this, 'asciiToUnicodeCallback'), $string); 91 } 92 93 return $string; 94 } 95 96 /** 97 * This will replace shortnames with their ascii equivalent. 98 * ex. :wink: --> ;^) 99 * This is useful for systems that don't support unicode or images. 100 * 101 * @param string $string The input string. 102 * @return string String with ascii replacements. 103 */ 104 public function shortnameToAscii($string) 105 { 106 $string = preg_replace_callback('/'.$this->ignoredRegexp.'|('.$this->shortcodeRegexp.')/Si', array($this, 'shortnameToAsciiCallback'), $string); 107 108 return $string; 109 } 110 111 /** 112 * This will replace ascii with their shortname equivalent, it bases on reversed ::shortnameToAsciiCallback 113 * ex. :) --> :slight_smile: 114 * This is useful for systems that don't ascii emoji. 115 * 116 * @param string $string The input ascii. 117 * @return string String with shortname replacements. 118 */ 119 public function asciiToShortname($string) 120 { 121 $ruleset = $this->getRuleset(); 122 $asciiRegexp = $ruleset->getAsciiRegexp(); 123 return preg_replace_callback('/'.$this->ignoredRegexp.'|((\\s|^)'.$asciiRegexp.'(?=\\s|$|[!,.?]))/S', array($this, 'asciiToShortnameCallback'), $string); 124 } 125 126 /** 127 * This will output image markup (for png or svg) from shortname input. 128 * 129 * @param string $string The input string. 130 * @return string String with appropriate html for rendering emoji. 131 */ 132 public function shortnameToImage($string) 133 { 134 if ($this->shortcodes) 135 { 136 $string = preg_replace_callback('/'.$this->ignoredRegexp.'|('.$this->shortcodeRegexp.')/Si', array($this, 'shortnameToImageCallback'), $string); 137 } 138 139 if ($this->ascii) 140 { 141 $ruleset = $this->getRuleset(); 142 $asciiRegexp = $ruleset->getAsciiRegexp(); 143 144 $string = preg_replace_callback('/'.$this->ignoredRegexp.'|((\\s|^)'.$asciiRegexp.'(?=\\s|$|[!,.?]))/S', array($this, 'asciiToImageCallback'), $string); 145 } 146 147 return $string; 148 } 149 150 /** 151 * This will return the shortname from unicode input. 152 * 153 * @param string $string The input string. 154 * @return string shortname 155 */ 156 public function toShort($string) 157 { 158 return preg_replace_callback('/'.$this->ignoredRegexp.'|'.$this->unicodeRegexp.'/S', array($this, 'toShortCallback'), $string); 159 } 160 161 /** 162 * This will output image markup (for png or svg) from unicode input. 163 * 164 * @param string $string The input string. 165 * @return string String with appropriate html for rendering emoji. 166 */ 167 public function unicodeToImage($string) 168 { 169 return preg_replace_callback('/'.$this->ignoredRegexp.'|'.$this->unicodeRegexp.'/S', array($this, 'unicodeToImageCallback'), $string); 170 } 171 172 // ########################################## 173 // ######## preg_replace callbacks 174 // ########################################## 175 176 /** 177 * @param array $m Results of preg_replace_callback(). 178 * @return string Ascii replacement result. 179 */ 180 public function shortnameToAsciiCallback($m) 181 { 182 if ((!is_array($m)) || (!isset($m[1])) || (empty($m[1]))) 183 { 184 return $m[0]; 185 } 186 else 187 { 188 $ruleset = $this->getRuleset(); 189 $shortcode_replace = $ruleset->getShortcodeReplace(); 190 $ascii_replace = $ruleset->getAsciiReplace(); 191 192 $aflipped = array_flip($ascii_replace); 193 194 $shortname = $m[0]; 195 196 if (!isset($shortcode_replace[$shortname])) 197 { 198 return $m[0]; 199 } 200 201 $unicode = $shortcode_replace[$shortname]; 202 203 return isset($aflipped[$unicode]) ? $aflipped[$unicode] : $m[0]; 204 } 205 } 206 207 /** 208 * @param array $m Results of preg_replace_callback(). 209 * @return string Unicode replacement result. 210 */ 211 public function shortnameToUnicodeCallback($m) 212 { 213 if ((!is_array($m)) || (!isset($m[1])) || (empty($m[1]))) { 214 return $m[0]; 215 } 216 else { 217 $ruleset = $this->getRuleset(); 218 $unicode_replace = $ruleset->getUnicodeReplace(); 219 220 221 $shortname = $m[1]; 222 223 if (!array_key_exists($shortname, $unicode_replace)) { 224 return $m[0]; 225 } 226 227 228 $unicode = $unicode_replace[$shortname]; 229 230 return $unicode; 231 } 232 } 233 234 /** 235 * @param array $m Results of preg_replace_callback(). 236 * @return string Image HTML replacement result. 237 */ 238 public function shortnameToImageCallback($m) 239 { 240 if ((!is_array($m)) || (!isset($m[1])) || (empty($m[1]))) { 241 return $m[0]; 242 } 243 else { 244 $ruleset = $this->getRuleset(); 245 $shortcode_replace = $ruleset->getShortcodeReplace(); 246 247 $shortname = $m[1]; 248 249 if (!isset($shortcode_replace[$shortname])) 250 { 251 return $m[0]; 252 } 253 254 255 $unicode = $shortcode_replace[$shortname]; 256 $filename = $unicode; 257 $titleTag = $this->imageTitleTag ? 'title="'.htmlspecialchars($shortname).'"' : ''; 258 259 if ($this->unicodeAlt) 260 { 261 $alt = $this->convert($unicode); 262 } 263 else 264 { 265 $alt = $shortname; 266 } 267 268 if ($this->imageType == 'png') 269 { 270 if ($this->sprites) 271 { 272 return '<span class="emojione emojione-'.$unicode.'" title="'.htmlspecialchars($shortname).'">'.$alt.'</span>'; 273 } 274 else 275 { 276 return '<img class="emojione" alt="'.$alt.'" '.$titleTag.' src="'.$this->imagePathPNG.$filename.'.png'.$this->cacheBustParam.'"/>'; 277 } 278 } 279 280 if ($this->sprites) 281 { 282 return '<svg class="emojione"><description>'.$alt.'</description><use xlink:href="'.$this->imagePathSVGSprites.'#emoji-'.$unicode.'"></use></svg>'; 283 } 284 else 285 { 286 return '<object class="emojione" data="'.$this->imagePathSVG.$filename.'.svg'.$this->cacheBustParam.'" type="image/svg+xml" standby="'.$alt.'">'.$alt.'</object>'; 287 } 288 } 289 } 290 291 /** 292 * @param array $m Results of preg_replace_callback(). 293 * @return string Unicode replacement result. 294 */ 295 public function asciiToUnicodeCallback($m) 296 { 297 if ((!is_array($m)) || (!isset($m[3])) || (empty($m[3]))) 298 { 299 return $m[0]; 300 } 301 else 302 { 303 $ruleset = $this->getRuleset(); 304 $ascii_replace = $ruleset->getAsciiReplace(); 305 306 $shortname = $m[3]; 307 $unicode = $ascii_replace[$shortname]; 308 return $m[2].$this->convert($unicode); 309 } 310 } 311 312 /** 313 * @param array $m Results of preg_replace_callback(). 314 * @return string Shortname replacement result. 315 */ 316 public function asciiToShortnameCallback($m) 317 { 318 if ((!is_array($m)) || (!isset($m[3])) || (empty($m[3]))) 319 { 320 return $m[0]; 321 } 322 else 323 { 324 $ruleset = $this->getRuleset(); 325 $ascii_replace = $ruleset->getAsciiReplace(); 326 327 $shortcode_replace = array_flip(array_reverse($ruleset->getShortcodeReplace())); 328 $shortname = $m[3]; 329 $unicode = $ascii_replace[$shortname]; 330 return $m[2].$shortcode_replace[$unicode]; 331 } 332 } 333 334 /** 335 * @param array $m Results of preg_replace_callback(). 336 * @return string Image HTML replacement result. 337 */ 338 public function asciiToImageCallback($m) 339 { 340 if ((!is_array($m)) || (!isset($m[3])) || (empty($m[3]))) 341 { 342 return $m[0]; 343 } 344 else 345 { 346 $ruleset = $this->getRuleset(); 347 $ascii_replace = $ruleset->getAsciiReplace(); 348 349 $shortname = html_entity_decode($m[3]); 350 $unicode = $ascii_replace[$shortname]; 351 $titleTag = $this->imageTitleTag ? 'title="'.htmlspecialchars($shortname).'"' : ''; 352 353 // unicode char or shortname for the alt tag? (unicode is better for copying and pasting the resulting text) 354 if ($this->unicodeAlt) 355 { 356 $alt = $this->convert($unicode); 357 } 358 else 359 { 360 $alt = htmlspecialchars($shortname); 361 } 362 363 if ($this->imageType == 'png') 364 { 365 if ($this->sprites) 366 { 367 return $m[2].'<span class="emojione emojione-'.$unicode.'" title="'.htmlspecialchars($shortname).'">'.$alt.'</span>'; 368 } 369 else 370 { 371 return $m[2].'<img class="emojione" alt="'.$alt.'" '.$titleTag.' src="'.$this->imagePathPNG.$unicode.'.png'.$this->cacheBustParam.'"/>'; 372 } 373 } 374 375 if ($this->sprites) 376 { 377 return $m[2].'<svg class="emojione"><description>'.$alt.'</description><use xlink:href="'.$this->imagePathSVGSprites.'#emoji-'.$unicode.'"></use></svg>'; 378 } 379 else 380 { 381 return $m[2].'<object class="emojione" data="'.$this->imagePathSVG.$unicode.'.svg'.$this->cacheBustParam.'" type="image/svg+xml" standby="'.$alt.'">'.$alt.'</object>'; 382 } 383 } 384 } 385 386 /** 387 * @param array $m Results of preg_replace_callback(). 388 * @return string shortname result 389 */ 390 public function toShortCallback($m) 391 { 392 if ((!is_array($m)) || (!isset($m[1])) || (empty($m[1]))) 393 { 394 return $m[0]; 395 } 396 else 397 { 398 $ruleset = $this->getRuleset(); 399 $unicode_replace = $ruleset->getUnicodeReplace(); 400 401 $unicode = $m[1]; 402 403 if (!in_array($unicode, $unicode_replace)) 404 { 405 $unicode .= "\xEF\xB8\x8F"; 406 407 if (!in_array($unicode, $unicode_replace)) 408 { 409 $unicode = substr($m[1], 0, 4); 410 411 if (!in_array($unicode, $unicode_replace)) 412 { 413 return $m[0]; 414 } 415 } 416 } 417 418 return array_search($unicode, $unicode_replace); 419 } 420 } 421 422 /** 423 * @param array $m Results of preg_replace_callback(). 424 * @return string Image HTML replacement result. 425 */ 426 public function unicodeToImageCallback($m) 427 { 428 if ((!is_array($m)) || (!isset($m[1])) || (empty($m[1]))) 429 { 430 return $m[0]; 431 } 432 else 433 { 434 $ruleset = $this->getRuleset(); 435 $shortcode_replace = $ruleset->getShortcodeReplace(); 436 $unicode_replace = $ruleset->getUnicodeReplace(); 437 438 $unicode = $m[1]; 439 440 if (!in_array($unicode, $unicode_replace)) 441 { 442 $unicode .= "\xEF\xB8\x8F"; 443 444 if (!in_array($unicode, $unicode_replace)) 445 { 446 $unicode = substr($m[1], 0, 4); 447 448 if (!in_array($unicode, $unicode_replace)) 449 { 450 if ("\xE2\x83\xA3" === substr($m[1], 1, 3)) 451 { 452 $unicode = substr($m[1], 0, 1) . "\xEF\xB8\x8F\xE2\x83\xA3"; 453 454 if (!in_array($unicode, $unicode_replace)) 455 { 456 return $m[0]; 457 } 458 } 459 else 460 { 461 return $m[0]; 462 } 463 } 464 } 465 } 466 467 $shortname = array_search($unicode, $unicode_replace); 468 $filename = $shortcode_replace[$shortname]; 469 $titleTag = $this->imageTitleTag ? 'title="'.htmlspecialchars($shortname).'"' : ''; 470 471 if ($this->unicodeAlt) 472 { 473 $alt = $unicode; 474 } 475 else 476 { 477 $alt = $shortname; 478 } 479 480 if ($this->imageType == 'png') 481 { 482 if ($this->sprites) 483 { 484 return '<span class="emojione emojione-'.$filename.'" title="'.htmlspecialchars($shortname).'">'.$alt.'</span>'; 485 } 486 else 487 { 488 return '<img class="emojione" alt="'.$alt.'" '.$titleTag.' title="'.htmlspecialchars($shortname).'" src="'.$this->imagePathPNG.$filename.'.png'.$this->cacheBustParam.'"/>'; 489 } 490 } 491 492 if ($this->sprites) 493 { 494 return '<svg class="emojione"><description>'.$alt.'</description><use xlink:href="'.$this->imagePathSVGSprites.'#emoji-'.$filename.'"></use></svg>'; 495 } 496 else 497 { 498 return '<object class="emojione" data="'.$this->imagePathSVG.$filename.'.svg'.$this->cacheBustParam.'" type="image/svg+xml" standby="'.$alt.'">'.$alt.'</object>'; 499 } 500 } 501 } 502 503 // ########################################## 504 // ######## helper methods 505 // ########################################## 506 507 /** 508 * Converts from unicode to hexadecimal NCR. 509 * 510 * @param string $unicode unicode character/s 511 * @return string hexadecimal NCR 512 * */ 513 public function convert($unicode) 514 { 515 if (stristr($unicode,'-')) 516 { 517 $pairs = explode('-',$unicode); 518 return '&#x'.implode(';&#x',$pairs).';'; 519 } 520 else 521 { 522 return '&#x'.$unicode.';'; 523 } 524 } 525 526 /** 527 * Get the Ruleset 528 * 529 * @return RulesetInterface The Ruleset 530 */ 531 public function getRuleset() 532 { 533 if ( $this->ruleset === null ) 534 { 535 $this->ruleset = new Ruleset; 536 } 537 538 return $this->ruleset; 539 } 540} 541