HEX
Server: Apache/2.4.59 (Debian)
System: Linux skycube.cz 4.19.0-25-amd64 #1 SMP Debian 4.19.289-2 (2023-08-08) x86_64
User: ilya (534)
PHP: 7.3.31-1~deb10u7
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,
Upload Files
File: /var/www/ilya/data/www/kamforum.ru/sources/classes/editor/class_editor_rte.php
<?php

/*
+--------------------------------------------------------------------------
|   Invision Power Board v2.1.5
|   =============================================
|   by Matthew Mecham
|   (c) 2001 - 2005 Invision Power Services, Inc.
|   http://www.invisionpower.com
|   =============================================
|   Web: http://www.invisionboard.com
|   Time: Tue, 18 Oct 2005 19:30:11 GMT
|   Release: 95f5a3c9ea538e4ebb097b9464fc22d2
|   Licence Info: http://www.invisionboard.com/?license
+---------------------------------------------------------------------------
|   > $Date: 2005-10-10 14:03:20 +0100 (Mon, 10 Oct 2005) $
|   > $Revision: 22 $
|   > $Author: matt $
+---------------------------------------------------------------------------
|
|   > Posting RTE Editor
|   > Module written by Matt Mecham
|   > Date started: Thursday 10th March 2005 13:47
|
+--------------------------------------------------------------------------
*/

/**
* Text Editor: RTE (WYSIWYG) Class
*
* Class for parsing RTE
*
* @package		InvisionPowerBoard
* @subpackage	TextEditor
* @author  	 	Matt Mecham
* @version		2.1
* @since		2.1.0
*/

/**
*
*/

/**
* Text Editor: RTE (WYSIWYG) Class
*
* Class for parsing RTE
*
* @package		InvisionPowerBoard
* @subpackage	TextEditor
* @author  	 	Matt Mecham
* @version		2.1
* @since		2.1.0
*/
class class_editor_module extends class_editor
{
	/**
	* Main IPS class object
	*
	* @var	object
	*/
	var $ipsclass;
	
	/**
	* Clean up HTML on save
	*
	* @var	integer
	*/
	var $clean_on_save = 1;
	
	/**
	* Allow HTML
	*
	* @var	integer
	*/
	var $allow_html = 0;
	
	/**
	* Debug level
	*
	* @var	integer
	*/
	var $debug      = 0;
	
	/**
	* Parsing array
	*
	* @var	array
	*/
	var $delimiters     = array( "'", '"' );
	
	/**
	* Parsing array
	*
	* @var	array
	*/
	var $non_delimiters = array( "=", ' ' );
	
	/**
	* Start tags
	*
	* @var	string
	*/
	var $start_tags;
	
	/**
	* End tags
	*
	* @var	string
	*/
	var $end_tags;
	
	/**
	* Dunno, forgotten
	*
	* @var	integer
	*/
	var $_called = 0;
	
	/*-------------------------------------------------------------------------*/
	// Process the raw post with BBCode before showing in the form
	/*-------------------------------------------------------------------------*/
	
	/**
	* Process the raw post with BBCode before showing in the form
	*
	* @param	string	Raw text
	* @return	string	Converted text
	*/
	function process_before_form( $t )
	{
		//-----------------------------------------
		// Opening style attributes
		//-----------------------------------------
		
		$t = preg_replace( "#<!--sizeo:(.+?)-->(.+?)<!--/sizeo-->#"               , '<font size="\\1">'       , $t );
		$t = preg_replace( "#<!--coloro:(.+?)-->(.+?)<!--/coloro-->#"             , '<font color="\\1">'      , $t );
		$t = preg_replace( "#<!--fonto:(.+?)-->(.+?)<!--/fonto-->#"               , '<font face="\\1">'       , $t );
		$t = preg_replace( "#<!--backgroundo:(.+?)-->(.+?)<!--/backgroundo-->#"   , '<font background="\\1">' , $t );
		
		//-----------------------------------------
		// Closing style attributes
		//-----------------------------------------
		
		$t = preg_replace( "#<!--sizec-->(.+?)<!--/sizec-->#"            , "</font>" , $t );
		$t = preg_replace( "#<!--colorc-->(.+?)<!--/colorc-->#"          , "</font>" , $t );
		$t = preg_replace( "#<!--fontc-->(.+?)<!--/fontc-->#"            , "</font>" , $t );
		$t = preg_replace( "#<!--backgroundc-->(.+?)<!--/backgroundc-->#", "</font>" , $t );
			
		//-----------------------------------------
		// Remove comments
		//-----------------------------------------
		
		$t = preg_replace( "#\<\!\-\-(.+?)\-\-\>#is", "", $t );
		
		//-----------------------------------------
		// Return
		//-----------------------------------------
		
		$t = $this->_make_rtf_safe( $t );
		
		//-----------------------------------------
		// Clean up nbsp
		//-----------------------------------------
		
		# Doing so knocks out tabs
		//$t = str_replace( '&nbsp;&nbsp;&nbsp;&nbsp;', "\t", $t );
		//$t = str_replace( '&nbsp;&nbsp;'            , "  ", $t );
		
		return $t;
	}
	
