* Invision Power Services * IP.Board v3.4.9 * Editor Library: RTE (WYSIWYG) Class * Last Updated: $Date: 2015-08-03 11:51:51 -0400 (Mon, 03 Aug 2015) $ * * * @author $Author: stuarts $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license http://www.invisionpower.com/company/standards.php#license * @package IP.Board * @link http://www.invisionpower.com * @since 9th March 2005 11:03 * @version $Revision: 12664 $ */ if ( ! defined( 'IN_IPB' ) ) { print "
toPlainTextArea( $content ); } $content = $this->fromPlainTextArea( $content ); } /* Is this HTML? */ if ( $this->getIsHtml() ) { $content = $this->_htmlize( $content ); } /* New object */ $purifier = $this->_newPurifierObject(); /* Prep it */ $content = $this->_prePurify( $content ); /* Clean it */ if ( $this->getBypassHtmlPurify() !== true ) { $cleanContent = $purifier->purify( $content ); /* Remove negative margins */ $cleanContent = preg_replace( '/margin\-left:\s*?\-([^;$]+?)(;|$)/', "$2", $cleanContent ); } else { $cleanContent = $content; } /* Tweak it */ $cleanContent = $this->_postPurify( $cleanContent ); /* Legacy? So expects BBCode? Ew @todo remove for IPS 4 */ if ( $this->getLegacyMode() ) { $parser = new class_text_parser_legacy(); $this->passSettings( $parser ); $cleanContent = $parser->postEditor( $cleanContent ); } /* Test for errors */ $parser = $this->_newParserObject(); if ( $this->getIsHtml() !== true && $parser->testForParsingLimits( $content ) !== true ) { $this->setParsingErrors( $parser->getErrors() ); } $cleanContent = $this->protectMedia( $cleanContent ); /* Like nice neat code? Look away now */ if ( $this->getIsHtml() AND IN_ACP AND str_replace( '', '', $cleanContent ) !== '' ) { $cleanContent = '' . $cleanContent; } else { $cleanContent = str_replace( '', '', $cleanContent ); } /* Reset */ $this->setIsHtml( false ); /* Return it */ return $cleanContent; } /** * Runs HTMLPurify only * @param string $content * @return string $content */ public function htmlPurify( $content ) { /* New object */ $purifier = $this->_newPurifierObject(); /* Prep it */ $content = $this->_prePurify( $content ); $cleanContent = $purifier->purify( $content ); /* Remove negative margins */ $cleanContent = preg_replace( '/margin\-left:\s*?\-([^;$]+?)(;|$)/', "$2", $cleanContent ); /* Tweak it */ $cleanContent = $this->_postPurify( $cleanContent ); /* Return it */ return $cleanContent; } /** * 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 = IPSText::getEmoticonDirectory(); $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 ); } /** * Fetches the saved content * @param string $autoSaveKey * @return array */ public function getAutoSavedContent( $autoSaveKey ) { $autoSaveKey = $this->_generateAutoSaveKey( $autoSaveKey ); if( isset($this->cachedAutosave[ $autoSaveKey ]) ) { return $this->cachedAutosave[ $autoSaveKey ]; } $return = array(); $parser = $this->_newParserObject(); /* 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['updatedDate'] = $this->registry->getClass('class_localization')->getDate( $return['updated'], 'LONG' ); /* Now figure out previewable content */ $return['parsed'] = $parser->display( $raw['eas_content'] ); } $this->cachedAutosave[ $autoSaveKey ] = $return; 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 */ $parser = $this->_newParserObject(); /* Unconvert emoticons, etc */ $this->setLegacyMode( false ); $content = $this->process( $content ); /* 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; } /** * CKEditor will send posts with HTML entites (<). If we have HTML enabled * we need to make sure that these HTML entites are converted back into HTML * and that
tags are converted into newlines * @param string $conten. */ protected function _htmlize( $content ) { return $content; $content = IPSText::br2nl( $content ); $content = IPSText::UNhtmlspecialchars( $content ); } /** * If we link a youtube URL, it should remain a link. Other code in the parser extracts URLs from links and auto-embeds, so * let's define a custom class here that we can later check for. * @param string HTML * @return string HTML */ protected function protectMedia( $content ) { $content = preg_replace( '#|data-ipb=\'nomediaparse\']*?)href=["\']([^"\']+?)?["\']([^>]*?)?>(.+?)#is', "$4" , $content ); return $content; } /** * Some characters aren't in ISO-8859-1 but browsers still * show them. But HTMLPurifier gets confused so.... */ protected function _cakeAndEatIt( $content ) { /* Uncomment to find ASCII codes */ /*$test = ''; $test = preg_split( '//', $content ); foreach( $test as $i ) { print $i . ' = ' . ord( $i ) . "\n"; } IPSDebug::plainPrint('');*/ # Polish z $content = str_replace( chr(158), 'ž', $content ); # Polish Z $content = str_replace( chr(142), 'Ž', $content ); return $content; } /** * Prep content for purification * @param string $content * @return string */ protected function _prePurify( $content ) { /* When pasting non char set characters, CKEditor adds a hidden body tag, this confuses HTMLPurifier */ $content = preg_replace( '#]+?)>#i', '', $content ); $parser = $this->_newParserObject(); /* Remove our special cite tags */ $content = preg_replace( '/]+?)?>.+?<\/cite>/', '', $content ); /* Must come first: Ensures manual quote tags inside code boxes aren't converted */ $content = $this->_makeCodeTagContentSafeForSaving( $content ); /* Make CODE boxes safe so contents are cleaned by HTML Purifer. Has to come before editorToHtml as that method strips HTML tags */ if ( $this->getAllowHtml() && $this->getIsHtml() ) { $content = $parser->HtmlAllowedPreContents( $content, 'code', array( '[', ']' ) ); } /* Next, convert manual code boxes into pre, quote into blockquote */ $content = $parser->editorToHtml( $content ); /* Now make sure manual IMG tags (that aren't inside of code boxes) are converted properly */ $content = preg_replace( '#\[img\]([^\[]+?)\[/img\]#i', '', $content ); $content = preg_replace( '#\[img=([^\[]+?)\]#i', '
', $content ); $content = $this->_denyLinkify( $content ); $content = str_replace( array( "\r\n", "\r" ), "\n", $content ); /* Fix up tags that don't want their contents running */ /* Stop HTMLPurifier autolinking the closing media tag */ $content = preg_replace( '#(://.+?(?=\[/(\w+?)\]))#i', '\1 ', $content ); if ( IPS_IS_UTF8 !== true ) { $content = $this->_cakeAndEatIt( $content ); } /* Stop made-safe (r) (tm) (c) from converting */ $content = preg_replace( '#&\#40;(tm|r|c)&\#41;#i', '($1)', $content ); /* Fix up weird alignment styles */ $content = preg_replace( '#<(p|div)([^>]+?)dir=([\'"])RTL([\'"])#i', '<\1\2style="text-align:right"', $content ); $content = preg_replace( '#<(p|div)([^>]+?)align=([\'"])right([\'"])#i', '<\1\2style="text-align:right"', $content ); $content = preg_replace( '#<(p|div)([^>]+?)align=([\'"])center([\'"])#i', '<\1\2style="text-align:center"', $content ); return $content; } /** * Tweak content post purify * @param string $content * @return string */ protected function _postPurify( $content ) { /* HTML Purifier helpfuly reconverts HTML entities */ $content = $this->_makeCodeTagContentSafeForSaving( $content ); /* Next */ $content = $this->_denyLinkify( $content, 2 ); /* Code tags are made safe, so unscrew the embedded links */ $content = preg_replace( '#(https|http|ftp)\-~~\-//#' , '\1://', $content ); /* Fix embedded A > IMG tags */ $content = preg_replace( '#
]+?)?>([^"]+?)"#i', '
]+?)?>#i', $content, $matches, PREG_SET_ORDER ); foreach( $matches as $val ) { $all = $val[0]; $src = $val[2]; $rest = $val[3]; if ( substr( $src, 0, 4 ) != 'http' ) { $content = str_replace( $all, '
', $content ); } } if ( IPS_IS_UTF8 ) { /* A control character sneaks in and ruins my life */ $content = preg_replace( '#\xC2\xa0#', ' ', $content ); } /* Filter specific classes in certain elements */ if ( !$this->getAllowHtml() || !$this->getIsHtml() ) { $content = $this->_stripNonAllowedClasses( $content, 'pre' , array( '_prettyXprint', '_linenums*', '_lang*' ) ); $content = $this->_stripNonAllowedClasses( $content, 'blockquote', array( 'ipsBlockquote' ) ); $content = $this->_stripNonAllowedClasses( $content, 'ol' , array( 'bbc', 'bbcol', 'decimal', 'lower-alpha', 'upper-alpha', 'lower-roman', 'upper-roman' ) ); $content = $this->_stripNonAllowedClasses( $content, 'ul' , array( 'bbc', 'bbcol', 'decimal', 'lower-alpha', 'upper-alpha', 'lower-roman', 'upper-roman' ) ); $content = $this->_stripNonAllowedClasses( $content, 'a' , array( 'bbc*' ) ); $content = $this->_stripNonAllowedClasses( $content, 'p' , array( 'bbc*' ) ); } /* Stop HTMLPurifier autolinking the closing media tag */ $content = preg_replace( '#(://.+?)(?=\s\[/(\w+?)\])\s\[/\w+?\]#i', '\1[/\2]', $content ); /* Make {style_images_url} safe */ $content = str_replace( '{style_images_url}', '{style_images_url}', $content ); $content = str_replace( '{style_image_url}', '{style_image_url}', $content ); /* Restore made-safe (r) (tm) (c) from converting */ $content = preg_replace( '#&\#40;(tm|r|c)&\#41;#i', '($1)', $content ); /* http://community.invisionpower.com/resources/bugs.html/_/ip-board/rte-vs-std-spacing-r41660 */ $content = preg_replace( '#
(\s{0,})
]+?)?>#is' , '' , $content ); $content = preg_replace( '#(\s{0,})
]+?)?>#is', '', $content ); return $content; } /** * Target specific tags to deny linkification * @param array $matches */ protected function _denyLinkify( $content, $pass=1 ) { $parser = $this->_newParserObject(); foreach( array( 'media', 'img', 'url' ) as $tag ) { /* CODE: Fetch paired opening and closing tags */ $data = $parser->getTagPositions( $content, $tag, array( '[' , ']' ) ); if ( is_array( $data['open'] ) ) { foreach( $data['open'] as $id => $val ) { if ( $tag == 'img' || $tag == 'url' ) { $o = $data['openWithTag'][ $id ]; $c = $data['closeWithTag'][ $id ] - $o; /* This format [img=..] then? */ if ( ! $c ) { $c = strpos( $content, ']', $o ) - $o; } } else { $o = $data['open'][ $id ]; $c = $data['close'][ $id ] - $o; } $slice = substr( $content, $o, $c ); /* Need to bump up lengths of opening and closing */ $_origLength = strlen( $slice ); if ( $pass == 1 ) { /* Yes this is happening */ $slice = preg_replace( '#(https|http|ftp)://#' , '\1--,,--//', $slice ); if ( $tag == 'url' ) { if ( stristr( $slice, '[url]' ) ) { preg_match( '#^\[url\](.*)\[/url\]$#i', $slice, $matches ); } else if ( preg_match( '#\[url=(?:"|")#i', $slice ) ) { preg_match( '#^\[url=(?:"|")(.*)(?:"|")\](.*)\[/url\]$#i', $slice, $matches ); } else if ( stristr( $slice, '[url=' ) ) { preg_match( '#^\[url=(.*)\](.*)\[/url\]$#i', $slice, $matches ); } /* Attempt to make ?[var]=1 safe */ if ( $matches[1] ) { /* Have we captured BBCode: http--,,--//www.example.com][b]RTE Bug[/b */ $openBracket = strpos( $matches[1], '[' ); $closeBracket = strpos( $matches[1], ']' ); while ( ( $openBracket ) AND ( $openBracket > $closeBracket ) ) { /* Strip off beginning to last open bracket */ $matches[1] = substr( $matches[1], 0, strrpos( $matches[1], '[' ) ); /* Retest */ $openBracket = strpos( $matches[1], '[' ); $closeBracket = strpos( $matches[1], ']' ); } /* Final clean up: http--,,--//www.example.com] */ if ( $closeBracket AND ! $openBracket ) { $matches[1] = substr( $matches[1], 0, strrpos( $matches[1], ']' ) ); } $url = $matches[1]; $url = str_replace( '[', '%5B', $url ); $url = str_replace( ']', '%5D', $url ); $slice = str_replace( $matches[1], $url, $slice ); } } } else { $slice = preg_replace( '#(https|http|ftp)--,,--//#' , '\1://', $slice ); } $_newLength = strlen( $slice ); $content = substr_replace( $content, $slice, $o, $c ); /* Bump! */ if ( $_newLength != $_origLength ) { foreach( $data['open'] as $_id => $_val ) { $_o = $data['open'][ $_id ]; if ( $_o > $o ) { $data['open'][ $_id ] += ( $_newLength - $_origLength ); $data['close'][ $_id ] += ( $_newLength - $_origLength ); $data['openWithTag'][ $_id ] += ( $_newLength - $_origLength ); $data['closeWithTag'][ $_id ] += ( $_newLength - $_origLength ); } } } } } } //IPSDebug::plainPrint ( $content ); /* Prevent media linkify by defaultify okify? */ if ( $pass == 1 ) { $content = preg_replace_callback( '#(^|\s|\)|\(|\{|\}|>|\]|\[|;|href=\S)((http|https|news|ftp)://(?:[^<>\)\[\"\s]+|[a-zA-Z0-9/\._\-!&\#;,%\+\?:=]+))()?#is', array( $this, '_denyMediaLinkify_CallBack' ), $content ); } if ( $pass == 2 ) { $content = preg_replace( '#(https|http|ftp)--,,--//#' , '\1://', $content ); } return $content; } /** * Callback to prevent media tags from being auto linkified * * @access protected * @param array Matches from the regular expression * @return string Converted text */ protected function _denyMediaLinkify_CallBack( $matches ) { $_extra = ''; /* Basic checking */ if ( stristr( $matches[1], 'href' ) ) { return $matches[0]; } if( strlen( $matches[2] ) < 12 ) { return $matches[0]; } if ( isset( $matches[4] ) AND stristr( $matches[4], '' ) ) { return $matches[0]; } /* Check for XSS */ if ( ! IPSText::xssCheckUrl( $matches[2] ) ) { return $matches[0]; } if( substr( $matches[2], -1 ) == ',' ) { $matches[2] = rtrim( $matches[2], ',' ); $_extra = ','; } /* Check for ! which is x; at this point */ if( preg_match( '/\d+?;$/', $matches[2], $_m ) ) { $matches[2] = str_replace( $_m[0], '', $matches[2] ); $_extra = $_m[0]; } /* Is this a media URL? */ if ( $this->settings['bbcode_automatic_media'] ) { $media = $this->cache->getCache('mediatag'); if ( is_array( $media ) AND count( $media ) ) { foreach( $media as $type => $r ) { if ( preg_match( "#^" . $r['match'] . "$#is", $matches[2] ) ) { $matches[2] = preg_replace( '#(https|http)://#' , '\1--,,--//', $matches[2] ); } } } } return $matches[1] . $matches[2] . $_extra; } /** * Fix code tags manually entered * @param array $matches */ protected function _fixManuallyEnteredCodeBoxesIntoRte( $content ) { $parser = $this->_newParserObject(); /* CODE: Fetch paired opening and closing tags */ $data = $parser->getTagPositions( $content, 'code', array( '[' , ']' ) ); if ( is_array( $data['open'] ) ) { foreach( $data['open'] as $id => $val ) { $o = $data['open'][ $id ]; $c = $data['close'][ $id ] - $o; $slice = substr( $content, $o, $c ); /* Need to bump up lengths of opening and closing */ $_origLength = strlen( $slice ); /* Extra conversion for BBCODE>HTML mode */ $slice = preg_replace( '#(https|http|ftp)://#' , '\1://', $slice ); $slice = str_replace( '' , '', $slice ); $slice = str_replace( "
\n", "\n", $slice ) ); /* Stop (r) (tm) and (c) from switching out */ $slice = preg_replace( '#\((tm|r|c)\)#i', '($1)', $slice ); $slice = IPSText::stripTags( $slice, 'pre' ); /* Turn code tags into PRE */ preg_match_all( '#\[code([^\]]+?)?\]#i', $content, $matches, PREG_SET_ORDER ); foreach( $matches as $val ) { $all = $val[0]; $option = $val[1]; $lineNums = 0; $langAdd = ''; if ( $option ) { list( $lang, $lineNums ) = explode( ':', $option ); } if ( $lang ) { $langAdd = ' _lang-' . trim( $lang ); } /* We use underscores so the code is not highlighted when using CKEditor */ $content = str_replace( $all, "
\n", "\n", $slice ); $slice = trim( str_replace( "", $content ); } $content = str_ireplace( '[/code]', '', $content ); $_newLength = strlen( $slice ); $content = substr_replace( $content, $slice, $o, $c ); /* Bump! */ if ( $_newLength != $_origLength ) { foreach( $data['open'] as $_id => $_val ) { $_o = $data['open'][ $_id ]; if ( $_o > $o ) { $data['open'][ $_id ] += ( $_newLength - $_origLength ); $data['close'][ $_id ] += ( $_newLength - $_origLength ); } } } } } return $content; } /** * Check and make safe embedded codes * @param array $matches */ protected function _makeCodeTagContentSafeForSaving( $content ) { $parser = $this->_newParserObject(); /* Fetch paired opening and closing tags */ $data = $parser->getTagPositions( $content, 'pre', array( '<', '>') ); if ( is_array( $data ) && count( $data ) ) { foreach( $data['open'] as $id => $val ) { $o = $data['open'][ $id ]; $c = $data['close'][ $id ] - $o; $slice = substr( $content, $o, $c ); $_origLen = strlen( $slice ); $slice = str_replace( '[', '[', $slice ); $slice = preg_replace( '/\/(\w+?)\]/', '/\1]', $slice ); $slice = str_replace( "{parse", "{parse", $slice ); $slice = preg_replace( '#(https|http|ftp)://#' , '\1-~~-//', $slice ); /* Stop (r) (tm) and (c) from switching out */ $slice = preg_replace( '#\((tm|r|c)\)#i', '($1)', $slice ); $slice = IPSText::stripTags( $slice, 'pre' ); $_newLen = strlen( $slice ); if ( $_newLen != $_origLen ) { /* Bump up next */ foreach( $data['open'] as $_id => $_val ) { $_o = $data['open'][ $_id ]; /* Ascii face alert */ if ( $_o > $o ) { $data['open'][ $_id ] += ( $_newLen - $_origLen ); $data['close'][ $_id ] += ( $_newLen - $_origLen ); } } } $content = substr_replace( $content, $slice, $o, $c ); } } return $content; } /** * Generates unique editor ID */ protected function _fetchEditorName() { return 'editor_' . uniqid(); } /** * 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'] ) ); } /** * Create a purifier object * @return Purifier object */ private function _newPurifierObject() { /* Fetch disabled tags */ $parser = $this->_newParserObject(); $disabledTags = $parser->getDisabledTags(); $disabledTagMap = array( 'b' => array('b', 'strong'), 'i' => array('em'), 's' => array('strike'), 'sup' => array('sup'), 'sub' => array('sub') ); $allowedCssProps = array( 'color', 'font-family', 'font-size', 'background-color', 'font-weight', 'font-style', 'text-align', 'margin', 'margin-left' ); /* Not used v */ $noTags = array( 'html', 'body', 'head', 'meta', 'title', 'script', 'style', 'frame', 'frameset', 'base', 'basefont', 'applet' ); /* P, DIV, PRE, BLOCQUOTE */ $tags = 'p[style|class],div[style],pre[class]' /*,cite[class|contenteditable]*/ . ',blockquote[class|data-author|data-cid|data-date|data-time|data-collapsed],' /* Span */ . 'span[style],br,' /* B, I, U, S, s, s */ . 'b,strong,i,em,u,strike,del,sup,sub,' /* LISTS */ . 'ol[type|start|class],ul[type|class],li,' /* IMG, A */ . 'img[src|width|height|title],a[href|target|title|class|rel]'; if ( count( $disabledTags ) ) { $workingProps = array_combine( $allowedCssProps, $allowedCssProps ); foreach( $disabledTags as $tag ) { if ( in_array( $tag, array_keys( $disabledTagMap ) ) ) { foreach( $disabledTagMap[ $tag ] as $_t ) { $tags = str_replace( ',' . $_t . ',', ',', $tags ); } } else { switch( $tag ) { case 'font': unset( $workingProps['font-family'] ); break; case 'color': unset( $workingProps['color'] ); break; case 'background': unset( $workingProps['background-color'] ); break; case 'size': unset( $workingProps['font-size'] ); break; } } } $allowedCssProps = array_values( $workingProps ); } $config = HTMLPurifier_Config::createDefault(); /* Cache path (Please put first)*/ $config->set('Cache.SerializerPath', HTML_PURIFIER_PATH . 'cache/tmp' ); /* Allow data- attributes */ $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); if ( IN_DEV ) { $config->set('Cache.DefinitionImpl', null); } $config->set('AutoFormat.Linkify', true ); $config->set('Core.Encoding', IPS_DOC_CHAR_SET ); if ( IPS_IS_UTF8 !== true ) { $config->set('Core.EscapeNonASCIICharacters', true ); } /* Allow UTF-8 domain names in URLs */ @set_include_path( IPS_KERNEL_PATH . 'PEAR/Net_IDNA2/' ); require_once( IPS_KERNEL_PATH . 'PEAR/Net_IDNA2/Net/IDNA2.php' ); $config->set('Core.EnableIDNA', true ); /* If HTML mode... as of 3.4.2, HTML enabled content bypasses this method */ if ( $this->getAllowHtml() && $this->getIsHtml() ) { /* Good luck, all */ $config->set('HTML.Trusted', true ); } else { /* Whitelist just the basics we need */ $config->set( 'HTML.Allowed', $tags ); $config->set( 'Core.EscapeInvalidTags', false ); /* Forbid *most* CSS */ $config->set( 'CSS.AllowedProperties', $allowedCssProps ); } /* Limit CSS Damage */ $config->set( 'CSS.AllowImportant', false ); $config->set( 'CSS.AllowTricky' , false ); $config->set( 'CSS.Trusted' , false ); /* Add custom data attributes */ $def = $config->getHTMLDefinition(true); $def->addAttribute( 'blockquote', 'data-author' , 'Text' ); $def->addAttribute( 'blockquote', 'data-cid' , 'Text' ); $def->addAttribute( 'blockquote', 'data-time' , 'Text' ); $def->addAttribute( 'blockquote', 'data-date' , 'Text' ); $def->addAttribute( 'blockquote', 'data-collapsed', 'Text' ); return new HTMLPurifier( $config ); } /** * Create a new parser object * @return object */ private function _newParserObject() { /* Load parser */ $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/text/parser.php', 'classes_text_parser' ); $parser = new $classToLoad(); $parser->set( array( 'parseArea' => $this->getBbcodeSection(), 'memberData' => $this->memberData, 'parseBBCode' => $this->getAllowBbcode(), 'parseHtml' => ( $this->getAllowHtml() && $this->getIsHtml() ), 'parseEmoticons' => $this->getAllowSmilies() ) ); return $parser; } /** * Strip non-allowed class names from items * @param string Text * @param string tag name * @param array Allowed classes */ protected function _stripNonAllowedClasses( $text, $tag, array $allowedClassNames ) { preg_match_all( '#<' . $tag . ' ([^>]+?)?class=([\'"])([^\'"]+?)([\'"])#i', $text, $matches, PREG_SET_ORDER ); foreach( $matches as $val ) { $all = $val[0]; $other = $val[1]; $open = $val[2]; $classes = trim( $val[3] ); $close = $val[4]; if ( $classes ) { $_names = explode( ' ', $classes ); $_use = array(); foreach( $_names as $n ) { $n = trim( $n ); foreach( $allowedClassNames as $allowed ) { if ( strstr( $allowed, '*' ) ) { $allowed = str_replace( '*', '(?:.*)', $allowed ); } if ( preg_match( '#^' . $allowed . '$#i', $n ) ) { $_use[] = $n; } } } $text = str_replace( $all, '<' . $tag . ' ' . $other . ' class=' . $open . implode( ' ', $_use ) . $close, $text ); } } return $text; } /** * Determines whether or not we can use the RTE * @return boolean */ protected function _canWeRte( $ignoreRTECheck=false ) { /* Have we specifically set RTE? */ if ( $this->getForceRte() !== null ) { return $this->getForceRte(); } /* Sent inline */ if ( $ignoreRTECheck === false && ( isset( $_REQUEST['isRte'] ) && $_REQUEST['isRte'] == 1 ) ) { return true; } $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; } else if ( $this->memberData['userAgentKey'] == 'iphone' ) { $return = false; } else if ( $this->memberData['userAgentKey'] == 'iPad' ) { $return = false; } return $return; } /** * Previous to 3.4, we allowed HTML mixed in with BBCode almost by accident. Now that we need HTML mode set via a checkbox, these * areas no longer work in the same way that they did in <= 3.3.x * * NOTE: This method does NOT attempt to verify if the user has HTML posting permissions or not. Use in conjunction with * proper permission checks in your own code. * Or better, pretend this method doesn't exist. * This is not a kitten: http://3.bp.blogspot.com/-pljPk5xFiK8/UC5Wi-5u5TI/AAAAAAAAA3A/Ojk4aFYyCM4/s1600/11059_sheldon-cooper-en-big-bang+copy.jpg */ protected function _tryAndDetermineHtmlStatusTheHackyWay( $content ) { if ( stristr( $content, '' ) ) { return true; } else { /* Ok, look for