* Invision Power Services * IP.Board v3.1.4 * Custom bbcode plugin interfaces * Last Updated: $Date: 2010-11-10 07:37:19 -0500 (Wed, 10 Nov 2010) $ * * * @author $author$ * @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 * @version $Rev: 7127 $ **/ interface bbcodePlugin { /** * Method that is run before the content is stored in the database * You are responsible for ensuring you mark the replaced text appropriately so that you * are able to unparse it, if you wish to have bbcode parsed on save * * @access public * @param string $txt BBCode text from submission to be stored in database * @return string Formatted content, ready for display */ public function preDbParse( $txt ); /** * Method that is run before the content is displayed to the user * This is the safest method of parsing, as the original submitted text is left in tact. * No markers are necessary if you use parse on display. * * @access public * @param string $txt BBCode/parsed text from database to be displayed * @return string Formatted content, ready for display */ public function preDisplayParse( $txt ); /** * Method that is run before the content is placed into an editor for editing * If you use "parse on display" you may simply return $txt * * @access public * @param string $txt Parsed text from database to be edited * @return string BBCode content, ready for editing */ public function preEditParse( $txt ); } /** *
 * Invision Power Services
 * IP.Board v3.1.4
 * BBCode parser: default custom bbcodes: img, quote, list, size, member, media, url, snapback
 * Last Updated: $Date: 2010-11-10 07:37:19 -0500 (Wed, 10 Nov 2010) $
 * 
* * @author $author$ * @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 * @version $Rev: 7127 $ * **/ class bbcode_parent_class { /** * Current position in the text document * * @access protected * @var integer */ protected $cur_pos = 0; /** * Stored position of ending quote tag * * @access protected * @var integer */ protected $end_pos = 0; /** * Error message * * @access public * @var string */ public $error = ''; /** * Warning message * * @access public * @var string */ public $warning = ''; /** * This bbcode's data * * @access protected * @var integer */ protected $_bbcode = array(); /** * Registry object * * @access protected * @var object */ protected $registry; /** * Database object * * @access protected * @var object */ protected $DB; /** * Settings object * * @access protected * @var object */ protected $settings; /** * Request object * * @access protected * @var object */ protected $request; /** * Language object * * @access protected * @var object */ protected $lang; /** * Member object * * @access protected * @var object */ protected $member; /** * Cache object * * @access protected * @var object */ protected $cache; /** * Core bbcode class * * @access protected * @var object */ protected $_parentBBcode; /** * Current bbcode * * @access protected * @var string */ protected $currentBbcode; /** * Constructor * * @access public * @param object Registry object * @param object Parent bbcode class * @return void */ public function __construct( ipsRegistry $registry, $_parent=null ) { /* Make object */ $this->registry = $registry; $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(); $this->_parentBBcode = $_parent; foreach( $this->cache->getCache('bbcode') as $bbcode ) { if( $bbcode['bbcode_tag'] == $this->currentBbcode ) { $this->_bbcode = $bbcode; break; } } } /** * Method that is run before the content is stored in the database * You are responsible for ensuring you mark the replaced text appropriately so that you * are able to unparse it, if you wish to have bbcode parsed on save * * @access public * @param string $txt BBCode text from submission to be stored in database * @return string Formatted content, ready for display */ public function preDbParse( $txt ) { return $txt; } /** * Method that is run before the content is displayed to the user * This is the safest method of parsing, as the original submitted text is left in tact. * No markers are necessary if you use parse on display. * * @access public * @param string $txt BBCode/parsed text from database to be displayed * @return string Formatted content, ready for display */ public function preDisplayParse( $txt ) { $this->cur_pos = 0; $this->end_pos = 0; $this->error = ''; return $this->_replaceText( $txt ); } /** * Method that is run before the content is placed into an editor for editing * If you use "parse on display" you may simply return $txt * * @access public * @param string $txt Parsed text from database to be edited * @return string BBCode content, ready for editing */ public function preEditParse( $txt ) { $this->error = ''; return $txt; } /** * Retrieves the tags used for this bbcode, including aliases * * @access public * @return array Array of tags to check */ protected function _retrieveTags() { $_tags = array( $this->_bbcode['bbcode_tag'] ); //----------------------------------------- // We'll also need to check for any aliases //----------------------------------------- if( $this->_bbcode['bbcode_aliases'] ) { $aliases = explode( ',', trim($this->_bbcode['bbcode_aliases']) ); if( is_array($aliases) AND count($aliases) ) { foreach( $aliases as $alias ) { $_tags[] = trim($alias); } } } return $_tags; } } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- class bbcode_img extends bbcode_parent_class implements bbcodePlugin { /** * Constructor * * @access public * @param object Registry object * @param object Parent bbcode class * @return void */ public function __construct( ipsRegistry $registry, $_parent=null ) { $this->currentBbcode = 'img'; parent::__construct( $registry, $_parent ); } /** * Method that is run before the content is stored in the database * You are responsible for ensuring you mark the replaced text appropriately so that you * are able to unparse it, if you wish to have bbcode parsed on save * * @access public * @param string $txt BBCode text from submission to be stored in database * @return string Formatted content, ready for display */ public function preDbParse( $txt ) { return parent::preDbParse( $txt ); } /** * Method that is run before the content is displayed to the user * This is the safest method of parsing, as the original submitted text is left in tact. * No markers are necessary if you use parse on display. * * @access public * @param string $txt BBCode/parsed text from database to be displayed * @return string Formatted content, ready for display */ public function preDisplayParse( $txt ) { return parent::preDisplayParse( $txt ); } /** * Do the actual replacement * * @access protected * @param string $txt Parsed text from database to be edited * @return string BBCode content, ready for editing */ protected function _replaceText( $txt ) { if ( !$this->settings['allow_images'] ) { return $txt; } $_tags = $this->_retrieveTags(); foreach( $_tags as $_tag ) { //----------------------------------------- // Start building open/close tag //----------------------------------------- $open_tag = '[' . $_tag . ']'; $close_tag = '[/' . $_tag . ']'; //----------------------------------------- // Member not viewing images? //----------------------------------------- if( !$this->memberData[ 'view_img' ] ) { $txt = str_replace( $open_tag, '', $txt ); $txt = str_replace( $close_tag, '', $txt ); continue; } //----------------------------------------- // Infinite loop catcher //----------------------------------------- $_iteration = 0; //----------------------------------------- // Doz I can haz opin tag? Loopy loo //----------------------------------------- while( ( $this->cur_pos = stripos( $txt, $open_tag, $this->cur_pos ) ) !== false ) { //----------------------------------------- // Stop infinite loops //----------------------------------------- if( $_iteration > $this->settings['max_bbcodes_per_post'] ) { break; } $_iteration++; //----------------------------------------- // Grab the new position to jump to //----------------------------------------- $new_pos = strpos( $txt, ']', $this->cur_pos ) ? strpos( $txt, ']', $this->cur_pos ) : $this->cur_pos + 1; //----------------------------------------- // No closing tag //----------------------------------------- if( stripos( $txt, $close_tag, $new_pos ) === false ) { break; } //----------------------------------------- // Grab the content //----------------------------------------- $_content = substr( $txt, ($this->cur_pos + strlen($open_tag)), (stripos( $txt, $close_tag, $this->cur_pos ) - ($this->cur_pos + strlen($open_tag))) ); //----------------------------------------- // If this is a single tag, that's it //----------------------------------------- if( $_content ) { /* Trying to sneak in another URL to auto parse? */ preg_match_all( '#http(s)?://#i', $_content, $_match ); if ( count( $_match[0] ) > 1 ) { /* Make safe */ $txt = preg_replace( "#(http|https|news|ftp)://#i", "\\1://", $txt ); return $txt; } $txt = substr_replace( $txt, $this->_buildOutput( $_content ), $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); } else { $txt = substr_replace( $txt, '', $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); } //----------------------------------------- // And reset current position to end of open tag //----------------------------------------- $this->cur_pos = stripos( $txt, $open_tag ) ? stripos( $txt, $open_tag ) : $this->cur_pos + 1; //$new_pos; if( $this->cur_pos > strlen($txt) ) { //----------------------------------------- // Need to reset for next "tag" //----------------------------------------- $this->cur_pos = 0; break; } } } return $txt; } /** * Build the actual output to show * * @access private * @param array $content Image URL to link to * @return string Content to replace bbcode with */ private function _buildOutput( $content ) { $content = trim($content); //----------------------------------------- // Too many images? //----------------------------------------- $existing = $this->cache->getCache('_tmp_bbcode_images'); $existing = intval($existing) + 1; if ( $this->settings['max_images'] AND $this->caches['_tmp_section'] != 'signatures' ) { if ($existing > $this->settings['max_images']) { $this->error = 'too_many_img'; return $content; } } $this->cache->updateCacheWithoutSaving( '_tmp_bbcode_images', $existing ); //----------------------------------------- // Some security checking //----------------------------------------- if ( IPSText::xssCheckUrl( $content ) !== TRUE ) { return $content; } foreach( $this->cache->getCache('bbcode') as $bbcode ) { $_tags = $this->_retrieveTags(); foreach( $_tags as $tag ) { if( stripos( $content, '[' . $tag ) !== false ) { return $content; } } } //----------------------------------------- // Dynamic image? // We removed this setting - no longer used //----------------------------------------- /*if( !$this->settings['allow_dynamic_img'] ) { $dynamic = array( '?', '&' ); foreach( $dynamic as $indicator ) { if( strpos( $content, $indicator ) !== false ) { $this->error = 'no_dynamic'; return $content; } } }*/ //----------------------------------------- // Allowed type? //----------------------------------------- if ( $this->settings['img_ext'] ) { $path = @parse_url( html_entity_decode( $content ), PHP_URL_PATH ); $pieces = explode( '.', $path ); $ext = array_pop( $pieces ); $ext = mb_strtolower( $ext ); if( !in_array( $ext, explode( ',', str_replace( '.', '', mb_strtolower($this->settings['img_ext']) ) ) ) ) { $this->error = 'invalid_ext'; return $content; } } //----------------------------------------- // URL filtering? //----------------------------------------- if ( $this->settings['ipb_use_url_filter'] ) { $list_type = $this->settings['ipb_url_filter_option'] == "black" ? "blacklist" : "whitelist"; if( $this->settings['ipb_url_' . $list_type ] ) { $list_values = array(); $list_values = explode( "\n", str_replace( "\r", "", $this->settings['ipb_url_' . $list_type ] ) ); if( $list_type == 'whitelist' ) { $list_values[] = "http://{$_SERVER['HTTP_HOST']}/*"; } if ( count( $list_values ) ) { $good_url = 0; foreach( $list_values as $my_url ) { if( !trim($my_url) ) { continue; } $my_url = preg_quote( $my_url, '/' ); $my_url = str_replace( '\*', "(.*?)", $my_url ); if ( $list_type == "blacklist" ) { if( preg_match( '/' . $my_url . '/i', $content ) ) { $this->error = 'domain_not_allowed'; return $content; } } else { if ( preg_match( '/' . $my_url . '/i', $content ) ) { $good_url = 1; } } } if ( ! $good_url AND $list_type == "whitelist" ) { $this->error = 'domain_not_allowed'; return $content; } } } } return "{$this->lang->words["; } } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- class bbcode_quote extends bbcode_parent_class implements bbcodePlugin { /**#@+ * Quote tracking * * @access protected * @var int */ protected $quote_open = 0; protected $quote_closed = 0; protected $quote_error = 0; /**#@-*/ /** * Constructor * * @access public * @param object Registry object * @param object Parent bbcode class * @return void */ public function __construct( ipsRegistry $registry, $_parent=null ) { $this->currentBbcode = 'quote'; parent::__construct( $registry, $_parent ); } /** * Method that is run before the content is stored in the database * You are responsible for ensuring you mark the replaced text appropriately so that you * are able to unparse it, if you wish to have bbcode parsed on save * * @access public * @param string $txt BBCode text from submission to be stored in database * @return string Formatted content, ready for display */ public function preDbParse( $txt ) { return parent::preDbParse( $txt ); } /** * Method that is run before the content is displayed to the user * This is the safest method of parsing, as the original submitted text is left in tact. * No markers are necessary if you use parse on display. * * @access public * @param string $txt BBCode/parsed text from database to be displayed * @return string Formatted content, ready for display */ public function preDisplayParse( $txt ) { return parent::preDisplayParse( $txt ); } /** * Do the actual replacement * * @access protected * @param string $txt Parsed text from database to be edited * @return string BBCode content, ready for editing */ protected function _replaceText( $txt ) { $txt = preg_replace_callback( '#(\[quote([^\]]+?)?\].*\[/quote\])#is' , array( $this, '_parseQuote' ), $txt ); return $txt; } /** * Build the actual output to show * * @access private * @param array $options [Optional] Quote options * @return string Content to replace bbcode with */ private function _buildOutput( $options=array() ) { //----------------------------------------- // Build output and return it //----------------------------------------- $output = "

"; $snapback = ''; if( $options['post'] ) { $snapback = "" . $this->registry->output->getReplacement( 'snapback' ) . ""; } if( $options['name'] OR $options['date'] OR $options['timestamp'] ) { // sort timestamp if ( $options['timestamp'] AND strlen( $options['timestamp'] ) == 10 AND ( intval($options['timestamp']) == $options['timestamp'] ) ) { if ( $this->settings['cc_on'] ) { /* Add for parsing */ $options['date'] = ''; } else { $options['date'] = $this->registry->getClass('class_localization')->getDate( $options['timestamp'], 'LONG' ); } } if( $options['name'] AND $options['date'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_full_cite'], $options['name'], $options['date'] ) ; } else if( $options['name'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_name_cite'], $options['name'] ) ; } else if( $options['date'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_date_cite'], $options['date'] ) ; } } else { $output .= $snapback . $this->lang->words['bbc_quote']; } $output .= "

"; $output .= '
quote_open = 0; $this->quote_closed = 0; $this->quote_error = 0; //----------------------------------------- // Make sure we don't have too many embedded //----------------------------------------- if ( $this->settings['max_quotes_per_post'] ) { if ( substr_count( mb_strtolower($txt), '[quote' ) > $this->settings['max_quotes_per_post'] ) { $this->error = 'too_many_quotes'; return $txt; } } //----------------------------------------- // Fix char 173 //----------------------------------------- $txt = str_replace( chr(173).']', ']', $txt ); //----------------------------------------- // Trim the quote content //----------------------------------------- $txt = preg_replace_callback( "#\[quote([^\]]+?)?\](.+?)\[/quote\]#si", array( $this, '_trimQuote' ), $txt ); //----------------------------------------- // Clean usernames with brackets and quotes //----------------------------------------- $txt = preg_replace_callback( "#(name=(?:&\#39;|"|'|\"))(.+?)(&\#39;|"|'|\")#si", array( $this, '_makeQuoteSafe' ), $txt ); $txt = preg_replace_callback( "#name=(&\#39;|"|'|\")(.+?)(\\1)(?:\s?(post|date|timestamp)=)?#si", array( $this, '_makeNameSafe' ), $txt ); //----------------------------------------- // Replace out end tag //----------------------------------------- $txt = str_ireplace( "[/quote]", "
", $txt, $this->quote_closed ); //----------------------------------------- // Replace the quote tag //----------------------------------------- $txt = preg_replace_callback( "#\[quote([^\]]+?)?\]#i", array( $this, '_replaceQuoteTag' ), $txt ); if ( substr_count( $txt, 'error = 'quote_mismatch'; return $_orig; } //----------------------------------------- // Newlines //----------------------------------------- //$txt = str_replace( "\n", "
", $txt ); $txt = str_replace( "

", "
", $txt ); //----------------------------------------- // Swap name replacement (_makeNameSafe) back //----------------------------------------- $txt = str_replace( ''', "'", $txt ); //----------------------------------------- // Turn attachments into links // Prevents em from breaking on other pages //----------------------------------------- preg_match_all( "#
(.+?)
#ims", $txt, $_outerMatches ); foreach( $_outerMatches[1] as $_outerMatch ) { preg_match_all( "#\[attachment=(.+?):(.+?)\]#", $_outerMatch, $_matches ); if( is_array( $_matches[1] ) && count( $_matches[1] ) ) { foreach( $_matches[1] as $idx => $attach_id ) { $txt = str_replace( "[attachment={$attach_id}:{$_matches[2][$idx]}]", $this->registry->getClass('output')->getReplacement('post_attach_link') . " {$_matches[2][$idx]}", $txt ); } } } //----------------------------------------- // If open and close tags match, we're good. // Otherwise, return an error. //----------------------------------------- if ( ( $this->quote_open == $this->quote_closed ) and ( $this->quote_error == 0 ) ) { return $txt; } else { $this->error = 'quote_mismatch'; return $_orig; } } /** * Callback for triming quote * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _trimQuote( $matches ) { $txt = $matches[2]; $extra = IPSText::stripUrls( $matches[1] ); if( $txt == "" ) { return "[quote][/quote]"; } else { $txt = trim( $txt ); return "[quote{$extra}]{$txt}[/quote]"; } } /** * Make the quoted content safe for regex parsing * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _makeQuoteSafe( $matches ) { //----------------------------------------- // INIT //----------------------------------------- $begin = $matches[1]; $end = $matches[3]; $txt = $matches[2]; //----------------------------------------- // Sort name //----------------------------------------- $txt = str_replace( "+", "+" , $txt ); $txt = str_replace( "-", "-" , $txt ); $txt = str_replace( ":", ":" , $txt ); $txt = str_replace( "[", "[" , $txt ); $txt = str_replace( "]", "]" , $txt ); $txt = str_replace( ")", ")" , $txt ); $txt = str_replace( "(", "(" , $txt ); $txt = str_replace( "'", "'" , $txt ); return $begin . IPSText::getTextClass('bbcode')->xssHtmlClean( IPSText::getTextClass('bbcode')->stripAllTags( IPSText::stripUrls( $txt ) ) ) . $end; } /** * Make the name used for the quote safe for regex parsing * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _makeNameSafe( $matches ) { $quote = $matches[1]; $name = $matches[2]; $next = $matches[4]; # Squeeze past the parser... $name = str_replace( ''', "'", $name ); $_last = $next ? ' ' . $next . '=' : ''; return 'name=' . $quote . IPSText::getTextClass('bbcode')->xssHtmlClean( IPSText::getTextClass('bbcode')->stripAllTags( IPSText::stripUrls( $name ) ) ) . $quote . $_last; } /** * Replace the quote tag * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _replaceQuoteTag( $matches ) { //----------------------------------------- // INIT //----------------------------------------- $extra = str_replace( ''', "'", $matches[1] ); $post_id = 0; $date = ''; $timestamp = 0; $name = ''; //----------------------------------------- // Inc.. //----------------------------------------- $this->quote_open++; //----------------------------------------- // Post? //----------------------------------------- preg_match( "#post=([\"']|"|&\#039;|&\#39;)?(\d+)(\\1)?#", $extra, $match ); if ( isset($match[2]) AND intval( $match[2] ) ) { $post_id = intval( $match[2] ); } //----------------------------------------- // Name? //----------------------------------------- preg_match( "#name=([\"']|"|&\#039;|&\#39;)(.*?)(\\1)\s?(date|post)?#is", $extra, $match ); if ( isset($match[2]) AND $match[2] ) { $name = $match[2] ? $this->_makeQuoteSafe( array( 2 => $match[2] ) ) : '-'; } //----------------------------------------- // Date? //----------------------------------------- preg_match( "#date=([\"']|"|&\#039;|&\#39;)(.*?)(\\1)#", $extra, $match ); if ( isset($match[2]) AND $match[2] ) { $date = $this->_makeQuoteSafe( array( 2 => $match[2] ) ); } //----------------------------------------- // Timestamp? //----------------------------------------- preg_match( "#timestamp=([\"']|"|&\#039;|&\#39;)(.*?)(\\1)#", $extra, $match ); if ( isset($match[2]) AND $match[2] ) { $timestamp = intval( $match[2] ); } return $this->_buildOutput( array( 'name' => $name, 'date' => $date, 'post' => $post_id, 'timestamp' => $timestamp ) ); } } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- class bbcode_list extends bbcode_parent_class implements bbcodePlugin { /** * Constructor * * @access public * @param object Registry object * @param object Parent bbcode class * @return void */ public function __construct( ipsRegistry $registry, $_parent=null ) { $this->currentBbcode = 'list'; parent::__construct( $registry, $_parent ); } //public function preDisplayParse($txt){parent::preDisplayParse($txt);exit;} /** * Do the actual replacement * * @access protected * @param string $txt Parsed text from database to be edited * @return string BBCode content, ready for editing */ protected function _replaceText( $txt ) { $_tags = $this->_retrieveTags(); foreach( $_tags as $_tag ) { while( preg_match( "#\n?\[{$_tag}(.*?)\](.+?)\[/{$_tag}\]\n?#is" , $txt, $matches ) ) { $txt = preg_replace_callback( "#(\n){0,1}\[{$_tag}(.*?)\](.+?)\[/{$_tag}\](\n){0,1}#is", array( &$this, '_buildOutput' ), $txt ); } /*while( preg_match( "#\n?\[{$_tag}=(a|A|i|I|1)\](.+?)\[/{$_tag}\]\n?#is" , $txt ) ) { $txt = preg_replace_callback( "#(\n){0,1}\[{$_tag}=(a|A|i|I|1)\](.+?)\[/{$_tag}\](\n){0,1}#is", array( &$this, '_buildOutput' ), $txt ); }*/ } return $txt; } /** * Build the actual output to show * * @access private * @param string $matches Array of regex matches * @return string Content to replace bbcode with */ private function _buildOutput( $matches=array() ) { //----------------------------------------- // Make sure we have at least one list item //----------------------------------------- //if( $matches[2] AND strpos( $matches[2], '[*]' ) === false ) //{ // return preg_replace( "#\[/?list\]?#i", "", $matches[0] . $matches[2] . $matches[3] ); //} //----------------------------------------- // INIT //----------------------------------------- if( $matches[2] ) { $matches[2] = str_replace( array( '"', "'", '"', ''', ''', '=' ), '', $matches[2] ); } $types = array( 'a', 'A', 'i', 'I', '1' ); if ( in_array( $matches[2], $types ) ) { $fnl = $matches[1]; $type = $matches[2]; $txt = $matches[3]; $lnl = $matches[4]; } else { $fnl = $matches[1]; $type = ''; $txt = $matches[3]; $lnl = $matches[4]; } if ( !$txt ) { return; } //----------------------------------------- // No br tags, br tags bad //----------------------------------------- $txt = str_replace( "\n", "", $txt ); $txt = str_replace( array( '
', '
'), "\n", $txt ); $txt = str_replace( "[/list]\n[*]", "[/list][*]", $txt ); $return = ''; if ( !$type ) { $return = $fnl . "" . $lnl; } else { $_cssClass = "decimal"; switch( $type ) { case 'a': $_cssClass = "lower-alpha"; break; case 'A': $_cssClass = "upper-alpha"; break; case 'i': $_cssClass = "lower-roman"; break; case 'I': $_cssClass = "upper-roman"; break; } if( !$this->caches['_tmp_bbcode_isForRte'] ) { $return = $fnl . "" . $lnl; } else { $return = $fnl . "
    " . $this->_listItem($txt) . "
" . $lnl; } } /* Need to remove any extra closing
  • tags. bug #20327 */ $return = preg_replace( "/