* Invision Power Services * IP.Board v3.4.9 * Text Parsing: HTML * Last Updated: $Date: 2012-06-08 09:28:02 +0100 (Fri, 08 Jun 2012) $ * * * @author $Author: mmecham $ * @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: 10894 $ * * Revised in 3.4 by Matt Mecham * Refactored into a text parsing module * */ if ( ! defined( 'IN_IPB' ) ) { print "

Incorrect access

You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files."; exit(); } class class_text_parser_html extends classes_text_parser { /** * Parsing array * * @access public * @var array */ public $_nonDelimiters = array( "=", ' ' ); /** * Parsing array * * @access public * @var array */ public $_delimiters = array( "'", '"' ); /** * Main font sizes * * @access protected * @var array */ private $_fontSizes = array( 1 => 8, 2 => 10, 3 => 12, 4 => 14, 5 => 18, 6 => 24, 7 => 36, 8 => 48 ); /** * Constructor * * @access public * @param object Registry object * @return @e void */ public function __construct() { return parent::__construct(); } /** * Takes HTML from CKEditor and makes it suitable for IPB display * @param string $html * @return string */ public function toDisplay( $html ) { /* Convert _prettyXprint _lang- _linenums */ preg_match_all( '# $match ) { $txt = $matches[0][$id]; $txt = str_replace( '_prettyXprint', 'prettyprint', $txt ); $txt = str_replace( '_lang-' , 'lang-' , $txt ); $txt = str_replace( '_linenums' , 'linenums' , $txt ); $html = str_replace( $matches[0][$id], $txt, $html ); } /* ensure emo dir is converted over (legacy posts) */ if ( strstr( $html, '<#EMO_DIR#>' ) ) { $html = str_replace( '<#EMO_DIR#>', $this->registry->getClass('output')->skin['set_emo_dir'], $html ); } /* Add lightbox rel tag. Emoticons should be code at this point */ if ( ! parent::$Perms['parseHtml'] ) { $html = preg_replace( "/(?!)]+?)>/i", "", $html ); $html = preg_replace( "/]+?)class='bbc_img'([^>]+?)>/i", "", $html ); } return $html; } /** * Convert to BBCode * @param string * @return string */ public function toBBCode( $content ) { /* Anything to parse? */ if ( ! $content ) { return $content; } $ot = $content; $content = str_replace( array( "\r\n", "\n" ), "\n", $content ); /* Tidy up first */ $content = str_replace( '', '

', $content ); $content = preg_replace( '#

