* @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 * Init bbcode cache entries if bitfield is specified */ function bbcode($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 (sizeof($str['search'])) { $message = str_replace($str['search'], $str['replace'], $message); $str = array('search' => array(), 'replace' => array()); } if (sizeof($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 $phpbb_root_path, $phpEx, $config, $user, $phpbb_dispatcher, $phpbb_extension_manager, $phpbb_path_helper, $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, $phpbb_container->getParameter('core.root_path') . 'cache/', $phpbb_container->get('ext.manager'), new \phpbb\template\twig\loader( $phpbb_filesystem ) ), $phpbb_container->getParameter('core.root_path') . 'cache/', $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 (sizeof($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
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); } // To perform custom second pass in extension, use $this->bbcode_second_pass_by_extension() // method which accepts variable number of parameters foreach ($bbcode_ids as $bbcode_id) { switch ($bbcode_id) { case 0: $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) { return $this->bbcode_second_pass_quote($match[1], $match[2]); }, ) ); break; case 1: $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 2: $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 3: $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 4: 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 5: $this->bbcode_cache[$bbcode_id] = array( 'preg' => array( '#\[size=([\-\+]?\d+):$uid\](.*?)\[/size:$uid\]#s' => $this->bbcode_tpl('size', $bbcode_id), ) ); break; case 6: $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 7: $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 8: $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 9: $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 10: $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 11: 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 12: $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' => '', 'b_close' => '', 'i_open' => '', 'i_close' => '', 'u_open' => '', 'u_close' => '', 'img' => '' . $user->lang['IMAGE'] . '', 'size' => '$2', 'color' => '$2', 'email' => '$2' ); } 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(); $matches = preg_match_all('#(.*?)#', $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, '
') === 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 */ 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; } }