Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+165 -42
View File
@@ -2,9 +2,9 @@
/**
* @brief Legacy Text Parser
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 12 Jun 2013
* @version SVN_VERSION_NUMBER
*/
@@ -182,7 +182,8 @@ class _LegacyParser
}
/* Grab a new parser object */
$this->parser = new \IPS\Text\Parser( TRUE, $this->idOne ? array( $this->idOne, $this->idTwo, $this->idThree ) : NULL, $this->member, $this->attachClass, TRUE, ( $this->allowHtml ? FALSE : TRUE ), NULL, 1 );
\IPS\Text\Parser::$requestTimeout = 1;
$this->parser = new \IPS\Text\Parser( TRUE, $this->idOne ? array( $this->idOne, $this->idTwo, $this->idThree ) : NULL, $this->member, $this->attachClass, TRUE, ( $this->allowHtml ? FALSE : TRUE ), NULL );
$updatedBbcodeTags = array_keys( $this->parser->bbcodeTags( $this->member, TRUE ) );
@@ -221,6 +222,7 @@ class _LegacyParser
public function parse( $value )
{
/* We have to fix up newlines a little bit */
$codeBlocks = array();
$value = str_replace( array( "\r\n", "\r" ), "\n", $value );
if ( ! \strstr( $value, "\n" ) && ( \stristr( $value, '<br>' ) || \stristr( $value, '<br />' ) ) )
@@ -229,14 +231,32 @@ class _LegacyParser
}
else
{
if ( \stristr( $value, '<br>' ) || \stristr( $value, '<br />' ) )
if ( \stristr( $value, '<br>' ) || \stristr( $value, '<br />' ) || \stristr( $value, '<p>' ) )
{
/* Protect code tags first... */
$value = preg_replace_callback( "/\[code.*?\](.+?)\[\/code\]/ims", function( $match ) use ( &$codeBlocks ) {
$hash = md5( uniqid() );
$codeBlocks[ $hash ] = ( \stristr( $match[0], '<br>' ) || \stristr( $match[0], '<br />' ) || \stristr( $match[0], '<p>' ) ) ? \str_replace( "\n", "", $match[0] ) : $match[0];
return $hash;
}, $value );
$value = preg_replace_callback( "/\<pre\s+?class=[\"']_prettyXprint(?:.*?)[\"']\>(.+?)\<\/pre\>/ims", function( $match ) use ( &$codeBlocks ) {
$hash = md5( uniqid() );
$codeBlocks[ $hash ] = ( \stristr( $match[0], '<br>' ) || \stristr( $match[0], '<br />' ) || \stristr( $match[0], '<p>' ) ) ? \str_replace( "\n", "", $match[0] ) : $match[0];
return $hash;
}, $value );
$value = \str_replace( "\n", "", $value );
}
}
$value = nl2br( $value );
/* If we had any code blocks, put them back now */
foreach( $codeBlocks as $hash => $code )
{
$value = \str_replace( $hash, $code, $value );
}
/* In case the content rebuild has ran or partially ran on this content... */
$value = preg_replace( '#<([^>]+?)(href|src)=(\'|")<fileStore\.([\d\w\_]+?)>/#i', '<\1\2=\3%7BfileStore.\4%7D/', $value );
@@ -288,8 +308,13 @@ class _LegacyParser
/* Old char conversions */
$value = str_replace( "&#160;", " ", $value );
$value = str_replace( "&#39;", "'", $value );
$value = str_replace( "&amp;;", "&", $value );
$value = str_replace( "&lt;#EMO_DIR&gt;", "<#EMO_DIR#>", $value );
$value = str_replace( "&#58;", ":", $value );
$value = str_replace( "&amp;", "&", $value );
/* The macro is not replaced out at runtime in 4.x and if we've made it to this point it is because the direct URL is still present
and the emoticon was not properly embedded. When trying to parse a URL with this macro in it, an error is thrown and the image will
always be broken, so our best option is to swap the macro out with the default folder ("default") as this will work for most users
and there is no other option to look up the emoticon at this point */
$value = str_replace( "&lt;#EMO_DIR&gt;", "default", $value );
/* Unconvert code */
$value = preg_replace_callback( "#<!--sql-->(.+?)<!--sql1-->(.+?)<!--sql2-->(.+?)<!--sql3-->#is", array( $this, '_parseOldCode'), $value );
@@ -299,6 +324,13 @@ class _LegacyParser
$value = preg_replace( "#<!--c2-->(.+?)<!--ec2-->#", '[/code]', $value );
$value = preg_replace( "#<div class=[\"']codetop['\"]>(.+?)</div><div class=[\"']codemain['\"] style=[\"']height:200px;white\-space:pre;overflow:auto['\"]>(.+?)</div>#is", "[code]\\2[/code]", $value );
/* Capital inconsistency */
foreach( array_keys( $this->parser->bbcodeTags( $this->member, TRUE ) ) as $bbcode )
{
$value = str_replace( '[' . mb_strtoupper( $bbcode ), '[' . $bbcode, $value );
$value = str_replace( '[/' . mb_strtoupper( $bbcode ), '[/' . $bbcode, $value );
}
/* Preserve code data */
$codeboxes = array();
preg_match_all( "/\[(code|codebox|sql|php|xml|html)(.*?)\](.+?)\[\/(code|codebox|sql|php|xml|html)\]/ims", $value, $matches );
@@ -312,10 +344,26 @@ class _LegacyParser
$value = str_replace( $m, $replacement, $value );
}
/* Nested spoilers do not parse correctly, so just do that manually here for now */
$value = str_replace( "[spoiler]", '</p><div class="ipsSpoiler" data-ipsSpoiler><div class="ipsSpoiler_header"><span></span></div><div class="ipsSpoiler_contents"><p>', $value );
$value = str_replace( "[/spoiler]", "</p></div></div><p>", $value );
/* Just remove this - our new parser doesn't need (or like) it */
$value = str_replace( "[/*]", '', $value );
/* URL bbcode tags that didn't have a scheme specified would get fixed automatically in 3.x */
$value = preg_replace_callback( "/\[url\](.+?)\[\/url\]/i", function( $matches ){
if( mb_substr( $matches[1], 0, 7 ) !== 'http://' AND mb_substr( $matches[1], 0, 8 ) !== 'https://' AND mb_substr( $matches[1], 0, 6 ) !== 'ftp://' )
{
return 'http://' . $matches[1];
}
else
{
return $matches[1];
}
}, $value );
/* Convert some old HTML back into bbcode to be properly parsed */
$value = preg_replace( "#<a href=[\"']index\.php\?automodule=blog(&|&amp;)showentry=(.+?)['\"]>(.+?)</a>#is", "[entry=\"\\2\"]\\3[/entry]", $value );
$value = preg_replace( "#<a href=[\"']index\.php\?automodule=blog(&|&amp;)blogid=(.+?)['\"]>(.+?)</a>#is", "[blog=\"\\2\"]\\3[/blog]", $value );
@@ -340,13 +388,6 @@ class _LegacyParser
$value = preg_replace( "#<blockquote>(.+?)</blockquote>#is" , "[indent]\\1[/indent]", $value );
}
/* Capital inconsistency */
foreach( array_keys( $this->parser->bbcodeTags( $this->member, TRUE ) ) as $bbcode )
{
$value = str_replace( '[' . mb_strtoupper( $bbcode ), '[' . $bbcode, $value );
$value = str_replace( '[/' . mb_strtoupper( $bbcode ), '[/' . $bbcode, $value );
}
/* Convert quote tag */
$value = preg_replace_callback( "#<blockquote\s+?class=['\"]ipsBlockquote[\"']([^>]*?)>#si", array( $this, '_parseOldBlockquote' ) , $value );
$value = preg_replace_callback( "#\[quote([^>]*?)\]#si" , array( $this, '_parseOldQuoteBbcode' ) , $value );
@@ -366,10 +407,17 @@ class _LegacyParser
$value = str_ireplace( "?>" , "?&gt;" , $value );
/* Fix bbcode attributes */
$value = preg_replace_callback( "/\[([^\]]+?)=(\"|'|&#39;|&#039;|&quot;)([^\]]+?)(\"|'|&#39;|&#039;|&quot;)\]/", function( $matches ) {
/* We strip the enclosing quotes and we strip any trailing ';' */
return "[" . $matches[1] . "=" . rtrim( $matches[3], ';' ) . "]";
}, $value );
preg_match_all( "/\[([^\]]+?)=(\"|'|&#39;|&#039;|&quot;)([^\]]+?)(\"|'|&#39;|&#039;|&quot;)\]/", $value, $matches );
foreach( $matches[0] as $bbcodeTag )
{
$newBbcodeTag = preg_replace_callback( "/\[([^\]]+?)=(\"|'|&#39;|&#039;|&quot;)([^\]]+?)(\"|'|&#39;|&#039;|&quot;)\]/", function( $tagMatches ) {
/* We strip the enclosing quotes and we strip any trailing ';' */
return "[" . $tagMatches[1] . "=" . rtrim( $tagMatches[3], ';' ) . "]";
}, $bbcodeTag );
$value = str_replace( $bbcodeTag, $newBbcodeTag, $value );
}
/* Convert bbcode tags, but only those our new parser doesn't handle */
foreach( $this->bbcodes as $code )
@@ -382,7 +430,7 @@ class _LegacyParser
/* Build the regex */
$regex = "/\[(?:{$code['bbcode_tag']}" . ( $code['bbcode_aliases'] ? '|' . str_replace( ',', '|', $code['bbcode_aliases'] ) : '' ) . ")" .
( $code['bbcode_useoption'] ? "=(.+?)" : '' ) . "\]" .
( $code['bbcode_useoption'] ? ( $code['bbcode_optional_option'] ? "(?:=(.+?))?" : "=(.+?)" ) : '' ) . "\]" .
( $code['bbcode_single_tag'] ? '' : ( "(.+?)\[\/(?:{$code['bbcode_tag']}" . ( $code['bbcode_aliases'] ? '|' . str_replace( ',', '|', $code['bbcode_aliases'] ) : '' ) . ")\]" ) ) .
"/ims";
@@ -460,6 +508,7 @@ class _LegacyParser
/* Now fix shared media */
$value = preg_replace_callback( "/\[sharedmedia=(.+?):(.+?):(.+?)\]/ims", function( $matches ) {
switch( $matches[1] )
{
case 'calendar':
@@ -474,15 +523,20 @@ class _LegacyParser
return "";
}
break;
case 'gallery':
/* Make sure gallery is enabled first */
if ( \IPS\Application::appIsEnabled( 'gallery' ) === FALSE )
{
return "";
}
if( $matches[2] == 'images' )
{
try
{
$image = \IPS\Db::i()->select( '*', 'gallery_images', array( 'image_id=?', (int) $matches[3] ) )->first();
$url = \IPS\Http\Url::internal( "app=gallery&module=gallery&controller=view&id={$image['image_id']}", 'front', 'gallery_image', $image['image_caption_seo'] );
return "<p><a href='{$url}'>{$url}</a></p>";
$image = \IPS\gallery\Image::constructFromData( \IPS\Db::i()->select( '*', 'gallery_images', array( 'image_id=?', (int) $matches[3] ) )->first() );
return "<p><a href='{$image->url()}'><img src='{$image->embedImage()->url}' alt='{$image->caption}'></a></p>";
}
catch( \Exception $e )
{
@@ -493,9 +547,8 @@ class _LegacyParser
{
try
{
$album = \IPS\Db::i()->select( '*', 'gallery_albums', array( 'album_id=?', (int) $matches[3] ) )->first();
$url = \IPS\Http\Url::internal( "app=gallery&module=gallery&controller=browse&album={$album['album_id']}", 'front', 'gallery_album', $album['album_name_seo'] );
return "<p><a href='{$url}'>{$url}</a></p>";
$album = \IPS\gallery\Album::constructFromData( \IPS\Db::i()->select( '*', 'gallery_albums', array( 'album_id=?', (int) $matches[3] ) )->first() );
return \IPS\Text\Parser::embeddableMedia( \IPS\Http\Url::createFromString( $album->url(), FALSE, TRUE ) );
}
catch( \Exception $e )
{
@@ -503,7 +556,7 @@ class _LegacyParser
}
}
break;
case 'downloads':
try
{
@@ -516,12 +569,12 @@ class _LegacyParser
return "";
}
break;
case 'core':
/* We'll just let the attachment parsing next take care of this */
return "[attachment={$matches[3]}:string]";
break;
case 'blog':
try
{
@@ -548,6 +601,12 @@ class _LegacyParser
$value = str_replace( $m, "youtube.com/watch?v=" . $matches[1][ $idx ], $value );
}
/* We previously supported multiple types of 'media' tags which we need to convert now */
foreach( array( 'youtube', 'blogmedia', 'flash', 'movie', 'video' ) as $tagname )
{
$value = str_replace( array( '[' . $tagname . ']', '[/' . $tagname . ']' ), array( '', ' ' ), $value );
}
/* Media */
$urls = array();
@@ -597,8 +656,26 @@ class _LegacyParser
/* Fix hrefs missing the wrapping " or ', but first protect <fileStore...> */
$value = preg_replace( '#<([^>]+?)(href|src)=(\'|")<fileStore\.([\d\w\_]+?)>/#i', '<\1\2=\3%7BfileStore.\4%7D/', $value );
$value = preg_replace_callback( '#<a(.+?)href=(?<![\'"])(.*?)(?!["\'])([ >])#is', function( $matches ){
return "<a" . $matches[1] . " href='" . str_replace( array( "'", '"' ), '', $matches[2] ) . "'" . $matches[3];
$value = preg_replace_callback( '#<a (.+?)>#is', function( $matches ){
if( mb_strpos( $matches[1], 'href="' ) !== FALSE OR mb_strpos( $matches[1], "href='" ) !== FALSE )
{
return $matches[0];
}
$params = explode( ' ', $matches[1] );
foreach( $params as $_idx => $attribute )
{
$attribute = trim( $attribute );
if( mb_strpos( $attribute, 'href=' ) === 0 )
{
$params[ $_idx ] = "href='" . str_replace( 'href=', '', $attribute ) . "'";
break;
}
}
return "<a " . implode( ' ', $params ) . '>';
}, $value );
$value = preg_replace( '#<([^>]+?)(href|src)=(\'|")%7BfileStore\.([\d\w\_]+?)%7D/#i', '<\1\2=\3<fileStore.\4>/', $value );
@@ -616,17 +693,21 @@ class _LegacyParser
$replacement = '<!--url{' . $c . '}-->';
/* In 3.x, hyperlinked YouTube URLs did replace as long as a data attribute wasn't present */
if( ! mb_strpos( $matches[1][ $k ], \IPS\Settings::i()->base_url ) AND mb_strpos( $matches[0][ $k ], 'youtube.co' ) and ! mb_strpos( $matches[0][ $k ], 'nomediaparse' ) AND $response = \IPS\Text\Parser::embeddableMedia( $matches[1][ $k ], TRUE ) )
try
{
$urls[ $c ] = $response;
/* In 3.x, hyperlinked YouTube URLs did replace as long as a data attribute wasn't present */
if( ! mb_strpos( $matches[1][ $k ], \IPS\Settings::i()->base_url ) AND ( mb_strpos( $matches[0][ $k ], 'youtube.co' ) OR mb_strpos( $matches[0][ $k ], 'youtu.be' ) ) and ! mb_strpos( $matches[0][ $k ], 'nomediaparse' ) AND $response = \IPS\Text\Parser::embeddableMedia( \IPS\Http\Url::createFromString( $matches[1][ $k ], FALSE, TRUE ) ) )
{
$urls[ $c ] = $response;
}
}
catch( \UnexpectedValueException $e ){}
$value = str_replace( $m, $replacement, $value );
}
/* Store images */
preg_match_all( '#<img.+?src=[\'"](.+?)["\'].*?>#is', $value, $matches );
preg_match_all( '#<img[^>]+?src=[\'"](.+?)["\'].*?>#is', $value, $matches );
foreach( $matches[0] as $m )
{
@@ -641,7 +722,7 @@ class _LegacyParser
$done = array();
foreach( $matches[0] as $m )
foreach( $matches[1] as $m )
{
if( in_array( $m, $done ) )
{
@@ -670,7 +751,11 @@ class _LegacyParser
$url = preg_replace( "/#entry(\d+)/", $sep . 'do=findComment' . $sep . "comment=$1", $m );
}
$me = \IPS\Text\Parser::embeddableMedia( $url, TRUE );
try
{
$me = \IPS\Text\Parser::embeddableMedia( \IPS\Http\Url::createFromString( $url, FALSE, TRUE ) );
}
catch( \Exception $e ){}
}
if( $me )
@@ -705,7 +790,7 @@ class _LegacyParser
if( $attachment['attach_is_image'] )
{
$attachment['attach_thumb_location'] = $attachment['attach_thumb_location'] ?: $attachment['attach_location'];
$return = "<a class='ipsAttachLink ipsAttachLink_image' href='{fileStore.core_Attachment}/{$attachment['attach_location']}'><img src='{fileStore.core_Attachment}/{$attachment['attach_thumb_location']}' data-fileid='" . \IPS\Settings::i()->base_url . "applications/core/interface/file/attachment.php?id={$attachment['attach_id']}'></a>";
$return = "<a class='ipsAttachLink ipsAttachLink_image' href='{fileStore.core_Attachment}/{$attachment['attach_location']}'><img src='{fileStore.core_Attachment}/{$attachment['attach_thumb_location']}' data-fileid='" . \IPS\Settings::i()->base_url . "applications/core/interface/file/attachment.php?id={$attachment['attach_id']}' class='ipsImage ipsImage_thumbnailed'></a>";
}
else
{
@@ -721,6 +806,9 @@ class _LegacyParser
$value = '<p>' . $value . '</p>';
$value = str_replace( array( '<br>', '<br />' ), '</p><p>', $value );
}
/* https://community.invisionpower.com/4bugtrack/active-reports/4081-editing-posts-ads-extra-spacing-r6742/ */
$value = preg_replace( '/<p>\s*<\/p>/i', '<p>&nbsp;</p>', $value );
/* But that may break blockquotes, which are block elements */
$value = str_replace( "<p><blockquote", "<blockquote", $value );
@@ -766,7 +854,7 @@ class _LegacyParser
$value = preg_replace( "/(<[u|o]l data-ipsBBCode-list=\"true\"(?:.+?)?>)\s*?<br>\s*?\[\*\]/", "$1\n[*]", $value );
$value = preg_replace( "/(<br \/>|<br>)\s*?\[\*\]/", "\n[*]", $value );
$value = preg_replace_callback( "/<[u|o]l data-ipsBBCode-list=\"true\"(?:.+?)?>.+?<\/[u|o]l>/ims", function( $matches )
$value = preg_replace_callback( "/<[u|o]l data-ipsBBCode-list=\"true\"(?:.*?)>.+?<\/[u|o]l>/ims", function( $matches )
{
$v = str_replace( '</p><p>', '<br>', $matches[0] );
$v = str_replace( array( '<p>', '</p>' ), '', $v );
@@ -866,7 +954,7 @@ class _LegacyParser
if ( $match[3] )
{
$return[] = " name='{$match[3]}'";
$return[] = " name='" . static::_cleanQuoteName( $match[3] ) . "'";
}
if ( $match[1] )
@@ -895,7 +983,7 @@ class _LegacyParser
if( count( $matches[1] ) )
{
preg_match( "/data-author=['\"](.+?)[\"']/i", $matches[1], $author );
preg_match( "/data-author=['\"](.+?)[\"']/i", static::_cleanQuoteName( $matches[1] ), $author );
preg_match( "/data-cid=['\"](.+?)[\"']/i", $matches[1], $cid );
preg_match( "/data-time=['\"](.+?)[\"']/i", $matches[1], $time );
@@ -907,7 +995,8 @@ class _LegacyParser
{
$pieces = explode( '_', $this->attachClass );
$parameters['data-ipsquote-contenttype'] = $pieces[0];
$parameters['data-ipsquote-contentapp'] = $pieces[0];
$parameters['data-ipsquote-contenttype'] = mb_strtolower( $pieces[1] );
$parameters['data-ipsquote-contentclass'] = str_replace( '\\', '_', mb_substr( $this->itemClass, 4 ) );
$parameters['data-ipsquote-contentid'] = $this->idOne;
}
@@ -968,6 +1057,24 @@ class _LegacyParser
}
}
/* Try to set the other parameters */
if( isset( $this->attachClass ) )
{
$attachBits = explode( '_', $this->attachClass );
$parameters['data-ipsquote-contentapp'] = $attachBits[0];
$parameters['data-ipsquote-contenttype'] = mb_strtolower( $attachBits[1] );
/* This is not perfect - if you quoted from another topic this would be wrong, however in most cases
this is the best guess so we'll use it */
$parameters['data-ipsquote-contentid'] = $this->idOne;
}
if( isset( $this->itemClass ) )
{
$parameters['data-ipsquote-contentclass'] = str_replace( '\\', '_', mb_substr( $this->itemClass, 4 ) );
}
$_parameterString = '';
foreach( $parameters as $key => $value )
@@ -1001,7 +1108,7 @@ class _LegacyParser
{
return '[code]' . rtrim( str_replace( "</span>", '', preg_replace( "#<span style='.+?'>#is", "", stripslashes( $matches[2] ) ) ) ) . '[/code]';
}
/**
* Convert download manager screenshot URLs
*
@@ -1028,4 +1135,20 @@ class _LegacyParser
return $matches[0];
}
}
/**
* Legacy names can have things like $ and ' which break new parser
*
* @param string Name
* @return string Converted name
*/
protected static function _cleanQuoteName( $name )
{
$name = str_replace( "'", '&#39;', $name );
$name = str_replace( '$', '&#36;', $name );
$name = str_replace( '[', '&#91;', $name );
$name = str_replace( ']', '&#93;', $name );
return $name;
}
}