Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

File diff suppressed because it is too large. Load diff
+33 -26
View File
@@ -11,16 +11,23 @@
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DOMDocument;
use DOMNode;
use DOMNodeList;
use function defined;
use function substr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* DOM Parser
*/
class _DOMParser
class DOMParser
{
/**
* @brief Callback to parse a DOMElement object
@@ -35,12 +42,12 @@ class _DOMParser
/**
* 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 )`
* @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 )`
* @return string
*/
public static function parse( $value, $elementParser, $textParser = NULL )
public static function parse( string $value, callable $elementParser, callable $textParser = NULL ): string
{
$content = static::getDocumentBodyContents( ( new static( $elementParser, $textParser ) )->parseValueIntoDocument( $value ) );
@@ -54,11 +61,11 @@ class _DOMParser
/**
* Constructor
*
* @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 )`
* @return string
* @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 )`
* @return void
*/
public function __construct( $elementParser, $textParser = NULL )
public function __construct( callable $elementParser, callable $textParser = NULL )
{
$this->elementParser = $elementParser;
$this->textParser = $textParser;
@@ -67,10 +74,10 @@ class _DOMParser
/**
* Parse Value into DOMDocument
*
* @param string $value Contents to parse
* @return \DOMDocument
* @param string $value Contents to parse
* @return \IPS\Xml\DOMDocument
*/
public function parseValueIntoDocument( $value )
public function parseValueIntoDocument( string $value ): \IPS\Xml\DOMDocument
{
/* Load the value into a DOMDocument */
$source = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
@@ -89,11 +96,11 @@ class _DOMParser
/**
* 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
* @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 )
public function _parseDomNode( DOMNode $node, DOMNode &$parent ) : void
{
switch ( $node->nodeType )
{
@@ -150,11 +157,11 @@ class _DOMParser
/**
* 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
* @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 )
public function _parseDomNodeList( DOMNodeList $children, DOMNode $parent ) : void
{
foreach ( $children as $child )
{
@@ -165,10 +172,10 @@ class _DOMParser
/**
* Get body contents from document
*
* @param \DOMDocument $document The document
* @return string
* @param DOMDocument $document The document
* @return DOMNode|null
*/
public static function getDocumentBody( \DOMDocument $document )
public static function getDocumentBody( DOMDocument $document ): ?DOMNode
{
return $document->getElementsByTagName('body')->item(0);
}
@@ -176,14 +183,14 @@ class _DOMParser
/**
* Get body contents from document
*
* @param \DOMDocument $document The document
* @param DOMDocument $document The document
* @return string
*/
public static function getDocumentBodyContents( \DOMDocument $document )
public static function getDocumentBodyContents( DOMDocument $document ): string
{
if ( $body = static::getDocumentBody( $document ) )
{
return \substr( $document->saveHTML( $body ), 6, -7 );
return substr( $document->saveHTML( $body ), 6, -7 );
}
else
{
+36 -27
View File
@@ -11,62 +11,71 @@
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use AesCtr;
use IPS\Settings;
use function defined;
use function function_exists;
use function in_array;
use function substr;
use const IPS\TEXT_ENCRYPTION_KEY;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Encrypted
*/
class _Encrypt
class Encrypt
{
/**
* Get Key
*
* @return void
* @return string
*/
public static function key()
public static function key() : string
{
return \IPS\TEXT_ENCRYPTION_KEY ?: md5( \IPS\Settings::i()->sql_pass . \IPS\Settings::i()->sql_database );
return TEXT_ENCRYPTION_KEY ?: md5( Settings::i()->sql_pass . Settings::i()->sql_database );
}
/**
* @brief Cipher
*/
public $cipher;
public ?string $cipher = NULL;
/**
* @brief IV
*/
protected $iv = NULL;
protected ?string $iv = NULL;
/**
* @brief Tag
*/
protected $tag = NULL;
protected ?string $tag = NULL;
/**
* @brief Hash of cipher
*/
protected $hmac = NULL;
protected ?string $hmac = NULL;
/**
* From plaintext
*
* @param string $plaintext Plaintext
* @param string $plaintext Plaintext
* @return static
*/
public static function fromPlaintext( $plaintext )
public static function fromPlaintext( string $plaintext ): static
{
$obj = new static;
/* Try to use OpenSSL if it's available... */
if ( \function_exists( 'openssl_get_cipher_methods' ) )
if ( function_exists( 'openssl_get_cipher_methods' ) )
{
/* If GCM is available (PHP 7.1+), use that as if provides authenticated encryption natively */
if ( \in_array( 'aes-128-gcm', openssl_get_cipher_methods() ) )
if ( in_array( 'aes-128-gcm', openssl_get_cipher_methods() ) )
{
$obj->iv = openssl_random_pseudo_bytes( openssl_cipher_iv_length( 'aes-128-gcm' ) );
$obj->cipher = openssl_encrypt( $plaintext, 'aes-128-gcm', static::key(), 0, $obj->iv, $obj->tag );
@@ -74,7 +83,7 @@ class _Encrypt
}
/* Otherwise, use CBC and store the hash so we can do our own authentication when decrypting */
elseif ( \in_array( 'aes-128-cbc', openssl_get_cipher_methods() ) )
elseif ( in_array( 'aes-128-cbc', openssl_get_cipher_methods() ) )
{
$obj->iv = openssl_random_pseudo_bytes( openssl_cipher_iv_length( 'aes-128-cbc' ) );
$obj->cipher = openssl_encrypt( $plaintext, 'aes-128-cbc', static::key(), OPENSSL_RAW_DATA, $obj->iv );
@@ -85,20 +94,20 @@ class _Encrypt
/* If we're still here, fallback to the PHP library */
require_once \IPS\ROOT_PATH . '/system/3rd_party/AES/AES.php';
$obj->cipher = \AesCtr::encrypt( $plaintext, static::key(), 256 );
$obj->cipher = AesCtr::encrypt( $plaintext, static::key(), 256 );
return $obj;
}
/**
* From plaintext
*
* @param string $cipher Cipher
* @param string|null $iv The IV, or if null, will use the PHP library rather than built-in openssl_*() methods
* @param string|null $tag The tag if using AES-128-GCM
* @param string|null $hash The hash if using AES-128-CBC
* @param string $cipher Cipher
* @param string|null $iv The IV, or if null, will use the PHP library rather than built-in openssl_*() methods
* @param string|null $tag The tag if using AES-128-GCM
* @param string|null $hash The hash if using AES-128-CBC
* @return static
*/
public static function fromCipher( $cipher, $iv = NULL, $tag = NULL, $hash = NULL )
public static function fromCipher( string $cipher, string $iv = NULL, string $tag = NULL, string $hash = NULL ): static
{
$obj = new static;
$obj->cipher = $cipher;
@@ -111,10 +120,10 @@ class _Encrypt
/**
* From tag
*
* @param string $tag Tag
* @param string $tag Tag
* @return static
*/
public static function fromTag( $tag )
public static function fromTag( string $tag ): static
{
if ( preg_match( '/^\[\!AES128GCM\[(.+?)\]\[(.+?)\]\[(.+?)\]\]/', $tag, $matches ) )
{
@@ -125,7 +134,7 @@ class _Encrypt
$cipher = base64_decode( $matches[1] );
$ivLength = openssl_cipher_iv_length('aes-128-cbc');
return static::fromCipher( \substr( $cipher, $ivLength + 32 ), \substr( $cipher, 0, $ivLength ), NULL, \substr( $cipher, $ivLength, 32 ) );
return static::fromCipher( substr( $cipher, $ivLength + 32 ), substr( $cipher, 0, $ivLength ), NULL, substr( $cipher, $ivLength, 32 ) );
}
elseif ( preg_match( '/^\[\!AES\[(.+?)\]\]/', $tag, $matches ) )
{
@@ -142,7 +151,7 @@ class _Encrypt
*
* @return string
*/
public function tag()
public function tag(): string
{
if ( $this->tag )
{
@@ -157,7 +166,7 @@ class _Encrypt
return '[!AES[' . $this->cipher . ']]';
}
}
/**
* Decrypt
*
@@ -184,7 +193,7 @@ class _Encrypt
else
{
require_once \IPS\ROOT_PATH . '/system/3rd_party/AES/AES.php';
return \AesCtr::decrypt( $this->cipher, $keyToUse, 256 );
return AesCtr::decrypt( $this->cipher, $keyToUse, 256 );
}
}
}
+27 -26
View File
@@ -10,16 +10,19 @@
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Data\Store;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Definition caching for HTML Purifier not on disk
*/
class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
class HtmlPurifierDefinitionCache extends HTMLPurifier_DefinitionCache
{
/**
* Adds a definition object to the cache
@@ -28,7 +31,7 @@ class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
* @param mixed $config HTML Purifier configuration
* @return bool
*/
public function add($def, $config)
public function add( mixed $def, mixed $config): bool
{
return $this->set( $def, $config );
}
@@ -40,24 +43,24 @@ class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
* @param mixed $config HTML Purifier configuration
* @return bool
*/
public function set($def, $config)
public function set( mixed $def, mixed $config): bool
{
/* If invalid type, just return */
if( !$this->checkDefType( $def ) )
{
return;
return FALSE;
}
/* Generate key and store it */
$key = $this->generateKey( $config );
\IPS\Data\Store::i()->$key = base64_encode( \serialize( $def ) );
Store::i()->$key = base64_encode( serialize( $def ) );
/* Store an array of all keys so we can implement flush() and cleanup() */
$currentKeys = \IPS\Data\Store::i()->htmlpurifier_definitions;
$currentKeys = Store::i()->htmlpurifier_definitions;
$currentKeys[ $key ] = $key;
\IPS\Data\Store::i()->htmlpurifier_definitions = $currentKeys;
Store::i()->htmlpurifier_definitions = $currentKeys;
/* Return TRUE */
return TRUE;
@@ -70,7 +73,7 @@ class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
* @param mixed $config HTML Purifier configuration
* @return bool
*/
public function replace($def, $config)
public function replace( mixed $def, mixed $config): bool
{
return $this->set( $def, $config );
}
@@ -81,13 +84,13 @@ class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
* @param mixed $config HTML Purifier configuration
* @return mixed FALSE if not found, or the definition object
*/
public function get($config)
public function get( mixed $config ): mixed
{
$key = $this->generateKey( $config );
if( isset( \IPS\Data\Store::i()->$key ) and \IPS\Data\Store::i()->$key !== 0 )
if( isset( Store::i()->$key ) and Store::i()->$key !== 0 )
{
return \unserialize( base64_decode( \IPS\Data\Store::i()->$key ) );
return unserialize( base64_decode( Store::i()->$key ) );
}
else
{
@@ -101,13 +104,13 @@ class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
* @param mixed $config HTML Purifier configuration
* @return bool
*/
public function remove($config)
public function remove( mixed $config ): bool
{
$key = $this->generateKey( $config );
if( isset( \IPS\Data\Store::i()->$key ) )
if( isset( Store::i()->$key ) )
{
unset( \IPS\Data\Store::i()->$key );
unset( Store::i()->$key );
}
return TRUE;
@@ -119,22 +122,20 @@ class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
* @param mixed $config HTML Purifier configuration
* @return void
*/
public function flush($config)
public function flush( mixed $config ) : void
{
if( !isset( \IPS\Data\Store::i()->htmlpurifier_definitions ) )
if( !isset( Store::i()->htmlpurifier_definitions ) )
{
\IPS\Data\Store::i()->htmlpurifier_definitions = array();
Store::i()->htmlpurifier_definitions = array();
}
foreach( \IPS\Data\Store::i()->htmlpurifier_definitions as $key )
foreach( Store::i()->htmlpurifier_definitions as $key )
{
if( isset( \IPS\Data\Store::i()->$key ) )
if( isset( Store::i()->$key ) )
{
unset( \IPS\Data\Store::i()->$key );
unset( Store::i()->$key );
}
}
return;
}
/**
@@ -143,8 +144,8 @@ class HtmlPurifierDefinitionCache extends \HTMLPurifier_DefinitionCache
* @param mixed $config HTML Purifier configuration
* @return void
*/
public function cleanup($config)
public function cleanup( mixed $config ) : void
{
return $this->flush( $config );
$this->flush( $config );
}
}
+13 -7
View File
@@ -11,9 +11,15 @@
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use HTMLPurifier_URIFilter;
use IPS\Settings;
use function defined;
use const PHP_URL_HOST;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
@@ -21,17 +27,17 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
* An HTMLPurifier image transformation that only allows HTTPS images
* @see https://stackoverflow.com/questions/34876151/htmlpurifier-allow-scheme-for-specific-tags
*/
class _HtmlPurifierHttpsImages extends \HTMLPurifier_URIFilter
class HtmlPurifierHttpsImages extends HTMLPurifier_URIFilter
{
public $name = 'HttpsEmbedScheme';
public $ourHost = '';
public string $ourHost = '';
public function prepare( $config )
public function prepare( $config ) : void
{
$this->ourHost = parse_url( \IPS\Settings::i()->base_url, \PHP_URL_HOST );
$this->ourHost = parse_url( Settings::i()->base_url, PHP_URL_HOST );
}
public function filter( &$uri, $config, $context )
public function filter( &$uri, $config, $context ): bool
{
/* We only care about embedded resources for this, skip src attr so our own parser can handle that. */
if ( !$context->get( 'EmbeddedURI', true ) OR $context->get( 'CurrentAttr', true ) == 'src' )
@@ -1,45 +0,0 @@
<?php
/**
* @brief A HTMLPurifier Attribute Definition used for attributes which must be internal URLs or Integers (such as data-fileid for attachments)
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 21 October 2024
*/
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 or Integers (such as data-fileid for attachments)
*/
class _HtmlPurifierIntOrInternalLink extends HtmlPurifierInternalLinkDef
{
/**
* Validate
*
* @param string $value
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Context $context
* @return bool|string
*/
public function validate( $value, $config, $context )
{
$parentCheck = parent::validate( $value, $config, $context );
if( $parentCheck !== FALSE )
{
return $parentCheck;
}
$integer = new \HTMLPurifier_AttrDef_Integer( false );
return $integer->validate( $value, $config, $context );
}
}
+36 -32
View File
@@ -11,29 +11,40 @@
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use HTMLPurifier_AttrDef_URI;
use HTMLPurifier_Config;
use HTMLPurifier_Context;
use IPS\File;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\Http\Url\Internal;
use IPS\Settings;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_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
class HtmlPurifierInternalLinkDef extends HTMLPurifier_AttrDef_URI
{
/**
* @brief Allowed bases
*/
protected $allowedBases = NULL;
protected ?array $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
* @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 )
public function __construct( bool $embeds_resource=FALSE, array $allowedBases = NULL )
{
$this->allowedBases = $allowedBases;
return parent::__construct( $embeds_resource );
@@ -43,24 +54,17 @@ class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
* Validate
*
* @param string $uri
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Context $context
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($uri, $config, $context)
{
public function validate($uri, $config, $context): bool|string
{
/* Create the URL */
try
{
$url = \IPS\Http\Url::createFromString( str_replace( array( '%7B___base_url___%7D/', '{___base_url___}/' ), \IPS\Settings::i()->base_url, $uri ) );
}
catch( \IPS\Http\Url\Exception $e )
{
return FALSE;
}
$url = Url::createFromString( str_replace( array( '%7B___base_url___%7D/', '{___base_url___}/' ), Settings::i()->base_url, $uri ) );
/* If it's not internal, we can stop now */
if ( !( $url instanceof \IPS\Http\Url\Internal ) )
if ( !( $url instanceof Internal ) )
{
return FALSE;
}
@@ -69,7 +73,7 @@ class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
if ( $this->allowedBases )
{
$isOkay = FALSE;
$queryString = ( $url instanceof \IPS\Http\Url\Friendly ) ? ( $url->hiddenQueryString + $url->queryString ) : $url->queryString;
$queryString = ( $url instanceof Friendly ) ? ( $url->hiddenQueryString + $url->queryString ) : $url->queryString;
foreach ( $this->allowedBases as $requiredQueryString )
{
@@ -117,15 +121,15 @@ class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
/**
* @brief Uploaded file base URLs (i.e. URL to uploads directory)
*/
protected static $uploadUrls = NULL;
protected static ?array $uploadUrls = NULL;
/**
* Fetch and cache upload URLs
*
* @return array
*/
protected static function getUploadUrls()
{
/**
* Fetch and cache upload URLs
*
* @return array|null
*/
protected static function getUploadUrls(): ?array
{
if( static::$uploadUrls !== NULL )
{
return static::$uploadUrls;
@@ -133,15 +137,15 @@ class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
static::$uploadUrls = array();
foreach( \IPS\File::getStore() as $configuration )
foreach( File::getStore() as $configuration )
{
$class = \IPS\File::getClass( $configuration['id'] );
$class = File::getClass( $configuration['id'] );
if( $class->baseUrl() !== NULL )
{
$url = new \IPS\Http\Url( $class->baseUrl() );
$url = new Url( $class->baseUrl() );
if( $url instanceof \IPS\Http\Url\Internal )
if( $url instanceof Internal )
{
static::$uploadUrls[ $class->baseUrl() ] = $class->baseUrl();
}
+15 -8
View File
@@ -11,27 +11,34 @@
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use HTMLPurifier_AttrDef_URI;
use HTMLPurifier_Config;
use HTMLPurifier_Context;
use function count;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_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
class HtmlPurifierSrcsetDef extends HTMLPurifier_AttrDef_URI
{
/**
* Validate
*
* @param string $srcset
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Context $context
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($srcset, $config, $context)
{
public function validate($srcset, $config, $context): bool|string
{
$return = array();
foreach ( explode( ',', $srcset ) as $src )
@@ -47,7 +54,7 @@ class _HtmlPurifierSrcsetDef extends \HTMLPurifier_AttrDef_URI
}
}
if ( \count( $return ) )
if ( count( $return ) )
{
return implode( ', ', $return );
}
+21 -13
View File
@@ -11,36 +11,44 @@
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use HTMLPurifier_AttrDef;
use HTMLPurifier_AttrDef_Switch;
use HTMLPurifier_Config;
use HTMLPurifier_Context;
use function count;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_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
class HtmlPurifierSwitchAttrDef extends HTMLPurifier_AttrDef_Switch
{
/**
* @brief Is the attribute required
*/
public $required = FALSE;
public bool $required = FALSE;
/**
* @brief Attributes
*/
protected $attributes = array();
protected array $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
* @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 )
public function __construct( string $tag, array $attributes, $with_tag, HTMLPurifier_AttrDef $without_tag )
{
$this->attributes = $attributes;
parent::__construct( $tag, $with_tag, $without_tag );
@@ -50,15 +58,15 @@ class _HtmlPurifierSwitchAttrDef extends \HTMLPurifier_AttrDef_Switch
* Validate
*
* @param string $string
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Context $context
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate( $string, $config, $context )
{
public function validate( $string, $config, $context ): bool|string
{
$token = $context->get('CurrentToken', true);
if ( \count( array_diff( $this->attributes, array_keys( $token->attr ) ) ) )
if ( count( array_diff( $this->attributes, array_keys( $token->attr ) ) ) )
{
return $this->withoutTag->validate( $string, $config, $context );
}
+145 -121
View File
@@ -11,72 +11,96 @@
namespace IPS\Text;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Application;
use IPS\Db;
use IPS\File;
use IPS\gallery\Album;
use IPS\gallery\Image;
use IPS\Http\Url;
use IPS\Member;
use IPS\Output;
use IPS\Settings;
use IPS\Theme;
use UnderflowException;
use UnexpectedValueException;
use function count;
use function defined;
use function in_array;
use function intval;
use function is_array;
use function str_ireplace;
use function str_replace;
use function stristr;
use function strstr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Legacy Text Parser
*/
class _LegacyParser
class LegacyParser
{
/**
* @brief Maximum number of embeds we will try.
* @note Attempting to pull too many during the rebuild process can easily time out
*/
public $maxEmbeds = 10;
public int $maxEmbeds = 10;
/**
* Parse statically
*
* @param string $value The value to parse
* @param \IPS\Member|null $member The member posting. NULL will use currently logged in member.
* @param bool $allowHtml Allow HTML
* @param string $attachClass Key to use for attachments
* @param int $id1 ID1 to use for attachments
* @param int $id2 ID2 to use for attachments
* @param int|NULL $id3 ID3 to use for attachments
* @param string $itemClass The *item* classname (e.g. IPS\forums\Topic)
* @param string $value The value to parse
* @param Member|null $member The member posting. NULL will use currently logged in member.
* @param bool $allowHtml Allow HTML
* @param string|null $attachClass Key to use for attachments
* @param int $id1 ID1 to use for attachments
* @param int $id2 ID2 to use for attachments
* @param int|null $id3 ID3 to use for attachments
* @param string|null $itemClass The *item* classname (e.g. IPS\forums\Topic)
* @return string
* @see __construct
*/
public static function parseStatic( $value, $member=NULL, $allowHtml=FALSE, $attachClass=null, $id1=0, $id2=0, $id3=NULL, $itemClass=NULL )
public static function parseStatic(string $value, Member $member=NULL, bool $allowHtml=FALSE, string $attachClass=null, int $id1=0, int $id2=0, int $id3=NULL, string $itemClass=NULL ): string
{
$obj = new static( $member, $allowHtml, $attachClass, $id1, $id2, $id3, $itemClass );
$obj = new static($member, $allowHtml, $attachClass, $id1, $id2, $id3, $itemClass);
return $obj->parse( $value );
}
/**
* @brief BBcodes
*/
public $bbcodes = NULL;
public static ?array $bbcodes = NULL;
/**
* @brief Emoticons
*/
public $emoticons = NULL;
public ?array $emoticons = NULL;
/**
* Can use plugin?
*
* @param \IPS\Member $member The member
* @param string $bbcode BBCode
* @param Member $member The member
* @param string $bbcode BBCode
* @return bool
*/
public static function canUse( \IPS\Member $member, $bbcode )
public static function canUse(Member $member, string $bbcode ): bool
{
foreach( static::$bbcodes as $code )
{
if( $code['bbcode_tag'] == $bbcode OR ( $code['bbcode_aliases'] AND \in_array( $bbcode, explode( ',', $code['bbcode_aliases'] ) ) ) )
if( $code['bbcode_tag'] == $bbcode OR ( $code['bbcode_aliases'] AND in_array( $bbcode, explode( ',', $code['bbcode_aliases'] ) ) ) )
{
if( $code['bbcode_groups'] == 'all' )
{
return true;
}
if( $member->inGroup( explode( ',', $bbcode['bbcode_groups'] ) ) )
if( $member->inGroup( explode( ',', $code['bbcode_groups'] ) ) )
{
return true;
}
@@ -89,64 +113,64 @@ class _LegacyParser
/**
* @brief Member
*/
protected $member = NULL;
protected ?Member $member = NULL;
/**
* @brief Allow HTML
*/
protected $allowHtml = FALSE;
protected bool $allowHtml = FALSE;
/**
* @brief Parser object
*/
public $parser = NULL;
public mixed $parser = NULL;
/**
* @brief Key for attachments
*/
protected $attachClass = NULL;
protected ?string $attachClass = NULL;
/**
* @brief Item classname
*/
protected $itemClass = NULL;
protected ?string $itemClass = NULL;
/**
* @brief ID1 to use for attachments
*/
protected $idOne = 0;
protected int $idOne = 0;
/**
* @brief ID2 to use for attachments
*/
protected $idTwo = 0;
protected int $idTwo = 0;
/**
* @brief ID3 to use for attachments
*/
protected $idThree = 0;
protected ?int $idThree = 0;
/**
* @brief Are Acronyms Supported?
*/
protected static $_newAcronyms = null;
protected static ?bool $_newAcronyms = null;
/**
* Constructor
*
* @param \IPS\Member|null $member The member posting. NULL will use guest.
* @param bool $allowHtml Allow HTML
* @param string $attachClass Key to use for attachments
* @param int $id1 ID1 to use for attachments
* @param int $id2 ID2 to use for attachments
* @param int|NULL $id3 ID3 to use for attachments
* @param string $itemClass The *item* classname (e.g. IPS\forums\Topic)
* @param Member|null $member The member posting. NULL will use guest.
* @param bool $allowHtml Allow HTML
* @param string|null $attachClass Key to use for attachments
* @param int $id1 ID1 to use for attachments
* @param int $id2 ID2 to use for attachments
* @param int|null $id3 ID3 to use for attachments
* @param string|null $itemClass The *item* classname (e.g. IPS\forums\Topic)
* @return void
*/
public function __construct( $member=NULL, $allowHtml=FALSE, $attachClass=null, $id1=0, $id2=0, $id3=NULL, $itemClass=NULL )
public function __construct( Member $member=NULL, bool $allowHtml=FALSE, string $attachClass=null, int $id1=0, int $id2=0, int $id3=NULL, string $itemClass=NULL )
{
/* Get member */
$this->member = $member === NULL ? \IPS\Member::load(0) : $member;
$this->member = $member === NULL ? Member::load(0) : $member;
/* Remember if we allow HTML */
$this->allowHtml = $allowHtml;
@@ -163,9 +187,9 @@ class _LegacyParser
/* Grab legacy custom bbcodes */
try
{
$this->bbcodes = iterator_to_array( \IPS\Db::i()->select( '*', 'custom_bbcode' )->setKeyField( 'bbcode_tag' ) );
$this->bbcodes = iterator_to_array( Db::i()->select( '*', 'custom_bbcode' )->setKeyField( 'bbcode_tag' ) );
}
catch( \IPS\Db\Exception $e )
catch(Db\Exception $e )
{
$this->bbcodes = array();
}
@@ -173,9 +197,9 @@ class _LegacyParser
/* Grab emoticons */
try
{
$this->emoticons = iterator_to_array( \IPS\Db::i()->select( '*', 'core_emoticons' )->setKeyField( 'typed' ) );
$this->emoticons = iterator_to_array( Db::i()->select( '*', 'core_emoticons' )->setKeyField( 'typed' ) );
}
catch( \IPS\Db\Exception $e )
catch(Db\Exception $e )
{
$this->emoticons = array();
}
@@ -183,20 +207,20 @@ class _LegacyParser
/* The `a_type` column is added in 4.5, so we need to account for this if running the legacy parser during an older upgrade */
if( static::$_newAcronyms === NULL )
{
static::$_newAcronyms = \IPS\Db::i()->checkForColumn( 'core_acronyms', 'a_type' );
static::$_newAcronyms = Db::i()->checkForColumn( 'core_acronyms', 'a_type' );
}
/* Grab a new parser object */
\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, static::$_newAcronyms );
Parser::$requestTimeout = 1;
$this->parser = new ConverterParser( TRUE, $this->idOne ? array( $this->idOne, $this->idTwo, $this->idThree ) : NULL, $this->member, $this->attachClass, TRUE, ( $this->allowHtml ? FALSE : TRUE ), NULL, static::$_newAcronyms );
/* If we're in the upgrader and the new acronym support hasn't been added yet, populate acronyms manually */
if( !static::$_newAcronyms )
{
$this->parser->caseSensitiveAcronyms = iterator_to_array( \IPS\Db::i()->select( array( 'a_short', 'a_long', "'acronym' as `a_type`" ), 'core_acronyms', array( 'a_casesensitive=1' ) )->setKeyField( 'a_short' ) );
$this->parser->caseSensitiveAcronyms = iterator_to_array( Db::i()->select( array( 'a_short', 'a_long', "'acronym' as `a_type`" ), 'core_acronyms', array( 'a_casesensitive=1' ) )->setKeyField( 'a_short' ) );
$this->parser->caseInsensitiveAcronyms = array();
foreach ( \IPS\Db::i()->select( array( 'a_short', 'a_long', "'acronym' as `a_type`" ), 'core_acronyms', array( 'a_casesensitive=0' ) )->setKeyField( 'a_short' ) as $k => $v )
foreach ( Db::i()->select( array( 'a_short', 'a_long', "'acronym' as `a_type`" ), 'core_acronyms', array( 'a_casesensitive=0' ) )->setKeyField( 'a_short' ) as $k => $v )
{
$this->parser->caseInsensitiveAcronyms[ mb_strtolower( $k ) ] = $v;
}
@@ -219,37 +243,37 @@ class _LegacyParser
/**
* Parse
*
* @param string $value Text to parse
* @param string $value Text to parse
* @return string
*/
public function parse( $value )
public function parse( string $value ): string
{
/* 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 />' ) ) )
if ( ! strstr( $value, "\n" ) && ( stristr( $value, '<br>' ) || stristr( $value, '<br />' ) ) )
{
$value = \str_ireplace( array( '<br>', '<br />' ), "\n", $value );
$value = str_ireplace( array( '<br>', '<br />' ), "\n", $value );
}
else
{
if ( \stristr( $value, '<br>' ) || \stristr( $value, '<br />' ) || \stristr( $value, '<p>' ) )
if ( stristr( $value, '<br>' ) || stristr( $value, '<br />' ) || stristr( $value, '<p>' ) )
{
/* Protect code tags first... */
$value = preg_replace_callback( "/\[(?:code|codebox|sql|php|xml|html).*?\](.+?)\[\/(?:code|codebox|sql|php|xml|html)\]/ims", function( $match ) use ( &$codeBlocks ) {
$hash = md5( $match[0] . mt_rand() );
$codeBlocks[ $hash ] = ( \stristr( $match[0], '<br>' ) || \stristr( $match[0], '<br />' ) || \stristr( $match[0], '<p>' ) ) ? \str_replace( "\n", "", $match[0] ) : $match[0];
$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( $match[0] . mt_rand() );
$codeBlocks[ $hash ] = ( \stristr( $match[0], '<br>' ) || \stristr( $match[0], '<br />' ) || \stristr( $match[0], '<p>' ) ) ? \str_replace( "\n", "", $match[0] ) : $match[0];
$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 = str_replace( "\n", "", $value );
}
}
@@ -267,7 +291,7 @@ class _LegacyParser
/* If we had any code blocks, put them back now */
foreach( $codeBlocks as $hash => $code )
{
$value = \str_replace( $hash, $code, $value );
$value = str_replace( $hash, $code, $value );
}
/* In case the content rebuild has ran or partially ran on this content... */
@@ -281,7 +305,7 @@ class _LegacyParser
preg_match_all( "#(<img(?:[^>]+?)class=['\"]bbc_emoticon[\"'](?:[^>]+?)alt=['\"](.+?)[\"'](?:[^>]+?)?>)#is", $value, $matches );
if( \is_array($matches[1]) AND \count($matches[1]) )
if( is_array($matches[1]) AND count($matches[1]) )
{
foreach( $matches[1] as $index => $match )
{
@@ -303,14 +327,14 @@ class _LegacyParser
{
$code = str_replace( '<', '&lt;', str_replace( '>', '&gt;', $emoticon['typed'] ) );
if( !$code or !\stristr( $value, $code ) )
if( !$code or !stristr( $value, $code ) )
{
continue;
}
if ( ! \IPS\File::isFullyQualifiedUrl( $emoticon['image'] ) )
if ( ! File::isFullyQualifiedUrl( $emoticon['image'] ) )
{
$emoticon['image'] = \IPS\File::getClass('core_Emoticons')->baseUrl() . '/' . $emoticon['image'];
$emoticon['image'] = File::getClass('core_Emoticons')->baseUrl() . '/' . $emoticon['image'];
}
$quotedCode = preg_quote( $code, '/' );
@@ -349,7 +373,7 @@ class _LegacyParser
foreach( $matches[0] as $k => $m )
{
$c = \count( $codeboxes );
$c = count( $codeboxes );
$codeboxes[ $c ] = $m;
$replacement = '<!--codeboxes{' . $c . '}-->';
@@ -361,7 +385,7 @@ class _LegacyParser
foreach( $matches[0] as $k => $m )
{
$c = \count( $codeboxes );
$c = count( $codeboxes );
$codeboxes[ $c ] = $m;
$replacement = '<!--codeboxes{' . $c . '}-->';
@@ -374,8 +398,8 @@ class _LegacyParser
$tables = array();
foreach( $matches[0] as $k => $m )
{
$c = \count( $tables );
$tables[ $c ] = \str_ireplace( '<td><br />', '<td>', $m ); // Fix up un-needed BRs added earlier in this method
$c = count( $tables );
$tables[ $c ] = str_ireplace( '<td><br />', '<td>', $m ); // Fix up un-needed BRs added earlier in this method
$replacement = '<!--tables{' . $c . '}-->';
@@ -432,7 +456,7 @@ class _LegacyParser
$value = str_replace( "[/quote]", "</div></blockquote>", $value );
/* Fix download manager embedded screenshot URLs */
$value = preg_replace_callback( "#<img src=['\"]" . preg_quote( rtrim( \IPS\Settings::i()->base_url, '/' ), '#' ) . "/index\.php\?app=downloads(?:&amp;|&)module=display(?:&amp;|&)section=screenshot(?:&amp;|&)id=(\d+?)['\"]([^>]*?)>#si", array( $this, '_fixDownloadsScreenshots' ), $value );
$value = preg_replace_callback( "#<img src=['\"]" . preg_quote( rtrim( Settings::i()->base_url, '/' ), '#' ) . "/index\.php\?app=downloads(?:&amp;|&)module=display(?:&amp;|&)section=screenshot(?:&amp;|&)id=(\d+?)['\"]([^>]*?)>#si", array( $this, '_fixDownloadsScreenshots' ), $value );
/* These were auto-replacements previously */
$value = str_ireplace( "(c)" , "&copy;" , $value );
@@ -457,7 +481,7 @@ class _LegacyParser
}
/* Convert bbcode tags, but only those our new parser doesn't handle */
foreach( $this->bbcodes as $code )
foreach( $this->parser->bbcodes as $code )
{
/* If we don't have a regex replacement, probably because we used to use a plugin, we can't do this automatically */
if( !$code['bbcode_replace'] )
@@ -520,7 +544,7 @@ class _LegacyParser
preg_match_all( "/\[attachment=(.+?):(.+?)\]/ims", $value, $matches );
if( \count( $matches[1] ) )
if( count( $matches[1] ) )
{
foreach( $matches[1] as $id )
{
@@ -535,11 +559,11 @@ class _LegacyParser
$where[] = array( 'id2=?', $this->idTwo );
}
$mappedAttachments = iterator_to_array( \IPS\Db::i()->select( '*', 'core_attachments_map', $where )->setKeyField( 'attachment_id' ) );
$mappedAttachments = iterator_to_array( Db::i()->select( '*', 'core_attachments_map', $where )->setKeyField( 'attachment_id' ) );
foreach( $mappedAttachments as $attachmentId => $map )
{
if( !\in_array( $attachmentId, $embeddedAttachments ) )
if( !in_array( $attachmentId, $embeddedAttachments ) )
{
$value .= '<p>[attachment=' . $attachmentId . ':name]</p>';
}
@@ -554,11 +578,11 @@ class _LegacyParser
case 'calendar':
try
{
$event = \IPS\Db::i()->select( '*', 'calendar_events', array( 'event_id=?', (int) $matches[3] ) )->first();
$url = \IPS\Http\Url::internal( "app=calendar&module=events&controller=event&id={$event['event_id']}", 'front', 'calendar_event', $event['event_title_seo'] );
$event = Db::i()->select( '*', 'calendar_events', array( 'event_id=?', (int) $matches[3] ) )->first();
$url = Url::internal( "app=calendar&module=events&controller=event&id={$event['event_id']}", 'front', 'calendar_event', $event['event_title_seo'] );
return "<p><a href='{$url}'>{$url}</a></p>";
}
catch( \Exception $e )
catch( Exception $e )
{
return "";
}
@@ -566,7 +590,7 @@ class _LegacyParser
case 'gallery':
/* Make sure gallery is enabled first */
if ( \IPS\Application::appIsEnabled( 'gallery' ) === FALSE )
if ( Application::appIsEnabled( 'gallery' ) === FALSE )
{
return "";
}
@@ -575,10 +599,10 @@ class _LegacyParser
{
try
{
$image = \IPS\gallery\Image::constructFromData( \IPS\Db::i()->select( '*', 'gallery_images', array( 'image_id=?', (int) $matches[3] ) )->first() );
$image = Image::constructFromData( 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 )
catch( Exception $e )
{
return "";
}
@@ -587,10 +611,10 @@ class _LegacyParser
{
try
{
$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(), TRUE, TRUE ), FALSE, $this->member );
$album = Album::constructFromData( Db::i()->select( '*', 'gallery_albums', array( 'album_id=?', (int) $matches[3] ) )->first() );
return Parser::embeddableMedia( Url::createFromString( $album->url(), TRUE, TRUE ), FALSE, $this->member );
}
catch( \Exception $e )
catch( Exception $e )
{
return "";
}
@@ -600,11 +624,11 @@ class _LegacyParser
case 'downloads':
try
{
$file = \IPS\Db::i()->select( '*', 'downloads_files', array( 'file_id=?', (int) $matches[3] ) )->first();
$url = \IPS\Http\Url::internal( "app=downloads&module=downloads&controller=view&id={$file['file_id']}", 'front', 'downloads_file', $file['file_name_furl'] );
$file = Db::i()->select( '*', 'downloads_files', array( 'file_id=?', (int) $matches[3] ) )->first();
$url = Url::internal( "app=downloads&module=downloads&controller=view&id={$file['file_id']}", 'front', 'downloads_file', $file['file_name_furl'] );
return "<p><a href='{$url}'>{$url}</a></p>";
}
catch( \Exception $e )
catch( Exception $e )
{
return "";
}
@@ -618,11 +642,11 @@ class _LegacyParser
case 'blog':
try
{
$entry = \IPS\Db::i()->select( '*', 'blog_entries', array( 'entry_id=?', (int) $matches[3] ) )->first();
$url = \IPS\Http\Url::internal( "app=blog&module=blogs&controller=entry&id={$entry['entry_id']}", 'front', 'blog_entry', $entry['entry_name_seo'] );
$entry = Db::i()->select( '*', 'blog_entries', array( 'entry_id=?', (int) $matches[3] ) )->first();
$url = Url::internal( "app=blog&module=blogs&controller=entry&id={$entry['entry_id']}", 'front', 'blog_entry', $entry['entry_name_seo'] );
return "<p><a href='{$url}'>{$url}</a></p>";
}
catch( \Exception $e )
catch( Exception $e )
{
return "";
}
@@ -742,7 +766,7 @@ class _LegacyParser
$url = preg_replace( "/#entry(\d+)/", $sep . "do=findComment&amp;comment=$1", $url );
}
$c = \count( $urls );
$c = count( $urls );
$urls[ $c ] = str_replace( $matches[1][ $k ], $url, $m );
$replacement = '<!--url{' . $c . '}-->';
@@ -750,12 +774,12 @@ class _LegacyParser
try
{
/* 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 ), FALSE, $this->member ) )
if( ! mb_strpos( $matches[1][ $k ], 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 = Parser::embeddableMedia( Url::createFromString( $matches[1][ $k ], FALSE, TRUE ), FALSE, $this->member ) )
{
$urls[ $c ] = $response;
}
}
catch( \UnexpectedValueException $e ){}
catch( UnexpectedValueException $e ){}
$value = str_replace( $m, $replacement, $value );
}
@@ -765,7 +789,7 @@ class _LegacyParser
foreach( $matches[0] as $m )
{
$c = \count( $urls );
$c = count( $urls );
$urls[ $c ] = $m;
$value = str_replace( $m, '<!--url{' . $c . '}-->', $value );
@@ -776,7 +800,7 @@ class _LegacyParser
foreach( $matches[1] as $k => $m )
{
$c = \count( $urls );
$c = count( $urls );
$urls[ $c ] = "<a href='{$m}'>{$m}</a>";
$value = str_replace( $matches[0][ $k ], '<!--url{' . $c . '}-->', $value );
@@ -809,9 +833,9 @@ class _LegacyParser
try
{
$me = \IPS\Text\Parser::embeddableMedia( \IPS\Http\Url::createFromString( $url, FALSE, TRUE ), FALSE, $this->member );
$me = Parser::embeddableMedia( Url::createFromString( $url, FALSE, TRUE ), FALSE, $this->member );
}
catch( \Exception $e ){}
catch( Exception $e ){}
}
if( $me )
@@ -819,7 +843,7 @@ class _LegacyParser
$embedCount++;
}
$c = \count( $urls );
$c = count( $urls );
$urls[ $c ] = $me ?: "<a href='{$m}'>{$m}</a>";
$value = str_replace( $matches[1][ $k ], '<!--url{' . $c . '}-->', $value );
@@ -836,14 +860,14 @@ class _LegacyParser
$value = preg_replace_callback( "/\[attachment=(.+?):(.+?)\]/ims", function( $matches ) {
try
{
$attachment = \IPS\Db::i()->select( '*', 'core_attachments', array( 'attach_id=?', (int) $matches[1] ) )->first();
$attachment = Db::i()->select( '*', 'core_attachments', array( 'attach_id=?', (int) $matches[1] ) )->first();
}
catch( \UnderflowException $e )
catch( UnderflowException $e )
{
$attachment = array( 'attach_is_image' => 0, 'attach_location' => '' );
}
$url = \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ) . "applications/core/interface/file/attachment.php?id=" . $attachment['attach_id'];
$url = Url::baseUrl( Url::PROTOCOL_RELATIVE ) . "applications/core/interface/file/attachment.php?id=" . $attachment['attach_id'];
if ( $attachment['attach_security_key'] )
{
$url .= "&key={$attachment['attach_security_key']}";
@@ -852,15 +876,15 @@ 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']}' class='ipsImage ipsImage_thumbnailed'></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='" . Settings::i()->base_url . "applications/core/interface/file/attachment.php?id={$attachment['attach_id']}' class='ipsImage ipsImage_thumbnailed'></a>";
}
elseif( \in_array( $attachment['attach_ext'], \IPS\File::$videoExtensions ) )
elseif( in_array( $attachment['attach_ext'], File::$videoExtensions ) )
{
$return = \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedVideo( $attachment['attach_location'], $url, $attachment['attach_file'], \IPS\File::getMimeType( $attachment['attach_file'] ), $attachment['attach_id'] );
$return = Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedVideo( $attachment['attach_location'], $url, $attachment['attach_file'], File::getMimeType( $attachment['attach_file'] ), $attachment['attach_id'] );
}
elseif( \in_array( $attachment['attach_ext'], \IPS\File::$audioExtensions ) )
elseif( in_array( $attachment['attach_ext'], File::$audioExtensions ) )
{
$return = \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedAudio( $attachment['attach_location'], $url, $attachment['attach_file'], \IPS\File::getMimeType( $attachment['attach_file'] ), $attachment['attach_id'] );
$return = Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedAudio( $attachment['attach_location'], $url, $attachment['attach_file'], File::getMimeType( $attachment['attach_file'] ), $attachment['attach_id'] );
}
else
{
@@ -989,13 +1013,13 @@ class _LegacyParser
/* Return */
if ( mb_stristr( $value, '<fileStore.' ) )
{
\IPS\Output::i()->parseFileObjectUrls( $value );
Output::i()->parseFileObjectUrls( $value );
}
$result = $this->parser->parse( $value );
/* If they are not using UTF8MB4, we need to convert emoji back to html entities as HTMLPurifier will have converted them on us */
if ( \IPS\Settings::i()->getFromConfGlobal('sql_utf8mb4') !== TRUE )
if ( Settings::i()->getFromConfGlobal('sql_utf8mb4') !== TRUE )
{
$result = preg_replace_callback( '/[\x{10000}-\x{10FFFF}]/u', function( $mb4Character ) {
return mb_convert_encoding( $mb4Character[0], 'HTML-ENTITIES', 'UTF-8' );
@@ -1011,10 +1035,10 @@ class _LegacyParser
/**
* Parse new quotes
*
* @param array $matches Data from preg_replace callback
* @param array $matches Data from preg_replace callback
* @return string Converted text
*/
protected function _parseOldQuote( $matches=array() )
protected function _parseOldQuote( array $matches=array() ): string
{
if ( !$matches[1] )
{
@@ -1033,7 +1057,7 @@ class _LegacyParser
if ( $match[1] )
{
$return[] = " post='" . \intval($match[1]) . "'";
$return[] = " post='" . intval($match[1]) . "'";
}
if ( $match[2] )
@@ -1048,14 +1072,14 @@ class _LegacyParser
/**
* Parse new quotes #2
*
* @param array $matches Data from preg_replace callback
* @param array $matches Data from preg_replace callback
* @return string Converted text
*/
protected function _parseOldBlockquote( $matches=array() )
protected function _parseOldBlockquote( array $matches=array() ): string
{
$parameters = array( 'data-ipsQuote' => '', 'class' => 'ipsQuote' );
if( \count( $matches ) )
if( count( $matches ) )
{
preg_match( "/data-author=['\"](.+?)[\"']/i", static::_cleanQuoteName( $matches[1] ), $author );
preg_match( "/data-cid=['\"](.+?)[\"']/i", $matches[1], $cid );
@@ -1101,14 +1125,14 @@ class _LegacyParser
/**
* Parse new quotes #3
*
* @param array $matches Data from preg_replace callback
* @param array $matches Data from preg_replace callback
* @return string Converted text
*/
protected function _parseOldQuoteBbcode( $matches=array() )
protected function _parseOldQuoteBbcode( array $matches=array() ): string
{
$parameters = array( 'data-ipsQuote' => '', 'class' => 'ipsQuote' );
if( \count( $matches ) )
if( count( $matches ) )
{
preg_match( "/name=['\"](.+?)[\"']/i", $matches[1], $author );
preg_match( "/post=['\"](.+?)[\"']/i", $matches[1], $cid );
@@ -1162,10 +1186,10 @@ class _LegacyParser
/**
* Convert flash HTML back into BBCode
*
* @param array $matches Data from preg_replace callback
* @param array $matches Data from preg_replace callback
* @return string Converted text
*/
protected function _parseOldFlash( $matches=array() )
protected function _parseOldFlash( array $matches=array() ): string
{
$f_arr = explode( "+", $matches[1] );
@@ -1175,10 +1199,10 @@ class _LegacyParser
/**
* Convert old code tags back into bbcode
*
* @param array $matches Data from preg_replace callback
* @param array $matches Data from preg_replace callback
* @return string Converted text
*/
protected function _parseOldCode( $matches=array() )
protected function _parseOldCode( array $matches=array() ): string
{
return '[code]' . rtrim( str_replace( "</span>", '', preg_replace( "#<span style='.+?'>#is", "", stripslashes( $matches[2] ) ) ) ) . '[/code]';
}
@@ -1186,20 +1210,20 @@ class _LegacyParser
/**
* Convert download manager screenshot URLs
*
* @param array $matches Data from preg_replace callback
* @param array $matches Data from preg_replace callback
* @return string Converted text
*/
protected function _fixDownloadsScreenshots( $matches )
protected function _fixDownloadsScreenshots( array $matches ): string
{
if( isset( $matches[1] ) )
{
try
{
$screenshot = \IPS\Db::i()->select( 'record_location, record_realname', 'downloads_files_records', array( "record_file_id=? and record_type IN('sslink','ssupload')", (int) $matches[1] ), 'record_id ASC', array( 0, 1 ) )->first();
$screenshot = Db::i()->select( 'record_location, record_realname', 'downloads_files_records', array( "record_file_id=? and record_type IN('sslink','ssupload')", (int) $matches[1] ), 'record_id ASC', array( 0, 1 ) )->first();
return "<img src='{$screenshot['record_location']}' alt='{$screenshot['record_realname']}'>";
}
catch( \Exception $e )
catch( Exception $e )
{
return $matches[0];
}
@@ -1216,7 +1240,7 @@ class _LegacyParser
* @param string $name Name
* @return string Converted name
*/
protected static function _cleanQuoteName( $name )
protected static function _cleanQuoteName( string $name ): string
{
$name = str_replace( "'", '&#39;', $name );
$name = str_replace( '$', '&#36;', $name );
+1082 -1986
View File
File diff suppressed because it is too large. Load diff