Augmentation vers version 3.3.0
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?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;
|
||||
|
||||
interface acp_utils_interface
|
||||
{
|
||||
/**
|
||||
* There is an issue with the definition
|
||||
*/
|
||||
const BBCODE_STATUS_INVALID_DEFINITION = 'invalid_definition';
|
||||
|
||||
/**
|
||||
* There is an issue with the template
|
||||
*/
|
||||
const BBCODE_STATUS_INVALID_TEMPLATE = 'invalid_template';
|
||||
|
||||
/**
|
||||
* The BBCode is valid and can be safely used by anyone
|
||||
*/
|
||||
const BBCODE_STATUS_SAFE = 'safe';
|
||||
|
||||
/**
|
||||
* The BBCode is valid but may be unsafe to use
|
||||
*/
|
||||
const BBCODE_STATUS_UNSAFE = 'unsafe';
|
||||
|
||||
/**
|
||||
* Analyse given BBCode definition for issues and safeness
|
||||
*
|
||||
* Required elements in the return array:
|
||||
* - status: see BBCODE_STATUS_* constants
|
||||
*
|
||||
* Optional elements in the return array:
|
||||
* - name: Name of the BBCode based on the definition. Required if status is "safe".
|
||||
* - error_text: Textual description of the issue in plain text or as a L_* string.
|
||||
* - error_html: Visual description of the issue in HTML.
|
||||
*
|
||||
* @param string $definition BBCode definition, e.g. [b]{TEXT}[/b]
|
||||
* @param string $template BBCode template, e.g. <b>{TEXT}</b>
|
||||
* @return array
|
||||
*/
|
||||
public function analyse_bbcode(string $definition, string $template): array;
|
||||
}
|
||||
67
install/update/new/phpbb/textformatter/s9e/acp_utils.php
Normal file
67
install/update/new/phpbb/textformatter/s9e/acp_utils.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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\acp_utils_interface;
|
||||
use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
|
||||
|
||||
class acp_utils implements acp_utils_interface
|
||||
{
|
||||
/**
|
||||
* @var factory $factory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* @param factory $factory
|
||||
*/
|
||||
public function __construct(factory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function analyse_bbcode(string $definition, string $template): array
|
||||
{
|
||||
$configurator = $this->factory->get_configurator();
|
||||
$return = ['status' => self::BBCODE_STATUS_SAFE];
|
||||
|
||||
// Capture and normalize the BBCode name manually because there's no easy way to retrieve
|
||||
// it in TextFormatter <= 2.x
|
||||
if (preg_match('(\\[([-\\w]++))', $definition, $m))
|
||||
{
|
||||
$return['name'] = strtoupper($m[1]);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$configurator->BBCodes->addCustom($definition, $template);
|
||||
}
|
||||
catch (UnsafeTemplateException $e)
|
||||
{
|
||||
$return['status'] = self::BBCODE_STATUS_UNSAFE;
|
||||
$return['error_text'] = $e->getMessage();
|
||||
$return['error_html'] = $e->highlightNode('<span class="highlight">');
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
$return['status'] = (preg_match('(xml|xpath|xsl)i', $e->getMessage())) ? self::BBCODE_STATUS_INVALID_TEMPLATE : self::BBCODE_STATUS_INVALID_DEFINITION;
|
||||
$return['error_text'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
199
install/update/new/phpbb/textformatter/s9e/bbcode_merger.php
Normal file
199
install/update/new/phpbb/textformatter/s9e/bbcode_merger.php
Normal file
@@ -0,0 +1,199 @@
|
||||
<?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\TemplateLoader;
|
||||
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_optional_bbcode($without, $with) && $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 = TemplateLoader::load($template);
|
||||
$dom->formatOutput = true;
|
||||
$template = TemplateLoader::save($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 bool
|
||||
*/
|
||||
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']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the two definitions form BBCode with an optional attribute
|
||||
*
|
||||
* @param array $without BBCode definition without an attribute
|
||||
* @param array $with BBCode definition with an attribute
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_optional_bbcode(array $without, array $with)
|
||||
{
|
||||
// Remove the default attribute from the definition
|
||||
$with['usage'] = preg_replace('(=[^\\]]++)', '', $with['usage']);
|
||||
|
||||
// Test whether both definitions are the same, regardless of case
|
||||
return strcasecmp($without['usage'], $with['usage']) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
678
install/update/new/phpbb/textformatter/s9e/factory.php
Normal file
678
install/update/new/phpbb/textformatter/s9e/factory.php
Normal file
@@ -0,0 +1,678 @@
|
||||
<?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}
|
||||
msg_id={UINT;optional}
|
||||
msg_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><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>',
|
||||
'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">&</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="@tseq">
|
||||
<img alt="{.}" class="emoji" draggable="false" src="//twemoji.maxcdn.com/2/svg/{@tseq}.svg"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<img alt="{.}" class="emoji" draggable="false" src="https://cdn.jsdelivr.net/gh/s9e/emoji-assets-twemoji@11.2/dist/svgz/{@seq}.svgz"/>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>';
|
||||
$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;
|
||||
}
|
||||
}
|
||||
115
install/update/new/phpbb/textformatter/s9e/link_helper.php
Normal file
115
install/update/new/phpbb/textformatter/s9e/link_helper.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?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 void
|
||||
*/
|
||||
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());
|
||||
if ($text === $tag->getAttribute('text'))
|
||||
{
|
||||
$tag->invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 and make it depends on this tag
|
||||
$link_text_tag = $parser->addSelfClosingTag('LINK_TEXT', $start, $length, 10);
|
||||
$link_text_tag->setAttribute('text', $text);
|
||||
$tag->cascadeInvalidationTo($link_text_tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void
|
||||
*/
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the replacement text set in a LINK_TEXT tag
|
||||
*
|
||||
* @param \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag
|
||||
* @return void
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
417
install/update/new/phpbb/textformatter/s9e/parser.php
Normal file
417
install/update/new/phpbb/textformatter/s9e/parser.php
Normal file
@@ -0,0 +1,417 @@
|
||||
<?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;
|
||||
use s9e\TextFormatter\Parser\Tag;
|
||||
|
||||
/**
|
||||
* 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' && $this->is_a_bbcode($context['tag']))
|
||||
{
|
||||
$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 || !is_numeric($size))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given tag consumes text that looks like BBCode-styled markup
|
||||
*
|
||||
* @param Tag $tag Original tag
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_a_bbcode(Tag $tag)
|
||||
{
|
||||
if ($tag->getLen() < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$markup = substr($this->parser->getText(), $tag->getPos(), $tag->getLen());
|
||||
|
||||
return (bool) preg_match('(^\\[\\w++.*?\\]$)s', $markup);
|
||||
}
|
||||
}
|
||||
87
install/update/new/phpbb/textformatter/s9e/quote_helper.php
Normal file
87
install/update/new/phpbb/textformatter/s9e/quote_helper.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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 private message link, uses {MSG_ID} as placeholder
|
||||
*/
|
||||
protected $msg_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->msg_url = append_sid($root_path . 'ucp.' . $php_ext, 'i=pm&mode=view&p={MSG_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)
|
||||
{
|
||||
return \s9e\TextFormatter\Utils::replaceAttributes(
|
||||
$xml,
|
||||
'QUOTE',
|
||||
function ($attributes)
|
||||
{
|
||||
if (isset($attributes['post_id']))
|
||||
{
|
||||
$attributes['post_url'] = str_replace('{POST_ID}', $attributes['post_id'], $this->post_url);
|
||||
}
|
||||
if (isset($attributes['msg_id']))
|
||||
{
|
||||
$attributes['msg_url'] = str_replace('{MSG_ID}', $attributes['msg_id'], $this->msg_url);
|
||||
}
|
||||
if (isset($attributes['time']))
|
||||
{
|
||||
$attributes['date'] = $this->user->format_date($attributes['time']);
|
||||
}
|
||||
if (isset($attributes['user_id']))
|
||||
{
|
||||
$attributes['profile_url'] = str_replace('{USER_ID}', $attributes['user_id'], $this->profile_url);
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user