* Invision Power Services
* IP.Board v3.4.9
* BBCode parsing core - common methods
* Last Updated: $Date: 2014-12-31 17:46:06 -0500 (Wed, 31 Dec 2014) $
*
*
* @author $Author: rashbrook $
* @copyright (c) 2001 - 2009 Invision Power Services, Inc.
* @license http://www.invisionpower.com/company/standards.php#license
* @package IP.Board
* @link http://www.invisionpower.com
* @since 9th March 2005 11:03
* @version $Revision: 12581 $
*
* Basic usage examples
*
* $parser = new parseBbcode( $registry );
*
* # If you wish convert posted text to store in the database
* $parser->parse_smilies = 1;
* $parser->parse_bbcode = 1;
* $parser->parsing_section = 'your section key';
* $bbcode_text = $parser->preDbParse( $_POST['text'] );
*
* # If you wish to display this content
* $parser->parse_html = 0;
* $parser->parse_nl2br = 1;
* $parser->parsing_section = 'your section key';
* $parser->parsing_mgroup = 'member group id of poster';
* $parser->parsing_mgroup_others = 'member other group ids of poster';
* $ready_to_print = $parser->preDisplayParse( $bbcode_text );
*
* # If you wish to convert already converted BBCode back into the raw format
* # (for use in an editing screen, for example) use this:
* $raw_post = $parser->preEditParse( $parsed_text );
*
* # Of course, if you're using the rich text editor (WYSIWYG) then you don't want to uncovert the HTML
* # otherwise the rich text editor will show unparsed BBCode tags, and not formatted HTML. In this case use this:
* $raw_post = $parser->convert_ipb_html_to_html( $parsed_text );
*
*
*/
if ( ! defined( 'IN_IPB' ) )
{
print "
{content}
'; $this->_bbcodes['display']['right']['bbcode_replace'] = '{content}
'; $this->_bbcodes['display']['center']['bbcode_replace'] = '{content}
'; $this->_bbcodes['display']['indent']['bbcode_replace'] = '{content}
'; $this->_bbcodes['display']['b']['bbcode_replace'] = '{content}'; $this->_bbcodes['display']['i']['bbcode_replace'] = '{content}'; $this->_bbcodes['display']['u']['bbcode_replace'] = '{content}'; $this->_bbcodes['display']['strike']['bbcode_replace'] = '' AND !empty( $txt ) ) { $txt = '
' . $txt . '
'; if ( strpos( $txt, "
', $txt );
}
}
//-----------------------------------------
// Badwords
//-----------------------------------------
$txt = $this->badWords($txt);
return $txt;
}
/**
* This function processes the DB post before printing as output
*
* @access public
* @param string Raw text
* @return string Converted text
*/
public function preDisplayParse( $txt="" )
{
$this->cache->updateCacheWithoutSaving( '_tmp_bbcode_media', 0 );
$this->cache->updateCacheWithoutSaving( '_tmp_bbcode_images', 0 );
if ( $this->parse_html )
{
//-----------------------------------------
// Store true line breaks first
//-----------------------------------------
$txt = str_replace( '
', "~~~~~_____~~~~~", $txt );
$txt = $this->_parseHtml( $txt );
/* We still don't want XSS thx */
if ( ! $this->skipXssCheck )
{
$txt = $this->checkXss( $txt, true );
}
}
/* http://community.invisionpower.com/resources/bugs.html/_/ip-board/profile-quotes-in-likes-tab-does-not-appear-r42346
else
{
$txt = str_replace( ' ', ' ', $txt );
}*/
//-----------------------------------------
// Fix "{style_images_url}"
//-----------------------------------------
$txt = str_replace( "{style_images_url}", "{style_images_url}", $txt );
//-----------------------------------------
// Custom BB code
//-----------------------------------------
$_NOW = IPSDebug::getMemoryDebugFlag();
IPSDebug::setMemoryDebugFlag( "PreDisplayParse - parsed BBCode", $_NOW );
//-----------------------------------------
// Fix line breaks
//-----------------------------------------
if ( $this->parse_html )
{
$txt = str_replace( "~~~~~_____~~~~~", '
', $txt );
}
$_memberData = array( 'member_group_id' => $this->parsing_mgroup, 'mgroup_others' => $this->parsing_mgroup_others );
if( $this->parsing_mgroup )
{
$_memberData = array_merge( $_memberData, $this->caches['group_cache'][ $this->parsing_mgroup ] );
}
if( $this->parsing_mgroup_others )
{
$_memberData = ips_MemberRegistry::setUpSecondaryGroups( $_memberData );
}
/* Finish hiiiiiiiiiiiiiiim */
$classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/text/parser.php', 'classes_text_parser' );
$parser = new $classToLoad();
$parser->set( array( 'memberData' => $_memberData,
'parseBBCode' => $this->parse_bbcode,
'parseArea' => $this->parsing_section,
'parseHtml' => $this->parse_html,
'parseEmoticons' => $this->parse_smilies ) );
/* Convert emos back into code */
$txt = $parser->emoticonImgtoCode( $txt );
$txt = $parser->display( $txt );
//-----------------------------------------
// Fix images nested inside anchors
//-----------------------------------------
$txt = preg_replace_callback( '#(\]+bbc_url[^\>]+\>)\s*?(.+?)\s*?(\<\/a\>)#im', array( $this, 'removeLightboxSpans' ), $txt );
return $txt;
}
/**
* Remove tags
*
* @see http://community.invisionpower.com/tracker/issue-34619-two-images-wrapped-by-a-link-causes-weird-stuff-in-ie78
* @param array Regex matches
* @return string
*/
protected function removeLightboxSpans( $matches )
{
return $matches[1] . preg_replace( '#\(.+?)\<\/span\>#im', '$1', $matches[2] ) . $matches[3];
}
/**
* This function processes the text before showing for editing, etc
*
* @access public
* @param string Raw text
* @return string Converted text
*/
public function preEditParse($txt="")
{
//-----------------------------------------
// Clean up BR tags
//-----------------------------------------
if ( ! $this->parse_html )
{
$txt = str_replace( "\n" , "" , $txt );
$txt = str_replace( "
" , "\n" , $txt );
$txt = str_replace( "
", "\n" , $txt );
}
//-----------------------------------------
// Unconvert smilies
//-----------------------------------------
$txt = IPSText::unconvertSmilies( $txt );
//-----------------------------------------
// Clean up nbsp
//-----------------------------------------
$txt = str_replace( ' ', "\t", $txt );
$txt = str_replace( ' ' , " ", $txt );
//-----------------------------------------
// Parse html
//-----------------------------------------
if ( $this->parse_html )
{
$txt = str_replace( "'", "'", $txt);
}
//-----------------------------------------
// Fix "{style_images_url}"
//-----------------------------------------
$txt = str_replace( "{style_images_url}", "{style_images_url}", $txt );
/* check for missing options in bbcode tags */
$txt = preg_replace( '#\[(\S+?)=\]#', '[\1]', $txt );
return trim( $txt );
}
/**
* Loop over the bbcode and make replacements as necessary
*
* @access public
* @param string Current text
* @param string [db|display] Current method to parse
* @param mixed [optional] Only parse the selected code(s)
* @return string Converted text
*/
public function parseBbcode( $txt, $cur_method='db', $_code=null )
{
//-----------------------------------------
// Pull out the non-replacable codes
//-----------------------------------------
if( !is_string($_code) )
{
$txt = $this->_storeNonParsed( $txt, $cur_method );
}
//-----------------------------------------
// We want preDbParse method called for shared
// media for permission checking, so force it for now..
//-----------------------------------------
if ( $cur_method == 'db' )
{
$this->_bbcodes[ $cur_method ]['sharedmedia'] = $this->_bbcodes['display']['sharedmedia'];
$txt = preg_replace_callback( '#(\[code.*\[/code\])#is' , array( $this, '_checkForEmbeddedCode' ), $txt );
}
//-----------------------------------------
// Regular replacing
//-----------------------------------------
if( isset( $this->_bbcodes[ $cur_method ] ) AND is_array($this->_bbcodes[ $cur_method ]) AND count($this->_bbcodes[ $cur_method ]) )
{
foreach( $this->_bbcodes[ $cur_method ] as $_bbcode )
{
//-----------------------------------------
// Can this group use this bbcode?
//-----------------------------------------
if( $_bbcode['bbcode_groups'] != 'all' AND $this->parsing_mgroup )
{
$pass = false;
$groups = array_diff( explode( ',', $_bbcode['bbcode_groups'] ), array('') );
$mygroups = array( $this->parsing_mgroup );
if( $this->parsing_mgroup_others )
{
$mygroups = array_diff( array_merge( $mygroups, explode( ',', IPSText::cleanPermString( $this->parsing_mgroup_others ) ) ), array('') );
}
foreach( $groups as $g_id )
{
if( in_array( $g_id, $mygroups ) )
{
$pass = true;
break;
}
}
if( !$pass )
{
continue;
}
}
//-----------------------------------------
// Reset our current position
//-----------------------------------------
$this->cur_pos = 0;
//-----------------------------------------
// Store teh tags
//-----------------------------------------
$_tags = array( $_bbcode['bbcode_tag'] );
//-----------------------------------------
// We'll also need to check for any aliases
//-----------------------------------------
if( $_bbcode['bbcode_aliases'] )
{
$aliases = explode( ',', trim($_bbcode['bbcode_aliases']) );
if( is_array($aliases) AND count($aliases) )
{
foreach( $aliases as $alias )
{
$_tags[] = trim($alias);
}
}
}
//-----------------------------------------
// If we have a plugin, just pass off
//-----------------------------------------
if( $_bbcode['bbcode_php_plugin'] )
{
/* Legacy issues */
if ( $_bbcode['bbcode_php_plugin'] == 'defaults.php' )
{
$file = IPS_ROOT_PATH . 'sources/classes/text/parser/bbcode/' . $_bbcode['bbcode_php_plugin'];
$class = 'bbcode_plugin_' . IPSText::alphanumericalClean( $_bbcode['bbcode_tag'] );
$method = "run";
}
else
{
$file = IPS_ROOT_PATH . 'sources/classes/bbcode/custom/' . $_bbcode['bbcode_php_plugin'];
$class = 'bbcode_' . IPSText::alphanumericalClean( $_bbcode['bbcode_tag'] );
$method = "pre" . ucwords($cur_method) . "Parse";
}
//-----------------------------------------
// Are we only parsing one code?
//-----------------------------------------
if( is_array($_code) )
{
$good = false;
foreach( $_tags as $_tag )
{
if( in_array( $_tag, $_code ) )
{
$good = true;
break; // Got one, stop here
}
}
if( !$good )
{
continue;
}
}
else if( is_string($_code) )
{
if( !in_array( $_code, $_tags ) )
{
continue;
}
}
$_key = md5($_bbcode['bbcode_tag']);
//-----------------------------------------
// Do we already have this plugin in our registry?
//-----------------------------------------
if( isset($this->plugins[ $_key ]) )
{
//-----------------------------------------
// Run the method if it exists
//-----------------------------------------
if( method_exists( $this->plugins[ $_key ], $method ) )
{
$_original = $txt;
$txt = $this->plugins[ $_key ]->$method( $txt, ( $method == 'run' ) ? ( $cur_method == 'db' ? 'html' : 'display' ) : 'bbcode' );
if( !$txt )
{
$txt = $_original;
}
else if( $this->plugins[ $_key ]->error )
{
$this->error = $this->plugins[ $_key ]->error;
return $txt;
}
else if( $this->plugins[ $_key ]->warning )
{
$this->warning = $this->plugins[ $_key ]->warning;
}
}
}
//-----------------------------------------
// First time we've called this plugin
//-----------------------------------------
elseif ( is_file( $file ) )
{
$_classname = IPSLib::loadLibrary( $file, $class );
//-----------------------------------------
// Class we need exists
//-----------------------------------------
if( class_exists( $_classname ) )
{
//-----------------------------------------
// New instance of class, store in plugin registry for use next time
//-----------------------------------------
$plugin = new $_classname( $this->registry, $this );
//$method = "pre" . ucwords($cur_method) . "Parse";
$this->plugins[ md5($_bbcode['bbcode_tag']) ] = $plugin;
//-----------------------------------------
// Method we need exists
//-----------------------------------------
if( method_exists( $plugin, $method ) )
{
$_original = $txt;
$txt = $plugin->$method( $txt, ( $method == 'run' ) ? ( $cur_method == 'db' ? 'html' : 'display' ) : 'bbcode' );
if( !$txt )
{
$txt = $_original;
}
else if( $plugin->error )
{
$this->error = $plugin->error;
return $txt;
}
else if( $plugin->warning )
{
$this->warning = $plugin->warning;
}
}
}
}
//-----------------------------------------
// When we run a plugin, we don't do any other processing "automatically".
// Plugin is capable of doing what it wants that way.
//-----------------------------------------
continue;
}
//-----------------------------------------
// Loop over this bbcode's tags
//-----------------------------------------
foreach( $_tags as $_tag )
{
//-----------------------------------------
// Are we only parsing one code?
//-----------------------------------------
if( is_array($_code) AND !in_array( $_tag, $_code ) )
{
continue;
}
else if( is_string($_code) AND $_tag != $_code )
{
continue;
}
//-----------------------------------------
// Infinite loop catcher
//-----------------------------------------
$_iteration = 0;
//-----------------------------------------
// Start building open tag
//-----------------------------------------
$open_tag = '[' . $_tag;
//-----------------------------------------
// Doz I can haz opin tag? Loopy loo
//-----------------------------------------
while( ( $this->cur_pos = stripos( $txt, $open_tag, $this->cur_pos ) ) !== false )
{
//-----------------------------------------
// Stop infinite loops
//-----------------------------------------
if( $_iteration > $this->settings['max_bbcodes_per_post'] )
{
break;
}
$open_length = strlen($open_tag);
//-----------------------------------------
// Grab the new position to jump to
//-----------------------------------------
$new_pos = strpos( $txt, ']', $this->cur_pos ) ? strpos( $txt, ']', $this->cur_pos ) : $this->cur_pos + 1;
//-----------------------------------------
// Extract the option (like surgery)
//-----------------------------------------
$_option = '';
if( $_bbcode['bbcode_useoption'] )
{
//-----------------------------------------
// Is option optional?
//-----------------------------------------
if( $_bbcode['bbcode_optional_option'] )
{
//-----------------------------------------
// Does we haz it?
//-----------------------------------------
if( substr( $txt, $this->cur_pos + strlen($open_tag), 1 ) == '=' )
{
$open_length += 1;
$_option = substr( $txt, $this->cur_pos + $open_length, (strpos( $txt, ']', $this->cur_pos ) - ($this->cur_pos + $open_length)) );
}
//-----------------------------------------
// If not, [u] != [url] (for example)
//-----------------------------------------
else if( (strpos( $txt, ']', $this->cur_pos ) - ( $this->cur_pos + $open_length )) !== 0 )
{
if( strpos( $txt, ']', $this->cur_pos ) )
{
$this->cur_pos = $new_pos;
continue;
}
else
{
break;
}
}
}
//-----------------------------------------
// No? Then just grab it
//-----------------------------------------
else
{
$open_length += 1;
$_option = substr( $txt, $this->cur_pos + $open_length, (strpos( $txt, ']', $this->cur_pos ) - ($this->cur_pos + $open_length)) );
}
}
//-----------------------------------------
// [img] != [i] (for example)
//-----------------------------------------
else if( (strpos( $txt, ']', $this->cur_pos ) - ( $this->cur_pos + $open_length )) !== 0 )
{
if( strpos( $txt, ']', $this->cur_pos ) )
{
$this->cur_pos = $new_pos;
continue;
}
}
$_iteration++;
//-----------------------------------------
// Protect against XSS
//-----------------------------------------
$_optionStrLen = IPSText::mbstrlen( $_option );
$_optionSlenstr = strlen($_option);
$_option = $this->checkXss($_option, false, $_tag);
if ( $_option !== FALSE )
{
/* Not parsing URls? - Needs to be AFTER the FALSE check just above */
if ( !empty( $_bbcode['bbcode_no_auto_url_parse'] ) )
{
$_option = preg_replace( "#(http|https|news|ftp)://#i", "\\1://", $_option );
}
//-----------------------------------------
// If this is a single tag, that's it
//-----------------------------------------
if( $_bbcode['bbcode_single_tag'] )
{
$txt = substr_replace( $txt, $this->_bbcodeToHtml( $_bbcode, $_option, '' ), $this->cur_pos, ($open_length + $_optionSlenstr + 1) );
}
//-----------------------------------------
// Otherwise replace out the content too
//-----------------------------------------
else
{
$close_tag = '[/' . $_tag . ']';
if( stripos( $txt, $close_tag, $new_pos ) !== false )
{
$_content = substr( $txt, ($this->cur_pos + $open_length + $_optionSlenstr + 1), (stripos( $txt, $close_tag, $this->cur_pos ) - ($this->cur_pos + $open_length + $_optionSlenstr + 1)) );
if( $_bbcode['bbcode_useoption'] AND $_bbcode['bbcode_optional_option'] AND ! $_option AND ! stristr( $_bbcode['bbcode_replace'], '{option}' ) )
{
$_option = $_content;
$_option = $this->checkXss($_option, false, $_tag);
}
/* Not parsing URls? */
if ( !empty( $_bbcode['bbcode_no_auto_url_parse'] ) )
{
$_content = preg_replace( "#(http|https|news|ftp)://#i", "\\1://", $_content );
}
$txt = substr_replace( $txt, $this->_bbcodeToHtml( $_bbcode, $_option, $_content ), $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) );
}
else
{
//-----------------------------------------
// If there's no close tag, no need to continue
//-----------------------------------------
break;
}
}
}
//-----------------------------------------
// And reset current position to end of open tag
// Bug 14744 - if we jump to $new_pos it can skip the opening of the next bbcode tag
// when the replacement HTML is shorter than the full bbcode representation...
//-----------------------------------------
$this->cur_pos = stripos( $txt, $open_tag ) ? stripos( $txt, $open_tag ) : $this->cur_pos + 1; //$new_pos;
if( $this->cur_pos > strlen($txt) )
{
break;
}
}
}
}
}
//-----------------------------------------
// (c) (r) and (tm)
//-----------------------------------------
if( $cur_method == 'display' AND $_code !== 'code' AND $_code !== 'php' AND $_code !== 'sql' AND $_code !== 'xml' )
{
$txt = str_ireplace( "(c)" , "©" , $txt );
$txt = str_ireplace( "(tm)" , "" , $txt );
$txt = str_ireplace( "(r)" , "®" , $txt );
}
//-----------------------------------------
// And finally replace those bbcodes
//-----------------------------------------
if( !$_code )
{
$txt = $this->_parseNonParsed( $txt, $cur_method );
}
//-----------------------------------------
// Auto parse URLs (only if this is full sweep)
//-----------------------------------------
if ( ! $_code AND $cur_method == 'display' )
{
/* If we parse http://site[color=red].com[/color], it breaks
* @link http://community.invisionpower.com/tracker/issue-24318-colors-in-urls-as-names-breaks-them/
* Here we will extract pairs, put in */
$_storedLinks = array();
$_counter = 0;
while( preg_match( '/(.+?)<\/a>/is', $txt, $matches ) )
{
/* Is this a linked media URL? */
if ( $this->settings['bbcode_automatic_media'] and isset( $this->_bbcodes['display']['media'] ) and ( $this->_bbcodes['display']['media']['bbcode_sections'] == 'all' or in_array( $this->parsing_section, explode( ',', $this->_bbcodes['display']['media']['bbcode_sections'] ) ) ) )
{
$media = $this->cache->getCache( 'mediatag' );
#href must match text (or has been shortened) and not a check this out! style link
if ( $matches[1] == $matches[3] OR strstr( $matches[3], '...' ) )
{
if ( is_array( $media ) AND count( $media ) )
{
foreach( $media as $type => $r )
{
if ( preg_match( "#^" . $r['match'] . "$#is", $matches[1] ) )
{
$this->cache->updateCacheWithoutSaving( '_tmp_autoparse_media', 1 );
$_result = $this->parseBbcode( '[media]' . $matches[1] . '[/media]', 'display', 'media' );
$this->cache->updateCacheWithoutSaving( '_tmp_autoparse_media', 0 );
$txt = str_replace( $matches[0], $_result, $txt );
$this->_mediaUrlConverted[] = $matches[1];
continue;
}
}
}
}
}
$_counter++;
$_storedLinks[ $_counter ] = $matches[0];
$txt = str_replace( $matches[0], '', $txt );
}
/* Capture 'href="' and '' as [URL] is now parsed first, we discard these in _autoParseUrls */
/**
* @link http://community.invisionpower.com/tracker/issue-23726-parser-wrong-url-with-unicode-chars/
* I had to add the /u modifier to correct this. Previously, the first byte sequence of the word was matching \s.
* @link http://community.invisionpower.com/tracker/issue-24684-posts-are-blankmissing/
* Reverting this fix as it's breaking in some environments - not really sure what we can do about this at this point
*/
//$opts = ( IPS_DOC_CHAR_SET == 'UTF-8' ) ? 'isu' : 'is';
if ( ! $this->parse_html )
{
$opts = "is";
$txt = preg_replace_callback( '#(^|\s|\)|\(|\{|\}|>|\]|\[|;|href=\S)((http|https|news|ftp)://(?:[^<>\)\[\"\s]+|[a-zA-Z0-9/\._\-!&\#;,%\+\?:=]+))()?#' . $opts, array( $this, '_autoParseUrls' ), $txt );
}
/* Now put back stored links */
foreach( $_storedLinks as $_inc => $_storedLink )
{
$txt = str_replace( '', $_storedLink, $txt );
}
}
return $txt;
}
/**
* Parse emoticons in text
* @param string $txt
* @return string $txt
*/
public function parseEmoticons( $txt )
{
/* Sort them in length order first */
$this->_sortSmilies();
/* Now parse them! */
if ( $this->parse_smilies && ! $this->parse_html )
{
$codes_seen = array();
if ( count( $this->_sortedSmilies ) > 0 )
{
foreach( $this->_sortedSmilies as $row )
{
if ( is_array($this->registry->output->skin) AND $this->registry->output->skin['set_emo_dir'] AND $row['emo_set'] != $this->registry->output->skin['set_emo_dir'] )
{
continue;
}
$code = $row['typed'];
if ( in_array( $code, $codes_seen ) )
{
continue;
}
$codes_seen[] = $code;
//-----------------------------------------
// Now, check for the html safe versions
//-----------------------------------------
$_emoCode = str_replace( '<', '<', str_replace( '>', '>', $code ) );
$_emoImage = $row['image'];
$emoPosition = 0;
//-----------------------------------------
// These are chars that can't surround the emo
//-----------------------------------------
$invalidWrappers = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\"/";
/* Cheap check */
if ( ! stristr( $txt, $_emoCode ) )
{
continue;
}
//-----------------------------------------
// Have any more chars to look at?
//-----------------------------------------
while( ( $position = stripos( $txt, $_emoCode, $emoPosition ) ) !== false )
{
$lastOpenTagPosition = strrpos( substr( $txt, 0, $position ), '[' );
$lastCloseTagPosition = strrpos( substr( $txt, 0, $position ), ']' );
//-----------------------------------------
// Are we at the start of the string, or
// is the preceeding char not an invalid wrapper?
//-----------------------------------------
if( ( $position === 0 OR stripos( $invalidWrappers, substr( $txt, $position-1, 1 ) ) === false )
//-----------------------------------------
// Are we inside a [tag]
//-----------------------------------------
AND ( $lastOpenTagPosition === FALSE or ( $lastCloseTagPosition !== FALSE and $lastCloseTagPosition > $lastOpenTagPosition ) )
//-----------------------------------------
// Are we at the end of the string or is the
// next char not an invalid wrapper?
//-----------------------------------------
AND ( strlen($txt) == ($position + strlen($_emoCode)) OR stripos( $invalidWrappers, substr( $txt, ($position + strlen($_emoCode)), 1 ) ) === false ) )
{
//-----------------------------------------
// Replace the emoticon and increment position counter
//-----------------------------------------
$replace = $this->_retrieveSmiley( $_emoCode, $_emoImage );
$txt = substr_replace( $txt, $replace, $position, strlen($_emoCode) );
$position += strlen($replace);
}
$emoPosition = $position + 1;
if( $emoPosition > strlen($txt) )
{
break;
}
}
}
}
if ( $this->settings['max_emos'] )
{
if ( $this->emoticon_count > $this->settings['max_emos'] )
{
$this->error = 'too_many_emoticons';
}
}
/* Put alt tags in */
if( is_array( $this->emoticon_alts ) && count( $this->emoticon_alts ) )
{
foreach( $this->emoticon_alts as $r )
{
$txt = str_replace( $r[0], $r[1], $txt );
}
}
}
return $txt;
}
/**
* This is a utility function to finish parsing the "non-parsed" items, such as image and code tags, when we are only
* parsing one tag at a time. For instance, see the parsePollTags method of han_parse_bbcode.php
*
* @see parseBbcode::parsePollTags()
* @access public
* @param string BBCode with non-parse markers
* @param string Current method
* @param string BBCode with non-parse markers replaced
*/
public function finishNonParsed( $txt, $cur_method='db' )
{
return $this->_parseNonParsed( $txt, $cur_method );
}
/**
* Does the actual bbcode replacement
*
* @access protected
* @param string Current bbcode to parse
* @param string [Optional] Option text
* @param string [Optional for single tag bbcodes] Content text
* @return string Converted text
*/
protected function _bbcodeToHtml( $_bbcode, $option='', $content='' )
{
//-----------------------------------------
// Strip the optional quote delimiters
//-----------------------------------------
$option = str_replace( '"', '"', $option );
$option = str_replace( ''', "'", $option );
$option = trim( $option, '"' . "'" );
//-----------------------------------------
// Stop CSS injection
//-----------------------------------------
if( $option )
{
//-----------------------------------------
// Cut off for entities in option
// @see http://community.invisionpower.com/tracker/issue-19958-acronym/
//-----------------------------------------
$option = IPSText::UNhtmlspecialchars( $option );
$option = str_replace( '!', '!', $option );
$option = IPSText::decodeNamedHtmlEntities( $option );
if ( strpos( $option, ';' ) !== false )
{
$option = substr( $option, 0, strpos( $option, ';' ) );
}
$charSet = ( IPS_DOC_CHAR_SET == 'ISO-8859-1' ) ? 'ISO-8859-15' : IPS_DOC_CHAR_SET;
$option = @htmlentities( $option, ENT_NOQUOTES, $charSet );
$option = str_replace( '!', '!', $option );
}
$option = str_replace( '"', '"', $option );
$option = str_replace( "'", ''', $option );
//-----------------------------------------
// Swapping option/content?
//-----------------------------------------
if( $_bbcode['bbcode_switch_option'] )
{
$_tmp = $content;
$content = $option;
$option = $_tmp;
}
//-----------------------------------------
// Replace
//-----------------------------------------
$replaceCode = $_bbcode['bbcode_replace'];
$replaceCode = str_replace( '{base_url}', $this->settings['board_url'] . '/index.php?', $replaceCode );
$replaceCode = str_replace( '{image_url}', $this->settings['img_url'], $replaceCode );
preg_match( '/\{text\.(.+?)\}/i', $replaceCode, $matches );
if( is_array($matches) AND count($matches) )
{
$replaceCode = str_replace( $matches[0], $this->lang->words[ $matches[1] ], $replaceCode );
}
$replaceCode = str_replace( '{option}', $option, $replaceCode );
$replaceCode = str_replace( '{content}', $content, $replaceCode );
//-----------------------------------------
// Fix linebreaks in textareas
//-----------------------------------------
if( stripos( $replaceCode, "', "", $replaceCode );
$replaceCode = str_replace( "\r", "", $replaceCode );
$replaceCode = str_replace( "\n", "
", $replaceCode );
}
return $replaceCode;
}
/**
* Parse escaped HTML for display
*
* @access protected
* @param string Current text to parse
* @return string Converted text
*/
protected function _parseHtml( $txt="" )
{
if ( $txt == "" )
{
return $txt;
}
//-----------------------------------------
//
s are <br> at this point :)
//-----------------------------------------
$txt = str_replace( "'" , "'", $txt );
$txt = str_replace( "!" , "!", $txt );
$txt = str_replace( "$", "$", $txt );
$txt = str_replace( "|", "|", $txt );
$txt = str_replace( "&" , "&", $txt );
$txt = str_replace( ">" , ">", $txt );
$txt = str_replace( "<" , "<", $txt );
$txt = str_replace( "<" , "<", $txt );
$txt = str_replace( ">" , ">", $txt );
$txt = str_replace( """, '"', $txt );
return $txt;
}
/**
* Store the bbcode content that should not have inner content parsed
*
* @access protected
* @param string Current text
* @param string [db|display] Current method to parse
* @return string Converted text
*/
protected function _storeNonParsed( $txt, $cur_method='db' )
{
//-----------------------------------------
// Pull out the non-replacable codes
//-----------------------------------------
if( isset( $this->_bbcodes[ $cur_method ] ) AND is_array($this->_bbcodes[ $cur_method ]) AND count($this->_bbcodes[ $cur_method ]) )
{
foreach( $this->_bbcodes[ $cur_method ] as $_bbcode )
{
//-----------------------------------------
// Are we parsing inside?
//-----------------------------------------
if( !$_bbcode['bbcode_no_parsing'] )
{
continue;
}
//-----------------------------------------
// If not, pull out and store for later
//-----------------------------------------
$_curPosition = 0;
$_tags = array( $_bbcode['bbcode_tag'] );
//-----------------------------------------
// We'll also need to check for any aliases
//-----------------------------------------
if( $_bbcode['bbcode_aliases'] )
{
$aliases = explode( ',', trim($_bbcode['bbcode_aliases']) );
if( is_array($aliases) AND count($aliases) )
{
foreach( $aliases as $alias )
{
$_tags[] = trim($alias);
}
}
}
//-----------------------------------------
// Loop over this bbcode's tags
//-----------------------------------------
foreach( $_tags as $_tag )
{
//-----------------------------------------
// Start building open tag
//-----------------------------------------
$open_tag = '[' . $_tag;
//-----------------------------------------
// Doz I can haz opin tag? Loopy loo
//-----------------------------------------
$openNestedTags = 0;
while( $_curPosition <= strlen($txt) AND ( $_curPosition = stripos( $txt, $open_tag, $_curPosition ) ) !== false )
{
$open_length = strlen($open_tag);
$this->_storedNoParsing++;
$_thisTag = "";
//-----------------------------------------
// Extract the option (like surgery)
//-----------------------------------------
$_option = '';
if( $_bbcode['bbcode_useoption'] )
{
//-----------------------------------------
// Is option optional?
//-----------------------------------------
if( $_bbcode['bbcode_optional_option'] )
{
//-----------------------------------------
// Does we haz it?
//-----------------------------------------
if( substr( $txt, $_curPosition + strlen($open_tag), 1 ) == '=' )
{
$open_length += 1;
$_option = substr( $txt, $_curPosition + $open_length, (strpos( $txt, ']', $_curPosition ) - ($_curPosition + $open_length)) );
}
//-----------------------------------------
// If not, [u] != [url] (for example)
//-----------------------------------------
else if( (strpos( $txt, ']', $_curPosition ) - ( $_curPosition + $open_length )) !== 0 )
{
$_curPosition = strpos( $txt, ']', $_curPosition );
continue;
}
}
//-----------------------------------------
// No? Then just grab it
//-----------------------------------------
else
{
$open_length += 1;
$_option = substr( $txt, $_curPosition + $open_length, (strpos( $txt, ']', $_curPosition ) - ($_curPosition + $open_length)) );
}
}
//-----------------------------------------
// [img] != [i] (for example)
//-----------------------------------------
else if( (strpos( $txt, ']', $_curPosition ) - ( $_curPosition + $open_length )) !== 0 )
{
$_curPosition = ( strpos( $txt, ']', $_curPosition ) !== false ) ? strpos( $txt, ']', $_curPosition ) : $_curPosition + 1;
continue;
}
//-----------------------------------------
// Grab the new position to jump to
//-----------------------------------------
$new_pos = ( strpos( $txt, ']', $_curPosition ) !== false ) ? strpos( $txt, ']', $_curPosition ) : $_curPosition + 1;
//-----------------------------------------
// If this is a single tag, that's it
//-----------------------------------------
if( $_bbcode['bbcode_single_tag'] )
{
$_currentContent = substr( $txt, $_curPosition, ($open_length + strlen($_option) + 1) );
$txt = substr_replace( $txt, $_thisTag, $_curPosition, ($open_length + strlen($_option) + 1) );
}
//-----------------------------------------
// Otherwise replace out the content too
//-----------------------------------------
else
{
$close_tag = '[/' . $_tag . ']';
if( stripos( $txt, $close_tag, $new_pos ) !== false )
{
$_currentContent = substr( $txt, $_curPosition, (stripos( $txt, $close_tag, $_curPosition ) + strlen($close_tag) - $_curPosition) );
$txt = substr_replace( $txt, $_thisTag, $_curPosition, (stripos( $txt, $close_tag, $_curPosition ) + strlen($close_tag) - $_curPosition) );
}
else
{
$_curPosition = $new_pos;
continue;
}
}
$this->noParseStorage[$this->_storedNoParsing] = array(
'find' => $_thisTag,
'replace' => $_currentContent,
'code' => $_tag,
);
//-----------------------------------------
// And reset current position to end of open tag
//-----------------------------------------
$_curPosition = $new_pos;
}
}
}
}
return $txt;
}
/**
* Parse the stored non-inner parsed bbcode
*
* @access protected
* @param string Current text
* @param string [db|display] Current method to parse
* @return string Converted text
*/
protected function _parseNonParsed( $txt, $cur_method='db' )
{
if( is_array( $this->noParseStorage ) AND count( $this->noParseStorage ) )
{
$this->resetPerPost();
foreach( $this->noParseStorage as $replacement )
{
//-----------------------------------------
// Fix HTML in code tags
//-----------------------------------------
if( $this->parse_html )
{
//$replacement['replace'] = str_replace( "", "&" , $replacement['replace'] );
$replacement['replace'] = str_replace( "&", "&" , $replacement['replace'] );
$replacement['replace'] = str_replace( "'", "'" , $replacement['replace'] );
$replacement['replace'] = str_replace( "!", "!" , $replacement['replace'] );
$replacement['replace'] = str_replace( "$", "$", $replacement['replace'] );
$replacement['replace'] = str_replace( "|", "|", $replacement['replace'] );
$replacement['replace'] = str_replace( ">", ">" , $replacement['replace'] );
$replacement['replace'] = str_replace( "<", "<" , $replacement['replace'] );
$replacement['replace'] = str_replace( '"', """, $replacement['replace'] );
$replacement['replace'] = str_replace( '<', "<", $replacement['replace'] );
$replacement['replace'] = str_replace( '\', "\", $replacement['replace'] );
//$replacement['replace'] = str_replace( '<br />', "
", $replacement['replace'] );
}
if ( $this->_isConvertingForEditor === true && $replacement['code'] == 'sharedmedia' )
{
$_final = $replacement['replace'];
}
else
{
$_final = $this->parseBbcode( $replacement['replace'], $cur_method, $replacement['code'] );
$_final = $this->preEditParse( $_final );
}
/* Bug fix: #20973 */
if( preg_match( '/\<\!\-\-NoParse(\d+)\-\-\>/', $_final, $matches ) )
{
if( $matches[1] )
{
$_final = str_replace( "", $this->noParseStorage[ $matches[1] ]['replace'], $_final );
unset( $this->noParseStorage[ $matches[1] ] );
}
}
//-----------------------------------------
// We don't want any bbcodes to parse..
//-----------------------------------------
$_final = str_replace( "[", "[" , $_final );
$_final = str_replace( "]", "]" , $_final );
$txt = str_replace( $replacement['find'], $_final, $txt );
}
}
$this->noParseStorage = array();
return $txt;
}
/**
* Callback to auto-parse urls
*
* @access protected
* @param array Matches from the regular expression
* @return string Converted text
*/
protected function _autoParseUrls( $matches )
{
$_extra = '';
/* Basic checking */
if ( stristr( $matches[1], 'href' ) )
{
return $matches[0];
}
if( strlen( $matches[2] ) < 12 )
{
return $matches[0];
}
if ( isset( $matches[4] ) AND stristr( $matches[4], '' ) )
{
return $matches[0];
}
/* Check for XSS */
if ( ! IPSText::xssCheckUrl( $matches[2] ) )
{
return $matches[0];
}
if( substr( $matches[2], -1 ) == ',' )
{
$matches[2] = rtrim( $matches[2], ',' );
$_extra = ',';
}
if( substr( $matches[2], -15 ) == '~~~~~_____~~~~~' )
{
$matches[2] = substr( $matches[2], 0, -15 );
$_extra = '~~~~~_____~~~~~';
}
/* Check for ! which is x; at this point */
if( preg_match( '/\d+?;$/', $matches[2], $_m ) )
{
$matches[2] = str_replace( $_m[0], '', $matches[2] );
$_extra = $_m[0];
}
/* Is this a media URL? */
if( $this->settings['bbcode_automatic_media'] and isset( $this->_bbcodes['display']['media'] ) and ( $this->_bbcodes['display']['media']['bbcode_sections'] == 'all' or in_array( $this->parsing_section, explode( ',', $this->_bbcodes['display']['media']['bbcode_sections'] ) ) ) )
{
$media = $this->cache->getCache( 'mediatag' );
/* Already converted? */
if ( in_array( $matches[2], $this->_mediaUrlConverted ) )
{
return $matches[0];
}
if ( is_array( $media ) AND count( $media ) )
{
foreach( $media as $type => $r )
{
if ( preg_match( "#^" . $r['match'] . "$#is", $matches[2] ) )
{
$this->cache->updateCacheWithoutSaving( '_tmp_autoparse_media', 1 );
$_result = $this->parseBbcode( $matches[1] . '[media]' . $matches[2] . '[/media]' . $_extra, 'display', 'media' );
$this->cache->updateCacheWithoutSaving( '_tmp_autoparse_media', 0 );
return $_result;
}
}
}
}
/* It's not media - so we'll use [url] - check we're allowed first */
if( !isset( $this->_bbcodes['display']['url'] ) or ( $this->_bbcodes['display']['url']['bbcode_sections'] != 'all' and !in_array( $this->parsing_section, explode( ',', $this->_bbcodes['display']['url']['bbcode_sections'] ) ) ) )
{
// We're not allowed to use [url] here
return $matches[0];
}
/* Ensure bbcode is stripped for the actual URL */
/* @link http://community.invisionpower.com/tracker/issue-22580-bbcode-breaks-link-add-bold-formatting-to-part-of-link/ */
if ( preg_match( '#\[\w#', $matches[2] ) )
{
$wFormatting = $matches[2];
$matches[2] = $this->stripAllTags( $matches[2] );
return $this->parseBbcode( $matches[1] . '[url="' . $matches[2] . '"]' . $wFormatting . '[/url]' . $_extra, 'display', 'url' );
}
else
{
/* Is option enforced? */
if ( empty( $this->_bbcodes['display']['url']['bbcode_optional_option'] ) )
{
return $this->parseBbcode( $matches[1] . '[url="' . $matches[2] . '"]' . $matches[2] . '[/url]' . $_extra, 'display', 'url' );
}
else
{
return $this->parseBbcode( $matches[1] . '[url]' . $matches[2] . '[/url]' . $_extra, 'display', 'url' );
}
}
}
/**
* Retrieve the proper emoticon image code
*
* @access protected
* @param string Emoticon code we are replacing (i.e. :D)
* @param string Emoticon image to display (i.e. 'biggrin.png')
* @return string Converted text
*/
protected function _retrieveSmiley( $_emoCode, $_emoImage )
{
//-----------------------------------------
// INIT
//-----------------------------------------
if ( ! $_emoCode or ! $_emoImage )
{
return '';
}
$this->emoticon_count++;
$this->emoticon_alts[ $this->emoticon_count ] = array( "#EMO_ALT_{$this->emoticon_count}#", $_emoCode );
//-----------------------------------------
// Return
//-----------------------------------------
return "";
}
/**
* Sort the smilies nicely in order of length.
*/
protected function _sortSmilies()
{
$emoticons = array();
if ( ! count( $this->_sortedSmilies ) )
{
/* Sort them! */
$this->_sortedSmilies = $this->cache->getCache('emoticons');
usort( $this->_sortedSmilies, array( $this, '_thisUsort' ) );
}
}
/**
* Custom sort operation
*
* @param string A
* @param string B
* @return integer
*/
protected static function _thisUsort($a, $b)
{
if ( strlen($a['typed']) == strlen($b['typed']) )
{
return 0;
}
return ( strlen($a['typed']) > strlen($b['typed']) ) ? -1 : 1;
}
/**
* Check and make safe embedded codes
* @param array $matches
*/
protected function _checkForEmbeddedCode( $matches )
{
$txt = $matches[0];
$map = array();
/* Load parser */
$classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/text/parser.php', 'classes_text_parser' );
$parser = new $classToLoad();
/* Fetch paired opening and closing tags */
$data = $parser->getTagPositions( $txt, 'code' );
if ( is_array( $data ) && count( $data ) )
{
foreach( $data['open'] as $id => $val )
{
$o = $data['open'][ $id ];
$c = $data['close'][ $id ] - $o;
$slice = substr( $txt, $o, $c );
$_origLen = strlen( $slice );
$slice = preg_replace( '/\[/', '[', $slice );
$slice = preg_replace( '/\/(\w+?)\]/', '/\1]', $slice );
$_newLen = strlen( $slice );
if ( $_newLen != $_origLen )
{
/* Bump up next */
foreach( $data['open'] as $_id => $_val )
{
$_o = $data['open'][ $_id ];
/* Ascii face alert */
if ( $_o > $o )
{
$data['open'][ $_id ] += ( $_newLen - $_origLen );
$data['close'][ $_id ] += ( $_newLen - $_origLen );
}
}
}
$txt = substr_replace( $txt, $slice, $o, $c );
}
}
return $txt;
}
/**
* Return paired opening and closing positions.
* @param string $txt
* @param string $tag
* @return array
*/
protected function _getEmbeddedPositions( $txt, $tag )
{
$close_tag = '[/' . $tag . ']';
$open_tag = '[' . $tag;
$map = array();
$iteration = 0;
/* Pick through bit of code */
while( ( $curPos = stripos( $txt, $open_tag, $curPos ) ) !== false )
{
$map['open'][ $iteration ] = $curPos;
$new_pos = strpos( $txt, ']', $curPos ) ? strpos( $txt, ']', $curPos ) : $curPos + 1;
/* Got an option, grab that */
$_option = substr( $txt, $curPos + strlen($open_tag), (strpos( $txt, ']', $curPos ) - ($curPos + strlen($open_tag) )) );
/* Got a closing tag? */
$closingTagPos = stripos( $txt, $close_tag, $new_pos );
if ( $closingTagPos !== false )
{
$map['close'][ $iteration ] = $closingTagPos;
$_content = substr( $txt, ($curPos + strlen( $open_tag ) + strlen($_option) + 1), (stripos( $txt, $close_tag, $curPos ) - ($curPos + strlen($open_tag) + strlen($_option) + 1)) );
$_tagToEnd = substr( $txt, ($curPos + strlen( $open_tag ) + strlen($_option) + 1), strlen( $txt ));
/* Did we have an opening tag in that mess? */
if ( $_content && stristr( $_content, $open_tag ) )
{
$count = substr_count( strtolower( $_content ), strtolower( $open_tag) );
/* Found N opening tags in portion of text */
if ( $count > 0 )
{
/* So now find Nth closing tag */
$_nPos = $closingTagPos + strlen( $close_tag );
while( $count > 0 )
{
$_closePos = stripos( $txt, $close_tag, $_nPos );
if ( $_closePos !== false )
{
$map['close'][ $iteration ] = $_closePos;
$_nPos = $_closePos + strlen( $close_tag );
if ( $_nPos >= strlen( $txt ) )
{
$count == 0;
}
}
$count--;
}
}
}
}
$iteration++;
$curPos = stripos( $txt, $open_tag ) ? stripos( $txt, $open_tag ) : $curPos + 1;
if ( $curPos > strlen($txt) )
{
$curPos = 0;
break;
}
}
return $map;
}
}