PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,183 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
use phpbb\textformatter\s9e\factory;
use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
use s9e\TextFormatter\Configurator\Items\UnsafeTemplate;
class bbcode_merger
{
/**
* @var \s9e\TextFormatter\Configurator $configurator Configurator instance used to inspect BBCodes
*/
protected $configurator;
/**
* @param \phpbb\textformatter\s9e\factory $factory
*/
public function __construct(factory $factory)
{
$this->configurator = $factory->get_configurator();
}
/**
* Merge two BBCode definitions
*
* All of the arrays contain a "usage" element and a "template" element
*
* @throws InvalidArgumentException if a definition cannot be interpreted
* @throws RuntimeException if something unexpected occurs
*
* @param array $without BBCode definition without an attribute
* @param array $with BBCode definition with an attribute
* @return array Merged definition
*/
public function merge_bbcodes(array $without, array $with)
{
$without = $this->create_bbcode($without);
$with = $this->create_bbcode($with);
// Select the appropriate strategy for merging this BBCode
if ($this->is_content_bbcode($without, $with))
{
$merged = $this->merge_content_bbcode($without, $with);
}
else
{
$merged = $this->merge_optional_bbcode($without, $with);
}
$merged['template'] = $this->normalize_template($merged['template']);
return $merged;
}
/**
* Create a custom BBCode for inspection
*
* @param array $definition Original BBCode definition
* @return array Updated definition containing a BBCode object and a Tag
*/
protected function create_bbcode(array $definition)
{
$bbcode = $this->configurator->BBCodes->addCustom(
$definition['usage'],
new UnsafeTemplate($definition['template'])
);
$definition['bbcode'] = $bbcode;
$definition['tag'] = $this->configurator->tags[$bbcode->tagName];
return $definition;
}
/**
* Indent given template for readability
*
* @param string $template
* @return string
*/
protected function indent_template($template)
{
$dom = TemplateHelper::loadTemplate($template);
$dom->formatOutput = true;
$template = TemplateHelper::saveTemplate($dom);
// Remove the first level of indentation if the template starts with whitespace
if (preg_match('(^\\n +)', $template, $m))
{
$template = str_replace($m[0], "\n", $template);
}
return trim($template);
}
/**
* Test whether the two definitions form a "content"-style BBCode
*
* Such BBCodes include the [URL] BBCode, which uses its text content as
* attribute if none is provided
*
* @param array $without BBCode definition without an attribute
* @param array $with BBCode definition with an attribute
* @return array Merged definition
*/
protected function is_content_bbcode(array $without, array $with)
{
// Test whether we find the same non-TEXT token between "]" and "[" in the usage
// as between ">" and "<" in the template
return (preg_match('(\\]\\s*(\\{(?!TEXT)[^}]+\\})\\s*\\[)', $without['usage'], $m)
&& preg_match('(>[^<]*?' . preg_quote($m[1]) . '[^>]*?<)s', $without['template']));
}
/**
* Merge the two BBCode definitions of a "content"-style BBCode
*
* @param array $without BBCode definition without an attribute
* @param array $with BBCode definition with an attribute
* @return array Merged definition
*/
protected function merge_content_bbcode(array $without, array $with)
{
// Convert [X={X}] into [X={X;useContent}]
$usage = preg_replace('(\\})', ';useContent}', $with['usage'], 1);
// Use the template from the definition that uses an attribute
$template = $with['tag']->template;
return ['usage' => $usage, 'template' => $template];
}
/**
* Merge the two BBCode definitions of a BBCode with an optional argument
*
* Such BBCodes include the [QUOTE] BBCode, which takes an optional argument
* but otherwise does not behave differently
*
* @param array $without BBCode definition without an attribute
* @param array $with BBCode definition with an attribute
* @return array Merged definition
*/
protected function merge_optional_bbcode(array $without, array $with)
{
// Convert [X={X}] into [X={X?}]
$usage = preg_replace('(\\})', '?}', $with['usage'], 1);
// Build a template for both versions
$template = '<xsl:choose><xsl:when test="@' . $with['bbcode']->defaultAttribute . '">' . $with['tag']->template . '</xsl:when><xsl:otherwise>' . $without['tag']->template . '</xsl:otherwise></xsl:choose>';
return ['usage' => $usage, 'template' => $template];
}
/**
* Normalize a template
*
* @param string $template
* @return string
*/
protected function normalize_template($template)
{
// Normalize the template to simplify it
$template = $this->configurator->templateNormalizer->normalizeTemplate($template);
// Convert xsl:value-of elements back to {L_} tokens where applicable
$template = preg_replace('(<xsl:value-of select="\\$(L_\\w+)"/>)', '{$1}', $template);
// Beautify the template
$template = $this->indent_template($template);
return $template;
}
}

View File

