/phpBB/includes/bbcode.php
PHP | 705 lines | 518 code | 82 blank | 105 comment | 61 complexity | caa9f2c9a3426c3c0336908174431d72 MD5 | raw file
Possible License(s): GPL-3.0, AGPL-1.0
- <?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.
- *
- */
- /**
- * @ignore
- */
- if (!defined('IN_PHPBB'))
- {
- exit;
- }
- /**
- * BBCode class
- */
- class bbcode
- {
- var $bbcode_uid = '';
- var $bbcode_bitfield = '';
- var $bbcode_cache = array();
- var $bbcode_template = array();
- var $bbcodes = array();
- var $template_bitfield;
- /**
- * Constructor
- */
- function __construct($bitfield = '')
- {
- $this->bbcode_set_bitfield($bitfield);
- }
- /**
- * Init bbcode cache entries if bitfield is specified
- *
- * @param string $bitfield The bbcode bitfield
- */
- function bbcode_set_bitfield($bitfield = '')
- {
- if ($bitfield)
- {
- $this->bbcode_bitfield = $bitfield;
- $this->bbcode_cache_init();
- }
- }
- /**
- * Second pass bbcodes
- */
- function bbcode_second_pass(&$message, $bbcode_uid = '', $bbcode_bitfield = false)
- {
- if ($bbcode_uid)
- {
- $this->bbcode_uid = $bbcode_uid;
- }
- if ($bbcode_bitfield !== false)
- {
- $this->bbcode_bitfield = $bbcode_bitfield;
- // Init those added with a new bbcode_bitfield (already stored codes will not get parsed again)
- $this->bbcode_cache_init();
- }
- if (!$this->bbcode_bitfield)
- {
- // Remove the uid from tags that have not been transformed into HTML
- if ($this->bbcode_uid)
- {
- $message = str_replace(':' . $this->bbcode_uid, '', $message);
- }
- return;
- }
- $str = array('search' => array(), 'replace' => array());
- $preg = array('search' => array(), 'replace' => array());
- $bitfield = new bitfield($this->bbcode_bitfield);
- $bbcodes_set = $bitfield->get_all_set();
- $undid_bbcode_specialchars = false;
- foreach ($bbcodes_set as $bbcode_id)
- {
- if (!empty($this->bbcode_cache[$bbcode_id]))
- {
- foreach ($this->bbcode_cache[$bbcode_id] as $type => $array)
- {
- foreach ($array as $search => $replace)
- {
- ${$type}['search'][] = str_replace('$uid', $this->bbcode_uid, $search);
- ${$type}['replace'][] = $replace;
- }
- if (count($str['search']))
- {
- $message = str_replace($str['search'], $str['replace'], $message);
- $str = array('search' => array(), 'replace' => array());
- }
- if (count($preg['search']))
- {
- // we need to turn the entities back into their original form to allow the
- // search patterns to work properly
- if (!$undid_bbcode_specialchars)
- {
- $message = str_replace(array(':', '.'), array(':', '.'), $message);
- $undid_bbcode_specialchars = true;
- }
- foreach ($preg['search'] as $key => $search)
- {
- if (is_callable($preg['replace'][$key]))
- {
- $message = preg_replace_callback($search, $preg['replace'][$key], $message);
- }
- else
- {
- $message = preg_replace($search, $preg['replace'][$key], $message);
- }
- }
- $preg = array('search' => array(), 'replace' => array());
- }
- }
- }
- }
- // Remove the uid from tags that have not been transformed into HTML
- $message = str_replace(':' . $this->bbcode_uid, '', $message);
- }
- /**
- * Init bbcode cache
- *
- * requires: $this->bbcode_bitfield
- * sets: $this->bbcode_cache with bbcode templates needed for bbcode_bitfield
- */
- function bbcode_cache_init()
- {
- global $user, $phpbb_dispatcher, $phpbb_extension_manager, $phpbb_container, $phpbb_filesystem;
- if (empty($this->template_filename))
- {
- $this->template_bitfield = new bitfield($user->style['bbcode_bitfield']);
- $template = new \phpbb\template\twig\twig(
- $phpbb_container->get('path_helper'),
- $phpbb_container->get('config'),
- new \phpbb\template\context(),
- new \phpbb\template\twig\environment(
- $phpbb_container->get('config'),
- $phpbb_container->get('filesystem'),
- $phpbb_container->get('path_helper'),
- $phpbb_container->getParameter('core.cache_dir'),
- $phpbb_container->get('ext.manager'),
- new \phpbb\template\twig\loader()
- ),
- $phpbb_container->getParameter('core.cache_dir'),
- $phpbb_container->get('user'),
- $phpbb_container->get('template.twig.extensions.collection'),
- $phpbb_extension_manager
- );
- $template->set_style();
- $template->set_filenames(array('bbcode.html' => 'bbcode.html'));
- $this->template_filename = $template->get_source_file_for_handle('bbcode.html');
- }
- $bbcode_ids = $rowset = $sql = array();
- $bitfield = new bitfield($this->bbcode_bitfield);
- $bbcodes_set = $bitfield->get_all_set();
- foreach ($bbcodes_set as $bbcode_id)
- {
- if (isset($this->bbcode_cache[$bbcode_id]))
- {
- // do not try to re-cache it if it's already in
- continue;
- }
- $bbcode_ids[] = $bbcode_id;
- if ($bbcode_id > NUM_CORE_BBCODES)
- {
- $sql[] = $bbcode_id;
- }
- }
- if (count($sql))
- {
- global $db;
- $sql = 'SELECT *
- FROM ' . BBCODES_TABLE . '
- WHERE ' . $db->sql_in_set('bbcode_id', $sql);
- $result = $db->sql_query($sql, 3600);
- while ($row = $db->sql_fetchrow($result))
- {
- // To circumvent replacing newlines with <br /> for the generated html,
- // we use carriage returns here. They are later changed back to newlines
- $row['bbcode_tpl'] = str_replace("\n", "\r", $row['bbcode_tpl']);
- $row['second_pass_replace'] = str_replace("\n", "\r", $row['second_pass_replace']);
- $rowset[$row['bbcode_id']] = $row;
- }
- $db->sql_freeresult($result);
- }
- foreach ($bbcode_ids as $bbcode_id)
- {
- switch ($bbcode_id)
- {
- case BBCODE_ID_QUOTE:
- $this->bbcode_cache[$bbcode_id] = array(
- 'str' => array(
- '[/quote:$uid]' => $this->bbcode_tpl('quote_close', $bbcode_id)
- ),
- 'preg' => array(
- '#\[quote(?:="(.*?)")?:$uid\]((?!\[quote(?:=".*?")?:$uid\]).)?#is' => function ($match) {
- if (!isset($match[2]))
- {
- $match[2] = '';
- }
- return $this->bbcode_second_pass_quote($match[1], $match[2]);
- },
- )
- );
- break;
- case BBCODE_ID_B:
- $this->bbcode_cache[$bbcode_id] = array(
- 'str' => array(
- '[b:$uid]' => $this->bbcode_tpl('b_open', $bbcode_id),
- '[/b:$uid]' => $this->bbcode_tpl('b_close', $bbcode_id),
- )
- );
- break;
- case BBCODE_ID_I:
- $this->bbcode_cache[$bbcode_id] = array(
- 'str' => array(
- '[i:$uid]' => $this->bbcode_tpl('i_open', $bbcode_id),
- '[/i:$uid]' => $this->bbcode_tpl('i_close', $bbcode_id),
- )
- );
- break;
- case BBCODE_ID_URL:
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[url:$uid\]((.*?))\[/url:$uid\]#s' => $this->bbcode_tpl('url', $bbcode_id),
- '#\[url=([^\[]+?):$uid\](.*?)\[/url:$uid\]#s' => $this->bbcode_tpl('url', $bbcode_id),
- )
- );
- break;
- case BBCODE_ID_IMG:
- if ($user->optionget('viewimg'))
- {
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[img:$uid\](.*?)\[/img:$uid\]#s' => $this->bbcode_tpl('img', $bbcode_id),
- )
- );
- }
- else
- {
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[img:$uid\](.*?)\[/img:$uid\]#s' => str_replace('$2', '[ img ]', $this->bbcode_tpl('url', $bbcode_id, true)),
- )
- );
- }
- break;
- case BBCODE_ID_SIZE:
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[size=([\-\+]?\d+):$uid\](.*?)\[/size:$uid\]#s' => $this->bbcode_tpl('size', $bbcode_id),
- )
- );
- break;
- case BBCODE_ID_COLOR:
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '!\[color=(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z\-]+):$uid\](.*?)\[/color:$uid\]!is' => $this->bbcode_tpl('color', $bbcode_id),
- )
- );
- break;
- case BBCODE_ID_U:
- $this->bbcode_cache[$bbcode_id] = array(
- 'str' => array(
- '[u:$uid]' => $this->bbcode_tpl('u_open', $bbcode_id),
- '[/u:$uid]' => $this->bbcode_tpl('u_close', $bbcode_id),
- )
- );
- break;
- case BBCODE_ID_CODE:
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[code(?:=([a-z]+))?:$uid\](.*?)\[/code:$uid\]#is' => function ($match) {
- return $this->bbcode_second_pass_code($match[1], $match[2]);
- },
- )
- );
- break;
- case BBCODE_ID_LIST:
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#(\[\/?(list|\*):[mou]?:?$uid\])[\n]{1}#' => "\$1",
- '#(\[list=([^\[]+):$uid\])[\n]{1}#' => "\$1",
- '#\[list=([^\[]+):$uid\]#' => function ($match) {
- return $this->bbcode_list($match[1]);
- },
- ),
- 'str' => array(
- '[list:$uid]' => $this->bbcode_tpl('ulist_open_default', $bbcode_id),
- '[/list:u:$uid]' => $this->bbcode_tpl('ulist_close', $bbcode_id),
- '[/list:o:$uid]' => $this->bbcode_tpl('olist_close', $bbcode_id),
- '[*:$uid]' => $this->bbcode_tpl('listitem', $bbcode_id),
- '[/*:$uid]' => $this->bbcode_tpl('listitem_close', $bbcode_id),
- '[/*:m:$uid]' => $this->bbcode_tpl('listitem_close', $bbcode_id)
- ),
- );
- break;
- case BBCODE_ID_EMAIL:
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[email:$uid\]((.*?))\[/email:$uid\]#is' => $this->bbcode_tpl('email', $bbcode_id),
- '#\[email=([^\[]+):$uid\](.*?)\[/email:$uid\]#is' => $this->bbcode_tpl('email', $bbcode_id)
- )
- );
- break;
- case BBCODE_ID_FLASH:
- if ($user->optionget('viewflash'))
- {
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[flash=([0-9]+),([0-9]+):$uid\](.*?)\[/flash:$uid\]#' => $this->bbcode_tpl('flash', $bbcode_id),
- )
- );
- }
- else
- {
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array(
- '#\[flash=([0-9]+),([0-9]+):$uid\](.*?)\[/flash:$uid\]#' => str_replace('$1', '$3', str_replace('$2', '[ flash ]', $this->bbcode_tpl('url', $bbcode_id, true)))
- )
- );
- }
- break;
- case BBCODE_ID_ATTACH:
- $this->bbcode_cache[$bbcode_id] = array(
- 'str' => array(
- '[/attachment:$uid]' => $this->bbcode_tpl('inline_attachment_close', $bbcode_id)
- ),
- 'preg' => array(
- '#\[attachment=([0-9]+):$uid\]#' => $this->bbcode_tpl('inline_attachment_open', $bbcode_id)
- )
- );
- break;
- default:
- if (isset($rowset[$bbcode_id]))
- {
- if ($this->template_bitfield->get($bbcode_id))
- {
- // The bbcode requires a custom template to be loaded
- if (!$bbcode_tpl = $this->bbcode_tpl($rowset[$bbcode_id]['bbcode_tag'], $bbcode_id))
- {
- // For some reason, the required template seems not to be available, use the default template
- $bbcode_tpl = (!empty($rowset[$bbcode_id]['second_pass_replace'])) ? $rowset[$bbcode_id]['second_pass_replace'] : $rowset[$bbcode_id]['bbcode_tpl'];
- }
- else
- {
- // In order to use templates with custom bbcodes we need
- // to replace all {VARS} to corresponding backreferences
- // Note that backreferences are numbered from bbcode_match
- if (preg_match_all('/\{(URL|LOCAL_URL|EMAIL|TEXT|SIMPLETEXT|INTTEXT|IDENTIFIER|COLOR|NUMBER)[0-9]*\}/', $rowset[$bbcode_id]['bbcode_match'], $m))
- {
- foreach ($m[0] as $i => $tok)
- {
- $bbcode_tpl = str_replace($tok, '$' . ($i + 1), $bbcode_tpl);
- }
- }
- }
- }
- else
- {
- // Default template
- $bbcode_tpl = (!empty($rowset[$bbcode_id]['second_pass_replace'])) ? $rowset[$bbcode_id]['second_pass_replace'] : $rowset[$bbcode_id]['bbcode_tpl'];
- }
- // Replace {L_*} lang strings
- $bbcode_tpl = preg_replace_callback('/{L_([A-Z0-9_]+)}/', function ($match) use ($user) {
- return (!empty($user->lang[$match[1]])) ? $user->lang($match[1]) : ucwords(strtolower(str_replace('_', ' ', $match[1])));
- }, $bbcode_tpl);
- if (!empty($rowset[$bbcode_id]['second_pass_replace']))
- {
- // The custom BBCode requires second-pass pattern replacements
- $this->bbcode_cache[$bbcode_id] = array(
- 'preg' => array($rowset[$bbcode_id]['second_pass_match'] => $bbcode_tpl)
- );
- }
- else
- {
- $this->bbcode_cache[$bbcode_id] = array(
- 'str' => array($rowset[$bbcode_id]['second_pass_match'] => $bbcode_tpl)
- );
- }
- }
- else
- {
- $this->bbcode_cache[$bbcode_id] = false;
- }
- break;
- }
- }
- $bbcode_cache = $this->bbcode_cache;
- $bbcode_bitfield = $this->bbcode_bitfield;
- $bbcode_uid = $this->bbcode_uid;
- /**
- * Use this event to modify the bbcode_cache
- *
- * @event core.bbcode_cache_init_end
- * @var array bbcode_cache The array of cached search and replace patterns of bbcodes
- * @var string bbcode_bitfield The bbcode bitfield
- * @var string bbcode_uid The bbcode uid
- * @since 3.1.3-RC1
- */
- $vars = array('bbcode_cache', 'bbcode_bitfield', 'bbcode_uid');
- extract($phpbb_dispatcher->trigger_event('core.bbcode_cache_init_end', compact($vars)));
- $this->bbcode_cache = $bbcode_cache;
- $this->bbcode_bitfield = $bbcode_bitfield;
- $this->bbcode_uid = $bbcode_uid;
- }
- /**
- * Return bbcode template
- */
- function bbcode_tpl($tpl_name, $bbcode_id = -1, $skip_bitfield_check = false)
- {
- static $bbcode_hardtpl = array();
- if (empty($bbcode_hardtpl))
- {
- global $user;
- $bbcode_hardtpl = array(
- 'b_open' => '<span style="font-weight: bold">',
- 'b_close' => '</span>',
- 'i_open' => '<span style="font-style: italic">',
- 'i_close' => '</span>',
- 'u_open' => '<span style="text-decoration: underline">',
- 'u_close' => '</span>',
- 'img' => '<img src="$1" class="postimage" alt="' . $user->lang['IMAGE'] . '" />',
- 'size' => '<span style="font-size: $1%; line-height: normal">$2</span>',
- 'color' => '<span style="color: $1">$2</span>',
- 'email' => '<a href="mailto:$1">$2</a>'
- );
- }
- if ($bbcode_id != -1 && !$skip_bitfield_check && !$this->template_bitfield->get($bbcode_id))
- {
- return (isset($bbcode_hardtpl[$tpl_name])) ? $bbcode_hardtpl[$tpl_name] : false;
- }
- if (empty($this->bbcode_template))
- {
- if (($tpl = file_get_contents($this->template_filename)) === false)
- {
- trigger_error('Could not load bbcode template', E_USER_ERROR);
- }
- // replace \ with \\ and then ' with \'.
- $tpl = str_replace('\\', '\\\\', $tpl);
- $tpl = str_replace("'", "\'", $tpl);
- // strip newlines and indent
- $tpl = preg_replace("/\n[\n\r\s\t]*/", '', $tpl);
- // Turn template blocks into PHP assignment statements for the values of $bbcode_tpl..
- $this->bbcode_template = array();
- // Capture the BBCode template matches
- // Allow phpBB template or the Twig syntax
- $matches = (preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END (?:.*?) -->#', $tpl, $match)) ?:
- preg_match_all('#{% for (.*?) in .*? %}(.*?){% endfor %}#s', $tpl, $match);
- for ($i = 0; $i < $matches; $i++)
- {
- if (empty($match[1][$i]))
- {
- continue;
- }
- $this->bbcode_template[$match[1][$i]] = $this->bbcode_tpl_replace($match[1][$i], $match[2][$i]);
- }
- }
- return (isset($this->bbcode_template[$tpl_name])) ? $this->bbcode_template[$tpl_name] : ((isset($bbcode_hardtpl[$tpl_name])) ? $bbcode_hardtpl[$tpl_name] : false);
- }
- /**
- * Return bbcode template replacement
- */
- function bbcode_tpl_replace($tpl_name, $tpl)
- {
- global $user;
- static $replacements = array(
- 'quote_username_open' => array('{USERNAME}' => '$1'),
- 'color' => array('{COLOR}' => '$1', '{TEXT}' => '$2'),
- 'size' => array('{SIZE}' => '$1', '{TEXT}' => '$2'),
- 'img' => array('{URL}' => '$1'),
- 'flash' => array('{WIDTH}' => '$1', '{HEIGHT}' => '$2', '{URL}' => '$3'),
- 'url' => array('{URL}' => '$1', '{DESCRIPTION}' => '$2'),
- 'email' => array('{EMAIL}' => '$1', '{DESCRIPTION}' => '$2')
- );
- $tpl = preg_replace_callback('/{L_([A-Z0-9_]+)}/', function ($match) use ($user) {
- return (!empty($user->lang[$match[1]])) ? $user->lang($match[1]) : ucwords(strtolower(str_replace('_', ' ', $match[1])));
- }, $tpl);
- if (!empty($replacements[$tpl_name]))
- {
- $tpl = strtr($tpl, $replacements[$tpl_name]);
- }
- return trim($tpl);
- }
- /**
- * Second parse list bbcode
- */
- function bbcode_list($type)
- {
- if ($type == '')
- {
- $tpl = 'ulist_open_default';
- $type = 'default';
- }
- else if ($type == 'i')
- {
- $tpl = 'olist_open';
- $type = 'lower-roman';
- }
- else if ($type == 'I')
- {
- $tpl = 'olist_open';
- $type = 'upper-roman';
- }
- else if (preg_match('#^(disc|circle|square)$#i', $type))
- {
- $tpl = 'ulist_open';
- $type = strtolower($type);
- }
- else if (preg_match('#^[a-z]$#', $type))
- {
- $tpl = 'olist_open';
- $type = 'lower-alpha';
- }
- else if (preg_match('#[A-Z]#', $type))
- {
- $tpl = 'olist_open';
- $type = 'upper-alpha';
- }
- else if (is_numeric($type))
- {
- $tpl = 'olist_open';
- $type = 'decimal';
- }
- else
- {
- $tpl = 'olist_open';
- $type = 'decimal';
- }
- return str_replace('{LIST_TYPE}', $type, $this->bbcode_tpl($tpl));
- }
- /**
- * Second parse quote tag
- */
- function bbcode_second_pass_quote($username, $quote)
- {
- // when using the /e modifier, preg_replace slashes double-quotes but does not
- // seem to slash anything else
- $quote = str_replace('\"', '"', $quote);
- $username = str_replace('\"', '"', $username);
- // remove newline at the beginning
- if ($quote == "\n")
- {
- $quote = '';
- }
- $quote = (($username) ? str_replace('$1', $username, $this->bbcode_tpl('quote_username_open')) : $this->bbcode_tpl('quote_open')) . $quote;
- return $quote;
- }
- /**
- * Second parse code tag
- */
- function bbcode_second_pass_code($type, $code)
- {
- // when using the /e modifier, preg_replace slashes double-quotes but does not
- // seem to slash anything else
- $code = str_replace('\"', '"', $code);
- switch ($type)
- {
- case 'php':
- // Not the english way, but valid because of hardcoded syntax highlighting
- if (strpos($code, '<span class="syntaxdefault"><br /></span>') === 0)
- {
- $code = substr($code, 41);
- }
- // no break;
- default:
- $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);
- }
- break;
- }
- $code = $this->bbcode_tpl('code_open') . $code . $this->bbcode_tpl('code_close');
- return $code;
- }
- /**
- * Function to perform custom bbcode second pass by extensions
- * can be used to assign bbcode pattern replacement
- * Example: '#\[list=([^\[]+):$uid\]#e' => "\$this->bbcode_second_pass_by_extension('\$1')"
- *
- * Accepts variable number of parameters
- *
- * @return mixed Second pass result
- *
- * @deprecated 3.2.10 (To be removed 4.0.0)
- */
- function bbcode_second_pass_by_extension()
- {
- global $phpbb_dispatcher;
- $return = false;
- $params_array = func_get_args();
- /**
- * Event to perform bbcode second pass with
- * the custom validating methods provided by extensions
- *
- * @event core.bbcode_second_pass_by_extension
- * @var array params_array Array with the function parameters
- * @var mixed return Second pass result to return
- *
- * @since 3.1.5-RC1
- */
- $vars = array('params_array', 'return');
- extract($phpbb_dispatcher->trigger_event('core.bbcode_second_pass_by_extension', compact($vars)));
- return $return;
- }
- }