<?php

/**
 * <pre>
 * Invision Power Services
 * IP.Board v3.2.3
 * Editor Library: RTE (WYSIWYG) Class
 * Last Updated: $Date: 2011-10-11 07:49:07 -0400 (Tue, 11 Oct 2011) $
 * </pre>
 *
 * @author 		$Author: mmecham $
 * @copyright	(c) 2001 - 2009 Invision Power Services, Inc.
 * @license		http://www.invisionpower.com/community/board/license.html
 * @package		IP.Board
 * @link		http://www.invisionpower.com
 * @since		9th March 2005 11:03
 * @version		$Revision: 9593 $
 */

if ( ! defined( 'IN_IPB' ) )
{
	print "<h1>Incorrect access</h1>You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files.";
	exit();
}

/**
 * It's surprisingly difficult to keep posts consistent through various processes.
 * I'll keep some notes here for future me or future developers woring on this.
 * 
 * The text that is passed off in "process" must have HTML entities made safe, any
 * non char set characters converted into their numeric equivalent &#xxx; make
 * every effort to not auto-parse typed in entities. That is, if someone types in &#39; it
 * should NOT convert that to ' when they hit submit.
 * Test input: < > ' " & ¦ $ \ ! &amp; &#39; &quot; &lt; &gt; &para;
 * Should be processed as: &lt; &gt; &#39; &quot; &amp; ¦ $ &#92; ! &amp;amp; &amp;#39; &amp;quot; &amp;lt; &amp;gt; &amp;para;
 * This is *the* golden rule. Use this to test any fixes. It must work in the following modes:
 * RTE > STD Switch
 * STD > RTE Switch
 * RTE > Preview
 * STD > Preview
 * RTE > Edit
 * STD > Edit
 * RTE > Edit > More options
 * STD > Edit > More options
 * RTE > Quote from post
 * STD > Quote from post
 * 
 * Please test all these modes when you have finished modifying this class
 */
/**
 * 
 * IP.Board PHP work to display an editor
 * @author Matt
 *
 * Example:
 * $editor = new classes_editor_composite();
 * $editor->setAllowBbcode( true );
 * $editor->setAllowHtml( false );
 * $editor->setContent( '[b]Hello, I am some text from the database[/b]' );
 * $html = $editor->show('post_content');
 */
class classes_editor_composite
{
	/**
	 * Use P for line breaks (std CKEditor mode)
	 */
	const IPS_P_MODE = true;
	/**
	 * Parsing array
	 *
	 * @access	public
	 * @var		array
	 */
	public $delimiters			= array( "'", '"' );
	
	/**
	 * Parsing array
	 *
	 * @access	public
	 * @var		array
	 */
	public $non_delimiters		= array( "=", ' ' );
	
	/**
	 * Start tags
	 *
	 * @access	public
	 * @var		array
	 */
	public $start_tags			= array();
	
	/**
	 * End tags
	 *
	 * @access	public
	 * @var		array
	 */
	public $end_tags			= array();
	
	/**#@+
	* Internal setting objects
	*
	* @var		boolean
	*/	
	protected $allowHtml		= false;
	protected $allowBbcode		= true;
	protected $content			= null;
	protected $rteEnabled		= false;
	protected $allowSmilies		= true;
	protected $_edCount			= 1;
	/**#@+
	* Registry objects
	*
	* @var		object
	*/	
	protected $registry;
	protected $DB;
	protected $settings;
	protected $request;
	protected $lang;
	protected $member;
	protected $memberData;
	protected $cache;
	protected $caches;
	
	protected $_isSwitchingFrom = null;
	/**
	 * Main font sizes
	 *
	 * @access	protected
	 * @var		array
	 */
	protected $font_sizes     = array( 1 => 8,
									   2 => 10,
									   3 => 12,
									   4 => 14,
									   5 => 18,
									   6 => 24,
									   7 => 36,
									   8 => 48 );

	
	/**
	 * Constructor
	 *
	 * @return	@e void
	 */
	public function __construct()
	{
		/* Make object */
		$this->registry   =  ipsRegistry::instance();
		$this->DB	      =  $this->registry->DB();
		$this->settings   =& $this->registry->fetchSettings();
		$this->request    =& $this->registry->fetchRequest();
		$this->lang	      =  $this->registry->getClass('class_localization');
		$this->member     =  $this->registry->member();
		$this->memberData =& $this->registry->member()->fetchMemberData();
		$this->cache	  =  $this->registry->cache();
		$this->caches     =& $this->registry->cache()->fetchCaches();
		
		define( 'IPS_EDITOR_NO_SANITIZE', true );
		define( 'IPS_MAKE_AMP_SAFE'     , true );
		define( 'IPS_QUICK_EDIT_TO_FULL', ( $this->request['_from'] == 'quickedit' ) ? true : false );
		
		/* Auto set the RTE */
		$this->setRteEnabled( $this->_canWeRte() );
		
		/* Set up default options */
		$this->setAllowBbcode( true );
		$this->setAllowSmilies( true );
		$this->setAllowHtml( false );
		
		foreach( $this->font_sizes as $bbcode => $real )
		{
			$this->rev_font_sizes[ $real ] = $bbcode;
		}
	}	
	
	/**
	 * @return the $allowSmilies
	 */
	public function getAllowSmilies()
	{
		return $this->allowSmilies;
	}

	/**
	 * @return the $allowHtml
	 */
	public function getAllowHtml()
	{
		return $this->allowHtml;
	}

	/**
	 * @return the $allowBbcode
	 */
	public function getAllowBbcode()
	{
		return $this->allowBbcode;
	}

	/**
	 * @return the $content
	 */
	public function getContent()
	{
		return $this->content;
	}
	
	/**
	 * @return the $rteEnabled
	 */
	public function getRteEnabled()
	{
		return $this->rteEnabled;
	}
	
	/**
	 * @param boolean $allowHtml
	 */
	public function setAllowHtml( $allowHtml )
	{
		$this->allowHtml = $allowHtml ? true : false;
	}

	/**
	 * @param boolean $allowBbcode
	 */
	public function setAllowBbcode( $allowBbcode )
	{
		$this->allowBbcode = $allowBbcode ? true : false;
	}

	/**
	 * @param string $content
	 * @param string BBCode parsing section
	 */
	public function setContent( $content, $section='topics' )
	{
		$this->content = $this->convertContent( $content, 'auto', $section );
	}
	
