Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

factory.php

Zuletzt modifiziert: 09.10.2024, 12:54 - Dateigröße: 20.69 KiB


 001  <?php
 002  /**
 003  *
 004  * This file is part of the phpBB Forum Software package.
 005  *
 006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
 007  * @license GNU General Public License, version 2 (GPL-2.0)
 008  *
 009  * For full copyright and license information, please see
 010  * the docs/CREDITS.txt file.
 011  *
 012  */
 013   
 014  namespace phpbb\textformatter\s9e;
 015   
 016  use s9e\TextFormatter\Configurator;
 017  use s9e\TextFormatter\Configurator\Items\AttributeFilters\RegexpFilter;
 018  use s9e\TextFormatter\Configurator\Items\UnsafeTemplate;
 019   
 020  /**
 021  * Creates s9e\TextFormatter objects
 022  */
 023  class factory implements \phpbb\textformatter\cache_interface
 024  {
 025      /**
 026      * @var \phpbb\textformatter\s9e\link_helper
 027      */
 028      protected $link_helper;
 029   
 030      /**
 031      * @var \phpbb\cache\driver\driver_interface
 032      */
 033      protected $cache;
 034   
 035      /**
 036      * @var string Path to the cache dir
 037      */
 038      protected $cache_dir;
 039   
 040      /**
 041      * @var string Cache key used for the parser
 042      */
 043      protected $cache_key_parser;
 044   
 045      /**
 046      * @var string Cache key used for the renderer
 047      */
 048      protected $cache_key_renderer;
 049   
 050      /**
 051      * @var \phpbb\config\config
 052      */
 053      protected $config;
 054   
 055      /**
 056      * @var array Custom tokens used in bbcode.html and their corresponding token from the definition
 057      */
 058      protected $custom_tokens = array(
 059          'email' => array('{DESCRIPTION}' => '{TEXT}'),
 060          'flash' => array('{WIDTH}' => '{NUMBER1}', '{HEIGHT}' => '{NUMBER2}'),
 061          'img'   => array('{URL}' => '{IMAGEURL}'),
 062          'list'  => array('{LIST_TYPE}' => '{HASHMAP}'),
 063          'quote' => array('{USERNAME}' => '{TEXT1}'),
 064          'size'  => array('{SIZE}' => '{FONTSIZE}'),
 065          'url'   => array('{DESCRIPTION}' => '{TEXT}'),
 066      );
 067   
 068      /**
 069      * @var \phpbb\textformatter\data_access
 070      */
 071      protected $data_access;
 072   
 073      /**
 074      * @var array Default BBCode definitions
 075      */
 076      protected $default_definitions = array(
 077          'attachment' => '[ATTACHMENT index={NUMBER} filename={TEXT;useContent}]',
 078          'b'     => '[B]{TEXT}[/B]',
 079          'code'  => '[CODE lang={IDENTIFIER;optional}]{TEXT}[/CODE]',
 080          'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]',
 081          'email' => '[EMAIL={EMAIL;useContent} subject={TEXT;optional;postFilter=rawurlencode} body={TEXT;optional;postFilter=rawurlencode}]{TEXT}[/EMAIL]',
 082          'flash' => '[FLASH={NUMBER1},{NUMBER2} width={NUMBER1;postFilter=#flashwidth} height={NUMBER2;postFilter=#flashheight} url={URL;useContent} /]',
 083          'i'     => '[I]{TEXT}[/I]',
 084          'img'   => '[IMG src={IMAGEURL;useContent}]',
 085          '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]',
 086          'li'    => '[* $tagName=LI]{TEXT}[/*]',
 087          'quote' =>
 088              "[QUOTE
 089                  author={TEXT1;optional}
 090                  post_id={UINT;optional}
 091                  post_url={URL;optional;postFilter=#false}
 092                  profile_url={URL;optional;postFilter=#false}
 093                  time={UINT;optional}
 094                  url={URL;optional}
 095                  user_id={UINT;optional}
 096                  author={PARSE=/^\\[url=(?'url'.*?)](?'author'.*)\\[\\/url]$/i}
 097                  author={PARSE=/^\\[url](?'author'(?'url'.*?))\\[\\/url]$/i}
 098                  author={PARSE=/(?'url'https?:\\/\\/[^[\\]]+)/i}
 099              ]{TEXT2}[/QUOTE]",
 100          'size'  => '[SIZE={FONTSIZE}]{TEXT}[/SIZE]',
 101          'u'     => '[U]{TEXT}[/U]',
 102          'url'   => '[URL={URL;useContent} $forceLookahead=true]{TEXT}[/URL]',
 103      );
 104   
 105      /**
 106      * @var array Default templates, taken from bbcode::bbcode_tpl()
 107      */
 108      protected $default_templates = array(
 109          'b'     => '<span style="font-weight: bold"><xsl:apply-templates/></span>',
 110          'i'     => '<span style="font-style: italic"><xsl:apply-templates/></span>',
 111          'u'     => '<span style="text-decoration: underline"><xsl:apply-templates/></span>',
 112          'img'   => '<img src="{IMAGEURL}" class="postimage" alt="{L_IMAGE}"/>',
 113          'size'  => '<span style="font-size: {FONTSIZE}%; line-height: normal"><xsl:apply-templates/></span>',
 114          'color' => '<span style="color: {COLOR}"><xsl:apply-templates/></span>',
 115          'email' => '<a>
 116              <xsl:attribute name="href">
 117                  <xsl:text>mailto:</xsl:text>
 118                  <xsl:value-of select="@email"/>
 119                  <xsl:if test="@subject or @body">
 120                      <xsl:text>?</xsl:text>
 121                      <xsl:if test="@subject">subject=<xsl:value-of select="@subject"/></xsl:if>
 122                      <xsl:if test="@body"><xsl:if test="@subject">&amp;</xsl:if>body=<xsl:value-of select="@body"/></xsl:if>
 123                  </xsl:if>
 124              </xsl:attribute>
 125              <xsl:apply-templates/>
 126          </a>',
 127      );
 128   
 129      /**
 130      * @var \phpbb\event\dispatcher_interface
 131      */
 132      protected $dispatcher;
 133   
 134      /**
 135      * Constructor
 136      *
 137      * @param \phpbb\textformatter\data_access $data_access
 138      * @param \phpbb\cache\driver\driver_interface $cache
 139      * @param \phpbb\event\dispatcher_interface $dispatcher
 140      * @param \phpbb\config\config $config
 141      * @param \phpbb\textformatter\s9e\link_helper $link_helper
 142      * @param string $cache_dir          Path to the cache dir
 143      * @param string $cache_key_parser   Cache key used for the parser
 144      * @param string $cache_key_renderer Cache key used for the renderer
 145      */
 146      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, $cache_dir, $cache_key_parser, $cache_key_renderer)
 147      {
 148          $this->link_helper = $link_helper;
 149          $this->cache = $cache;
 150          $this->cache_dir = $cache_dir;
 151          $this->cache_key_parser = $cache_key_parser;
 152          $this->cache_key_renderer = $cache_key_renderer;
 153          $this->config = $config;
 154          $this->data_access = $data_access;
 155          $this->dispatcher = $dispatcher;
 156      }
 157   
 158      /**
 159      * {@inheritdoc}
 160      */
 161      public function invalidate()
 162      {
 163          $this->regenerate();
 164      }
 165   
 166      /**
 167      * {@inheritdoc}
 168      *
 169      * Will remove old renderers from the cache dir but won't touch the current renderer
 170      */
 171      public function tidy()
 172      {
 173          // Get the name of current renderer
 174          $renderer_data = $this->cache->get($this->cache_key_renderer);
 175          $renderer_file = ($renderer_data) ? $renderer_data['class'] . '.php' : null;
 176   
 177          foreach (glob($this->cache_dir . 's9e_*') as $filename)
 178          {
 179              // Only remove the file if it's not the current renderer
 180              if (!$renderer_file || substr($filename, -strlen($renderer_file)) !== $renderer_file)
 181              {
 182                  unlink($filename);
 183              }
 184          }
 185      }
 186   
 187      /**
 188      * Generate and return a new configured instance of s9e\TextFormatter\Configurator
 189      *
 190      * @return Configurator
 191      */
 192      public function get_configurator()
 193      {
 194          // Create a new Configurator
 195          $configurator = new Configurator;
 196   
 197          /**
 198          * Modify the s9e\TextFormatter configurator before the default settings are set
 199          *
 200          * @event core.text_formatter_s9e_configure_before
 201          * @var \s9e\TextFormatter\Configurator configurator Configurator instance
 202          * @since 3.2.0-a1
 203          */
 204          $vars = array('configurator');
 205          extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_before', compact($vars)));
 206   
 207          // Reset the list of allowed schemes
 208          foreach ($configurator->urlConfig->getAllowedSchemes() as $scheme)
 209          {
 210              $configurator->urlConfig->disallowScheme($scheme);
 211          }
 212          foreach (explode(',', $this->config['allowed_schemes_links']) as $scheme)
 213          {
 214              $configurator->urlConfig->allowScheme(trim($scheme));
 215          }
 216   
 217          // Convert newlines to br elements by default
 218          $configurator->rootRules->enableAutoLineBreaks();
 219   
 220          // Don't automatically ignore text in places where text is not allowed
 221          $configurator->rulesGenerator->remove('IgnoreTextIfDisallowed');
 222   
 223          // Don't remove comments and instead convert them to xsl:comment elements
 224          $configurator->templateNormalizer->remove('RemoveComments');
 225          $configurator->templateNormalizer->add('TransposeComments');
 226   
 227          // Set the rendering engine and configure it to save to the cache dir
 228          $configurator->rendering->engine = 'PHP';
 229          $configurator->rendering->engine->cacheDir = $this->cache_dir;
 230          $configurator->rendering->engine->defaultClassPrefix = 's9e_renderer_';
 231          $configurator->rendering->engine->enableQuickRenderer = true;
 232   
 233          // Create custom filters for BBCode tokens that are supported in phpBB but not in
 234          // s9e\TextFormatter
 235          $filter = new RegexpFilter('#^' . get_preg_expression('relative_url') . '$#Du');
 236          $configurator->attributeFilters->add('#local_url', $filter);
 237          $configurator->attributeFilters->add('#relative_url', $filter);
 238   
 239          // INTTEXT regexp from acp_bbcodes
 240          $filter = new RegexpFilter('!^([\p{L}\p{N}\-+,_. ]+)$!Du');
 241          $configurator->attributeFilters->add('#inttext', $filter);
 242   
 243          // Create custom filters for Flash restrictions, which use the same values as the image
 244          // restrictions but have their own error message
 245          $configurator->attributeFilters
 246              ->add('#flashheight', __NAMESPACE__ . '\\parser::filter_flash_height')
 247              ->addParameterByName('max_img_height')
 248              ->addParameterByName('logger');
 249   
 250          $configurator->attributeFilters
 251              ->add('#flashwidth', __NAMESPACE__ . '\\parser::filter_flash_width')
 252              ->addParameterByName('max_img_width')
 253              ->addParameterByName('logger');
 254   
 255          // Create a custom filter for phpBB's per-mode font size limits
 256          $configurator->attributeFilters
 257              ->add('#fontsize', __NAMESPACE__ . '\\parser::filter_font_size')
 258              ->addParameterByName('max_font_size')
 259              ->addParameterByName('logger')
 260              ->markAsSafeInCSS();
 261   
 262          // Create a custom filter for image URLs
 263          $configurator->attributeFilters
 264              ->add('#imageurl', __NAMESPACE__ . '\\parser::filter_img_url')
 265              ->addParameterByName('urlConfig')
 266              ->addParameterByName('logger')
 267              ->addParameterByName('max_img_height')
 268              ->addParameterByName('max_img_width')
 269              ->markAsSafeAsURL();
 270   
 271          // Add default BBCodes
 272          foreach ($this->get_default_bbcodes($configurator) as $bbcode)
 273          {
 274              $configurator->BBCodes->addCustom($bbcode['usage'], $bbcode['template']);
 275          }
 276   
 277          // Modify the template to disable images/flash depending on user's settings
 278          foreach (array('FLASH', 'IMG') as $name)
 279          {
 280              $tag = $configurator->tags[$name];
 281              $tag->template = '<xsl:choose><xsl:when test="$S_VIEW' . $name . '">' . $tag->template . '</xsl:when><xsl:otherwise><xsl:apply-templates/></xsl:otherwise></xsl:choose>';
 282          }
 283   
 284          // Load custom BBCodes
 285          foreach ($this->data_access->get_bbcodes() as $row)
 286          {
 287              // Insert the board's URL before {LOCAL_URL} tokens
 288              $tpl = preg_replace_callback(
 289                  '#\\{LOCAL_URL\\d*\\}#',
 290                  function ($m)
 291                  {
 292                      return generate_board_url() . '/' . $m[0];
 293                  },
 294                  $row['bbcode_tpl']
 295              );
 296   
 297              try
 298              {
 299                  $configurator->BBCodes->addCustom($row['bbcode_match'], new UnsafeTemplate($tpl));
 300              }
 301              catch (\Exception $e)
 302              {
 303                  /**
 304                  * @todo log an error?
 305                  */
 306              }
 307          }
 308   
 309          // Load smilies
 310          foreach ($this->data_access->get_smilies() as $row)
 311          {
 312              $configurator->Emoticons->set(
 313                  $row['code'],
 314                  '<img class="smilies" src="{$T_SMILIES_PATH}/' . htmlspecialchars($row['smiley_url']) . '" width="' . $row['smiley_width'] . '" height="' . $row['smiley_height'] . '" alt="{.}" title="' . htmlspecialchars($row['emotion']) . '"/>'
 315              );
 316          }
 317   
 318          if (isset($configurator->Emoticons))
 319          {
 320              // Force emoticons to be rendered as text if $S_VIEWSMILIES is not set
 321              $configurator->Emoticons->notIfCondition = 'not($S_VIEWSMILIES)';
 322   
 323              // Only parse emoticons at the beginning of the text or if they're preceded by any
 324              // one of: a new line, a space, a dot, or a right square bracket
 325              $configurator->Emoticons->notAfter = '[^\\n .\\]]';
 326          }
 327   
 328          // Load the censored words
 329          $censor = $this->data_access->get_censored_words();
 330          if (!empty($censor))
 331          {
 332              // Use a namespaced tag to avoid collisions
 333              $configurator->plugins->load('Censor', array('tagName' => 'censor:tag'));
 334              foreach ($censor as $row)
 335              {
 336                  // NOTE: words are stored as HTML, we need to decode them to plain text
 337                  $configurator->Censor->add(htmlspecialchars_decode($row['word']),  htmlspecialchars_decode($row['replacement']));
 338              }
 339          }
 340   
 341          // Load the magic links plugins. We do that after BBCodes so that they use the same tags
 342          $this->configure_autolink($configurator);
 343   
 344          // Register some vars with a default value. Those should be set at runtime by whatever calls
 345          // the parser
 346          $configurator->registeredVars['max_font_size'] = 0;
 347          $configurator->registeredVars['max_img_height'] = 0;
 348          $configurator->registeredVars['max_img_width'] = 0;
 349   
 350          // Load the Emoji plugin and modify its tag's template to obey viewsmilies
 351          $configurator->Emoji->omitImageSize();
 352          $configurator->Emoji->useSVG();
 353          $tag = $configurator->Emoji->getTag();
 354          $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>';
 355   
 356          /**
 357          * Modify the s9e\TextFormatter configurator after the default settings are set
 358          *
 359          * @event core.text_formatter_s9e_configure_after
 360          * @var \s9e\TextFormatter\Configurator configurator Configurator instance
 361          * @since 3.2.0-a1
 362          */
 363          $vars = array('configurator');
 364          extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_after', compact($vars)));
 365   
 366          return $configurator;
 367      }
 368   
 369      /**
 370      * Regenerate and cache a new parser and renderer
 371      *
 372      * @return array Associative array with at least two elements: "parser" and "renderer"
 373      */
 374      public function regenerate()
 375      {
 376          $configurator = $this->get_configurator();
 377   
 378          // Get the censor helper and remove the Censor plugin if applicable
 379          if (isset($configurator->Censor))
 380          {
 381              $censor = $configurator->Censor->getHelper();
 382              unset($configurator->Censor);
 383              unset($configurator->tags['censor:tag']);
 384          }
 385   
 386          $objects  = $configurator->finalize();
 387          $parser   = $objects['parser'];
 388          $renderer = $objects['renderer'];
 389   
 390          // Cache the parser as-is
 391          $this->cache->put($this->cache_key_parser, $parser);
 392   
 393          // We need to cache the name of the renderer's generated class
 394          $renderer_data = array('class' => get_class($renderer));
 395          if (isset($censor))
 396          {
 397              $renderer_data['censor'] = $censor;
 398          }
 399          $this->cache->put($this->cache_key_renderer, $renderer_data);
 400   
 401          return array('parser' => $parser, 'renderer' => $renderer);
 402      }
 403   
 404      /**
 405      * Configure the Autolink / Autoemail plugins used to linkify text
 406      *
 407      * @param  \s9e\TextFormatter\Configurator $configurator
 408      * @return void
 409      */
 410      protected function configure_autolink(Configurator $configurator)
 411      {
 412          $configurator->plugins->load('Autoemail');
 413          $configurator->plugins->load('Autolink', array('matchWww' => true));
 414   
 415          // Add a tag filter that creates a tag that stores and replace the
 416          // content of a link created by the Autolink plugin
 417          $configurator->Autolink->getTag()->filterChain
 418              ->add(array($this->link_helper, 'generate_link_text_tag'))
 419              ->resetParameters()
 420              ->addParameterByName('tag')
 421              ->addParameterByName('parser');
 422   
 423          // Create a tag that will be used to display the truncated text by
 424          // replacing the original content with the content of the @text attribute
 425          $tag = $configurator->tags->add('LINK_TEXT');
 426          $tag->attributes->add('text');
 427          $tag->template = '<xsl:value-of select="@text"/>';
 428   
 429          $tag->filterChain
 430              ->add(array($this->link_helper, 'truncate_local_url'))
 431              ->resetParameters()
 432              ->addParameterByName('tag')
 433              ->addParameterByValue(generate_board_url() . '/');
 434          $tag->filterChain
 435              ->add(array($this->link_helper, 'truncate_text'))
 436              ->resetParameters()
 437              ->addParameterByName('tag');
 438          $tag->filterChain
 439              ->add(array($this->link_helper, 'cleanup_tag'))
 440              ->resetParameters()
 441              ->addParameterByName('tag')
 442              ->addParameterByName('parser');
 443      }
 444   
 445      /**
 446      * Return the default BBCodes configuration
 447      *
 448      * @return array 2D array. Each element has a 'usage' key, a 'template' key, and an optional 'options' key
 449      */
 450      protected function get_default_bbcodes($configurator)
 451      {
 452          // For each BBCode, build an associative array matching style_ids to their template
 453          $templates = array();
 454          foreach ($this->data_access->get_styles_templates() as $style_id => $data)
 455          {
 456              foreach ($this->extract_templates($data['template']) as $bbcode_name => $template)
 457              {
 458                  $templates[$bbcode_name][$style_id] = $template;
 459              }
 460   
 461              // Add default templates wherever missing, or for BBCodes that were not specified in
 462              // this template's bitfield. For instance, prosilver has a custom template for b but its
 463              // bitfield does not enable it so the default template is used instead
 464              foreach ($this->default_templates as $bbcode_name => $template)
 465              {
 466                  if (!isset($templates[$bbcode_name][$style_id]) || !in_array($bbcode_name, $data['bbcodes'], true))
 467                  {
 468                      $templates[$bbcode_name][$style_id] = $template;
 469                  }
 470              }
 471          }
 472   
 473          // Replace custom tokens and normalize templates
 474          foreach ($templates as $bbcode_name => $style_templates)
 475          {
 476              foreach ($style_templates as $i => $template)
 477              {
 478                  if (isset($this->custom_tokens[$bbcode_name]))
 479                  {
 480                      $template = strtr($template, $this->custom_tokens[$bbcode_name]);
 481                  }
 482   
 483                  $templates[$bbcode_name][$i] = $configurator->templateNormalizer->normalizeTemplate($template);
 484              }
 485          }
 486   
 487          $bbcodes = array();
 488          foreach ($this->default_definitions as $bbcode_name => $usage)
 489          {
 490              $bbcodes[$bbcode_name] = array(
 491                  'usage'    => $usage,
 492                  'template' => $this->merge_templates($templates[$bbcode_name]),
 493              );
 494          }
 495   
 496          return $bbcodes;
 497      }
 498   
 499      /**
 500      * Extract and recompose individual BBCode templates from a style's template file
 501      *
 502      * @param  string $template Style template (bbcode.html)
 503      * @return array Associative array matching BBCode names to their template
 504      */
 505      protected function extract_templates($template)
 506      {
 507          // Capture the template fragments
 508          preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END .*? -->#s', $template, $matches, PREG_SET_ORDER);
 509   
 510          $fragments = array();
 511          foreach ($matches as $match)
 512          {
 513              // Normalize the whitespace
 514              $fragment = preg_replace('#>\\n\\t*<#', '><', trim($match[2]));
 515   
 516              $fragments[$match[1]] = $fragment;
 517          }
 518   
 519          // Automatically recompose templates split between *_open and *_close
 520          foreach ($fragments as $fragment_name => $fragment)
 521          {
 522              if (preg_match('#^(\\w+)_close$#', $fragment_name, $match))
 523              {
 524                  $bbcode_name = $match[1];
 525   
 526                  if (isset($fragments[$bbcode_name . '_open']))
 527                  {
 528                      $templates[$bbcode_name] = $fragments[$bbcode_name . '_open'] . '<xsl:apply-templates/>' . $fragment;
 529                  }
 530              }
 531          }
 532   
 533          // Manually recompose and overwrite irregular templates
 534          $templates['list'] =
 535              '<xsl:choose>
 536                  <xsl:when test="not(@type)">
 537                      ' . $fragments['ulist_open_default'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . '
 538                  </xsl:when>
 539                  <xsl:when test="contains(\'upperlowerdecim\',substring(@type,1,5))">
 540                      ' . $fragments['olist_open'] . '<xsl:apply-templates/>' . $fragments['olist_close'] . '
 541                  </xsl:when>
 542                  <xsl:otherwise>
 543                      ' . $fragments['ulist_open'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . '
 544                  </xsl:otherwise>
 545              </xsl:choose>';
 546   
 547          $templates['li'] = $fragments['listitem'] . '<xsl:apply-templates/>' . $fragments['listitem_close'];
 548   
 549          // Replace the regular quote template with the extended quote template if available
 550          if (isset($fragments['quote_extended']))
 551          {
 552              $templates['quote'] = $fragments['quote_extended'];
 553          }
 554   
 555          // The [attachment] BBCode uses the inline_attachment template to output a comment that
 556          // is post-processed by parse_attachments()
 557          $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'];
 558   
 559          // Add fragments as templates
 560          foreach ($fragments as $fragment_name => $fragment)
 561          {
 562              if (preg_match('#^\\w+$#', $fragment_name))
 563              {
 564                  $templates[$fragment_name] = $fragment;
 565              }
 566          }
 567   
 568          // Keep only templates that are named after an existing BBCode
 569          $templates = array_intersect_key($templates, $this->default_definitions);
 570   
 571          return $templates;
 572      }
 573   
 574      /**
 575      * Merge the templates from any number of styles into one BBCode template
 576      *
 577      * When multiple templates are available for the same BBCode (because of multiple styles) we
 578      * merge them into a single template that uses an xsl:choose construct that determines which
 579      * style to use at rendering time.
 580      *
 581      * @param  array  $style_templates Associative array matching style_ids to their template
 582      * @return string
 583      */
 584      protected function merge_templates(array $style_templates)
 585      {
 586          // Return the template as-is if there's only one style or all styles share the same template
 587          if (count(array_unique($style_templates)) === 1)
 588          {
 589              return end($style_templates);
 590          }
 591   
 592          // Group identical templates together
 593          $grouped_templates = array();
 594          foreach ($style_templates as $style_id => $style_template)
 595          {
 596              $grouped_templates[$style_template][] = '$STYLE_ID=' . $style_id;
 597          }
 598   
 599          // Sort templates by frequency descending
 600          $templates_cnt = array_map('sizeof', $grouped_templates);
 601          array_multisort($grouped_templates, $templates_cnt);
 602   
 603          // Remove the most frequent template from the list; It becomes the default
 604          reset($grouped_templates);
 605          $default_template = key($grouped_templates);
 606          unset($grouped_templates[$default_template]);
 607   
 608          // Build an xsl:choose switch
 609          $template = '<xsl:choose>';
 610          foreach ($grouped_templates as $style_template => $exprs)
 611          {
 612              $template .= '<xsl:when test="' . implode(' or ', $exprs) . '">' . $style_template . '</xsl:when>';
 613          }
 614          $template .= '<xsl:otherwise>' . $default_template . '</xsl:otherwise></xsl:choose>';
 615   
 616          return $template;
 617      }
 618  }
 619