@ -0,0 +1,668 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Configurator\Items\AttributeFilters\RegexpFilter;
use s9e\TextFormatter\Configurator\Items\UnsafeTemplate;
/**
* Creates s9e\TextFormatter objects
*/
class factory implements \phpbb\textformatter\cache_interface
{
/**
* @var \phpbb\textformatter\s9e\link_helper
*/
protected $link_helper;
/**
* @var \phpbb\cache\driver\driver_interface
*/
protected $cache;
/**
* @var string Path to the cache dir
*/
protected $cache_dir;
/**
* @var string Cache key used for the parser
*/
protected $cache_key_parser;
/**
* @var string Cache key used for the renderer
*/
protected $cache_key_renderer;
/**
* @var \phpbb\config\config
*/
protected $config;
/**
* @var array Custom tokens used in bbcode.html and their corresponding token from the definition
*/
protected $custom_tokens = array(
'email' => array('{DESCRIPTION}' => '{TEXT}'),
'flash' => array('{WIDTH}' => '{NUMBER1}', '{HEIGHT}' => '{NUMBER2}'),
'img' => array('{URL}' => '{IMAGEURL}'),
'list' => array('{LIST_TYPE}' => '{HASHMAP}'),
'quote' => array('{USERNAME}' => '{TEXT1}'),
'size' => array('{SIZE}' => '{FONTSIZE}'),
'url' => array('{DESCRIPTION}' => '{TEXT}'),
);
/**
* @var \phpbb\textformatter\data_access
*/
protected $data_access;
/**
* @var array Default BBCode definitions
*/
protected $default_definitions = array(
'attachment' => '[ATTACHMENT index={NUMBER} filename={TEXT;useContent}]',
'b' => '[B]{TEXT}[/B]',
'code' => '[CODE lang={IDENTIFIER;optional}]{TEXT}[/CODE]',
'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]',
'email' => '[EMAIL={EMAIL;useContent} subject={TEXT1;optional;postFilter=rawurlencode} body={TEXT2;optional;postFilter=rawurlencode}]{TEXT}[/EMAIL]',
'flash' => '[FLASH={NUMBER1},{NUMBER2} width={NUMBER1;postFilter=#flashwidth} height={NUMBER2;postFilter=#flashheight} url={URL;useContent} /]',
'i' => '[I]{TEXT}[/I]',
'img' => '[IMG src={IMAGEURL;useContent}]',
'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]',
'li' => '[* $tagName=LI]{TEXT}[/*]',
'quote' =>
"[QUOTE
author={TEXT1;optional}
post_id={UINT;optional}
post_url={URL;optional;postFilter=#false}
profile_url={URL;optional;postFilter=#false}
time={UINT;optional}
url={URL;optional}
user_id={UINT;optional}
author={PARSE=/^\\[url=(?'url'.*?)](?'author'.*)\\[\\/url]$/i}
author={PARSE=/^\\[url](?'author'(?'url'.*?))\\[\\/url]$/i}
author={PARSE=/(?'url'https?:\\/\\/[^[\\]]+)/i}
]{TEXT2}[/QUOTE]",
'size' => '[SIZE={FONTSIZE}]{TEXT}[/SIZE]',
'u' => '[U]{TEXT}[/U]',
'url' => '[URL={URL;useContent} $forceLookahead=true]{TEXT}[/URL]',
);
/**
* @var array Default templates, taken from bbcode::bbcode_tpl()
*/
protected $default_templates = array(
'b' => '<span style="font-weight: bold"><xsl:apply-templates/></span>',
'i' => '<span style="font-style: italic"><xsl:apply-templates/></span>',
'u' => '<span style="text-decoration: underline"><xsl:apply-templates/></span>',
'img' => '<img src="{IMAGEURL}" class="postimage" alt="{L_IMAGE}"/>',
'size' => '<span style="font-size: {FONTSIZE}%; line-height: normal"><xsl:apply-templates/></span>',
'color' => '<span style="color: {COLOR}"><xsl:apply-templates/></span>',
'email' => '<a>
<xsl:attribute name="href">
<xsl:text>mailto:</xsl:text>
<xsl:value-of select="@email"/>
<xsl:if test="@subject or @body">
<xsl:text>?</xsl:text>
<xsl:if test="@subject">subject=<xsl:value-of select="@subject"/></xsl:if>
<xsl:if test="@body"><xsl:if test="@subject">&amp;</xsl:if>body=<xsl:value-of select="@body"/></xsl:if>
</xsl:if>
</xsl:attribute>
<xsl:apply-templates/>
</a>',
);
/**
* @var \phpbb\event\dispatcher_interface
*/
protected $dispatcher;
/**
* @var \phpbb\log\log_interface
*/
protected $log;
/**
* Constructor
*
* @param \phpbb\textformatter\data_access $data_access
* @param \phpbb\cache\driver\driver_interface $cache
* @param \phpbb\event\dispatcher_interface $dispatcher
* @param \phpbb\config\config $config
* @param \phpbb\textformatter\s9e\link_helper $link_helper
* @param \phpbb\log\log_interface $log
* @param string $cache_dir Path to the cache dir
* @param string $cache_key_parser Cache key used for the parser
* @param string $cache_key_renderer Cache key used for the renderer
*/
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)
{
$this->link_helper = $link_helper;
$this->cache = $cache;
$this->cache_dir = $cache_dir;
$this->cache_key_parser = $cache_key_parser;
$this->cache_key_renderer = $cache_key_renderer;
$this->config = $config;
$this->data_access = $data_access;
$this->dispatcher = $dispatcher;
$this->log = $log;
}
/**
* {@inheritdoc}
*/
public function invalidate()
{
$this->regenerate();
}
/**
* {@inheritdoc}
*
* Will remove old renderers from the cache dir but won't touch the current renderer
*/
public function tidy()
{
// Get the name of current renderer
$renderer_data = $this->cache->get($this->cache_key_renderer);
$renderer_file = ($renderer_data) ? $renderer_data['class'] . '.php' : null;
foreach (glob($this->cache_dir . 's9e_*') as $filename)
{
// Only remove the file if it's not the current renderer
if (!$renderer_file || substr($filename, -strlen($renderer_file)) !== $renderer_file)
{
unlink($filename);
}
}
}
/**
* Generate and return a new configured instance of s9e\TextFormatter\Configurator
*
* @return Configurator
*/
public function get_configurator()
{
// Create a new Configurator
$configurator = new Configurator;
/**
* Modify the s9e\TextFormatter configurator before the default settings are set
*
* @event core.text_formatter_s9e_configure_before
* @var \s9e\TextFormatter\Configurator configurator Configurator instance
* @since 3.2.0-a1
*/
$vars = array('configurator');
extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_before', compact($vars)));
// Reset the list of allowed schemes
foreach ($configurator->urlConfig->getAllowedSchemes() as $scheme)
{
$configurator->urlConfig->disallowScheme($scheme);
}
foreach (explode(',', $this->config['allowed_schemes_links']) as $scheme)
{
$configurator->urlConfig->allowScheme(trim($scheme));
}
// Convert newlines to br elements by default
$configurator->rootRules->enableAutoLineBreaks();
// Don't automatically ignore text in places where text is not allowed
$configurator->rulesGenerator->remove('IgnoreTextIfDisallowed');
// Don't remove comments and instead convert them to xsl:comment elements
$configurator->templateNormalizer->remove('RemoveComments');
$configurator->templateNormalizer->add('TransposeComments');
// Set the rendering engine and configure it to save to the cache dir
$configurator->rendering->engine = 'PHP';
$configurator->rendering->engine->cacheDir = $this->cache_dir;
$configurator->rendering->engine->defaultClassPrefix = 's9e_renderer_';
$configurator->rendering->engine->enableQuickRenderer = true;
// Create custom filters for BBCode tokens that are supported in phpBB but not in
// s9e\TextFormatter
$filter = new RegexpFilter('#^' . get_preg_expression('relative_url') . '$#Du');
$configurator->attributeFilters->add('#local_url', $filter);
$configurator->attributeFilters->add('#relative_url', $filter);
// INTTEXT regexp from acp_bbcodes
$filter = new RegexpFilter('!^([\p{L}\p{N}\-+,_. ]+)$!Du');
$configurator->attributeFilters->add('#inttext', $filter);
// Create custom filters for Flash restrictions, which use the same values as the image
// restrictions but have their own error message
$configurator->attributeFilters
->add('#flashheight', __NAMESPACE__ . '\\parser::filter_flash_height')
->addParameterByName('max_img_height')
->addParameterByName('logger');
$configurator->attributeFilters
->add('#flashwidth', __NAMESPACE__ . '\\parser::filter_flash_width')
->addParameterByName('max_img_width')
->addParameterByName('logger');
// Create a custom filter for phpBB's per-mode font size limits
$configurator->attributeFilters
->add('#fontsize', __NAMESPACE__ . '\\parser::filter_font_size')
->addParameterByName('max_font_size')
->addParameterByName('logger')
->markAsSafeInCSS();
// Create a custom filter for image URLs
$configurator->attributeFilters
->add('#imageurl', __NAMESPACE__ . '\\parser::filter_img_url')
->addParameterByName('urlConfig')
->addParameterByName('logger')
->addParameterByName('max_img_height')
->addParameterByName('max_img_width')
->markAsSafeAsURL()
->setJS('UrlFilter.filter');
// Add default BBCodes
foreach ($this->get_default_bbcodes($configurator) as $bbcode)
{
$this->add_bbcode($configurator, $bbcode['usage'], $bbcode['template']);
}
if (isset($configurator->tags['QUOTE']))
{
// Remove the nesting limit and let other services remove quotes at parsing time
$configurator->tags['QUOTE']->nestingLimit = PHP_INT_MAX;
}
// Modify the template to disable images/flash depending on user's settings
foreach (array('FLASH', 'IMG') as $name)
{
$tag = $configurator->tags[$name];
$tag->template = '<xsl:choose><xsl:when test="$S_VIEW' . $name . '">' . $tag->template . '</xsl:when><xsl:otherwise><xsl:apply-templates/></xsl:otherwise></xsl:choose>';
}
// Load custom BBCodes
foreach ($this->data_access->get_bbcodes() as $row)
{
// Insert the board's URL before {LOCAL_URL} tokens
$tpl = preg_replace_callback(
'#\\{LOCAL_URL\\d*\\}#',
function ($m)
{
return generate_board_url() . '/' . $m[0];
},
$row['bbcode_tpl']
);
$this->add_bbcode($configurator, $row['bbcode_match'], $tpl);
}
// Load smilies
foreach ($this->data_access->get_smilies() as $row)
{
$configurator->Emoticons->set(
$row['code'],
'<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']) . '"/>'
);
}
if (isset($configurator->Emoticons))
{
// Force emoticons to be rendered as text if $S_VIEWSMILIES is not set
$configurator->Emoticons->notIfCondition = 'not($S_VIEWSMILIES)';
// Only parse emoticons at the beginning of the text or if they're preceded by any
// one of: a new line, a space, a dot, or a right square bracket
$configurator->Emoticons->notAfter = '[^\\n .\\]]';
// Ignore emoticons that are immediately followed by a "word" character
$configurator->Emoticons->notBefore = '\\w';
}
// Load the censored words
$censor = $this->data_access->get_censored_words();
if (!empty($censor))
{
// Use a namespaced tag to avoid collisions
$configurator->plugins->load('Censor', array('tagName' => 'censor:tag'));
foreach ($censor as $row)
{
$configurator->Censor->add($row['word'], $row['replacement']);
}
}
// Load the magic links plugins. We do that after BBCodes so that they use the same tags
$this->configure_autolink($configurator);
// Register some vars with a default value. Those should be set at runtime by whatever calls
// the parser
$configurator->registeredVars['max_font_size'] = 0;
$configurator->registeredVars['max_img_height'] = 0;
$configurator->registeredVars['max_img_width'] = 0;
// Load the Emoji plugin and modify its tag's template to obey viewsmilies
$tag = $configurator->Emoji->getTag();
$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>';
/**
* Modify the s9e\TextFormatter configurator after the default settings are set
*
* @event core.text_formatter_s9e_configure_after
* @var \s9e\TextFormatter\Configurator configurator Configurator instance
* @since 3.2.0-a1
*/
$vars = array('configurator');
extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_after', compact($vars)));
return $configurator;
}
/**
* Regenerate and cache a new parser and renderer
*
* @return array Associative array with at least two elements: "parser" and "renderer"
*/
public function regenerate()
{
$configurator = $this->get_configurator();
// Get the censor helper and remove the Censor plugin if applicable
if (isset($configurator->Censor))
{
$censor = $configurator->Censor->getHelper();
unset($configurator->Censor);
unset($configurator->tags['censor:tag']);
}
$objects = $configurator->finalize();
/**
* Access the objects returned by finalize() before they are saved to cache
*
* @event core.text_formatter_s9e_configure_finalize
* @var array objects Array containing a "parser" object, a "renderer" object and optionally a "js" string
* @since 3.2.2-RC1
*/
$vars = array('objects');
extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_finalize', compact($vars)));
$parser = $objects['parser'];
$renderer = $objects['renderer'];
// Cache the parser as-is
$this->cache->put($this->cache_key_parser, $parser);
// We need to cache the name of the renderer's generated class
$renderer_data = array('class' => get_class($renderer));
if (isset($censor))
{
$renderer_data['censor'] = $censor;
}
$this->cache->put($this->cache_key_renderer, $renderer_data);
return array('parser' => $parser, 'renderer' => $renderer);
}
/**
* Add a BBCode to given configurator
*
* @param Configurator $configurator
* @param string $usage
* @param string $template
* @return void
*/
protected function add_bbcode(Configurator $configurator, $usage, $template)
{
try
{
$configurator->BBCodes->addCustom($usage, new UnsafeTemplate($template));
}
catch (\Exception $e)
{
$this->log->add('critical', null, null, 'LOG_BBCODE_CONFIGURATION_ERROR', false, [$usage, $e->getMessage()]);
}
}
/**
* Configure the Autolink / Autoemail plugins used to linkify text
*
* @param \s9e\TextFormatter\Configurator $configurator
* @return void
*/
protected function configure_autolink(Configurator $configurator)
{
$configurator->plugins->load('Autoemail');
$configurator->plugins->load('Autolink', array('matchWww' => true));
// Add a tag filter that creates a tag that stores and replace the
// content of a link created by the Autolink plugin
$configurator->Autolink->getTag()->filterChain
->add(array($this->link_helper, 'generate_link_text_tag'))
->resetParameters()
->addParameterByName('tag')
->addParameterByName('parser');
// Create a tag that will be used to display the truncated text by
// replacing the original content with the content of the @text attribute
$tag = $configurator->tags->add('LINK_TEXT');
$tag->attributes->add('text');
$tag->template = '<xsl:value-of select="@text"/>';
$tag->filterChain
->add(array($this->link_helper, 'truncate_local_url'))
->resetParameters()
->addParameterByName('tag')
->addParameterByValue(generate_board_url() . '/');
$tag->filterChain
->add(array($this->link_helper, 'truncate_text'))
->resetParameters()
->addParameterByName('tag');
$tag->filterChain
->add(array($this->link_helper, 'cleanup_tag'))
->resetParameters()
->addParameterByName('tag')
->addParameterByName('parser');
}
/**
* Escape a literal to be used in an HTML attribute in an XSL template
*
* Escapes "HTML special chars" for obvious reasons and curly braces to avoid them
* being interpreted as an attribute value template
*
* @param string $value Original string
* @return string Escaped string
*/
protected function escape_html_attribute($value)
{
return htmlspecialchars(strtr($value, ['{' => '{{', '}' => '}}']), ENT_COMPAT | ENT_XML1, 'UTF-8');
}
/**
* Return the default BBCodes configuration
*
* @return array 2D array. Each element has a 'usage' key, a 'template' key, and an optional 'options' key
*/
protected function get_default_bbcodes($configurator)
{
// For each BBCode, build an associative array matching style_ids to their template
$templates = array();
foreach ($this->data_access->get_styles_templates() as $style_id => $data)
{
foreach ($this->extract_templates($data['template']) as $bbcode_name => $template)
{
$templates[$bbcode_name][$style_id] = $template;
}
// Add default templates wherever missing, or for BBCodes that were not specified in
// this template's bitfield. For instance, prosilver has a custom template for b but its
// bitfield does not enable it so the default template is used instead
foreach ($this->default_templates as $bbcode_name => $template)
{
if (!isset($templates[$bbcode_name][$style_id]) || !in_array($bbcode_name, $data['bbcodes'], true))
{
$templates[$bbcode_name][$style_id] = $template;
}
}
}
// Replace custom tokens and normalize templates
foreach ($templates as $bbcode_name => $style_templates)
{
foreach ($style_templates as $i => $template)
{
if (isset($this->custom_tokens[$bbcode_name]))
{
$template = strtr($template, $this->custom_tokens[$bbcode_name]);
}
$templates[$bbcode_name][$i] = $configurator->templateNormalizer->normalizeTemplate($template);
}
}
$bbcodes = array();
foreach ($this->default_definitions as $bbcode_name => $usage)
{
$bbcodes[$bbcode_name] = array(
'usage' => $usage,
'template' => $this->merge_templates($templates[$bbcode_name]),
);
}
return $bbcodes;
}
/**
* Extract and recompose individual BBCode templates from a style's template file
*
* @param string $template Style template (bbcode.html)
* @return array Associative array matching BBCode names to their template
*/
protected function extract_templates($template)
{
// Capture the template fragments
// Allow either phpBB template or the Twig syntax
preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END .*? -->#s', $template, $matches, PREG_SET_ORDER) ?:
preg_match_all('#{% for (.*?) in .*? %}(.*?){% endfor %}#s', $template, $matches, PREG_SET_ORDER);
$fragments = array();
foreach ($matches as $match)
{
// Normalize the whitespace
$fragment = preg_replace('#>\\n\\t*<#', '><', trim($match[2]));
$fragments[$match[1]] = $fragment;
}
// Automatically recompose templates split between *_open and *_close
foreach ($fragments as $fragment_name => $fragment)
{
if (preg_match('#^(\\w+)_close$#', $fragment_name, $match))
{
$bbcode_name = $match[1];
if (isset($fragments[$bbcode_name . '_open']))
{
$templates[$bbcode_name] = $fragments[$bbcode_name . '_open'] . '<xsl:apply-templates/>' . $fragment;
}
}
}
// Manually recompose and overwrite irregular templates
$templates['list'] =
'<xsl:choose>
<xsl:when test="not(@type)">
' . $fragments['ulist_open_default'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . '
</xsl:when>
<xsl:when test="contains(\'upperlowerdecim\',substring(@type,1,5))">
' . $fragments['olist_open'] . '<xsl:apply-templates/>' . $fragments['olist_close'] . '
</xsl:when>
<xsl:otherwise>
' . $fragments['ulist_open'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . '
</xsl:otherwise>
</xsl:choose>';
$templates['li'] = $fragments['listitem'] . '<xsl:apply-templates/>' . $fragments['listitem_close'];
// Replace the regular quote template with the extended quote template if available
if (isset($fragments['quote_extended']))
{
$templates['quote'] = $fragments['quote_extended'];
}
// The [attachment] BBCode uses the inline_attachment template to output a comment that
// is post-processed by parse_attachments()
$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'];
// Add fragments as templates
foreach ($fragments as $fragment_name => $fragment)
{
if (preg_match('#^\\w+$#', $fragment_name))
{
$templates[$fragment_name] = $fragment;
}
}
// Keep only templates that are named after an existing BBCode
$templates = array_intersect_key($templates, $this->default_definitions);
return $templates;
}
/**
* Merge the templates from any number of styles into one BBCode template
*
* When multiple templates are available for the same BBCode (because of multiple styles) we
* merge them into a single template that uses an xsl:choose construct that determines which
* style to use at rendering time.
*
* @param array $style_templates Associative array matching style_ids to their template
* @return string
*/
protected function merge_templates(array $style_templates)
{
// Return the template as-is if there's only one style or all styles share the same template
if (count(array_unique($style_templates)) === 1)
{
return end($style_templates);
}
// Group identical templates together
$grouped_templates = array();
foreach ($style_templates as $style_id => $style_template)
{
$grouped_templates[$style_template][] = '$STYLE_ID=' . $style_id;
}
// Sort templates by frequency descending
$templates_cnt = array_map('sizeof', $grouped_templates);
array_multisort($grouped_templates, $templates_cnt);
// Remove the most frequent template from the list; It becomes the default
reset($grouped_templates);
$default_template = key($grouped_templates);
unset($grouped_templates[$default_template]);
// Build an xsl:choose switch
$template = '<xsl:choose>';
foreach ($grouped_templates as $style_template => $exprs)
{
$template .= '<xsl:when test="' . implode(' or ', $exprs) . '">' . $style_template . '</xsl:when>';
}
$template .= '<xsl:otherwise>' . $default_template . '</xsl:otherwise></xsl:choose>';
return $template;
}
}

