Completed
Push — master ( 7e512b...a3ced4 )
by Josh
03:28
created

Configurator::__get()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
ccs 8
cts 8
cp 1
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2019 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter;
9
10
use InvalidArgumentException;
11
use RuntimeException;
12
use s9e\TextFormatter\Configurator\BundleGenerator;
13
use s9e\TextFormatter\Configurator\Collections\AttributeFilterCollection;
14
use s9e\TextFormatter\Configurator\Collections\PluginCollection;
15
use s9e\TextFormatter\Configurator\Collections\Ruleset;
16
use s9e\TextFormatter\Configurator\Collections\TagCollection;
17
use s9e\TextFormatter\Configurator\ConfigProvider;
18
use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
19
use s9e\TextFormatter\Configurator\Helpers\RulesHelper;
20
use s9e\TextFormatter\Configurator\JavaScript;
21
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
22
use s9e\TextFormatter\Configurator\Rendering;
23
use s9e\TextFormatter\Configurator\RulesGenerator;
24
use s9e\TextFormatter\Configurator\TemplateChecker;
25
use s9e\TextFormatter\Configurator\TemplateNormalizer;
26
use s9e\TextFormatter\Configurator\UrlConfig;
27
28
/**
29
* @property Plugins\Autoemail\Configurator $Autoemail Autoemail plugin's configurator
30
* @property Plugins\Autoimage\Configurator $Autolink Autoimage plugin's configurator
31
* @property Plugins\Autolink\Configurator $Autolink Autolink plugin's configurator
32
* @property Plugins\Autovideo\Configurator $Autovideo Autovideo plugin's configurator
33
* @property Plugins\BBCodes\Configurator $BBCodes BBCodes plugin's configurator
34
* @property Plugins\Censor\Configurator $Censor Censor plugin's configurator
35
* @property Plugins\Emoji\Configurator $Emoji Emoji plugin's configurator
36
* @property Plugins\Emoticons\Configurator $Emoticons Emoticons plugin's configurator
37
* @property Plugins\Escaper\Configurator $Escaper Escaper plugin's configurator
38
* @property Plugins\FancyPants\Configurator $FancyPants FancyPants plugin's configurator
39
* @property Plugins\HTMLComments\Configurator $HTMLComments HTMLComments plugin's configurator
40
* @property Plugins\HTMLElements\Configurator $HTMLElements HTMLElements plugin's configurator
41
* @property Plugins\HTMLEntities\Configurator $HTMLEntities HTMLEntities plugin's configurator
42
* @property Plugins\Keywords\Configurator $Keywords Keywords plugin's configurator
43
* @property Plugins\Litedown\Configurator $Litedown Litedown plugin's configurator
44
* @property Plugins\MediaEmbed\Configurator $MediaEmbed MediaEmbed plugin's configurator
45
* @property Plugins\PipeTables\Configurator $PipeTables PipeTables plugin's configurator
46
* @property Plugins\Preg\Configurator $Preg Preg plugin's configurator
47
* @property UrlConfig $urlConfig Default URL config
48
*/
49
class Configurator implements ConfigProvider
50
{
51
	/**
52
	* @var AttributeFilterCollection Dynamically-populated collection of AttributeFilter instances
53
	*/
54
	public $attributeFilters;
55
56
	/**
57
	* @var BundleGenerator Default bundle generator
58
	*/
59
	public $bundleGenerator;
60
61
	/**
62
	* @var JavaScript JavaScript manipulation object
63
	*/
64
	public $javascript;
65
66
	/**
67
	* @var PluginCollection Loaded plugins
68
	*/
69
	public $plugins;
70
71
	/**
72
	* @var array Array of variables that are available to the filters during parsing
73
	*/
74
	public $registeredVars;
75
76
	/**
77
	* @var Rendering Rendering configuration
78
	*/
79
	public $rendering;
80
81
	/**
82
	* @var Ruleset Rules that apply at the root of the text
83
	*/
84
	public $rootRules;
85
86
	/**
87
	* @var RulesGenerator Generator used by $this->getRenderer()
88
	*/
89
	public $rulesGenerator;
90
91
	/**
92
	* @var TagCollection Tags repository
93
	*/
94
	public $tags;
95
96
	/**
97
	* @var TemplateChecker Default template checker
98
	*/
99
	public $templateChecker;
100
101
	/**
102
	* @var TemplateNormalizer Default template normalizer
103
	*/
104
	public $templateNormalizer;
105
106
	/**
107
	* Constructor
108
	*
109
	* Prepares the collections that hold tags and filters, the UrlConfig object as well as the
110
	* various helpers required to generate a full config.
111
	*/
112 46
	public function __construct()
113
	{
114 46
		$this->attributeFilters   = new AttributeFilterCollection;
115 46
		$this->bundleGenerator    = new BundleGenerator($this);
116 46
		$this->plugins            = new PluginCollection($this);
117 46
		$this->registeredVars     = ['urlConfig' => new UrlConfig];
118 46
		$this->rendering          = new Rendering($this);
119 46
		$this->rootRules          = new Ruleset;
120 46
		$this->rulesGenerator     = new RulesGenerator;
121 46
		$this->tags               = new TagCollection;
122 46
		$this->templateChecker    = new TemplateChecker;
123 46
		$this->templateNormalizer = new TemplateNormalizer;
124
	}
125
126
	/**
127
	* Magic __get automatically loads plugins, returns registered vars
128
	*
129
	* @param  string $k Property name
130
	* @return mixed
131
	*/
132 5
	public function __get($k)
133
	{
134 5
		if (preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
135
		{
136 3
			return (isset($this->plugins[$k]))
137 1
			     ? $this->plugins[$k]
138 3
			     : $this->plugins->load($k);
139
		}
140
141 2
		if (isset($this->registeredVars[$k]))
142
		{
143 1
			return $this->registeredVars[$k];
144
		}
145
146 1
		throw new RuntimeException("Undefined property '" . __CLASS__ . '::$' . $k . "'");
147
	}
148
149
	/**
150
	* Magic __isset checks existence in the plugins collection and registered vars
151
	*
152
	* @param  string $k Property name
153
	* @return bool
154
	*/
155 5
	public function __isset($k)
156
	{
157 5
		if (preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
158
		{
159 2
			return isset($this->plugins[$k]);
160
		}
161
162 3
		return isset($this->registeredVars[$k]);
163
	}
164
165
	/**
166
	* Magic __set adds to the plugins collection, registers vars
167
	*
168
	* @param  string $k Property name
169
	* @param  mixed  $v Property value
170
	* @return mixed
171
	*/
172 2
	public function __set($k, $v)
173
	{
174 2
		if (preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
175
		{
176 1
			$this->plugins[$k] = $v;
177
		}
178
		else
179
		{
180 1
			$this->registeredVars[$k] = $v;
181
		}
182
	}
183
184
	/**
185
	* Magic __set removes plugins from the plugins collection, unregisters vars
186
	*
187
	* @param  string $k Property name
188
	* @return mixed
189
	*/
190 2
	public function __unset($k)
191
	{
192 2
		if (preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
193
		{
194 1
			unset($this->plugins[$k]);
195
		}
196
		else
197
		{
198 1
			unset($this->registeredVars[$k]);
199
		}
200
	}
201
202
	/**
203
	* Enable the creation of a JavaScript parser
204
	*
205
	* @return void
206
	*/
207 4
	public function enableJavaScript()
208
	{
209 4
		if (!isset($this->javascript))
210
		{
211 4
			$this->javascript = new JavaScript($this);
212
		}
213
	}
214
215
	/**
216
	* Finalize this configuration and return all the relevant objects
217
	*
218
	* @return array One "parser" element and one "renderer" element unless specified otherwise
219
	*/
220 13
	public function finalize()
221
	{
222 13
		$return = [];
223
224
		// Finalize the plugins' config
225 13
		$this->plugins->finalize();
226
227
		// Normalize the tags' templates
228 13
		foreach ($this->tags as $tag)
229
		{
230 5
			$this->templateNormalizer->normalizeTag($tag);
231
		}
232
233
		// Create a renderer
234 13
		$return['renderer'] = $this->rendering->getRenderer();
235
236
		// Add the generated tag rules
237 13
		$this->addTagRules();
238
239
		// Prepare the parser config
240 13
		$config = $this->asConfig();
241 13
		if (isset($this->javascript))
242
		{
243 2
			$return['js'] = $this->javascript->getParser(ConfigHelper::filterConfig($config, 'JS'));
244
		}
245
246
		// Remove JS-specific data from the config
247 13
		$config = ConfigHelper::filterConfig($config, 'PHP');
248 13
		ConfigHelper::optimizeArray($config);
249
250
		// Create a parser
251 13
		$return['parser'] = new Parser($config);
252
253 13
		return $return;
254
	}
255
256
	/**
257
	* Load a bundle into this configuration
258
	*
259
	* @param  string $bundleName Name of the bundle
260
	* @return void
261
	*/
262 1
	public function loadBundle($bundleName)
263
	{
264 1
		if (!preg_match('#^[A-Z][A-Za-z0-9]+$#D', $bundleName))
265
		{
266 1
			throw new InvalidArgumentException("Invalid bundle name '" . $bundleName . "'");
267
		}
268
269
		$className = __CLASS__ . '\\Bundles\\' . $bundleName;
270
271
		$bundle = new $className;
272
		$bundle->configure($this);
273
	}
274
275
	/**
276
	* Create and save a bundle based on this configuration
277
	*
278
	* @param  string $className Name of the bundle class
279
	* @param  string $filepath  Path where to save the bundle file
280
	* @param  array  $options   Options passed to the bundle generator
281
	* @return bool              Whether the write succeeded
282
	*/
283 3
	public function saveBundle($className, $filepath, array $options = [])
284
	{
285 3
		$file = "<?php\n\n" . $this->bundleGenerator->generate($className, $options);
286
287 3
		return (file_put_contents($filepath, $file) !== false);
288
	}
289
290
	/**
291
	* Generate and return the complete config array
292
	*
293
	* @return array
294
	*/
295 22
	public function asConfig()
296
	{
297
		// Remove properties that shouldn't be turned into config arrays
298 22
		$properties = get_object_vars($this);
299 22
		unset($properties['attributeFilters']);
300 22
		unset($properties['bundleGenerator']);
301 22
		unset($properties['javascript']);
302 22
		unset($properties['rendering']);
303 22
		unset($properties['rulesGenerator']);
304 22
		unset($properties['registeredVars']);
305 22
		unset($properties['templateChecker']);
306 22
		unset($properties['templateNormalizer']);
307 22
		unset($properties['stylesheet']);
308
309
		// Create the config array
310 22
		$config    = ConfigHelper::toArray($properties);
311 22
		$bitfields = RulesHelper::getBitfields($this->tags, $this->rootRules);
312
313
		// Save the root context
314 22
		$config['rootContext'] = $bitfields['root'];
315 22
		$config['rootContext']['flags'] = $config['rootRules']['flags'];
316
317
		// Save the registered vars (including the empty ones)
318 22
		$config['registeredVars'] = ConfigHelper::toArray($this->registeredVars, true);
319
320
		// Make sure those keys exist even if they're empty
321
		$config += [
322 22
			'plugins' => [],
323
			'tags'    => []
324
		];
325
326
		// Remove unused tags
327 22
		$config['tags'] = array_intersect_key($config['tags'], $bitfields['tags']);
328
329
		// Add the bitfield information to each tag
330 22
		foreach ($bitfields['tags'] as $tagName => $tagBitfields)
331
		{
332 6
			$config['tags'][$tagName] += $tagBitfields;
333
		}
334
335
		// Remove unused entries
336 22
		unset($config['rootRules']);
337
338 22
		return $config;
339
	}
340
341
	/**
342
	* Add the rules generated by $this->rulesGenerator
343
	*
344
	* @return void
345
	*/
346 13
	protected function addTagRules()
347
	{
348
		// Get the rules
349 13
		$rules = $this->rulesGenerator->getRules($this->tags);
350
351
		// Add the rules pertaining to the root
352 13
		$this->rootRules->merge($rules['root'], false);
353
354
		// Add the rules pertaining to each tag
355 13
		foreach ($rules['tags'] as $tagName => $tagRules)
356
		{
357 5
			$this->tags[$tagName]->rules->merge($tagRules, false);
358
		}
359
	}
360
}