Completed
Branch TemplateInspector (5726eb)
by Josh
09:25
created

Configurator::saveBundle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 2
cts 3
cp 0.6667
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 3
crap 1.037
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2017 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 45
	public function __construct()
113
	{
114 45
		$this->attributeFilters   = new AttributeFilterCollection;
115 45
		$this->bundleGenerator    = new BundleGenerator($this);
116 45
		$this->plugins            = new PluginCollection($this);
117 45
		$this->registeredVars     = ['urlConfig' => new UrlConfig];
118 45
		$this->rendering          = new Rendering($this);
119 45
		$this->rootRules          = new Ruleset;
120 45
		$this->rulesGenerator     = new RulesGenerator;
121 45
		$this->tags               = new TagCollection;
122 45
		$this->templateChecker    = new TemplateChecker;
123 45
		$this->templateNormalizer = new TemplateNormalizer;
124 45
	}
125
126
	/**
127
	* Magic __get automatically loads plugins, returns registered vars
128
	*
129
	* @param  string $k Property name
130
	* @return mixed
131
	*/
132 6
	public function __get($k)
133
	{
134 6
		if (preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
135
		{
136 4
			return (isset($this->plugins[$k]))
137 2
			     ? $this->plugins[$k]
138 4
			     : $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 2
	}
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 2
	}
201
202
	/**
203
	* Enable the creation of a JavaScript parser
204
	*
205
	* @return void
206
	*/
207 3
	public function enableJavaScript()
208
	{
209 3
		if (!isset($this->javascript))
210
		{
211 3
			$this->javascript = new JavaScript($this);
212
		}
213 3
	}
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 11
	public function finalize()
221
	{
222 11
		$return = [];
223
224
		// Finalize the plugins' config
225 11
		$this->plugins->finalize();
226
227
		// Normalize the tags' templates
228 11
		foreach ($this->tags as $tag)
229
		{
230 5
			$this->templateNormalizer->normalizeTag($tag);
231
		}
232
233
		// Create a renderer
234 11
		$return['renderer'] = $this->rendering->getRenderer();
235
236
		// Add the generated tag rules
237 11
		$this->addTagRules();
238
239
		// Prepare the parser config
240
		$config = $this->asConfig();
241
		if (isset($this->javascript))
242
		{
243
			$return['js'] = $this->javascript->getParser(ConfigHelper::filterConfig($config, 'JS'));
244
		}
245
246
		// Remove JS-specific data from the config
247
		$config = ConfigHelper::filterConfig($config, 'PHP');
248
		ConfigHelper::optimizeArray($config);
249
250
		// Create a parser
251
		$return['parser'] = new Parser($config);
252
253
		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 2
	public function loadBundle($bundleName)
263
	{
264 2
		if (!preg_match('#^[A-Z][A-Za-z0-9]+$#D', $bundleName))
265
		{
266 1
			throw new InvalidArgumentException("Invalid bundle name '" . $bundleName . "'");
267
		}
268
269 1
		$className = __CLASS__ . '\\Bundles\\' . $bundleName;
270
271 1
		$bundle = new $className;
272 1
		$bundle->configure($this);
273 1
	}
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
		return (file_put_contents($filepath, $file) !== false);
288
	}
289
290
	/**
291
	* Generate and return the complete config array
292
	*
293
	* @return array
294
	*/
295 9
	public function asConfig()
296
	{
297
		// Finalize the plugins' config
298 9
		$this->plugins->finalize();
299
300
		// Remove properties that shouldn't be turned into config arrays
301 9
		$properties = get_object_vars($this);
302 9
		unset($properties['attributeFilters']);
303 9
		unset($properties['bundleGenerator']);
304 9
		unset($properties['javascript']);
305 9
		unset($properties['rendering']);
306 9
		unset($properties['rulesGenerator']);
307 9
		unset($properties['registeredVars']);
308 9
		unset($properties['templateChecker']);
309 9
		unset($properties['templateNormalizer']);
310 9
		unset($properties['stylesheet']);
311
312
		// Create the config array
313 9
		$config    = ConfigHelper::toArray($properties);
314 9
		$bitfields = RulesHelper::getBitfields($this->tags, $this->rootRules);
315
316
		// Save the root context
317 9
		$config['rootContext'] = $bitfields['root'];
318 9
		$config['rootContext']['flags'] = $config['rootRules']['flags'];
319
320
		// Save the registered vars (including the empty ones)
321 9
		$config['registeredVars'] = ConfigHelper::toArray($this->registeredVars, true);
322
323
		// Make sure those keys exist even if they're empty
324
		$config += [
325 9
			'plugins' => [],
326
			'tags'    => []
327
		];
328
329
		// Remove unused tags
330 9
		$config['tags'] = array_intersect_key($config['tags'], $bitfields['tags']);
331
332
		// Add the bitfield information to each tag
333 9
		foreach ($bitfields['tags'] as $tagName => $tagBitfields)
334
		{
335 1
			$config['tags'][$tagName] += $tagBitfields;
336
		}
337
338
		// Remove unused entries
339 9
		unset($config['rootRules']);
340
341 9
		return $config;
342
	}
343
344
	/**
345
	* Add the rules generated by $this->rulesGenerator
346
	*
347
	* @return void
348
	*/
349 11
	protected function addTagRules()
350
	{
351
		// Get the rules
352 11
		$rules = $this->rulesGenerator->getRules($this->tags);
353
354
		// Add the rules pertaining to the root
355
		$this->rootRules->merge($rules['root'], false);
356
357
		// Add the rules pertaining to each tag
358
		foreach ($rules['tags'] as $tagName => $tagRules)
359
		{
360
			$this->tags[$tagName]->rules->merge($tagRules, false);
361
		}
362
	}
363
}