View File

@ -0,0 +1,118 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
class link_helper
{
/**
* Clean up and invalidate a LINK_TEXT tag if applicable
*
* Will invalidate the tag if its replacement text is the same as the original
* text and would have no visible effect
*
* @param \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag
* @param \s9e\TextFormatter\Parser $parser Parser
* @return bool Whether the tag is valid
*/
public function cleanup_tag(\s9e\TextFormatter\Parser\Tag $tag, \s9e\TextFormatter\Parser $parser)
{
// Invalidate if the content of the tag matches the text attribute
$text = substr($parser->getText(), $tag->getPos(), $tag->getLen());
return ($text !== $tag->getAttribute('text'));
}
/**
* Create a LINK_TEXT tag inside of a link
*
* Meant to only apply to linkified URLs and [url] BBCodes without a parameter
*
* @param \s9e\TextFormatter\Parser\Tag $tag URL tag (start tag)
* @param \s9e\TextFormatter\Parser $parser Parser
* @return bool Always true to indicate that the tag is valid
*/
public function generate_link_text_tag(\s9e\TextFormatter\Parser\Tag $tag, \s9e\TextFormatter\Parser $parser)
{
// Only create a LINK_TEXT tag if the start tag is paired with an end
// tag, which is the case with tags from the Autolink plugins and with
// the [url] BBCode when its content is used for the URL
if (!$tag->getEndTag() || !$this->should_shorten($tag, $parser->getText()))
{
return true;
}
// Capture the text between the start tag and its end tag
$start = $tag->getPos() + $tag->getLen();
$end = $tag->getEndTag()->getPos();
$length = $end - $start;
$text = substr($parser->getText(), $start, $length);
// Create a tag that consumes the link's text
$parser->addSelfClosingTag('LINK_TEXT', $start, $length)->setAttribute('text', $text);
return true;
}
/**
* Test whether we should shorten this tag's text
*
* Will test whether the tag either does not use any markup or uses a single
* [url] BBCode
*
* @param \s9e\TextFormatter\Parser\Tag $tag URL tag
* @param string $text Original text
* @return bool
*/
protected function should_shorten(\s9e\TextFormatter\Parser\Tag $tag, $text)
{
return ($tag->getLen() === 0 || strtolower(substr($text, $tag->getPos(), $tag->getLen())) === '[url]');
}
/**
* Remove the board's root URL from a the start of a string
*
* @param \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag
* @param string $board_url Forum's root URL (with trailing slash)
* @return bool Always true to indicate that the tag is valid
*/
public function truncate_local_url(\s9e\TextFormatter\Parser\Tag $tag, $board_url)
{
$text = $tag->getAttribute('text');
if (stripos($text, $board_url) === 0 && strlen($text) > strlen($board_url))
{
$tag->setAttribute('text', substr($text, strlen($board_url)));
}
return true;
}
/**
* Truncate the replacement text set in a LINK_TEXT tag
*
* @param \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag
* @return bool Always true to indicate that the tag is valid
*/
public function truncate_text(\s9e\TextFormatter\Parser\Tag $tag)
{
$text = $tag->getAttribute('text');
if (utf8_strlen($text) > 55)
{
$text = utf8_substr($text, 0, 39) . ' ... ' . utf8_substr($text, -10);
}
$tag->setAttribute('text', $text);
return true;
}
}