	/**
	 * Takes raw content and makes it good for RTE or STD editor
	 * 
	 * @param string $content
	 * @param string auto = detect, rte = CKEditor, std = textarea
	 * @param string BBCode parsing section
	 */
	public function convertContent( $content, $rteMethod='auto', $section='topics' )
	{
		/* start */
		$isRte = true;
		
		/* Return if empty */
		if ( ! $content )
		{
			return $content;
		}
		
		/* Fix method */
		switch ( $rteMethod )
		{
			case 'auto':
				$isRte = ( ( $this->contentIsRte( $content ) || $this->getRteEnabled() ) && ! $this->_isInSourceMode() ) ? true : false;		
			break;
			case 'rte':
				$isRte = true;
			break;
			case 'std':
				$isRte = false;
			break;
		}
		
		/* RTE needs a code tidy whereas STD needs BBCode */
		if ( $isRte )
		{
			IPSText::getTextClass('bbcode')->parse_html = 0;
			
			if ( $this->getAllowHtml() )
			{
				if ( ! IPSText::getTextClass('bbcode')->parse_nl2br )
				{
					$content = str_replace( '<br />', "", $content );
					$content = str_replace( '<br>'	, "", $content );
				}
			}
			
			$content = $this->_rtePreShow( IPSText::getTextClass('bbcode')->convertForRTE( $content ), true );
		}
		else
		{
			IPSText::getTextClass('bbcode')->parse_html			= $this->getAllowHtml();
			IPSText::getTextClass('bbcode')->parse_nl2br		= 0;
			IPSText::getTextClass('bbcode')->parse_smilies		= $this->getAllowSmilies();
			IPSText::getTextClass('bbcode')->parse_bbcode		= $this->getAllowBbcode();
			IPSText::getTextClass('bbcode')->parsing_section	= $section;

			if ( $this->getAllowHtml() )
			{
				if ( ! IPSText::getTextClass('bbcode')->parse_nl2br )
				{
					$content = str_replace( '<br />', "", $content );
					$content = str_replace( '<br>'	, "", $content );
				}
			}
			
			if ( IPS_QUICK_EDIT_TO_FULL )
			{
				$content = str_replace( '&', '&amp;', $content );
			}

			$content = $this->_stdPreShow( IPSText::getTextClass('bbcode')->preEditParse( $content ) );
			
			/* This is designed only for new STD forms with content and NOT when switching modes */
			if ( ! IPS_IS_AJAX )
			{
				$content = str_replace( '&', '&amp;', $content );
			}
			else
			{
				$content = str_replace( '&quot;', '"', $content );
			}
		}
		
		$content = $this->_allowNonUtf8CharsWhenNotUsingUtf8Doc( $content );
		
		return $content;
	}

	/**
	 * @param boolean $rteEnabled
	 */
	public function setRteEnabled( $rteEnabled )
	{
		$this->rteEnabled = $rteEnabled ? true : false;
	}
	
	/**
	 * @param boolean $allowSmilies
	 */
	public function setAllowSmilies( $allowSmilies )
	{
		$this->allowSmilies = $allowSmilies ? true : false;
	}

	/**
	 * Determines whether or not we can use the RTE
	 * @return	boolean
	 */
	protected function _canWeRte()
	{
		$return = FALSE;

		if ( $this->memberData['userAgentKey'] == 'explorer' AND $this->memberData['userAgentVersion'] >= 7 )
		{
			$return = TRUE;
		}
		else if ( $this->memberData['userAgentKey'] == 'opera' AND $this->memberData['userAgentVersion'] >= 9.00 )
		{
			$return = TRUE;
		}
		else if ( $this->memberData['userAgentKey'] == 'firefox' AND $this->memberData['userAgentVersion'] >= 3 )
		{
			$return = TRUE;
		}
		else if ( $this->memberData['userAgentKey'] == 'safari' AND $this->memberData['userAgentVersion'] >= 4 )
		{
			$return = TRUE;
		}
		else if ( $this->memberData['userAgentKey'] == 'chrome' AND $this->memberData['userAgentVersion'] >= 2 )
		{
			$return = TRUE;
		}
		else if ( $this->memberData['userAgentKey'] == 'camino' AND $this->memberData['userAgentVersion'] >= 2 )
		{
			$return = TRUE;
		}
		else if ( $this->memberData['userAgentKey'] == 'mozilla' AND $this->memberData['userAgentVersion'] >= 4 )
		{
			$return = TRUE;
		}
		else if ( $this->memberData['userAgentKey'] == 'aol' AND $this->memberData['userAgentVersion'] >= 9 )
		{
			$return = TRUE;
		}
		
		/* No iDevice */
		if ( $this->registry->output->isLargeTouchDevice() || $this->registry->output->isSmallTouchDevice() )
		{
			$return = FALSE;
		}
		
		return $return;
	}
	
	/**
	 * Have we specifically set our CKEditor to saucy mode?
	 * @return boolean
	 */
	protected function _isInSourceMode()
	{
		/* Sent inline */
		if ( isset( $_REQUEST['isRte'] ) )
		{
			return ( $_REQUEST['isRte'] == 0 ) ? true : false;
		}
		
		/* Check the mode */
		if ( IPSCookie::get('rteStatus') == 'std' )
		{
			return true;
		}
		
		return false;
	}
	
	/**
	 * Shows the editor
	 * print $editor->show( 'message', 'reply-topic-1244' );
	 * @param	string	Field
	 * @param	array   Options: Auto save key, a unique key for the page. If supplied, editor will auto-save at regular intervals. Works for logged in members only
	 * @param	string	Optional content
	 */
	public function show( $fieldName, $options=array(), $content='' )
	{
		$_autoSaveKeyOrig		     = ( ! empty( $options['autoSaveKey'] ) ) ? $options['autoSaveKey'] : '';
		$options['editorName']       = ( ! empty( $options['editorName'] ) ) ? $options['editorName'] : $this->_fetchEditorName();
		$options['autoSaveKey']      = ( $_autoSaveKeyOrig && $this->memberData['member_id'] ) ? $this->_generateAutoSaveKey( $_autoSaveKeyOrig ) : '';
		$options['type']             = ( ! empty( $options['type'] ) && $options['type'] == 'mini' ) ? 'mini' : 'full';
		$options['minimize']	     = intval( $options['minimize'] );
		$options['height']	     	 = intval( $options['height'] );
		$options['isTypingCallBack'] = ( ! empty( $options['isTypingCallBack'] ) ) ? $options['isTypingCallBack'] : '';
		$options['noSmilies']		 = ( ! empty( $options['noSmilies'] ) ) ? true : false;
		$options['delayInit']		 = ( ! empty( $options['delayInit'] ) ) ? 1 : 0;
		$options['smilies']          = $this->fetchEmoticons( 20 );
		$options['bypassCKEditor']   = $this->getRteEnabled() ? 0 : 1;
		$options['isRte']			 = $this->_isInSourceMode() ? 0 : 1;
		$html         = '';

		/* inline content */
		if ( $content )
		{
			$this->setContent( str_replace( '\\\'', '\'', $content ) );
		}
		
		/* Store last editor ID in case calling scripts need it */
		$this->settings['_lastEditorId']	= $options['editorName'];

		if ( IN_ACP )
  		{
  			$html = $this->registry->getClass('output')->global_template->editor( $fieldName, $this->getContent(), $options, $this->getAutoSavedContent( $_autoSaveKeyOrig ) );
		}
		else
		{
			$html = $this->registry->getClass('output')->getTemplate('editors')->editor( $fieldName, $this->getContent(), $options, $this->getAutoSavedContent( $_autoSaveKeyOrig ) );
		}
		
		return $html;
	}
	
