Version 4.3.0
This commit is contained in:
1 parent
96997ddd8e
commit
fe1acf984a
1349 files changed
+116159
-67711
No files matched your search
+219
-85
@@ -22,6 +22,8 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class _Parser
|
||||
{
|
||||
const EMOJI_REGEX = '[\\x{fe00}-\\x{fe0f}\\x{2712}\\x{2714}\\x{2716}\\x{271d}\\x{2721}\\x{2728}\\x{2733}\\x{2734}\\x{2744}\\x{2747}\\x{274c}\\x{274e}\\x{2753}-\\x{2755}\\x{2757}\\x{2763}\\x{2764}\\x{2795}-\\x{2797}\\x{27a1}\\x{27b0}\\x{27bf}\\x{2934}\\x{2935}\\x{2b05}-\\x{2b07}\\x{2b1b}\\x{2b1c}\\x{2b50}\\x{2b55}\\x{3030}\\x{303d}\\x{1f004}\\x{1f0cf}\\x{1f170}\\x{1f171}\\x{1f17e}\\x{1f17f}\\x{1f18e}\\x{1f191}-\\x{1f19a}\\x{1f201}\\x{1f202}\\x{1f21a}\\x{1f22f}\\x{1f232}-\\x{1f23a}\\x{1f250}\\x{1f251}\\x{1f300}-\\x{1f321}\\x{1f324}-\\x{1f393}\\x{1f396}\\x{1f397}\\x{1f399}-\\x{1f39b}\\x{1f39e}-\\x{1f3f0}\\x{1f3f3}-\\x{1f3f5}\\x{1f3f7}-\\x{1f4fd}\\x{1f4ff}-\\x{1f53d}\\x{1f549}-\\x{1f54e}\\x{1f550}-\\x{1f567}\\x{1f56f}\\x{1f570}\\x{1f573}-\\x{1f579}\\x{1f587}\\x{1f58a}-\\x{1f58d}\\x{1f590}\\x{1f595}\\x{1f596}\\x{1f5a5}\\x{1f5a8}\\x{1f5b1}\\x{1f5b2}\\x{1f5bc}\\x{1f5c2}-\\x{1f5c4}\\x{1f5d1}-\\x{1f5d3}\\x{1f5dc}-\\x{1f5de}\\x{1f5e1}\\x{1f5e3}\\x{1f5ef}\\x{1f5f3}\\x{1f5fa}-\\x{1f64f}\\x{1f680}-\\x{1f6c5}\\x{1f6cb}-\\x{1f6d0}\\x{1f6e0}-\\x{1f6e5}\\x{1f6e9}\\x{1f6eb}\\x{1f6ec}\\x{1f6f0}\\x{1f6f3}\\x{1f910}-\\x{1f918}\\x{1f980}-\\x{1f984}\\x{1f9c0}\\x{3297}\\x{3299}\\x{a9}\\x{ae}\\x{203c}\\x{2049}\\x{2122}\\x{2139}\\x{2194}-\\x{2199}\\x{21a9}\\x{21aa}\\x{231a}\\x{231b}\\x{2328}\\x{2388}\\x{23cf}\\x{23e9}-\\x{23f3}\\x{23f8}-\\x{23fa}\\x{24c2}\\x{25aa}\\x{25ab}\\x{25b6}\\x{25c0}\\x{25fb}-\\x{25fe}\\x{2600}-\\x{2604}\\x{260e}\\x{2611}\\x{2614}\\x{2615}\\x{2618}\\x{261d}\\x{2620}\\x{2622}\\x{2623}\\x{2626}\\x{262a}\\x{262e}\\x{262f}\\x{2638}-\\x{263a}\\x{2648}-\\x{2653}\\x{2660}\\x{2663}\\x{2665}\\x{2666}\\x{2668}\\x{267b}\\x{267f}\\x{2692}-\\x{2694}\\x{2696}\\x{2697}\\x{2699}\\x{269b}\\x{269c}\\x{26a0}\\x{26a1}\\x{26aa}\\x{26ab}\\x{26b0}\\x{26b1}\\x{26bd}\\x{26be}\\x{26c4}\\x{26c5}\\x{26c8}\\x{26ce}\\x{26cf}\\x{26d1}\\x{26d3}\\x{26d4}\\x{26e9}\\x{26ea}\\x{26f0}-\\x{26f5}\\x{26f7}-\\x{26fa}\\x{26fd}\\x{2702}\\x{2705}\\x{2708}-\\x{270d}\\x{270f}]|\\x{23}\\x{20e3}|\\x{2a}\\x{20e3}|\\x{30}\\x{20e3}|\\x{31}\\x{20e3}|\\x{32}\\x{20e3}|\\x{33}\\x{20e3}|\\x{34}\\x{20e3}|\\x{35}\\x{20e3}|\\x{36}\\x{20e3}|\\x{37}\\x{20e3}|\\x{38}\\x{20e3}|\\x{39}\\x{20e3}|\\x{1f1e6}[\\x{1f1e8}-\\x{1f1ec}\\x{1f1ee}\\x{1f1f1}\\x{1f1f2}\\x{1f1f4}\\x{1f1f6}-\\x{1f1fa}\\x{1f1fc}\\x{1f1fd}\\x{1f1ff}]|\\x{1f1e7}[\\x{1f1e6}\\x{1f1e7}\\x{1f1e9}-\\x{1f1ef}\\x{1f1f1}-\\x{1f1f4}\\x{1f1f6}-\\x{1f1f9}\\x{1f1fb}\\x{1f1fc}\\x{1f1fe}\\x{1f1ff}]|\\x{1f1e8}[\\x{1f1e6}\\x{1f1e8}\\x{1f1e9}\\x{1f1eb}-\\x{1f1ee}\\x{1f1f0}-\\x{1f1f5}\\x{1f1f7}\\x{1f1fa}-\\x{1f1ff}]|\\x{1f1e9}[\\x{1f1ea}\\x{1f1ec}\\x{1f1ef}\\x{1f1f0}\\x{1f1f2}\\x{1f1f4}\\x{1f1ff}]|\\x{1f1ea}[\\x{1f1e6}\\x{1f1e8}\\x{1f1ea}\\x{1f1ec}\\x{1f1ed}\\x{1f1f7}-\\x{1f1fa}]|\\x{1f1eb}[\\x{1f1ee}-\\x{1f1f0}\\x{1f1f2}\\x{1f1f4}\\x{1f1f7}]|\\x{1f1ec}[\\x{1f1e6}\\x{1f1e7}\\x{1f1e9}-\\x{1f1ee}\\x{1f1f1}-\\x{1f1f3}\\x{1f1f5}-\\x{1f1fa}\\x{1f1fc}\\x{1f1fe}]|\\x{1f1ed}[\\x{1f1f0}\\x{1f1f2}\\x{1f1f3}\\x{1f1f7}\\x{1f1f9}\\x{1f1fa}]|\\x{1f1ee}[\\x{1f1e8}-\\x{1f1ea}\\x{1f1f1}-\\x{1f1f4}\\x{1f1f6}-\\x{1f1f9}]|\\x{1f1ef}[\\x{1f1ea}\\x{1f1f2}\\x{1f1f4}\\x{1f1f5}]|\\x{1f1f0}[\\x{1f1ea}\\x{1f1ec}-\\x{1f1ee}\\x{1f1f2}\\x{1f1f3}\\x{1f1f5}\\x{1f1f7}\\x{1f1fc}\\x{1f1fe}\\x{1f1ff}]|\\x{1f1f1}[\\x{1f1e6}-\\x{1f1e8}\\x{1f1ee}\\x{1f1f0}\\x{1f1f7}-\\x{1f1fb}\\x{1f1fe}]|\\x{1f1f2}[\\x{1f1e6}\\x{1f1e8}-\\x{1f1ed}\\x{1f1f0}-\\x{1f1ff}]|\\x{1f1f3}[\\x{1f1e6}\\x{1f1e8}\\x{1f1ea}-\\x{1f1ec}\\x{1f1ee}\\x{1f1f1}\\x{1f1f4}\\x{1f1f5}\\x{1f1f7}\\x{1f1fa}\\x{1f1ff}]|\\x{1f1f4}\\x{1f1f2}|\\x{1f1f5}[\\x{1f1e6}\\x{1f1ea}-\\x{1f1ed}\\x{1f1f0}-\\x{1f1f3}\\x{1f1f7}-\\x{1f1f9}\\x{1f1fc}\\x{1f1fe}]|\\x{1f1f6}\\x{1f1e6}|\\x{1f1f7}[\\x{1f1ea}\\x{1f1f4}\\x{1f1f8}\\x{1f1fa}\\x{1f1fc}]|\\x{1f1f8}[\\x{1f1e6}-\\x{1f1ea}\\x{1f1ec}-\\x{1f1f4}\\x{1f1f7}-\\x{1f1f9}\\x{1f1fb}\\x{1f1fd}-\\x{1f1ff}]|\\x{1f1f9}[\\x{1f1e6}\\x{1f1e8}\\x{1f1e9}\\x{1f1eb}-\\x{1f1ed}\\x{1f1ef}-\\x{1f1f4}\\x{1f1f7}\\x{1f1f9}\\x{1f1fb}\\x{1f1fc}\\x{1f1ff}]|\\x{1f1fa}[\\x{1f1e6}\\x{1f1ec}\\x{1f1f2}\\x{1f1f8}\\x{1f1fe}\\x{1f1ff}]|\\x{1f1fb}[\\x{1f1e6}\\x{1f1e8}\\x{1f1ea}\\x{1f1ec}\\x{1f1ee}\\x{1f1f3}\\x{1f1fa}]|\\x{1f1fc}[\\x{1f1eb}\\x{1f1f8}]|\\x{1f1fd}\\x{1f1f0}|\\x{1f1fe}[\\x{1f1ea}\\x{1f1f9}]|\\x{1f1ff}[\\x{1f1e6}\\x{1f1f2}\\x{1f1fc}]';
|
||||
|
||||
/* !Parser: Bootstrap */
|
||||
|
||||
/**
|
||||
@@ -175,7 +177,7 @@ class _Parser
|
||||
* @return string
|
||||
*/
|
||||
public function parse( $value )
|
||||
{
|
||||
{
|
||||
/* CKEditor sometimes includes these for markers. HTMLPurifier will remove the style attribute so we need to strip them first */
|
||||
$value = str_replace( '<span style="display: none;"> </span>', '', $value );
|
||||
|
||||
@@ -223,17 +225,17 @@ class _Parser
|
||||
{
|
||||
if( $status )
|
||||
{
|
||||
\IPS\Text\Parser::_parseImageProxySrc( $element );
|
||||
static::_parseImageProxySrc( $element );
|
||||
|
||||
/* Replace srcset also */
|
||||
if ( $element->getAttribute('srcset') )
|
||||
{
|
||||
\IPS\Text\Parser::_parseImageProxySrcSet( $element );
|
||||
static::_parseImageProxySrcSet( $element );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Text\Parser::_removeImageProxy( $element );
|
||||
static::_removeImageProxy( $element );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +265,7 @@ class _Parser
|
||||
$value = preg_replace( '#<([^>]+?)(href|src)=(\'|")%7BfileStore\.([\d\w\_]+?)%7D/#i', '<\1\2=\3<fileStore.\4>/', $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)([^>]+?)%7B___base_url___%7D([^>]+?)>#i', $value, $matches, PREG_SET_ORDER );
|
||||
preg_match_all( '#<(img|a|iframe|video|source)([^>]+?)%7B___base_url___%7D([^>]+?)>#i', $value, $matches, PREG_SET_ORDER );
|
||||
foreach( $matches as $val )
|
||||
{
|
||||
$changed = $val[0];
|
||||
@@ -314,6 +316,7 @@ class _Parser
|
||||
open us to phishing and other such security issues */
|
||||
$config->set( 'HTML.SafeIframe', true );
|
||||
$config->set( 'URI.SafeIframeRegexp', static::safeIframeRegexp() );
|
||||
$config->set( 'Output.Newline', "\n" );
|
||||
|
||||
/* Set allowed CSS classes. We limit this to a whitelist because to allow any iframe would open
|
||||
us to phishing (for example, someone posts something which, by using our CSS classes, looks like a
|
||||
@@ -389,7 +392,7 @@ class _Parser
|
||||
$def->addAttribute( 'iframe', 'data-embedid', 'Text' ); // used in core/global/embed/iframe.phtml
|
||||
$def->addAttribute( 'iframe', 'data-embedcontent', 'Text' ); // used in embeddableMedia
|
||||
$def->addAttribute( 'iframe', 'allowfullscreen', 'Text' ); // Some services will specify this property
|
||||
|
||||
|
||||
/* data-controllers */
|
||||
$allowedDivDataControllers = array(
|
||||
'core.front.core.articlePages', // [page] (set by _parseContent)
|
||||
@@ -407,6 +410,16 @@ class _Parser
|
||||
/* data-munge-src used by _removeMunge() */
|
||||
$def->addAttribute( 'img', 'data-munge-src', 'Text' );
|
||||
$def->addAttribute( 'iframe', 'data-munge-src', 'Text' );
|
||||
|
||||
/* Videos */
|
||||
$def->addElement( 'video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
|
||||
'controls' => 'Bool',
|
||||
'data-controller' => new \HTMLPurifier_AttrDef_Enum( array( 'core.global.core.embeddedvideo' ) )
|
||||
) );
|
||||
$def->addElement( 'source', 'Block', 'Flow', 'Common', array(
|
||||
'src' => 'URI',
|
||||
'type' => 'Text',
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -477,6 +490,7 @@ class _Parser
|
||||
* Get URL bases (whout schema) that we'll allow iframes from
|
||||
*
|
||||
* @return array
|
||||
* @note When updating this list, be sure to update the editor_embeds_desc lang string
|
||||
*/
|
||||
protected static function allowedIFrameBases()
|
||||
{
|
||||
@@ -501,6 +515,9 @@ class _Parser
|
||||
'www.google.com/maps/',
|
||||
'www.screencast.com/users/',
|
||||
'fast.wistia.net/embed/',
|
||||
'www.screencast.com/users/',
|
||||
'clips.twitch.tv/embed',
|
||||
'player.twitch.tv/'
|
||||
) );
|
||||
|
||||
/* Extra admin-defined options */
|
||||
@@ -568,6 +585,7 @@ class _Parser
|
||||
$return[] = 'ipsAttachLink_image';
|
||||
$return[] = 'ipsAttachLink_left';
|
||||
$return[] = 'ipsAttachLink_right';
|
||||
$return[] = 'ipsEmoji';
|
||||
|
||||
/* Embeds (used by various return values of embeddedMedia) */
|
||||
$return[] = 'ipsEmbedded';
|
||||
@@ -744,7 +762,7 @@ class _Parser
|
||||
/* Element-specific parsing */
|
||||
if ( $okayToParse )
|
||||
{
|
||||
$this->_parseElement( $newElement );
|
||||
$newElement = $this->_parseElement( $newElement );
|
||||
}
|
||||
|
||||
/* Append */
|
||||
@@ -830,7 +848,7 @@ class _Parser
|
||||
|
||||
/* Init */
|
||||
$text = $textNode->wholeText;
|
||||
$breakPoints = array();
|
||||
$breakPoints = array( '([\x{00A0}-\x{1F9FF}])' );
|
||||
|
||||
/* Contains [page] tags? */
|
||||
if ( mb_strpos( $text, '[page]' ) !== FALSE )
|
||||
@@ -961,17 +979,24 @@ class _Parser
|
||||
$originalParent = $parent;
|
||||
|
||||
/* Acronym? */
|
||||
if ( array_key_exists( $section, $this->caseSensitiveAcronyms ) and !in_array( $this->caseSensitiveAcronyms[ $section ], $this->openAbbrTags ) )
|
||||
if ( $this->bbcodeParse and array_key_exists( $section, $this->caseSensitiveAcronyms ) and !in_array( $this->caseSensitiveAcronyms[ $section ], $this->openAbbrTags ) )
|
||||
{
|
||||
$parent = $parent->appendChild( new \DOMElement( 'abbr' ) );
|
||||
$parent->setAttribute( 'title', $this->caseSensitiveAcronyms[ $section ] );
|
||||
}
|
||||
elseif ( array_key_exists( mb_strtolower( $section ), $this->caseInsensitiveAcronyms ) and !in_array( $this->caseInsensitiveAcronyms[ mb_strtolower( $section ) ], $this->openAbbrTags ) )
|
||||
elseif ( $this->bbcodeParse and array_key_exists( mb_strtolower( $section ), $this->caseInsensitiveAcronyms ) and !in_array( $this->caseInsensitiveAcronyms[ mb_strtolower( $section ) ], $this->openAbbrTags ) )
|
||||
{
|
||||
$parent = $parent->appendChild( new \DOMElement( 'abbr' ) );
|
||||
$parent->setAttribute( 'title', $this->caseInsensitiveAcronyms[ mb_strtolower( $section ) ] );
|
||||
}
|
||||
|
||||
/* 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' );
|
||||
}
|
||||
|
||||
/* Insert the text */
|
||||
$this->_insertNodeApplyingInlineBBcode( new \DOMText( $section ), $parent );
|
||||
|
||||
@@ -1024,7 +1049,7 @@ class _Parser
|
||||
}
|
||||
|
||||
/* Parse it */
|
||||
$this->_parseElement( $bbcodeElement );
|
||||
$bbcodeElement = $this->_parseElement( $bbcodeElement );
|
||||
|
||||
/* Single only? */
|
||||
if ( isset( $bbcode['single'] ) and $bbcode['single'] )
|
||||
@@ -1077,7 +1102,7 @@ class _Parser
|
||||
}
|
||||
|
||||
/* Add to $openBlockBBcode for closing later */
|
||||
$id = uniqid() . '-' . $tag;
|
||||
$id = mt_rand() . '-' . $tag;
|
||||
$this->openBlockBBCodeByTag[ $tag ][ $id ] = $bbcodeElement;
|
||||
|
||||
/* Add to $penBlockBBCodeInOrder to that so _parseDomElement() will use that as the parent for subsequent elements */
|
||||
@@ -1234,7 +1259,10 @@ class _Parser
|
||||
/* If we have an open block-level BBCode element, decrease the depth we're at */
|
||||
if ( $this->openBlockDepth )
|
||||
{
|
||||
$parent = $parent->parentNode;
|
||||
if ( $this->openBlockDepth == 1 )
|
||||
{
|
||||
$parent = $parent->parentNode;
|
||||
}
|
||||
$this->openBlockDepth--;
|
||||
}
|
||||
}
|
||||
@@ -1245,7 +1273,7 @@ class _Parser
|
||||
* Element-Specific Parsing
|
||||
*
|
||||
* @param \DOMElement $element The element
|
||||
* @return void
|
||||
* @return \DOMNode|NULL
|
||||
*/
|
||||
protected function _parseElement( \DOMElement $element )
|
||||
{
|
||||
@@ -1253,15 +1281,15 @@ class _Parser
|
||||
switch ( $element->tagName )
|
||||
{
|
||||
case 'a':
|
||||
$this->_parseAElement( $element );
|
||||
$element = $this->_parseAElement( $element );
|
||||
break;
|
||||
|
||||
case 'img':
|
||||
$this->_parseImgElement( $element );
|
||||
$element = $this->_parseImgElement( $element );
|
||||
break;
|
||||
|
||||
case 'iframe':
|
||||
$this->_parseIframeElement( $element );
|
||||
$element = $this->_parseIframeElement( $element );
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1286,6 +1314,9 @@ class _Parser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1332,7 +1363,6 @@ class _Parser
|
||||
|
||||
try
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $element->getAttribute('href') );
|
||||
$rels = $this->_getRelAttributes( \IPS\Http\Url::createFromString( $element->getAttribute('href') ) );
|
||||
}
|
||||
catch( \IPS\Http\Url\Exception $e )
|
||||
@@ -1342,6 +1372,8 @@ class _Parser
|
||||
|
||||
/* Add rels */
|
||||
$element->setAttribute( 'rel', implode( ' ', $rels ) );
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1359,37 +1391,47 @@ class _Parser
|
||||
{
|
||||
/* When editing content in the AdminCP, images and iframes get the src munged. When we save, we need to put that back */
|
||||
$this->_removeMunge( $element );
|
||||
|
||||
/* Is it an emoji? */
|
||||
if ( $element->getAttribute('class') and in_array( 'ipsEmoji', explode( ' ', $element->getAttribute('class') ) ) and $element->getAttribute('alt') )
|
||||
{
|
||||
$newElement = $element->ownerDocument->importNode( new \DOMElement( 'span' ) );
|
||||
$newElement->setAttribute( 'class', 'ipsEmoji' );
|
||||
$newElement->appendChild( new \DOMText( $element->getAttribute('alt') ) );
|
||||
return $newElement;
|
||||
}
|
||||
|
||||
/* If it's not allowed, remove the src */
|
||||
if ( !static::isAllowedUrl( $element->getAttribute('src') ) )
|
||||
{
|
||||
$element->setAttribute( 'data-ipsplaintext-img', $element->getAttribute('src') );
|
||||
$element->removeAttribute('src');
|
||||
return;
|
||||
return $element;
|
||||
}
|
||||
|
||||
/* Is it an emoticon? */
|
||||
if ( $emoticon = $this->_getEmoticon( $element->getAttribute('src') ) )
|
||||
if ( $element->hasAttribute('data-emoticon') )
|
||||
{
|
||||
if ( $this->_emoticons < 75 )
|
||||
{
|
||||
$element->setAttribute( 'src', str_replace( static::$fileObjectClasses['core_Emoticons']->baseUrl(), '{fileStore.core_Emoticons}', $element->getAttribute('src') ) );
|
||||
$element->setAttribute( 'alt', $emoticon['typed'] );
|
||||
$element->setAttribute( 'data-emoticon', TRUE );
|
||||
|
||||
if( $emoticon['image_2x'] and $emoticon['width'] and $emoticon['height'] )
|
||||
if ( !isset( static::$fileObjectClasses['core_Emoticons'] ) )
|
||||
{
|
||||
$element->setAttribute( 'srcset', str_replace( static::$fileObjectClasses['core_Emoticons']->baseUrl(), '%7BfileStore.core_Emoticons%7D', $emoticon['image_2x'] ) );
|
||||
$element->setAttribute( 'width', $emoticon['width'] );
|
||||
$element->setAttribute( 'height', $emoticon['height'] );
|
||||
static::$fileObjectClasses['core_Emoticons'] = \IPS\File::getClass('core_Emoticons' );
|
||||
}
|
||||
|
||||
|
||||
$element->setAttribute( 'src', str_replace( static::$fileObjectClasses['core_Emoticons']->baseUrl(), '{fileStore.core_Emoticons}', $element->getAttribute('src') ) );
|
||||
|
||||
if ( $srcSet = $element->getAttribute('srcset') )
|
||||
{
|
||||
$element->setAttribute( 'srcset', str_replace( static::$fileObjectClasses['core_Emoticons']->baseUrl(), '%7BfileStore.core_Emoticons%7D', $srcSet ) );
|
||||
}
|
||||
|
||||
$this->_emoticons++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Set an attribute on the element - we'll need to know this later */
|
||||
$element->setAttribute( 'data-ipsEmoticon-plain', $emoticon['typed'] );
|
||||
$element->setAttribute( 'data-ipsEmoticon-plain', $element->getAttribute('title') );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1437,6 +1479,8 @@ class _Parser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1451,7 +1495,7 @@ class _Parser
|
||||
$imageSrc = str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $element->getAttribute('src') );
|
||||
|
||||
/* Check for file store tags */
|
||||
if( mb_stristr( $imageSrc, 'fileStore.') )
|
||||
if( preg_match( '#({|%7B|\<|%3C)fileStore\.#', $imageSrc ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1481,6 +1525,7 @@ class _Parser
|
||||
) );
|
||||
|
||||
$element->setAttribute( 'src', (string) $newUrl );
|
||||
$element->setAttribute( 'data-imageproxy-source', (string) $imageSrc );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1507,7 +1552,7 @@ class _Parser
|
||||
$imageSrc = str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $data[0] );
|
||||
|
||||
/* Check for file store tags */
|
||||
if( mb_stristr( $imageSrc, 'fileStore.') )
|
||||
if( preg_match( '#({|%7B|\<|%3C)fileStore\.#', $imageSrc ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1581,6 +1626,8 @@ class _Parser
|
||||
catch ( \IPS\Http\Url\Exception $e ) { }
|
||||
|
||||
$this->_removeMunge( $element );
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1595,17 +1642,32 @@ class _Parser
|
||||
|
||||
$imageSrc = $element->getAttribute( 'src' );
|
||||
|
||||
/* If it's a local placeholder, we don't need to process it. */
|
||||
if( mb_stristr( $imageSrc, 'fileStore.' ) )
|
||||
/* If it's a local placeholder, we don't need to process it.
|
||||
* - There was a minor bug in 4.2.5 that could affect some internal images,
|
||||
* this specifically only checks that the string starts with the file storage replacement
|
||||
* so that it can still fix the issue by disabling the image proxy.
|
||||
*/
|
||||
if( preg_match( '#^({|%7B|\<|%3C)fileStore\.#', $imageSrc ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
elseif( mb_stristr( $imageSrc, (string) $imageProxyUrl ) )
|
||||
|
||||
if( mb_stristr( $imageSrc, (string) $imageProxyUrl ) )
|
||||
{
|
||||
$srcUrl = \IPS\Http\Url::createFromString( str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $imageSrc ) );
|
||||
$element->setAttribute( 'src', $srcUrl->queryString['img'] );
|
||||
|
||||
/* We also need to remove image proxy from the parent A tag if it exists */
|
||||
if( $element->parentNode->tagName === 'a' AND mb_stristr( $element->parentNode->getAttribute( 'href' ), (string) $imageProxyUrl ) )
|
||||
{
|
||||
$hrefSrcUrl = \IPS\Http\Url::createFromString( str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $element->parentNode->getAttribute( 'href' ) ) );
|
||||
$element->parentNode->setAttribute( 'href', $hrefSrcUrl->queryString['img'] );
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove data attribute */
|
||||
$element->removeAttribute('data-imageproxy-source');
|
||||
|
||||
if( $element->getAttribute('srcset') )
|
||||
{
|
||||
$urls = explode( ',', $element->getAttribute('srcset') );
|
||||
@@ -1670,22 +1732,43 @@ class _Parser
|
||||
$rels[] = 'norewrite';
|
||||
}
|
||||
|
||||
/* external / nofollow */
|
||||
if ( !$url->isInternal )
|
||||
/* We add external/nofollow rel attributes for non-internal non-local links */
|
||||
if ( !$url->isInternal and !$url->isLocalhost() )
|
||||
{
|
||||
$rels[] = 'external';
|
||||
|
||||
/* Do we also want to add nofollow? */
|
||||
if( \IPS\Settings::i()->posts_add_nofollow and ( !\IPS\Settings::i()->posts_add_nofollow_exclude or ( isset( $url->data['host'] ) and !in_array( preg_replace( '/^www\./', '', $url->data['host'] ), array_map( function( $val ) {
|
||||
return preg_replace( '/^www\./', '', $val );
|
||||
}, json_decode( \IPS\Settings::i()->posts_add_nofollow_exclude ) ) ) ) ) )
|
||||
if( \IPS\Settings::i()->posts_add_nofollow )
|
||||
{
|
||||
$rels[] = 'nofollow';
|
||||
/* If we aren't excluding any domains, then add it */
|
||||
if( !\IPS\Settings::i()->posts_add_nofollow_exclude )
|
||||
{
|
||||
$rels[] = 'nofollow';
|
||||
}
|
||||
else
|
||||
{
|
||||
/* HTML Purifier converts IDN to punycode, so we need to do the same with our 'follow' domains */
|
||||
if ( !function_exists('idn_to_ascii') )
|
||||
{
|
||||
\IPS\IPS::$PSR0Namespaces['TrueBV'] = \IPS\ROOT_PATH . "/system/3rd_party/php-punycode";
|
||||
require_once \IPS\ROOT_PATH . "/system/3rd_party/php-punycode/polyfill.php";
|
||||
}
|
||||
|
||||
$follow = array_map( function( $val ) {
|
||||
return idn_to_ascii( preg_replace( '/^www\./', '', $val ) );
|
||||
}, json_decode( \IPS\Settings::i()->posts_add_nofollow_exclude ) );
|
||||
|
||||
if( isset( $url->data['host'] ) AND !in_array( preg_replace( '/^www\./', '', $url->data['host'] ), $follow ) )
|
||||
{
|
||||
$rels[] = 'nofollow';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is allowed URL
|
||||
@@ -1767,34 +1850,6 @@ class _Parser
|
||||
*/
|
||||
protected static $fileObjectClasses = array();
|
||||
|
||||
/**
|
||||
* Get emoticon data from URL
|
||||
*
|
||||
* @param string $url The URL
|
||||
* @return array|NULL
|
||||
*/
|
||||
protected function _getEmoticon( $url )
|
||||
{
|
||||
if ( !isset( static::$fileObjectClasses['core_Emoticons'] ) )
|
||||
{
|
||||
static::$fileObjectClasses['core_Emoticons'] = \IPS\File::getClass('core_Emoticons' );
|
||||
}
|
||||
|
||||
if ( preg_match( '#^(' . preg_quote( rtrim( static::$fileObjectClasses['core_Emoticons']->baseUrl(), '/' ), '#' ) . ')/(.+?)$#', $url, $matches ) )
|
||||
{
|
||||
foreach ( \IPS\Helpers\Form\Editor::getEmoticons() as $key => $emoticon )
|
||||
{
|
||||
if ( $emoticon['image'] == $url )
|
||||
{
|
||||
$emoticon['typed'] = $key;
|
||||
return $emoticon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment data from URL
|
||||
*
|
||||
@@ -1960,7 +2015,7 @@ class _Parser
|
||||
$code = array( 'tag' => 'pre', 'attributes' => array( 'class' => 'ipsCode' ), 'block' => TRUE, 'noParse' => TRUE, 'noChildren' => TRUE, 'allowOption' => TRUE, 'finishedCallback' => function( \DOMElement $originalNode ) {
|
||||
|
||||
/* Parse breaks - with BBCode we'll be getting things like [code]line1<br>line2[/code] so we need to make sure they're formatted properly */
|
||||
$contents = \substr( \IPS\Text\DOMParser::parse(
|
||||
$contents = \substr( trim( \IPS\Text\DOMParser::parse(
|
||||
$originalNode->ownerDocument->saveHtml( $originalNode ),
|
||||
/* DOMElement Parse */
|
||||
function ( \DOMElement $element, \DOMNode $parent, \IPS\Text\DOMParser $parser )
|
||||
@@ -2005,7 +2060,7 @@ class _Parser
|
||||
/* Insert */
|
||||
$parent->appendChild( new \DOMText( $text ) );
|
||||
}
|
||||
), 21, -6 );
|
||||
) ), 21, -6 );
|
||||
|
||||
/* Create a new <pre> with those contents */
|
||||
$return = $originalNode->ownerDocument->createElement( 'pre' );
|
||||
@@ -2568,17 +2623,15 @@ class _Parser
|
||||
),
|
||||
'gfycat.com' => array( 'https://api.gfycat.com/v1/oembed', static::EMBED_IMAGE ),
|
||||
'dailymotion.com' => array( 'https://www.dailymotion.com/services/oembed', static::EMBED_VIDEO ),
|
||||
'codepen.io' => array( 'http://codepen.io/api/oembed', static::EMBED_LINK ),
|
||||
'coub.com' => array( 'http://coub.com/api/oembed.json', 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 ),
|
||||
'funnyordie.com' => array( 'http://www.funnyordie.com/oembed.json', static::EMBED_VIDEO ),
|
||||
'gettyimages.com' => array( 'http://embed.gettyimages.com/oembed', static::EMBED_IMAGE ),
|
||||
'ifixit.com' => array( 'http://www.ifixit.com/Embed', static::EMBED_LINK ),
|
||||
'imgur.com' => array( 'http://api.imgur.com/oembed', static::EMBED_IMAGE ),
|
||||
'kickstarter.com' => array( 'http://www.kickstarter.com/services/oembed', static::EMBED_LINK ),
|
||||
'meetup.com' => array( 'https://api.meetup.com/oembed', static::EMBED_LINK ),
|
||||
'mixcloud.com' => array( 'http://www.mixcloud.com/oembed', static::EMBED_VIDEO ),
|
||||
'mixcloud.com' => array( 'https://www.mixcloud.com/oembed/', static::EMBED_VIDEO ),
|
||||
'mix.office.com' => array( 'https://mix.office.com/oembed', static::EMBED_VIDEO ),
|
||||
'reddit.com' => array( 'http://www.reddit.com/oembed', static::EMBED_LINK ),
|
||||
'reverbnation.com' => array( 'https://www.reverbnation.com/oembed', static::EMBED_VIDEO ),
|
||||
@@ -2588,6 +2641,7 @@ class _Parser
|
||||
'ustream.tv' => array( 'https://www.ustream.tv/oembed', static::EMBED_VIDEO ),
|
||||
'*.wistia.com' => array( 'http://fast.wistia.com/oembed', static::EMBED_VIDEO ),
|
||||
'*.wi.st' => array( 'http://fast.wistia.com/oembed', static::EMBED_VIDEO ),
|
||||
'clips.twitch.tv' => array( 'https://api.twitch.tv/v5/oembed', static::EMBED_VIDEO),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2736,16 +2790,16 @@ class _Parser
|
||||
switch( $response->httpResponseCode )
|
||||
{
|
||||
case '404':
|
||||
throw new \UnexpectedValueException( 'embed__fail_404', $endtype );
|
||||
throw new \UnexpectedValueException( 'embed__fail_404', (float) $endtype );
|
||||
break;
|
||||
|
||||
case '401':
|
||||
case '403':
|
||||
throw new \UnexpectedValueException( 'embed__fail_403', $endtype );
|
||||
throw new \UnexpectedValueException( 'embed__fail_403', (float) $endtype );
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \UnexpectedValueException( 'embed__fail_500', $endtype );
|
||||
throw new \UnexpectedValueException( 'embed__fail_500', (float) $endtype );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2755,7 +2809,7 @@ class _Parser
|
||||
/* If it error'd (connection error or unexpected response), we'll not embed this */
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \UnexpectedValueException( 'embed__fail_500', $endtype );
|
||||
throw new \UnexpectedValueException( 'embed__fail_500', (float) $endtype );
|
||||
}
|
||||
|
||||
/* Gfycat and Facebook videos report as video but they're not HTML5 videos with the aspect ratio we need, so treat it as rich */
|
||||
@@ -2777,6 +2831,11 @@ class _Parser
|
||||
{
|
||||
$response['html'] = str_replace( '?feature=oembed', '?feature=oembed&rel=0', $response['html'] );
|
||||
}
|
||||
|
||||
if ( isset( $url->queryString['start'] ) )
|
||||
{
|
||||
$response['html'] = str_replace( '?feature=oembed', '?feature=oembed&start=' . intval( $url->queryString['start'] ), $response['html'] );
|
||||
}
|
||||
}
|
||||
|
||||
/* We need a type otherwise we can't embed */
|
||||
@@ -2805,7 +2864,7 @@ class _Parser
|
||||
}
|
||||
else
|
||||
{
|
||||
$embedId = md5( uniqid() );
|
||||
$embedId = md5( mt_rand() );
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->iframe( (string) \IPS\Http\Url::internal( 'app=core&module=system&controller=embed', 'front' )->setQueryString( 'url', (string) $url ), NULL, NULL, $embedId );
|
||||
}
|
||||
@@ -2838,7 +2897,7 @@ class _Parser
|
||||
}
|
||||
else
|
||||
{
|
||||
$embedId = md5( uniqid() );
|
||||
$embedId = md5( mt_rand() );
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->iframe( (string) \IPS\Http\Url::internal( 'app=core&module=system&controller=embed', 'front' )->setQueryString( 'url', (string) $url ), NULL, NULL, $embedId );
|
||||
}
|
||||
@@ -3062,7 +3121,40 @@ class _Parser
|
||||
break;
|
||||
|
||||
case 'dir':
|
||||
$params = array( 'origin' => $qbits[1], 'destination' => $qbits[2] );
|
||||
/* Let's do some cleanup - we may have 'waypoints' in between the origin and destination, but Google doesn't tell us that from the URL */
|
||||
$route = array();
|
||||
foreach( $qbits AS $bit )
|
||||
{
|
||||
/* Skip these as we know for sure that they are not a part of the route. */
|
||||
if (
|
||||
$bit === 'dir' # This is safe because we already know we're requesting directions
|
||||
OR mb_substr( $bit, 0, 1 ) === '@' # This is safe because Google only looks for three value types to find a place in a route (Name, Address, or PlaceID) - Lat/Long is not one of them
|
||||
OR mb_substr( $bit, 0, 5 ) === 'data=' # This is simply miscellaneous data which we do not need
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$route[] = $bit;
|
||||
}
|
||||
|
||||
/* Assign our origin, which will always be at the start */
|
||||
$origin = array_shift( $route );
|
||||
|
||||
/* And our destination, which will always be at the end */
|
||||
$destination = array_pop( $route );
|
||||
|
||||
/* If we have anything left, then they are waypoints which need to be shown between the origin and destination - do that */
|
||||
if ( count( $route ) )
|
||||
{
|
||||
$params = array( 'origin' => $origin, 'waypoints' => implode( '|', $route ), 'destination' => $destination );
|
||||
}
|
||||
else
|
||||
/* Otherwise, just pass the start and end */
|
||||
{
|
||||
$params = array( 'origin' => $origin, 'destination' => $destination );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->googleMaps( $params, 'dir' );
|
||||
break;
|
||||
|
||||
@@ -3079,6 +3171,24 @@ class _Parser
|
||||
}
|
||||
}
|
||||
|
||||
/* Twitch TV Live Streams*/
|
||||
if ( preg_match( '/^https:\/\/www\.?twitch\.tv\/(.+)/i', (string) $url, $matches ) )
|
||||
{
|
||||
$qbits = explode( "/", $matches[1] );
|
||||
|
||||
/* Work out if we are embedding a collection, video or channel */
|
||||
if( isset( $qbits[1] ) )
|
||||
{
|
||||
$type = ( $qbits[0] == 'collections' ) ? 'collection' : 'video';
|
||||
return \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->twitch( $type, $qbits[1] );
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->twitch( 'channel', $qbits[0] );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* So if it's not that, just return */
|
||||
return NULL;
|
||||
}
|
||||
@@ -3341,17 +3451,18 @@ class _Parser
|
||||
/**
|
||||
* Munge resources in ACP
|
||||
*
|
||||
* @param string $value The value to parse
|
||||
* @param string $value The value to parse
|
||||
* @param bool $makeLinksAcpSafe If TRUE, will also make links safe for ACP
|
||||
* @return string
|
||||
*/
|
||||
public static function mungeResources( $value )
|
||||
public static function mungeResources( $value, $makeLinksAcpSafe=FALSE )
|
||||
{
|
||||
if ( !$value )
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
return DOMParser::parse( $value, function( \DOMElement $element, \DOMNode $parent, \IPS\Text\DOMParser $parser )
|
||||
return DOMParser::parse( $value, function( \DOMElement $element, \DOMNode $parent, \IPS\Text\DOMParser $parser ) use ( $makeLinksAcpSafe )
|
||||
{
|
||||
/* Munge */
|
||||
if ( $element->tagName === 'img' OR $element->tagName === 'iframe' )
|
||||
@@ -3369,6 +3480,14 @@ class _Parser
|
||||
}
|
||||
}
|
||||
|
||||
if ( $makeLinksAcpSafe and $element->tagName == 'a' )
|
||||
{
|
||||
$element->setAttribute( 'href', (string) \IPS\Http\Url::internal( "app=core&module=system&controller=redirect", 'front' )->setQueryString( array(
|
||||
'url' => $element->getAttribute('href'),
|
||||
'key' => hash_hmac( "sha256", $element->getAttribute('href'), \IPS\Settings::i()->site_secret_key ),
|
||||
) ) );
|
||||
}
|
||||
|
||||
/* Import it */
|
||||
$ownerDocument = $parent->ownerDocument ?: $parent;
|
||||
$newElement = $ownerDocument->importNode( $element );
|
||||
@@ -3425,7 +3544,22 @@ class _Parser
|
||||
$image->setAttribute( 'src', '{fileStore.core_Attachment}/' . ( $attachment['attach_thumb_location'] ? $attachment['attach_thumb_location'] : $attachment['attach_location'] ) );
|
||||
|
||||
$anchor = $image->parentNode;
|
||||
$anchor->setAttribute( 'href', '{fileStore.core_Attachment}/' . $attachment['attach_location'] );
|
||||
|
||||
/* Make sure it's actually an anchor */
|
||||
if ( $anchor->tagName !== 'a' )
|
||||
{
|
||||
/* It's not, so create one and add the image to it */
|
||||
$parent = $image->parentNode;
|
||||
$anchor = $content->createElement( 'a' );
|
||||
$anchor->setAttribute( 'href', '{fileStore.core_Attachment}/' . $attachment['attach_location'] );
|
||||
$anchor->appendChild( $image );
|
||||
$parent->appendChild( $anchor );
|
||||
$parent->removeChild( $image );
|
||||
}
|
||||
else
|
||||
{
|
||||
$anchor->setAttribute( 'href', '{fileStore.core_Attachment}/' . $attachment['attach_location'] );
|
||||
}
|
||||
|
||||
$rebuilt = TRUE;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user