View File

@ -0,0 +1,399 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
use s9e\TextFormatter\Parser\AttributeFilters\UrlFilter;
use s9e\TextFormatter\Parser\Logger;
/**
* s9e\TextFormatter\Parser adapter
*/
class parser implements \phpbb\textformatter\parser_interface
{
/**
* @var \phpbb\event\dispatcher_interface
*/
protected $dispatcher;
/**
* @var \s9e\TextFormatter\Parser
*/
protected $parser;
/**
* Constructor
*
* @param \phpbb\cache\driver_interface $cache
* @param string $key Cache key
* @param factory $factory
* @param \phpbb\event\dispatcher_interface $dispatcher
*/
public function __construct(\phpbb\cache\driver\driver_interface $cache, $key, factory $factory, \phpbb\event\dispatcher_interface $dispatcher)
{
$parser = $cache->get($key);
if (!$parser)
{
$objects = $factory->regenerate();
$parser = $objects['parser'];
}
$this->dispatcher = $dispatcher;
$this->parser = $parser;
$parser = $this;
/**
* Configure the parser service
*
* Can be used to:
* - toggle features or BBCodes
* - register variables or custom parsers in the s9e\TextFormatter parser
* - configure the s9e\TextFormatter parser's runtime settings
*
* @event core.text_formatter_s9e_parser_setup
* @var \phpbb\textformatter\s9e\parser parser This parser service
* @since 3.2.0-a1
*/
$vars = array('parser');
extract($dispatcher->trigger_event('core.text_formatter_s9e_parser_setup', compact($vars)));
}
/**
* {@inheritdoc}
*/
public function parse($text)
{
$parser = $this;
/**
* Modify a text before it is parsed
*
* @event core.text_formatter_s9e_parse_before
* @var \phpbb\textformatter\s9e\parser parser This parser service
* @var string text The original text
* @since 3.2.0-a1
*/
$vars = array('parser', 'text');
extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_before', compact($vars)));
$xml = $this->parser->parse($text);
/**
* Modify a parsed text in its XML form
*
* @event core.text_formatter_s9e_parse_after
* @var \phpbb\textformatter\s9e\parser parser This parser service
* @var string xml The parsed text, in XML
* @since 3.2.0-a1
*/
$vars = array('parser', 'xml');
extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_after', compact($vars)));
return $xml;
}
/**
* {@inheritdoc}
*/
public function disable_bbcode($name)
{
$this->parser->disableTag(strtoupper($name));
}
/**
* {@inheritdoc}
*/
public function disable_bbcodes()
{
$this->parser->disablePlugin('BBCodes');
}
/**
* {@inheritdoc}
*/
public function disable_censor()
{
$this->parser->disablePlugin('Censor');
}
/**
* {@inheritdoc}
*/
public function disable_magic_url()
{
$this->parser->disablePlugin('Autoemail');
$this->parser->disablePlugin('Autolink');
}
/**
* {@inheritdoc}
*/
public function disable_smilies()
{
$this->parser->disablePlugin('Emoticons');
$this->parser->disablePlugin('Emoji');
}
/**
* {@inheritdoc}
*/
public function enable_bbcode($name)
{
$this->parser->enableTag(strtoupper($name));
}
/**
* {@inheritdoc}
*/
public function enable_bbcodes()
{
$this->parser->enablePlugin('BBCodes');
}
/**
* {@inheritdoc}
*/
public function enable_censor()
{
$this->parser->enablePlugin('Censor');
}
/**
* {@inheritdoc}
*/
public function enable_magic_url()
{
$this->parser->enablePlugin('Autoemail');
$this->parser->enablePlugin('Autolink');
}
/**
* {@inheritdoc}
*/
public function enable_smilies()
{
$this->parser->enablePlugin('Emoticons');
$this->parser->enablePlugin('Emoji');
}
/**
* {@inheritdoc}
*
* This will convert the log entries found in s9e\TextFormatter's logger into phpBB error
* messages
*/
public function get_errors()
{
$errors = array();
foreach ($this->parser->getLogger()->getLogs() as $entry)
{
list(, $msg, $context) = $entry;
if ($msg === 'Tag limit exceeded')
{
if ($context['tagName'] === 'E')
{
$errors[] = array('TOO_MANY_SMILIES', $context['tagLimit']);
}
else if ($context['tagName'] === 'URL')
{
$errors[] = array('TOO_MANY_URLS', $context['tagLimit']);
}
}
else if ($msg === 'MAX_FONT_SIZE_EXCEEDED')
{
$errors[] = array($msg, $context['max_size']);
}
else if (preg_match('/^MAX_(?:FLASH|IMG)_(HEIGHT|WIDTH)_EXCEEDED$/D', $msg, $m))
{
$errors[] = array($msg, $context['max_' . strtolower($m[1])]);
}
else if ($msg === 'Tag is disabled')
{
$name = strtolower($context['tag']->getName());
$errors[] = array('UNAUTHORISED_BBCODE', '[' . $name . ']');
}
else if ($msg === 'UNABLE_GET_IMAGE_SIZE')
{
$errors[] = array($msg);
}
}
// Deduplicate error messages. array_unique() only works on strings so we have to serialize
if (!empty($errors))
{
$errors = array_map('unserialize', array_unique(array_map('serialize', $errors)));
}
return $errors;
}
/**
* Return the instance of s9e\TextFormatter\Parser used by this object
*
* @return \s9e\TextFormatter\Parser
*/
public function get_parser()
{
return $this->parser;
}
/**
* {@inheritdoc}
*/
public function set_var($name, $value)
{
if ($name === 'max_smilies')
{
$this->parser->setTagLimit('E', $value ?: PHP_INT_MAX);
}
else if ($name === 'max_urls')
{
$this->parser->setTagLimit('URL', $value ?: PHP_INT_MAX);
}
else
{
$this->parser->registeredVars[$name] = $value;
}
}
/**
* {@inheritdoc}
*/
public function set_vars(array $vars)
{
foreach ($vars as $name => $value)
{
$this->set_var($name, $value);
}
}
/**
* Filter a flash object's height
*
* @see bbcode_firstpass::bbcode_flash()
*
* @param string $height
* @param integer $max_height
* @param Logger $logger
* @return mixed Original value if valid, FALSE otherwise
*/
static public function filter_flash_height($height, $max_height, Logger $logger)
{
if ($max_height && $height > $max_height)
{
$logger->err('MAX_FLASH_HEIGHT_EXCEEDED', array('max_height' => $max_height));
return false;
}
return $height;
}
/**
* Filter a flash object's width
*
* @see bbcode_firstpass::bbcode_flash()
*
* @param string $width
* @param integer $max_width
* @param Logger $logger
* @return mixed Original value if valid, FALSE otherwise
*/
static public function filter_flash_width($width, $max_width, Logger $logger)
{
if ($max_width && $width > $max_width)
{
$logger->err('MAX_FLASH_WIDTH_EXCEEDED', array('max_width' => $max_width));
return false;
}
return $width;
}
/**
* Filter the value used in a [size] BBCode
*
* @see bbcode_firstpass::bbcode_size()
*
* @param string $size Original size
* @param integer $max_size Maximum allowed size
* @param Logger $logger
* @return mixed Original value if valid, FALSE otherwise
*/
static public function filter_font_size($size, $max_size, Logger $logger)
{
if ($max_size && $size > $max_size)
{
$logger->err('MAX_FONT_SIZE_EXCEEDED', array('max_size' => $max_size));
return false;
}
if ($size < 1)
{
return false;
}
return $size;
}
/**
* Filter an image's URL to enforce restrictions on its dimensions
*
* @see bbcode_firstpass::bbcode_img()
*
* @param string $url Original URL
* @param array $url_config Config used by the URL filter
* @param Logger $logger
* @param integer $max_height Maximum height allowed
* @param integer $max_width Maximum width allowed
* @return string|bool Original value if valid, FALSE otherwise
*/
static public function filter_img_url($url, array $url_config, Logger $logger, $max_height, $max_width)
{
// Validate the URL
$url = UrlFilter::filter($url, $url_config, $logger);
if ($url === false)
{
return false;
}
if ($max_height || $max_width)
{
$imagesize = new \FastImageSize\FastImageSize();
$size_info = $imagesize->getImageSize($url);
if ($size_info === false)
{
$logger->err('UNABLE_GET_IMAGE_SIZE');
return false;
}
if ($max_height && $max_height < $size_info['height'])
{
$logger->err('MAX_IMG_HEIGHT_EXCEEDED', array('max_height' => $max_height));
return false;
}
if ($max_width && $max_width < $size_info['width'])
{
$logger->err('MAX_IMG_WIDTH_EXCEEDED', array('max_width' => $max_width));
return false;
}
}
return $url;
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
class quote_helper
{
/**
* @var string Base URL for a post link, uses {POST_ID} as placeholder
*/
protected $post_url;
/**
* @var string Base URL for a profile link, uses {USER_ID} as placeholder
*/
protected $profile_url;
/**
* @var \phpbb\user
*/
protected $user;
/**
* Constructor
*
* @param \phpbb\user $user
* @param string $root_path
* @param string $php_ext
*/
public function __construct(\phpbb\user $user, $root_path, $php_ext)
{
$this->post_url = append_sid($root_path . 'viewtopic.' . $php_ext, 'p={POST_ID}#p{POST_ID}', false);
$this->profile_url = append_sid($root_path . 'memberlist.' . $php_ext, 'mode=viewprofile&u={USER_ID}', false);
$this->user = $user;
}
/**
* Inject dynamic metadata into QUOTE tags in given XML
*
* @param string $xml Original XML
* @return string Modified XML
*/
public function inject_metadata($xml)
{
$post_url = $this->post_url;
$profile_url = $this->profile_url;
$user = $this->user;
return \s9e\TextFormatter\Utils::replaceAttributes(
$xml,
'QUOTE',
function ($attributes) use ($post_url, $profile_url, $user)
{
if (isset($attributes['post_id']))
{
$attributes['post_url'] = str_replace('{POST_ID}', $attributes['post_id'], $post_url);
}
if (isset($attributes['time']))
{
$attributes['date'] = $user->format_date($attributes['time']);
}
if (isset($attributes['user_id']))
{
$attributes['profile_url'] = str_replace('{USER_ID}', $attributes['user_id'], $profile_url);
}
return $attributes;
}
);
}
}

View File

@ -0,0 +1,313 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
/**
* s9e\TextFormatter\Renderer adapter
*/
class renderer implements \phpbb\textformatter\renderer_interface
{
/**
* @var \s9e\TextFormatter\Plugins\Censor\Helper
*/
protected $censor;
/**
* @var \phpbb\event\dispatcher_interface
*/
protected $dispatcher;
/**
* @var quote_helper
*/
protected $quote_helper;
/**
* @var \s9e\TextFormatter\Renderer
*/
protected $renderer;
/**
* @var bool Status of the viewcensors option
*/
protected $viewcensors = false;
/**
* @var bool Status of the viewflash option
*/
protected $viewflash = false;
/**
* @var bool Status of the viewimg option
*/
protected $viewimg = false;
/**
* @var bool Status of the viewsmilies option
*/
protected $viewsmilies = false;
/**
* Constructor
*
* @param \phpbb\cache\driver\driver_interface $cache
* @param string $cache_dir Path to the cache dir
* @param string $key Cache key
* @param factory $factory
* @param \phpbb\event\dispatcher_interface $dispatcher
*/
public function __construct(\phpbb\cache\driver\driver_interface $cache, $cache_dir, $key, factory $factory, \phpbb\event\dispatcher_interface $dispatcher)
{
$renderer_data = $cache->get($key);
if ($renderer_data)
{
$class = $renderer_data['class'];
if (!class_exists($class, false))
{
// Try to load the renderer class from its cache file
$cache_file = $cache_dir . $class . '.php';
if (file_exists($cache_file))
{
include($cache_file);
}
}
if (class_exists($class, false))
{
$renderer = new $class;
}
if (isset($renderer_data['censor']))
{
$censor = $renderer_data['censor'];
}
}
if (!isset($renderer))
{
$objects = $factory->regenerate();
$renderer = $objects['renderer'];
}
if (isset($censor))
{
$this->censor = $censor;
}
$this->dispatcher = $dispatcher;
$this->renderer = $renderer;
$renderer = $this;
/**
* Configure the renderer service
*
* @event core.text_formatter_s9e_renderer_setup
* @var \phpbb\textformatter\s9e\renderer renderer This renderer service
* @since 3.2.0-a1
*/
$vars = array('renderer');
extract($dispatcher->trigger_event('core.text_formatter_s9e_renderer_setup', compact($vars)));
}
/**
* Configure the quote_helper object used to display extended information in quotes
*
* @param quote_helper $quote_helper
*/
public function configure_quote_helper(quote_helper $quote_helper)
{
$this->quote_helper = $quote_helper;
}
/**
* Automatically set the smilies path based on config
*
* @param \phpbb\config\config $config
* @param \phpbb\path_helper $path_helper
* @return null
*/
public function configure_smilies_path(\phpbb\config\config $config, \phpbb\path_helper $path_helper)
{
/**
* @see smiley_text()
*/
$root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $path_helper->get_web_root_path();
$this->set_smilies_path($root_path . $config['smilies_path']);
}
/**
* Configure this renderer as per the user's settings
*
* Should set the locale as well as the viewcensor/viewflash/viewimg/viewsmilies options.
*
* @param \phpbb\user $user
* @param \phpbb\config\config $config
* @param \phpbb\auth\auth $auth
* @return null
*/
public function configure_user(\phpbb\user $user, \phpbb\config\config $config, \phpbb\auth\auth $auth)
{
$censor = $user->optionget('viewcensors') || !$config['allow_nocensors'] || !$auth->acl_get('u_chgcensors');
$this->set_viewcensors($censor);
$this->set_viewflash($user->optionget('viewflash'));
$this->set_viewimg($user->optionget('viewimg'));
$this->set_viewsmilies($user->optionget('viewsmilies'));
// Set the stylesheet parameters
foreach (array_keys($this->renderer->getParameters()) as $param_name)
{
if (strpos($param_name, 'L_') === 0)
{
// L_FOO is set to $user->lang('FOO')
$this->renderer->setParameter($param_name, $user->lang(substr($param_name, 2)));
}
}
// Set this user's style id and other parameters
$this->renderer->setParameters(array(
'S_IS_BOT' => $user->data['is_bot'],
'S_REGISTERED_USER' => $user->data['is_registered'],
'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS),
'STYLE_ID' => $user->style['style_id'],
));
}
/**
* Return the instance of s9e\TextFormatter\Renderer used by this object
*
* @return \s9e\TextFormatter\Renderer
*/
public function get_renderer()
{
return $this->renderer;
}
/**
* {@inheritdoc}
*/
public function get_viewcensors()
{
return $this->viewcensors;
}
/**
* {@inheritdoc}
*/
public function get_viewflash()
{
return $this->viewflash;
}
/**
* {@inheritdoc}
*/
public function get_viewimg()
{
return $this->viewimg;
}
/**
* {@inheritdoc}
*/
public function get_viewsmilies()
{
return $this->viewsmilies;
}
/**
* {@inheritdoc}
*/
public function render($xml)
{
if (isset($this->quote_helper))
{
$xml = $this->quote_helper->inject_metadata($xml);
}
$renderer = $this;
/**
* Modify a parsed text before it is rendered
*
* @event core.text_formatter_s9e_render_before
* @var \phpbb\textformatter\s9e\renderer renderer This renderer service
* @var string xml The parsed text, in its XML form
* @since 3.2.0-a1
*/
$vars = array('renderer', 'xml');
extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_before', compact($vars)));
$html = $this->renderer->render($xml);
if (isset($this->censor) && $this->viewcensors)
{
$html = $this->censor->censorHtml($html, true);
}
/**
* Modify a rendered text
*
* @event core.text_formatter_s9e_render_after
* @var string html The rendered text's HTML
* @var \phpbb\textformatter\s9e\renderer renderer This renderer service
* @since 3.2.0-a1
*/
$vars = array('html', 'renderer');
extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_after', compact($vars)));
return $html;
}
/**
* {@inheritdoc}
*/
public function set_smilies_path($path)
{
$this->renderer->setParameter('T_SMILIES_PATH', $path);
}
/**
* {@inheritdoc}
*/
public function set_viewcensors($value)
{
$this->viewcensors = $value;
$this->renderer->setParameter('S_VIEWCENSORS', $value);
}
/**
* {@inheritdoc}
*/
public function set_viewflash($value)
{
$this->viewflash = $value;
$this->renderer->setParameter('S_VIEWFLASH', $value);
}
/**
* {@inheritdoc}
*/
public function set_viewimg($value)
{
$this->viewimg = $value;
$this->renderer->setParameter('S_VIEWIMG', $value);
}
/**
* {@inheritdoc}
*/
public function set_viewsmilies($value)
{
$this->viewsmilies = $value;
$this->renderer->setParameter('S_VIEWSMILIES', $value);
}
}

View File

@ -0,0 +1,152 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
/**
* Text manipulation utilities
*/
class utils implements \phpbb\textformatter\utils_interface
{
/**
* Replace BBCodes and other formatting elements with whitespace
*
* NOTE: preserves smilies as text
*
* @param string $xml Parsed text
* @return string Plain text
*/
public function clean_formatting($xml)
{
// Insert a space before <s> and <e> then remove formatting
$xml = preg_replace('#<[es]>#', ' $0', $xml);
return \s9e\TextFormatter\Utils::removeFormatting($xml);
}
/**
* Format given string to be used as an attribute value
*
* Will return the string as-is if it can be used in a BBCode without quotes. Otherwise,
* it will use either single- or double- quotes depending on whichever requires less escaping.
* Quotes and backslashes are escaped with backslashes where necessary
*
* @param string $str Original string
* @return string Same string if possible, escaped string within quotes otherwise
*/
protected function format_attribute_value($str)
{
if (!preg_match('/[ "\'\\\\\\]]/', $str))
{
// Return as-is if it contains none of: space, ' " \ or ]
return $str;
}
$singleQuoted = "'" . addcslashes($str, "\\'") . "'";
$doubleQuoted = '"' . addcslashes($str, '\\"') . '"';
return (strlen($singleQuoted) < strlen($doubleQuoted)) ? $singleQuoted : $doubleQuoted;
}
/**
* {@inheritdoc}
*/
public function generate_quote($text, array $attributes = array())
{
$text = trim($text);
$quote = '[quote';
if (isset($attributes['author']))
{
// Add the author as the BBCode's default attribute
$quote .= '=' . $this->format_attribute_value($attributes['author']);
unset($attributes['author']);
}
if (isset($attributes['user_id']) && $attributes['user_id'] == ANONYMOUS)
{
unset($attributes['user_id']);
}
ksort($attributes);
foreach ($attributes as $name => $value)
{
$quote .= ' ' . $name . '=' . $this->format_attribute_value($value);
}
$quote .= ']';
$newline = (strlen($quote . $text . '[/quote]') > 80 || strpos($text, "\n") !== false) ? "\n" : '';
$quote .= $newline . $text . $newline . '[/quote]';
return $quote;
}
/**
* Get a list of quote authors, limited to the outermost quotes
*
* @param string $xml Parsed text
* @return string[] List of authors
*/
public function get_outermost_quote_authors($xml)
{
$authors = array();
if (strpos($xml, '<QUOTE ') === false)
{
return $authors;
}
$dom = new \DOMDocument;
$dom->loadXML($xml);
$xpath = new \DOMXPath($dom);
foreach ($xpath->query('//QUOTE[not(ancestor::QUOTE)]/@author') as $author)
{
$authors[] = $author->textContent;
}
return $authors;
}
/**
* Remove given BBCode and its content, at given nesting depth
*
* @param string $xml Parsed text
* @param string $bbcode_name BBCode's name
* @param integer $depth Minimum nesting depth (number of parents of the same name)
* @return string Parsed text
*/
public function remove_bbcode($xml, $bbcode_name, $depth = 0)
{
return \s9e\TextFormatter\Utils::removeTag($xml, strtoupper($bbcode_name), $depth);
}
/**
* Return a parsed text to its original form
*
* @param string $xml Parsed text
* @return string Original plain text
*/
public function unparse($xml)
{
return \s9e\TextFormatter\Unparser::unparse($xml);
}
/**
* {@inheritdoc}
*/
public function is_empty($text)
{
if ($text === null || $text === '')
{
return true;
}
return trim($this->unparse($text)) === '';
}
}