Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
JavaScript.php
001 <?php
002
003 /**
004 * @package s9e\TextFormatter
005 * @copyright Copyright (c) 2010-2022 The s9e authors
006 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
007 */
008 namespace s9e\TextFormatter\Configurator;
009
010 use ReflectionClass;
011 use s9e\TextFormatter\Configurator;
012 use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
013 use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
014 use s9e\TextFormatter\Configurator\JavaScript\CallbackGenerator;
015 use s9e\TextFormatter\Configurator\JavaScript\Code;
016 use s9e\TextFormatter\Configurator\JavaScript\ConfigOptimizer;
017 use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
018 use s9e\TextFormatter\Configurator\JavaScript\Encoder;
019 use s9e\TextFormatter\Configurator\JavaScript\HintGenerator;
020 use s9e\TextFormatter\Configurator\JavaScript\Minifier;
021 use s9e\TextFormatter\Configurator\JavaScript\Minifiers\Noop;
022 use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor;
023 use s9e\TextFormatter\Configurator\JavaScript\StylesheetCompressor;
024 use s9e\TextFormatter\Configurator\RendererGenerators\XSLT;
025
026 class JavaScript
027 {
028 /**
029 * @var CallbackGenerator
030 */
031 protected $callbackGenerator;
032
033 /**
034 * @var array Configuration, filtered for JavaScript
035 */
036 protected $config;
037
038 /**
039 * @var ConfigOptimizer
040 */
041 protected $configOptimizer;
042
043 /**
044 * @var Configurator Configurator this instance belongs to
045 */
046 protected $configurator;
047
048 /**
049 * @var Encoder
050 */
051 public $encoder;
052
053 /**
054 * @var array List of methods and properties to be exported in the s9e.TextFormatter object
055 */
056 public $exports = [
057 'disablePlugin',
058 'disableTag',
059 'enablePlugin',
060 'enableTag',
061 'getLogger',
062 'parse',
063 'preview',
064 'registeredVars',
065 'setNestingLimit',
066 'setParameter',
067 'setTagLimit'
068 ];
069
070 /**
071 * @var HintGenerator
072 */
073 protected $hintGenerator;
074
075 /**
076 * @var Minifier Instance of Minifier used to minify the JavaScript parser
077 */
078 protected $minifier;
079
080 /**
081 * @var StylesheetCompressor
082 */
083 protected $stylesheetCompressor;
084
085 /**
086 * @var string Stylesheet used for rendering
087 */
088 protected $xsl;
089
090 /**
091 * Constructor
092 *
093 * @param Configurator $configurator Configurator
094 */
095 public function __construct(Configurator $configurator)
096 {
097 $this->encoder = new Encoder;
098 $this->callbackGenerator = new CallbackGenerator;
099 $this->configOptimizer = new ConfigOptimizer($this->encoder);
100 $this->configurator = $configurator;
101 $this->hintGenerator = new HintGenerator;
102 $this->stylesheetCompressor = new StylesheetCompressor;
103 }
104
105 /**
106 * Return the cached instance of Minifier (creates one if necessary)
107 *
108 * @return Minifier
109 */
110 public function getMinifier()
111 {
112 if (!isset($this->minifier))
113 {
114 $this->minifier = new Noop;
115 }
116
117 return $this->minifier;
118 }
119
120 /**
121 * Get a JavaScript parser
122 *
123 * @param array $config Config array returned by the configurator
124 * @return string JavaScript parser
125 */
126 public function getParser(array $config = null)
127 {
128 $this->configOptimizer->reset();
129
130 // Get the stylesheet used for rendering
131 $xslt = new XSLT;
132 $xslt->normalizer->remove('RemoveLivePreviewAttributes');
133 $this->xsl = $xslt->getXSL($this->configurator->rendering);
134
135 // Prepare the parser's config
136 $this->config = $config ?? $this->configurator->asConfig();
137 $this->config = ConfigHelper::filterConfig($this->config, 'JS');
138 $this->config = $this->callbackGenerator->replaceCallbacks($this->config);
139
140 // Get the parser's source and inject its config
141 $src = $this->getHints() . $this->injectConfig($this->getSource());
142
143 // Export the public API
144 $src .= "if (!window['s9e']) window['s9e'] = {};\n" . $this->getExports();
145
146 // Minify the source
147 $src = $this->getMinifier()->get($src);
148
149 // Wrap the source in a function to protect the global scope
150 $src = '(function(){' . $src . '})();';
151
152 return $src;
153 }
154
155 /**
156 * Set the cached instance of Minifier
157 *
158 * Extra arguments will be passed to the minifier's constructor
159 *
160 * @param string|Minifier $minifier Name of a supported minifier, or an instance of Minifier
161 * @return Minifier The new minifier
162 */
163 public function setMinifier($minifier)
164 {
165 if (is_string($minifier))
166 {
167 $className = __NAMESPACE__ . '\\JavaScript\\Minifiers\\' . $minifier;
168
169 // Pass the extra argument to the constructor, if applicable
170 $args = array_slice(func_get_args(), 1);
171 if (!empty($args))
172 {
173 $reflection = new ReflectionClass($className);
174 $minifier = $reflection->newInstanceArgs($args);
175 }
176 else
177 {
178 $minifier = new $className;
179 }
180 }
181
182 $this->minifier = $minifier;
183
184 return $minifier;
185 }
186
187 //==========================================================================
188 // Internal
189 //==========================================================================
190
191 /**
192 * Encode a PHP value into an equivalent JavaScript representation
193 *
194 * @param mixed $value Original value
195 * @return string JavaScript representation
196 */
197 protected function encode($value)
198 {
199 return $this->encoder->encode($value);
200 }
201
202 /**
203 * Generate and return the public API
204 *
205 * @return string JavaScript Code
206 */
207 protected function getExports()
208 {
209 if (empty($this->exports))
210 {
211 return '';
212 }
213
214 $exports = [];
215 foreach ($this->exports as $export)
216 {
217 $exports[] = "'" . $export . "':" . $export;
218 }
219 sort($exports);
220
221 return "window['s9e']['TextFormatter'] = {" . implode(',', $exports) . '};';
222 }
223
224 /**
225 * @return string Function cache serialized as a JavaScript object
226 */
227 protected function getFunctionCache(): string
228 {
229 preg_match_all('(data-s9e-livepreview-on\\w+="([^">]++)(?=[^<>]++>))', $this->xsl, $m);
230
231 $cache = [];
232 foreach ($m[1] as $js)
233 {
234 $avt = AVTHelper::parse($js);
235 if (count($avt) === 1 && $avt[0][0] === 'literal')
236 {
237 $js = htmlspecialchars_decode($js);
238 $cache[] = json_encode($js) . ':/**@this {!Element}*/function(){' . trim($js, ';') . ';}';
239 }
240 }
241
242 return '{' . implode(',', $cache) . '}';
243 }
244
245 /**
246 * Generate a HINT object that contains informations about the configuration
247 *
248 * @return string JavaScript Code
249 */
250 protected function getHints()
251 {
252 $this->hintGenerator->setConfig($this->config);
253 $this->hintGenerator->setPlugins($this->configurator->plugins);
254 $this->hintGenerator->setXSL($this->xsl);
255
256 return $this->hintGenerator->getHints();
257 }
258
259 /**
260 * Return the plugins' config
261 *
262 * @return Dictionary
263 */
264 protected function getPluginsConfig()
265 {
266 $plugins = new Dictionary;
267
268 foreach ($this->config['plugins'] as $pluginName => $pluginConfig)
269 {
270 if (!isset($pluginConfig['js']))
271 {
272 // Skip this plugin
273 continue;
274 }
275 $js = $pluginConfig['js'];
276 unset($pluginConfig['js']);
277
278 // Not needed in JavaScript
279 unset($pluginConfig['className']);
280
281 // Ensure that quickMatch is UTF-8 if present
282 if (isset($pluginConfig['quickMatch']))
283 {
284 // Well-formed UTF-8 sequences
285 $valid = [
286 '[[:ascii:]]',
287 // [1100 0000-1101 1111] [1000 0000-1011 1111]
288 '[\\xC0-\\xDF][\\x80-\\xBF]',
289 // [1110 0000-1110 1111] [1000 0000-1011 1111]{2}
290 '[\\xE0-\\xEF][\\x80-\\xBF]{2}',
291 // [1111 0000-1111 0111] [1000 0000-1011 1111]{3}
292 '[\\xF0-\\xF7][\\x80-\\xBF]{3}'
293 ];
294
295 $regexp = '#(?>' . implode('|', $valid) . ')+#';
296
297 // Keep only the first valid sequence of UTF-8, or unset quickMatch if none is found
298 if (preg_match($regexp, $pluginConfig['quickMatch'], $m))
299 {
300 $pluginConfig['quickMatch'] = $m[0];
301 }
302 else
303 {
304 unset($pluginConfig['quickMatch']);
305 }
306 }
307
308 /**
309 * @var array Keys of elements that are kept in the global scope. Everything else will be
310 * moved into the plugin's parser
311 */
312 $globalKeys = [
313 'quickMatch' => 1,
314 'regexp' => 1,
315 'regexpLimit' => 1
316 ];
317
318 $globalConfig = array_intersect_key($pluginConfig, $globalKeys);
319 $localConfig = array_diff_key($pluginConfig, $globalKeys);
320
321 if (isset($globalConfig['regexp']) && !($globalConfig['regexp'] instanceof Code))
322 {
323 $globalConfig['regexp'] = new Code(RegexpConvertor::toJS($globalConfig['regexp'], true));
324 }
325
326 $globalConfig['parser'] = new Code(
327 '/**
328 * @param {string} text
329 * @param {!Array.<!Array>} matches
330 */
331 function(text, matches)
332 {
333 /** @const */
334 var config=' . $this->encode($localConfig) . ';
335 ' . $js . '
336 }'
337 );
338
339 $plugins[$pluginName] = $globalConfig;
340 }
341
342 return $plugins;
343 }
344
345 /**
346 * Return the registeredVars config
347 *
348 * @return Dictionary
349 */
350 protected function getRegisteredVarsConfig()
351 {
352 $registeredVars = $this->config['registeredVars'];
353
354 // Remove cacheDir from the registered vars. Not only it is useless in JavaScript, it could
355 // leak some informations about the server
356 unset($registeredVars['cacheDir']);
357
358 return new Dictionary($registeredVars);
359 }
360
361 /**
362 * Return the root context config
363 *
364 * @return array
365 */
366 protected function getRootContext()
367 {
368 return $this->config['rootContext'];
369 }
370
371 /**
372 * Return the parser's source
373 *
374 * @return string
375 */
376 protected function getSource()
377 {
378 $rootDir = __DIR__ . '/..';
379 $src = '';
380
381 // If getLogger() is not exported we use a dummy Logger that can be optimized away
382 $logger = (in_array('getLogger', $this->exports)) ? 'Logger.js' : 'NullLogger.js';
383
384 // Prepare the list of files
385 $files = glob($rootDir . '/Parser/AttributeFilters/*.js');
386 $files[] = $rootDir . '/Parser/utils.js';
387 $files[] = $rootDir . '/Parser/FilterProcessing.js';
388 $files[] = $rootDir . '/Parser/' . $logger;
389 $files[] = $rootDir . '/Parser/Tag.js';
390 $files[] = $rootDir . '/Parser.js';
391
392 // Append render.js if we export the preview method
393 if (in_array('preview', $this->exports, true))
394 {
395 $files[] = $rootDir . '/render.js';
396 $src .= '/** @const */ var xsl=' . $this->getStylesheet() . ";\n";
397 $src .= 'var functionCache=' . $this->getFunctionCache() . ";\n";
398 }
399
400 $src .= implode("\n", array_map('file_get_contents', $files));
401
402 return $src;
403 }
404
405 /**
406 * Return the JavaScript representation of the stylesheet
407 *
408 * @return string
409 */
410 protected function getStylesheet()
411 {
412 return $this->stylesheetCompressor->encode($this->xsl);
413 }
414
415 /**
416 * Return the tags' config
417 *
418 * @return Dictionary
419 */
420 protected function getTagsConfig()
421 {
422 // Prepare a Dictionary that will preserve tags' names
423 $tags = new Dictionary;
424 foreach ($this->config['tags'] as $tagName => $tagConfig)
425 {
426 if (isset($tagConfig['attributes']))
427 {
428 // Make the attributes array a Dictionary, to preserve the attributes' names
429 $tagConfig['attributes'] = new Dictionary($tagConfig['attributes']);
430 }
431
432 $tags[$tagName] = $tagConfig;
433 }
434
435 return $tags;
436 }
437
438 /**
439 * Inject the parser config into given source
440 *
441 * @param string $src Parser's source
442 * @return string Modified source
443 */
444 protected function injectConfig($src)
445 {
446 $config = array_map(
447 [$this, 'encode'],
448 $this->configOptimizer->optimize(
449 [
450 'plugins' => $this->getPluginsConfig(),
451 'registeredVars' => $this->getRegisteredVarsConfig(),
452 'rootContext' => $this->getRootContext(),
453 'tagsConfig' => $this->getTagsConfig()
454 ]
455 )
456 );
457
458 $src = preg_replace_callback(
459 '/(\\nvar (' . implode('|', array_keys($config)) . '))(;)/',
460 function ($m) use ($config)
461 {
462 return $m[1] . '=' . $config[$m[2]] . $m[3];
463 },
464 $src
465 );
466
467 // Prepend the deduplicated objects
468 $src = $this->configOptimizer->getVarDeclarations() . $src;
469
470 return $src;
471 }
472 }