1<?php
2/**
3 * EmojiOne extension (Helper Component)
4 *
5 * @license     GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author      Patrick Brown <ptbrown@whoopdedo.org></ptbrown>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12require_once 'emojione/RulesetInterface.php';
13require_once 'emojione/ClientInterface.php';
14require_once 'emojione/Client.php';
15require_once 'emojione/Ruleset.php';
16
17class dwplugin_emoji_ruleset extends Emojione\Ruleset {
18    protected static $smileys = array(
19        '8-O' => '1F62F',
20        '8-o' => '1F62F',
21        ':-\\' => '1F615',
22        ':-?' => '1F616',
23        ':-|' => '1F601',
24        '^_^' => '1F604',
25        ':?:' => '2753',
26        ':!:' => '26A0',
27    );
28
29    public function __construct() {
30        $this->ascii_replace = array_merge($this->ascii_replace, static::$smileys);
31        $smileys = array_keys($this->ascii_replace);
32        $this->asciiRegexp = '(?:'.join('|',array_map('preg_quote_cb', $smileys)).')';
33        /* Inserts smileys into the shortcode list so I can use a single callback to handle both. */
34        $this->shortcode_replace = array_merge($this->shortcode_replace, $this->ascii_replace);
35    }
36
37    public function isShortCode($match) {
38        return isset($this->shortcode_replace[$match]);
39    }
40
41    public function replaceShortCode($match) {
42        return $this->shortcode_replace[$match];
43    }
44}
45
46class syntax_plugin_emoji extends DokuWiki_Syntax_Plugin {
47
48    /**
49     * Match emoji code points:
50     *   - Most characters followed by variant selector 16
51     *   - No characters followed by variant selector 15
52     *   - Characters above U+1F000
53     *   - Numbers with combining keycap U+20E3
54     *   - Miscellaneous Technical
55     *   - Control Pictures
56     *   - Miscellaneous Symbols and Dingbats
57     *   - Miscellaneous Symbols and Arrows
58     */
59    public $unicodeRegexp = '(?:[*#0-9](?>\\xEF\\xB8\\x8F)?\\xE2\\x83\\xA3(?!\\xEF\\xB8\\x8E)|[*#0-9]\\xEF\\xB8\\x8F|\\xC2[\\xA9\\xAE]\\xEF\\xB8\\x8F|\\xE2..(?>\\xF0\\x9F\\x8F[\\xBB-\\xBF])?\\xEF\\xB8\\x8F|\\xE2[\\x8C-\\x90\\x98-\\x9E\\xAC-\\xAF].(?>\\xF0\\x9F\\x8F[\\xBB-\\xBF])?(?!\\xEF\\xB8\\x8E)|\\xE3(?>\\x80[\\xB0\\xBD]|\\x8A[\\x97\\x99])\\xEF\\xB8\\x8F|\\xF0\\x9F(?>\\x87.\\xF0\\x9F\\x87.|..(?>\\xEF\\xB8\\x8F)?)(?!\\xEF\\xB8\\x8E))';
60
61    protected $client;
62    protected $ruleset;
63
64    public function __construct() {
65        $this->ruleset = new dwplugin_emoji_ruleset();
66
67        $this->client = new Emojione\Client($this->ruleset);
68        $this->client->unicodeAlt = true;
69        $this->client->sprites = false;
70        $this->client->imageType = 'png';
71        $assetsrc = DOKU_BASE.'lib/plugins/emoji/';
72        switch($this->getConf('assetsrc')) {
73            case 'cdn':
74                $assetsrc = 'https://cdn.jsdelivr.net/emojione/';
75                break;
76            case 'external':
77                /* really should be called "asseturl", oops. Too late now */
78                $asseturi = $this->getConf('asseturi');
79                if($asseturi)
80                    $assetsrc = $asseturi;
81                break;
82        }
83        $this->client->imagePathPNG = $assetsrc.'assets/png/';
84    }
85
86    public function getType() {
87        return 'substition';
88    }
89
90    public function getSort() {
91        return 229;
92    }
93
94    public function connectTo($mode) {
95        $this->Lexer->addSpecialPattern($this->getUnicodeRegexp(), $mode, 'plugin_emoji');
96        $this->Lexer->addSpecialPattern('(?<=\W|^)'.$this->getShortnameRegexp().'(?=\W|$)', $mode, 'plugin_emoji');
97        $this->Lexer->addSpecialPattern('(?<=\W|^)'.$this->getSmileyRegexp().'(?=\W|$)', $mode, 'plugin_emoji');
98    }
99
100    public function handle($match, $state, $pos, Doku_Handler $handler) {
101        /* Clean up variant selector, I don't trust the library to do this. */
102        $match = str_replace("\xEF\xB8\x8F", "", $match);
103        $unicode = $this->toUnicode($match);
104        return array($match,$unicode);
105    }
106
107    public function render($mode, Doku_Renderer $renderer, $data) {
108        list($match,$unicode) = $data;
109        switch($mode) {
110            case 'xhtml':
111                if($this->ruleset->isShortCode($match))
112                    $renderer->doc .= $this->shortnameToImage($match);
113                else
114                    $renderer->doc .= $this->unicodeToImage($unicode);
115                break;
116            case 'odt':
117                if($this->ruleset->isShortCode($match))
118                    $link = $this->shortnameToImage($match);
119                else
120                    $link = $this->unicodeToImage($unicode);
121
122                if (preg_match('#[^/]\w*\.png#', $link, $matches) == 1) {
123                    $path = DOKU_PLUGIN.'emoji/assets/png/'.$matches[0];
124                    list($width, $height)  = $renderer->_odtGetImageSize ($path, '20', '20');
125                    $renderer->_odtAddImage($path, $width.'cm', $height.'cm');
126                }
127                break;
128            default:
129                /* Adds the text variant selector */
130                $renderer->cdata($unicode . "\xEF\xB8\x8E");
131                break;
132        }
133        return true;
134    }
135
136    private function getUnicodeRegexp() {
137        return $this->unicodeRegexp;
138    }
139
140    private function getShortnameRegexp() {
141        return preg_replace('/\((?!\?)/', '(?:', $this->client->shortcodeRegexp);
142    }
143
144    private function getSmileyRegexp() {
145        return $this->ruleset->getAsciiRegexp();
146    }
147
148    private function toUnicode($shortname) {
149        if(isset($this->smileys[$shortname])) {
150            $unicode = $this->smileys[$shortname];
151        }
152        elseif($this->ruleset->isShortCode($shortname)) {
153            $unicode = $this->ruleset->replaceShortCode($shortname);
154        }
155        else {
156            return $shortname;
157        }
158        if(stristr($unicode,'-')) {
159            $pairs = explode('-',$unicode);
160        }
161        else {
162            $pairs = array($unicode);
163        }
164        return unicode_to_utf8(array_map('hexdec', $pairs));
165    }
166
167    private function toShortname($unicode) {
168        return $this->client->toShortCallback(array($unicode,$unicode));
169    }
170
171    private function shortnameToImage($shortname) {
172        return $this->client->shortnameToImageCallback(array($shortname,$shortname));
173    }
174
175    private function unicodeToImage($unicode) {
176        return $this->client->unicodeToImageCallback(array($unicode,$unicode));
177    }
178
179}
180