* 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) $ * * * @author $Author: mmecham $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license This is NULLED! * @package IP.Board * @link http://hatynka.in * @since 9th March 2005 11:03 * @version $Revision: 9593 $ */ if ( ! defined( 'IN_IPB' ) ) { print "

Incorrect access

You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files."; exit(); } /** * 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 ' it * should NOT convert that to ' when they hit submit. * Test input: < > ' " & ¦ $ \ ! & ' " < > ¶ * Should be processed as: < > ' " & ¦ $ \ ! &amp; &#39; &quot; &lt; &gt; &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( '
', "", $content ); $content = str_replace( '
' , "", $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( '
', "", $content ); $content = str_replace( '
' , "", $content ); } } if ( IPS_QUICK_EDIT_TO_FULL ) { $content = str_replace( '&', '&', $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( '&', '&', $content ); } else { $content = str_replace( '"', '"', $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 ) == '

' && substr( $content, -4 ) == '

' ) { 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( '\\', '\', $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( '\', '\\', $content ); } else { $this->_isSwitchingFrom = 'rte'; /* Assume BBCode has been sent in and we want RTE */ //$content = str_replace( '<', '<', $content ); # Makes manually entered < safe //$content = str_replace( '>', '>', $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( '!', '!', $t ); $t = str_replace( '"', '"', $t ); $t = str_replace( '"', '"', $t ); $t = str_replace( '<', '<', $t ); $t = str_replace( '>', '>', $t ); $t = str_replace( '<', '<', $t ); $t = str_replace( '>', '>', $t ); $t = str_replace( ''', "'", $t ); $t = str_replace( '$', "$", $t ); $t = str_replace( '$', "$", $t ); $t = str_replace( '\', '\\', $t ); $t = str_replace( '\', '\\', $t ); $t = str_replace( ' ', ' ', $t ); /* We replace \ to 092 so convert it back */ $t = str_replace( '&#092;', '\\', $t ); if ( ! IPS_IS_AJAX ) { $t = str_replace( '&', '&', $t ); } $t = str_replace( '_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( '
', '
' ), '__~~--__', $t ); if ( $encodeEntities ) { $t = str_replace( '<', '-<-', $t ); $t = str_replace( '>', '->-', $t ); } else { $t = str_replace( array( '<', '<' ), '-<-', $t ); $t = str_replace( array( '>', '>' ), '->-', $t ); } $t = str_replace( ' ', '
', $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( '&', '&', $t ); } $t = str_replace( '-<-', '<', $t ); $t = str_replace( '->-', '>', $t ); $t = str_replace( '__~~--__', '
', $t ); /* Clean up quote tags (remove many
s */ $t = preg_replace( '#(\[quote([^\]]+?)\])(
){1,}#is', "\\1
", $t ); /* Replace tabs */ $t = str_replace( "{'tab'}", "    ", $t ); /* Clean up the rest of the tags */ //$t = str_replace( array( '<br>', '<br />' ), '
', IPSText::htmlspecialchars( $t ) ); //$t = str_replace( array( '<br>', '<br />' ), '
', $t ); $t = str_replace( "<#IMG_DIR#>", "<#IMG_DIR#>", $t ); $t = str_replace( "<#EMO_DIR#>", "<#EMO_DIR#>", $t ); /* Convert multiple spaces */ $t = str_replace( " ", "  ", $t ); /* Fix up lists */ $t = str_replace( '
', '', $t ); /* make it look a bit pretty */ $t = str_replace( '
', "
\n", $t ); /* CK needs this - legacy data only, new stuff uses proper tags */ if ( stristr( $t, '' ) ) { $t = str_replace( '' , '', $t ); $t = str_replace( '', '', $t ); } if ( stristr( $t, '' ) ) { $t = str_replace( '' , '', $t ); $t = str_replace( '', '', $t ); } /* Wrap in P tags */ if ( self::IPS_P_MODE ) { if ( $t AND ( ! preg_match( '#^<(p)#', $t ) ) ) { $t = $this->_convertBrToWrappedP( $t ); $t = preg_replace( '#

(\s+?)?(\s+?)?

#is', '', $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( '#(]+?)/>)

$#', $test ) ) { $t = preg_replace( '#

$#', ' 

', $t ); } } IPSDebug::fireBug( 'info', array( 'End (rte): ' . $t ) ); return $t; } /** * Convert
tagged data to

* @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 '

' . $string . '

'; /*return '

' . preg_replace('#(\s*?){2,}#', '

'."\n".'

', $string ) . '

'; */ } /** * 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 *

test

*

More data

*


*/ if ( self::IPS_P_MODE ) { /* Tidy up first */ $content = str_replace( '', '

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

(\s+?)?]+?)?'.'> 

#is', "
", $content ); $content = preg_replace( '#(\s+?)?(]+?)?>|

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


      • ' );
        		}
        
        		//-----------------------------------------
        		// WYSI-Weirdness #2: named anchors
        		//-----------------------------------------
        		
        		$content = preg_replace( '#(.+?)#is', "\\1", $content );
        		
        		//-----------------------------------------
        		// WYSI-Weirdness #2.1: Empty a hrefs
        		//-----------------------------------------
        		
        		$content = preg_replace( '#]+)>#is'         , ""   , $content );
        		$content = preg_replace( '#\\1(.+?)#is', "\\1", $content );
        		
        		//-----------------------------------------
        		// WYSI-Weirdness #2.2: Double linked links
        		//-----------------------------------------
        		
        		$content = preg_replace( '#href=[\"\']\w+://(%27|\'|\"|")(.+?)\\1[\"\']#is', "href=\"\\2\"", $content );
        		
        		//-----------------------------------------
        		// WYSI-Weirdness #3: Headline tags
        		//-----------------------------------------
        		
        		$content = preg_replace( "#<(h[0-9])(?:[^>]+?)?>(.+?)#is", "\n[b]\\2[/b]\n", $content );
        		
        		//-----------------------------------------
        		// WYSI-Weirdness #4: Font tags
        		//-----------------------------------------
        		
        		$content = preg_replace( '#(\s*)#is', " ", $content );
        
        		//-----------------------------------------
        		// WYSI-Weirdness #5a: Fix up smilies: IE RTE
        		// @see Ticket 623146
        		//-----------------------------------------
        		
        		$content = preg_replace( '#(\S+?)#i', "\\2", $content );
        		$content = preg_replace( '#alt=\'[\"\'](\S+?)[\'\"]\'#i', "alt='\\1'", $content );
        		$content = preg_replace( '#class=\'[\"\'](\S+?)[\'\"]\'#i', "class='\\1'", $content );
        		$content = preg_replace( '#([a-zA-Z0-9])[\"\'](.+?)[\"\']#i', "\\1 \\4", $content );
        		
        		/* Remove  */
        		$content = preg_replace( '##', '', $content );
        		
        		//-----------------------------------------
        		// WYSI-Weirdness #6: Image tags
        		//-----------------------------------------
        		
        		$content = preg_replace( '#[\"\'][\"\']#', "", $content );
        		$content = preg_replace( '#]+?)?'.'>#is', "[img]\\1[/img]", $content );
        		
        		//-----------------------------------------
        		// WYSI-Weirdness #7: Linked URL tags
        		//-----------------------------------------
        		
        		$content = preg_replace( '#\[url=(\"|\'|")#is', "[url=\\1\\2", $content );
        		
        		//-----------------------------------------
        		// WYSI-Weirdness #8: Make relative images full links
        		//-----------------------------------------
        		
        		$content = preg_replace( '#\[img\](/)?public/style_(emoticons|images)#i', '[img]' . $this->settings['board_url'] . '/public/style_' . '\\2', $content );
        	
        		//-----------------------------------------
        		// Clean up whitespace between lis
        		//-----------------------------------------
        		
        		$content = preg_replace( '#
      • \s+?(\S)#', '
      • \\1', $content ); $content = preg_replace( '#
      • \s+?(\S)#', '\\1', $content ); $content = preg_replace( '#
        (\s+?)?#si', '', $content ); //----------------------------------------- // Now, recursively parse the other tags // to make sure we get the nested ones //----------------------------------------- $content = $this->_recurseAndParse( 'b' , $content, "_parseSimpleTag", 'b' ); $content = $this->_recurseAndParse( 'u' , $content, "_parseSimpleTag", 'u' ); $content = $this->_recurseAndParse( 'strong' , $content, "_parseSimpleTag", 'b' ); $content = $this->_recurseAndParse( 'i' , $content, "_parseSimpleTag", 'i' ); $content = $this->_recurseAndParse( 'em' , $content, "_parseSimpleTag", 'i' ); $content = $this->_recurseAndParse( 'strike' , $content, "_parseSimpleTag", 's' ); $content = $this->_recurseAndParse( 'del' , $content, "_parseSimpleTag", 's' ); $content = $this->_recurseAndParse( 's' , $content, "_parseSimpleTag", 's' ); $content = $this->_recurseAndParse( '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( "

        ", "

        " ), "\n\n", $content ); //----------------------------------------- // WYSI-Weirdness #10: Random junk //----------------------------------------- $content = str_ireplace( array( "", "", "" ), "", $content ); //----------------------------------------- // WYSI-Weirdness #11: Fix up list stuff //----------------------------------------- $content = preg_replace( '#

      • (.*)((?=
      • )|
      • )#is', '\\1', $content ); //----------------------------------------- // WYSI-Weirdness #11.1: Safari badness //----------------------------------------- $content = str_replace( "
  • ", "", $content ); /* Sometimes, unclosed P tags remain if text is copied and pasted directly */ $content = preg_replace( '#<(p|div|ul|li)([^\>]+?)\>#is', '', $content ); //----------------------------------------- // WYSI-Weirdness #12: Convert rest to HTML //----------------------------------------- $content = str_replace( '<' , '<', $content ); $content = str_replace( '>' , '>', $content ); //----------------------------------------- // WYSI-Weirdness #13: Remove useless tags //----------------------------------------- /* Remove embedded stuffs */ $content = preg_replace( '#\[(center|right|left)\]\[\1\]#i', '[\1]', $content ); $content = preg_replace( '#\[/(center|right|left)\]\[/\1\]#i', '[/\1]', $content ); while( preg_match( '#\[(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)="([^\"\']+?)"\]\[/\\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=" is fixed */ $content = preg_replace( '#\[(\w+?)="(.+?)"\]#', "[\\1=\"\\2\"]", $content ); /* Relative paths */ $content = preg_replace( '#\[img\](../../|&\#46;&\#46;/&\#46;&\#46;/)public/#', '[img]' . $this->settings['board_url'] . '/public/', $content ); /* 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 "

    "; print nl2br(htmlspecialchars($ot)); print "
    "; print nl2br($content); print "
    "; 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( '#]+?)\>#is', '
  • ', $between_text ); $between_text = preg_replace( '#
  • \s+?
  • #is', '', $between_text ); $between_text = preg_replace('#
  • ((.(?!\\1
  • ', $between_text); $between_text = trim( $this->_recurseAndParse( 'li', $between_text, "_parseListElement" ) ); $allowed_types = array( 'upper-alpha' => 'A', 'upper-roman' => 'I', 'lower-alpha' => 'a', 'lower-roman' => 'i', 'decimal' => '1' ); if ( ! $allowed_types[ $list_type ] ) { $open_tag = "[list]\n"; } else { $open_tag = '[list=' . $allowed_types[ $list_type ] . "]\n"; } return $open_tag . $this->_recurseAndParse( $tag, $between_text, '_parseListTag' ) . "\n[/list]"; } /** * RTE: Parse List Element tag * * @access protected * @param string Tag * @param string Text between opening and closing tag * @param string Opening tag complete * @param string Parse tag * @return string 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( '[quote', '[/quote]' ), $between_text ); } return '[*]' . rtrim( $between_text ) . "\n"; } /** * RTE: Parse paragraph tags * * @access protected * @param string Tag * @param string Text between opening and closing tag * @param string Opening tag complete * @param string Parse tag * @return string 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, '", $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 "DIV FIRED
    Start tags: {$this->start_tags}
    End tags: {$this->end_tags}
    Between text:
    ".htmlspecialchars($between_text)."
    "; } //----------------------------------------- // 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
    FINISHED
    ".$start_tags . trim( $final ) . $end_tags."
    "; } //----------------------------------------- // 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 "DIV FIRED
    Start tags: {$this->start_tags}
    End tags: {$this->end_tags}
    Between text:
    ".htmlspecialchars($between_text)."
    "; } //----------------------------------------- // 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
    FINISHED
    ".$start_tags . $final . $end_tags."
    "; } //----------------------------------------- // 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' => '(? 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( '#(?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 "
    Got bbcode=$bbcode / opening_tag=$opening_tag"; print "
    - Adding [$bbcode=\"$option\"] [/$bbcode]"; print "
    -- start tags now: {$start_tags}"; print "
    -- end tags now: {$end_tags}"; } } } //----------------------------------------- // Now check for inline style moz may have // added //----------------------------------------- $this->_parseStyles( $opening_tag, $start_tags, $end_tags ); return $start_tags . $this->_recurseAndParse( 'font', $between_text, '_parseFontTag' ) . $end_tags; } /** * RTE: Simple tags * * @access protected * @param string Tag * @param string Text between opening and closing tag * @param string Opening tag complete * @param string Parse tag * @return string 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( '<', '<', $href ); $href = str_replace( '>', '>', $href ); $href = str_replace( ' ', '%20' , $href ); if ( preg_match( '#^mailto\:#is', $href ) ) { $mytag = 'email'; $href = str_replace( "mailto:", "", $href ); } return "[{$mytag}=\"{$href}\"]" . $this->_recurseAndParse( $tag, $between_text, '_parseAnchorTag', $parse_tag ) . "[/{$mytag}]"; } /** * RTE: Recursively parse tags * * @access protected * @param string Tag * @param string Text between opening and closing tag * @param string Callback Function * @param string Parse tag * @return string 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 = ""; $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
    ( ".htmlspecialchars($open_tag)." ) NEST FOUND\n\n"; } $tag_end_loc = strpos($lowtext, $close_tag, $tag_end_loc + $close_tag_len); $nest_open_pos = strpos($lowtext, $open_tag , $nest_open_pos + $open_tag_len ); } //----------------------------------------- // Make sure we have an end location //----------------------------------------- if ( $tag_end_loc === FALSE ) { $start_search_pos = $end_opt; continue; } $this_text_begin = $end_opt + 1; $between_text = substr($text, $this_text_begin, $tag_end_loc - $this_text_begin); $offset = $tag_end_loc + $close_tag_len - $tag_begin_loc; //----------------------------------------- // Pass to function //----------------------------------------- $final_text = $this->$function($tag, $between_text, $tag_opts, $parse_tag); //----------------------------------------- // #DEBUG //----------------------------------------- if ( $this->debug == 2) { print "
    REPLACED {$function}($tag, ..., $tag_opts):
    ".htmlspecialchars(substr($text, $tag_begin_loc, $offset))."
    WITH:
    ".htmlspecialchars($final_text)."
    NEXT ITERATION"; } //----------------------------------------- // Swap text //----------------------------------------- $text = substr_replace($text, $final_text, $tag_begin_loc, $offset); $start_search_pos = $tag_begin_loc + strlen($final_text); } return $text; } /** * RTE: Extract option HTML * * @access protected * @param string Option * @param string Text * @return string 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( '<', '<', $url ); $url = str_replace( '>', '>', $url ); $url = str_replace( ' ', '%20' , $url ); return '<' . $type . '="' . $url . '"'; } /** * Fetches the value of an inline style * @param string $style * @param string $lookFor * @return string|boolean */ protected function _extractCssValue( $style, $lookFor ) { if ( strstr( $style, 'style=') ) { $style = $this->_getValueOfOption( 'style', $style ); } if ( strstr( $style, $lookFor ) ) { if ( preg_match( '#' . preg_quote( $lookFor, '#') . ':(?:\s+?)?(.+?)(;|$|\n)#', $style, $matches ) ) { return trim( $matches[1] ); } } return false; } /** * Converts color:rgb(x,x,x) to color:#xxxxxx * * @access protected * @param string rgb contents: x,x,x * @param string regex end * @return string 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]+;)#', '&', $t ); $t = preg_replace("/&#([0-9]+);/s", "&#\\1;", $t ); } $t = str_replace( ">", ">", $t ); $t = str_replace( "<", "<", $t ); /* We replace \ to 092 so convert it back */ $t = str_replace( '&#092;', '\\', $t ); /* Make it safe */ if ( $NO_SANITIZE !== true ) { $t = str_replace( "" , "-->" , $t ); $t = str_ireplace( "", $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'], "'" ) ) { $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
    */ if ( ! stristr( $matches[0], '", str_replace( array( '
    ', '
    ' ), '', $matches[0] ) ); } return $matches[0]; } /** * The crazy crap we have to deal with. User enters squiggly char, it gets turned into € or Á 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 ´ style chars */ preg_match_all( '/&([a-zA-Z]{5,8});/', $string, $matches ); foreach( $matches[1] as $word ) { $converted = str_replace( '&', '&', $this->_convertEntity( array( 1 => $word ) ) ); if ( $converted ) { $string = str_replace( '&' . $word . ';', $converted, $string ); } } preg_match_all( '/&#([0-9]{3,6});/', $string, $matches ); foreach( $matches[1] as $word ) { if ( $word > 127 ) { $string = str_replace( '&#' . $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' => '"','amp' => '&','lt' => '<','gt' => '>',*/'OElig' => 'Œ','oelig' => 'œ','Scaron' => 'Š','scaron' => 'š','Yuml' => 'Ÿ', 'circ' => 'ˆ','tilde' => '˜','ensp' => ' ','emsp' => ' ','thinsp' => ' ','zwnj' => '‌','zwj' => '‍','lrm' => '‎','rlm' => '‏', 'ndash' => '–','mdash' => '—','lsquo' => '‘','rsquo' => '’','sbquo' => '‚','ldquo' => '“','rdquo' => '”','bdquo' => '„','dagger' => '†', 'Dagger' => '‡','permil' => '‰','lsaquo' => '‹','rsaquo' => '›','euro' => '€','fnof' => 'ƒ','Alpha' => 'Α','Beta' => 'Β','Gamma' => 'Γ', 'Delta' => 'Δ','Epsilon' => 'Ε','Zeta' => 'Ζ','Eta' => 'Η','Theta' => 'Θ','Iota' => 'Ι','Kappa' => 'Κ','Lambda' => 'Λ','Mu' => 'Μ','Nu' => 'Ν', 'Xi' => 'Ξ','Omicron' => 'Ο','Pi' => 'Π','Rho' => 'Ρ','Sigma' => 'Σ','Tau' => 'Τ','Upsilon' => 'Υ','Phi' => 'Φ','Chi' => 'Χ','Psi' => 'Ψ', 'Omega' => 'Ω','alpha' => 'α','beta' => 'β','gamma' => 'γ','delta' => 'δ','epsilon' => 'ε','zeta' => 'ζ','eta' => 'η','theta' => 'θ','iota' => 'ι', 'kappa' => 'κ','lambda' => 'λ','mu' => 'μ','nu' => 'ν','xi' => 'ξ','omicron' => 'ο','pi' => 'π','rho' => 'ρ','sigmaf' => 'ς','sigma' => 'σ', 'tau' => 'τ','upsilon' => 'υ','phi' => 'φ','chi' => 'χ','psi' => 'ψ','omega' => 'ω','thetasym' => 'ϑ','upsih' => 'ϒ','piv' => 'ϖ','bull' => '•', 'hellip' => '…','prime' => '′','Prime' => '″','oline' => '‾','frasl' => '⁄','weierp' => '℘','image' => 'ℑ','real' => 'ℜ','trade' => '™', 'alefsym' => 'ℵ','larr' => '←','uarr' => '↑','rarr' => '→','darr' => '↓','harr' => '↔','crarr' => '↵','lArr' => '⇐','uArr' => '⇑', 'rArr' => '⇒','dArr' => '⇓','hArr' => '⇔','forall' => '∀','part' => '∂','exist' => '∃','empty' => '∅','nabla' => '∇','isin' => '∈', 'notin' => '∉','ni' => '∋','prod' => '∏','sum' => '∑','minus' => '−','lowast' => '∗','radic' => '√','prop' => '∝','infin' => '∞', 'ang' => '∠','and' => '∧','or' => '∨','cap' => '∩','cup' => '∪','int' => '∫','there4' => '∴','sim' => '∼','cong' => '≅','asymp' => '≈', 'ne' => '≠','equiv' => '≡','le' => '≤','ge' => '≥','sub' => '⊂','sup' => '⊃','nsub' => '⊄','sube' => '⊆','supe' => '⊇','oplus' => '⊕', 'otimes' => '⊗','perp' => '⊥','sdot' => '⋅','lceil' => '⌈','rceil' => '⌉','lfloor' => '⌊','rfloor' => '⌋','lang' => '〈','rang' => '〉', 'loz' => '◊','spades' => '♠','clubs' => '♣','hearts' => '♥','diams' => '♦','nbsp' => ' ','iexcl' => '¡','cent' => '¢','pound' => '£', 'curren' => '¤','yen' => '¥','brvbar' => '¦','sect' => '§','uml' => '¨','copy' => '©','ordf' => 'ª','laquo' => '«','not' => '¬','shy' => '­', 'reg' => '®','macr' => '¯','deg' => '°','plusmn' => '±','sup2' => '²','sup3' => '³','acute' => '´','micro' => 'µ','para' => '¶','middot' => '·', 'cedil' => '¸','sup1' => '¹','ordm' => 'º','raquo' => '»','frac14' => '¼','frac12' => '½','frac34' => '¾','iquest' => '¿','Agrave' => 'À', 'Aacute' => 'Á','Acirc' => 'Â','Atilde' => 'Ã','Auml' => 'Ä','Aring' => 'Å','AElig' => 'Æ','Ccedil' => 'Ç','Egrave' => 'È','Eacute' => 'É', 'Ecirc' => 'Ê','Euml' => 'Ë','Igrave' => 'Ì','Iacute' => 'Í','Icirc' => 'Î','Iuml' => 'Ï','ETH' => 'Ð','Ntilde' => 'Ñ','Ograve' => 'Ò', 'Oacute' => 'Ó','Ocirc' => 'Ô','Otilde' => 'Õ','Ouml' => 'Ö','times' => '×','Oslash' => 'Ø','Ugrave' => 'Ù','Uacute' => 'Ú','Ucirc' => 'Û', 'Uuml' => 'Ü','Yacute' => 'Ý','THORN' => 'Þ','szlig' => 'ß','agrave' => 'à','aacute' => 'á','acirc' => 'â','atilde' => 'ã','auml' => 'ä', 'aring' => 'å','aelig' => 'æ','ccedil' => 'ç','egrave' => 'è','eacute' => 'é','ecirc' => 'ê','euml' => 'ë','igrave' => 'ì','iacute' => 'í', 'icirc' => 'î','iuml' => 'ï','eth' => 'ð','ntilde' => 'ñ','ograve' => 'ò','oacute' => 'ó','ocirc' => 'ô','otilde' => 'õ','ouml' => 'ö', 'divide' => '÷','oslash' => 'ø','ugrave' => 'ù','uacute' => 'ú','ucirc' => 'û','uuml' => 'ü','yacute' => 'ý','thorn' => 'þ','yuml' => 'ÿ' ); return isset( $table[ $matches[1] ] ) ? $table[ $matches[1] ] : '&' . $matches[1] . ';'; } }