	/**
	 * Process contents of RTE into BBCode ready for storing
	 * @param  string	$content
	 * @return string	$content
	 */
	public function process( $content )
	{
		//header("Content-type: text/plain");
		return ( ( $this->contentIsRte( $content ) || $this->getRteEnabled() ) && ! $this->_isInSourceMode() ) ? $this->_rteProcess( $content ) : $this->_stdProcess( $content, false );
		//exit();
	}
	
	/**
	 * Fetches the saved content
	 * @param string $autoSaveKey
	 * @return array
	 */
	public function getAutoSavedContent( $autoSaveKey )
	{
		$autoSaveKey = $this->_generateAutoSaveKey( $autoSaveKey );
		$return      = array();
		/* fetch from the dee bee */
		$raw = $this->DB->buildAndFetch( array( 'select' => '*',
												'from'   => 'core_editor_autosave',
												'where'  => 'eas_key=\'' . $autoSaveKey . '\'' ) );
		
		/* Make sure no tomfoolery is occuring */
		if ( $raw['eas_key'] && ( $this->memberData['member_id'] == $raw['eas_member_id'] ) )
		{
			$return['key']         = $raw['eas_key'];
			$return['updated']     = $raw['eas_updated'];
			$return['raw']         = $raw['eas_content'];
			$return['restore_rte'] = $this->switchContent( $return['raw'], false );
			$return['restore_std'] = $this->switchContent( $return['raw'], true );
			$return['updatedDate'] = $this->registry->getClass('class_localization')->getDate( $return['updated'], 'LONG' );
			
			/* Now figure out previewable content */
			$return['parsed']  = IPSText::getTextClass('bbcode')->preDisplayParse( IPSText::getTextClass('bbcode')->preDbParse( $this->_rteProcess( $raw['eas_content'] ) ) );
		}
		
		return $return;
	}
	
	/**
	 * Remove auto saved content
	 * @param array $where options member_id = x , app = x, time = x 
	 */
	public function removeAutoSavedContent( $where=array() )
	{
		$_sql = array();
		
		if ( ! count( $where ) )
		{
			$_sql[] = 'eas_app=\'' . IPS_APP_COMPONENT . '\'';
		}
		
		if ( ! empty( $where['app'] ) )
		{
			$_sql[] = 'eas_app=\'' . $this->DB->addSlashes( $where['app'] ) . '\'';
		}
		
		if ( ! empty( $where['member_id'] ) )
		{
			$_sql[] = 'eas_member_id=' . intval( $where['member_id'] );
		}
		
		if ( ! empty( $where['autoSaveKey'] ) )
		{
			if ( strlen( $where['autoSaveKey'] ) != 32 )
			{
				$where['autoSaveKey'] = $this->_generateAutoSaveKey( $where['autoSaveKey'] );
			}
			
			$_sql[] = 'eas_key=\'' . trim( $where['autoSaveKey'] ) . '\'';
		}
		
		if ( ! empty( $where['time'] ) )
		{
			$_sql[] = 'eas_updated < ' . intval( $where['time'] );
		}
		
		$this->DB->delete( 'core_editor_autosave', implode( ' AND ', $_sql ) );
	}
	
	/**
	 * Sniff out RTE content
	 * @param string $content
	 * @return boolean
	 */
	public function contentIsRte( $content )
	{
		$content = trim( $content );
		
		if ( substr( $content, 0, 3 ) == '<p>' && substr( $content, -4 ) == '</p>' )
		{
			return true;
		}
		
		return false;
	}
	
	/**
	 * Auto save via ajax
	 * @param string $content
	 * @param string $autoSaveKey
	 */
	public function autoSave( $content, $autoSaveKey )
	{
		/* Convert the data so it is safe to store and preview */
		$content = IPSText::getTextClass('bbcode')->preEditParse( $this->_rteProcess( $content, IPS_EDITOR_NO_SANITIZE ) );
		
		/* Pretty much just dump it in the DB */
		$this->DB->replace( 'core_editor_autosave', array( 'eas_key'       => $autoSaveKey,
														   'eas_member_id' => $this->memberData['member_id'],
														   'eas_app'	   => IPS_APP_COMPONENT,
														   'eas_section'   => $this->request['module'] . '.' . $this->request['section'],
														   'eas_updated'   => time(),
														   'eas_content'   => $content ), array( 'eas_key' ) );
		
		return true;
	}
	
	/**
	 * Switch content from one mode t'other
	 * 
	 * @param	string	Content
	 * @param	int		Is RTE?
	 */
	public function switchContent( $content, $isRte )
	{
		IPSText::getTextClass('bbcode')->parse_html			= false;
		IPSText::getTextClass('bbcode')->parse_nl2br		= 0;
		IPSText::getTextClass('bbcode')->parse_smilies		= true;
		IPSText::getTextClass('bbcode')->parse_bbcode		= true;
		IPSText::getTextClass('bbcode')->parsing_section	= 'topics';
		
		/* Default state for ACP */
		if ( IN_ACP )
		{
			IPSText::getTextClass('bbcode')->parse_html = true;
		}
		
		if ( $isRte )
		{
			$this->_isSwitchingFrom = 'std';
			/* Assume RTE text has been sent in and we want BBCode */
			$content = str_replace( '\\', '&#92;', $content ); # Makes manually entered < safe
			$content = $this->_rteProcess( $content, IPS_EDITOR_NO_SANITIZE );
			$content = IPSText::getTextClass('bbcode')->preEditParse( $content );
			$content = $this->_stdPreShow( $content );
			$content = str_replace( '&#92;', '\\', $content );
		}
		else
		{
			$this->_isSwitchingFrom = 'rte';
			/* Assume BBCode has been sent in and we want RTE */
			//$content = str_replace( '<', '&lt;', $content ); # Makes manually entered < safe
			//$content = str_replace( '>', '&gt;', $content ); # Makes manually entered > safe
			$content = $this->_rtePreShow( IPSText::getTextClass('bbcode')->convertForRTE( $this->_stdProcess( $content, IPS_EDITOR_NO_SANITIZE ) ), false, true );
		}
		
		$this->_isSwitchingFrom = null;
		
		return $content;
	}
	
