[ Index ]

PHP Cross Reference of phpBB-3.3.3-deutsch

title

Body

[close]

/phpbb/textformatter/s9e/ -> factory.php (source)

   1  <?php
   2  /**
   3  *
   4  * This file is part of the phpBB Forum Software package.
   5  *
   6  * @copyright (c) phpBB Limited <https://www.phpbb.com>
   7  * @license GNU General Public License, version 2 (GPL-2.0)
   8  *
   9  * For full copyright and license information, please see
  10  * the docs/CREDITS.txt file.
  11  *
  12  */
  13  
  14  namespace phpbb\textformatter\s9e;
  15  
  16  use s9e\TextFormatter\Configurator;
  17  use s9e\TextFormatter\Configurator\Items\AttributeFilters\RegexpFilter;
  18  use s9e\TextFormatter\Configurator\Items\UnsafeTemplate;
  19  
  20  /**
  21  * Creates s9e\TextFormatter objects
  22  */
  23  class factory implements \phpbb\textformatter\cache_interface
  24  {
  25      /**
  26      * @var \phpbb\textformatter\s9e\link_helper
  27      */
  28      protected $link_helper;
  29  
  30      /**
  31      * @var \phpbb\cache\driver\driver_interface
  32      */
  33      protected $cache;
  34  
  35      /**
  36      * @var string Path to the cache dir
  37      */
  38      protected $cache_dir;
  39  
  40      /**
  41      * @var string Cache key used for the parser
  42      */
  43      protected $cache_key_parser;
  44  
  45      /**
  46      * @var string Cache key used for the renderer
  47      */
  48      protected $cache_key_renderer;
  49  
  50      /**
  51      * @var \phpbb\config\config
  52      */
  53      protected $config;
  54  
  55      /**
  56      * @var array Custom tokens used in bbcode.html and their corresponding token from the definition
  57      */
  58      protected $custom_tokens = array(
  59          'email' => array('{DESCRIPTION}' => '{TEXT}'),
  60          'flash' => array('{WIDTH}' => '{NUMBER1}', '{HEIGHT}' => '{NUMBER2}'),
  61          'img'   => array('{URL}' => '{IMAGEURL}'),
  62          'list'  => array('{LIST_TYPE}' => '{HASHMAP}'),
  63          'quote' => array('{USERNAME}' => '{TEXT1}'),
  64          'size'  => array('{SIZE}' => '{FONTSIZE}'),
  65          'url'   => array('{DESCRIPTION}' => '{TEXT}'),
  66      );
  67  
  68      /**
  69      * @var \phpbb\textformatter\data_access
  70      */
  71      protected $data_access;
  72  
  73      /**
  74      * @var array Default BBCode definitions
  75      */
  76      protected $default_definitions = array(
  77          'attachment' => '[ATTACHMENT index={NUMBER} filename={TEXT;useContent}]',
  78          'b'     => '[B]{TEXT}[/B]',
  79          'code'  => '[CODE lang={IDENTIFIER;optional}]{TEXT}[/CODE]',
  80          'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]',
  81          'email' => '[EMAIL={EMAIL;useContent} subject={TEXT1;optional;postFilter=rawurlencode} body={TEXT2;optional;postFilter=rawurlencode}]{TEXT}[/EMAIL]',
  82          'flash' => '[FLASH={NUMBER1},{NUMBER2} width={NUMBER1;postFilter=#flashwidth} height={NUMBER2;postFilter=#flashheight} url={URL;useContent} /]',
  83          'i'     => '[I]{TEXT}[/I]',
  84          'img'   => '[IMG src={IMAGEURL;useContent}]',
  85          'list'  => '[LIST type={HASHMAP=1:decimal,a:lower-alpha,A:upper-alpha,i:lower-roman,I:upper-roman;optional;postFilter=#simpletext} #createChild=LI]{TEXT}[/LIST]',
  86          'li'    => '[* $tagName=LI]{TEXT}[/*]',
  87          'quote' =>
  88              "[QUOTE
  89                  author={TEXT1;optional}
  90                  post_id={UINT;optional}
  91                  post_url={URL;optional;postFilter=#false}
  92                  msg_id={UINT;optional}
  93                  msg_url={URL;optional;postFilter=#false}
  94                  profile_url={URL;optional;postFilter=#false}
  95                  time={UINT;optional}
  96                  url={URL;optional}
  97                  user_id={UINT;optional}
  98                  author={PARSE=/^\\[url=(?'url'.*?)](?'author'.*)\\[\\/url]$/i}
  99                  author={PARSE=/^\\[url](?'author'(?'url'.*?))\\[\\/url]$/i}
 100                  author={PARSE=/(?'url'https?:\\/\\/[^[\\]]+)/i}
 101              ]{TEXT2}[/QUOTE]",
 102          'size'  => '[SIZE={FONTSIZE}]{TEXT}[/SIZE]',
 103          'u'     => '[U]{TEXT}[/U]',
 104          'url'   => '[URL={URL;useContent} $forceLookahead=true]{TEXT}[/URL]',
 105      );
 106  
 107      /**
 108      * @var array Default templates, taken from bbcode::bbcode_tpl()
 109      */
 110      protected $default_templates = array(
 111          'b'     => '<span style="font-weight: bold"><xsl:apply-templates/></span>',
 112          'i'     => '<span style="font-style: italic"><xsl:apply-templates/></span>',
 113          'u'     => '<span style="text-decoration: underline"><xsl:apply-templates/></span>',
 114          'img'   => '<img src="{IMAGEURL}" class="postimage" alt="{L_IMAGE}"/>',
 115          'size'    => '<span><xsl:attribute name="style"><xsl:text>font-size: </xsl:text><xsl:value-of select="substring(@size, 1, 4)"/><xsl:text>%; line-height: normal</xsl:text></xsl:attribute><xsl:apply-templates/></span>',
 116          'color' => '<span style="color: {COLOR}"><xsl:apply-templates/></span>',
 117          'email' => '<a>
 118              <xsl:attribute name="href">
 119                  <xsl:text>mailto:</xsl:text>
 120                  <xsl:value-of select="@email"/>
 121                  <xsl:if test="@subject or @body">
 122                      <xsl:text>?</xsl:text>
 123                      <xsl:if test="@subject">subject=<xsl:value-of select="@subject"/></xsl:if>
 124                      <xsl:if test="@body"><xsl:if test="@subject">&amp;</xsl:if>body=<xsl:value-of select="@body"/></xsl:if>
 125                  </xsl:if>
 126              </xsl:attribute>
 127              <xsl:apply-templates/>
 128          </a>',
 129      );
 130  
 131      /**
 132      * @var \phpbb\event\dispatcher_interface
 133      */
 134      protected $dispatcher;
 135  
 136      /**
 137      * @var \phpbb\log\log_interface
 138      */
 139      protected $log;
 140  
 141      /**
 142      * Constructor
 143      *
 144      * @param \phpbb\textformatter\data_access $data_access
 145      * @param \phpbb\cache\driver\driver_interface $cache
 146      * @param \phpbb\event\dispatcher_interface $dispatcher
 147      * @param \phpbb\config\config $config
 148      * @param \phpbb\textformatter\s9e\link_helper $link_helper
 149      * @param \phpbb\log\log_interface $log
 150      * @param string $cache_dir          Path to the cache dir
 151      * @param string $cache_key_parser   Cache key used for the parser
 152      * @param string $cache_key_renderer Cache key used for the renderer
 153      */
 154  	public function __construct(\phpbb\textformatter\data_access $data_access, \phpbb\cache\driver\driver_interface $cache, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\config\config $config, \phpbb\textformatter\s9e\link_helper $link_helper, \phpbb\log\log_interface $log, $cache_dir, $cache_key_parser, $cache_key_renderer)
 155      {
 156          $this->link_helper = $link_helper;
 157          $this->cache = $cache;
 158          $this->cache_dir = $cache_dir;
 159          $this->cache_key_parser = $cache_key_parser;
 160          $this->cache_key_renderer = $cache_key_renderer;
 161          $this->config = $config;
 162          $this->data_access = $data_access;
 163          $this->dispatcher = $dispatcher;
 164          $this->log = $log;
 165      }
 166  
 167      /**
 168      * {@inheritdoc}
 169      */
 170  	public function invalidate()
 171      {
 172          $this->regenerate();
 173      }
 174  
 175      /**
 176      * {@inheritdoc}
 177      *
 178      * Will remove old renderers from the cache dir but won't touch the current renderer
 179      */
 180  	public function tidy()
 181      {
 182          // Get the name of current renderer
 183          $renderer_data = $this->cache->get($this->cache_key_renderer);
 184          $renderer_file = ($renderer_data) ? $renderer_data['class'] . '.php' : null;
 185  
 186          foreach (glob($this->cache_dir . 's9e_*') as $filename)
 187          {
 188              // Only remove the file if it's not the current renderer
 189              if (!$renderer_file || substr($filename, -strlen($renderer_file)) !== $renderer_file)
 190              {
 191                  unlink($filename);
 192              }
 193          }
 194      }
 195  
 196      /**
 197      * Generate and return a new configured instance of s9e\TextFormatter\Configurator
 198      *
 199      * @return Configurator
 200      */
 201  	public function get_configurator()
 202      {
 203          // Create a new Configurator
 204          $configurator = new Configurator;
 205  
 206          /**
 207          * Modify the s9e\TextFormatter configurator before the default settings are set
 208          *
 209          * @event core.text_formatter_s9e_configure_before
 210          * @var \s9e\TextFormatter\Configurator configurator Configurator instance
 211          * @since 3.2.0-a1
 212          */
 213          $vars = array('configurator');
 214          extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_before', compact($vars)));
 215  
 216          // Reset the list of allowed schemes
 217          foreach ($configurator->urlConfig->getAllowedSchemes() as $scheme)
 218          {
 219              $configurator->urlConfig->disallowScheme($scheme);
 220          }
 221          foreach (array_filter(explode(',', $this->config['allowed_schemes_links'])) as $scheme)
 222          {
 223              $configurator->urlConfig->allowScheme(trim($scheme));
 224          }
 225  
 226          // Convert newlines to br elements by default
 227          $configurator->rootRules->enableAutoLineBreaks();
 228  
 229          // Don't automatically ignore text in places where text is not allowed
 230          $configurator->rulesGenerator->remove('IgnoreTextIfDisallowed');
 231  
 232          // Don't remove comments and instead convert them to xsl:comment elements
 233          $configurator->templateNormalizer->remove('RemoveComments');
 234          $configurator->templateNormalizer->add('TransposeComments');
 235  
 236          // Set the rendering engine and configure it to save to the cache dir
 237          $configurator->rendering->engine = 'PHP';
 238          $configurator->rendering->engine->cacheDir = $this->cache_dir;
 239          $configurator->rendering->engine->defaultClassPrefix = 's9e_renderer_';
 240          $configurator->rendering->engine->enableQuickRenderer = true;
 241  
 242          // Create custom filters for BBCode tokens that are supported in phpBB but not in
 243          // s9e\TextFormatter
 244          $filter = new RegexpFilter('#^' . get_preg_expression('relative_url') . '$#Du');
 245          $configurator->attributeFilters->add('#local_url', $filter);
 246          $configurator->attributeFilters->add('#relative_url', $filter);
 247  
 248          // INTTEXT regexp from acp_bbcodes
 249          $filter = new RegexpFilter('!^([\p{L}\p{N}\-+,_. ]+)$!Du');
 250          $configurator->attributeFilters->add('#inttext', $filter);
 251  
 252          // Create custom filters for Flash restrictions, which use the same values as the image
 253          // restrictions but have their own error message
 254          $configurator->attributeFilters
 255              ->add('#flashheight', __NAMESPACE__ . '\\parser::filter_flash_height')
 256              ->addParameterByName('max_img_height')
 257              ->addParameterByName('logger');
 258  
 259          $configurator->attributeFilters
 260              ->add('#flashwidth', __NAMESPACE__ . '\\parser::filter_flash_width')
 261              ->addParameterByName('max_img_width')
 262              ->addParameterByName('logger');
 263  
 264          // Create a custom filter for phpBB's per-mode font size limits
 265          $configurator->attributeFilters
 266              ->add('#fontsize', __NAMESPACE__ . '\\parser::filter_font_size')
 267              ->addParameterByName('max_font_size')
 268              ->addParameterByName('logger')
 269              ->markAsSafeInCSS();
 270  
 271          // Create a custom filter for image URLs
 272          $configurator->attributeFilters
 273              ->add('#imageurl', __NAMESPACE__ . '\\parser::filter_img_url')
 274              ->addParameterByName('urlConfig')
 275              ->addParameterByName('logger')
 276              ->markAsSafeAsURL()
 277              ->setJS('UrlFilter.filter');
 278  
 279          // Add default BBCodes
 280          foreach ($this->get_default_bbcodes($configurator) as $bbcode)
 281          {
 282              $this->add_bbcode($configurator, $bbcode['usage'], $bbcode['template']);
 283          }
 284          if (isset($configurator->tags['QUOTE']))
 285          {
 286              // Remove the nesting limit and let other services remove quotes at parsing time
 287              $configurator->tags['QUOTE']->nestingLimit = PHP_INT_MAX;
 288          }
 289  
 290          // Modify the template to disable images/flash depending on user's settings
 291          foreach (array('FLASH', 'IMG') as $name)
 292          {
 293              $tag = $configurator->tags[$name];
 294              $tag->template = '<xsl:choose><xsl:when test="$S_VIEW' . $name . '">' . $tag->template . '</xsl:when><xsl:otherwise><xsl:apply-templates/></xsl:otherwise></xsl:choose>';
 295          }
 296  
 297          // Load custom BBCodes
 298          foreach ($this->data_access->get_bbcodes() as $row)
 299          {
 300              // Insert the board's URL before {LOCAL_URL} tokens
 301              $tpl = preg_replace_callback(
 302                  '#\\{LOCAL_URL\\d*\\}#',
 303                  function ($m)
 304                  {
 305                      return generate_board_url() . '/' . $m[0];
 306                  },
 307                  $row['bbcode_tpl']
 308              );
 309              $this->add_bbcode($configurator, $row['bbcode_match'], $tpl);
 310          }
 311  
 312          // Load smilies
 313          foreach ($this->data_access->get_smilies() as $row)
 314          {
 315              $configurator->Emoticons->set(
 316                  $row['code'],
 317                  '<img class="smilies" src="{$T_SMILIES_PATH}/' . $this->escape_html_attribute($row['smiley_url']) . '" width="' . $row['smiley_width'] . '" height="' . $row['smiley_height'] . '" alt="{.}" title="' . $this->escape_html_attribute($row['emotion']) . '"/>'
 318              );
 319          }
 320  
 321          if (isset($configurator->Emoticons))
 322          {
 323              // Force emoticons to be rendered as text if $S_VIEWSMILIES is not set
 324              $configurator->Emoticons->notIfCondition = 'not($S_VIEWSMILIES)';
 325  
 326              // Only parse emoticons at the beginning of the text or if they're preceded by any
 327              // one of: a new line, a space, a dot, or a right square bracket
 328              $configurator->Emoticons->notAfter = '[^\\n .\\]]';
 329  
 330              // Ignore emoticons that are immediately followed by a "word" character
 331              $configurator->Emoticons->notBefore = '\\w';
 332          }
 333  
 334          // Load the censored words
 335          $censor = $this->data_access->get_censored_words();
 336          if (!empty($censor))
 337          {
 338              // Use a namespaced tag to avoid collisions
 339              $configurator->plugins->load('Censor', array('tagName' => 'censor:tag'));
 340              foreach ($censor as $row)
 341              {
 342                  $configurator->Censor->add($row['word'], $row['replacement']);
 343              }
 344          }
 345  
 346          // Load the magic links plugins. We do that after BBCodes so that they use the same tags
 347          $this->configure_autolink($configurator);
 348  
 349          // Register some vars with a default value. Those should be set at runtime by whatever calls
 350          // the parser
 351          $configurator->registeredVars['max_font_size'] = 0;
 352          $configurator->registeredVars['max_img_height'] = 0;
 353          $configurator->registeredVars['max_img_width'] = 0;
 354  
 355          // Load the Emoji plugin and modify its tag's template to obey viewsmilies
 356          $tag = $configurator->Emoji->getTag();
 357          $tag->template = '<xsl:choose>
 358              <xsl:when test="@tseq">
 359                  <img alt="{.}" class="emoji" draggable="false" src="//twemoji.maxcdn.com/2/svg/{@tseq}.svg"/>
 360              </xsl:when>
 361              <xsl:otherwise>
 362                  <img alt="{.}" class="emoji" draggable="false" src="https://cdn.jsdelivr.net/gh/s9e/emoji-assets-twemoji@11.2/dist/svgz/{@seq}.svgz"/>
 363              </xsl:otherwise>
 364          </xsl:choose>';
 365          $tag->template = '<xsl:choose><xsl:when test="$S_VIEWSMILIES">' . str_replace('class="emoji"', 'class="emoji smilies"', $tag->template) . '</xsl:when><xsl:otherwise><xsl:value-of select="."/></xsl:otherwise></xsl:choose>';
 366  
 367          /**
 368          * Modify the s9e\TextFormatter configurator after the default settings are set
 369          *
 370          * @event core.text_formatter_s9e_configure_after
 371          * @var \s9e\TextFormatter\Configurator configurator Configurator instance
 372          * @since 3.2.0-a1
 373          */
 374          $vars = array('configurator');
 375          extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_after', compact($vars)));
 376  
 377          return $configurator;
 378      }
 379  
 380      /**
 381      * Regenerate and cache a new parser and renderer
 382      *
 383      * @return array Associative array with at least two elements: "parser" and "renderer"
 384      */
 385  	public function regenerate()
 386      {
 387          $configurator = $this->get_configurator();
 388  
 389          // Get the censor helper and remove the Censor plugin if applicable
 390          if (isset($configurator->Censor))
 391          {
 392              $censor = $configurator->Censor->getHelper();
 393              unset($configurator->Censor);
 394              unset($configurator->tags['censor:tag']);
 395          }
 396  
 397          $objects = $configurator->finalize();
 398  
 399          /**
 400          * Access the objects returned by finalize() before they are saved to cache
 401          *
 402          * @event core.text_formatter_s9e_configure_finalize
 403          * @var array objects Array containing a "parser" object, a "renderer" object and optionally a "js" string
 404          * @since 3.2.2-RC1
 405          */
 406          $vars = array('objects');
 407          extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_finalize', compact($vars)));
 408  
 409          $parser   = $objects['parser'];
 410          $renderer = $objects['renderer'];
 411  
 412          // Cache the parser as-is
 413          $this->cache->put($this->cache_key_parser, $parser);
 414  
 415          // We need to cache the name of the renderer's generated class
 416          $renderer_data = array('class' => get_class($renderer));
 417          if (isset($censor))
 418          {
 419              $renderer_data['censor'] = $censor;
 420          }
 421          $this->cache->put($this->cache_key_renderer, $renderer_data);
 422  
 423          return array('parser' => $parser, 'renderer' => $renderer);
 424      }
 425  
 426      /**
 427      * Add a BBCode to given configurator
 428      *
 429      * @param  Configurator $configurator
 430      * @param  string       $usage
 431      * @param  string       $template
 432      * @return void
 433      */
 434  	protected function add_bbcode(Configurator $configurator, $usage, $template)
 435      {
 436          try
 437          {
 438              $configurator->BBCodes->addCustom($usage, new UnsafeTemplate($template));
 439          }
 440          catch (\Exception $e)
 441          {
 442              $this->log->add('critical', null, null, 'LOG_BBCODE_CONFIGURATION_ERROR', false, [$usage, $e->getMessage()]);
 443          }
 444      }
 445  
 446      /**
 447      * Configure the Autolink / Autoemail plugins used to linkify text
 448      *
 449      * @param  \s9e\TextFormatter\Configurator $configurator
 450      * @return void
 451      */
 452  	protected function configure_autolink(Configurator $configurator)
 453      {
 454          $configurator->plugins->load('Autoemail');
 455          $configurator->plugins->load('Autolink', array('matchWww' => true));
 456  
 457          // Add a tag filter that creates a tag that stores and replace the
 458          // content of a link created by the Autolink plugin
 459          $configurator->Autolink->getTag()->filterChain
 460              ->add(array($this->link_helper, 'generate_link_text_tag'))
 461              ->resetParameters()
 462              ->addParameterByName('tag')
 463              ->addParameterByName('parser');
 464  
 465          // Create a tag that will be used to display the truncated text by
 466          // replacing the original content with the content of the @text attribute
 467          $tag = $configurator->tags->add('LINK_TEXT');
 468          $tag->attributes->add('text');
 469          $tag->template = '<xsl:value-of select="@text"/>';
 470  
 471          $board_url = generate_board_url() . '/';
 472          $tag->filterChain
 473              ->add(array($this->link_helper, 'truncate_local_url'))
 474              ->resetParameters()
 475              ->addParameterByName('tag')
 476              ->addParameterByValue($board_url);
 477          $tag->filterChain
 478              ->add(array($this->link_helper, 'truncate_local_url'))
 479              ->resetParameters()
 480              ->addParameterByName('tag')
 481              ->addParameterByValue(preg_replace('(^\\w+:)', '', $board_url));
 482          $tag->filterChain
 483              ->add(array($this->link_helper, 'truncate_text'))
 484              ->resetParameters()
 485              ->addParameterByName('tag');
 486          $tag->filterChain
 487              ->add(array($this->link_helper, 'cleanup_tag'))
 488              ->resetParameters()
 489              ->addParameterByName('tag')
 490              ->addParameterByName('parser');
 491      }
 492  
 493      /**
 494      * Escape a literal to be used in an HTML attribute in an XSL template
 495      *
 496      * Escapes "HTML special chars" for obvious reasons and curly braces to avoid them
 497      * being interpreted as an attribute value template
 498      *
 499      * @param  string $value Original string
 500      * @return string        Escaped string
 501      */
 502  	protected function escape_html_attribute($value)
 503      {
 504          return htmlspecialchars(strtr($value, ['{' => '{{', '}' => '}}']), ENT_COMPAT | ENT_XML1, 'UTF-8');
 505      }
 506  
 507      /**
 508      * Return the default BBCodes configuration
 509      *
 510      * @return array 2D array. Each element has a 'usage' key, a 'template' key, and an optional 'options' key
 511      */
 512  	protected function get_default_bbcodes($configurator)
 513      {
 514          // For each BBCode, build an associative array matching style_ids to their template
 515          $templates = array();
 516          foreach ($this->data_access->get_styles_templates() as $style_id => $data)
 517          {
 518              foreach ($this->extract_templates($data['template']) as $bbcode_name => $template)
 519              {
 520                  $templates[$bbcode_name][$style_id] = $template;
 521              }
 522  
 523              // Add default templates wherever missing, or for BBCodes that were not specified in
 524              // this template's bitfield. For instance, prosilver has a custom template for b but its
 525              // bitfield does not enable it so the default template is used instead
 526              foreach ($this->default_templates as $bbcode_name => $template)
 527              {
 528                  if (!isset($templates[$bbcode_name][$style_id]) || !in_array($bbcode_name, $data['bbcodes'], true))
 529                  {
 530                      $templates[$bbcode_name][$style_id] = $template;
 531                  }
 532              }
 533          }
 534  
 535          // Replace custom tokens and normalize templates
 536          foreach ($templates as $bbcode_name => $style_templates)
 537          {
 538              foreach ($style_templates as $i => $template)
 539              {
 540                  if (isset($this->custom_tokens[$bbcode_name]))
 541                  {
 542                      $template = strtr($template, $this->custom_tokens[$bbcode_name]);
 543                  }
 544  
 545                  $templates[$bbcode_name][$i] = $configurator->templateNormalizer->normalizeTemplate($template);
 546              }
 547          }
 548  
 549          $bbcodes = array();
 550          foreach ($this->default_definitions as $bbcode_name => $usage)
 551          {
 552              $bbcodes[$bbcode_name] = array(
 553                  'usage'    => $usage,
 554                  'template' => $this->merge_templates($templates[$bbcode_name]),
 555              );
 556          }
 557  
 558          return $bbcodes;
 559      }
 560  
 561      /**
 562      * Extract and recompose individual BBCode templates from a style's template file
 563      *
 564      * @param  string $template Style template (bbcode.html)
 565      * @return array Associative array matching BBCode names to their template
 566      */
 567  	protected function extract_templates($template)
 568      {
 569          // Capture the template fragments
 570          // Allow either phpBB template or the Twig syntax
 571          preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END .*? -->#s', $template, $matches, PREG_SET_ORDER) ?:
 572              preg_match_all('#{% for (.*?) in .*? %}(.*?){% endfor %}#s', $template, $matches, PREG_SET_ORDER);
 573  
 574          $fragments = array();
 575          foreach ($matches as $match)
 576          {
 577              // Normalize the whitespace
 578              $fragment = preg_replace('#>\\n\\t*<#', '><', trim($match[2]));
 579  
 580              $fragments[$match[1]] = $fragment;
 581          }
 582  
 583          // Automatically recompose templates split between *_open and *_close
 584          foreach ($fragments as $fragment_name => $fragment)
 585          {
 586              if (preg_match('#^(\\w+)_close$#', $fragment_name, $match))
 587              {
 588                  $bbcode_name = $match[1];
 589  
 590                  if (isset($fragments[$bbcode_name . '_open']))
 591                  {
 592                      $templates[$bbcode_name] = $fragments[$bbcode_name . '_open'] . '<xsl:apply-templates/>' . $fragment;
 593                  }
 594              }
 595          }
 596  
 597          // Manually recompose and overwrite irregular templates
 598          $templates['list'] =
 599              '<xsl:choose>
 600                  <xsl:when test="not(@type)">
 601                      ' . $fragments['ulist_open_default'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . '
 602                  </xsl:when>
 603                  <xsl:when test="contains(\'upperlowerdecim\',substring(@type,1,5))">
 604                      ' . $fragments['olist_open'] . '<xsl:apply-templates/>' . $fragments['olist_close'] . '
 605                  </xsl:when>
 606                  <xsl:otherwise>
 607                      ' . $fragments['ulist_open'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . '
 608                  </xsl:otherwise>
 609              </xsl:choose>';
 610  
 611          $templates['li'] = $fragments['listitem'] . '<xsl:apply-templates/>' . $fragments['listitem_close'];
 612  
 613          // Replace the regular quote template with the extended quote template if available
 614          if (isset($fragments['quote_extended']))
 615          {
 616              $templates['quote'] = $fragments['quote_extended'];
 617          }
 618  
 619          // The [attachment] BBCode uses the inline_attachment template to output a comment that
 620          // is post-processed by parse_attachments()
 621          $templates['attachment'] = $fragments['inline_attachment_open'] . '<xsl:comment> ia<xsl:value-of select="@index"/> </xsl:comment><xsl:value-of select="@filename"/><xsl:comment> ia<xsl:value-of select="@index"/> </xsl:comment>' . $fragments['inline_attachment_close'];
 622  
 623          // Add fragments as templates
 624          foreach ($fragments as $fragment_name => $fragment)
 625          {
 626              if (preg_match('#^\\w+$#', $fragment_name))
 627              {
 628                  $templates[$fragment_name] = $fragment;
 629              }
 630          }
 631  
 632          // Keep only templates that are named after an existing BBCode
 633          $templates = array_intersect_key($templates, $this->default_definitions);
 634  
 635          return $templates;
 636      }
 637  
 638      /**
 639      * Merge the templates from any number of styles into one BBCode template
 640      *
 641      * When multiple templates are available for the same BBCode (because of multiple styles) we
 642      * merge them into a single template that uses an xsl:choose construct that determines which
 643      * style to use at rendering time.
 644      *
 645      * @param  array  $style_templates Associative array matching style_ids to their template
 646      * @return string
 647      */
 648  	protected function merge_templates(array $style_templates)
 649      {
 650          // Return the template as-is if there's only one style or all styles share the same template
 651          if (count(array_unique($style_templates)) === 1)
 652          {
 653              return end($style_templates);
 654          }
 655  
 656          // Group identical templates together
 657          $grouped_templates = array();
 658          foreach ($style_templates as $style_id => $style_template)
 659          {
 660              $grouped_templates[$style_template][] = '$STYLE_ID=' . $style_id;
 661          }
 662  
 663          // Sort templates by frequency descending
 664          $templates_cnt = array_map('sizeof', $grouped_templates);
 665          array_multisort($grouped_templates, $templates_cnt);
 666  
 667          // Remove the most frequent template from the list; It becomes the default
 668          reset($grouped_templates);
 669          $default_template = key($grouped_templates);
 670          unset($grouped_templates[$default_template]);
 671  
 672          // Build an xsl:choose switch
 673          $template = '<xsl:choose>';
 674          foreach ($grouped_templates as $style_template => $exprs)
 675          {
 676              $template .= '<xsl:when test="' . implode(' or ', $exprs) . '">' . $style_template . '</xsl:when>';
 677          }
 678          $template .= '<xsl:otherwise>' . $default_template . '</xsl:otherwise></xsl:choose>';
 679  
 680          return $template;
 681      }
 682  }


Generated: Sun Feb 14 20:08:31 2021 Cross-referenced by PHPXref 0.7.1