(\s+?)?(\s+?)?(]+?)?>|

)#is', '', $content ); /* Before we content/strip newlines, lets make code safe */ $content = $this->_recurseAndParse( 'pre', $content, "_parsePreTag" ); $content = str_replace( '    ', "{'tab'}", $content ); $content = str_replace( "\t", "{'tab'}", $content ); $content = str_replace( ' ', ' ', $content ); /* looks to be non RTE content that has ended up there */ if ( ! strstr( $content, '", $content ); } else { $content = str_replace( "\n", "", $content ); } /* Restore preserved newlines */ $content = str_replace( "", "\n", $content ); // ----------------------------------------- // Clean up already encoded HTML // ----------------------------------------- $content = str_replace( '"', '"', $content ); $content = str_replace( ''', "'", $content ); // ----------------------------------------- // Fix up incorrectly nested urls / BBcode // ----------------------------------------- // @link // http://community.invisionpower.com/tracker/issue-24704-pasting-content-in-rte-with-image-first/ // Revert the fix for now as it causes more issues than the original one $content = preg_replace( '#]+?)\[(.+?)[\'"](.+?)' . '>(.+?)\[\\2#is', '\\4[\\2', $content ); // $content = preg_replace( // '#\'"]+?)[\'"](.*?)>(.+?)\[([^<]+?)#is', '\\3[\\4', $content ); // ----------------------------------------- // Make URLs safe (prevent tag stripping) // ----------------------------------------- $content = preg_replace_callback( '#<(a href|img src)=([\'"])([^>]+?)(\\2)#is', array( $this, '_unhtmlUrl' ), $content ); // ----------------------------------------- // WYSI-Weirdness #1: BR tags to \n // ----------------------------------------- $content = preg_replace( '#]+?)>#', "
", $content ); $content = str_ireplace( array( "
", "
" ), "\n", $content ); $content = trim( $content ); // ----------------------------------------- // Before we can use strip_tags, we should // clean out any javascript and CSS // ----------------------------------------- $content = preg_replace( '/\(.*?)\<\/script\>/', '', $content ); $content = preg_replace( '/\(.*?)\<\/style\>/', '', $content ); // ----------------------------------------- // Remove tags we're not bothering with // with PHPs wonderful strip tags func // ----------------------------------------- $content = strip_tags( $content, '


      • ' );	
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #2: named anchors
        		// -----------------------------------------
        		
        		$content = preg_replace( '#(.+?)#is', "\\1", $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #2.1: Empty a hrefs
        		// -----------------------------------------
        		
        		$content = preg_replace( '#]+)>#is', "", $content );
        		$content = preg_replace( '#\\1(.+?)#is', "\\1", $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #2.2: Double linked links
        		// -----------------------------------------
        		
        		$content = preg_replace( '#href=[\"\']\w+://(%27|\'|\"|")(.+?)\\1[\"\']#is', "href=\"\\2\"", $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #3: Headline tags
        		// -----------------------------------------
        		
        		$content = preg_replace( "#<(h[0-9])(?:[^>]+?)?>(.+?)#is", "\n[b]\\2[/b]\n", $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #4: Font tags
        		// -----------------------------------------
        		
        		$content = preg_replace( '#(\s*)#is', " ", $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #5a: Fix up smilies: IE RTE
        		// @see Ticket 623146
        		// -----------------------------------------
        		
        		$content = preg_replace( '#(\S+?)#i', "\\2", $content );
        		$content = preg_replace( '#alt=\'[\"\'](\S+?)[\'\"]\'#i', "alt='\\1'", $content );
        		$content = preg_replace( '#class=\'[\"\'](\S+?)[\'\"]\'#i', "class='\\1'", $content );
        		$content = preg_replace( '#([a-zA-Z0-9])[\"\'](.+?)[\"\']#i', "\\1 \\4", $content );
        		
        		/* Remove  */
        		$content = preg_replace( '##', '', $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #6: Image tags
        		// -----------------------------------------
        		
        		$content = preg_replace( '#[\"\'][\"\']#', "", $content );
        		$content = preg_replace( '#]+?)?' . '>#is', "[img]\\1[/img]", $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #7: Linked URL tags
        		// -----------------------------------------
        		
        		$content = preg_replace( '#\[url=(\"|\'|")#is', "[url=\\1\\2", $content );
        		
        		// -----------------------------------------
        		// WYSI-Weirdness #8: Make relative images full links
        		// -----------------------------------------
        		
        		$content = preg_replace( '#\[img\](/)?public/style_(emoticons|images)#i', '[img]' . $this->settings['board_url'] . '/public/style_' . '\\2', $content );
        		
        		// -----------------------------------------
        		// Clean up whitespace between lists
        		// -----------------------------------------
        		
        		$content = preg_replace( '#
      • \s+?(\S)#', '
      • \\1', $content ); $content = preg_replace( '#
      • \s+?(\S)#', '\\1', $content ); $content = preg_replace( '#
        (\s+?)?#si', '', $content ); // ----------------------------------------- // Now, recursively parse the other tags // to make sure we get the nested ones // ----------------------------------------- $content = $this->_recurseAndParse( 'b', $content, "_parseSimpleTag", 'b' ); $content = $this->_recurseAndParse( 'u', $content, "_parseSimpleTag", 'u' ); $content = $this->_recurseAndParse( 'strong', $content, "_parseSimpleTag", 'b' ); $content = $this->_recurseAndParse( 'i', $content, "_parseSimpleTag", 'i' ); $content = $this->_recurseAndParse( 'em', $content, "_parseSimpleTag", 'i' ); $content = $this->_recurseAndParse( 'strike', $content, "_parseSimpleTag", 's' ); $content = $this->_recurseAndParse( 'del', $content, "_parseSimpleTag", 's' ); $content = $this->_recurseAndParse( 's', $content, "_parseSimpleTag", 's' ); $content = $this->_recurseAndParse( 'sup', $content, "_parseSimpleTag", 'sup' ); $content = $this->_recurseAndParse( 'sub', $content, "_parseSimpleTag", 'sub' ); // ----------------------------------------- // More complex tags // ----------------------------------------- $content = $this->_recurseAndParse( 'a', $content, "_parseAnchorTag" ); $content = $this->_recurseAndParse( 'font', $content, "_parseFontTag" ); $content = $this->_recurseAndParse( 'div', $content, "_parseDivTag" ); $content = $this->_recurseAndParse( 'p', $content, "_parseParagraphTag" ); $content = $this->_recurseAndParse( 'span', $content, "_parseSpanTag" ); $content = $this->_recurseAndParse( 'blockquote', $content, "_parseBlockquoteTag" ); /* Possibility of preceeding \n because of P tag */ $content = trim( $content ); // ----------------------------------------- // Lists // ----------------------------------------- $content = $this->_recurseAndParse( 'ol', $content, "_parseListTag" ); $content = $this->_recurseAndParse( 'ul', $content, "_parseListTag" ); // ----------------------------------------- // WYSI-Weirdness #10: Random junk // ----------------------------------------- $content = str_ireplace( array( "", "", "" ), "", $content ); // ----------------------------------------- // WYSI-Weirdness #11: Fix up list stuff // ----------------------------------------- $content = preg_replace( '#
      • (.*)((?=
      • )|
      • )#is', '\\1', $content ); // ----------------------------------------- // WYSI-Weirdness #11.1: Safari badness // ----------------------------------------- $content = str_replace( "
  • ", "", $content ); /* * Sometimes, unclosed P tags remain if text is copied and pasted * directly */ $content = preg_replace( '#<(p|div|ul|li)([^\>]+?)\>#is', '', $content ); // ----------------------------------------- // WYSI-Weirdness #12: Convert rest to HTML // ----------------------------------------- //$content = str_replace( '<', '<', $content ); //$content = str_replace( '>', '>', $content ); // ----------------------------------------- // WYSI-Weirdness #13: Remove useless tags // ----------------------------------------- /* Remove embedded stuffs */ $content = preg_replace( '#\[(center|right|left)\]\[\1\]#i', '[\1]', $content ); $content = preg_replace( '#\[/(center|right|left)\]\[/\1\]#i', '[/\1]', $content ); while ( preg_match( '#\<(b|u|i|s|li)\>(\s+?)?\#is', $content ) ) { $content = preg_replace( '#\<(b|u|i|s|li)\>(\s+?)?\#is', "", $content ); } // ----------------------------------------- // WYSI-Weirdness #14: Opera crap // ----------------------------------------- $content = preg_replace( '#\[(font|size|color)\]=[\"\']([^\"\']+?)[\"\']\]\[/\\1\]#is', "", $content ); // ----------------------------------------- // WYSI-Weirdness #14.1: Safari crap // ----------------------------------------- $content = preg_replace( '#\[(font|size|color)="([^\"\']+?)"\]\[/\\1\]#is', "", $content ); // ----------------------------------------- // WYSI-Weirdness #15: No domain in FF? // ----------------------------------------- $content = preg_replace( '#(http|https):\/\/index.php(.*?)#is', $this->settings['board_url'] . '/index.php\\2', $content ); $content = preg_replace( '#\[url=[\'\"]index.php(.*?)[\"\']#is', "[url=\"" . $this->settings['board_url'] . '/index.php\\1"', $content ); /* Fix up incorrect tags outside of quotes */ $content = preg_replace( '#\[(b|u|s)\](\[quote)#', '\2[\1]', $content ); $content = preg_replace( '#(\[/quote])\[/(b|u|s)\]#', '[/\2]\1', $content ); // ----------------------------------------- // Replace tabs // ----------------------------------------- // PSDebug::addLogMessage( $content, 'editor', false, true ); $content = str_replace( "{'tab'}", "\t", $content ); /* Replace newlines */ $content = str_replace( "\n", "
    ", $content ); // ----------------------------------------- // Now call the santize routine to make // html and nasties safe. VITAL!! // ----------------------------------------- //$content = $this->_clean( trim( $content ), $NO_SANITIZE, true ); /* Ensure [xxx=" is fixed */ $content = preg_replace( '#\[(\w+?)="(.+?)"\]#', "[\\1=\"\\2\"]", $content ); /* Relative paths */ $content = preg_replace( '#\[img\](../../|&\#46;&\#46;/&\#46;&\#46;/)public/#', '[img]' . $this->settings['board_url'] . '/public/', $content ); return $content; } /** * RTE: Parse List tag * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseListTag( $tag, $between_text, $opening_tag, $parse_tag ) { $list_type = trim( preg_replace( '#"?list-style-type:\s+?([\d\w\_\-]+);?"?#si', '\\1', $this->_getValueOfOption( 'style', $opening_tag ) ) ); if ( ! $list_type ) { /* If we cannot find the list type from the style attribute, try classes */ $classes = explode( ' ', trim( $this->_getValueOfOption( 'class', $opening_tag ) ) ); $list_type = array_pop( $classes ); } $css = $this->_getValueOfOption( 'class', $opening_tag ); // ----------------------------------------- // Set up a default... // ----------------------------------------- if ( ( stristr( $css, ' decimal' ) ) or ! $list_type and $tag == 'ol' ) { $list_type = 'decimal'; } // ----------------------------------------- // Tricky regex to clean all list items // ----------------------------------------- $between_text = preg_replace( '#]+?)\>#is', '
  • ', $between_text ); $between_text = preg_replace( '#
  • \s+?
  • #is', '', $between_text ); //$between_text = preg_replace( '#
  • ((.(?!\\1
  • ', $between_text ); $between_text = trim( $this->_recurseAndParse( 'li', $between_text, "_parseListElement" ) ); $allowed_types = array( 'upper-alpha' => 'A', 'upper-roman' => 'I', 'lower-alpha' => 'a', 'lower-roman' => 'i', 'decimal' => '1' ); if ( ! $allowed_types[$list_type] ) { $open_tag = "[list]\n"; } else { $open_tag = '[list=' . $allowed_types[$list_type] . "]\n"; } return $open_tag . $this->_recurseAndParse( $tag, $between_text, '_parseListTag' ) . "\n[/list]"; } /** * RTE: Parse List Element tag * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseListElement( $tag, $between_text, $opening_tag, $parse_tag ) { /* Check for quote tags */ $openQuote = substr_count( strtolower( $between_text ), '[quote' ); $closeQuote = substr_count( strtolower( $between_text ), '[/quote]' ); if ( $openQuote != $closeQuote ) { $between_text = str_replace( array( '[quote', '[/quote]' ), array( '[quote', '[/quote]' ), $between_text ); } return '[*]' . rtrim( $between_text ) . "\n"; } /** * RTE: Parse paragraph tags * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseParagraphTag( $tag, $between_text, $opening_tag, $parse_tag ) { /* Got any text to wrap? beware empty() because we might have 0 */ if ( $between_text == '' ) { return; } // ----------------------------------------- // Reset local start tags // ----------------------------------------- $start_tags = ""; $end_tags = ""; // ----------------------------------------- // Check for inline style moz may have added and append start_tags // ----------------------------------------- $this->_parseStyles( $opening_tag, $start_tags, $end_tags ); // ----------------------------------------- // Now parse align and style (if any) // ----------------------------------------- $align = $this->_getValueOfOption( 'align', $opening_tag ); $style = $this->_getValueOfOption( 'style', $opening_tag ); $css = $this->_getValueOfOption( 'class', $opening_tag ); $textAlign = $this->_extractCssValue( $style, 'text-align' ); $marginLeft = intval( $this->_extractCssValue( $style, 'margin-left' ) ); if ( $align == 'center' or $textAlign == 'center' or stristr( $css, 'bbc_center' ) ) { $start_tags = "\n" . $start_tags; if ( ! stristr( $start_tags, '[center]' ) ) { $start_tags .= '[center]'; $end_tags .= '[/center]'; } } else if ( $align == 'left' or $textAlign == 'left' ) { $start_tags = "\n" . $start_tags; if ( ! stristr( $start_tags, '[left]' ) ) { $start_tags .= '[left]'; $end_tags .= '[/left]'; } } else if ( $align == 'right' or $textAlign == 'right' ) { $start_tags = "\n" . $start_tags; if ( ! stristr( $start_tags, '[right]' ) ) { $start_tags .= '[right]'; $end_tags .= '[/right]'; } } else if ( $marginLeft ) { $level = ( $marginLeft > 40 ) ? $marginLeft / 40 : 1; if ( trim( $between_text ) ) { $start_tags = "\n" . $start_tags; $start_tags .= '[indent=' . $level . ']'; $end_tags .= '[/indent]'; } } else { // No align? Make paragraph $start_tags .= "\n"; } /* Was there just a blank space in there? */ if ( preg_match( '#^[ ]+$#', $between_text ) ) { return "\n"; } return $start_tags . $this->_recurseAndParse( 'p', $between_text, '_parseParagraphTag' ) . $end_tags; } /** * RTE: Parse pre tags * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parsePreTag( $tag, $between_text, $opening_tag, $parse_tag ) { /* Got any text to wrap? beware empty() because we might have 0 */ if ( $between_text == '' ) { return; } $class = $this->_getValueOfOption( 'class', $opening_tag ); $lang = 'auto'; $linenum = 0; $names = preg_split( '/ /', $class, null, PREG_SPLIT_NO_EMPTY ); if ( count( $names ) ) { foreach( $names as $name ) { if ( strstr( $name, '_lang-' ) ) { list( $meh, $oohYeah ) = explode( '-', $name ); $lang = trim( $oohYeah ); } if ( strstr( $name, '_linenums:' ) ) { list( $Jacob, $Edward ) = explode( ':', $name ); $linenum = intval( $Edward ); } } } /* Make newlines safe */ $between_text = str_replace( "\n", "", $between_text ); return '[code=' . $lang . ':' . $linenum . ']' . $this->_recurseAndParse( 'pre', $between_text, '_parsePreTag' ) . '[/code]'; } /** * RTE: Parse Span tag * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseSpanTag( $tag, $between_text, $opening_tag, $parse_tag ) { $start_tags = ""; $end_tags = ""; // ----------------------------------------- // Check for inline style moz may have added // ----------------------------------------- $this->_parseStyles( $opening_tag, $start_tags, $end_tags ); return $start_tags . $this->_recurseAndParse( 'span', $between_text, '_parseSpanTag' ) . $end_tags; } /** * RTE: Parse Fieldset tag used to contain code * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseFieldsetTag( $tag, $between_text, $opening_tag, $parse_tag ) { // ----------------------------------------- // Reset local start tags // ----------------------------------------- $start_tags = ""; $end_tags = ""; // ----------------------------------------- // Check for inline style moz may have added // ----------------------------------------- $this->_parseStyles( $opening_tag, $start_tags, $end_tags ); // ----------------------------------------- // Now parse align (if any) // ----------------------------------------- $class = $this->_getValueOfOption( 'class', $opening_tag ); if ( $class == 'ipbCode' ) { $start_tags .= '[code]'; $end_tags .= '[/code]'; } // ----------------------------------------- // Get recursive text // ----------------------------------------- $final = $this->_recurseAndParse( 'fieldset', trim( $between_text ), '_parseFieldsetTag' ); // ----------------------------------------- // Now return // ----------------------------------------- return $start_tags . $final . $end_tags; } /** * RTE: Parse BLOCKQUOTE tag * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseBlockquoteTag( $tag, $between_text, $opening_tag, $parse_tag ) { // ----------------------------------------- // Reset local start tags // ----------------------------------------- $start_tags = ""; $end_tags = ""; // ----------------------------------------- // Check for inline style moz may have added // ----------------------------------------- $attr = $this->_parseDataAttributes( $opening_tag ); $extra = ''; if ( $attr['author'] ) { $extra .= ' name="' . trim( str_replace( '"', '"', $attr['author'] ) ) . '"'; } if ( $attr['cid'] ) { $extra .= ' post="' . intval( $attr['cid'] ) . '"'; } if ( $attr['time'] ) { $extra .= ' timestamp="' . intval( $attr['time'] ) . '"'; } if ( $attr['date'] ) { $extra .= ' date="' . trim( str_replace( '"', '"', $attr['date'] ) ) . '"'; } // ----------------------------------------- // Get recursive text // ----------------------------------------- $final = $this->_recurseAndParse( 'blockquote', $between_text, '_parseBlockquoteTag' ); // ----------------------------------------- // Now return // ----------------------------------------- return '[quote' . $extra . ']' . $final . '[/quote]'; } /** * RTE: Parse DIV tag * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseDivTag( $tag, $between_text, $opening_tag, $parse_tag ) { // ----------------------------------------- // Reset local start tags // ----------------------------------------- $start_tags = ""; $end_tags = ""; $allowEndNl = true; // ----------------------------------------- // Check for inline style moz may have added // ----------------------------------------- $this->_parseStyles( $opening_tag, $start_tags, $end_tags ); // ----------------------------------------- // Now parse align (if any) // ----------------------------------------- $align = $this->_getValueOfOption( 'align', $opening_tag ); if ( $align == 'center' ) { $start_tags .= '[center]'; $end_tags .= '[/center]'; $allowEndNl = false; } else if ( $align == 'left' ) { $start_tags .= '[left]'; $end_tags .= '[/left]'; $allowEndNl = false; } else if ( $align == 'right' ) { $start_tags .= '[right]'; $end_tags .= '[/right]'; $allowEndNl = false; } else { // No align? Make paragraph //$start_tags .= "\n"; } // ----------------------------------------- // Get recursive text // ----------------------------------------- $final = $this->_recurseAndParse( 'div', $between_text, '_parseDivTag' ); // ----------------------------------------- // Now return // ----------------------------------------- return $start_tags . $final . $end_tags; } /** * RTE: Parse HTML5 data- attributes * * @access protected * @param string Opening tag * @return array */ protected function _parseDataAttributes( $opening_tag ) { $attributes = array(); preg_match_all( '#data-(\w+?)=(?:[\'"])([^\'"]+?)(?:[\'"])#i', $opening_tag, $matches ); if ( is_array( $matches[1] ) && count( $matches[1]) ) { foreach( $matches[1] as $id => $match ) { $attr = $matches[1][$id]; $val = $matches[2][$id]; $attributes[ $attr ] = $val; } } return $attributes; } /** * RTE: Parse style attributes (color, font, size, b, i..etc) * * @access protected * @param * string Opening tag * @param * string Start tags * @param * string End tags * @return string text */ protected function _parseStyles( $opening_tag, &$start_tags, &$end_tags ) { $style_list = array( array( 'tag' => 'color', 'rx' => '(? 1 ), array( 'tag' => 'font', 'rx' => 'font-family:\s*([^;]+);?', 'match' => 1 ), array( 'tag' => 'size', 'rx' => 'font-size:\s*(.+);?', 'match' => 1 ), array( 'tag' => 'b', 'rx' => 'font-weight:\s*(bold);?' ), array( 'tag' => 'i', 'rx' => 'font-style:\s*(italic);?' ), array( 'tag' => 'u', 'rx' => 'text-decoration:\s*(underline);?' ), array( 'tag' => 'left', 'rx' => 'text-align:\s*(left);?' ), array( 'tag' => 'center', 'rx' => 'text-align:\s*(center);?' ), array( 'tag' => 'right', 'rx' => 'text-align:\s*(right);?' ), array( 'tag' => 'background', 'rx' => 'background-color:\s*([^;]+);?', 'match' => 1 ) ); // ----------------------------------------- // get style option // ----------------------------------------- $style = $this->_getValueOfOption( 'style', $opening_tag ); $class = $this->_getValueOfOption( 'class', $opening_tag ); // ----------------------------------------- // Convert RGB to hex // ----------------------------------------- $style = preg_replace_callback( '#(?_convertRealsizeToBbsize( $match[$data['match']] ) . "]"; } } else { $start_tags .= "[{$data['tag']}]"; } if ( $start_tags && $data['tag'] != 'font' or $match[$data['match']] != 'Verdana, arial, sans-serif' ) { $end_tags = "[/{$data['tag']}]" . $end_tags; } } } if ( $class == 'bbc_underline' ) { $start_tags = '[u]'; $end_tags = '[/u]'; } } /** * RTE: Parse FONT tag * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseFontTag( $tag, $between_text, $opening_tag, $parse_tag ) { $font_tags = array( 'font' => 'face', 'size' => 'size', 'color' => 'color' ); $start_tags = ""; $end_tags = ""; // ----------------------------------------- // Check for attributes // ----------------------------------------- foreach( $font_tags as $bbcode => $string ) { $option = $this->_getValueOfOption( $string, $opening_tag ); if ( $option ) { $start_tags .= "[{$bbcode}=\"{$option}\"]"; $end_tags = "[/{$bbcode}]" . $end_tags; if ( $this->debug == 2 ) { print "
    Got bbcode=$bbcode / opening_tag=$opening_tag"; print "
    - Adding [$bbcode=\"$option\"] [/$bbcode]"; print "
    -- start tags now: {$start_tags}"; print "
    -- end tags now: {$end_tags}"; } } } // ----------------------------------------- // Now check for inline style moz may have // added // ----------------------------------------- $this->_parseStyles( $opening_tag, $start_tags, $end_tags ); return $start_tags . $this->_recurseAndParse( 'font', $between_text, '_parseFontTag' ) . $end_tags; } /** * RTE: Simple tags * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseSimpleTag( $tag, $between_text, $opening_tag, $parse_tag ) { if ( ! $parse_tag ) { $parse_tag = $tag; } return "[{$parse_tag}]" . $this->_recurseAndParse( $tag, $between_text, '_parseSimpleTag', $parse_tag ) . "[/{$parse_tag}]"; } /** * RTE: Parse A HREF tag * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Opening tag complete * @param * string Parse tag * @return string text */ protected function _parseAnchorTag( $tag, $between_text, $opening_tag, $parse_tag = '' ) { $mytag = 'url'; $href = $this->_getValueOfOption( 'href', $opening_tag ); $class = $this->_getValueOfOption( 'class', $opening_tag ); $href = str_replace( '<', '<', $href ); $href = str_replace( '>', '>', $href ); $href = str_replace( ' ', '%20', $href ); if ( preg_match( '#^mailto\:#is', $href ) ) { $mytag = 'email'; $href = str_replace( "mailto:", "", $href ); } return "[{$mytag}=\"{$href}\"]" . $this->_recurseAndParse( $tag, $between_text, '_parseAnchorTag', $parse_tag ) . "[/{$mytag}]"; } /** * RTE: Recursively parse tags * * @access protected * @param * string Tag * @param * string Text between opening and closing tag * @param * string Callback Function * @param * string Parse tag * @return string text */ protected function _recurseAndParse( $tag, $text, $function, $parse_tag = '' ) { // ----------------------------------------- // INIT // ----------------------------------------- $tag = strtolower( $tag ); $open_tag = "<" . $tag; $open_tag_len = strlen( $open_tag ); $close_tag = ""; $close_tag_len = strlen( $close_tag ); $start_search_pos = 0; $tag_begin_loc = 1; // ----------------------------------------- // Start the loop // ----------------------------------------- while ( $tag_begin_loc !== FALSE ) { $lowtext = strtolower( $text ); $tag_begin_loc = @strpos( $lowtext, $open_tag, $start_search_pos ); $lentext = strlen( $text ); $quoted = ''; $got = FALSE; $tag_end_loc = FALSE; // ----------------------------------------- // No opening tag? Break // ----------------------------------------- if ( $tag_begin_loc === FALSE ) { break; } // ----------------------------------------- // Pick through text looking for delims // ----------------------------------------- for( $end_opt = $tag_begin_loc ; $end_opt <= $lentext ; $end_opt ++ ) { $chr = $text{$end_opt}; // ----------------------------------------- // We're now in a quote // ----------------------------------------- if ( ( in_array( $chr, $this->_delimiters ) ) and $quoted == '' ) { $quoted = $chr; } // ----------------------------------------- // We're not in a quote any more // ----------------------------------------- else if ( ( in_array( $chr, $this->_delimiters ) ) and $quoted == $chr ) { $quoted = ''; } // ----------------------------------------- // Found the closing bracket of the open tag // ----------------------------------------- else if ( $chr == '>' and ! $quoted ) { $got = TRUE; break; } else if ( ( in_array( $chr, $this->_nonDelimiters ) ) and ! $tag_end_loc ) { $tag_end_loc = $end_opt; } } // ----------------------------------------- // Not got the complete tag? // ----------------------------------------- if ( ! $got ) { break; } // ----------------------------------------- // Not got a tag end location? // ----------------------------------------- if ( ! $tag_end_loc ) { $tag_end_loc = $end_opt; } // ----------------------------------------- // Extract tag options... // ----------------------------------------- $tag_opts = substr( $text, $tag_begin_loc + $open_tag_len, $end_opt - ( $tag_begin_loc + $open_tag_len ) ); $actual_tag_name = substr( $lowtext, $tag_begin_loc + 1, ( $tag_end_loc - $tag_begin_loc ) - 1 ); // ----------------------------------------- // Check against actual tag name... // ----------------------------------------- if ( $actual_tag_name != $tag ) { $start_search_pos = $end_opt; continue; } // ----------------------------------------- // Now find the end tag location // ----------------------------------------- $tag_end_loc = strpos( $lowtext, $close_tag, $end_opt ); // ----------------------------------------- // Not got one? Break! // ----------------------------------------- if ( $tag_end_loc === FALSE ) { break; } // ----------------------------------------- // Check for nested tags // ----------------------------------------- $nest_open_pos = strpos( $lowtext, $open_tag, $end_opt ); while ( $nest_open_pos !== FALSE and $tag_end_loc !== FALSE ) { // ----------------------------------------- // It's not actually nested // ----------------------------------------- if ( $nest_open_pos > $tag_end_loc ) { break; } if ( $this->debug == 2 ) { print "\n\n
    ( " . htmlspecialchars( $open_tag ) . " ) NEST FOUND\n\n"; } $tag_end_loc = strpos( $lowtext, $close_tag, $tag_end_loc + $close_tag_len ); $nest_open_pos = strpos( $lowtext, $open_tag, $nest_open_pos + $open_tag_len ); } // ----------------------------------------- // Make sure we have an end location // ----------------------------------------- if ( $tag_end_loc === FALSE ) { $start_search_pos = $end_opt; continue; } $this_text_begin = $end_opt + 1; $between_text = substr( $text, $this_text_begin, $tag_end_loc - $this_text_begin ); $offset = $tag_end_loc + $close_tag_len - $tag_begin_loc; // ----------------------------------------- // Pass to function // ----------------------------------------- $final_text = $this->$function( $tag, $between_text, $tag_opts, $parse_tag ); // ----------------------------------------- // #DEBUG // ----------------------------------------- if ( $this->debug == 2 ) { print "
    REPLACED {$function}($tag, ..., $tag_opts):
    " . htmlspecialchars( substr( $text, $tag_begin_loc, $offset ) ) . "
    WITH:
    " . htmlspecialchars( $final_text ) . "
    NEXT ITERATION"; } // ----------------------------------------- // Swap text // ----------------------------------------- $text = substr_replace( $text, $final_text, $tag_begin_loc, $offset ); $start_search_pos = $tag_begin_loc + strlen( $final_text ); } return $text; } /** * RTE: Extract option HTML * * @access protected * @param * string Option * @param * string Text * @return string text */ protected function _getValueOfOption( $option, $text ) { if ( $option == 'face' ) { // Bad font face, bad preg_match( "#{$option}(\s+?)?\=(\s+?)?[\"']?(.+?)([\"']|$|color|size|>)#is", $text, $matches ); } else { // @link // http://community.invisionpower.com/tracker/issue-29336-colours-do-not-work-in-signatures // ckeditor should have a more universal formatting so let's go with // the one regex now // if( $option == 'style' AND ( $this->memberData['userAgentKey'] == // 'safari' OR $this->memberData['userAgentKey'] == 'chrome' ) ) // { /* * @link * http://community.invisionpower.com/tracker/issue-37385-editor-removes-apostrophe-from-url/ * tightened up regex end ([\"'](\s|$|>)) */ preg_match( "#{$option}(\s*?)?\=(\s*?)?[\"']?(.+?)([\"'](\s|$|>))#is", $text, $matches ); // } // else // { // preg_match( // "#{$option}(\s*?)?\=(\s*?)?[\"']?(.+?)([\"']|$|\s|>)#is", $text, // $matches ); // } } if ( $option == 'style' ) { switch( $matches[3] ) { case 'font-size: x-small;' : $matches[3] = 'font-size: 8;'; break; case 'font-size: small;' : $matches[3] = 'font-size: 10;'; break; case 'font-size: medium;' : $matches[3] = 'font-size: 12;'; break; case 'font-size: large;' : $matches[3] = 'font-size: 14;'; break; case 'font-size: x-large;' : $matches[3] = 'font-size: 18;'; break; case 'font-size: xx-large;' : $matches[3] = 'font-size: 24;'; break; case 'font-size: xxx-large;' : case 'font-size: -webkit-xxx-large;' : $matches[3] = 'font-size: 36;'; break; } } return isset( $matches[3] ) ? trim( $matches[3] ) : ''; } /** * unhtml url: Removes < and > * * @access protected * @param * array Matches from preg_replace_callback * @return string text */ protected function _unhtmlUrl( $matches = array() ) { $url = stripslashes( $matches[3] ); $type = stripslashes( $matches[1] ? $matches[1] : 'a href' ); $url = str_replace( '<', '<', $url ); $url = str_replace( '>', '>', $url ); $url = str_replace( ' ', '%20', $url ); return '<' . $type . '="' . $url . '"'; } /** * Fetches the value of an inline style * * @param string $style * @param string $lookFor * @return string boolean */ protected function _extractCssValue( $style, $lookFor ) { if ( strstr( $style, 'style=' ) ) { $style = $this->_getValueOfOption( 'style', $style ); } if ( strstr( $style, $lookFor ) ) { if ( preg_match( '#' . preg_quote( $lookFor, '#' ) . ':(?:\s+?)?(.+?)(;|$|\n)#', $style, $matches ) ) { return trim( $matches[1] ); } } return false; } /** * Converts color:rgb(x,x,x) to color:#xxxxxx * * @access protected * @param * string rgb contents: x,x,x * @param * string regex end * @return string text */ protected function _rgbToHex( $matches ) { $t = $matches[1]; $t2 = $matches[2]; $tmp = array_map( "trim", explode( ",", $t ) ); return 'color: ' . sprintf( "#%02X%02X%02X" . $t2, intval( $tmp[0] ), intval( $tmp[1] ), intval( $tmp[2] ) ); } /** * Get BBCode font size from real PX size * * @access public * @param integer PX Size * @return integer BBCode size */ protected function _convertRealsizeToBbsize( $real ) { $real = intval( $real ); $flip = array_flip( $this->_fontSizes ); //----------------------------------------- // If we have a true mapping, use it //----------------------------------------- if ( $flip[ $real ] ) { return $flip[ $real ]; } else { //----------------------------------------- // Otherwise find the next closest size down //----------------------------------------- foreach( $flip as $font => $bbcode ) { if ( $real < $font ) { return ( ( $bbcode - 1 ) > 1 ) ? ( $bbcode - 1 ) : 1; } } return 2; } } }