-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unwrap all embeds with paragraph tags #4650
Changes from all commits
7401840
3c57f35
7e1ccf0
3bb9d3e
be260f8
d7f3421
9c17878
91cdd3d
ce5ef79
575f173
d789507
2ee90b4
bd606bf
8e492b2
3f4f9ae
085d1cc
050b9e7
6893a47
34ca29c
c955f45
21ae657
8850a0f
decbb49
361b712
28b4394
c663535
1f4df49
573334a
c695846
67bf708
e1c768d
5b2ece9
5fa1cdb
85b55cb
996862a
1cd716f
4885788
59066e8
7c03ced
1d91d0b
cb3ef8b
beadf51
b0157a1
b3a2c1c
dfaf3e5
244b53d
a857f25
434660a
986c036
62fa5ae
0860f48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,6 +7,8 @@ | |||||||||||||||||||||
* @package AMP | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
|
||||||||||||||||||||||
use AmpProject\Dom\Document; | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Class AMP_Base_Embed_Handler | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
|
@@ -40,14 +42,18 @@ abstract class AMP_Base_Embed_Handler { | |||||||||||||||||||||
protected $did_convert_elements = false; | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This property can be marked as In the past it was used to determine whether or not the AMP component script needed to be added to the page. But this is obsolete. |
||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Registers embed. | ||||||||||||||||||||||
* Default AMP tag to be used when sanitizing embeds. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @var string | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
abstract public function register_embed(); | ||||||||||||||||||||||
protected $amp_tag = 'amp-iframe'; | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Unregisters embed. | ||||||||||||||||||||||
* Base URL used for identifying embeds. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @var string | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
abstract public function unregister_embed(); | ||||||||||||||||||||||
protected $base_embed_url = ''; | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see this property as being used anywhere. Can it be removed? |
||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Constructor. | ||||||||||||||||||||||
|
@@ -64,6 +70,56 @@ public function __construct( $args = [] ) { | |||||||||||||||||||||
); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Sanitize all embeds on the page to be AMP compatible. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param Document $dom DOM. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
public function sanitize_raw_embeds( Document $dom ) { | ||||||||||||||||||||||
$nodes = $this->get_raw_embed_nodes( $dom ); | ||||||||||||||||||||||
|
||||||||||||||||||||||
if ( null === $nodes ) { | ||||||||||||||||||||||
// Bail if the embed handler returns null. | ||||||||||||||||||||||
return; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
if ( 0 === $nodes->length ) { | ||||||||||||||||||||||
return; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
foreach ( $nodes as $node ) { | ||||||||||||||||||||||
if ( ! $this->is_raw_embed( $node ) ) { | ||||||||||||||||||||||
continue; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
$this->sanitize_raw_embed( $node ); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Determine if the node is indeed a raw embed. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param DOMElement $node DOM element. | ||||||||||||||||||||||
* @return bool True if it is a raw embed, false otherwise. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
protected function is_raw_embed( DOMElement $node ) { | ||||||||||||||||||||||
return $node->parentNode && $this->amp_tag !== $node->parentNode->nodeName; | ||||||||||||||||||||||
Comment on lines
+101
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To facilitate using this to loop over a list of arbitrary nodes (e.g.
Suggested change
|
||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Get all raw embeds from the DOM. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param Document $dom Document. | ||||||||||||||||||||||
* @return DOMNodeList|null A list of DOMElement nodes, or null if not implemented. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
abstract protected function get_raw_embed_nodes( Document $dom ); | ||||||||||||||||||||||
Comment on lines
+112
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If not implemented? Doesn't an |
||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Make embed AMP compatible. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param DOMElement $node DOM element. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
abstract protected function sanitize_raw_embed( DOMElement $node ); | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Get mapping of AMP component names to AMP script URLs. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
|
@@ -107,4 +163,90 @@ function ( $attr_name ) { | |||||||||||||||||||||
} | ||||||||||||||||||||||
return wp_array_slice_assoc( $matches, $attribute_names ); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Get all child elements of the specified element. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param DOMElement $node Element. | ||||||||||||||||||||||
* @return DOMElement[] Array of child elements for specified element. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
protected function get_child_elements( DOMElement $node ) { | ||||||||||||||||||||||
return array_filter( | ||||||||||||||||||||||
iterator_to_array( $node->childNodes ), | ||||||||||||||||||||||
static function ( DOMNode $child ) { | ||||||||||||||||||||||
return $child instanceof DOMElement; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Replace the node's parent with itself if the parent is a <p> tag, has no attributes and has no other children. | ||||||||||||||||||||||
* This usually happens while `wpautop()` processes the element. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @since 1.6 | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param DOMElement $node Node. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
protected function unwrap_p_element( DOMElement $node ) { | ||||||||||||||||||||||
$parent_node = $node->parentNode; | ||||||||||||||||||||||
while ( $parent_node && ! ( $parent_node instanceof DOMElement ) ) { | ||||||||||||||||||||||
$parent_node = $parent_node->parentNode; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
if ( $parent_node instanceof DOMElement && 'p' === $parent_node->nodeName && false === $parent_node->hasAttributes() ) { | ||||||||||||||||||||||
$child_element_count = count( $this->get_child_elements( $parent_node ) ); | ||||||||||||||||||||||
if ( 1 === $child_element_count ) { | ||||||||||||||||||||||
$parent_node->parentNode->replaceChild( $node, $parent_node ); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Removes the node's nearest <script> sibling with a `src` attribute containing the base `src` URL provided. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @since 1.6 | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param DOMElement $node The DOMNode to whose sibling is the script to be removed. | ||||||||||||||||||||||
* @param string $base_src_url Script URL to match against. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||||||||||||
* @param string $content Text content of node to match against. | ||||||||||||||||||||||
* @param bool $is_next_to Whether the script sibling is next or preceding the specified element. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
protected function remove_script_sibling( DOMElement $node, $base_src_url, $content = '', $is_next_to = true ) { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In seeing the usages of |
||||||||||||||||||||||
$sibling_location = $is_next_to ? 'nextSibling' : 'previousSibling'; | ||||||||||||||||||||||
$element_sibling = $node->{$sibling_location}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
while ( $element_sibling && ! ( $element_sibling instanceof DOMElement ) ) { | ||||||||||||||||||||||
$element_sibling = $element_sibling->{$sibling_location}; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
// Handle case where script is wrapped in paragraph by wpautop. | ||||||||||||||||||||||
if ( $element_sibling instanceof DOMElement && 'p' === $element_sibling->nodeName ) { | ||||||||||||||||||||||
$children_elements = array_values( $this->get_child_elements( $element_sibling ) ); | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
if ( | ||||||||||||||||||||||
1 === count( $children_elements ) && | ||||||||||||||||||||||
'script' === $children_elements[0]->nodeName && | ||||||||||||||||||||||
( | ||||||||||||||||||||||
( $base_src_url && false !== strpos( $children_elements[0]->getAttribute( 'src' ), $base_src_url ) ) || | ||||||||||||||||||||||
( $content && false !== strpos( $children_elements[0]->textContent, $content ) ) | ||||||||||||||||||||||
) | ||||||||||||||||||||||
) { | ||||||||||||||||||||||
$element_sibling->parentNode->removeChild( $element_sibling ); | ||||||||||||||||||||||
return; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
// Handle case where script is immediately following. | ||||||||||||||||||||||
$is_embed_script = ( | ||||||||||||||||||||||
$element_sibling instanceof DOMElement && | ||||||||||||||||||||||
'script' === strtolower( $element_sibling->nodeName ) && | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is
Suggested change
I found that in all PHP versions the value is normalized to lower case already: https://3v4l.org/oDCO6 |
||||||||||||||||||||||
( | ||||||||||||||||||||||
( $base_src_url && false !== strpos( $element_sibling->getAttribute( 'src' ), $base_src_url ) ) || | ||||||||||||||||||||||
( $content && false !== strpos( $element_sibling->textContent, $content ) ) | ||||||||||||||||||||||
) | ||||||||||||||||||||||
); | ||||||||||||||||||||||
if ( $is_embed_script ) { | ||||||||||||||||||||||
$element_sibling->parentNode->removeChild( $element_sibling ); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
* @package AMP | ||
*/ | ||
|
||
use AmpProject\AmpWP\Embed\Registerable; | ||
use AmpProject\Attribute; | ||
use AmpProject\Dom\Document; | ||
|
||
|
@@ -13,7 +14,7 @@ | |
* | ||
* @since 1.0 | ||
*/ | ||
class AMP_Core_Block_Handler extends AMP_Base_Embed_Handler { | ||
class AMP_Core_Block_Handler extends AMP_Base_Embed_Handler implements Registerable { | ||
|
||
/** | ||
* Attribute to store the original width on a video or iframe just before WordPress removes it. | ||
|
@@ -355,4 +356,22 @@ private function process_text_widgets( Document $dom ) { | |
} | ||
} | ||
} | ||
|
||
/** | ||
* Get all raw embeds from the DOM. | ||
* | ||
* @param Document $dom Document. | ||
* @return DOMNodeList|null A list of DOMElement nodes, or null if not implemented. | ||
*/ | ||
protected function get_raw_embed_nodes( Document $dom ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable | ||
return null; | ||
} | ||
Comment on lines
+360
to
+368
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just making this the method in the base class. |
||
|
||
/** | ||
* Make embed AMP compatible. | ||
* | ||
* @param DOMElement $node DOM element. | ||
*/ | ||
protected function sanitize_raw_embed( DOMElement $node ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable | ||
} | ||
Comment on lines
+375
to
+376
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be in the base class as well. |
||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,59 +8,128 @@ | |||||
* @since 1.2 | ||||||
*/ | ||||||
|
||||||
use AmpProject\AmpWP\Embed\Registerable; | ||||||
use AmpProject\Dom\Document; | ||||||
|
||||||
/** | ||||||
* Class AMP_Crowdsignal_Embed_Handler | ||||||
*/ | ||||||
class AMP_Crowdsignal_Embed_Handler extends AMP_Base_Embed_Handler { | ||||||
class AMP_Crowdsignal_Embed_Handler extends AMP_Base_Embed_Handler implements Registerable { | ||||||
|
||||||
/** | ||||||
* Register embed. | ||||||
* Register the embed. | ||||||
* | ||||||
* @return void | ||||||
*/ | ||||||
public function register_embed() { | ||||||
add_filter( 'embed_oembed_html', [ $this, 'filter_embed_oembed_html' ], 10, 3 ); | ||||||
if ( version_compare( get_bloginfo( 'version' ), '5.2', '>=' ) ) { | ||||||
return; | ||||||
} | ||||||
|
||||||
// The oEmbed providers for CrowdSignal embeds are outdated on WP < 5.1. Updating the providers here will | ||||||
// allow for the oEmbed HTML to be fetched, and can then sanitized later below. | ||||||
$formats = [ | ||||||
'#https?://(.+\.)?polldaddy\.com/.*#i', | ||||||
'#https?://poll\.fm/.*#i', | ||||||
'#https?://(.+\.)?survey\.fm/.*#i', // Not available on WP 5.2. | ||||||
]; | ||||||
|
||||||
foreach ( $formats as $format ) { | ||||||
wp_oembed_add_provider( $format, 'https://api.crowdsignal.com/oembed', true ); | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Unregister embed. | ||||||
* Unregister the embed. | ||||||
* | ||||||
* @return void | ||||||
*/ | ||||||
public function unregister_embed() { | ||||||
remove_filter( 'embed_oembed_html', [ $this, 'filter_embed_oembed_html' ], 10 ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Filter oEmbed HTML for Crowdsignal for AMP output. | ||||||
* Get all raw embeds from the DOM. | ||||||
* | ||||||
* @param string $cache Cache for oEmbed. | ||||||
* @param string $url Embed URL. | ||||||
* @param array $attr Shortcode attributes. | ||||||
* @return string Embed. | ||||||
* @param Document $dom Document. | ||||||
* @return DOMNodeList A list of DOMElement nodes. | ||||||
*/ | ||||||
public function filter_embed_oembed_html( $cache, $url, $attr ) { | ||||||
$parsed_url = wp_parse_url( $url ); | ||||||
if ( empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) || ! preg_match( '#(^|\.)(?P<host>polldaddy\.com|crowdsignal\.com|survey\.fm|poll\.fm)#', $parsed_url['host'], $matches ) ) { | ||||||
return $cache; | ||||||
} | ||||||
protected function get_raw_embed_nodes( Document $dom ) { | ||||||
$queries = [ | ||||||
// For poll embeds. | ||||||
'//iframe[ @class="cs-iframe-embed" and starts-with( @src, "https://poll.fm/" ) ]', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
// For survey embeds. | ||||||
'//div[ @class="pd-embed" and @data-settings ]', | ||||||
]; | ||||||
|
||||||
$parsed_url['host'] = $matches['host']; | ||||||
return $dom->xpath->query( implode( ' | ', $queries ) ); | ||||||
} | ||||||
|
||||||
$output = ''; | ||||||
/** | ||||||
* Make embed AMP compatible. | ||||||
* | ||||||
* @param DOMElement $node DOM element. | ||||||
*/ | ||||||
protected function sanitize_raw_embed( DOMElement $node ) { | ||||||
$is_poll = 'cs-iframe-embed' === $node->getAttribute( 'class' ); | ||||||
|
||||||
// Poll oEmbed responses include noscript which can be used as the AMP response. | ||||||
if ( preg_match( '#<noscript>(.+?)</noscript>#s', $cache, $matches ) ) { | ||||||
$output = $matches[1]; | ||||||
if ( $is_poll ) { | ||||||
$this->sanitize_poll_embed( $node ); | ||||||
} else { | ||||||
$this->sanitize_survey_embed( $node ); | ||||||
} | ||||||
} | ||||||
|
||||||
if ( empty( $output ) ) { | ||||||
if ( ! empty( $attr['title'] ) ) { | ||||||
$name = $attr['title']; | ||||||
} elseif ( 'survey.fm' === $parsed_url['host'] || preg_match( '#^/s/#', $parsed_url['path'] ) ) { | ||||||
$name = __( 'View Survey', 'amp' ); | ||||||
} else { | ||||||
$name = __( 'View Poll', 'amp' ); | ||||||
} | ||||||
$output = sprintf( '<a href="%s" target="_blank">%s</a>', esc_url( $url ), esc_html( $name ) ); | ||||||
/** | ||||||
* Sanitize poll embed. | ||||||
* | ||||||
* @param DOMElement $node Poll embed. | ||||||
*/ | ||||||
private function sanitize_poll_embed( DOMElement $node ) { | ||||||
// Replace the `noscript` parent element with the iframe. | ||||||
$node->parentNode->parentNode->replaceChild( $node, $node->parentNode ); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this confirm that the |
||||||
|
||||||
$this->unwrap_p_element( $node ); | ||||||
$this->remove_script_sibling( $node, 'https://secure.polldaddy.com', '', false ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Sanitize survey embed. | ||||||
* | ||||||
* @param DOMElement $node Survey embed. | ||||||
*/ | ||||||
private function sanitize_survey_embed( DOMElement $node ) { | ||||||
$settings = json_decode( $node->getAttribute( 'data-settings' ), false ); | ||||||
|
||||||
// We can't form the iframe URL without a domain and survey ID. | ||||||
if ( ! ( property_exists( $settings, 'domain' ) || property_exists( $settings, 'id' ) ) ) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return; | ||||||
} | ||||||
|
||||||
return $output; | ||||||
// Logic for building the iframe `src` can be found in https://polldaddy.com/survey.js. | ||||||
$iframe_src = sprintf( | ||||||
'https://%s/%s?%s', | ||||||
$settings->domain, | ||||||
$settings->id, | ||||||
property_exists( $settings, 'auto' ) && $settings->auto ? 'ft=1&iframe=' . amp_get_current_url() : 'iframe=1' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
); | ||||||
|
||||||
$iframe_node = AMP_DOM_Utils::create_node( | ||||||
Document::fromNode( $node ), | ||||||
'iframe', | ||||||
[ | ||||||
'src' => $iframe_src, | ||||||
'layout' => 'responsive', | ||||||
'width' => 600, | ||||||
'height' => 600, | ||||||
'frameborder' => 0, | ||||||
'scrolling' => 'no', | ||||||
'allowtransparency' => 'true', | ||||||
'sandbox' => 'allow-scripts allow-same-origin', | ||||||
] | ||||||
); | ||||||
|
||||||
$this->remove_script_sibling( $node, null, 'https://polldaddy.com/survey.js' ); | ||||||
|
||||||
$node->parentNode->replaceChild( $iframe_node, $node ); | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this have a backwards-incompatibility problem? If someone has existing subclasses of
AMP_Base_Embed_Handler
withregister_embed
defined, should it not do something like this: