]+?)?>#i', $text, $matches );
foreach( $matches[1] as $id => $match )
{
if ( stristr( $match, 'href' ) )
{
$count++;
}
}
return $count;
}
/**
* Get the number of images
* @param string $text
* @param boolean $parseTest
*/
public function getEmoticonCount( $text )
{
$count = 0;
$text = str_replace( "<#EMO_DIR#>", "", $text );
preg_match_all( '#
]+?)?>#i', $text, $matches );
foreach( $matches[1] as $id => $match )
{
if ( stristr( $match, 'class="bbc_emoticon"' ) || stristr( $match, "class='bbc_emoticon'" ) )
{
$count++;
}
}
return $count;
}
/**
* Is an allowed URL type
* @param string $url
* @return boolean
*/
public function isAllowedImgUrl( $url )
{
if ( $this->settings['img_ext'] )
{
$path = @parse_url( html_entity_decode( trim( $url ) ), PHP_URL_PATH );
$pieces = explode( '.', $path );
$ext = array_pop( $pieces );
$ext = strtolower( $ext );
if ( ! in_array( $ext, explode( ',', str_replace( '.', '', strtolower($this->settings['img_ext']) ) ) ) )
{
return false;
}
}
return true;
}
/**
* Is allowed URL
* @param string $url
* @return boolean
*/
public function isAllowedUrl( $url )
{
if ( $this->settings['ipb_use_url_filter'] )
{
$list_type = $this->settings['ipb_url_filter_option'] == "black" ? "blacklist" : "whitelist";
if( $this->settings['ipb_url_' . $list_type ] )
{
$list_values = array();
$list_values = explode( "\n", str_replace( "\r", "", $this->settings['ipb_url_' . $list_type ] ) );
if( $list_type == 'whitelist' )
{
$list_values[] = "http://{$_SERVER['HTTP_HOST']}/*";
}
if ( count( $list_values ) )
{
$good_url = 0;
foreach( $list_values as $my_url )
{
if( !trim($my_url) )
{
continue;
}
$my_url = preg_quote( $my_url, '/' );
$my_url = str_replace( '\*', "(.*?)", $my_url );
if ( $list_type == "blacklist" )
{
if( preg_match( '/' . $my_url . '/i', $url ) )
{
return false;
}
}
else
{
if ( preg_match( '/' . $my_url . '/i', $url ) )
{
$good_url = 1;
}
}
}
if ( ! $good_url AND $list_type == "whitelist" )
{
return false;
}
}
}
}
return true;
}
/**
* Replace bad words
*
* @param string Raw text
* @param bool If a URL is not allowed due to white/blacklist, the URL should be checked. See ticket 883140 & Line 1370 of classes_text_parser_bbcode
* @return string Converted text
*/
public function parseBadWords( $text='', $stripUrls = TRUE )
{
/* @link http://community.invisionpower.com/resources/bugs.html/_/ip-board/report-center-bypass-word-filter-r40719 */
if( self::$Perms['memberData']['member_group_id'] AND !self::$Perms['memberData']['g_id'] )
{
self::$Perms['memberData'] = array_merge( self::$Perms['memberData'], $this->caches['group_cache'][ self::$Perms['memberData']['member_group_id'] ] );
if( self::$Perms['memberData']['mgroup_others'] )
{
self::$Perms['memberData'] = ips_MemberRegistry::setUpSecondaryGroups( self::$Perms['memberData'] );
}
}
/* Empty text or bypass? */
if ( $text == '' || self::$Perms['memberData']['g_bypass_badwords'] )
{
return $text;
}
$badwords = $this->cache->getCache('badwords');
$temp_text = $text;
$urls = array();
/* Got any naughty words? */
if ( ! is_array( $badwords ) OR ! count( $badwords ) )
{
return $text;
}
/* strip out URLs so replacements aren't made */
if ( $stripUrls )
{
preg_match_all( '#((http|https|news|ftp)://(?:[^<>\)\[\"\s]+|[a-zA-Z0-9/\._\-!&\#;,%\+\?:=]+))#is', $text, $matches );
foreach( $matches[0] as $m )
{
$c = count( $urls );
$urls[ $c ] = $m;
$text = str_replace( $m, '', $text );
}
}
//-----------------------------------------
// Convert back entities
//-----------------------------------------
for( $i = 65; $i <= 90; $i++ )
{
$text = str_replace( "" . $i . ";", chr($i), $text );
}
for( $i = 97; $i <= 122; $i++ )
{
$text = str_replace( "" . $i . ";", chr($i), $text );
}
//-----------------------------------------
// Go all loopy
//-----------------------------------------
foreach( $badwords as $r )
{
$r['type'] = str_replace( '&', '&', IPSText::UNhtmlspecialchars( $r['type'] ) );
if ( $this->parseType != 'topics' )
{
$r['swop'] = strip_tags( $r['swop'] );
}
$replace = $r['swop'] ? $r['swop'] : '######';
if ( $r['m_exact'] )
{
$r['type'] = preg_quote( $r['type'], "/" );
/* Link */
// if ( IPS_DOC_CHAR_SET == 'UTF-8' && IPSText::isUTF8( $text ) )
// {
// $text = preg_replace( '/(^|\p{L}|\s)' . $r['type'] . '(\p{L}|!|\?|\.|,|$)/i', "\\1{$replace}\\2", $text );
// }
// else
// {
// \b does not work well because it matches word boundary, which is technically a \w to \W shift
// @see http://stackoverflow.com/questions/6531724/how-exactly-do-regular-expression-word-boundaries-work-in-php
// What we really want to look for is a non-word character on either side, so this works
// Bad word filter for $!^& becomes $!^&. Submitted in a post that is $!^&
and is not a shift from non-word to word character
if ( IPS_DOC_CHAR_SET == 'UTF-8' && IPSText::isUTF8( $text ) )
{
$text = preg_replace( '/(^|\W)' . $r['type'] . '(\W|$)/iu', "\\1" . $replace . "\\2", $text );
}
else
{
$text = preg_replace( '/(^|\W)' . $r['type'] . '(\W|$)/i', "\\1" . $replace . "\\2", $text );
}
/* I'd retest that for a dollar! */
if ( strstr( $r['type'], '$' ) )
{
$test = preg_replace( '#(\\\\)?\$#', '$', $r['type'] );
if ( DOC_IPS_CHAR_SET == 'UTF-8' && IPSText::isUTF8( $test ) )
{
$text = preg_replace( '/(^|\W)' . preg_quote( $test ) . '(\W|$)/iu', "\\1" . $replace . "\\2", $text );
}
else
{
$text = preg_replace( '/(^|\W)' . preg_quote( $test ) . '(\W|$)/i', "\\1" . $replace . "\\2", $text );
}
}
// }
}
else
{
//----------------------------
// 'ass' in 'class' kills css
//----------------------------
if( strtolower( $r['type'] ) == 'ass' )
{
$text = preg_replace( "/(?#is', $text, $matches );
for ( $i = 0; $i < count($matches[0]); $i++ )
{
if ( isset( $matches[1][$i] ) )
{
$text = str_replace( $matches[0][$i], $urls[ $matches[1][$i] ], $text );
}
}
}
return $text ? $text : $temp_text;
}
/**
* Parse emoticons in text
*
* @param string $txt
* @return string $txt
*/
public function parseEmoticons( $txt )
{
/* Sort them in length order first */
$this->_sortSmilies();
$_codeBlocks = array();
$_c = 0;
/* Now parse them! */
if ( self::$Perms['parseEmoticons'] && ! $this->parse_html )
{
/* Make CODE tags safe... */
while( preg_match( '/())<\/pre>)/s', $txt, $matches ) )
{
$find = $matches[0];
$replace = '';
$_codeBlocks[ $_c ] = $find;
$txt = str_replace( $find, $replace, $txt );
$_c++;
}
/* Make CODE tags safe... */
while( preg_match( '/(\[code(.+?(?=\[\/code\]))\[\/code\])/s', $txt, $matches ) )
{
$find = $matches[0];
$replace = '';
$_codeBlocks[ $_c ] = $find;
$txt = str_replace( $find, $replace, $txt );
$_c++;
}
$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 = IPSText::UNhtmlspecialchars( $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;
/* Cheap check */
if ( ! stristr( $txt, $_emoCode ) )
{
continue;
}
// -----------------------------------------
// These are chars that can't surround the emo
// -----------------------------------------
$invalidWrappers = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\"/";
// -----------------------------------------
// 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 || $lastCloseTagPosition === 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;
}
}
}
}
/* 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 );
}
}
/* Convert code tags back... */
while( preg_match( '//', $txt, $matches ) )
{
$find = $matches[0];
$replace = $_codeBlocks[ $matches[1] ];
$txt = str_replace( $find, $replace, $txt );
}
}
return $txt;
}
/**
* Remove quotes
* @param string $txt
* @param bool If true, only quotes created by us will be stripped
*/
public function stripQuotes( $txt, $onlyStripIpsQuotes=true )
{
if ( stristr( $txt, '[quote' ) )
{
$txt = $this->stripBbcode( 'quote', $txt );
}
if ( stristr( $txt, 'getTagPositions( $txt, 'blockquote', array( '<' , '>' ) );
if ( is_array( $data['openWithTag'] ) )
{
foreach( $data['openWithTag'] as $id => $val )
{
if ( $onlyStripIpsQuotes )
{
$tag = substr( $txt, $data['openWithTag'][ $id ], ( $data['open'][ $id ] - $data['openWithTag'][ $id ] ) );
if ( strpos( $tag, 'ipsBlockquote' ) === false )
{
continue;
}
}
$o = $data['openWithTag'][ $id ];
$c = $data['closeWithTag'][ $id ] - $o;
$slice = substr( $txt, $o, $c );
/* Need to bump up lengths of opening and closing */
$_origLength = strlen( $slice );
/* Remove */
$slice = '';
$_newLength = strlen( $slice );
$txt = substr_replace( $txt, $slice, $o, $c );
/* Bump! */
if ( $_newLength != $_origLength )
{
foreach( $data['openWithTag'] as $_id => $_val )
{
$_o = $data['openWithTag'][ $_id ];
if ( $_o > $o )
{
$data['openWithTag'][ $_id ] += ( $_newLength - $_origLength );
$data['closeWithTag'][ $_id ] += ( $_newLength - $_origLength );
}
}
}
}
}
}
return $txt;
}
/**
* Removes bbcode tag + contents within the tag
*
* @access public
* @param
* string Tag to strip
* @param
* string Raw text
* @return string text
*/
public function stripBbcode( $tag, $txt )
{
// -----------------------------------------
// Protect against endless loops
// -----------------------------------------
static $iteration = array();
if ( isset( $iteration[$tag] ) and $iteration[$tag] > $this->settings['max_bbcodes_per_post'] )
{
return $txt;
}
$iteration[$tag] = isset( $iteration[$tag] ) ? $iteration[$tag] ++ : 1;
// Got Quotes (tm)? or any tag really
if ( stripos( $txt, '[' . $tag ) !== false )
{
// -----------------------------------------
// First grab start and end positions
// -----------------------------------------
$start_position = stripos( $txt, '[' . $tag );
$end_position = stripos( $txt, '[/' . $tag . ']', $start_position );
// -----------------------------------------
// If no end position or start position,
// we have a mismatched bbcode...return
// -----------------------------------------
if ( $start_position === false or $end_position === false )
{
return $txt;
}
// -----------------------------------------
// Then extract the content inside the bbcode
// -----------------------------------------
$inner_content = substr( $txt, stripos( $txt, ']', $start_position ) + 1, $end_position - ( stripos( $txt, ']', $start_position ) + 1 ) );
// -----------------------------------------
// Is this bbcode nested in the inner content
// -----------------------------------------
$extra_closers = substr_count( $inner_content, '[' . $tag );
// -----------------------------------------
// If so we need to move to the last ending tag
// -----------------------------------------
if ( $extra_closers > 0 )
{
for( $done = 0 ; $done < $extra_closers ; $done ++ )
{
$end_position = stripos( $txt, '[/' . $tag . ']', $end_position + 1 );
}
}
// -----------------------------------------
// Get rid of the bbcode opening + content + closing
// -----------------------------------------
$txt = substr_replace( $txt, '', $start_position, $end_position - $start_position + strlen( '[/' . $tag . ']' ) );
// -----------------------------------------
// And parse recursively
// -----------------------------------------
return $this->stripBbcode( $tag, trim( $txt ) );
}
else
{
return $txt;
}
}
/**
* Remove ALL tags
*
* @access public
* @param
* string Raw text
* @param
* boolean Whether or not to run through pre-edit-parse first
* @return string text
*/
public function stripAllTags( $txt )
{
$txt = $this->stripBbcode( 'quote', $txt );
foreach( $this->cache->getCache( 'bbcode' ) as $bbcode )
{
$txt = preg_replace( "#\[{$bbcode['bbcode_tag']}\](.+?)\[/{$bbcode['bbcode_tag']}\]#is", "\\1 ", $txt );
$txt = preg_replace( "#\[{$bbcode['bbcode_tag']}=([^\]]+?)\](.+?)\[/{$bbcode['bbcode_tag']}\]#is", "\\2 ", $txt );
$txt = str_ireplace( "[{$bbcode['bbcode_tag']}]", '', $txt );
$txt = str_ireplace( "[/{$bbcode['bbcode_tag']}]", '', $txt );
// -----------------------------------------
// Strip single bbcodes properly
// -----------------------------------------
if ( $bbcode['bbcode_single_tag'] )
{
$regex = $bbcode['bbcode_single_tag'];
// -----------------------------------------
// If this has option, adjust regex
// -----------------------------------------
if ( $bbcode['bbcode_useoption'] )
{
$regex .= '=([^\]]+?)';
}
$txt = preg_replace( "#\[{$regex}\]#is", " ", $txt );
}
}
// $txt = preg_replace( "#\[(.+?)\]#is", " ", $txt );
$txt = preg_replace( '#\[([^\]]+?)=([^\]]+?)\]#is', " ", $txt );
$txt = preg_replace( '#\[/([^\]]+?)\]#is', " ", $txt );
$txt = preg_replace( '#\[attachment=(.+?)\]#is', " ", $txt );
$txt = str_replace( '[*]', '', $txt );
return $txt;
}
/**
* Remove raw smilies
*
* @access public
* @param
* string Raw text
* @return string with smiley codes removed
*/
public function stripEmoticons( $txt )
{
$codes_seen = array();
if ( count( $this->cache->getCache( 'emoticons' ) ) > 0 )
{
foreach( $this->cache->getCache( 'emoticons' ) 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;
$invalidWrappers = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/* Cheap check */
if ( ! stristr( $txt, $_emoCode ) )
{
continue;
}
while ( ( $position = strpos( $txt, $_emoCode, $emoPosition ) ) !== false )
{
if ( strpos( $invalidWrappers, substr( $txt, $position - 1, 1 ) ) === false and strpos( $invalidWrappers, substr( $txt, ( $position + strlen( $_emoCode ) ), 1 ) ) === false )
{
$txt = substr_replace( $txt, '', $position, strlen( $_emoCode ) );
$position += strlen( $_emoCode );
}
$emoPosition = $position + 1;
if ( $emoPosition > strlen( $txt ) )
{
break;
}
}
}
}
return $txt;
}
/**
* Strip shared media
*
* @param string Raw posted text
* @return string Raw text with no shared media
*/
public function stripSharedMedia( $txt )
{
$txt = preg_replace( '#\[sharedmedia=([^\]]+?)\]#is', " ", $txt );
return $txt;
}
/**
* Strip images
*
* @param string
* @return string
*/
public function stripImages( $txt )
{
$txt = preg_replace( '#
]+?)>#i', '', $txt );
return $txt;
}
/**
* Convert IMG codes into text smilies
*
* @param text $txt
* @return text $txt
*/
public function emoticonImgtoCode( $txt )
{
if ( count( $this->cache->getCache( 'emoticons' ) ) > 0 )
{
$emoDir = IPSText::getEmoticonDirectory();
$txt = str_replace( '<#EMO_DIR#>', $this->registry->output->skin['set_emo_dir'], $txt );
foreach( $this->cache->getCache( 'emoticons' ) as $row )
{
if ( $row['emo_set'] != $emoDir )
{
continue;
}
/* This can shave a lot of loading time off of a site if there is a lot of large text being parsed,
especially if there are a lot of emoticons too */
if( strpos( $txt, $row['image'] ) === false )
{
continue;
}
/* BBCode */
$txt = preg_replace( '#(\s)?\[img\]' . preg_quote( $this->settings['public_cdn_url'] . 'style_emoticons/' . $this->registry->output->skin['set_emo_dir'] . '/' . $row['image'], '#' ) . '\[/img\]#', ' ' . $row['typed'], $txt );
/* HTML */
$txt = preg_replace( '#(\s)?
]+?)src=(?:[\'"])' . preg_quote( $this->settings['public_cdn_url'] . 'style_emoticons/' . $this->registry->output->skin['set_emo_dir'] . '/' . $row['image'], '#' ) . '(?:[\'"])(?:[^>]+?)?>#', ' ' . $row['typed'], $txt );
}
}
return $txt;
}
/**
* Returns an array of tags this user is not allowed
* to use.
* @return array
*/
public function getDisabledTags()
{
$disabled = array();
$coreTags = array( "font", "size", "img", "url", "code", "quote", "color" );
$bbcodeCache = $this->cache->getCache('bbcode');
foreach( $coreTags as $tag )
{
if ( !in_array( $tag, array_keys( $bbcodeCache ) ) )
{
$disabled[] = $tag;
}
}
foreach( $bbcodeCache as $bbcode )
{
/* Allowed this BBCode? */
if ( $bbcode['bbcode_sections'] != 'all' || $bbcode['bbcode_groups'] != 'all' )
{
$sections = explode( ',', $bbcode['bbcode_sections'] );
$groups = array_diff( explode( ',', $bbcode['bbcode_groups'] ), array( '' ) );
$mygroups = array( self::$Perms['memberData']['member_group_id'] );
$group_pass = false;
$section_pass = false;
if ( self::$Perms['memberData']['mgroup_others'] )
{
$mygroups = array_diff( array_merge( $mygroups, explode( ',', IPSText::cleanPermString( self::$Perms['memberData']['mgroup_others'] ) ) ), array( '' ) );
}
/* Perms */
if ( $bbcode['bbcode_groups'] != 'all' )
{
foreach( $groups as $g_id )
{
if ( in_array( $g_id, $mygroups ) )
{
$group_pass = true;
}
}
}
else
{
$group_pass = true;
}
/* Sections */
if ( $bbcode['bbcode_sections'] != 'all' )
{
foreach( $sections as $section )
{
if ( $section == self::$Perms['parseArea'] )
{
$section_pass = true;
}
}
}
else
{
$section_pass = true;
}
if ( $section_pass == false || $group_pass == false )
{
$disabled[] = $bbcode['bbcode_tag'];
}
}
}
return $disabled;
}
/**
* Return paired opening and closing positions.
* @param string $txt
* @param string $tag
* @return array
*/
public function getTagPositions( $txt, $tag, $brackets=array('[',']') )
{
$close_tag = $brackets[0] . '/' . $tag . $brackets[1];
$open_tag = $brackets[0] . $tag;
$map = array();
$iteration = 0;
/* Pick through bit of code */
while( ( $curPos = stripos( $txt, $open_tag, $curPos ) ) !== false )
{
if ( $iteration > 1000 )
{
break;
}
/* Make sure the next character is either ] = or a space */
$nextChar = substr( $txt, $curPos + strlen( $tag ) + 1, 1 );
if ( $nextChar != $brackets[1] && $nextChar != '=' && $nextChar != ' ' )
{
if ( $curPos > strlen($txt) )
{
$curPos = 0;
break;
}
$curPos++;
continue;
}
$map['openWithTag'][ $iteration ] = $curPos;
$map['open'][ $iteration ] = $curPos + strlen( $open_tag );
$new_pos = stripos( $txt, $brackets[1], $curPos ) ? stripos( $txt, $brackets[1], $curPos ) : $curPos + 1;
/* Got an option, grab that */
$_option = substr( $txt, $curPos + strlen($open_tag), (stripos( $txt, $brackets[1], $curPos ) - ($curPos + strlen($open_tag) )) );
$map['open'][ $iteration ] += intval( strlen( $_option ) ) + 1;
/* Got a closing tag? */
$closingTagPos = stripos( $txt, $close_tag, $new_pos );
if ( $closingTagPos !== false )
{
$map['close'][ $iteration ] = $closingTagPos;
$map['closeWithTag'][ $iteration ] = $closingTagPos + strlen( $close_tag );
/* What content do we believe we have between the opening and closing tags? */
$_content = substr( $txt, ($curPos + strlen( $open_tag ) + strlen($_option) + 1), ($closingTagPos - ($curPos + strlen($open_tag) + strlen($_option) + 1)) );
/* Did we have an opening tag in that mess? */
if ( $_content && stristr( $_content, $open_tag ) )
{
/* How many opening tags did we find...probably just 1 */
$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 we have opening tags to inspect... */
while( $count > 0 )
{
$_closePos = stripos( $txt, $close_tag, $_nPos );
if ( $_closePos !== false )
{
$map['close'][ $iteration ] = $_closePos;
$map['closeWithTag'][ $iteration ] = $_closePos + strlen( $close_tag );
$_content = substr( $txt, ($curPos + strlen( $open_tag ) + strlen($_option) + 1), ($_closePos - ($curPos + strlen($open_tag) + strlen($_option) + 1)) );
$count = substr_count( strtolower( $_content ), strtolower( $open_tag ) );
$ccount = substr_count( strtolower( $_content ), strtolower( $close_tag ) );
if( $count == $ccount )
{
$count = 0;
}
$_nPos = $_closePos + strlen( $close_tag );
if ( $_nPos >= strlen( $txt ) )
{
$count == 0;
}
}
else
{
$count = 0;
}
}
}
}
}
$iteration++;
$curPos = $closingTagPos ? $closingTagPos : $curPos + 1;
if ( $curPos > strlen($txt) )
{
$curPos = 0;
break;
}
}
return $map;
}
/**
* Build a quote tag
* @param string $content
* @param string $author
* @param string $date
* @param int $pid
*/
public function buildQuoteTag( $content, $author='', $date='', $collapsed=0, $pid=0 )
{
$ops = array();
if ( $author )
{
$ops[] = 'data-author="' . $author . '"';
}
if ( $pid )
{
$ops[] = 'data-cid="' . $pid . '"';
}
if ( $date )
{
if ( strlen( $date ) == 10 && intval( $date ) == $date )
{
$ops[] = 'data-time="' . $date . '"';
}
else
{
$ops[] = 'data-date="' . $date . '"';
}
}
if ( $collapsed )
{
$ops[] = 'data-collapsed="' . $collapsed . '"';
}
/* Parse out attachments and make into links */
preg_match_all( '#\[attachment=(.+?):(.+?)\]#', $content, $_matches );
if( is_array( $_matches[1] ) && count( $_matches[1] ) )
{
foreach( $_matches[1] as $idx => $attach_id )
{
$content = str_replace( "[attachment={$attach_id}:{$_matches[2][$idx]}]", $this->registry->getClass('output')->getReplacement('post_attach_link') . " {$_matches[2][$idx]}", $content );
}
}
/* Convert if we need to */
if ( $this->isBBCode( $content ) )
{
$content = $this->BBCodeToHtml( $content );
}
/* ARGH MY EYES ARGH MY EYES SOMEONE DID A BAD WORD */
$content = $this->parseBadWords( $content );
/* We need the wrapping here - _stripParagraphWrap removes the first and last
tags if they exist, but that means content like this remains unchanged:
something
something else
and there's no wrapper container, which is necessary in the editor for our javascript. This fixes these reports:
@link http://community.invisionpower.com/resources/bugs.html/_/ip-board/cannot-go-down-using-keyboard-arrow-in-some-cases-r41735
@link http://community.invisionpower.com/resources/bugs.html/_/ip-board/cite-in-firefox-problematic-r41737
@link http://community.invisionpower.com/resources/bugs.html/_/ip-board/quote-inappropriately-split-r41736
@link http://community.invisionpower.com/resources/bugs.html/_/ip-board/two-lines-sometimes-still-removed-r41738
@link http://community.invisionpower.com/resources/bugs.html/_/ip-board/cant-make-new-line-in-editor-r41722 */
return "
' . $this->_stripParagraphWrap( $content ) . '
';
}
/**
* Parses the bbcode to be shown in the polls.
* Parses img and url, if enabled
*
* @param string Raw input text to parse
* @return string Parsed text ready to be displayed
*/
public function parsePollTags( $text )
{
if ( stristr( $text, '[img' ) || stristr( $text, '[url' ) || stristr( $text, '[sharedmedia' ) )
{
$classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/text/parser/bbcode.php', 'class_text_parser_bbcode' );
$bbcode = new $classToLoad();
$text = $this->display( $bbcode->_parseBBCode( $text, 'display', array( 'img', 'url', 'sharedmedia' ) ) );
}
return $text;
}
/**
* To fix legacy issues, all HTML entities are parsed into HTML tags
* So this function restores code boxes
* @param string $html
* @param string $tag
* @param array $brackets
*/
public function HtmlAllowedPreContents( $html, $tag, $brackets )
{
/* PRE: Fetch paired opening and closing tags */
$data = $this->getTagPositions( $html, $tag, $brackets );
if ( is_array( $data['open'] ) )
{
$count = count( $data['open'] );
foreach( range( 0, $count ) as $id )
{
$o = $data['open'][ $id ] ;
$c = $data['close'][ $id ] - $o;
$slice = substr( $html, $o, $c );
/* Need to bump up lengths of opening and closing */
$_origLength = strlen( $slice );
/* Fix up
[code]
....
[/code]
*/
$slice = preg_replace( '#^#', '', $slice );
$slice = trim( preg_replace( '#
$#', '', trim( $slice ) ) );
/* Extra conversion for BBCODE>HTML mode */
$slice = str_replace( "<", "<", $slice );
$slice = str_replace( ">", ">", $slice );
$_newLength = strlen( $slice );
$html = substr_replace( $html, $slice, $o, $c );
/* Bump! */
if ( $_newLength != $_origLength )
{
foreach( $data['open'] as $_id => $_val )
{
$_o = $data['open'][ $_id ];
if ( $_o > $o )
{
$data['open'][ $_id ] += ( $_newLength - $_origLength );
$data['close'][ $_id ] += ( $_newLength - $_origLength );
}
}
}
}
}
return $html;
}
/**
* Get attributes from a tag (lazy)
* @param string $tag
*/
public function getTagAttributes( $tag )
{
$processedTag = str_replace( array( '"' ), '"', $tag );
$processedTag = str_replace( array( ''', ''' ), '\'', $processedTag );
$processedTag = str_replace( ' ', ' ', $processedTag );
$attributes = array();
/* http://community.invisionpower.com/resources/bugs.html/_/ip-board/apostrophe-in-rte-in-quote-name-is-cut-off-r41322 */
preg_match_all( '#(\S+?)=(?:[\'"])(.+?)(?:[\'"])(\s|>|\]$)#', $processedTag, $matches, PREG_SET_ORDER );
foreach( $matches as $val )
{
$attributes[ trim( $val[1] ) ] = $val[2];
}
return $attributes;
}
/**
* Expand the acronyms for SEO
* @param string $txt
*/
protected function _seoAcronymExpansion( $txt )
{
if ( $txt == '' )
{
return $txt;
}
$acronyms = $this->cache->getCache('ipseo_acronyms');
if( !is_array($acronyms) OR !count($acronyms) )
{
return $txt;
}
$temp_text = $txt;
$urls = array();
$tags = array();
$txt = str_replace( '<#EMO_DIR#>', '-#-#-#EMO_DIR#-#-#-', $txt );
/* Grab images */
preg_match_all( '#
]+?)>#i', $txt, $matches );
foreach( $matches[0] as $m )
{
$c = count( $urls );
$urls[ $c ] = $m;
$txt = str_replace( $m, '', $txt );
}
/* Grab */
preg_match_all( '#]+?)>#i', $txt, $matches );
foreach( $matches[0] as $m )
{
$c = count( $urls );
$urls[ $c ] = $m;
$txt = str_replace( $m, '', $txt );
}
/* Grab all other tags */
preg_match_all( '#<(?:[/a-z]{1,})([^>]+?)>#i', $txt, $matches );
foreach( $matches[0] as $m )
{
$c = count( $tags );
$tags[ $c ] = $m;
$txt = str_replace( $m, '', $txt );
}
/* Grab non linked URLs */
preg_match_all( '#((http|https|news|ftp)://(?:[^<>\)\[\"\s]+|[a-zA-Z0-9/\._\-!&\#;,%\+\?:=]+))#is', $txt, $matches );
foreach( $matches[0] as $m )
{
$c = count( $urls );
$urls[ $c ] = $m;
$txt = str_replace( $m, '', $txt );
}
//-----------------------------------------
// Convert back entities
//-----------------------------------------
for( $i = 65; $i <= 90; $i++ )
{
$txt = str_replace( "" . $i . ";", chr($i), $txt );
}
for( $i = 97; $i <= 122; $i++ )
{
$txt = str_replace( "" . $i . ";", chr($i), $txt );
}
//-----------------------------------------
// Go all loopy
//-----------------------------------------
if ( is_array($acronyms) && count($acronyms) )
{
foreach( $acronyms as $r )
{
$this->_currentAcronym = $r;
/* vv Ticket #835804 */
$wordModifier = ( IPS_DOC_CHAR_SET == 'UTF-8' && IPSText::isUTF8( $txt ) ) ? '[^<>\p{L}]|\b' : '[^<>a-zA-Z0-9-_&;]';
$caseModifier = empty($r['a_casesensitive']) ? 'i' : '';
$r['a_short'] = preg_quote( $r['a_short'], "/" );
$txt = preg_replace_callback( '/(^|\b|\W)(' . $r['a_short'] . ')(\b|\W|$)/' . $caseModifier, array( $this, '_replaceAcronym' ), $txt );
}
}
/* replace urls */
if ( count( $urls ) )
{
foreach( $urls as $k => $v )
{
$txt = str_replace( "", $v, $txt );
}
}
/* replace tags */
if ( count( $tags ) )
{
foreach( $tags as $k => $v )
{
$txt = str_replace( "", $v, $txt );
}
}
$txt = str_replace( '-#-#-#EMO_DIR#-#-#-', '<#EMO_DIR#>', $txt );
return $txt ? $txt : $temp_text;
}
/**
* Callback function to replace a found acronym
*
* @param array $matches Array of matches
* @return @e string Replaced text
*/
private function _replaceAcronym( $matches=array() )
{
return $this->_currentAcronym['a_semantic'] ? "{$matches[1]}{$matches[2]}{$matches[3]}" : $matches[1] . $this->_currentAcronym['a_long'] . $matches[3];
}
/**
* Strip paragraph wrap tags
* @param string $txt
* @return string
*/
protected function _stripParagraphWrap( $txt )
{
$txt = trim( $txt );
/* Clean up */
$txt = preg_replace( '#^(
]+?)?>){1,}#i', '', $txt );
$txt = trim( $this->_stripEmptyLeadingAndTrailingParagraphTags( $txt ) );
if ( substr( $txt, 0, 3 ) == '' && substr( $txt, -4 ) == '
' )
{
$txt = substr( $txt, 3, -4 );
}
$txt = trim( $txt );
return $txt;
}
/**
* Strips off blank or empty P tags
* @param string $txt
* @return string
*/
protected function _stripEmptyLeadingAndTrailingParagraphTags( $txt )
{
/* Strip leading Ps */
while( preg_match( '#^]+?)?' . '>(( |\s)+?)?
#i', $txt, $match ) )
{
$txt = trim( preg_replace( '#^]+?)?' . '>(( |\s)+?)?
#i', '', $txt ) );
}
/* Strip trailing Ps */
while( preg_match( '#]+?)?' . '>(( |\s)+?)?
$#i', $txt, $match ) )
{
$txt = trim( preg_replace( '#]+?)?' . '>(( |\s)+?)?
$#i', '', $txt ) );
}
/* Strip trailing
*/
while( preg_match( '#
]+?)?' . '/>(( |\s)+?)?$#i', $txt, $match ) )
{
$txt = trim( preg_replace( '#
]+?)?' . '/>(( |\s)+?)?$#i', '', $txt ) );
}
/* Strip trailing */
while( preg_match( '#]+?)?' . '>(( |\s)+?)?
$#i', $txt, $match ) )
{
$txt = trim( preg_replace( '#]+?)?' . '>(( |\s)+?)?
$#i', '', $txt ) );
}
return $txt;
}
/**
* Check and make safe embedded codes
* @param array $matches
*/
protected function _preserveCodeBoxes( $txt )
{
$map = array();
/* CODE: Fetch paired opening and closing tags */
$data = $this->getTagPositions( $txt, 'code', array( '[' , ']' ) );
if ( is_array( $data['open'] ) )
{
foreach( $data['open'] as $id => $val )
{
$o = $data['open'][ $id ];
$c = $data['close'][ $id ] - $o;
/* Prevent unclosed tags from breaking this */
if ( $o < 1 || $c < 1 )
{
continue;
}
$slice = substr( $txt, $o, $c );
/* Need to bump up lengths of opening and closing */
$_origLength = strlen( $slice );
/* Extra conversion for BBCODE>HTML mode */
$slice = str_replace( "[", "[", $slice );
$slice = preg_replace( '#(https|http|ftp)://#' , '\1://', $slice );
#$slice = str_replace( "]", "]", $slice );
$slice = str_replace( "\n", "", $slice );
/* Stop (r) (tm) and (c) from switching out */
$slice = preg_replace( '#\((tm|r|c)\)#i', '($1)', $slice );
$_newLength = strlen( $slice );
$txt = substr_replace( $txt, $slice, $o, $c );
/* Bump! */
if ( $_newLength != $_origLength )
{
foreach( $data['open'] as $_id => $_val )
{
$_o = $data['open'][ $_id ];
if ( $_o > $o )
{
$data['open'][ $_id ] += ( $_newLength - $_origLength );
$data['close'][ $_id ] += ( $_newLength - $_origLength );
}
}
}
}
}
/* PRE: Fetch paired opening and closing tags */
$data = $this->getTagPositions( $txt, 'pre', array( '<' , '>' ) );
if ( is_array( $data['open'] ) )
{
foreach( $data['open'] as $id => $val )
{
$o = $data['open'][ $id ] ;
$c = $data['close'][ $id ] - $o;
$slice = substr( $txt, $o, $c );
/* Need to bump up lengths of opening and closing */
$_origLength = strlen( $slice );
/* Extra conversion for BBCODE>HTML mode */
$slice = str_replace( "[", "[", $slice );
$slice = str_replace( "]", "]", $slice );
$slice = preg_replace( '#(https|http|ftp)://#' , '\1://', $slice );
$slice = str_replace( "\n", "", $slice );
/* Stop (r) (tm) and (c) from switching out */
$slice = preg_replace( '#\((tm|r|c)\)#i', '($1)', $slice );
$_newLength = strlen( $slice );
$txt = substr_replace( $txt, $slice, $o, $c );
/* Bump! */
if ( $_newLength != $_origLength )
{
foreach( $data['open'] as $_id => $_val )
{
$_o = $data['open'][ $_id ] + strlen( ' $o )
{
$data['open'][ $_id ] += ( $_newLength - $_origLength );
$data['close'][ $_id ] += ( $_newLength - $_origLength );
}
}
}
}
}
return $txt;
}
/**
* 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;
}
/**
* Add to errors
* @param string $error
*/
protected function _addParsingError( $error )
{
if ( $error && is_string( $error ) )
{
$this->_errors[] = $error;
/* Legacy @todo remove in 4 */
$this->error = $error;
}
}
/**
* 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[] = array( "#EMO_ALT_{$this->emoticon_count}#", $_emoCode );
/**
* <#EMO_DIR#> can end up not replaced at this point in specific situations.
* @link http://community.invisionpower.com/resources/bugs.html/_/ip-board/emoticons-bug-with-pm-archives-r42796
*/
if ( strstr( $this->settings['emoticons_url'], '<#EMO_DIR#>' ) AND $this->registry->output->skin['set_emo_dir'] )
{
$this->settings['emoticons_url'] = str_replace( '<#EMO_DIR#>', $this->registry->output->skin['set_emo_dir'], $this->settings['emoticons_url'] );
}
//-----------------------------------------
// Return
//-----------------------------------------
return "
";
}
/**
* Prevent auto parsing codes
*/
protected function _processNoParseCodes( $content, $passLevel=1, $allowCodeTag=false )
{
if ( $passLevel == 3 )
{
/* At this point bbcode has been converted... */
return preg_replace( '#(https|http|ftp)-~~-//#', '\1://', $content );
}
$noParse = array();
/* Find no parse codes */
foreach( $this->cache->getCache('bbcode') as $bbcode )
{
/* Allowed this BBCode? */
if ( $bbcode['bbcode_no_parsing'] )
{
/* CODE is a special case */
if ( $allowCodeTag === false && $bbcode['bbcode_tag'] == 'code' )
{
continue;
}
$noParse[ $bbcode['bbcode_tag'] ] = $bbcode['bbcode_tag'];
if ( $bbcode['bbcode_aliases'] )
{
$tmp = explode( ',', $bbcode['bbcode_aliases'] );
foreach( $tmp as $bc )
{
if ( trim( $bc ) )
{
$noParse[ $bc ] = $bc;
}
}
}
}
}
/* Got anything? */
if ( ! count( $noParse ) )
{
return $content;
}
/* Sort by key length so that IMGTEST parses before IMG, for example */
uksort( $noParse, create_function('$a,$b', 'return strlen($a) < strlen($b);') );
foreach( $noParse as $tag )
{
if( $tag == 'img' )
{
continue;
}
/* Fetch paired opening and closing tags */
$data = $this->getTagPositions( $content, $tag, array( '[', ']' ) );
if ( is_array( $data ) && count( $data ) )
{
foreach( $data['open'] as $id => $val )
{
$o = $data['open'][ $id ];
$c = $data['close'][ $id ] - $o;
/* Prevent unclosed tags from breaking this */
if ( $o < 1 || $c < 1 )
{
continue;
}
$slice = substr( $content, $o, $c );
$_origLen = strlen( $slice );
if ( $passLevel == 2 )
{
//* No linkify */
preg_match_all( '#]+?)href=["\']([^"\']+?)["\']([^>]+?)?>(.+?)(|$)#is', $slice, $urlMatches );
/* Finish up URLs and such */
for( $i = 0 ; $i < count( $urlMatches[0] ) ; $i++ )
{
$raw = $urlMatches[0][ $i ];
$url = $urlMatches[1][ $i ];
$text = $urlMatches[3][ $i ];
/* Some posts end up with the closing tag as part of the URL */
$url = str_replace( '%5B/' . $tag . '%5D', '', $url );
$slice = str_replace( $raw, preg_replace( '#(https|http|ftp)://#' , '\1-~~-//', $url ), $slice );
}
}
else
{
$slice = preg_replace( '/\[/', '[', $slice );
$slice = preg_replace( '/\/(\w+?)\]/', '/\1]', $slice );
$slice = preg_replace( '#\((r|tm|c)\)#', '(\1)', $slice );
$slice = str_replace( "{parse", "{parse", $slice );
$slice = preg_replace( '#(https|http|ftp)://#' , '\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 );
}
}
}
$content = substr_replace( $content, $slice, $o, $c );
}
}
}
return $content;
}
}