	/**
	 * Processes text ready for non RTE
	 *
	 * @access	public
	 * @param	string		Raw text
	 * @return	string		Text ready for editor
	 */
	public function _stdPreShow( $t )
	{
		$t = str_replace( array( "\r\n", "\r" ), "\n", $t );
		
		IPSDebug::fireBug( 'info', array( 'Start (std): ' . nl2br($t) ) );

		$t = IPSText::decodeNamedHtmlEntities( $t );
		
		$t = str_replace( '&#33;', '!', $t );
		$t = str_replace( '&#34;', '"', $t );
		$t = str_replace( '&quot;', '"', $t );
		$t = str_replace( '&#60;', '<', $t );
		$t = str_replace( '&#62;', '>', $t );
		$t = str_replace( '&lt;', '<', $t );
		$t = str_replace( '&gt;', '>', $t );
		$t = str_replace( '&#39;', "'", $t );
		$t = str_replace( '&#036;', "$", $t );
		$t = str_replace( '&#36;', "$", $t );
		$t = str_replace( '&#92;', '\\', $t );
		$t = str_replace( '&#092;', '\\', $t );
		$t = str_replace( '&#160;', ' ', $t );
		
		/* We replace \ to 092 so convert it back */
		$t = str_replace( '&amp;#092;', '\\', $t );
		
		if ( ! IPS_IS_AJAX )
		{
			$t = str_replace( '&amp;', '&', $t );
		}
		
		$t = str_replace( '</textarea', '&lt;textarea', $t );	
		
		$t = $this->_allowNonUtf8CharsWhenNotUsingUtf8Doc( $t );
		
		IPSDebug::fireBug( 'info', array( 'End (std): ' . nl2br($t) ) );

		return $t;
	}
	
	/**
	 * Processes text for RTE
	 *
	 * @access	public
	 * @param	string		Raw text
	 * @param	bool		Double encode entities (not needed for switching, only for full editor)
	 * @param	bool		Ajax request
	 * @return	string		Text ready for editor
	 */
	public function _rtePreShow( $t, $encodeEntities=false )
	{ 
		IPSDebug::fireBug( 'info', array( 'Start (rte): ' . nl2br($t) ) );
		
		/* Trim and remove comments */
		$t = preg_replace( '#\<\!\-\-(.+?)\-\-\>#is', "", $t );
		$t = rtrim($t);
		
		/* Convert single/double quotes */
		if ( strtolower(IPS_DOC_CHAR_SET) != 'utf-8' )
		{
			$t = str_replace(chr(145), chr(39), $t);
			$t = str_replace(chr(146), chr(39), $t);
			$t = str_replace(chr(147), chr(34), $t);
			$t = str_replace(chr(148), chr(34), $t);
		}

		$t = str_replace( "\t", "{'tab'}", $t );

		/* Remove CR/LF */
		$t = str_replace(chr(10), "", $t);
		$t = str_replace(chr(13), "", $t);

		/* Fix some entities to display correctly */
		$t	= str_replace( array( '<br />', '<br>' ), '__~~--__', $t );
		
		if ( $encodeEntities )
		{
			$t	= str_replace( '&#60;', '-<-', $t );
			$t	= str_replace( '&#62;', '->-', $t );
		}
		else
		{
			$t	= str_replace( array( '&#60;', '&lt;' ), '-<-', $t );
			$t	= str_replace( array( '&#62;', '&gt;' ), '->-', $t );
		}

		$t	= str_replace( '&#10;', '<br />', $t );
		
		/* AJAX content is inserted into editor via insertText which does not parse entities as they would normally display */
		if( ! IPS_IS_AJAX )
		{
			$t	= str_replace( '&', '&amp;', $t );
		}
			
		$t	= str_replace( '-<-', '&lt;', $t );
		$t	= str_replace( '->-', '&gt;', $t );
		$t	= str_replace( '__~~--__', '<br />', $t );

		/* Clean up quote tags (remove many <br />s */
		$t = preg_replace( '#(\[quote([^\]]+?)\])(<br />){1,}#is', "\\1<br />", $t );
		
		/* Replace tabs */	
		$t = str_replace( "{'tab'}", "&nbsp;&nbsp;&nbsp;&nbsp;", $t );
		
		/* Clean up the rest of the tags */
		//$t = str_replace( array( '&lt;br&gt;', '&lt;br /&gt;' ), '<br />', IPSText::htmlspecialchars( $t ) );
		//$t = str_replace( array( '&lt;br&gt;', '&lt;br /&gt;' ), '<br />', $t );
		$t = str_replace( "&lt;#IMG_DIR#&gt;", "<#IMG_DIR#>", $t );
		$t = str_replace( "&lt;#EMO_DIR#&gt;", "<#EMO_DIR#>", $t );
		
		/* Convert multiple spaces */
		$t = str_replace( "  ", "&nbsp;&nbsp;", $t );
		
		/* Fix up lists */
		$t = str_replace( '<br /></li>', '</li>', $t );
		
		/* make it look a bit pretty */
		$t = str_replace( '<br />', "<br />\n", $t );
		
		/* CK needs this - legacy data only, new stuff uses proper tags */
		if ( stristr( $t, '<b>' ) )
		{
			$t = str_replace( '<b>' , '<strong>', $t );
			$t = str_replace( '</b>', '</strong>', $t );
		}
		
		if ( stristr( $t, '<i>' ) )
		{
			$t = str_replace( '<i>' , '<em>', $t );
			$t = str_replace( '</i>', '</em>', $t );
		}
		
		/* Wrap in P tags */
		if ( self::IPS_P_MODE )
		{
			if ( $t AND ( ! preg_match( '#^<(p)#', $t ) ) )
			{
				$t = $this->_convertBrToWrappedP( $t );
				
				$t = preg_replace( '#<p>(\s+?)?<ul#is', '<ul', $t );
				$t = preg_replace( '#</ul>(\s+?)?</p>#is', '</ul>', $t );
			}
		}
		
		/* If the last item is an image, this causes issues in IE8 */
		if ( $this->memberData['userAgentKey'] == 'explorer' )
		{
			$test = str_replace( "<#EMO_DIR#>", '', $t );
			
			if ( preg_match( '#(<img(?:[^>]+?)/>)</p>$#', $test ) )
			{
				$t = preg_replace( '#</p>$#', '<span>&nbsp;</span></p>', $t );
			}
		}
		
		IPSDebug::fireBug( 'info', array( 'End (rte): ' . $t ) );

		return $t;
	}
	
	/**
	 * Convert <br /> tagged data to <p>
	 * @link http://www.php.net/manual/en/function.nl2br.php#97643
	 * @author James bandit.co dot nz
	 * @param string $string
	 */
	protected function _convertBrToWrappedP( $string )
	{
		return '<p>' . $string . '</p>';

		/*return  '<p>' 
            	. preg_replace('#(<br\s*?/?>\s*?){2,}#', '</p>'."\n".'<p>', $string ) 
            	. '</p>'; */
    } 
	
	/**
	 * Take content from STD and make safe
	 * @param string $content
	 * @return string
	 */
	protected function _stdProcess( $content, $NO_SANITIZE=false )
	{
		$content = str_replace( array( "\r\n", "\r" ), "\n", $content );
		
		$content = preg_replace( '#^([ ]+?)\n#m', "\n", $content );
		
		return trim( $this->_clean( $content, $NO_SANITIZE, false ) );
	}
	
