From c54a02d6dc9f23025def8b0fde094d5c11815a84 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 8 Feb 2018 21:05:38 -0800
Subject: [PATCH 1/5] Refactor remove_invalid_callback to take single DOMNode
arg, whether DOMElement or DOMAttr
---
.../sanitizers/class-amp-base-sanitizer.php | 23 +++--
.../class-amp-tag-and-attribute-sanitizer.php | 5 +-
includes/utils/class-amp-validation-utils.php | 94 ++++++++-----------
tests/test-class-amp-base-sanitizer.php | 21 ++---
tests/test-class-amp-theme-support.php | 16 +++-
tests/test-class-amp-validation-utils.php | 82 ++++++----------
6 files changed, 102 insertions(+), 139 deletions(-)
diff --git a/includes/sanitizers/class-amp-base-sanitizer.php b/includes/sanitizers/class-amp-base-sanitizer.php
index 8e7a936afb7..a0331d019e7 100644
--- a/includes/sanitizers/class-amp-base-sanitizer.php
+++ b/includes/sanitizers/class-amp-base-sanitizer.php
@@ -314,12 +314,12 @@ public function maybe_enforce_https_src( $src, $force_https = false ) {
* @since 0.7
*
* @param DOMElement $child The node to remove.
- * @return void.
+ * @return void
*/
public function remove_invalid_child( $child ) {
$child->parentNode->removeChild( $child );
if ( isset( $this->args['remove_invalid_callback'] ) ) {
- call_user_func( $this->args['remove_invalid_callback'], $child, AMP_Validation_Utils::NODE_REMOVED );
+ call_user_func( $this->args['remove_invalid_callback'], $child );
}
}
@@ -331,14 +331,23 @@ public function remove_invalid_child( $child ) {
*
* @since 0.7
*
- * @param DOMElement $element The node for which to remove the attribute.
- * @param string $attribute The attribute to remove from the node.
- * @return void.
+ * @param DOMElement $element The node for which to remove the attribute.
+ * @param DOMAttr|string $attribute The attribute to remove from the element.
+ * @return void
*/
public function remove_invalid_attribute( $element, $attribute ) {
- $element->removeAttribute( $attribute );
if ( isset( $this->args['remove_invalid_callback'] ) ) {
- call_user_func( $this->args['remove_invalid_callback'], $element, AMP_Validation_Utils::ATTRIBUTE_REMOVED, $attribute );
+ if ( is_string( $attribute ) ) {
+ $attribute = $element->getAttributeNode( $attribute );
+ }
+ if ( $attribute ) {
+ $element->removeAttributeNode( $attribute );
+ call_user_func( $this->args['remove_invalid_callback'], $attribute );
+ }
+ } elseif ( is_string( $attribute ) ) {
+ $element->removeAttribute( $attribute );
+ } else {
+ $element->removeAttributeNode( $attribute );
}
}
diff --git a/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php b/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php
index b4c0faeee31..bdc9aa54e72 100644
--- a/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php
+++ b/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php
@@ -729,10 +729,7 @@ private function sanitize_disallowed_attributes_in_node( $node, $attr_spec_list
}
foreach ( $attrs_to_remove as $attr ) {
- $node->removeAttributeNode( $attr );
- if ( isset( $this->args['remove_invalid_callback'], $attr->name ) ) {
- call_user_func( $this->args['remove_invalid_callback'], $node, AMP_Validation_Utils::ATTRIBUTE_REMOVED, $attr->name );
- }
+ $this->remove_invalid_attribute( $node, $attr );
}
}
diff --git a/includes/utils/class-amp-validation-utils.php b/includes/utils/class-amp-validation-utils.php
index c7450248f81..986665a4a4c 100644
--- a/includes/utils/class-amp-validation-utils.php
+++ b/includes/utils/class-amp-validation-utils.php
@@ -12,20 +12,6 @@
*/
class AMP_Validation_Utils {
- /**
- * The argument if an attribute was removed.
- *
- * @var array.
- */
- const ATTRIBUTE_REMOVED = 'removed_attr';
-
- /**
- * The argument if a node was removed.
- *
- * @var array.
- */
- const NODE_REMOVED = 'removed';
-
/**
* Key for the markup value in the REST API endpoint.
*
@@ -68,24 +54,17 @@ class AMP_Validation_Utils {
*/
const ERROR_NONCE_ACTION = 'invalid_amp_message';
- /**
- * The attributes that the sanitizer removed.
- *
- * @var array.
- */
- public static $removed_attributes;
-
/**
* The nodes that the sanitizer removed.
*
- * @var array.
+ * @var DOMNode[]
*/
- public static $removed_nodes;
+ public static $removed_nodes = array();
/**
* Add the actions.
*
- * @return void.
+ * @return void
*/
public static function init() {
add_action( 'rest_api_init', array( __CLASS__, 'amp_rest_validation' ) );
@@ -94,32 +73,13 @@ public static function init() {
}
/**
- * Tracks when a sanitizer removes an attribute or node.
+ * Tracks when a sanitizer removes an node (element or attribute).
*
- * @param DOMNode|DOMElement $node The node in which there was a removal.
- * @param string $removal_type The removal: 'removed_attr' for an attribute, or 'removed' for a node or element.
- * @param string $attr_name The name of the attribute removed (optional).
- * @return void.
- */
- public static function track_removed( $node, $removal_type, $attr_name = null ) {
- if ( ( self::ATTRIBUTE_REMOVED === $removal_type ) && isset( $attr_name ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
- self::$removed_attributes = self::increment_count( self::$removed_attributes, $attr_name );
- } elseif ( ( self::NODE_REMOVED === $removal_type ) && isset( $node->nodeName ) ) {
- self::$removed_nodes = self::increment_count( self::$removed_nodes, $node->nodeName ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
- }
- }
-
- /**
- * Tracks when a sanitizer removes an attribute or node.
- *
- * @param array $histogram The count of attributes or nodes removed.
- * @param string $key The attribute or node name removed.
- * @return array $histogram The incremented histogram.
+ * @param DOMNode $node The node which was removed.
+ * @return void
*/
- public static function increment_count( $histogram, $key ) {
- $current_value = isset( $histogram[ $key ] ) ? $histogram[ $key ] : 0;
- $histogram[ $key ] = $current_value + 1;
- return $histogram;
+ public static function track_removed( $node ) {
+ self::$removed_nodes[] = $node;
}
/**
@@ -212,11 +172,34 @@ public static function get_response( $markup = null ) {
self::process_markup( $markup );
$response['processed_markup'] = $markup;
}
- $response = array_merge( array(
- self::ERROR_KEY => self::was_node_removed(),
- 'removed_nodes' => self::$removed_nodes,
- 'removed_attributes' => self::$removed_attributes,
- ), $response );
+
+ $removed_elements = array();
+ $removed_attributes = array();
+ foreach ( self::$removed_nodes as $node ) {
+ if ( $node instanceof DOMAttr ) {
+ if ( ! isset( $removed_attributes[ $node->nodeName ] ) ) {
+ $removed_attributes[ $node->nodeName ] = 1;
+ } else {
+ $removed_attributes[ $node->nodeName ]++;
+ }
+ } elseif ( $node instanceof DOMElement ) {
+ if ( ! isset( $removed_elements[ $node->nodeName ] ) ) {
+ $removed_elements[ $node->nodeName ] = 1;
+ } else {
+ $removed_elements[ $node->nodeName ]++;
+ }
+ }
+ }
+
+ $response = array_merge(
+ array(
+ self::ERROR_KEY => self::was_node_removed(),
+ ),
+ compact(
+ 'removed_elements',
+ 'removed_attributes'
+ ),
+ $response );
self::reset_removed();
return $response;
@@ -232,8 +215,7 @@ public static function get_response( $markup = null ) {
* @return void.
*/
public static function reset_removed() {
- self::$removed_nodes = null;
- self::$removed_attributes = null;
+ self::$removed_nodes = array();
}
/**
@@ -272,7 +254,7 @@ public static function validate_content( $post_id, $post ) {
$location = AMP_Validation_Utils::error_message( $location );
$location = add_query_arg(
array(
- 'removed_elements' => array_keys( $response['removed_nodes'] ),
+ 'removed_elements' => array_keys( $response['removed_elements'] ),
'removed_attributes' => array_keys( $response['removed_attributes'] ),
),
$location
diff --git a/tests/test-class-amp-base-sanitizer.php b/tests/test-class-amp-base-sanitizer.php
index a7c6bcd8f08..ef4bab3beb5 100644
--- a/tests/test-class-amp-base-sanitizer.php
+++ b/tests/test-class-amp-base-sanitizer.php
@@ -245,26 +245,23 @@ public function test_enforce_sizes_attribute( $source_params, $expected_value, $
/**
* Tests remove_child.
*
- * @see AMP_Base_Sanitizer::remove_invalid_child()
+ * @covers AMP_Base_Sanitizer::remove_invalid_child()
*/
public function test_remove_child() {
$parent_tag_name = 'div';
- $child_tag_name = 'h1';
$dom_document = new DOMDocument( '1.0', 'utf-8' );
$parent = $dom_document->createElement( $parent_tag_name );
$child = $dom_document->createElement( 'h1' );
$parent->appendChild( $child );
- // To ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar.
- // @codingStandardsIgnoreStart
-
$this->assertEquals( $child, $parent->firstChild );
$sanitizer = new AMP_Iframe_Sanitizer( $dom_document, array(
'remove_invalid_callback' => 'AMP_Validation_Utils::track_removed',
) );
$sanitizer->remove_invalid_child( $child );
$this->assertEquals( null, $parent->firstChild );
- $this->assertEquals( 1, AMP_Validation_Utils::$removed_nodes[ $child_tag_name ] );
+ $this->assertCount( 1, AMP_Validation_Utils::$removed_nodes );
+ $this->assertEquals( $child, AMP_Validation_Utils::$removed_nodes[0] );
$parent->appendChild( $child );
$this->assertEquals( $child, $parent->firstChild );
@@ -272,14 +269,13 @@ public function test_remove_child() {
$this->assertEquals( null, $parent->firstChild );
$this->assertEquals( null, $child->parentNode );
- // @codingStandardsIgnoreEnd
AMP_Validation_Utils::$removed_nodes = null;
}
/**
* Tests remove_child.
*
- * @see AMP_Base_Sanitizer::remove_invalid_child()
+ * @covers AMP_Base_Sanitizer::remove_invalid_child()
*/
public function test_remove_attribute() {
AMP_Validation_Utils::reset_removed();
@@ -288,20 +284,15 @@ public function test_remove_attribute() {
$dom_document = new DOMDocument( '1.0', 'utf-8' );
$video = $dom_document->createElement( $video_name );
$video->setAttribute( $attribute, 'someFunction()' );
+ $attr_node = $video->getAttributeNode( $attribute );
- // To ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar.
- // @codingStandardsIgnoreStart
$args = array(
'remove_invalid_callback' => 'AMP_Validation_Utils::track_removed',
);
- $expected_removed = array(
- $attribute => 1,
- );
$sanitizer = new AMP_Video_Sanitizer( $dom_document, $args );
$sanitizer->remove_invalid_attribute( $video, $attribute );
$this->assertEquals( null, $video->getAttribute( $attribute ) );
- $this->assertEquals( $expected_removed, AMP_Validation_Utils::$removed_attributes );
- // @codingStandardsIgnoreEnd
+ $this->assertEquals( array( $attr_node ), AMP_Validation_Utils::$removed_nodes );
AMP_Validation_Utils::reset_removed();
}
diff --git a/tests/test-class-amp-theme-support.php b/tests/test-class-amp-theme-support.php
index b71c4d41228..eacee79e26f 100644
--- a/tests/test-class-amp-theme-support.php
+++ b/tests/test-class-amp-theme-support.php
@@ -101,7 +101,7 @@ public function test_prepare_response() {
>
-
+
@@ -114,12 +114,19 @@ public function test_prepare_response() {
data-aax_src="302">
+
+
function( $node ) use ( &$removed_nodes ) {
+ $removed_nodes[ $node->nodeName ] = $node;
+ },
+ ) );
$this->assertContains( '', $sanitized_html );
$this->assertContains( '', $sanitized_html );
@@ -136,6 +143,11 @@ public function test_prepare_response() {
$this->assertContains( '
It seemed to have removed the tag,
As it contained a disallowed element.
But it's not needed to report that the
is removed.
So remove 'wpautop' as a callback for 'the_content.'
Gutenberg also does this, unless the post has no block.
@see gutenberg_wpautop().
---
includes/utils/class-amp-validation-utils.php | 8 +++++++-
tests/test-class-amp-validation-utils.php | 14 ++++++++++++--
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/includes/utils/class-amp-validation-utils.php b/includes/utils/class-amp-validation-utils.php
index b8d96e75cd8..dd539573ab6 100644
--- a/includes/utils/class-amp-validation-utils.php
+++ b/includes/utils/class-amp-validation-utils.php
@@ -72,7 +72,12 @@ public static function was_node_removed() {
* @return void.
*/
public static function process_markup( $markup ) {
- $args = array(
+ AMP_Theme_Support::register_content_embed_handlers();
+ remove_filter( 'the_content', 'wpautop' );
+
+ /** This filter is documented in wp-includes/post-template.php */
+ $markup = apply_filters( 'the_content', $markup );
+ $args = array(
'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH,
);
if ( self::has_cap() ) {
@@ -215,6 +220,7 @@ public static function validate_content( $post ) {
if ( ! post_supports_amp( $post ) || ! self::has_cap() ) {
return;
}
+ AMP_Theme_Support::register_content_embed_handlers();
/** This filter is documented in wp-includes/post-template.php */
$filtered_content = apply_filters( 'the_content', $post->post_content, $post->ID );
$response = self::get_response( $filtered_content );
diff --git a/tests/test-class-amp-validation-utils.php b/tests/test-class-amp-validation-utils.php
index 98ac9f54e43..0be1cbcf39f 100644
--- a/tests/test-class-amp-validation-utils.php
+++ b/tests/test-class-amp-validation-utils.php
@@ -277,8 +277,6 @@ public function test_validate_content() {
$this->assertNotContains( 'notice notice-warning', $output );
$this->assertNotContains( 'Warning:', $output );
- $post_id = $this->factory()->post->create();
- $post = get_post( $post_id );
$post->post_content = $this->disallowed_tag;
ob_start();
AMP_Validation_Utils::validate_content( $post );
@@ -287,6 +285,18 @@ public function test_validate_content() {
$this->assertContains( 'notice notice-warning', $output );
$this->assertContains( 'Warning:', $output );
$this->assertContains( 'script
', $output );
+ AMP_Validation_Utils::reset_removed();
+
+ $youtube = 'https://www.youtube.com/watch?v=GGS-tKTXw4Y';
+ $post->post_content = $youtube;
+ ob_start();
+ AMP_Validation_Utils::validate_content( $post );
+ $output = ob_get_clean();
+
+ // The YouTube embed handler should convert the URL into a valid AMP element.
+ $this->assertNotContains( 'notice notice-warning', $output );
+ $this->assertNotContains( 'Warning:', $output );
+ AMP_Validation_Utils::reset_removed();
}
/**
From 18a877e369263884109a800cfe3075b71de6fb8d Mon Sep 17 00:00:00 2001
From: Ryan Kienstra
Date: Tue, 13 Feb 2018 14:48:38 -0600
Subject: [PATCH 5/5] Issue #843: Exit early if ! has_cap().
Instead of wrapping the 'invalid_callback' in this,
simply exist process_markup().
If that callback isn't added,
there's no need for the rest of the function.
---
includes/utils/class-amp-validation-utils.php | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/includes/utils/class-amp-validation-utils.php b/includes/utils/class-amp-validation-utils.php
index dd539573ab6..6b79504e7f8 100644
--- a/includes/utils/class-amp-validation-utils.php
+++ b/includes/utils/class-amp-validation-utils.php
@@ -72,17 +72,19 @@ public static function was_node_removed() {
* @return void.
*/
public static function process_markup( $markup ) {
+ if ( ! self::has_cap() ) {
+ return;
+ }
+
AMP_Theme_Support::register_content_embed_handlers();
remove_filter( 'the_content', 'wpautop' );
/** This filter is documented in wp-includes/post-template.php */
$markup = apply_filters( 'the_content', $markup );
$args = array(
- 'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH,
+ 'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH,
+ 'remove_invalid_callback' => 'AMP_Validation_Utils::track_removed',
);
- if ( self::has_cap() ) {
- $args['remove_invalid_callback'] = 'AMP_Validation_Utils::track_removed';
- }
AMP_Content_Sanitizer::sanitize( $markup, amp_get_content_sanitizers(), $args );
}