shortcode replacement.
* Patterns are matched against URLs (src or movie HTML attributes).
*
* @var array
*/
public static $strpos_filters = array();
/**
* Array of patterns to search for via preg_match().
* Keys are patterns, values are callback functions that implement the HTML -> shortcode replacement.
* Patterns are matched against URLs (src or movie HTML attributes).
*
* @var array
*/
public static $regexp_filters = array();
/**
* HTML element being processed.
*
* @var string
*/
public static $current_element = false;
/**
* Array of patterns to search for via strpos().
* Keys are patterns, values are callback functions that implement the HTML -> shortcode replacement.
* Patterns are matched against full HTML elements.
*
* @var array
*/
public static $html_strpos_filters = array();
/**
* Array of patterns to search for via preg_match().
* Keys are patterns, values are callback functions that implement the HTML -> shortcode replacement.
* Patterns are matched against full HTML elements.
*
* @var array
*/
public static $html_regexp_filters = array();
/**
* Failed embeds (stripped)
*
* @var array
*/
public static $failed_embeds = array();
/**
* Store tokens found in Syntax Highlighter.
*
* @since 4.5.0
*
* @var array
*/
private static $sh_unfiltered_content_tokens;
/**
* Capture tokens found in Syntax Highlighter and collect them in self::$sh_unfiltered_content_tokens.
*
* @since 4.5.0
*
* @param array $match Array of Syntax Highlighter matches.
*
* @return string
*/
public static function sh_regexp_callback( $match ) {
$token = sprintf(
'[prekses-filter-token-%1$d-%2$s-%1$d]',
wp_rand(),
md5( $match[0] )
);
self::$sh_unfiltered_content_tokens[ $token ] = $match[0];
return $token;
}
/**
* Look for HTML elements that match the registered patterns.
* Replace them with the HTML generated by the registered replacement callbacks.
*
* @param string $html Post content.
*/
public static function filter( $html ) {
if ( ! $html || ! is_string( $html ) ) {
return $html;
}
$regexps = array(
'object' => '%%i',
'embed' => '%)?%i',
'iframe' => '%%i',
'div' => '%
]*+>(?>[^<]*+(?><(?!/div>)[^<]*+)*+)(?:
)+%i',
'script' => '%%i',
);
$unfiltered_content_tokens = array();
self::$sh_unfiltered_content_tokens = array();
// Check here to make sure that SyntaxHighlighter is still used. (Just a little future proofing).
if ( class_exists( 'SyntaxHighlighter' ) ) {
/*
* Replace any "code" shortcode blocks with a token that we'll later replace with its original text.
* This will keep the contents of the shortcode from being filtered.
*/
global $SyntaxHighlighter; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
// Check to see if the $syntax_highlighter object has been created and is ready for use.
if ( isset( $SyntaxHighlighter ) && is_array( $SyntaxHighlighter->shortcodes ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
$shortcode_regex = implode( '|', array_map( 'preg_quote', $SyntaxHighlighter->shortcodes ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
$html = preg_replace_callback(
'/\[(' . $shortcode_regex . ')(\s[^\]]*)?\][\s\S]*?\[\/\1\]/m',
array( __CLASS__, 'sh_regexp_callback' ),
$html
);
$unfiltered_content_tokens = self::$sh_unfiltered_content_tokens;
}
}
foreach ( $regexps as $element => $regexp ) {
self::$current_element = $element;
if ( false !== stripos( $html, "<$element" ) ) {
$new_html = preg_replace_callback( $regexp, array( __CLASS__, 'dispatch' ), $html );
if ( $new_html ) {
$html = $new_html;
}
}
if ( false !== stripos( $html, "<$element" ) ) {
$regexp_entities = self::regexp_entities( $regexp );
$new_html = preg_replace_callback( $regexp_entities, array( __CLASS__, 'dispatch_entities' ), $html );
if ( $new_html ) {
$html = $new_html;
}
}
}
if ( count( $unfiltered_content_tokens ) > 0 ) {
// Replace any tokens generated earlier with their original unfiltered text.
$html = str_replace( array_keys( $unfiltered_content_tokens ), $unfiltered_content_tokens, $html );
}
return $html;
}
/**
* Replace HTML entities in current HTML element regexp.
* This is useful when the content is HTML encoded by TinyMCE.
*
* @param string $regexp Selected regexp.
*/
public static function regexp_entities( $regexp ) {
return preg_replace(
'/\[\^&([^\]]+)\]\*\+/',
'(?>[^&]*+(?>&(?!\1)[^&])*+)*+',
str_replace( '?>', '?' . '>', htmlspecialchars( $regexp, ENT_NOQUOTES ) )
);
}
/**
* Register a filter to convert a matching HTML element to a shortcode.
*
* We can match the provided pattern against the source URL of the HTML element
* (generally the value of the src attribute of the HTML element), or against the full HTML element.
*
* The callback is passed an array containing the raw HTML of the element as well as pre-parsed attribute name/values.
*
* @param string $match Pattern to search for: either a regular expression to use with preg_match() or a search string to use with strpos().
* @param string $callback Function used to convert embed into shortcode.
* @param bool $is_regexp Is $match a regular expression? If true, match using preg_match(). If not, match using strpos(). Default false.
* @param bool $is_html_filter Match the pattern against the full HTML (true) or just the source URL (false)? Default false.
*/
public static function register( $match, $callback, $is_regexp = false, $is_html_filter = false ) {
if ( $is_html_filter ) {
if ( $is_regexp ) {
self::$html_regexp_filters[ $match ] = $callback;
} else {
self::$html_strpos_filters[ $match ] = $callback;
}
} else {
if ( $is_regexp ) {
self::$regexp_filters[ $match ] = $callback;
} else {
self::$strpos_filters[ $match ] = $callback;
}
}
}
/**
* Delete an existing registered pattern/replacement filter.
*
* @param string $match Embed regexp.
*/
public static function unregister( $match ) {
// Allow themes/plugins to remove registered embeds.
unset( self::$regexp_filters[ $match ] );
unset( self::$strpos_filters[ $match ] );
unset( self::$html_regexp_filters[ $match ] );
unset( self::$html_strpos_filters[ $match ] );
}
/**
* Filter and replace HTML element entity.
*
* @param array $matches Array of matches.
*/
private static function dispatch_entities( $matches ) {
$orig_html = $matches[0];
$decoded_matches = array( html_entity_decode( $matches[0] ) );
return self::dispatch( $decoded_matches, $orig_html );
}
/**
* Filter and replace HTML element.
*
* @param array $matches Array of matches.
* @param string $orig_html Original html. Returned if no results are found via $matches processing.
*/
private static function dispatch( $matches, $orig_html = null ) {
if ( null === $orig_html ) {
$orig_html = $matches[0];
}
$html = preg_replace( '%*58;//%', '://', $matches[0] );
$attrs = self::get_attrs( $html );
if ( isset( $attrs['src'] ) ) {
$src = $attrs['src'];
} elseif ( isset( $attrs['movie'] ) ) {
$src = $attrs['movie'];
} else {
// no src found, search html.
foreach ( self::$html_strpos_filters as $match => $callback ) {
if ( false !== strpos( $html, $match ) ) {
return call_user_func( $callback, $attrs );
}
}
foreach ( self::$html_regexp_filters as $match => $callback ) {
if ( preg_match( $match, $html ) ) {
return call_user_func( $callback, $attrs );
}
}
return $orig_html;
}
$src = trim( $src );
// check source filter.
foreach ( self::$strpos_filters as $match => $callback ) {
if ( false !== strpos( $src, $match ) ) {
return call_user_func( $callback, $attrs );
}
}
foreach ( self::$regexp_filters as $match => $callback ) {
if ( preg_match( $match, $src ) ) {
return call_user_func( $callback, $attrs );
}
}
// check html filters.
foreach ( self::$html_strpos_filters as $match => $callback ) {
if ( false !== strpos( $html, $match ) ) {
return call_user_func( $callback, $attrs );
}
}
foreach ( self::$html_regexp_filters as $match => $callback ) {
if ( preg_match( $match, $html ) ) {
return call_user_func( $callback, $attrs );
}
}
// Log the strip.
if ( function_exists( 'wp_kses_reject' ) ) {
wp_kses_reject(
sprintf(
/* translators: placeholder is an HTML tag. */
__( '%s HTML tag removed as it is not allowed', 'jetpack' ),
'<' . self::$current_element . '>'
),
array( self::$current_element => $attrs )
);
}
// Keep the failed match so we can later replace it with a link,
// but return the original content to give others a chance too.
self::$failed_embeds[] = array(
'match' => $orig_html,
'src' => esc_url( $src ),
);
return $orig_html;
}
/**
* Failed embeds are stripped, so let's convert them to links at least.
*
* @param string $string Failed embed string.
*
* @return string $string Linkified string.
*/
public static function maybe_create_links( $string ) {
if ( empty( self::$failed_embeds ) ) {
return $string;
}
foreach ( self::$failed_embeds as $entry ) {
$html = sprintf( '%s', esc_url( $entry['src'] ), esc_url( $entry['src'] ) );
// Check if the string doesn't contain iframe, before replace.
if ( ! preg_match( '/