	/**
	 * Process the content before passing off to the bbcode library
	 *
	 * @access	public
	 * @param	string		Form field name OR Raw text
	 * @param	boolean		Does not run _clean() to santize HTML. Used when 'switching' editors via ajax
	 * @return	string		Text ready for editor
	 */
	protected function _rteProcess( $content, $NO_SANITIZE=false )
	{
		//-----------------------------------------
		// Save some processing if no content
		//-----------------------------------------
		
		if ( ! $content )
		{
			return $content;
		}
		
		$ot	= $content;
		
		$content = str_replace( array( "\r\n", "\n" ), "\n", $content );
		
		/* Using P mode? CKEditor will send data like so
		 * <p>test</p>
		 * <p>More data</p>
		 * <p><br /></p>
		 */
		if ( self::IPS_P_MODE )
		{
			/* Tidy up first */
			$content = str_replace( '<div' , '<p' , $content );
			$content = str_replace( '</div>', '</p>', $content );
			$content = preg_replace( '#<p>(\s+?)?<ul#is', '<ul', $content );
			$content = preg_replace( '#(\r\n|\r|\n)<p#is', '<p', $content );
			//$content = preg_replace( '#(\r\n|\r|\n)<p([^>]+?)?'.'>&nbsp;</p>#is', "<br />", $content );
			$content = preg_replace( '#</ul>(\s+?)?(<br([^>]+?)?>|</p>)#is', '</ul>', $content );
		}
		
		/* Before we content/strip newlines, lets make code safe */
		$content = $this->_recurseAndParse( 'pre', $content, "_parsePreTag" );
	
		//-----------------------------------------
		// Fix up tabs/spaces
		//-----------------------------------------

		$content = str_replace( '&nbsp;&nbsp;&nbsp;&nbsp;', "{'tab'}", $content );
		$content = str_replace( '&nbsp;', ' ', $content );
		
		/* looks to be non RTE content that has ended up there */
		if ( ! strstr( $content, '<br' ) && ! strstr( $content, '<p' ) && strstr( $content, "\n" ) )
		{
			$content = str_replace( "\n", "<br />", $content );
		}
		else
		{
			$content = str_replace( "\n", "", $content );
		}
		
		//-----------------------------------------
		// Clean up already encoded HTML
		//-----------------------------------------
		
		$content = str_replace( '&quot;', '"', $content );
		$content = str_replace( '&apos;', "'", $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( '#<a\s+?href=[\'"]([^>]+?)\[(.+?)[\'"](.+?)'.'>(.+?)\[\\2</a>#is', '<a href="\\1"\\3>\\4</a>[\\2', $content );
		//$content = preg_replace( '#<a\s+?href=[\'"]([^>\'"]+?)[\'"](.*?)>(.+?)\[([^<]+?)</a>#is', '<a href="\\1">\\3</a>[\\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( '#<br([^>]+?)>#', "<br />", $content );
		$content = str_ireplace( array( "<br>", "<br />" ), "\n", $content );
		
		$content = trim( $content );
		
		//-----------------------------------------
		// Before we can use strip_tags, we should
		// clean out any javascript and CSS
		//-----------------------------------------
		
		$content	= preg_replace( '/\<script(.*?)\>(.*?)\<\/script\>/', '', $content );
		$content	= preg_replace( '/\<style(.*?)\>(.*?)\<\/style\>/', '', $content );
		
		//-----------------------------------------
		// Remove tags we're not bothering with
		// with PHPs wonderful strip tags func
		//-----------------------------------------
		
		if ( ! $this->getAllowHtml() )
		{
			$content = strip_tags( $content, '<h1><h2><h3><h4><h5><h6><font><span><div><br><p><img><a><li><ol><ul><b><strong><em><i><u><s><strike><del><blockquote><sub><sup><pre>' );
		}

		//-----------------------------------------
		// WYSI-Weirdness #2: named anchors
		//-----------------------------------------
		
		$content = preg_replace( '#<a\s+?name=.+?'.'>(.+?)</a>#is', "\\1", $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #2.1: Empty a hrefs
		//-----------------------------------------
		
		$content = preg_replace( '#<a\s+?href([^>]+)></a>#is'         , ""   , $content );
		$content = preg_replace( '#<a\s+?href=([\'\"])>\\1(.+?)</a>#is', "\\1", $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #2.2: Double linked links
		//-----------------------------------------
		
		$content = preg_replace( '#href=[\"\']\w+://(%27|\'|\"|&quot;)(.+?)\\1[\"\']#is', "href=\"\\2\"", $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #3: Headline tags
		//-----------------------------------------
		
		$content = preg_replace( "#<(h[0-9])(?:[^>]+?)?>(.+?)</\\1>#is", "\n[b]\\2[/b]\n", $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #4: Font tags
		//-----------------------------------------
		
		$content = preg_replace( '#<font (color|size|face)=\"([a-zA-Z0-9\s\#\-]*?)\">(\s*)</font>#is', " ", $content );

		//-----------------------------------------
		// WYSI-Weirdness #5a: Fix up smilies: IE RTE
		// @see Ticket 623146
		//-----------------------------------------
		
		$content = preg_replace( '#<img class=(\S+?) alt=(\S+?) src=[\"\'](.+?)[\"\']>#i', "<img src='\\3' class='\\1' alt='\\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])<img src=[\"\'](.+?)[\"\'] class=[\"\'](.+?)[\"\'] alt=[\"\'](.+?)[\"\'] />#i', "\\1 <img src='\\2' class='\\3' alt='\\4' />", $content );
		
		/* Remove <img src="data:"> */
		$content = preg_replace( '#<img\s+?(alt=""\s+?)?src="data:([^"]+?)"\s+?/>#', '', $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #6: Image tags
		//-----------------------------------------
		
		$content = preg_replace( '#<img alt=[\"\'][\"\'] height=[\"\']\d+?[\"\'] width=[\"\']\d+?[\"\']\s+?/>#', "", $content );
		$content = preg_replace( '#<img.+?src=[\"\'](.+?)[\"\']([^>]+?)?'.'>#is', "[img]\\1[/img]", $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #7: Linked URL tags
		//-----------------------------------------
		
		$content = preg_replace( '#\[url=(\"|\'|&quot;)<a\s+?href=[\"\'](.*)/??[\'\"]\\2/??</a>#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 lis
		//-----------------------------------------
		
		$content = preg_replace( '#<li>\s+?(\S)#', '<li>\\1', $content );
		$content = preg_replace( '#</li>\s+?(\S)#', '</li>\\1', $content );
		$content = preg_replace( '#<br />(\s+?)?</li>#si', '</li>', $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( 'blockquote', $content, "_parseSimpleTag", 'indent' );
		$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" );
	
		/* 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 #9: Fix up para tags
		//-----------------------------------------
		
		//$content = str_ireplace( array( "<p>", "<p />" ), "\n\n", $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #10: Random junk
		//-----------------------------------------
		
		$content = str_ireplace( array( "<a>", "</a>", "</li>" ), "", $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #11: Fix up list stuff
		//-----------------------------------------
		
		$content = preg_replace( '#<li>(.*)((?=<li>)|</li>)#is', '\\1', $content );
		
		//-----------------------------------------
		// WYSI-Weirdness #11.1: Safari badness
		//-----------------------------------------
		
		$content	= str_replace( "</div>", "", $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(  '&lt;' , '<', $content );
		$content = str_replace(  '&gt;' , '>', $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( '#\[(url|img|b|u|i|s|email|list|indent|right|left|center)\](\s+?)?\[/\1\]#is', $content ) )
		{
			$content = preg_replace( '#\[(url|img|b|u|i|s|email|list|indent|right|left|center)\](\s+?)?\[/\1\]#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)=&quot;([^\"\']+?)&quot;\]\[/\\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 );	
		
		//-----------------------------------------
		// Replace tabs
		//-----------------------------------------
		
		$content = str_replace( "{'tab'}", "\t", $content );
	
		//-----------------------------------------
		// Now call the santize routine to make
		// html and nasties safe. VITAL!!
		//-----------------------------------------
		
		$content = $this->_clean( trim( $content ), $NO_SANITIZE, true );

		/* Ensure [xxx=&quot; is fixed */
		$content = preg_replace( '#\[(\w+?)=&quot;(.+?)&quot;\]#', "[\\1=\"\\2\"]", $content );
		
		/* Relative paths */
		$content = preg_replace( '#\[img\](../../|&\#46;&\#46;/&\#46;&\#46;/)public/#', '[img]' . $this->settings['board_url'] . '/public/', $content );
				
		/* Finally ensure emoticons are converted to normal tags */
		if ( count( $this->cache->getCache('emoticons') ) > 0 )
		{
			$emoDir = $this->_fetchSmilieDir();
			
			foreach( $this->cache->getCache('emoticons') as $row )
			{
				if ( $row['emo_set'] != $emoDir )
				{
					continue;
				}
				
				$content = 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'], $content ); 
				
			}
		}
		
		//-----------------------------------------
		// Debug?
		//-----------------------------------------
		
		if ( $this->debug )
		{
			print "<pre><hr>";
			print nl2br(htmlspecialchars($ot));
			print "<hr>";
			print nl2br($content);
			print "<hr>";
			exit();
		}
		
		//-----------------------------------------
		// Done
		//-----------------------------------------
		
		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	Converted 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 ) ) );
		
		//-----------------------------------------
		// 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([^\>]+?)\>#is', '<li>', $between_text );
		$between_text = preg_replace( '#<li>\s+?</li>#is', '', $between_text );
		$between_text = preg_replace('#<li>((.(?!</li))*)(?=</?ul|</?ol|\[list|<li|\[/list)#siU', '<li>\\1</li>', $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	Converted 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( '&#91;quote', '&#91;/quote&#93;' ), $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	Converted 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   = "";
		//print $opening_tag . "\n" . $tag . "\n'" . $between_text. "'\n----\n";
		
		//-----------------------------------------
		// 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 );
		$textAlign  = $this->_extractCssValue( $style, 'text-align' );
		$marginLeft = intval( $this->_extractCssValue( $style, 'margin-left' ) );
		
		if ( $align == 'center' OR $textAlign == '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	Converted 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;
		}
		
		if ( ! stristr( $between_text, '<br' ) && stristr( $between_text, "\n" ) )
		{
			$between_text = str_replace( "\n", "<br />", $between_text );
		}
		
		return $this->_recurseAndParse( 'pre', $between_text, '_parsePreTag' );
	}	
	
	/**
	 * 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	Converted 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	Converted text
	 */
	protected function _parseFieldsetTag( $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->_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' );
		
		//-----------------------------------------
		// #DEBUG
		//-----------------------------------------
		
		if ( $this->debug == 2 )
		{
			print "\n<hr><b style='color:green'>FINISHED</b><br/ >".$start_tags . trim( $final ) . $end_tags."<hr>";
		}
		
		//-----------------------------------------
		// Now return
		//-----------------------------------------
		
		return $start_tags . $final . $end_tags;
	}
	
	/**
	 * 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	Converted text
	 */
	protected function _parseDivTag( $tag, $between_text, $opening_tag, $parse_tag )
	{
		//-----------------------------------------
		// Reset local start tags
		//-----------------------------------------
		
		$start_tags = "";
		$end_tags   = "";
		$allowEndNl = true;
		
		//-----------------------------------------
		// #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->_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' );
		
		//-----------------------------------------
		// #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;
	}
	
	/**
	 * 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	Converted text
	 */
	protected function _parseStyles( $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*(.+);?'			, '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->_getValueOfOption( 'style', $opening_tag );
		$class = $this->_getValueOfOption( 'class', $opening_tag );

		//-----------------------------------------
		// Convert RGB to hex
		//-----------------------------------------

		$style = preg_replace_callback( '#(?<![\w\-])color:\s+?rgb\((\d+,\s+?\d+,\s+?\d+)\)(;?)#i', array( &$this, '_rgbToHex' ), $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' )
					{
						if( $data['tag'] != 'font' OR $match[ $data['match'] ] != 'Verdana, arial, sans-serif' )
						{
							$start_tags .= "[{$data['tag']}={$match[$data['match']]}]";
						}
					}
					else
					{
						$start_tags .= "[{$data['tag']}=" . $this->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	Converted 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 "<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->_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	Converted 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	Converted 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( '<', '&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->_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	Converted text
	 */
	protected function _recurseAndParse( $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: Extract option HTML
	 *
	 * @access	protected
	 * @param	string	Option
	 * @param	string	Text
	 * @return	string	Converted 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' ) )
			//{
				preg_match( "#{$option}(\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		Converted text
	 */
	protected function _unhtmlUrl( $matches=array() )
	{
		$url  = stripslashes( $matches[3] );
		$type = stripslashes( $matches[1] ? $matches[1] : 'a href' );
		
		$url  = str_replace( '<', '&lt;', $url );
		$url  = str_replace( '>', '&gt;', $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	Converted 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]) );
	}
	
	/**
	 * Generates unique editor ID
	 */
	protected function _fetchEditorName()
	{
		return 'editor_' . uniqid();
	}
	
	/**
	 * Returns current emoticon directory...
	 * @return string
	 */
	protected function _fetchSmilieDir()
	{
  		if ( IN_ACP )
  		{
			$image_set = $this->DB->buildAndFetch( array( 'select' => 'set_image_dir, set_emo_dir', 'from' => 'skin_collections', 'where' => 'set_is_default=1' ) );

			return $image_set['set_image_dir'];
  		}
  		else
  		{
			return ipsRegistry::getClass('output')->skin['set_emo_dir'];
  		}
	}
	
	/**
	 * Generates a full autosave key
	 * @param string $autoSaveKey
	 * @return string
	 */
	protected function _generateAutoSaveKey( $autoSaveKey )
	{
		return md5( IPS_APP_COMPONENT . '-' . trim( $autoSaveKey ) . '-' . intval( $this->memberData['member_id'] ) );
	}
	
	/**
	 * Clean up and make the text for the DB
	 *
	 * @access	protected
	 * @param	string		Raw text
	 * @return	string		Converted text
	 */
	protected function _clean( $t, $NO_SANITIZE=false, $isFromRte=false )
    {
    	if ( $t == "" )
    	{
    		return "";
    	}
		
		if ( IPS_DOC_CHAR_SET == 'UTF-8' || $this->_isSwitchingFrom == 'std' )
		{
    		$t = IPSText::decodeNamedHtmlEntities( $t );
		}
		
    	if ( $isFromRte === false )
    	{
	    	$t = preg_replace( '#&(?!\#[0-9]+;)#', '&amp;', $t );
	    	
	    	$t = preg_replace("/&#([0-9]+);/s", "&amp;#\\1;", $t );
    	}
    	
    	$t = str_replace( ">", "&gt;", $t );
	    $t = str_replace( "<", "&lt;", $t );
	    	
    	/* We replace \ to 092 so convert it back */
		$t = str_replace( '&amp;#092;', '\\', $t );
		
    	/* Make it safe */
    	if ( $NO_SANITIZE !== true )
    	{   			
	    	$t = str_replace( "<!--"		, "&#60;&#33;--"  , $t );
	    	$t = str_replace( "-->"			, "--&#62;"       , $t );
	    	$t = str_ireplace( "<script"	, "&#60;script"   , $t );
	    	$t = str_replace( '"'			, "&quot;"        , $t );
	    	$t = str_replace( '$'			, "&#036;"        , $t );
	    	$t = str_replace( "\r"			, ""              , $t );
	    	$t = str_replace( "!"			, "&#33;"         , $t );
	    	$t = str_replace( "'"			, "&#39;"         , $t );
    	}    	
    		
    	$t = str_replace( "&#34;", '&quot;', $t );
    	$t = str_replace( "&#38;", '&amp;', $t );
    	$t = str_replace( "&#160;", ' ', $t );

    	$t = str_replace( "\n", "<br />", $t );
    	
    	$t = $this->_allowNonUtf8CharsWhenNotUsingUtf8Doc( $t );
    	
    	return $t;
    }
    
	/**
	 * Get BBCode font size from real PX size
	 *
	 * @access	public
	 * @param	integer		PX Size
	 * @return	integer		BBCode size
	 */
	public function convertRealsizeToBbsize( $real )
	{
		$real = intval( $real );
		
		//-----------------------------------------
		// If we have a true mapping, use it
		//-----------------------------------------
		
		if ( $this->rev_font_sizes[ $real ] )
		{
			return $this->rev_font_sizes[ $real ];
		}
		else
		{
			//-----------------------------------------
			// Otherwise find the next closest size down
			//-----------------------------------------
			
			foreach( $this->rev_font_sizes as $font => $bbcode )
			{
				if( $real < $font )
				{
					return ( ( $bbcode - 1 ) > 1 ) ? ( $bbcode - 1 ) : 1;
				}
			}
			
			return 2;
		}
	}
	
	/**
	 * Fetch emoticons as JSON for editors, etc
	 *
	 * @param	mixed		Number of emoticons to fetch (false to fetch all, or an int limit)
	 * @return	string		JSON
	 */
	public function fetchEmoticons( $fetchFirstX=false )
	{
		$emoDir    = $this->_fetchSmilieDir();
		$emoString = '';
		$smilie_id = 0;
		$total     = 0;
		
		foreach( ipsRegistry::cache()->getCache('emoticons') as $elmo )
		{
			if ( $elmo['emo_set'] != $emoDir )
			{
				continue;
			}
			
			$total++;
			
			if ( $fetchFirstX !== false && ( $smilie_id + 1 > $fetchFirstX ) )
			{
				continue;
			}

			//-----------------------------------------
			// Make single quotes as URL's with html entites in them
			// are parsed by the browser, so ' causes JS error :o
			//-----------------------------------------
			
			if ( strstr( $elmo['typed'], "&#39;" ) )
			{
				$in_delim  = '"';
			}
			else
			{
				$in_delim  = "'";
			}
			
			$emoArray[ $smilie_id ] = array( 'src'  => $elmo['image'],
											 'text' => addslashes($elmo['typed']) );
			
			
			$smilie_id++;
		}
		
		return array( 'total' => $total, 'count' => $smilie_id, 'emoticons' => $emoArray );
	}

	/**
	 * Makes code stuff safe
	 * @param array $matches
	 * @return string
	 */
	protected function _rteMakeCodeSafeWhileParsing( $matches )
	{
		/* Code can come in PRE tags so there will be \n but no <br> */
		if ( ! stristr( $matches[0], '<br' ) && stristr( $matches[0], "\n" ) )
		{
			return str_replace( "\n", "<code-new-line>", str_replace( array( '<br>', '<br />' ), '', $matches[0] ) );
		}
		
		return $matches[0];
	}
	
	/**
	 * The crazy crap we have to deal with. User enters squiggly char, it gets turned into &#128; or &Aacute; but there is
	 * no matching character in the doc set so we have to try and decode manually

	 * @param string Raw string
	 * @return string	Converted string
	 */
	protected function _allowNonUtf8CharsWhenNotUsingUtf8Doc( $string )
	{
		/* If we're not using UTF-8, lets try and handle encoded data. */
		if ( IPS_DOC_CHAR_SET != 'UTF-8' )
		{
			/* First, convert any left over &acute; style chars */
			preg_match_all( '/&amp;([a-zA-Z]{5,8});/', $string, $matches );
			
			foreach( $matches[1] as $word )
			{
				$converted = str_replace( '&', '&amp;', $this->_convertEntity( array( 1 => $word ) ) );
				
				if ( $converted  )
				{
					$string = str_replace( '&amp;' . $word . ';', $converted, $string );
				}
			}
			
			preg_match_all( '/&amp;#([0-9]{3,6});/', $string, $matches );
			
			foreach( $matches[1] as $word )
			{
				if ( $word > 127 )
				{
					$string = str_replace( '&amp;#' . $word . ';', '&#' . $word . ';', $string );
				}
			}
		}
		
		return $string;
	}
	
	/**
	 * Function to convert named entities to numeric entities
	 *
	 * @param	array 	$matches	Results from preg_match call
	 * @return	string
	 * @link	http://www.lazycat.org/php-convert-entities.php
	 */
	protected function _convertEntity( $matches )
	{
	  static $table = array(/*'quot' => '&#34;','amp' => '&#38;','lt' => '&#60;','gt' => '&#62;',*/'OElig' => '&#338;','oelig' => '&#339;','Scaron' => '&#352;','scaron' => '&#353;','Yuml' => '&#376;',
							'circ' => '&#710;','tilde' => '&#732;','ensp' => '&#8194;','emsp' => '&#8195;','thinsp' => '&#8201;','zwnj' => '&#8204;','zwj' => '&#8205;','lrm' => '&#8206;','rlm' => '&#8207;',
							'ndash' => '&#8211;','mdash' => '&#8212;','lsquo' => '&#8216;','rsquo' => '&#8217;','sbquo' => '&#8218;','ldquo' => '&#8220;','rdquo' => '&#8221;','bdquo' => '&#8222;','dagger' => '&#8224;',
							'Dagger' => '&#8225;','permil' => '&#8240;','lsaquo' => '&#8249;','rsaquo' => '&#8250;','euro' => '&#8364;','fnof' => '&#402;','Alpha' => '&#913;','Beta' => '&#914;','Gamma' => '&#915;',
							'Delta' => '&#916;','Epsilon' => '&#917;','Zeta' => '&#918;','Eta' => '&#919;','Theta' => '&#920;','Iota' => '&#921;','Kappa' => '&#922;','Lambda' => '&#923;','Mu' => '&#924;','Nu' => '&#925;',
							'Xi' => '&#926;','Omicron' => '&#927;','Pi' => '&#928;','Rho' => '&#929;','Sigma' => '&#931;','Tau' => '&#932;','Upsilon' => '&#933;','Phi' => '&#934;','Chi' => '&#935;','Psi' => '&#936;',
							'Omega' => '&#937;','alpha' => '&#945;','beta' => '&#946;','gamma' => '&#947;','delta' => '&#948;','epsilon' => '&#949;','zeta' => '&#950;','eta' => '&#951;','theta' => '&#952;','iota' => '&#953;',
							'kappa' => '&#954;','lambda' => '&#955;','mu' => '&#956;','nu' => '&#957;','xi' => '&#958;','omicron' => '&#959;','pi' => '&#960;','rho' => '&#961;','sigmaf' => '&#962;','sigma' => '&#963;',
							'tau' => '&#964;','upsilon' => '&#965;','phi' => '&#966;','chi' => '&#967;','psi' => '&#968;','omega' => '&#969;','thetasym' => '&#977;','upsih' => '&#978;','piv' => '&#982;','bull' => '&#8226;',
							'hellip' => '&#8230;','prime' => '&#8242;','Prime' => '&#8243;','oline' => '&#8254;','frasl' => '&#8260;','weierp' => '&#8472;','image' => '&#8465;','real' => '&#8476;','trade' => '&#8482;',
							'alefsym' => '&#8501;','larr' => '&#8592;','uarr' => '&#8593;','rarr' => '&#8594;','darr' => '&#8595;','harr' => '&#8596;','crarr' => '&#8629;','lArr' => '&#8656;','uArr' => '&#8657;',
							'rArr' => '&#8658;','dArr' => '&#8659;','hArr' => '&#8660;','forall' => '&#8704;','part' => '&#8706;','exist' => '&#8707;','empty' => '&#8709;','nabla' => '&#8711;','isin' => '&#8712;',
							'notin' => '&#8713;','ni' => '&#8715;','prod' => '&#8719;','sum' => '&#8721;','minus' => '&#8722;','lowast' => '&#8727;','radic' => '&#8730;','prop' => '&#8733;','infin' => '&#8734;',
							'ang' => '&#8736;','and' => '&#8743;','or' => '&#8744;','cap' => '&#8745;','cup' => '&#8746;','int' => '&#8747;','there4' => '&#8756;','sim' => '&#8764;','cong' => '&#8773;','asymp' => '&#8776;',
							'ne' => '&#8800;','equiv' => '&#8801;','le' => '&#8804;','ge' => '&#8805;','sub' => '&#8834;','sup' => '&#8835;','nsub' => '&#8836;','sube' => '&#8838;','supe' => '&#8839;','oplus' => '&#8853;',
							'otimes' => '&#8855;','perp' => '&#8869;','sdot' => '&#8901;','lceil' => '&#8968;','rceil' => '&#8969;','lfloor' => '&#8970;','rfloor' => '&#8971;','lang' => '&#9001;','rang' => '&#9002;',
							'loz' => '&#9674;','spades' => '&#9824;','clubs' => '&#9827;','hearts' => '&#9829;','diams' => '&#9830;','nbsp' => ' ','iexcl' => '&#161;','cent' => '&#162;','pound' => '&#163;',
							'curren' => '&#164;','yen' => '&#165;','brvbar' => '&#166;','sect' => '&#167;','uml' => '&#168;','copy' => '&#169;','ordf' => '&#170;','laquo' => '&#171;','not' => '&#172;','shy' => '&#173;',
							'reg' => '&#174;','macr' => '&#175;','deg' => '&#176;','plusmn' => '&#177;','sup2' => '&#178;','sup3' => '&#179;','acute' => '&#180;','micro' => '&#181;','para' => '&#182;','middot' => '&#183;',
							'cedil' => '&#184;','sup1' => '&#185;','ordm' => '&#186;','raquo' => '&#187;','frac14' => '&#188;','frac12' => '&#189;','frac34' => '&#190;','iquest' => '&#191;','Agrave' => '&#192;',
							'Aacute' => '&#193;','Acirc' => '&#194;','Atilde' => '&#195;','Auml' => '&#196;','Aring' => '&#197;','AElig' => '&#198;','Ccedil' => '&#199;','Egrave' => '&#200;','Eacute' => '&#201;',
							'Ecirc' => '&#202;','Euml' => '&#203;','Igrave' => '&#204;','Iacute' => '&#205;','Icirc' => '&#206;','Iuml' => '&#207;','ETH' => '&#208;','Ntilde' => '&#209;','Ograve' => '&#210;',
							'Oacute' => '&#211;','Ocirc' => '&#212;','Otilde' => '&#213;','Ouml' => '&#214;','times' => '&#215;','Oslash' => '&#216;','Ugrave' => '&#217;','Uacute' => '&#218;','Ucirc' => '&#219;',
							'Uuml' => '&#220;','Yacute' => '&#221;','THORN' => '&#222;','szlig' => '&#223;','agrave' => '&#224;','aacute' => '&#225;','acirc' => '&#226;','atilde' => '&#227;','auml' => '&#228;',
							'aring' => '&#229;','aelig' => '&#230;','ccedil' => '&#231;','egrave' => '&#232;','eacute' => '&#233;','ecirc' => '&#234;','euml' => '&#235;','igrave' => '&#236;','iacute' => '&#237;',
							'icirc' => '&#238;','iuml' => '&#239;','eth' => '&#240;','ntilde' => '&#241;','ograve' => '&#242;','oacute' => '&#243;','ocirc' => '&#244;','otilde' => '&#245;','ouml' => '&#246;',
							'divide' => '&#247;','oslash' => '&#248;','ugrave' => '&#249;','uacute' => '&#250;','ucirc' => '&#251;','uuml' => '&#252;','yacute' => '&#253;','thorn' => '&#254;','yuml' => '&#255;' );

		return isset( $table[ $matches[1] ] ) ? $table[ $matches[1] ] : '&' . $matches[1] . ';';
	}
}