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