	/*-------------------------------------------------------------------------*/
	// Process the raw post with BBCode before saving
	/*-------------------------------------------------------------------------*/
	
	/**
	* Process the raw post with BBCode before saving
	*
	* @param	string	Raw text
	* @return	string	Converted text
	*/
	function process_after_form( $form_field )
	{
		return $this->_rte_html_to_bbcode( $this->ipsclass->txt_stripslashes($_POST[ $form_field ]) );
	}
	
	/*-------------------------------------------------------------------------*/
	// Covert RTE-HTML to BBCode
	/*-------------------------------------------------------------------------*/
	
	/**
	* Covert RTE-HTML to BBCode
	*
	* @param	string	Raw text
	* @return	string	Converted text
	*/
	function _rte_html_to_bbcode( $t )
	{
		if ( $this->debug )
		{
			$ot = $t;
		}
		
		//-----------------------------------------
		// Fix up spaces
		//-----------------------------------------
		
		$t = str_replace( '&nbsp;', ' ', $t );
		
		//-----------------------------------------
		// Gecko engine seems to put \r\n at edge
		// of iframe when wrapping? If so, add a 
		// space or it'll get weird later
		//-----------------------------------------
		
		if ( $this->ipsclass->browser['browser'] == 'mozilla' OR $this->ipsclass->browser['browser'] == 'gecko' )
		{
			$t = str_replace( "\r\n", " ", $t );
		}
		else
		{
			$t = str_replace( "\r\n", "", $t );
		}

		//-----------------------------------------
		// Clean up already encoded HTML
		//-----------------------------------------
		
		$t = str_replace( '&quot;', '"', $t );
		$t = str_replace( '&apos;', "'", $t );
		
		//-----------------------------------------
		// Fix up incorrectly nested urls / BBcode
		//-----------------------------------------
		
		$t = preg_replace( '#<a\s+?href=[\'"]([^>]+?)\[(.+?)[\'"](.+?)'.'>(.+?)\[\\2</a>#is', '<a href="\\1"\\3>\\4</a>[\\2', $t );
		
		//-----------------------------------------
		// Make URLs safe (prevent tag stripping)
		//-----------------------------------------
		
		$t = preg_replace( '#<(a href|img src)=([\'"])([^>]+?)(\\3)#ise', "\$this->unhtml_url( '\\1', '\\3' )", $t );
		
		//-----------------------------------------
		// Make EMOids safe (prevent tag stripping)
		//-----------------------------------------
		
		$t = preg_replace( '#(emoid=\")(.+?)(\")#ise', "'emoid=\"'.\$this->unhtml_emoid('\\2').'\"';", $t );
		
		//-----------------------------------------
		// Remove tags we're not bothering with
		// with PHPs wonderful strip tags func
		//-----------------------------------------
		
		if ( ! $this->allow_html )
		{
			$t = strip_tags( $t, '<h1><h2><h3><h4><h5><h6><font><span><div><br><p><img><a><li><ol><ul><b><strong><em><i><u><strike><blockquote>' );
		}
		
		//-----------------------------------------
		// WYSI-Weirdness #0: BR tags to \n
		//-----------------------------------------
		
		$t = preg_replace( "#<br.*>#isU", "\n", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #1: Named anchors
		//-----------------------------------------
		
		$t = preg_replace( "#<a\s+?name=.+?".">(.+?)</a>#is", "\\1", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #1.5: Empty a hrefs
		//-----------------------------------------
		
		$t = preg_replace( "#<a\s+?href([^>]+)></a>#is"         , ""   , $t );
		$t = preg_replace( "#<a\s+?href=(['\"])>\\1(.+?)</a>#is", "\\1", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #1.6: Double linked links
		//-----------------------------------------
		
		$t = preg_replace( "#href=[\"']\w+://(%27|'|\"|&quot;)(.+?)\\1[\"']#is", "href=\"\\2\"", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #2: Headline tags
		//-----------------------------------------
		
		$t = preg_replace( "#<(h[0-9])>(.+?)</\\1>#is", "\n[b]\\2[/b]\n", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #2.5: Fix up smilies
		//-----------------------------------------
		
		# Legacy Fix: Old smilies
		
		$t = str_replace( 'emoid=":""' , 'emoid="{{:&amp;quot;}}"', $t );
		$t = str_replace( 'emoid=":-""', 'emoid="{{:-&amp;quot;}}"', $t );
		
		# Make EMO_DIR safe so the ^> regex works
		$t = str_replace( "<#EMO_DIR#>", "&lt;#EMO_DIR&gt;", $t );
		
		# Parse...
		$t = preg_replace( "#[ ]?<([^>]+?)emoid=\"(.+?)\"([^>]+?)?".">[ ]?#ise", "' '.\$this->html_emoid('\\2').' '", $t );
		
		# And convert it back again...
		$t = str_replace( "&lt;#EMO_DIR&gt;", "<#EMO_DIR#>", $t );
		
		# Convert back
		$t = str_replace( "{{:&amp;quot;}}" , ':"' , $t );
		$t = str_replace( "{{:-&amp;quot;}}", ':-"', $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #3: Image tags
		//-----------------------------------------
		
		$t = preg_replace( "#<img.+?src=[\"'](.+?)[\"']([^>]+?)?".">#is", "[img]\\1[/img]", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #4: Linked URL tags
		//-----------------------------------------
		
		$t = preg_replace( "#\[url=(\"|'|&quot;)<a\s+?href=[\"'](.*)/??['\"]\\2/??</a>#is", "[url=\\1\\2", $t );
	
		//-----------------------------------------
		// Now, recursively parse the other tags
		// to make sure we get the nested ones
		//-----------------------------------------
		
		$t = $this->_recurse_and_parse( 'b'          , $t, "_parse_simple_tag", 'b' );
		$t = $this->_recurse_and_parse( 'u'          , $t, "_parse_simple_tag", 'u' );
		$t = $this->_recurse_and_parse( 'strong'     , $t, "_parse_simple_tag", 'b' );
		$t = $this->_recurse_and_parse( 'i'          , $t, "_parse_simple_tag", 'i' );
		$t = $this->_recurse_and_parse( 'em'         , $t, "_parse_simple_tag", 'i' );
		$t = $this->_recurse_and_parse( 'strike'     , $t, "_parse_simple_tag", 's' );
		$t = $this->_recurse_and_parse( 'blockquote' , $t, "_parse_simple_tag", 'indent' );
		
		//-----------------------------------------
		// More complex tags
		//-----------------------------------------
		
		$t = $this->_recurse_and_parse( 'a'          , $t, "_parse_anchor_tag" );
		$t = $this->_recurse_and_parse( 'font'       , $t, "_parse_font_tag" );
		$t = $this->_recurse_and_parse( 'div'        , $t, "_parse_div_tag" );
		$t = $this->_recurse_and_parse( 'span'       , $t, "_parse_span_tag" );
		$t = $this->_recurse_and_parse( 'p'          , $t, "_parse_paragraph_tag" );
		
		//-----------------------------------------
		// Lists
		//-----------------------------------------
		
		$t = $this->_recurse_and_parse( 'ol'         , $t, "_parse_list_tag" );
		$t = $this->_recurse_and_parse( 'ul'         , $t, "_parse_list_tag" );
		
		//-----------------------------------------
		// WYSI-Weirdness #6: Fix up para tags
		//-----------------------------------------
		
		$t = preg_replace( "#<p.*>#isU", "\n\n", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #7: Random junk
		//-----------------------------------------
		
		$t = preg_replace( "#(<a>|</a>|</li>)#is", "", $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #8: Fix up list stuff
		//-----------------------------------------
		
		$t = preg_replace( '#<li>(.*)((?=<li>)|</li>)#is', '\\1', $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #9: Convert rest to HTML
		//-----------------------------------------
		
		$t = str_replace(  '&lt;' , '<', $t );
		$t = str_replace(  '&gt;' , '>', $t );
		$t = str_replace(  '&amp;', '&', $t );
		$t = preg_replace( '#&amp;(quot|lt|gt);#', '&\\1;', $t );
		
		//-----------------------------------------
		// WYSI-Weirdness #10: Remove useless tags
		//-----------------------------------------
		
		while( preg_match( "#\[(url|img|b|u|i|s|email|list|indent|right|left|center)\]\[/\\1\]#is", $t ) )
		{
			$t = preg_replace( "#\[(url|img|b|u|i|s|email|list|indent|right|left|center)\]\[/\\1\]#is", "", $t );
		}
		
		//-----------------------------------------
		// Now call the santize routine to make
		// html and nasties safe. VITAL!!
		//-----------------------------------------
		
		$t = $this->_clean_post( $t );
		
		//-----------------------------------------
		// Debug?
		//-----------------------------------------
		
		if ( $this->debug )
		{
			print "<hr>";
			print nl2br(htmlspecialchars($ot));
			print "<hr>";
			print nl2br($t);
			print "<hr>";
			exit();
		}
		
		//-----------------------------------------
		// Done
		//-----------------------------------------
		
		return $t;
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse list tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse List tag
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_list_tag( $tag, $between_text, $opening_tag, $parse_tag )
	{
		$list_type = trim( preg_replace( '#"?list-style-type:\s+?([\d\w\_\-]+);?"?#si', '\\1', $this->_get_value_of_option( 'style', $opening_tag ) ) );
		
		//-----------------------------------------
		// Set up a default...
		//-----------------------------------------
		
		if ( ! $list_type and $tag == 'ol' )
		{
			$list_type = 'decimal';
		}
		
		//-----------------------------------------
		// Tricky regex to clean all list items
		//-----------------------------------------
		
		$between_text = preg_replace('#<li>((.(?!</li))*)(?=</?ul|</?ol|\[list|<li|\[/list)#siU', '<li>\\1</li>', $between_text);
		
		$between_text = $this->_recurse_and_parse( 'li', $between_text, "_parse_listelement_tag" );
		
		$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]';
		}
		else
		{
			$open_tag = '[list='.$allowed_types[ $list_type ].']';
		}
		
		return $open_tag . $this->_recurse_and_parse( $tag, $between_text, '_parse_list_tag' ) . '[/list]';
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse anchor tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse List Element tag
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_listelement_tag( $tag, $between_text, $opening_tag, $parse_tag )
	{
		return '[*]' . rtrim( $between_text );
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse anchor tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse paragraph tags
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_paragraph_tag( $tag, $between_text, $opening_tag, $parse_tag )
	{
		//-----------------------------------------
		// Reset local start tags
		//-----------------------------------------
		
		$start_tags = "";
		$end_tags   = "";
		
		//-----------------------------------------
		// Check for inline style moz may have added
		//-----------------------------------------
		
		$this->_parse_style_attributes( $opening_tag, $start_tags, $end_tags );
		
		//-----------------------------------------
		// Now parse align and style (if any)
		//-----------------------------------------
		
		$align = $this->_get_value_of_option( 'align', $opening_tag );
		$style = $this->_get_value_of_option( 'style', $opening_tag );
		
		if ( $align == 'center' )
		{
			$start_tags .= '[center]';
			$end_tags   .= '[/center]';
		}
		else if ( $align == 'left' )
		{
			$start_tags .= '[left]';
			$end_tags   .= '[/left]';
		}
		else if ( $align == 'right' )
		{
			$start_tags .= '[right]';
			$end_tags   .= '[/right]';
		}
		else
		{
			# No align? Make paragraph
			$end_tags .= "\n";
		}
		
		$end_tags .= "\n";
		
		return $start_tags . $this->_recurse_and_parse( 'p', $between_text, '_parse_paragraph_tag' ) . $end_tags;
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse anchor tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse Span tag
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_span_tag( $tag, $between_text, $opening_tag, $parse_tag )
	{
		$start_tags = "";
		$end_tags   = "";
		
		//-----------------------------------------
		// Check for inline style moz may have added
		//-----------------------------------------
		
		$this->_parse_style_attributes( $opening_tag, $start_tags, $end_tags );
		
		return $start_tags . $this->_recurse_and_parse( 'span', $between_text, '_parse_span_tag' ) . $end_tags;
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse anchor tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse DIV tag
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_div_tag( $tag, $between_text, $opening_tag, $parse_tag )
	{
		//-----------------------------------------
		// Reset local start tags
		//-----------------------------------------
		
		$start_tags = "";
		$end_tags   = "";
		
		//-----------------------------------------
		// #DEBUG
		//-----------------------------------------
		
		if ( $this->debug == 2 )
		{
			print "<b><span style='color:red'>DIV FIRED</b></span><br />Start tags: {$this->start_tags}<br />End tags: {$this->end_tags}<br />Between text:<br />".htmlspecialchars($between_text)."<hr />";
		}
		
		//-----------------------------------------
		// Check for inline style moz may have added
		//-----------------------------------------
		
		$this->_parse_style_attributes( $opening_tag, $start_tags, $end_tags );
		
		//-----------------------------------------
		// Now parse align (if any)
		//-----------------------------------------
		
		$align = $this->_get_value_of_option( 'align', $opening_tag );
		
		if ( $align == 'center' )
		{
			$start_tags .= '[center]';
			$end_tags   .= '[/center]';
		}
		else if ( $align == 'left' )
		{
			$start_tags .= '[left]';
			$end_tags   .= '[/left]';
		}
		else if ( $align == 'right' )
		{
			$start_tags .= '[right]';
			$end_tags   .= '[/right]';
		}
		
		//$end_tags .= "\n";
		
		//-----------------------------------------
		// Get recursive text
		//-----------------------------------------
		
		$final = $this->_recurse_and_parse( 'div', $between_text, '_parse_div_tag' );
		
		//-----------------------------------------
		// #DEBUG
		//-----------------------------------------
		
		if ( $this->debug == 2 )
		{
			print "\n<hr><b style='color:green'>FINISHED</b><br/ >".$start_tags . $final . $end_tags."<hr>";
		}
		
		//-----------------------------------------
		// Now return
		//-----------------------------------------
		
		return $start_tags . $final . $end_tags;
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse style attributes
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse style attributes (color, font, size, b, i..etc)
	*
	* @param	string	Opening tag complete
	* @return	string	Converted text
	*/
	function _parse_style_attributes( $opening_tag, &$start_tags, &$end_tags )
	{
		$style_list = array(
							array('tag' => 'color' , 'rx' => '(?<!\w)color:\s*([^;]+);?'  , 'match' => 1),
							array('tag' => 'font'  , 'rx' => 'font-family:\s*([^;]+);?'   , 'match' => 1),
							array('tag' => 'size'  , 'rx' => 'font-size:\s*([\d]+);?'     , '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);?'),
						  );
		
		//-----------------------------------------
		// get style option
		//-----------------------------------------
		
		$style = $this->_get_value_of_option( 'style', $opening_tag );
		
		//-----------------------------------------
		// Convert RGB to hex
		//-----------------------------------------
		
		$style = preg_replace( '#(?<!\w)color:\s+?rgb\((\d+,\s+?\d+,\s+?\d+)\)(;?)#ie', "'color: '.\$this->_rgb_to_hex( '\\1','\\2' );", $style );
		
		//-----------------------------------------
		// Pick through possible styles
		//-----------------------------------------
		
		foreach( $style_list as $data )
		{
			if ( preg_match( '#'.$data['rx'].'#i', $style, $match ) )
			{
				if ( $data['match'] )
				{
					if ( $data['tag'] != 'size' )
					{
						$start_tags .= "[{$data['tag']}={$match[$data['match']]}]";
					}
					else
					{
						$start_tags .= "[{$data['tag']}=" . $this->convert_realsize_to_bbsize($match[$data['match']]) ."]";
					}
				}
				else
				{
					$start_tags .= "[{$data['tag']}]";
				}
				
				$end_tags = "[/{$data['tag']}]" . $end_tags;
			}
		}
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse font tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse FONT tag
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_font_tag( $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->_get_value_of_option( $string, $opening_tag );
			
			if ( $option )
			{
				$start_tags .= "[$bbcode=\"$option\"]";
				$end_tags    = "[/$bbcode]" . $end_tags;
				
				if ( $this->debug == 2 )
				{
					print "<br />Got bbcode=$bbcode / opening_tag=$opening_tag";
					print "<br />- Adding [$bbcode=\"$option\"] [/$bbcode]";
					print "<br />-- start tags now: {$start_tags}";
					print "<br />-- end tags now: {$end_tags}";
				}
			}
		}
		
		//-----------------------------------------
		// Now check for inline style moz may have
		// added
		//-----------------------------------------
		
		$this->_parse_style_attributes( $opening_tag, $start_tags, $end_tags );
		
		return $start_tags . $this->_recurse_and_parse( 'font', $between_text, '_parse_font_tag' ) . $end_tags;
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse anchor tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Simple tags
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_simple_tag( $tag, $between_text, $opening_tag, $parse_tag )
	{
		if ( ! $parse_tag )
		{
			$parse_tag = $tag;
		}
		
		return "[$parse_tag]".$this->_recurse_and_parse( $tag, $between_text, '_parse_simple_tag', $parse_tag )."[/$parse_tag]";
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse simple tag
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Parse A HREF tag
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Opening tag complete
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _parse_anchor_tag( $tag, $between_text, $opening_tag, $parse_tag='' )
	{
		$mytag = 'url';
		$href  = $this->_get_value_of_option( 'href', $opening_tag );
		
		$href  = str_replace( '<', '&lt;', $href );
		$href  = str_replace( '>', '&gt;', $href );
		$href  = str_replace( ' ', '%20' , $href );
		
		if ( preg_match( '#^mailto\:#is', $href ) )
		{
			$mytag = 'email';
			$href  = str_replace( "mailto:", "", $href );
		}
		
		return "[$mytag=\"$href\"]".$this->_recurse_and_parse( $tag, $between_text, '_parse_anchor_tag', $parse_tag )."[/$mytag]";
	}
	
	/*-------------------------------------------------------------------------*/
	// Recursively parse tags
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Recursively parse tags
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Callback Function
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _recurse_and_parse( $tag, $text, $function, $parse_tag='' )
	{
		//-----------------------------------------
		// INIT
		//-----------------------------------------
		
		$tag              = strtolower($tag);
		$open_tag         = "<".$tag;
		$open_tag_len     = strlen($open_tag);
		$close_tag        = "</".$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->non_delimiters ) ) 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<hr>( ".htmlspecialchars($open_tag)." ) NEST FOUND</hr>\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 "<hr><b>REPLACED {$function}($tag, ..., $tag_opts):</b><br />".htmlspecialchars(substr($text, $tag_begin_loc, $offset))."<br /><b>WITH:</b><br />".htmlspecialchars($final_text)."<hr>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: parse recursively: Quick method
	*
	* @param	string	Tag
	* @param	string	Text between opening and closing tag
	* @param	string	Callback Function
	* @param	string	Parse tag
	* @return	string	Converted text
	*/
	function _recurse_and_parse_quick( $tag, $text, $function, $parse_tag='' )
	{
		//-----------------------------------------
		// Set up
		//-----------------------------------------
		
		$tag       = strtolower( $tag );
		$open_tag  = "<".$tag;
		$close_tag = "</".$tag.">";
		$lowtext   = strtolower( $text );
		
		//-----------------------------------------
		// Return if no content or no opening tag
		//-----------------------------------------
		
		if ( ( ! $text ) OR ( ! strstr( $lowtext, $open_tag ) ) )
		{
			return $text;
		}
		
		//-----------------------------------------
		// Go loopy
		//-----------------------------------------
		
		while( strstr( $lowtext, $open_tag ) !== FALSE )
		{
			//-----------------------------------------
			// Quick check
			//-----------------------------------------
			
			if ( ! strstr( $lowtext, $open_tag ) )
			{
				break;
			}
			
			if ( ! strstr( $lowtext, $close_tag ) )
			{
				break;
			}
			
			//-----------------------------------------
			// Try and get a match. First tag to the
			// very last tag
			//-----------------------------------------
			
			preg_match( "#($open_tag(?:.+?)?".">)(.+?)$close_tag#is", $text, $match );
			
			if ( $this->debug == 2 )
			{
				print "<hr><pre>";
				print_r( array_map('htmlspecialchars',$match) );
				print "</pre></hr>";
			}
			
			$entire_match = $match[0];
			$opening_tag  = $match[1];
			$between_text = $match[2];
			
			if ( ! $entire_match )
			{
				break;
			}
			
			//-----------------------------------------
			// Look for nested tags
			//-----------------------------------------
			
			if ( strstr( strtolower( $between_text ), $open_tag ) !== FALSE )
			{
				if ( $this->debug == 2)
				{
					print "\n\n<hr>( ".htmlspecialchars($open_tag)." ) NEST FOUND</hr>\n\n";
				}
				
				//-----------------------------------------
				// How many opening tags?
				//-----------------------------------------
				
				$open_tag_count  = substr_count( $between_text, $open_tag );
				$close_tag_count = substr_count( $between_text, $close_tag );
				$chopped_text    = $entire_match;
				$tmp_text        = preg_replace( "#(^|.+?)".preg_quote( $entire_match, '#' )."#s", '', $text );
				
				//-----------------------------------------
				// Okay, pick through the text finding as
				// many closing tags as we have opening tags
				//-----------------------------------------
				
				while( ( $open_tag_count != $close_tag_count ) OR $tmp_text != '' )
				{
					preg_match( "#(.+?)$close_tag#is", $tmp_text, $match );
					
					if ( $this->debug == 2)
					{
						print "<hr>IN RECURSE TMP TEXT: ".htmlspecialchars($tmp_text)."<br>Open tags: {$open_tag_count}<br />Closed tags: {$close_tag_count}<hr>";
					}
			
					if ( $match[1] )
					{
						$open_tag_count  += intval( substr_count( $match[1], $open_tag ) );
						$close_tag_count++;
						
						$chopped_text .= $match[0];
						$tmp_text      = preg_replace( "#(^|.+?)".preg_quote( $match[0], '#' )."#s", '', $tmp_text );
					}
					else
					{
						// Cannot find, somethings wrong so break
						break;
					}
				}
				
				//-----------------------------------------
				// Piece back together the 'between_text'
				//-----------------------------------------
				
				preg_match( "#$open_tag(?:.+?)?".">(.*)$close_tag#is", $chopped_text, $newmatch );
				
				$between_text = $newmatch[1];
				$entire_match = $chopped_text;
			}
			
			//-----------------------------------------
			// Pass to handler
			//-----------------------------------------
			
			if ( $between_text )
			{
				$newtext = $this->$function( $tag, $between_text, $opening_tag, $parse_tag );
				//$text    = preg_replace( "#".preg_quote( $entire_match, '#' )."#", $newtext, $text );
				$text    = str_replace( $entire_match, $newtext, $text );
				
				if ( $this->debug == 2)
				{
					print "<hr><b>REPLACED:</b><br />".htmlspecialchars($entire_match)."<br /><b>WITH:</b><br />".htmlspecialchars($newtext)."<br /><b>TEXT IS NOW</b>:<br />".htmlspecialchars($text)."<hr>";
				}
			}
		}
		
		if ( $this->debug == 2)
		{
			print "<hr>NEW TEXT: ".htmlspecialchars($text)."\n\n<br /><span style='color:red'>ALL DONE - RETURNING \$text now....</span><hr>";
		}
			
		return $text;
	}
	
	/*-------------------------------------------------------------------------*/
	// Get value of option
	/*-------------------------------------------------------------------------*/
	
	/**
	* RTE: Extract option HTML
	*
	* @param	string	Option
	* @param	string	Text
	* @return	string	Converted text
	*/
	function _get_value_of_option( $option, $text )
	{
		preg_match( "#$option(\s+?)?\=(\s+?)?[\"']?(.+?)([\"']|(\s+?)|$|>)#is", $text, $matches );
		//preg_match( "#$option(\s+?)?\=(\s+?)?[\"']?(.+?)([\"']|$|>|\s)#is", $text, $matches );
		return trim( $matches[3] );
	}
	
}


?>