diff options
Diffstat (limited to 'phpBB')
21 files changed, 2146 insertions, 130 deletions
diff --git a/phpBB/composer.json b/phpBB/composer.json index dcb4000d34..2c734847cf 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -28,6 +28,7 @@ "php": ">=5.3.9", "lusitanian/oauth": "0.2.*", "patchwork/utf8": "1.1.*", + "s9e/text-formatter": "dev-release/php5.3", "symfony/config": "2.7.*@dev", "symfony/console": "2.7.*@dev", "symfony/dependency-injection": "2.7.*@dev", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index b6957aa667..8a634a1ffd 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2038bc8bd0fea66b22774ca7bca11a79", + "hash": "25df57c9c90534dcc86d31175b248719", "packages": [ { "name": "lusitanian/oauth", @@ -145,7 +145,7 @@ "Psr\\Log\\": "" } }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "http://packagist.org/downloads/", "license": [ "MIT" ], @@ -164,6 +164,64 @@ "time": "2012-12-21 11:40:51" }, { + "name": "s9e/text-formatter", + "version": "dev-release/php5.3", + "source": { + "type": "git", + "url": "https://github.com/s9e/TextFormatter.git", + "reference": "4e0d311a3c56d0db4a7789e31457053be8148283" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/4e0d311a3c56d0db4a7789e31457053be8148283", + "reference": "4e0d311a3c56d0db4a7789e31457053be8148283", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "lib-pcre": ">=7.2", + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "Allows international URLs to be accepted by the URL filter", + "ext-json": "Enables the generation of a JavaScript parser", + "ext-mbstring": "Enables some optimizations in the PHP renderer", + "ext-tokenizer": "Enables optimizations in the PHP renderer", + "ext-xsl": "Enables the XSLT renderer", + "ext-zlib": "Enables gzip compression when scraping content via the MediaEmbed plugin" + }, + "type": "library", + "autoload": { + "psr-4": { + "s9e\\TextFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Multi-purpose text formatting and markup library. Plugins offer support for BBCodes, Markdown, emoticons, HTML, embedding media (YouTube, etc...), enhanced typography and more.", + "keywords": [ + "bbcode", + "bbcodes", + "blog", + "censor", + "embed", + "emoji", + "emoticons", + "engine", + "forum", + "html", + "markdown", + "markup", + "media", + "parser", + "shortcodes" + ], + "time": "2015-03-30 22:52:16" + }, + { "name": "symfony/config", "version": "2.7.x-dev", "target-dir": "Symfony/Component/Config", @@ -960,16 +1018,14 @@ "Twig_": "lib/" } }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "http://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" + "email": "fabien@symfony.com" }, { "name": "Armin Ronacher", @@ -1376,8 +1432,7 @@ "authors": [ { "name": "Michiel Rook", - "email": "mrook@php.net", - "role": "Lead" + "email": "mrook@php.net" }, { "name": "Phing Community", @@ -2530,6 +2585,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "s9e/text-formatter": 20, "symfony/config": 20, "symfony/console": 20, "symfony/dependency-injection": 20, diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index 36f22d72d6..4f1aba953f 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -13,6 +13,7 @@ imports: - { resource: services_notification.yml } - { resource: services_password.yml } - { resource: services_profilefield.yml } + - { resource: services_text_formatter.yml } - { resource: services_twig.yml } - { resource: services_user.yml } diff --git a/phpBB/config/default/container/services_text_formatter.yml b/phpBB/config/default/container/services_text_formatter.yml new file mode 100644 index 0000000000..972be31b31 --- /dev/null +++ b/phpBB/config/default/container/services_text_formatter.yml @@ -0,0 +1,61 @@ +parameters: + text_formatter.cache.dir: %core.root_path%cache/%core.environment%/ + text_formatter.cache.parser.key: _text_formatter_parser + text_formatter.cache.renderer.key: _text_formatter_renderer + +services: + text_formatter.cache: + alias: text_formatter.s9e.factory + + text_formatter.data_access: + class: phpbb\textformatter\data_access + arguments: + - @dbal.conn + - %tables.bbcodes% + - %tables.smilies% + - %tables.styles% + - %tables.words% + - %core.root_path%styles/ + + text_formatter.parser: + alias: text_formatter.s9e.parser + + text_formatter.renderer: + alias: text_formatter.s9e.renderer + + text_formatter.utils: + alias: text_formatter.s9e.utils + + text_formatter.s9e.factory: + class: phpbb\textformatter\s9e\factory + arguments: + - @text_formatter.data_access + - @cache.driver + - @dispatcher + - %text_formatter.cache.dir% + - %text_formatter.cache.parser.key% + - %text_formatter.cache.renderer.key% + + text_formatter.s9e.parser: + class: phpbb\textformatter\s9e\parser + arguments: + - @cache.driver + - %text_formatter.cache.parser.key% + - @user + - @text_formatter.s9e.factory + - @dispatcher + + text_formatter.s9e.renderer: + class: phpbb\textformatter\s9e\renderer + arguments: + - @cache.driver + - %text_formatter.cache.dir% + - %text_formatter.cache.renderer.key% + - @text_formatter.s9e.factory + - @dispatcher + calls: + - [configure_smilies_path, [@config, @path_helper]] + - [configure_user, [@user, @config, @auth]] + + text_formatter.s9e.utils: + class: phpbb\textformatter\s9e\utils diff --git a/phpBB/config/default/container/tables.yml b/phpBB/config/default/container/tables.yml index 2fe2a33be8..00067d5abe 100644 --- a/phpBB/config/default/container/tables.yml +++ b/phpBB/config/default/container/tables.yml @@ -1,6 +1,7 @@ parameters: tables.auth_provider_oauth_token_storage: %core.table_prefix%oauth_tokens tables.auth_provider_oauth_account_assoc: %core.table_prefix%oauth_accounts + tables.bbcodes: %core.table_prefix%bbcodes tables.captcha_qa_questions: %core.table_prefix%captcha_questions tables.captcha_qa_answers: %core.table_prefix%captcha_answers tables.captcha_qa_confirm: %core.table_prefix%qa_confirm @@ -18,6 +19,9 @@ parameters: tables.profile_fields_options_language: %core.table_prefix%profile_fields_lang tables.profile_fields_language: %core.table_prefix%profile_lang tables.posts: %core.table_prefix%posts + tables.smilies: %core.table_prefix%smilies + tables.styles: %core.table_prefix%styles tables.topics: %core.table_prefix%topics tables.user_notifications: %core.table_prefix%user_notifications tables.users: %core.table_prefix%users + tables.words: %core.table_prefix%words diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index a5cd48c444..d451b4d899 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -25,7 +25,7 @@ class acp_bbcodes function main($id, $mode) { - global $db, $user, $auth, $template, $cache, $request, $phpbb_dispatcher; + global $db, $user, $auth, $template, $cache, $request, $phpbb_dispatcher, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx, $phpbb_log; $user->add_lang('acp/posting'); @@ -269,6 +269,7 @@ class acp_bbcodes $db->sql_query('INSERT INTO ' . BBCODES_TABLE . $db->sql_build_array('INSERT', $sql_ary)); $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); $lang = 'BBCODE_ADDED'; $log_action = 'LOG_BBCODE_ADD'; @@ -280,6 +281,7 @@ class acp_bbcodes WHERE bbcode_id = ' . $bbcode_id; $db->sql_query($sql); $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); $lang = 'BBCODE_EDITED'; $log_action = 'LOG_BBCODE_EDIT'; @@ -319,6 +321,7 @@ class acp_bbcodes { $db->sql_query('DELETE FROM ' . BBCODES_TABLE . " WHERE bbcode_id = $bbcode_id"); $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_BBCODE_DELETE', false, array($row['bbcode_tag'])); if ($request->is_ajax()) diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index fdf366097a..5d1756de45 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -28,7 +28,7 @@ class acp_icons function main($id, $mode) { - global $db, $user, $auth, $template, $cache; + global $db, $user, $auth, $template, $cache, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; global $request, $phpbb_container; @@ -486,6 +486,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); $level = ($icons_updated) ? E_USER_NOTICE : E_USER_WARNING; $errormsgs = ''; @@ -661,6 +662,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); trigger_error($user->lang[$lang . '_IMPORT_SUCCESS'] . adm_back_link($this->u_action)); } @@ -783,6 +785,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); if ($request->is_ajax()) { @@ -848,6 +851,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); if ($request->is_ajax()) { diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index c49ccdf479..9a99666c75 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -352,6 +352,11 @@ class acp_main $config->increment('assets_version', 1); $cache->purge(); + // Remove old renderers from the text_formatter service. Since this + // operation is performed after the cache is purged, there is not "current" + // renderer and in effect all renderers will be purged + $phpbb_container->get('text_formatter.cache')->tidy(); + // Clear permissions $auth->acl_clear_prefetch(); phpbb_cache_moderators($db, $cache, $auth); diff --git a/phpBB/includes/acp/acp_styles.php b/phpBB/includes/acp/acp_styles.php index 45f224f8b1..b652fd6587 100644 --- a/phpBB/includes/acp/acp_styles.php +++ b/phpBB/includes/acp/acp_styles.php @@ -53,6 +53,9 @@ class acp_styles /** @var \phpbb\auth\auth */ protected $auth; + /** @var \phpbb\textformatter\cache_interface */ + protected $text_formatter_cache; + /** @var string */ protected $phpbb_root_path; @@ -61,7 +64,7 @@ class acp_styles public function main($id, $mode) { - global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config; + global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $phpbb_container; $this->db = $db; $this->user = $user; @@ -69,6 +72,7 @@ class acp_styles $this->request = $request; $this->cache = $cache; $this->auth = $auth; + $this->text_formatter_cache = $phpbb_container->get('text_formatter.cache'); $this->config = $config; $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $phpEx; @@ -216,6 +220,12 @@ class acp_styles } } + // Invalidate the text formatter's cache for the new styles to take effect + if (!empty($installed_names)) + { + $this->text_formatter_cache->invalidate(); + } + // Show message if (!count($messages)) { diff --git a/phpBB/includes/acp/acp_words.php b/phpBB/includes/acp/acp_words.php index d28aa8e60b..ea8d47a109 100644 --- a/phpBB/includes/acp/acp_words.php +++ b/phpBB/includes/acp/acp_words.php @@ -28,7 +28,7 @@ class acp_words function main($id, $mode) { - global $db, $user, $auth, $template, $cache, $phpbb_log, $request; + global $db, $user, $auth, $template, $cache, $phpbb_log, $request, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; $user->add_lang('acp/posting'); @@ -115,6 +115,7 @@ class acp_words } $cache->destroy('_word_censors'); + $phpbb_container->get('text_formatter.cache')->invalidate(); $log_action = ($word_id) ? 'LOG_WORD_EDIT' : 'LOG_WORD_ADD'; @@ -148,6 +149,7 @@ class acp_words $db->sql_query($sql); $cache->destroy('_word_censors'); + $phpbb_container->get('text_formatter.cache')->invalidate(); $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_WORD_DELETE', false, array($deleted_word)); diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index bdbc8a92fa..60511d89a4 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -389,46 +389,68 @@ function phpbb_clean_search_string($search_string) /** * Decode text whereby text is coming from the db and expected to be pre-parsed content * We are placing this outside of the message parser because we are often in need of it... +* +* NOTE: special chars are kept encoded +* +* @param string &$message Original message, passed by reference +* @param string $bbcode_uid BBCode UID +* @return null */ function decode_message(&$message, $bbcode_uid = '') { - global $config; + global $phpbb_container; - if ($bbcode_uid) + if (preg_match('#^<[rt][ >]#', $message)) { - $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid"); - $replace = array("\n", '', '', '', ''); + $message = htmlspecialchars($phpbb_container->get('text_formatter.utils')->unparse($message), ENT_COMPAT); } else { - $match = array('<br />'); - $replace = array("\n"); - } + if ($bbcode_uid) + { + $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid"); + $replace = array("\n", '', '', '', ''); + } + else + { + $match = array('<br />'); + $replace = array("\n"); + } - $message = str_replace($match, $replace, $message); + $message = str_replace($match, $replace, $message); - $match = get_preg_expression('bbcode_htm'); - $replace = array('\1', '\1', '\2', '\1', '', ''); + $match = get_preg_expression('bbcode_htm'); + $replace = array('\1', '\1', '\2', '\1', '', ''); - $message = preg_replace($match, $replace, $message); + $message = preg_replace($match, $replace, $message); + } } /** -* Strips all bbcode from a text and returns the plain content +* Strips all bbcode from a text in place */ function strip_bbcode(&$text, $uid = '') { - if (!$uid) + global $phpbb_container; + + if (preg_match('#^<[rt][ >]#', $text)) { - $uid = '[0-9a-z]{5,}'; + $text = $phpbb_container->get('text_formatter.utils')->clean_formatting($text); } + else + { + if (!$uid) + { + $uid = '[0-9a-z]{5,}'; + } - $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:".*"|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text); + $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:".*"|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text); - $match = get_preg_expression('bbcode_htm'); - $replace = array('\1', '\1', '\2', '\1', '', ''); + $match = get_preg_expression('bbcode_htm'); + $replace = array('\1', '\1', '\2', '\1', '', ''); - $text = preg_replace($match, $replace, $text); + $text = preg_replace($match, $replace, $text); + } } /** @@ -438,7 +460,7 @@ function strip_bbcode(&$text, $uid = '') function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text = true) { static $bbcode; - global $phpbb_dispatcher; + global $phpbb_dispatcher, $phpbb_container; if ($text === '') { @@ -459,34 +481,56 @@ function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text $vars = array('text', 'uid', 'bitfield', 'flags', 'censor_text'); extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_before', compact($vars))); - if ($censor_text) + if (preg_match('#^<[rt][ >]#', $text)) { - $text = censor_text($text); - } + $renderer = $phpbb_container->get('text_formatter.renderer'); - // Parse bbcode if bbcode uid stored and bbcode enabled - if ($uid && ($flags & OPTION_FLAG_BBCODE)) - { - if (!class_exists('bbcode')) + // Temporarily switch off viewcensors if applicable + $old_censor = $renderer->get_viewcensors(); + if ($old_censor !== $censor_text) { - global $phpbb_root_path, $phpEx; - include($phpbb_root_path . 'includes/bbcode.' . $phpEx); + $renderer->set_viewcensors($censor_text); } - if (empty($bbcode)) + $text = $renderer->render($text); + + // Restore the previous value + if ($old_censor !== $censor_text) { - $bbcode = new bbcode($bitfield); + $renderer->set_viewcensors($old_censor); } - else + } + else + { + if ($censor_text) { - $bbcode->bbcode($bitfield); + $text = censor_text($text); } - $bbcode->bbcode_second_pass($text, $uid); - } + // Parse bbcode if bbcode uid stored and bbcode enabled + if ($uid && ($flags & OPTION_FLAG_BBCODE)) + { + if (!class_exists('bbcode')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/bbcode.' . $phpEx); + } + + if (empty($bbcode)) + { + $bbcode = new bbcode($bitfield); + } + else + { + $bbcode->bbcode($bitfield); + } - $text = bbcode_nl2br($text); - $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES)); + $bbcode->bbcode_second_pass($text, $uid); + } + + $text = bbcode_nl2br($text); + $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES)); + } /** * Use this event to modify the text after it is parsed @@ -550,11 +594,6 @@ function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bb $uid = $bitfield = ''; $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0); - if ($text === '') - { - return; - } - if (!class_exists('parse_message')) { include($phpbb_root_path . 'includes/message_parser.' . $phpEx); diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index fe892233ce..8353ae6843 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -1116,7 +1116,7 @@ class parse_message extends bbcode_firstpass */ function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post') { - global $config, $db, $user, $phpbb_dispatcher; + global $config, $db, $user, $phpbb_dispatcher, $phpbb_container; $this->mode = $mode; @@ -1145,12 +1145,6 @@ class parse_message extends bbcode_firstpass $this->decode_message(); } - // Do some general 'cleanup' first before processing message, - // e.g. remove excessive newlines(?), smilies(?) - $match = array('#(script|about|applet|activex|chrome):#i'); - $replace = array("\\1:"); - $this->message = preg_replace($match, $replace, trim($this->message)); - // Store message length... $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)); @@ -1223,47 +1217,29 @@ class parse_message extends bbcode_firstpass return (!$update_this_message) ? $return_message : $this->warn_msg; } - // Prepare BBcode (just prepares some tags for better parsing) - if ($allow_bbcode && strpos($this->message, '[') !== false) - { - $this->bbcode_init(); - $disallow = array('img', 'flash', 'quote', 'url'); - foreach ($disallow as $bool) - { - if (!${'allow_' . $bool . '_bbcode'}) - { - $this->bbcodes[$bool]['disabled'] = true; - } - } - - $this->prepare_bbcodes(); - } - - // Parse smilies - if ($allow_smilies) - { - $this->smilies($config['max_' . $mode . '_smilies']); - } - - $num_urls = 0; + // Get the parser + $parser = $phpbb_container->get('text_formatter.parser'); - // Parse BBCode - if ($allow_bbcode && strpos($this->message, '[') !== false) - { - $this->parse_bbcode(); - $num_urls += $this->parsed_items['url']; - } + // Set the parser's options + ($allow_bbcode) ? $parser->enable_bbcodes() : $parser->disable_bbcodes(); + ($allow_magic_url) ? $parser->enable_magic_url() : $parser->disable_magic_url(); + ($allow_smilies) ? $parser->enable_smilies() : $parser->disable_smilies(); + ($allow_img_bbcode) ? $parser->enable_bbcode('img') : $parser->disable_bbcode('img'); + ($allow_flash_bbcode) ? $parser->enable_bbcode('flash') : $parser->disable_bbcode('flash'); + ($allow_quote_bbcode) ? $parser->enable_bbcode('quote') : $parser->disable_bbcode('quote'); + ($allow_url_bbcode) ? $parser->enable_bbcode('url') : $parser->disable_bbcode('url'); - // Parse URL's - if ($allow_magic_url) - { - $this->magic_url(generate_board_url()); + // Set some config values + $parser->set_vars(array( + 'max_font_size' => $config['max_' . $this->mode . '_font_size'], + 'max_img_height' => $config['max_' . $this->mode . '_img_height'], + 'max_img_width' => $config['max_' . $this->mode . '_img_width'], + 'max_smilies' => $config['max_' . $this->mode . '_smilies'], + 'max_urls' => $config['max_' . $this->mode . '_urls'] + )); - if ($config['max_' . $mode . '_urls']) - { - $num_urls += preg_match_all('#\<!-- ([lmwe]) --\>.*?\<!-- \1 --\>#', $this->message, $matches); - } - } + // Parse this message + $this->message = $parser->parse(htmlspecialchars_decode($this->message, ENT_QUOTES)); // Check for out-of-bounds characters that are currently // not supported by utf8_bin in MySQL @@ -1282,10 +1258,12 @@ class parse_message extends bbcode_firstpass return (!$update_this_message) ? $return_message : $this->warn_msg; } - // Check number of links - if ($config['max_' . $mode . '_urls'] && $num_urls > $config['max_' . $mode . '_urls']) + // Check for errors + $errors = $parser->get_errors(); + if ($errors) { - $this->warn_msg[] = sprintf($user->lang['TOO_MANY_URLS'], $config['max_' . $mode . '_urls']); + $this->warn_msg = array_merge($this->warn_msg, $errors); + return (!$update_this_message) ? $return_message : $this->warn_msg; } @@ -1305,7 +1283,7 @@ class parse_message extends bbcode_firstpass */ function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true) { - global $phpbb_dispatcher; + global $phpbb_container, $phpbb_dispatcher; // If false, then the parsed message get returned but internal message not processed. if (!$update_this_message) @@ -1314,26 +1292,25 @@ class parse_message extends bbcode_firstpass $return_message = &$this->message; } - if ($this->message_status == 'plain') + // NOTE: message_status is unreliable for detecting unparsed text because some callers + // change $this->message without resetting $this->message_status to 'plain' so we + // inspect the message instead + //if ($this->message_status == 'plain') + if (!preg_match('/^<[rt][ >]/', $this->message)) { // Force updating message - of course. $this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_flash_bbcode, $this->allow_quote_bbcode, $this->allow_url_bbcode, true); } - // Replace naughty words such as farty pants - $this->message = censor_text($this->message); - - // Parse BBcode - if ($allow_bbcode) + // There's a bug when previewing a topic with no poll, because the empty title of the poll + // gets parsed but $this->message still ends up empty. This fixes it, until a proper fix is + // devised + if ($this->message === '') { - $this->bbcode_cache_init(); - - // We are giving those parameters to be able to use the bbcode class on its own - $this->bbcode_second_pass($this->message, $this->bbcode_uid); + $this->message = $phpbb_container->get('text_formatter.parser')->parse($this->message); } - $this->message = bbcode_nl2br($this->message); - $this->message = smiley_text($this->message, !$allow_smilies); + $this->message = $phpbb_container->get('text_formatter.renderer')->render($this->message); $text = $this->message; $uid = $this->bbcode_uid; @@ -1797,24 +1774,22 @@ class parse_message extends bbcode_firstpass $poll_max_options = $poll['poll_max_options']; - // Parse Poll Option text ;) + // Parse Poll Option text $tmp_message = $this->message; - $this->message = $poll['poll_option_text']; - $bbcode_bitfield = $this->bbcode_bitfield; - $poll['poll_option_text'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll'); + $poll['poll_options'] = explode("\n", trim($poll['poll_option_text'])); + $poll['poll_options_size'] = sizeof($poll['poll_options']); - $bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield)); - $this->message = $tmp_message; + foreach ($poll['poll_options'] as &$poll_option) + { + $this->message = $poll_option; + $poll_option = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll'); + } + unset($poll_option); + $poll['poll_option_text'] = implode("\n", $poll['poll_options']); // Parse Poll Title - $tmp_message = $this->message; $this->message = $poll['poll_title']; - $this->bbcode_bitfield = $bbcode_bitfield; - - $poll['poll_options'] = explode("\n", trim($poll['poll_option_text'])); - $poll['poll_options_size'] = sizeof($poll['poll_options']); - if (!$poll['poll_title'] && $poll['poll_options_size']) { $this->warn_msg[] = $user->lang['NO_POLL_TITLE']; @@ -1832,10 +1807,6 @@ class parse_message extends bbcode_firstpass } } - $this->bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield)); - $this->message = $tmp_message; - unset($tmp_message); - if (sizeof($poll['poll_options']) == 1) { $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS']; @@ -1850,6 +1821,8 @@ class parse_message extends bbcode_firstpass } $poll['poll_max_options'] = ($poll['poll_max_options'] < 1) ? 1 : (($poll['poll_max_options'] > $config['max_poll_options']) ? $config['max_poll_options'] : $poll['poll_max_options']); + + $this->message = $tmp_message; } /** diff --git a/phpBB/phpbb/textformatter/cache_interface.php b/phpBB/phpbb/textformatter/cache_interface.php new file mode 100644 index 0000000000..f6b5f195c7 --- /dev/null +++ b/phpBB/phpbb/textformatter/cache_interface.php @@ -0,0 +1,31 @@ +<?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; + +/** +* Currently only used to signal that something that could effect the rendering has changed. +* BBCodes, smilies, censored words, templates, etc... +*/ +interface cache_interface +{ + /** + * Invalidate and/or regenerate this text formatter's cache(s) + */ + public function invalidate(); + + /** + * Tidy/prune this text formatter's cache(s) + */ + public function tidy(); +} diff --git a/phpBB/phpbb/textformatter/data_access.php b/phpBB/phpbb/textformatter/data_access.php new file mode 100644 index 0000000000..8938d66935 --- /dev/null +++ b/phpBB/phpbb/textformatter/data_access.php @@ -0,0 +1,228 @@ +<?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; + +/** +* Data access layer that fetchs BBCodes, smilies and censored words from the database. +* To be extended to include insert/update/delete operations. +* +* Also used to get templates. +*/ +class data_access +{ + /** + * @var string Name of the BBCodes table + */ + protected $bbcodes_table; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var string Name of the smilies table + */ + protected $smilies_table; + + /** + * @var string Name of the styles table + */ + protected $styles_table; + + /** + * @var string Path to the styles dir + */ + protected $styles_path; + + /** + * @var string Name of the words table + */ + protected $words_table; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param string $bbcodes_table Name of the BBCodes table + * @param string $smilies_table Name of the smilies table + * @param string $styles_table Name of the styles table + * @param string $words_table Name of the words table + * @param string $styles_path Path to the styles dir + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $bbcodes_table, $smilies_table, $styles_table, $words_table, $styles_path) + { + $this->db = $db; + + $this->bbcodes_table = $bbcodes_table; + $this->smilies_table = $smilies_table; + $this->styles_table = $styles_table; + $this->words_table = $words_table; + + $this->styles_path = $styles_path; + } + + /** + * Return the list of custom BBCodes + * + * @return array + */ + public function get_bbcodes() + { + $sql = 'SELECT bbcode_match, bbcode_tpl FROM ' . $this->bbcodes_table; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Return the list of smilies + * + * @return array + */ + public function get_smilies() + { + // NOTE: smilies that are displayed on the posting page are processed first because they're + // typically the most used smilies and it ends up producing a slightly more efficient + // renderer + $sql = 'SELECT code, emotion, smiley_url, smiley_width, smiley_height + FROM ' . $this->smilies_table . ' + ORDER BY display_on_posting DESC'; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Return the list of installed styles + * + * @return array + */ + protected function get_styles() + { + $sql = 'SELECT style_id, style_path, bbcode_bitfield FROM ' . $this->styles_table; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Return the bbcode.html template for every installed style + * + * @return array 2D array. style_id as keys, each element is an array with a "template" element that contains the style's bbcode.html and a "bbcodes" element that contains the name of each BBCode that is to be stylised + */ + public function get_styles_templates() + { + $templates = array(); + + $bbcode_ids = array( + 'quote' => 0, + 'b' => 1, + 'i' => 2, + 'url' => 3, + 'img' => 4, + 'size' => 5, + 'color' => 6, + 'u' => 7, + 'code' => 8, + 'list' => 9, + '*' => 9, + 'email' => 10, + 'flash' => 11, + 'attachment' => 12, + ); + + $styles = array(); + foreach ($this->get_styles() as $row) + { + $styles[$row['style_id']] = $row; + } + + foreach ($styles as $style_id => $style) + { + $bbcodes = array(); + + // Collect the name of the BBCodes whose bit is set in the style's bbcode_bitfield + $template_bitfield = new \bitfield($style['bbcode_bitfield']); + foreach ($bbcode_ids as $bbcode_name => $bit) + { + if ($template_bitfield->get($bit)) + { + $bbcodes[] = $bbcode_name; + } + } + + $filename = $this->resolve_style_filename($styles, $style); + if ($filename === false) + { + // Ignore this style, it will use the default templates + continue; + } + + $templates[$style_id] = array( + 'bbcodes' => $bbcodes, + 'template' => file_get_contents($filename), + ); + } + + return $templates; + } + + /** + * Resolve inheritance for given style and return the path to their bbcode.html file + * + * @param array $styles Associative array of [style_id => style] containing all styles + * @param array $style Style for which we resolve + * @return string|bool Path to this style's bbcode.html, or FALSE + */ + protected function resolve_style_filename(array $styles, array $style) + { + // Look for a bbcode.html in this style's dir + $filename = $this->styles_path . $style['style_path'] . '/template/bbcode.html'; + if (file_exists($filename)) + { + return $filename; + } + + // Resolve using this style's parent + $parent_id = $style['style_parent_id']; + if ($parent_id && !empty($styles[$parent_id])) + { + return $this->resolve_style_filename($styles, $styles[$parent_id]); + } + + return false; + } + + /** + * Return the list of censored words + * + * @return array + */ + public function get_censored_words() + { + $sql = 'SELECT word, replacement FROM ' . $this->words_table; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } +} diff --git a/phpBB/phpbb/textformatter/parser_interface.php b/phpBB/phpbb/textformatter/parser_interface.php new file mode 100644 index 0000000000..3cb9f8e977 --- /dev/null +++ b/phpBB/phpbb/textformatter/parser_interface.php @@ -0,0 +1,111 @@ +<?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 parser_interface +{ + /** + * Parse given text + * + * @param string $text + * @return string + */ + public function parse($text); + + /** + * Disable a specific BBCode + * + * @param string $name BBCode name + * @return null + */ + public function disable_bbcode($name); + + /** + * Disable BBCodes in general + */ + public function disable_bbcodes(); + + /** + * Disable the censor + */ + public function disable_censor(); + + /** + * Disable magic URLs + */ + public function disable_magic_url(); + + /** + * Disable smilies + */ + public function disable_smilies(); + + /** + * Enable a specific BBCode + * + * @param string $name BBCode name + * @return null + */ + public function enable_bbcode($name); + + /** + * Enable BBCodes in general + */ + public function enable_bbcodes(); + + /** + * Enable the censor + */ + public function enable_censor(); + + /** + * Enable magic URLs + */ + public function enable_magic_url(); + + /** + * Enable smilies + */ + public function enable_smilies(); + + /** + * Get the list of errors that were generated during last parsing + * + * @return array + */ + public function get_errors(); + + /** + * Set a variable to be used by the parser + * + * - max_font_size + * - max_img_height + * - max_img_width + * - max_smilies + * - max_urls + * + * @param string $name + * @param mixed $value + * @return null + */ + public function set_var($name, $value); + + /** + * Set multiple variables to be used by the parser + * + * @param array $vars Associative array of [name => value] + * @return null + */ + public function set_vars(array $vars); +} diff --git a/phpBB/phpbb/textformatter/renderer_interface.php b/phpBB/phpbb/textformatter/renderer_interface.php new file mode 100644 index 0000000000..609b0bb642 --- /dev/null +++ b/phpBB/phpbb/textformatter/renderer_interface.php @@ -0,0 +1,92 @@ +<?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 renderer_interface +{ + /** + * Render given text + * + * @param string $text Text, as parsed by something that implements \phpbb\textformatter\parser + * @return string + */ + public function render($text); + + /** + * Set the smilies' path + * + * @return null + */ + public function set_smilies_path($path); + + /** + * Return the value of the "viewcensors" option + * + * @return bool Option's value + */ + public function get_viewcensors(); + + /** + * Return the value of the "viewflash" option + * + * @return bool Option's value + */ + public function get_viewflash(); + + /** + * Return the value of the "viewimg" option + * + * @return bool Option's value + */ + public function get_viewimg(); + + /** + * Return the value of the "viewsmilies" option + * + * @return bool Option's value + */ + public function get_viewsmilies(); + + /** + * Set the "viewcensors" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewcensors($value); + + /** + * Set the "viewflash" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewflash($value); + + /** + * Set the "viewimg" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewimg($value); + + /** + * Set the "viewsmilies" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewsmilies($value); +} diff --git a/phpBB/phpbb/textformatter/s9e/factory.php b/phpBB/phpbb/textformatter/s9e/factory.php new file mode 100644 index 0000000000..9576abe1f0 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/factory.php @@ -0,0 +1,545 @@ +<?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\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 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]{TEXT}[/CODE]', + 'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]', + 'email' => '[EMAIL={EMAIL;useContent}]{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}]{TEXT}[/LIST]', + 'li' => '[* $tagName=LI]{TEXT}[/*]', + 'quote' => + "[QUOTE + author={TEXT1;optional} + url={URL;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}]{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}" 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 href="mailto:{EMAIL}"><xsl:apply-templates/></a>', + ); + + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * Constructor + * + * @param \phpbb\textformatter\data_access $data_access + * @param \phpbb\cache\driver\driver_interface $cache + * @param \phpbb\event\dispatcher_interface $dispatcher + * @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, $cache_dir, $cache_key_parser, $cache_key_renderer) + { + $this->cache = $cache; + $this->cache_dir = $cache_dir; + $this->cache_key_parser = $cache_key_parser; + $this->cache_key_renderer = $cache_key_renderer; + $this->data_access = $data_access; + $this->dispatcher = $dispatcher; + } + + /** + * {@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))); + + // 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(); + + // Add default BBCodes + foreach ($this->get_default_bbcodes($configurator) as $bbcode) + { + $configurator->BBCodes->addCustom($bbcode['usage'], $bbcode['template']); + } + + // 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'] + ); + + try + { + $configurator->BBCodes->addCustom($row['bbcode_match'], new UnsafeTemplate($tpl)); + } + catch (\Exception $e) + { + /** + * @todo log an error? + */ + } + } + + // Load smilies + foreach ($this->data_access->get_smilies() as $row) + { + $configurator->Emoticons->add( + $row['code'], + '<img class="smilies" src="{$T_SMILIES_PATH}/' . htmlspecialchars($row['smiley_url']) . '" alt="{.}" title="' . htmlspecialchars($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 .\\]]'; + } + + // 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) + { + // NOTE: words are stored as HTML, we need to decode them to plain text + $configurator->Censor->add(htmlspecialchars_decode($row['word']), htmlspecialchars_decode($row['replacement'])); + } + } + + // Load the magic links plugins. We do that after BBCodes so that they use the same tags + $configurator->plugins->load('Autoemail'); + $configurator->plugins->load('Autolink', array('matchWww' => true)); + + // 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; + + /** + * 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(); + $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); + } + + /** + * 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 + preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END .*? -->#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']; + + $fragments['quote_username_open'] = str_replace( + '{USERNAME}', + '<xsl:choose> + <xsl:when test="@url">' . str_replace('{DESCRIPTION}', '{USERNAME}', $fragments['url']) . '</xsl:when> + <xsl:otherwise>{USERNAME}</xsl:otherwise> + </xsl:choose>', + $fragments['quote_username_open'] + ); + + $templates['quote'] = + '<xsl:choose> + <xsl:when test="@author"> + ' . $fragments['quote_username_open'] . '<xsl:apply-templates/>' . $fragments['quote_close'] . ' + </xsl:when> + <xsl:otherwise> + ' . $fragments['quote_open'] . '<xsl:apply-templates/>' . $fragments['quote_close'] . ' + </xsl:otherwise> + </xsl:choose>'; + + // 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; + } +} diff --git a/phpBB/phpbb/textformatter/s9e/parser.php b/phpBB/phpbb/textformatter/s9e/parser.php new file mode 100644 index 0000000000..77328ee4d9 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/parser.php @@ -0,0 +1,404 @@ +<?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\BuiltInFilters; +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; + + /** + * @var \phpbb\user User object, used for translating errors + */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\cache\driver_interface $cache + * @param string $key Cache key + * @param \phpbb\user $user + * @param factory $factory + * @param \phpbb\event\dispatcher_interface $dispatcher + */ + public function __construct(\phpbb\cache\driver\driver_interface $cache, $key, \phpbb\user $user, 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; + $this->user = $user; + $parser = $this; + + /** + * Configure the parser service + * + * Can be used to: + * - toggle features according to the user's preferences, + * - toggle BBCodes according to the user's permissions, + * - register variables or custom parsers in the s9e\TextFormatter + * - configure the s9e\TextFormatter parser + * + * @event core.text_formatter_s9e_parser_setup + * @var \phpbb\textformatter\s9e\parser parser This parser service + * @var \phpbb\user user Current user + * @since 3.2.0-a1 + */ + $vars = array('parser', 'user'); + 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'); + } + + /** + * {@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'); + } + + /** + * {@inheritdoc} + * + * This will translate the log entries found in s9e\TextFormatter's logger into phpBB error + * messages + */ + public function get_errors() + { + $errors = array(); + + foreach ($this->parser->getLogger()->get() as $entry) + { + list($type, $msg, $context) = $entry; + + if ($msg === 'Tag limit exceeded') + { + if ($context['tagName'] === 'E') + { + $errors[] = $this->user->lang('TOO_MANY_SMILIES', $context['tagLimit']); + } + else if ($context['tagName'] === 'URL') + { + $errors[] = $this->user->lang('TOO_MANY_URLS', $context['tagLimit']); + } + } + else if ($msg === 'MAX_FONT_SIZE_EXCEEDED') + { + $errors[] = $this->user->lang($msg, $context['max_size']); + } + else if (preg_match('/^MAX_(?:FLASH|IMG)_(HEIGHT|WIDTH)_EXCEEDED$/D', $msg, $m)) + { + $errors[] = $this->user->lang($msg, $context['max_' . strtolower($m[1])]); + } + else if ($msg === 'Tag is disabled') + { + $name = strtolower($context['tag']->getName()); + $errors[] = $this->user->lang('UNAUTHORISED_BBCODE', '[' . $name . ']'); + } + else if ($msg === 'UNABLE_GET_IMAGE_SIZE') + { + $errors[] = $this->user->lang($msg); + } + } + + return array_unique($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 = BuiltInFilters::filterUrl($url, $url_config, $logger); + + if ($url === false) + { + return false; + } + + if ($max_height || $max_width) + { + $stats = @getimagesize($url); + + if ($stats === false) + { + $logger->err('UNABLE_GET_IMAGE_SIZE'); + + return false; + } + + if ($max_height && $max_height < $stats[1]) + { + $logger->err('MAX_IMG_HEIGHT_EXCEEDED', array('max_height' => $max_height)); + + return false; + } + + if ($max_width && $max_width < $stats[0]) + { + $logger->err('MAX_IMG_WIDTH_EXCEEDED', array('max_width' => $max_width)); + + return false; + } + } + + return $url; + } +} diff --git a/phpBB/phpbb/textformatter/s9e/renderer.php b/phpBB/phpbb/textformatter/s9e/renderer.php new file mode 100644 index 0000000000..8999f1d25f --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/renderer.php @@ -0,0 +1,338 @@ +<?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 \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))); + } + + /** + * 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) + { + $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))); + + if (isset($this->censor) && $this->viewcensors) + { + // NOTE: censorHtml() is XML-safe + $xml = $this->censor->censorHtml($xml, true); + } + + $html = $this->renderer->render($xml); + if (stripos($html, '<code') !== false) + { + $html = $this->replace_tabs_in_code($html); + } + + /** + * 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; + } + + /** + * Replace tabs in code elements + * + * @see bbcode::bbcode_second_pass_code() + * + * @param string $html Original HTML + * @return string Modified HTML + */ + protected function replace_tabs_in_code($html) + { + return preg_replace_callback( + '((<code[^>]*>)(.*?)(</code>))is', + function ($captures) + { + $code = $captures[2]; + + $code = str_replace("\t", ' ', $code); + $code = str_replace(' ', ' ', $code); + $code = str_replace(' ', ' ', $code); + $code = str_replace("\n ", "\n ", $code); + + // keep space at the beginning + if (!empty($code) && $code[0] == ' ') + { + $code = ' ' . substr($code, 1); + } + + // remove newline at the beginning + if (!empty($code) && $code[0] == "\n") + { + $code = substr($code, 1); + } + + return $captures[1] . $code . $captures[3]; + }, + $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); + } +} diff --git a/phpBB/phpbb/textformatter/s9e/utils.php b/phpBB/phpbb/textformatter/s9e/utils.php new file mode 100644 index 0000000000..2018bbf519 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/utils.php @@ -0,0 +1,60 @@ +<?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); + } + + /** + * 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); + } +} diff --git a/phpBB/phpbb/textformatter/utils_interface.php b/phpBB/phpbb/textformatter/utils_interface.php new file mode 100644 index 0000000000..132dc8ece4 --- /dev/null +++ b/phpBB/phpbb/textformatter/utils_interface.php @@ -0,0 +1,48 @@ +<?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; + +/** +* Used to manipulate a parsed text +*/ +interface utils_interface +{ + /** + * Replace BBCodes and other formatting elements with whitespace + * + * NOTE: preserves smilies as text + * + * @param string $text Parsed text + * @return string Plain text + */ + public function clean_formatting($text); + + /** + * Remove given BBCode and its content, at given nesting depth + * + * @param string $text 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($text, $bbcode_name, $depth = 0); + + /** + * Return a parsed text to its original form + * + * @param string $text Parsed text + * @return string Original plain text + */ + public function unparse($text); +} |