Version 4.6.0
This commit is contained in:
1 parent
f79dcf067a
commit
517a5e1f70
2036 files changed
+110041
-26162
No files matched your search
@@ -35,7 +35,7 @@ class _Encrypt
|
||||
/**
|
||||
* @brief Cipher
|
||||
*/
|
||||
protected $cipher;
|
||||
public $cipher;
|
||||
|
||||
/**
|
||||
* @brief IV
|
||||
|
||||
@@ -33,8 +33,8 @@ class _HtmlPurifierHttpsImages extends \HTMLPurifier_URIFilter
|
||||
|
||||
public function filter( &$uri, $config, $context )
|
||||
{
|
||||
/* We only care about embedded resources for this */
|
||||
if ( !$context->get( 'EmbeddedURI', true ) )
|
||||
/* 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' )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
|
||||
if ( $this->allowedBases )
|
||||
{
|
||||
$isOkay = FALSE;
|
||||
$queryString = $url->isFriendly ? ( $url->hiddenQueryString + $url->queryString ) : $url->queryString;
|
||||
$queryString = ( $url instanceof \IPS\Http\Url\Friendly ) ? ( $url->hiddenQueryString + $url->queryString ) : $url->queryString;
|
||||
|
||||
foreach ( $this->allowedBases as $requiredQueryString )
|
||||
{
|
||||
@@ -126,7 +126,7 @@ class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
|
||||
|
||||
static::$uploadUrls = array();
|
||||
|
||||
foreach( \IPS\File::getConfigurations() as $configuration )
|
||||
foreach( \IPS\File::getStore() as $configuration )
|
||||
{
|
||||
$class = \IPS\File::getClass( $configuration['id'] );
|
||||
|
||||
@@ -134,7 +134,7 @@ class _HtmlPurifierInternalLinkDef extends \HTMLPurifier_AttrDef_URI
|
||||
{
|
||||
$url = new \IPS\Http\Url( $class->baseUrl() );
|
||||
|
||||
if( $url->isInternal )
|
||||
if( $url instanceof \IPS\Http\Url\Internal )
|
||||
{
|
||||
static::$uploadUrls[ $class->baseUrl() ] = $class->baseUrl();
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class _LegacyParser
|
||||
/**
|
||||
* @brief Parser object
|
||||
*/
|
||||
protected $parser = NULL;
|
||||
public $parser = NULL;
|
||||
|
||||
/**
|
||||
* @brief Key for attachments
|
||||
@@ -126,6 +126,11 @@ class _LegacyParser
|
||||
*/
|
||||
protected $idThree = 0;
|
||||
|
||||
/**
|
||||
* @brief Are Acronyms Supported?
|
||||
*/
|
||||
protected static $_newAcronyms = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -176,14 +181,17 @@ 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 */
|
||||
$newAcronyms = \IPS\Db::i()->checkForColumn( 'core_acronyms', 'a_type' );
|
||||
if( static::$_newAcronyms === NULL )
|
||||
{
|
||||
static::$_newAcronyms = \IPS\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, $newAcronyms );
|
||||
$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 );
|
||||
|
||||
/* If we're in the upgrader and the new acronym support hasn't been added yet, populate acronyms manually */
|
||||
if( !$newAcronyms )
|
||||
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' ) );
|
||||
|
||||
|
||||
+337
-141
@@ -26,7 +26,7 @@ class _Parser
|
||||
* @brief Regex for detecting an Emoji character
|
||||
* @note This string is automatically generated by the Emoji Data Builder. Do not modify it manually.
|
||||
*/
|
||||
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{E0073}\x{E0063}\x{E0074}\x{E007F}|\x{1F3F4}\x{E0067}\x{E0062}\x{E0077}\x{E006C}\x{E0073}\x{E007F}|\x{1F469}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F466}|\x{1F468}\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{1F469}\x{200D}\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F469}\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{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F467}\x{200D}\x{1F467}|\x{1F93D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F93D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F575}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F477}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2696}\x{FE0F}|\x{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2695}\x{FE0F}|\x{1F477}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\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{1F939}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9B8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\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{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2708}\x{FE0F}|\x{1F937}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9B8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9B9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9B9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9CD}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F646}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F473}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F481}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F926}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F937}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6B4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F647}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F647}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64B}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F64B}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F645}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F64D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F64E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F645}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6A3}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6A3}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F481}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B5}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6B5}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6B6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F575}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F487}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F487}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F486}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F486}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F926}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9CE}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F482}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F482}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9CE}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9CD}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F473}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3CB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\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{1F9CF}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D7}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3CA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9D8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3CA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CC}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9DB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640Line truncated
|
||||
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
|
||||
|
||||
/* !Parser: Bootstrap */
|
||||
|
||||
@@ -40,6 +40,11 @@ class _Parser
|
||||
*/
|
||||
protected $attachIds = NULL;
|
||||
|
||||
/**
|
||||
* @brief Attachment Lang
|
||||
*/
|
||||
protected $attachIdsLang = NULL;
|
||||
|
||||
/**
|
||||
* @brief Rows from core_attachments_map containing attachments which belong to the content being edited - as they are found by the parser, they will be removed so we are left with attachments that have been removed
|
||||
*/
|
||||
@@ -96,9 +101,10 @@ class _Parser
|
||||
* @param bool $cleanHtml If TRUE, HTML will be cleaned through HTMLPurifier
|
||||
* @param callback $htmlPurifierConfig A function which will be passed the HTMLPurifier_Config object to customise it - see example
|
||||
* @param bool $parseAcronyms Parse acronyms?
|
||||
* @param ?int $attachIdsLang Language ID number if this Editor is part of a Translatable field.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $bbcode=FALSE, $attachIds=NULL, \IPS\Member $member=NULL, $area=FALSE, $filterProfanity=TRUE, $cleanHtml=TRUE, $htmlPurifierConfig=NULL, $parseAcronyms=TRUE )
|
||||
public function __construct( $bbcode=FALSE, $attachIds=NULL, \IPS\Member $member=NULL, $area=FALSE, $filterProfanity=TRUE, $cleanHtml=TRUE, $htmlPurifierConfig=NULL, $parseAcronyms=TRUE, $attachIdsLang=NULL )
|
||||
{
|
||||
/* Set the Member */
|
||||
$this->member = $member ?: \IPS\Member::loggedIn();
|
||||
@@ -117,6 +123,7 @@ class _Parser
|
||||
|
||||
/* Get attachments */
|
||||
$this->attachIds = $attachIds;
|
||||
$this->attachIdsLang = $attachIdsLang;
|
||||
if( $attachIds !== NULL )
|
||||
{
|
||||
$where = array( array( 'location_key=?', $area ) );
|
||||
@@ -211,6 +218,33 @@ class _Parser
|
||||
* @return string
|
||||
*/
|
||||
public function parse( $value )
|
||||
{
|
||||
/* Clean HTML */
|
||||
$value = $this->purify( $value );
|
||||
|
||||
/* BBCode, Profanity, etc. */
|
||||
if ( $value )
|
||||
{
|
||||
$value = $this->_parseContent( $value );
|
||||
}
|
||||
|
||||
/* Clean HTML */
|
||||
$value = $this->purify( $value );
|
||||
|
||||
/* Replace any {fileStore.whatever} tags with <fileStore.whatever> */
|
||||
$value = static::replaceFileStoreTags( $value );
|
||||
|
||||
/* Return */
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse
|
||||
*
|
||||
* @param string $value HTML to run through HTML purifier
|
||||
* @return string
|
||||
*/
|
||||
public function purify( $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 );
|
||||
@@ -221,22 +255,6 @@ class _Parser
|
||||
$value = $this->htmlPurifier->purify( $value );
|
||||
}
|
||||
|
||||
/* BBCode, Profanity, etc. */
|
||||
if ( $value )
|
||||
{
|
||||
$value = $this->_parseContent( $value );
|
||||
}
|
||||
|
||||
/* Clean HTML */
|
||||
if ( $value and $this->htmlPurifier )
|
||||
{
|
||||
$value = $this->htmlPurifier->purify( $value );
|
||||
}
|
||||
|
||||
/* Replace any {fileStore.whatever} tags with <fileStore.whatever> */
|
||||
$value = static::replaceFileStoreTags( $value );
|
||||
|
||||
/* Return */
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -351,7 +369,7 @@ class _Parser
|
||||
$element->setAttribute( 'width', $width );
|
||||
}
|
||||
|
||||
$element->setAttribute( 'data-ratio', (float) round( ( $height / $width ) * 100, 2 ) );
|
||||
$element->setAttribute( 'data-ratio', number_format( (float) round( ( $height / $width ) * 100, 2 ), 2, '.', '' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,7 +575,7 @@ class _Parser
|
||||
|
||||
/* CSS Definition */
|
||||
$cssDefinition = $config->getCSSDefinition();
|
||||
$this->_htmlPurifierModifyCssDefinition( $cssDefinition );
|
||||
$this->_htmlPurifierModifyCssDefinition( $cssDefinition, $config );
|
||||
|
||||
if( \IPS\Settings::i()->allow_remote_images AND \IPS\Settings::i()->allow_only_https_remote_images )
|
||||
{
|
||||
@@ -663,14 +681,25 @@ class _Parser
|
||||
/* Picture tag - we don't use it, but RSS imports might */
|
||||
$def->addElement( 'picture', 'Inline', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Maximum allowed width/height px values (to prevent causing page layout oddities)
|
||||
*/
|
||||
protected $cssMaxWidthHeight = 1000;
|
||||
|
||||
/**
|
||||
* @brief Maximum allowed border width px value (to prevent causing page layout oddities)
|
||||
*/
|
||||
protected $cssMaxBorderWidth = 50;
|
||||
|
||||
/**
|
||||
* Customize HTML Purifier CSS Definition
|
||||
*
|
||||
* @param HTMLPurifier_HTMLDefinition $def The definition
|
||||
* @param HTMLPurifier_Config $config HTML Purifier configuration object
|
||||
* @return void
|
||||
*/
|
||||
protected function _htmlPurifierModifyCssDefinition( \HTMLPurifier_CSSDefinition $def )
|
||||
protected function _htmlPurifierModifyCssDefinition( \HTMLPurifier_CSSDefinition $def, \HTMLPurifier_Config $config )
|
||||
{
|
||||
/* Do not allow negative margins */
|
||||
$margin = $def->info['margin-right'] = $def->info['margin-left'] = $def->info['margin-bottom'] = $def->info['margin-top'] = new \HTMLPurifier_AttrDef_CSS_Composite(
|
||||
@@ -686,6 +715,28 @@ class _Parser
|
||||
$def->info['white-space'] = new \HTMLPurifier_AttrDef_Enum(
|
||||
array( 'normal', 'pre', 'pre-wrap', 'pre-line')
|
||||
);
|
||||
|
||||
/* Limit the maximum width and height allowed */
|
||||
$def->info['width'] = $def->info['height'] = new \HTMLPurifier_AttrDef_CSS_Composite( array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length( '0px', $this->cssMaxWidthHeight . 'px' ),
|
||||
new \HTMLPurifier_AttrDef_CSS_Percentage( true ),
|
||||
new \HTMLPurifier_AttrDef_Enum( array( 'auto' ) )
|
||||
) );
|
||||
|
||||
/* Limit the maximum border width allowed */
|
||||
$border_width =
|
||||
$def->info['border-top-width'] =
|
||||
$def->info['border-bottom-width'] =
|
||||
$def->info['border-left-width'] =
|
||||
$def->info['border-right-width'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_Enum( array( 'thin', 'medium', 'thick' ) ),
|
||||
new \HTMLPurifier_AttrDef_CSS_Length( '0px', $this->cssMaxBorderWidth . 'px' )
|
||||
) );
|
||||
|
||||
$def->info['border-width'] = new \HTMLPurifier_AttrDef_CSS_Multiple( $border_width );
|
||||
|
||||
/* We have to reset this so the constructor picks up the new values we just specified */
|
||||
$def->info['border'] = $def->info['border-bottom'] = $def->info['border-top'] = $def->info['border-left'] = $def->info['border-right'] = new \HTMLPurifier_AttrDef_CSS_Border( $config );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -758,8 +809,6 @@ class _Parser
|
||||
'www.screencast.com/users/',
|
||||
'fast.wistia.net/embed/',
|
||||
'www.screencast.com/users/',
|
||||
'clips.twitch.tv/embed',
|
||||
'player.twitch.tv/'
|
||||
) );
|
||||
|
||||
/* Extra admin-defined options */
|
||||
@@ -1943,7 +1992,7 @@ class _Parser
|
||||
// removing the src attrib causes a FOUC in firefox, so we need to set it to an empty page instead (see #2178)
|
||||
$element->setAttribute( 'src', static::blankPage() );
|
||||
}
|
||||
else if ( $src->isInternal )
|
||||
else if ( $src instanceof \IPS\Http\Url\Internal )
|
||||
{
|
||||
/* If this is an internal embed, replace src and controller for lazy loading */
|
||||
if ( \IPS\Settings::i()->lazy_load_enabled )
|
||||
@@ -1988,14 +2037,34 @@ class _Parser
|
||||
|
||||
if( mb_stristr( $imageSrc, (string) $imageProxyUrl ) )
|
||||
{
|
||||
$srcUrl = \IPS\Http\Url::createFromString( str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $imageSrc ) );
|
||||
$element->setAttribute( $attributeName, static::_getProxiedImageUrl( $srcUrl->queryString['img'], $useProxyUrl ) );
|
||||
try
|
||||
{
|
||||
$srcUrl = \IPS\Http\Url::createFromString( str_replace( array( '<___base_url___>', '%7B___base_url___%7D' ), rtrim( \IPS\Settings::i()->base_url, '/' ), $imageSrc ) )->queryString['img'];
|
||||
}
|
||||
catch( \IPS\Http\Url\Exception $e )
|
||||
{
|
||||
parse_str( parse_url( str_replace( array( '<___base_url___>', '%7B___base_url___%7D' ), rtrim( \IPS\Settings::i()->base_url, '/' ), $imageSrc ), PHP_URL_QUERY ), $queryString );
|
||||
|
||||
$srcUrl = static::_getProxiedImageUrl( $queryString['img'], $useProxyUrl );
|
||||
}
|
||||
|
||||
$element->setAttribute( $attributeName, static::_getProxiedImageUrl( $srcUrl, $useProxyUrl ) );
|
||||
|
||||
/* 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', static::_getProxiedImageUrl( $hrefSrcUrl->queryString['img'], $useProxyUrl ) );
|
||||
try
|
||||
{
|
||||
$hrefSrcUrl = \IPS\Http\Url::createFromString( str_replace( array( '<___base_url___>', '%7B___base_url___%7D' ), rtrim( \IPS\Settings::i()->base_url, '/' ), $element->parentNode->getAttribute( 'href' ) ) )->queryString['img'];
|
||||
}
|
||||
catch( \IPS\Http\Url\Exception $e )
|
||||
{
|
||||
parse_str( parse_url( str_replace( array( '<___base_url___>', '%7B___base_url___%7D' ), rtrim( \IPS\Settings::i()->base_url, '/' ), $element->parentNode->getAttribute( 'href' ) ), PHP_URL_QUERY ), $queryString );
|
||||
|
||||
$srcUrl = static::_getProxiedImageUrl( $queryString['img'], $useProxyUrl );
|
||||
}
|
||||
|
||||
$element->parentNode->setAttribute( 'href', static::_getProxiedImageUrl( $hrefSrcUrl, $useProxyUrl ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2017,28 +2086,26 @@ class _Parser
|
||||
/* If for some reason we're processing an existing imageproxy URL, set the full URL. */
|
||||
try
|
||||
{
|
||||
$imageSrcUrl = \IPS\Http\Url::createFromString( str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $data[0] ) );
|
||||
$imageSrcUrl = \IPS\Http\Url::createFromString( str_replace( array( '<___base_url___>', '%7B___base_url___%7D' ), rtrim( \IPS\Settings::i()->base_url, '/' ), $data[0] ) );
|
||||
|
||||
if( isset( $imageSrcUrl->queryString['img'] ) )
|
||||
{
|
||||
$imageSrcUrl = $imageSrcUrl->queryString['img'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$imageSrcUrl = $data[0];
|
||||
}
|
||||
}
|
||||
catch( \IPS\Http\Url\Exception $e )
|
||||
{
|
||||
/* Make sure the URL doesn't start with a local placeholder. See comment above. */
|
||||
if ( $e->getMessage() === 'INVALID_URL' AND preg_match( '#^({|%7B|\<|%3C)fileStore\.#', $data[0] ) )
|
||||
{
|
||||
/* If it does, it's fine. Above we simply return, however here we are dealing with multiple URL's, so add it to the fixed list to be safe. */
|
||||
$fixedUrls[] = $data[0] . ( !empty( $data[1] ) ? ' ' . $data[1] : '' );
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Still here? Something else is wrong, so throw */
|
||||
throw $e;
|
||||
/* If we're here just return the original URL. Legacy data can result in many different problems that just can't be accounted for in every case. */
|
||||
parse_str( parse_url( str_replace( array( '<___base_url___>', '%7B___base_url___%7D' ), rtrim( \IPS\Settings::i()->base_url, '/' ), $data[0] ), PHP_URL_QUERY ), $queryString );
|
||||
|
||||
$imageSrcUrl = ( isset( $queryString['img'] ) ) ? $queryString['img'] : $data[0];
|
||||
}
|
||||
|
||||
|
||||
if( !isset( $imageSrcUrl->queryString['img'] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$fixedUrls[] = static::_getProxiedImageUrl( $imageSrcUrl->queryString['img'], $useProxyUrl ) . ( ! empty( $data[1] ) ? ' ' . $data[1] : '' );
|
||||
$fixedUrls[] = static::_getProxiedImageUrl( $imageSrcUrl, $useProxyUrl ) . ( ! empty( $data[1] ) ? ' ' . $data[1] : '' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2064,6 +2131,12 @@ class _Parser
|
||||
return $url;
|
||||
}
|
||||
|
||||
/* If the table no longer exists because it was dropped but we are re-running a remove image proxy step, just use original URL */
|
||||
if( !\IPS\Db::i()->checkForTable( 'core_image_proxy' ) )
|
||||
{
|
||||
return $url;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$cacheEntry = \IPS\Db::i()->select( '*', 'core_image_proxy', array( 'md5_url=?', md5( $url ) ) )->first();
|
||||
@@ -2114,7 +2187,7 @@ class _Parser
|
||||
}
|
||||
|
||||
/* We add external/nofollow rel attributes for non-internal non-local links */
|
||||
if ( !$url->isInternal )
|
||||
if ( !( $url instanceof \IPS\Http\Url\Internal ) )
|
||||
{
|
||||
$rels[] = 'external';
|
||||
|
||||
@@ -2328,7 +2401,7 @@ class _Parser
|
||||
}
|
||||
|
||||
/* If it's URL to applications/core/interface/file/attachment.php, it's definitely an attachment */
|
||||
if ( preg_match( '#^(?:http:|https:)?' . preg_quote( rtrim( str_replace( array( 'http://', 'https://' ), '//', \IPS\Settings::i()->base_url ), '/' ), '#' ) . '/applications/core/interface/file/attachment\.php\?id=(\d+)$#', $url, $matches ) )
|
||||
if ( preg_match( '#^(?:http:|https:)?' . preg_quote( rtrim( str_replace( array( 'http://', 'https://' ), '//', \IPS\Settings::i()->base_url ), '/' ), '#' ) . '/applications/core/interface/file/attachment\.php\?id=(\d+)(?:&key=[a-f0-9]{32})?$#', $url, $matches ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2375,15 +2448,16 @@ class _Parser
|
||||
}
|
||||
elseif ( $attachment['attach_member_id'] === (int) $this->member->member_id and !\in_array( $attachment['attach_id'], $this->mappedAttachments ) )
|
||||
{
|
||||
if( $this->area )
|
||||
{
|
||||
if( $this->area and !isset( \IPS\Request::i()->_previewField ) )
|
||||
{
|
||||
\IPS\Db::i()->replace( 'core_attachments_map', array(
|
||||
'attachment_id' => $attachment['attach_id'],
|
||||
'location_key' => $this->area,
|
||||
'id1' => ( \is_array( $this->attachIds ) and isset( $this->attachIds[0] ) ) ? $this->attachIds[0] : NULL,
|
||||
'id2' => ( \is_array( $this->attachIds ) and isset( $this->attachIds[1] ) ) ? $this->attachIds[1] : NULL,
|
||||
'id3' => ( \is_array( $this->attachIds ) and isset( $this->attachIds[2] ) ) ? $this->attachIds[2] : NULL,
|
||||
'temp' => \is_string( $this->attachIds ) ? $this->attachIds : NULL
|
||||
'temp' => \is_string( $this->attachIds ) ? $this->attachIds : NULL,
|
||||
'lang' => $this->attachIdsLang
|
||||
) );
|
||||
}
|
||||
|
||||
@@ -3066,7 +3140,7 @@ class _Parser
|
||||
*/
|
||||
protected static function oembedServices()
|
||||
{
|
||||
return array(
|
||||
$services = array(
|
||||
'youtube.com' => array( 'https://www.youtube.com/oembed', static::EMBED_VIDEO ),
|
||||
'm.youtube.com' => array( 'https://www.youtube.com/oembed', static::EMBED_VIDEO ),
|
||||
'youtu.be' => array( 'https://www.youtube.com/oembed', static::EMBED_VIDEO ),
|
||||
@@ -3077,17 +3151,11 @@ class _Parser
|
||||
'collegehumor.com' => array( 'http://www.collegehumor.com/oembed.json', static::EMBED_VIDEO ),
|
||||
'twitter.com' => array( 'https://publish.twitter.com/oembed', static::EMBED_TWEET ),
|
||||
'mobile.twitter.com' => array( 'https://publish.twitter.com/oembed', static::EMBED_TWEET ),
|
||||
'instagr.am' => array( 'https://api.instagram.com/oembed', static::EMBED_IMAGE ),
|
||||
'instagram.com' => array( 'https://api.instagram.com/oembed', static::EMBED_IMAGE ),
|
||||
'soundcloud.com' => array( 'https://soundcloud.com/oembed', static::EMBED_VIDEO ),
|
||||
'open.spotify.com' => array( 'https://embed.spotify.com/oembed', static::EMBED_VIDEO ),
|
||||
'play.spotify.com' => array( 'https://embed.spotify.com/oembed', static::EMBED_VIDEO ),
|
||||
'ted.com' => array( 'https://www.ted.com/services/v1/oembed', static::EMBED_VIDEO ),
|
||||
'vine.co' => array( 'https://vine.co/oembed.json', static::EMBED_VIDEO ),
|
||||
'facebook.com' => array(
|
||||
'(\/.+?\/videos\/|video.php)' => array( 'https://www.facebook.com/plugins/video/oembed.json', static::EMBED_VIDEO ),
|
||||
0 => array( 'https://www.facebook.com/plugins/post/oembed.json', static::EMBED_STATUS ),
|
||||
),
|
||||
'dailymotion.com' => 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 ),
|
||||
@@ -3106,10 +3174,60 @@ class _Parser
|
||||
'*.smugmug.com' => array( 'http://api.smugmug.com/services/oembed', static::EMBED_IMAGE ),
|
||||
'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 ),
|
||||
'*.wi.st' => array( 'http://fast.wistia.com/oembed', static::EMBED_VIDEO ),
|
||||
'tiktok.com' => array( 'https://www.tiktok.com/oembed', static::EMBED_VIDEO ),
|
||||
'm.tiktok.com' => array( 'https://www.tiktok.com/oembed', static::EMBED_VIDEO ),
|
||||
'vm.tiktok.com' => array( 'https://www.tiktok.com/oembed', static::EMBED_VIDEO ),
|
||||
);
|
||||
|
||||
/* Can we support Facebook and Instagram oembeds? */
|
||||
if( $token = static::getFacebookToken() )
|
||||
{
|
||||
$services['instagram.com'] = array(
|
||||
array( 'https://graph.facebook.com/v9.0/instagram_oembed?access_token=' . $token, static::EMBED_IMAGE )
|
||||
);
|
||||
|
||||
$services['instagr.am'] = $services['instagram.com'];
|
||||
|
||||
$services['facebook.com'] = array(
|
||||
'(\/.+?\/videos\/|video.php)' => array( 'https://graph.facebook.com/v9.0/oembed_video?access_token=' . $token, static::EMBED_VIDEO ),
|
||||
'(\/.+?\/posts\/|\/.+?\/activity\/|\/.+?\/photos?\/|photo.php|\/.+?\/media\/|permalink.php|\/.+?\/questions\/|\/.+?\/notes\/|\/.+?\/media\/)' => array( 'https://graph.facebook.com/v9.0/oembed_post?access_token=' . $token, static::EMBED_STATUS ),
|
||||
0 => array( 'https://graph.facebook.com/v9.0/oembed_page?access_token=' . $token, static::EMBED_STATUS ),
|
||||
);
|
||||
}
|
||||
|
||||
return $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an application token from Facebook for oembed support
|
||||
*
|
||||
* @return NULL|string
|
||||
*/
|
||||
protected static function getFacebookToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
if( !\IPS\Settings::i()->fb_ig_oembed_token AND \IPS\Settings::i()->fb_ig_oembed_appid AND \IPS\Settings::i()->fb_ig_oembed_appsecret )
|
||||
{
|
||||
$value = \IPS\Http\Url::external( "https://graph.facebook.com/oauth/access_token?client_id=" . \IPS\Settings::i()->fb_ig_oembed_appid . "&client_secret=" . \IPS\Settings::i()->fb_ig_oembed_appsecret . "&grant_type=client_credentials" )->request()->get()->decodeJson();
|
||||
|
||||
if( !empty( $value['access_token'] ) )
|
||||
{
|
||||
\IPS\Settings::i()->changeValues( array( 'fb_ig_oembed_token' => $value['access_token'] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Log::debug( "Facebook access token could not be retrieved for oembed:\n" . var_export( $value, true ), 'facebook_oembed' );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::debug( $e, 'facebook_oembed' );
|
||||
}
|
||||
|
||||
return \IPS\Settings::i()->fb_ig_oembed_token ? \IPS\Settings::i()->fb_ig_oembed_token : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3128,7 +3246,7 @@ class _Parser
|
||||
public static function embeddableMedia( $url, $iframe=FALSE, $member=NULL )
|
||||
{
|
||||
/* Internal */
|
||||
if ( $url->isInternal )
|
||||
if ( $url instanceof \IPS\Http\Url\Internal )
|
||||
{
|
||||
/* Internal Embed */
|
||||
if ( $embedCode = static::_internalEmbed( $url, $iframe, $member ) )
|
||||
@@ -3169,11 +3287,12 @@ class _Parser
|
||||
/**
|
||||
* oEmbed Embed Code
|
||||
*
|
||||
* @param \IPS\Http\Url $url URL
|
||||
* @param bool $iframe Some services need to be in iFrames so they cannot be edited in the editor. If TRUE, will return contents for iframe, if FALSE, return the iframe.
|
||||
* @param \IPS\Http\Url $url URL
|
||||
* @param bool $iframe Some services need to be in iFrames so they cannot be edited in the editor. If TRUE, will return contents for iframe, if FALSE, return the iframe.
|
||||
* @param int $attemptNumber The attempt number, useful for automatically retrying a service
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function _oembedEmbed( \IPS\Http\Url $url, $iframe=FALSE )
|
||||
protected static function _oembedEmbed( \IPS\Http\Url $url, $iframe=FALSE, $attemptNumber=1 )
|
||||
{
|
||||
/* Strip the "www." from the domain */
|
||||
$domain = $url->data['host'];
|
||||
@@ -3186,10 +3305,27 @@ class _Parser
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Twitch clips will use www.twitch.tv, but support oembed - however other twitch URLs are treated special so we can't just manipulate the entire URL */
|
||||
if( $domain == 'twitch.tv' AND mb_strpos( $url->data['path'], '/clip/' ) !== FALSE )
|
||||
/* TikTok does not support oembed for their short links, but will redirect to the full URL which does */
|
||||
if( $domain == 'vm.tiktok.com' )
|
||||
{
|
||||
$domain = 'clips.twitch.tv';
|
||||
try
|
||||
{
|
||||
$response = $url->request( null, null, 0 )->get();
|
||||
|
||||
if( $response->httpResponseCode == 301 )
|
||||
{
|
||||
$url = \IPS\Http\Url::external( $response->httpHeaders['Location'] )->stripQueryString();
|
||||
$domain = $url->data['host'];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Http\Request\Exception;
|
||||
}
|
||||
}
|
||||
catch( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \UnexpectedValueException( 'embed__fail_500', (float) static::EMBED_VIDEO );
|
||||
}
|
||||
}
|
||||
|
||||
/* TikTok doesn't return logical response codes for invalid URLs */
|
||||
@@ -3197,16 +3333,22 @@ class _Parser
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
elseif( $domain == 'm.tiktok.com' AND mb_strpos( $url->data['path'], '/v/' ) === FALSE )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get oEmbed Services */
|
||||
$oembedServices = static::oembedServices();
|
||||
|
||||
/* If the URL's domain is in the list... */
|
||||
$entry = NULL;
|
||||
$entry = NULL;
|
||||
$entryKey = NULL;
|
||||
|
||||
if ( array_key_exists( $domain, $oembedServices ) )
|
||||
{
|
||||
$entry = $oembedServices[ $domain ];
|
||||
$entry = $oembedServices[ $domain ];
|
||||
$entryKey = $domain;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3216,7 +3358,8 @@ class _Parser
|
||||
{
|
||||
if( preg_match( "/^" . str_replace( array( '.', '*' ), array( '\\.', '.*?' ), $k ) . "$/i", $domain ) )
|
||||
{
|
||||
$entry = $v;
|
||||
$entry = $v;
|
||||
$entryKey = $k;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3251,7 +3394,14 @@ class _Parser
|
||||
$endpoint = $entry[0];
|
||||
$endtype = $entry[1];
|
||||
}
|
||||
|
||||
|
||||
/* Youtube shorts feature doesn't return a valid response in its own oembed API so we need to tweak it */
|
||||
if ( ( $domain == 'youtube.com' or $domain == 'm.youtube.com' or $domain == 'youtu.be' ) and mb_strpos( $url->data['path'], '/shorts/' ) !== FALSE)
|
||||
{
|
||||
$url->queryString['v'] = preg_replace( '#/shorts/(\w+?)#', '$1', $url->data['path'] );
|
||||
$url->data['path'] = '/watch';
|
||||
}
|
||||
|
||||
/* Call oEmbed Service */
|
||||
try
|
||||
{
|
||||
@@ -3269,6 +3419,13 @@ class _Parser
|
||||
|
||||
if( $response->httpResponseCode != '200' )
|
||||
{
|
||||
/* If this is FB/IG we can reattempt one more time in case the stored access token was incorrect or expired */
|
||||
if( \in_array( $entryKey, array( 'instagram.com', 'instagr.am', 'facebook.com' ) ) AND $attemptNumber < 2 )
|
||||
{
|
||||
\IPS\Settings::i()->changeValues( array( 'fb_ig_oembed_token' => '' ) );
|
||||
return static::_oembedEmbed( $url, $iframe, 2 );
|
||||
}
|
||||
|
||||
switch( $response->httpResponseCode )
|
||||
{
|
||||
case '404':
|
||||
@@ -3293,19 +3450,7 @@ class _Parser
|
||||
{
|
||||
throw new \UnexpectedValueException( 'embed__fail_500', (float) $endtype );
|
||||
}
|
||||
|
||||
/* Facebook videos report as video but they're not HTML5 videos with the aspect ratio we need, so treat it as rich */
|
||||
if ( $domain == 'facebook.com' and preg_match( '(\/.+?\/videos\/|video.php)', $url ) )
|
||||
{
|
||||
$response['type'] = 'rich';
|
||||
}
|
||||
|
||||
/* For Facebook, we need to be a bit hacky to make it responsive */
|
||||
if ( $domain == 'facebook.com' )
|
||||
{
|
||||
$response['html'] = preg_replace( '/data\-width="\d*"/', 'data-width="auto"', $response['html'] );
|
||||
}
|
||||
|
||||
|
||||
/* Flickr when used in the video template is a bit quirky. Requires rich. */
|
||||
if ( $domain == 'flickr.com' OR $domain == 'flic.kr' )
|
||||
{
|
||||
@@ -3345,7 +3490,7 @@ class _Parser
|
||||
}
|
||||
|
||||
/* Tiktok tries to embed a <script src="https://tiktok.com/embed.js"></script> tag which isn't triggered by CKEditor and doesn't survive HTMLPurifier, so force it to load in an iframe */
|
||||
if ( $domain == 'tiktok.com' )
|
||||
if ( $domain == 'tiktok.com' OR $domain == 'm.tiktok.com' )
|
||||
{
|
||||
$response['type'] = 'rich';
|
||||
}
|
||||
@@ -3668,24 +3813,6 @@ 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;
|
||||
}
|
||||
@@ -3808,25 +3935,6 @@ class _Parser
|
||||
throw new \UnexpectedValueException( 'embed__fail_500', static::EMBED_LINK );
|
||||
}
|
||||
|
||||
/* Strip the CSRF key if present - normally shouldnt't be, but just in case..*/
|
||||
$preview = $preview->setQueryString( 'do', 'embed' )->stripQueryString( 'csrfKey' );
|
||||
if( isset( $url->queryString['do'] ) and $url->queryString['do'] == 'findComment' )
|
||||
{
|
||||
$preview = $preview->setQueryString( 'embedComment', $url->queryString['comment'] );
|
||||
}
|
||||
elseif( isset( $url->queryString['do'] ) and $url->queryString['do'] == 'findReview' )
|
||||
{
|
||||
$preview = $preview->setQueryString( 'embedReview', $url->queryString['review'] );
|
||||
}
|
||||
if( isset( $url->queryString['do'] ) )
|
||||
{
|
||||
$preview = $preview->setQueryString( 'embedDo', $url->queryString['do'] );
|
||||
}
|
||||
if ( isset( $url->queryString['page'] ) AND $url->queryString['page'] > 1 )
|
||||
{
|
||||
$preview = $preview->setPage( 'page', $url->queryString['page'] );
|
||||
}
|
||||
|
||||
/* Add the author ID for notifications */
|
||||
try
|
||||
{
|
||||
@@ -3844,6 +3952,45 @@ class _Parser
|
||||
$author = 0;
|
||||
}
|
||||
|
||||
/* Strip the CSRF key if present - normally shouldnt't be, but just in case..*/
|
||||
$preview = $preview->setQueryString( 'do', 'embed' )->stripQueryString( 'csrfKey' );
|
||||
if( isset( $url->queryString['do'] ) and $url->queryString['do'] == 'findComment' )
|
||||
{
|
||||
$preview = $preview->setQueryString( 'embedComment', $url->queryString['comment'] );
|
||||
|
||||
/* Get the correct author ID */
|
||||
$commentClass = $item::$commentClass;
|
||||
|
||||
try
|
||||
{
|
||||
$comment = $commentClass::loadAndCheckPerms( $url->queryString['comment'] );
|
||||
$author = $comment->author()->member_id;
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
elseif( isset( $url->queryString['do'] ) and $url->queryString['do'] == 'findReview' )
|
||||
{
|
||||
$preview = $preview->setQueryString( 'embedReview', $url->queryString['review'] );
|
||||
|
||||
/* Get the correct author ID */
|
||||
$reviewClass = $item::$reviewClass;
|
||||
|
||||
try
|
||||
{
|
||||
$review = $reviewClass::loadAndCheckPerms( $url->queryString['review'] );
|
||||
$author = $review->author()->member_id;
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
if( isset( $url->queryString['do'] ) )
|
||||
{
|
||||
$preview = $preview->setQueryString( 'embedDo', $url->queryString['do'] );
|
||||
}
|
||||
if ( isset( $url->queryString['page'] ) AND $url->queryString['page'] > 1 )
|
||||
{
|
||||
$preview = $preview->setPage( 'page', $url->queryString['page'] );
|
||||
}
|
||||
|
||||
/* And return */
|
||||
return "<iframe src='{$preview}' data-embedContent data-controller='core.front.core.autosizeiframe' data-embedauthorid='{$author}' allowfullscreen=''></iframe>";
|
||||
}
|
||||
@@ -3877,27 +4024,31 @@ class _Parser
|
||||
$widthToUse = $width;
|
||||
$heightToUse = $height;
|
||||
|
||||
/* Adjust the width/height according to our maximum dimensions */
|
||||
if ( $width > $maxImageDims[0] )
|
||||
/* 0x0 means unlimited, so only do these calculations if a specific size has been set */
|
||||
if( \intval( $maxImageDims[0] ) !== 0 || \intval( $maxImageDims[1] ) !== 0 )
|
||||
{
|
||||
$widthToUse = $maxImageDims[0];
|
||||
$heightToUse = floor( $height / $width * $widthToUse );
|
||||
|
||||
if ( $heightToUse > $maxImageDims[1] )
|
||||
/* Adjust the width/height according to our maximum dimensions */
|
||||
if ( $width > $maxImageDims[0] )
|
||||
{
|
||||
$widthToUse = floor( $maxImageDims[1] * ( $widthToUse / $heightToUse ) );
|
||||
$heightToUse = $maxImageDims[1];
|
||||
$widthToUse = $maxImageDims[0];
|
||||
$heightToUse = floor( $height / $width * $widthToUse );
|
||||
|
||||
if ( $heightToUse > $maxImageDims[1] )
|
||||
{
|
||||
$widthToUse = floor( $maxImageDims[1] * ( $widthToUse / $heightToUse ) );
|
||||
$heightToUse = $maxImageDims[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif( $height > $maxImageDims[1] )
|
||||
{
|
||||
$heightToUse = $maxImageDims[1];
|
||||
$widthToUse = floor( $width / $height * $heightToUse );
|
||||
|
||||
if ( $widthToUse > $maxImageDims[0] )
|
||||
elseif( $height > $maxImageDims[1] )
|
||||
{
|
||||
$heightToUse = floor( $maxImageDims[0] * ( $heightToUse / $widthToUse ) );
|
||||
$widthToUse = $maxImageDims[0];
|
||||
$heightToUse = $maxImageDims[1];
|
||||
$widthToUse = floor( $width / $height * $heightToUse );
|
||||
|
||||
if ( $widthToUse > $maxImageDims[0] )
|
||||
{
|
||||
$heightToUse = floor( $maxImageDims[0] * ( $heightToUse / $widthToUse ) );
|
||||
$widthToUse = $maxImageDims[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4089,11 +4240,11 @@ class _Parser
|
||||
{
|
||||
/* It's not, so create one and add the image to it */
|
||||
$parent = $image->parentNode;
|
||||
$clonedImage = clone $image;
|
||||
$anchor = $content->createElement( 'a' );
|
||||
$anchor->setAttribute( 'href', '{fileStore.core_Attachment}/' . $attachment['attach_location'] );
|
||||
$anchor->appendChild( $image );
|
||||
$parent->appendChild( $anchor );
|
||||
$parent->removeChild( $image );
|
||||
$anchor->appendChild( $clonedImage );
|
||||
$parent->replaceChild( $anchor, $image );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4168,6 +4319,51 @@ class _Parser
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild rel tag contents for posts
|
||||
*
|
||||
* @param string $textContent The content of the text, and they say comments aren't useful
|
||||
* @param \IPS\Member $member The author of the content
|
||||
* @return mixed FALSE or changed content
|
||||
*/
|
||||
public static function rebuildUrlRels( $textContent, $member )
|
||||
{
|
||||
$obj = new static( TRUE, NULL, $member );
|
||||
$rebuilt = FALSE;
|
||||
|
||||
/* Create DOMDocument */
|
||||
$content = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
|
||||
@$content->loadHTML( \IPS\Xml\DOMDocument::wrapHtml( $textContent ) );
|
||||
|
||||
$xpath = new \DOMXpath( $content );
|
||||
foreach( $xpath->query('//a') as $link )
|
||||
{
|
||||
if ( $link->getAttribute('href') )
|
||||
{
|
||||
try
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( str_replace( array( '<___base_url___>', '%7B___base_url___%7D' ), rtrim( \IPS\Settings::i()->base_url, '/' ), $link->getAttribute('href') ) );
|
||||
$rels = $obj->_getRelAttributes( $url );
|
||||
$link->setAttribute( 'rel', implode( ' ', $rels ) );
|
||||
|
||||
$rebuilt = TRUE;
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
if ( $rebuilt )
|
||||
{
|
||||
$value = $content->saveHTML();
|
||||
$value = preg_replace( '/<meta http-equiv(?:[^>]+?)>/i', '', preg_replace( '/^<!DOCTYPE.+?>/', '', str_replace( array( '<html>', '</html>', '<body>', '</body>', '<head>', '</head>' ), '', $value ) ) );
|
||||
|
||||
/* DOMDocument::saveHTML will encode the base_url brackets, so we need to make sure it's in the expected format. */
|
||||
return str_replace( '<___base_url___>', '<___base_url___>', $value );
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a safe html_entity_decode if you are not using UTF-8 MB4
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user