[ Index ]

PHP Cross Reference of phpBB-3.3.11-deutsch

title

Body

[close]

/vendor/s9e/text-formatter/src/Configurator/ -> JavaScript.php (source)

   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  }


Generated: Sat Nov 4 14:26:03 2023 Cross-referenced by PHPXref 0.7.1