[ Index ] |
PHP Cross Reference of phpBB-3.3.14-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package s9e\TextFormatter 5 * @copyright Copyright (c) 2010-2022 The s9e authors 6 * @license http://www.opensource.org/licenses/mit-license.php The MIT License 7 */ 8 namespace s9e\TextFormatter\Configurator; 9 10 use ReflectionClass; 11 use s9e\TextFormatter\Configurator; 12 use s9e\TextFormatter\Configurator\Helpers\AVTHelper; 13 use s9e\TextFormatter\Configurator\Helpers\ConfigHelper; 14 use s9e\TextFormatter\Configurator\JavaScript\CallbackGenerator; 15 use s9e\TextFormatter\Configurator\JavaScript\Code; 16 use s9e\TextFormatter\Configurator\JavaScript\ConfigOptimizer; 17 use s9e\TextFormatter\Configurator\JavaScript\Dictionary; 18 use s9e\TextFormatter\Configurator\JavaScript\Encoder; 19 use s9e\TextFormatter\Configurator\JavaScript\HintGenerator; 20 use s9e\TextFormatter\Configurator\JavaScript\Minifier; 21 use s9e\TextFormatter\Configurator\JavaScript\Minifiers\Noop; 22 use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor; 23 use s9e\TextFormatter\Configurator\JavaScript\StylesheetCompressor; 24 use s9e\TextFormatter\Configurator\RendererGenerators\XSLT; 25 26 class JavaScript 27 { 28 /** 29 * @var CallbackGenerator 30 */ 31 protected $callbackGenerator; 32 33 /** 34 * @var array Configuration, filtered for JavaScript 35 */ 36 protected $config; 37 38 /** 39 * @var ConfigOptimizer 40 */ 41 protected $configOptimizer; 42 43 /** 44 * @var Configurator Configurator this instance belongs to 45 */ 46 protected $configurator; 47 48 /** 49 * @var Encoder 50 */ 51 public $encoder; 52 53 /** 54 * @var array List of methods and properties to be exported in the s9e.TextFormatter object 55 */ 56 public $exports = [ 57 'disablePlugin', 58 'disableTag', 59 'enablePlugin', 60 'enableTag', 61 'getLogger', 62 'parse', 63 'preview', 64 'registeredVars', 65 'setNestingLimit', 66 'setParameter', 67 'setTagLimit' 68 ]; 69 70 /** 71 * @var HintGenerator 72 */ 73 protected $hintGenerator; 74 75 /** 76 * @var Minifier Instance of Minifier used to minify the JavaScript parser 77 */ 78 protected $minifier; 79 80 /** 81 * @var StylesheetCompressor 82 */ 83 protected $stylesheetCompressor; 84 85 /** 86 * @var string Stylesheet used for rendering 87 */ 88 protected $xsl; 89 90 /** 91 * Constructor 92 * 93 * @param Configurator $configurator Configurator 94 */ 95 public function __construct(Configurator $configurator) 96 { 97 $this->encoder = new Encoder; 98 $this->callbackGenerator = new CallbackGenerator; 99 $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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Nov 25 19:05:08 2024 | Cross-referenced by PHPXref 0.7.1 |