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

+189
View File
@@ -0,0 +1,189 @@
<?php
/**
* @brief DOM Parser
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 8 Feb 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* DOM Parser
*/
class _DOMParser
{
/**
* @brief Callback to parse a DOMElement object
*/
protected $elementParser;
/**
* @brief Callback to parse a DOMText object
*/
protected $textParser;
/**
* Parse
*
* @param string $value Contents to parse
* @param callback $elementParser Callback to parse a DOMElement object. Is expected to call $parent->appendChild( $element ) after doing any parsing and call $parser->_parseDomNodeList(). function ( \DOMElement $element, \DOMNode $parent, \IPS\Text\DOMParser $parser )
* @param callback|NULL $textParser Callback to parse a DOMText object. Is expected to call $parent->appendChild( $element ) after doing any parsing. function ( \DOMText $textNode, \DOMNode $parent, \IPS\Text\DOMParser $parser )
* @retrun string
*/
public static function parse( $value, $elementParser, $textParser = NULL )
{
return static::getDocumentBodyContents( ( new static( $elementParser, $textParser ) )->parseValueIntoDocument( $value ) );
}
/**
* Constructor
*
* @param string $value
* @param callback $elementParser Callback to parse a DOMElement object. Is expected to call $parent->appendChild( $element ) after doing any parsing and call $parser->_parseDomNodeList(). function ( \DOMElement $element, \DOMNode $parent, \IPS\Text\DOMParser $parser )
* @param callback|NULL $textParser Callback to parse a DOMText object. Is expected to call $parent->appendChild( $element ) after doing any parsing. function ( \DOMText $textNode, \DOMNode $parent, \IPS\Text\DOMParser $parser )
* @retrun string
*/
public function __construct( $elementParser, $textParser = NULL )
{
$this->elementParser = $elementParser;
$this->textParser = $textParser;
}
/**
* Parse Value into DOMDocument
*
* @param string $value Contents to parse
* @return \DOMDocument
*/
public function parseValueIntoDocument( $value )
{
/* Load the value into a DOMDocument */
$source = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
$source->loadHTML( \IPS\Xml\DOMDocument::wrapHtml( $value ) );
/* Create a new DOMDocument which we will move nodes into */
$document = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
/* Parse */
$this->_parseDomNode( $source, $document );
/* Return */
return $document;
}
/**
* Parse DOMNode
*
* @param \DOMNode $node The node from the source document to parse
* @param \DOMNode $parent The node from the new document which will be this node's parent
* @return void
*/
public function _parseDomNode( \DOMNode $node, \DOMNode &$parent )
{
switch ( $node->nodeType )
{
/* This is the main DOMDocument object and it contains HTML. We just need to loop children */
case XML_HTML_DOCUMENT_NODE:
$this->_parseDomNodeList( $node->childNodes, $parent );
break;
/* This is a HTML element (e.g. <html>, <p>, <a>, etc.) represented as a DOMElement object. Parse it. */
case XML_ELEMENT_NODE:
call_user_func( $this->elementParser, $node, $parent, $this );
break;
/* This is text represented as a DOMText object. Parse it. */
case XML_TEXT_NODE:
if ( $this->textParser )
{
$function = $this->textParser;
$function( $node, $parent, $this );
}
else
{
$parent->appendChild( $parent->ownerDocument->importNode( $node ) );
}
break;
/* This is text represented as a DOMCharacterData object, for example, the
contents of a <script> tag - we just insert it */
case XML_CDATA_SECTION_NODE:
$parent->appendChild( $parent->ownerDocument->importNode( $node ) );
break;
/* These types of nodes are ignored */
case XML_DOCUMENT_TYPE_NODE: // DOMDocumentType
case XML_ATTRIBUTE_NODE: // DOMAttr
case XML_ENTITY_REF_NODE: // DOMEntityReference
case XML_ENTITY_NODE: // DOMEntity
case XML_PI_NODE: // DOMProcessingInstruction
case XML_COMMENT_NODE: // DOMComment
case XML_DOCUMENT_NODE: // DOMDocument but not a HTML document
case XML_DOCUMENT_FRAG_NODE: // DOMDocumentFragment
case XML_NOTATION_NODE: // DOMNotation
case XML_DTD_NODE:
case XML_ELEMENT_DECL_NODE:
case XML_ATTRIBUTE_DECL_NODE:
case XML_ENTITY_DECL_NODE:
case XML_NAMESPACE_DECL_NODE:
default:
break;
}
}
/**
* Loop child nodes of a node and parse them
*
* @param \DOMNodeList $children The child nodes from the source document
* @param \DOMNode $parent The node from the new document which will be the parent of all these nodes
* @return void
*/
public function _parseDomNodeList( \DOMNodeList $children, \DOMNode $parent )
{
foreach ( $children as $child )
{
$this->_parseDomNode( $child, $parent );
}
}
/**
* Get body contents from document
*
* @param \DOMDocument $document The document
* @return string
*/
public static function getDocumentBody( \DOMDocument $document )
{
return $document->getElementsByTagName('body')->item(0);
}
/**
* Get body contents from document
*
* @param \DOMDocument $document The document
* @return string
*/
public static function getDocumentBodyContents( \DOMDocument $document )
{
if ( $body = static::getDocumentBody( $document ) )
{
return \substr( $document->saveHTML( $body ), 6, -7 );
}
else
{
return '';
}
}
}
+3 -3
View File
@@ -2,9 +2,9 @@
/**
* @brief Encrypt Text
* @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 17 Apr 2014
* @version SVN_VERSION_NUMBER
*/
@@ -30,7 +30,7 @@ class _Encrypt
*/
public static function key()
{
return md5( \IPS\Settings::i()->sql_pass . \IPS\Settings::i()->sql_database );
return \IPS\TEXT_ENCRYPTION_KEY ?: md5( \IPS\Settings::i()->sql_pass . \IPS\Settings::i()->sql_database );
}
/**
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Definition caching for HTML Purifier not on disk
* @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 25 Oct 2013
* @version SVN_VERSION_NUMBER
*/
+147
View File
@@ -0,0 +1,147 @@
<?php
/**
* @brief A HTMLPurifier Attribute Definition used for attributes which must be internal URLs
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 21 Mar 2016
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* A HTMLPurifier Attribute Definition used for attributes which must be internal URLs
*/
class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
{
/**
* @brief Allowed bases
*/
protected $allowedBases = NULL;
/**
* Constructor
*
* @param bool $embeds_resource Does the URI here result in an extra HTTP request?
* @param array|null $allowedBases If an array is provided, only URLs with the query strings set allowed - for example array( array( 'app' => 'core', 'module' => 'members', 'controller' => 'profile' ) ) will only allow URLs to profiles. If NULL, any internal URL beside the open proxy and attachment downloads is allowed
*/
public function __construct( $embeds_resource = false, $allowedBases = NULL )
{
$this->allowedBases = $allowedBases;
return parent::__construct( $embeds_resource );
}
/**
* Validate
*
* @param string $uri
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($uri, $config, $context)
{
/* Create the URL */
$url = \IPS\Http\Url::createFromString( str_replace( '%7B___base_url___%7D/', \IPS\Settings::i()->base_url, $uri ) );
/* If it's not internal, we can stop now */
if ( !( $url instanceof \IPS\Http\Url\Internal ) )
{
return FALSE;
}
/* If we have allowed templates, check those... */
if ( $this->allowedBases )
{
$isOkay = FALSE;
$queryString = $url->isFriendly ? ( $url->hiddenQueryString + $url->queryString ) : $url->queryString;
foreach ( $this->allowedBases as $requiredQueryString )
{
$queryStringDiff = array_diff( $requiredQueryString, $queryString );
if ( empty( $queryStringDiff ) )
{
$isOkay = TRUE;
break;
}
}
if ( !$isOkay )
{
return FALSE;
}
}
/* Otherwise exclude the open proxy */
if ( ( isset( $url->queryString['section'] ) and $url->queryString['section'] == 'redirect' ) or ( isset( $url->queryString['controller'] ) and $url->queryString['controller'] == 'redirect' ) )
{
return FALSE;
}
/* And exclude the attachments downloading gateway */
if( mb_strpos( preg_replace( '#/{1,}#', '/', $url->data['path'] ), 'applications/core/interface/file/' ) !== FALSE )
{
return FALSE;
}
/* And exclude uploaded files (we do this by checking the beginning of the (protocol-relative) URL against all upload configurations) */
$url = '//' . mb_substr( (string) $url, mb_strpos( (string) $url, '://' ) + 3 );
foreach( static::getUploadUrls() as $uploadBaseUrl )
{
if( mb_strpos( $url, '//' . mb_substr( $uploadBaseUrl, mb_strpos( $uploadBaseUrl, '://' ) + 3 ) ) === 0 )
{
return FALSE;
}
}
/* Still here? We can pass up */
return parent::validate( $uri, $config, $context );
}
/**
* @brief Uploaded file base URLs (i.e. URL to uploads directory)
*/
protected static $uploadUrls = NULL;
/**
* Fetch and cache upload URLs
*
* @return array
*/
protected static function getUploadUrls()
{
if( static::$uploadUrls !== NULL )
{
return static::$uploadUrls;
}
static::$uploadUrls = array();
foreach( \IPS\File::getConfigurations() as $configuration )
{
$class = \IPS\File::getClass( $configuration['id'] );
if( $class->baseUrl() !== NULL )
{
$url = new \IPS\Http\Url( $class->baseUrl() );
if( $url->isInternal )
{
static::$uploadUrls[ $class->baseUrl() ] = $class->baseUrl();
}
}
}
return static::$uploadUrls;
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
/**
* @brief A HTMLPurifier Attribute Definition for srcset attributes
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 4 May 2016
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* A HTMLPurifier Attribute Definition used for attributes which must be internal URLs
*/
class _HtmlPurifierSrcsetDef extends \HTMLPurifier_AttrDef_URI
{
/**
* Validate
*
* @param string $srcset
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($srcset, $config, $context)
{
$return = array();
foreach ( explode( ',', $srcset ) as $src )
{
$exploded = explode( ' ', trim( $src ) );
$uri = array_shift( $exploded );
$descriptor = implode( ' ', $exploded );
$validated = parent::validate( $uri, $config, $context );
if ( $validated !== FALSE )
{
$return[] = $validated . ( $descriptor ? ( ' ' . $descriptor ) : '' );
}
}
if ( count( $return ) )
{
return implode( ', ', $return );
}
return FALSE;
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
/**
* @brief A HTMLPurifier Attribute Definition which imitates HTMLPurifier_AttrDef_Switch but allows checking if certain attributes exist
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 4 May 2016
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* A HTMLPurifier Attribute Definition which imitates HTMLPurifier_AttrDef_Switch but allows checking if certain attributes exist
*/
class _HtmlPurifierSwitchAttrDef extends \HTMLPurifier_AttrDef_Switch
{
public $required = FALSE;
/**
* @brief Attributes
*/
protected $attributes = array();
/**
* Constructor
*
* @param string $tag The tag name to check
* @param array $attributes The attributes to check on
* @param \HTMLPurifier_AttrDef $with_tag If $tag matches and all $attributes are present, this definition will be used
* @param \HTMLPurifier_AttrDef $without_tag Otherwise, this definition will be used
*/
public function __construct( $tag, $attributes, $with_tag, $without_tag )
{
$this->attributes = $attributes;
parent::__construct( $tag, $with_tag, $without_tag );
}
/**
* Validate
*
* @param string $string
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Context $context
* @return bool|string
*/
public function validate( $string, $config, $context )
{
$token = $context->get('CurrentToken', true);
if ( count( array_diff( $this->attributes, array_keys( $token->attr ) ) ) )
{
return $this->withoutTag->validate( $string, $config, $context );
}
return parent::validate( $string, $config, $context );
}
}
+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;
}
}
+3094 -2166
View File
File diff suppressed because it is too large. Load diff