Completed
Push — master ( e8b1d2...39a41b )
by Josh
18:51
created

Configurator::getRenderer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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