[ Index ] |
PHP Cross Reference of phpBB-3.3.7-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package s9e\TextFormatter 5 * @copyright Copyright (c) 2010-2021 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\ConfigHelper; 13 use s9e\TextFormatter\Configurator\JavaScript\CallbackGenerator; 14 use s9e\TextFormatter\Configurator\JavaScript\Code; 15 use s9e\TextFormatter\Configurator\JavaScript\ConfigOptimizer; 16 use s9e\TextFormatter\Configurator\JavaScript\Dictionary; 17 use s9e\TextFormatter\Configurator\JavaScript\Encoder; 18 use s9e\TextFormatter\Configurator\JavaScript\HintGenerator; 19 use s9e\TextFormatter\Configurator\JavaScript\Minifier; 20 use s9e\TextFormatter\Configurator\JavaScript\Minifiers\Noop; 21 use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor; 22 use s9e\TextFormatter\Configurator\JavaScript\StylesheetCompressor; 23 use s9e\TextFormatter\Configurator\RendererGenerators\XSLT; 24 25 class JavaScript 26 { 27 /** 28 * @var CallbackGenerator 29 */ 30 protected $callbackGenerator; 31 32 /** 33 * @var array Configuration, filtered for JavaScript 34 */ 35 protected $config; 36 37 /** 38 * @var ConfigOptimizer 39 */ 40 protected $configOptimizer; 41 42 /** 43 * @var Configurator Configurator this instance belongs to 44 */ 45 protected $configurator; 46 47 /** 48 * @var Encoder 49 */ 50 public $encoder; 51 52 /** 53 * @var array List of methods and properties to be exported in the s9e.TextFormatter object 54 */ 55 public $exports = [ 56 'disablePlugin', 57 'disableTag', 58 'enablePlugin', 59 'enableTag', 60 'getLogger', 61 'parse', 62 'preview', 63 'registeredVars', 64 'setNestingLimit', 65 'setParameter', 66 'setTagLimit' 67 ]; 68 69 /** 70 * @var HintGenerator 71 */ 72 protected $hintGenerator; 73 74 /** 75 * @var Minifier Instance of Minifier used to minify the JavaScript parser 76 */ 77 protected $minifier; 78 79 /** 80 * @var StylesheetCompressor 81 */ 82 protected $stylesheetCompressor; 83 84 /** 85 * @var string Stylesheet used for rendering 86 */ 87 protected $xsl; 88 89 /** 90 * Constructor 91 * 92 * @param Configurator $configurator Configurator 93 */ 94 public function __construct(Configurator $configurator) 95 { 96 $this->encoder = new Encoder; 97 $this->callbackGenerator = new CallbackGenerator; 98 $this->configOptimizer = new ConfigOptimizer($this->encoder); 99 $this->configurator = $configurator; 100 $this->hintGenerator = new HintGenerator; 101 $this->stylesheetCompressor = new StylesheetCompressor; 102 } 103 104 /** 105 * Return the cached instance of Minifier (creates one if necessary) 106 * 107 * @return Minifier 108 */ 109 public function getMinifier() 110 { 111 if (!isset($this->minifier)) 112 { 113 $this->minifier = new Noop; 114 } 115 116 return $this->minifier; 117 } 118 119 /** 120 * Get a JavaScript parser 121 * 122 * @param array $config Config array returned by the configurator 123 * @return string JavaScript parser 124 */ 125 public function getParser(array $config = null) 126 { 127 $this->configOptimizer->reset(); 128 129 // Get the stylesheet used for rendering 130 $xslt = new XSLT; 131 $xslt->normalizer->remove('RemoveLivePreviewAttributes'); 132 $this->xsl = $xslt->getXSL($this->configurator->rendering); 133 134 // Prepare the parser's config 135 $this->config = $config ?? $this->configurator->asConfig(); 136 $this->config = ConfigHelper::filterConfig($this->config, 'JS'); 137 $this->config = $this->callbackGenerator->replaceCallbacks($this->config); 138 139 // Get the parser's source and inject its config 140 $src = $this->getHints() . $this->injectConfig($this->getSource()); 141 142 // Export the public API 143 $src .= "if (!window['s9e']) window['s9e'] = {};\n" . $this->getExports(); 144 145 // Minify the source 146 $src = $this->getMinifier()->get($src); 147 148 // Wrap the source in a function to protect the global scope 149 $src = '(function(){' . $src . '})();'; 150 151 return $src; 152 } 153 154 /** 155 * Set the cached instance of Minifier 156 * 157 * Extra arguments will be passed to the minifier's constructor 158 * 159 * @param string|Minifier $minifier Name of a supported minifier, or an instance of Minifier 160 * @return Minifier The new minifier 161 */ 162 public function setMinifier($minifier) 163 { 164 if (is_string($minifier)) 165 { 166 $className = __NAMESPACE__ . '\\JavaScript\\Minifiers\\' . $minifier; 167 168 // Pass the extra argument to the constructor, if applicable 169 $args = array_slice(func_get_args(), 1); 170 if (!empty($args)) 171 { 172 $reflection = new ReflectionClass($className); 173 $minifier = $reflection->newInstanceArgs($args); 174 } 175 else 176 { 177 $minifier = new $className; 178 } 179 } 180 181 $this->minifier = $minifier; 182 183 return $minifier; 184 } 185 186 //========================================================================== 187 // Internal 188 //========================================================================== 189 190 /** 191 * Encode a PHP value into an equivalent JavaScript representation 192 * 193 * @param mixed $value Original value 194 * @return string JavaScript representation 195 */ 196 protected function encode($value) 197 { 198 return $this->encoder->encode($value); 199 } 200 201 /** 202 * Generate and return the public API 203 * 204 * @return string JavaScript Code 205 */ 206 protected function getExports() 207 { 208 if (empty($this->exports)) 209 { 210 return ''; 211 } 212 213 $exports = []; 214 foreach ($this->exports as $export) 215 { 216 $exports[] = "'" . $export . "':" . $export; 217 } 218 sort($exports); 219 220 return "window['s9e']['TextFormatter'] = {" . implode(',', $exports) . '};'; 221 } 222 223 /** 224 * Generate a HINT object that contains informations about the configuration 225 * 226 * @return string JavaScript Code 227 */ 228 protected function getHints() 229 { 230 $this->hintGenerator->setConfig($this->config); 231 $this->hintGenerator->setPlugins($this->configurator->plugins); 232 $this->hintGenerator->setXSL($this->xsl); 233 234 return $this->hintGenerator->getHints(); 235 } 236 237 /** 238 * Return the plugins' config 239 * 240 * @return Dictionary 241 */ 242 protected function getPluginsConfig() 243 { 244 $plugins = new Dictionary; 245 246 foreach ($this->config['plugins'] as $pluginName => $pluginConfig) 247 { 248 if (!isset($pluginConfig['js'])) 249 { 250 // Skip this plugin 251 continue; 252 } 253 $js = $pluginConfig['js']; 254 unset($pluginConfig['js']); 255 256 // Not needed in JavaScript 257 unset($pluginConfig['className']); 258 259 // Ensure that quickMatch is UTF-8 if present 260 if (isset($pluginConfig['quickMatch'])) 261 { 262 // Well-formed UTF-8 sequences 263 $valid = [ 264 '[[:ascii:]]', 265 // [1100 0000-1101 1111] [1000 0000-1011 1111] 266 '[\\xC0-\\xDF][\\x80-\\xBF]', 267 // [1110 0000-1110 1111] [1000 0000-1011 1111]{2} 268 '[\\xE0-\\xEF][\\x80-\\xBF]{2}', 269 // [1111 0000-1111 0111] [1000 0000-1011 1111]{3} 270 '[\\xF0-\\xF7][\\x80-\\xBF]{3}' 271 ]; 272 273 $regexp = '#(?>' . implode('|', $valid) . ')+#'; 274 275 // Keep only the first valid sequence of UTF-8, or unset quickMatch if none is found 276 if (preg_match($regexp, $pluginConfig['quickMatch'], $m)) 277 { 278 $pluginConfig['quickMatch'] = $m[0]; 279 } 280 else 281 { 282 unset($pluginConfig['quickMatch']); 283 } 284 } 285 286 /** 287 * @var array Keys of elements that are kept in the global scope. Everything else will be 288 * moved into the plugin's parser 289 */ 290 $globalKeys = [ 291 'quickMatch' => 1, 292 'regexp' => 1, 293 'regexpLimit' => 1 294 ]; 295 296 $globalConfig = array_intersect_key($pluginConfig, $globalKeys); 297 $localConfig = array_diff_key($pluginConfig, $globalKeys); 298 299 if (isset($globalConfig['regexp']) && !($globalConfig['regexp'] instanceof Code)) 300 { 301 $globalConfig['regexp'] = new Code(RegexpConvertor::toJS($globalConfig['regexp'], true)); 302 } 303 304 $globalConfig['parser'] = new Code( 305 '/** 306 * @param {string} text 307 * @param {!Array.<!Array>} matches 308 */ 309 function(text, matches) 310 { 311 /** @const */ 312 var config=' . $this->encode($localConfig) . '; 313 ' . $js . ' 314 }' 315 ); 316 317 $plugins[$pluginName] = $globalConfig; 318 } 319 320 return $plugins; 321 } 322 323 /** 324 * Return the registeredVars config 325 * 326 * @return Dictionary 327 */ 328 protected function getRegisteredVarsConfig() 329 { 330 $registeredVars = $this->config['registeredVars']; 331 332 // Remove cacheDir from the registered vars. Not only it is useless in JavaScript, it could 333 // leak some informations about the server 334 unset($registeredVars['cacheDir']); 335 336 return new Dictionary($registeredVars); 337 } 338 339 /** 340 * Return the root context config 341 * 342 * @return array 343 */ 344 protected function getRootContext() 345 { 346 return $this->config['rootContext']; 347 } 348 349 /** 350 * Return the parser's source 351 * 352 * @return string 353 */ 354 protected function getSource() 355 { 356 $rootDir = __DIR__ . '/..'; 357 $src = ''; 358 359 // If getLogger() is not exported we use a dummy Logger that can be optimized away 360 $logger = (in_array('getLogger', $this->exports)) ? 'Logger.js' : 'NullLogger.js'; 361 362 // Prepare the list of files 363 $files = glob($rootDir . '/Parser/AttributeFilters/*.js'); 364 $files[] = $rootDir . '/Parser/utils.js'; 365 $files[] = $rootDir . '/Parser/FilterProcessing.js'; 366 $files[] = $rootDir . '/Parser/' . $logger; 367 $files[] = $rootDir . '/Parser/Tag.js'; 368 $files[] = $rootDir . '/Parser.js'; 369 370 // Append render.js if we export the preview method 371 if (in_array('preview', $this->exports, true)) 372 { 373 $files[] = $rootDir . '/render.js'; 374 $src .= '/** @const */ var xsl=' . $this->getStylesheet() . ";\n"; 375 } 376 377 $src .= implode("\n", array_map('file_get_contents', $files)); 378 379 return $src; 380 } 381 382 /** 383 * Return the JavaScript representation of the stylesheet 384 * 385 * @return string 386 */ 387 protected function getStylesheet() 388 { 389 return $this->stylesheetCompressor->encode($this->xsl); 390 } 391 392 /** 393 * Return the tags' config 394 * 395 * @return Dictionary 396 */ 397 protected function getTagsConfig() 398 { 399 // Prepare a Dictionary that will preserve tags' names 400 $tags = new Dictionary; 401 foreach ($this->config['tags'] as $tagName => $tagConfig) 402 { 403 if (isset($tagConfig['attributes'])) 404 { 405 // Make the attributes array a Dictionary, to preserve the attributes' names 406 $tagConfig['attributes'] = new Dictionary($tagConfig['attributes']); 407 } 408 409 $tags[$tagName] = $tagConfig; 410 } 411 412 return $tags; 413 } 414 415 /** 416 * Inject the parser config into given source 417 * 418 * @param string $src Parser's source 419 * @return string Modified source 420 */ 421 protected function injectConfig($src) 422 { 423 $config = array_map( 424 [$this, 'encode'], 425 $this->configOptimizer->optimize( 426 [ 427 'plugins' => $this->getPluginsConfig(), 428 'registeredVars' => $this->getRegisteredVarsConfig(), 429 'rootContext' => $this->getRootContext(), 430 'tagsConfig' => $this->getTagsConfig() 431 ] 432 ) 433 ); 434 435 $src = preg_replace_callback( 436 '/(\\nvar (' . implode('|', array_keys($config)) . '))(;)/', 437 function ($m) use ($config) 438 { 439 return $m[1] . '=' . $config[$m[2]] . $m[3]; 440 }, 441 $src 442 ); 443 444 // Prepend the deduplicated objects 445 $src = $this->configOptimizer->getVarDeclarations() . $src; 446 447 return $src; 448 } 449 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Mar 24 21:31:15 2022 | Cross-referenced by PHPXref 0.7.1 |