Completed
Push — master ( 9d183f...fa9903 )
by Josh
26:00
created

Configurator   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 181
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 181
rs 10
ccs 77
cts 77
cp 1
wmc 16
lcom 1
cbo 11

5 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 9 2
A finalize() 0 9 2
B asConfig() 0 67 7
A getJSHints() 0 4 1
B getTemplate() 0 37 4
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2016 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Plugins\Emoticons;
9
10
use ArrayAccess;
11
use Countable;
12
use Iterator;
13
use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
14
use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
15
use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
16
use s9e\TextFormatter\Configurator\Items\Regexp;
17
use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor;
18
use s9e\TextFormatter\Configurator\Traits\CollectionProxy;
19
use s9e\TextFormatter\Plugins\ConfiguratorBase;
20
use s9e\TextFormatter\Plugins\Emoticons\Configurator\EmoticonCollection;
21
22
/**
23
* @method mixed   add(string $key, mixed $value)
24
* @method array   asConfig()
25
* @method void    clear()
26
* @method bool    contains(mixed $value)
27
* @method integer count()
28
* @method mixed   current()
29
* @method void    delete(string $key)
30
* @method bool    exists(string $key)
31
* @method mixed   get(string $key)
32
* @method mixed   indexOf(mixed $value)
33
* @method integer|string key()
34
* @method mixed   next()
35
* @method string  normalizeKey(string $key)
36
* @method string  normalizeValue(string $value)
37
* @method bool    offsetExists(string|integer $offset)
38
* @method mixed   offsetGet(string|integer $offset)
39
* @method void    offsetSet(string|integer $offset, mixed $value)
40
* @method void    offsetUnset(string|integer $offset)
41
* @method string  onDuplicate(string|null $action)
42
* @method void    rewind()
43
* @method mixed   set(string $key, mixed $value)
44
* @method bool    valid()
45
*/
46
class Configurator extends ConfiguratorBase implements ArrayAccess, Countable, Iterator
47
{
48
	use CollectionProxy;
49
50
	/**
51
	* @var EmoticonCollection
52
	*/
53
	protected $collection;
54
55
	/**
56
	* @var string PCRE subpattern used in a negative lookbehind assertion before the emoticons
57
	*/
58
	public $notAfter = '';
59
60
	/**
61
	* @var string PCRE subpattern used in a negative lookahead assertion after the emoticons
62
	*/
63
	public $notBefore = '';
64
65
	/**
66
	* @var string XPath expression that, if true, forces emoticons to be rendered as text
67
	*/
68
	public $notIfCondition;
69
70
	/**
71
	* @var string Name of the tag used by this plugin
72
	*/
73
	protected $tagName = 'E';
74
75
	/**
76
	* Plugin's setup
77
	*
78
	* Will create the tag used by this plugin
79
	*/
80 24
	protected function setUp()
81
	{
82 24
		$this->collection = new EmoticonCollection;
83
84 24
		if (!$this->configurator->tags->exists($this->tagName))
85 24
		{
86 23
			$this->configurator->tags->add($this->tagName);
87 23
		}
88 24
	}
89
90
	/**
91
	* Create the template used for emoticons
92
	*
93
	* @return void
94
	*/
95 1
	public function finalize()
96
	{
97 1
		$tag = $this->getTag();
98
99 1
		if (!isset($tag->template))
100 1
		{
101 1
			$tag->template = $this->getTemplate();
102 1
		}
103 1
	}
104
105
	/**
106
	* @return array
107
	*/
108 11
	public function asConfig()
109
	{
110 11
		if (!count($this->collection))
111 11
		{
112 1
			return;
113
		}
114
115
		// Grab the emoticons from the collection
116 10
		$codes = array_keys(iterator_to_array($this->collection));
117
118
		// Build the regexp used to match emoticons
119 10
		$regexp = '/';
120
121 10
		if ($this->notAfter !== '')
122 10
		{
123 5
			$regexp .= '(?<!' . $this->notAfter . ')';
124 5
		}
125
126 10
		$regexp .= RegexpBuilder::fromList($codes);
127
128 10
		if ($this->notBefore !== '')
129 10
		{
130 2
			$regexp .= '(?!' . $this->notBefore . ')';
131 2
		}
132
133 10
		$regexp .= '/S';
134
135
		// Set the Unicode mode if Unicode properties are used
136 10
		if (preg_match('/\\\\[pP](?>\\{\\^?\\w+\\}|\\w\\w?)/', $regexp))
137 10
		{
138 2
			$regexp .= 'u';
139 2
		}
140
141
		// Force the regexp to use atomic grouping for performance
142 10
		$regexp = preg_replace('/(?<!\\\\)((?>\\\\\\\\)*)\\(\\?:/', '$1(?>', $regexp);
143
144
		// Prepare the config array
145
		$config = [
146 10
			'quickMatch' => $this->quickMatch,
147 10
			'regexp'     => $regexp,
148 10
			'tagName'    => $this->tagName
149 10
		];
150
151
		// If notAfter is used, we need to create a JavaScript-specific regexp that does not use a
152
		// lookbehind assertion, and we add the notAfter subpattern to the config as a variant
153 10
		if ($this->notAfter !== '')
154 10
		{
155
			// Skip the first assertion by skipping the first N characters, where N equals the
156
			// length of $this->notAfter plus 1 for the first "/" and 5 for "(?<!)"
157 5
			$lpos = 6 + strlen($this->notAfter);
158 5
			$rpos = strrpos($regexp, '/');
159 5
			$jsRegexp = RegexpConvertor::toJS('/' . substr($regexp, $lpos, $rpos - $lpos) . '/', true);
160
161 5
			$config['regexp'] = new Regexp($regexp);
162 5
			$config['regexp']->setJS($jsRegexp);
163
164 5
			$config['notAfter'] = new Regexp('/' . $this->notAfter . '/');
165 6
		}
166
167
		// Try to find a quickMatch if none is set
168 10
		if ($this->quickMatch === false)
169 10
		{
170 10
			$config['quickMatch'] = ConfigHelper::generateQuickMatchFromList($codes);
171 10
		}
172
173 10
		return $config;
174
	}
175
176
	/**
177
	* {@inheritdoc}
178
	*/
179 2
	public function getJSHints()
180
	{
181 2
		return ['EMOTICONS_NOT_AFTER' => (int) !empty($this->notAfter)];
182
	}
183
184
	/**
185
	* Generate the dynamic template that renders all emoticons
186
	*
187
	* @return string
188
	*/
189 7
	public function getTemplate()
190
	{
191
		// Build the <xsl:choose> node
192 7
		$xsl = '<xsl:choose>';
193
194
		// First, test whether the emoticon should be rendered as text if applicable
195 7
		if (!empty($this->notIfCondition))
196 7
		{
197 1
			$xsl .= '<xsl:when test="' . htmlspecialchars($this->notIfCondition) . '">'
198 1
			      . '<xsl:value-of select="."/>'
199 1
			      . '</xsl:when>'
200 1
			      . '<xsl:otherwise>'
201 1
			      . '<xsl:choose>';
202 1
		}
203
204
		// Iterate over codes, create an <xsl:when> for each emote
205 7
		foreach ($this->collection as $code => $template)
206
		{
207 6
			$xsl .= '<xsl:when test=".=' . htmlspecialchars(XPathHelper::export($code)) . '">'
208 6
			      . $template
209 6
			      . '</xsl:when>';
210 7
		}
211
212
		// Finish it with an <xsl:otherwise> that displays the unknown codes as text
213 7
		$xsl .= '<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>';
214
215
		// Close the emote switch
216 7
		$xsl .= '</xsl:choose>';
217
218
		// Close the "notIf" condition if applicable
219 7
		if (!empty($this->notIfCondition))
220 7
		{
221 1
			$xsl .= '</xsl:otherwise></xsl:choose>';
222 1
		}
223
224 7
		return $xsl;
225
	}
226
}