Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

+170 -10
View File
@@ -28,6 +28,11 @@ class _Parser
*/
const EMOJI_REGEX = '\x{1F468}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F468}|\x{1F469}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F468}|\x{1F469}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F469}|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{1F91D}\x{200D}\x{1F9D1}|\x{1F3F4}\x{E0067}\x{E0062}\x{E0065}\x{E006E}\x{E0067}\x{E007F}|\x{1F3F4}\x{E0067}\x{E0062}\x{E0077}\x{E006C}\x{E0073}\x{E007F}|\x{1F3F4}\x{E0067}\x{E0062}\x{E0073}\x{E0063}\x{E0074}\x{E007F}|\x{1F468}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F467}\x{200D}\x{1F467}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F467}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F467}|\x{1F468}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F467}|\x{1F3CA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2696}\x{FE0F}|\x{1F64D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F64B}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64B}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F647}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F647}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F646}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F646}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2695}\x{FE0F}|\x{1F645}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F645}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9B9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3C3}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3C3}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F64E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2708}\x{FE0F}|\x{1F9D6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9D7}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D7}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9D8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F575}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F575}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9B9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F93D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F93D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F93E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F93E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9B8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6A3}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9CD}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9CF}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3CC}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CC}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F939}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F938}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F938}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9CE}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F937}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F937}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9CE}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F935}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F935}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9CF}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6A3}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F926}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F926}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F939}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9CD}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6B6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2695}\x{FE0F}|\x{1F6B5}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3C4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3C4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6B5}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6B4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9B8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2696}\x{FE0F}|\x{1F9DB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9DA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F469}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2708}\x{FE0F}|\x{1F469}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2696Line truncated
/**
* @brief Regex for detecting email addresses
*/
const EMAIL_REGEX = '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,9}';
/* !Parser: Bootstrap */
/**
@@ -89,6 +94,11 @@ class _Parser
* @brief If cleaning HTML, the HTMLPurifier object
*/
protected $htmlPurifier = NULL;
/**
* @brief Save on queries and fetch the alt label would just the once
*/
protected $_altLabelWord = NULL;
/**
* Constructor
@@ -454,7 +464,7 @@ class _Parser
$element->removeAttribute( 'data-video-embed' );
}
}
/* Swap data-video-src for src */
$contentVideos = $source->getElementsByTagName( 'source' );
foreach( $contentVideos as $element )
@@ -496,7 +506,7 @@ class _Parser
public static function replaceFileStoreTags( $value )
{
/* Some tags have multiple __base_url__ replacements, so we have to replace this in a safe way ensuring we only match inside A, IMG, IFRAME and VIDEO tags to prevent tampering */
preg_match_all( '#<(img|a|iframe|video|source)([^>]+?)%7B___base_url___%7D([^>]+?)>#i', $value, $matches, PREG_SET_ORDER );
preg_match_all( '#<(img|a|iframe|video|audio|source)([^>]+?)%7B___base_url___%7D([^>]+?)>#i', $value, $matches, PREG_SET_ORDER );
foreach( $matches as $val )
{
$changed = $val[0];
@@ -512,7 +522,7 @@ class _Parser
}
}
$changed = preg_replace( '#(href|src|data\-src|data\-video\-src|data\-fileid|data\-ipshover\-target)=(\'|")%7B___base_url___%7D/#i', '\1=\2<___base_url___>/', $changed );
$changed = preg_replace( '#(href|src|data\-src|data\-video\-src|data\-audio\-src|data\-fileid|data\-ipshover\-target)=(\'|")%7B___base_url___%7D/#i', '\1=\2<___base_url___>/', $changed );
if ( $changed != $val[0] )
{
$value = str_replace( $val[0], $changed, $value );
@@ -520,7 +530,7 @@ class _Parser
}
/* Replace {fileStore.xxx} with <fileStore.xxx> */
$value = preg_replace( '#(srcset|href|data\-src|src|data\-video\-src)=(\'|")(%7B|\{)fileStore\.([\d\w\_]+?)(%7D|\})/#i', '\1=\2<fileStore.\4>/', $value );
$value = preg_replace( '#(srcset|href|data\-src|src|data\-video\-src|data\-video\-src)=(\'|")(%7B|\{)fileStore\.([\d\w\_]+?)(%7D|\})/#i', '\1=\2<fileStore.\4>/', $value );
/* Return */
return $value;
@@ -678,6 +688,18 @@ class _Parser
'data-video-src' => new \HTMLPurifier_AttrDef_URI( TRUE )
) );
/* Audio */
$def->addElement('audio', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
'controls' => 'Bool',
'data-controller' => new \HTMLPurifier_AttrDef_Enum( array( 'core.global.core.embeddedaudio' ) ),
'src' => new \HTMLPurifier_AttrDef_URI( TRUE ),
'srcset' => new HtmlPurifierSrcsetDef( TRUE ),
'media' => 'Text',
'type' => 'Text',
'data-audio-embed' => 'Text',
'data-audio-src' => new \HTMLPurifier_AttrDef_URI( TRUE )
));
/* Picture tag - we don't use it, but RSS imports might */
$def->addElement( 'picture', 'Inline', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array() );
}
@@ -764,16 +786,33 @@ class _Parser
site.com/index.php?app=forums&module=forums&controller=topic&id=1&do=embed
Not Allowed: Anything which goes to anything in an /interface directory - e.g.:
site.com/core/interface/file/attachment.php - this would automatically cause files to be downloaded
Not Allowed: Any file traversal to expose FileSystem directories, e.g:
site.com/core/../uploads/monthly_xx_xx/file.js?
Not Allowed: URLs to the open proxy:
site.com/index.php?app=core&module=system&controller=redirect
*/
$interfaces = array();
$notAllowed = array();
foreach( \IPS\Application::enabledApplications() as $app )
{
$interfaces[] = str_replace( '/', '(?:/{1,})', '(?:/{0,})' . preg_quote( $app->directory . '/interface/', '%' ) );
$notAllowed[] = str_replace( '/', '(?:/{1,})', '(?:/{0,})' . preg_quote( $app->directory . '/interface/', '%' ) );
}
$return[] = '(https?:)?//' . preg_quote( str_replace( array( 'http://', 'https://' ), '', \IPS\Settings::i()->base_url ), '%' ) . '\/?(\?|index\.php\?|(?!' . implode( '|', $interfaces ) . ').+?\?)((?!(controller|section)=redirect).)*$';
$return[] = preg_quote( '%7B___base_url___%7D', '%' ) . '\/?(\?|index\.php\?|(?!' . implode( '|', $interfaces ) . ').+?\?)((?!(controller|section)=redirect).)*$';
foreach( \IPS\File::getStore() as $configuration )
{
if ( $configuration['method'] == 'FileSystem' and ! empty( $configuration['configuration'] ) and $config = json_decode( $configuration['configuration'], TRUE ) )
{
if ( !empty( $config['dir'] ) and mb_strpos( $config['dir'], '{root}' ) !== false )
{
if ( $path = trim( str_replace( '{root}', '', $config['dir'] ), '\/' ) )
{
$notAllowed[] = '(?:.+?/{1,})\.{1,}(?:/{1,})' . $path;
}
}
}
}
$return[] = '(https?:)?//' . preg_quote( str_replace( array( 'http://', 'https://' ), '', \IPS\Settings::i()->base_url ), '%' ) . '\/?(\?|index\.php\?|(?!' . implode( '|', $notAllowed ) . ').+?\?)((?!(controller|section)=redirect).)*$';
$return[] = preg_quote( '%7B___base_url___%7D', '%' ) . '\/?(\?|index\.php\?|(?!' . implode( '|', $notAllowed ) . ').+?\?)((?!(controller|section)=redirect).)*$';
/* Return */
return '%^(' . implode( '|', $return ) . ')%';
@@ -792,6 +831,7 @@ class _Parser
/* Our default embed options */
$return = array_merge( $return, array(
'www.youtube.com/embed/',
'www.youtube-nocookie.com/embed/',
'player.vimeo.com/video/',
'www.hulu.com/embed.html',
'www.collegehumor.com/e/',
@@ -809,6 +849,7 @@ class _Parser
'www.screencast.com/users/',
'fast.wistia.net/embed/',
'www.screencast.com/users/',
'players.brightcove.net/',
) );
/* Extra admin-defined options */
@@ -892,6 +933,11 @@ class _Parser
/* Links (Used to replace disallowed URLs */
$return[] = 'ipsType_noLinkStyling';
/* Brightcove */
$return[] = 'ipsEmbeddedBrightcove';
$return[] = 'ipsEmbeddedBrightcove_inner';
$return[] = 'ipsEmbeddedBrightcove_frame';
/* Custom */
if( \IPS\Settings::i()->editor_allowed_classes )
{
@@ -1387,13 +1433,19 @@ class _Parser
break;
}
}
/* Emoji? */
if ( ( !$originalParent->getAttribute('class') or !\in_array( 'ipsEmoji', explode( ' ', $originalParent->getAttribute('class') ) ) ) and preg_match( '/' . static::EMOJI_REGEX . '/u', $section ) )
{
$parent = $parent->appendChild( new \DOMElement( 'span' ) );
$parent->setAttribute( 'class', 'ipsEmoji' );
}
/* Check for emails */
if ( \IPS\Settings::i()->email_filter_action == 'replace' and preg_match( '/' . static::EMAIL_REGEX . '/u', $section ) )
{
$section = preg_replace( '/' . static::EMAIL_REGEX . '/u', \IPS\Settings::i()->email_filter_replace_text, $section );
}
/* Insert the text */
$this->_insertNodeApplyingInlineBBcode( new \DOMText( $section ), $parent );
@@ -1699,6 +1751,10 @@ class _Parser
$element = $this->_parseVideoElement( $element );
break;
case 'audio':
$element = $this->_parseAudioElement( $element, $originalElement );
break;
case 'source':
$element = $this->_parseSourceElement( $element, $originalElement );
break;
@@ -1763,6 +1819,19 @@ class _Parser
$element->setAttribute( 'data-fileid', $attachment['attach_id'] );
$element->setAttribute( 'href', str_replace( array( 'http:', 'https:' ), '', str_replace( static::$fileObjectClasses['core_Attachment']->baseUrl(), '{fileStore.core_Attachment}', $element->getAttribute('href') ) ) );
$element->setAttribute( 'data-fileext', $attachment['attach_ext'] );
if ( ! empty( $attachment['attach_labels'] ) and $labels = static::getAttachmentLabels( $attachment ) )
{
if ( \count( $labels ) )
{
if ( $this->_altLabelWord === NULL )
{
$this->_altLabelWord = \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'alt_label_could_be' );
}
$element->setAttribute( 'alt', $this->_altLabelWord . ' ' . implode( ', ', $labels ) );
}
}
$this->_logAttachment( $attachment );
}
@@ -1860,6 +1929,20 @@ class _Parser
{
$element->setAttribute( 'data-fileid', $attachment['attach_id'] );
$element->setAttribute( 'src', str_replace( array( 'http:', 'https:' ), '', str_replace( static::$fileObjectClasses['core_Attachment']->baseUrl(), '{fileStore.core_Attachment}', $element->getAttribute( $srcAttribute ) ) ) );
if ( ! empty( $attachment['attach_labels'] ) and $labels = static::getAttachmentLabels( $attachment ) )
{
if ( \count( $labels ) )
{
if ( $this->_altLabelWord === NULL )
{
$this->_altLabelWord = \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'alt_label_could_be' );
}
$element->setAttribute( 'alt', $this->_altLabelWord . ' ' . implode( ', ', $labels ) );
}
}
if ( !$element->getAttribute('alt') )
{
$element->setAttribute( 'alt', $attachment['attach_file'] );
@@ -1940,6 +2023,28 @@ class _Parser
return $element;
}
/**
* Parse <audio> element
*
* @param \DOMElement $element The element
* @return \DOMElement
*/
protected function _parseAudioElement( \DOMElement $element )
{
if ( \IPS\Settings::i()->lazy_load_enabled )
{
$element->removeAttribute( 'data-controller' );
$element->setAttribute( 'data-audio-embed', '' );
}
else
{
$element->setAttribute( 'data-controller', 'core.global.core.embeddedaudio' );
$element->removeAttribute( 'data-audio-embed' );
}
return $element;
}
/**
* Parse <source> element
*
@@ -3157,6 +3262,7 @@ class _Parser
'ted.com' => array( 'https://www.ted.com/services/v1/oembed', static::EMBED_VIDEO ),
'vine.co' => array( 'https://vine.co/oembed.json', static::EMBED_VIDEO ),
'dailymotion.com' => array( 'https://www.dailymotion.com/services/oembed', static::EMBED_VIDEO ),
'dai.ly' => array( 'https://www.dailymotion.com/services/oembed', static::EMBED_VIDEO ),
'coub.com' => array( 'http://coub.com/api/oembed.json' . ( \IPS\Settings::i()->max_video_width ? ( '?maxwidth=' . \IPS\Settings::i()->max_video_width ) : '' ), static::EMBED_VIDEO ),
'*.deviantart.com' => array( 'https://backend.deviantart.com/oembed', static::EMBED_IMAGE ),
'docs.com' => array( 'http://docs.com/api/oembed', static::EMBED_LINK ),
@@ -3478,6 +3584,12 @@ class _Parser
/* For Youtube, we need to be a bit hacky to pass rel=0 */
if ( $domain == 'youtube.com' or $domain == 'm.youtube.com' or $domain == 'youtu.be' )
{
/* YouTube has no way to make it return youtube-nocookie from the oEmbed endpoint, so we need to swap it manually. */
if ( mb_strpos( $response['html'], 'youtube-nocookie.com/embed' ) === FALSE )
{
$response['html'] = str_replace( 'youtube.com', 'youtube-nocookie.com', $response['html'] );
}
if ( isset( $url->queryString['rel'] ) )
{
$response['html'] = str_replace( '?feature=oembed', '?feature=oembed&rel=0', $response['html'] );
@@ -3812,6 +3924,40 @@ class _Parser
}
}
}
/* Brightcove */
$domain = $url->data['host'];
if( !$domain )
{
return null;
}
if( $domain == 'bcove.video' or $domain == 'players.brightcove.net' )
{
/* If using a shortcode we need to redirect to the full URL */
if( $domain == 'bcove.video' )
{
try
{
$response = $url->request( null, null, 0 )->get();
if( $response->httpResponseCode == 301 )
{
$url = \IPS\Http\Url::external( $response->httpHeaders['Location'] );
}
else
{
throw new \IPS\Http\Request\Exception;
}
}
catch( \IPS\Http\Request\Exception $e )
{
throw new \UnexpectedValueException( 'embed__fail_500', (float) static::EMBED_VIDEO );
}
}
return \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->brightcove( $url );
}
/* So if it's not that, just return */
return NULL;
@@ -4356,7 +4502,10 @@ class _Parser
{
$value = $content->saveHTML();
$value = preg_replace( '/<meta http-equiv(?:[^>]+?)>/i', '', preg_replace( '/^<!DOCTYPE.+?>/', '', str_replace( array( '<html>', '</html>', '<body>', '</body>', '<head>', '</head>' ), '', $value ) ) );
/* Replace file storage tags */
$value = preg_replace( '/&lt;fileStore\.([\d\w\_]+?)&gt;/i', '<fileStore.$1>', $value );
/* DOMDocument::saveHTML will encode the base_url brackets, so we need to make sure it's in the expected format. */
return str_replace( '&lt;___base_url___&gt;', '<___base_url___>', $value );
}
@@ -4467,4 +4616,15 @@ class _Parser
return TRUE;
}
/**
* Parse attachment labels and pull the required data
*
* @param array $attachment Row from core_attachments
* @return array
*/
public static function getAttachmentLabels( $attachment ): array
{
return [];
}
}