diff --git a/includes/sanitizers/class-amp-audio-sanitizer.php b/includes/sanitizers/class-amp-audio-sanitizer.php index a90040a7cce..e204c7e27cb 100644 --- a/includes/sanitizers/class-amp-audio-sanitizer.php +++ b/includes/sanitizers/class-amp-audio-sanitizer.php @@ -8,6 +8,10 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer { private static $script_slug = 'amp-audio'; private static $script_src = 'https://cdn.ampproject.org/v0/amp-audio-0.1.js'; + protected $DEFAULT_ARGS = array( + 'require_https_src' => true, + ); + public function get_scripts() { if ( ! $this->did_convert_elements ) { return array(); @@ -24,9 +28,15 @@ public function sanitize() { } for ( $i = $num_nodes - 1; $i >= 0; $i-- ) { + $orig_src = array(); + $node = $nodes->item( $i ); $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node ); + if ( isset( $old_attributes['src'] ) ) { + $orig_src[] = $old_attributes['src']; + } + $new_attributes = $this->filter_attributes( $old_attributes ); $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-audio', $new_attributes ); @@ -35,21 +45,36 @@ public function sanitize() { foreach ( $node->childNodes as $child_node ) { $new_child_node = $child_node->cloneNode( true ); $old_child_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $new_child_node ); + + if ( isset( $old_child_attributes['src'] ) ) { + $orig_src[] = $old_child_attributes['src']; + } + $new_child_attributes = $this->filter_attributes( $old_child_attributes ); // Only append source tags with a valid src attribute if ( ! empty( $new_child_attributes['src'] ) && 'source' === $new_child_node->tagName ) { + AMP_DOM_Utils::add_attributes_to_node( $new_child_node, $new_child_attributes ); $new_node->appendChild( $new_child_node ); } } // If the node has at least one valid source, replace the old node with it. + // If the node has no valid sources, but at least one invalid (http) one, add a fallback element. // Otherwise, just remove the node. - // - // TODO: Add a fallback handler. - // See: https://github.com/ampproject/amphtml/issues/2261 if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) { - $node->parentNode->removeChild( $node ); + if ( ! empty( $orig_src ) ) { + $fallback_node = $this->create_fallback_node( + sprintf( + wp_kses( __( 'Could not load audio.', 'amp' ), array( 'a' => array( 'href' => true ) ) ), + esc_url( array_shift( $orig_src ) ) + ) + ); + + $node->parentNode->replaceChild( $fallback_node, $node ); + } else { + $node->parentNode->removeChild( $node ); + } } else { $node->parentNode->replaceChild( $new_node, $node ); } diff --git a/includes/sanitizers/class-amp-base-sanitizer.php b/includes/sanitizers/class-amp-base-sanitizer.php index 3a4d78c079d..4ada9b64156 100644 --- a/includes/sanitizers/class-amp-base-sanitizer.php +++ b/includes/sanitizers/class-amp-base-sanitizer.php @@ -102,14 +102,31 @@ public function add_or_append_attribute( &$attributes, $key, $value, $separator * @return string */ public function maybe_enforce_https_src( $src, $force_https = false ) { + $https_required = isset( $this->args['require_https_src'] ) && true === $this->args['require_https_src']; $protocol = strtok( $src, ':' ); + + if ( $protocol === $src && ( $https_required || $force_https ) ) { + if ( 0 === strpos( $src, '//' ) ) { + // src has relative protocol, ie //example.com/asdf, so add https + $src = set_url_scheme( $src, 'https' ); + } else if ( 0 === strpos( $src, '/' ) ) { + // src is URL relative to the site root + $src = home_url( $src ); + } else { + // src is URL relative to current URI + global $wp; + $src = home_url( trailingslashit( $wp->request ) . $src ); + } + + $protocol = strtok( $src, ':' ); + } + if ( 'https' !== $protocol ) { // Check if https is required - if ( isset( $this->args['require_https_src'] ) && true === $this->args['require_https_src'] ) { + if ( $https_required ) { // Remove the src. Let the implementing class decide what do from here. $src = ''; - } elseif ( ( ! isset( $this->args['require_https_src'] ) || false === $this->args['require_https_src'] ) - && true === $force_https ) { + } elseif ( ! $https_required && true === $force_https ) { // Don't remove the src, but force https instead $src = set_url_scheme( $src, 'https' ); } @@ -117,4 +134,25 @@ public function maybe_enforce_https_src( $src, $force_https = false ) { return $src; } + + protected function create_fallback_node( $content, $container_el = 'blockquote', $attributes = array() ) { + $defaults = array( + 'class' => 'amp-wp-fallback', + ); + $attributes = wp_parse_args( $attributes, $defaults ); + + $fallback_node = AMP_DOM_Utils::create_node( + $this->dom, + $container_el, + $attributes + ); + + if ( $content ) { + $fallback_content = $this->dom->createDocumentFragment(); + $fallback_content->appendXML( $content ); + $fallback_node->appendChild( $fallback_content ); + } + + return $fallback_node; + } } diff --git a/includes/sanitizers/class-amp-iframe-sanitizer.php b/includes/sanitizers/class-amp-iframe-sanitizer.php index 95de9680f65..0d8edcbc5bd 100644 --- a/includes/sanitizers/class-amp-iframe-sanitizer.php +++ b/includes/sanitizers/class-amp-iframe-sanitizer.php @@ -15,7 +15,8 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer { private static $script_src = 'https://cdn.ampproject.org/v0/amp-iframe-0.1.js'; protected $DEFAULT_ARGS = array( - 'add_placeholder' => false, + 'require_https_src' => true, + 'add_placeholder' => false, ); public function get_scripts() { @@ -37,16 +38,29 @@ public function sanitize() { $node = $nodes->item( $i ); $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node ); + $orig_src = ''; + if ( isset( $old_attributes['src'] ) ) { + $orig_src = $old_attributes['src']; + } + $new_attributes = $this->filter_attributes( $old_attributes ); - // If the src doesn't exist, remove the node. - // This means that it never existed or was invalidated - // while filtering attributes above. - // - // TODO: add a filter to allow for a fallback element in this instance. - // See: https://github.com/ampproject/amphtml/issues/2261 + // If the filtered src doesn't exist, but there's an invalid src, add a fallback element. + // Otherwise remove the node. if ( empty( $new_attributes['src'] ) ) { - $node->parentNode->removeChild( $node ); + if ( ! empty( $orig_src ) ) { + $fallback_node = $this->create_fallback_node( + sprintf( + wp_kses( __( 'Could not load iframe.', 'amp' ), array( 'a' => array( 'href' => true ) ) ), + esc_url( $orig_src ) + ) + ); + + $node->parentNode->replaceChild( $fallback_node, $node ); + } else { + $node->parentNode->removeChild( $node ); + } + continue; } diff --git a/includes/sanitizers/class-amp-video-sanitizer.php b/includes/sanitizers/class-amp-video-sanitizer.php index b17f9e5836e..cd4b33d9ed9 100644 --- a/includes/sanitizers/class-amp-video-sanitizer.php +++ b/includes/sanitizers/class-amp-video-sanitizer.php @@ -10,6 +10,10 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer { public static $tag = 'video'; + protected $DEFAULT_ARGS = array( + 'require_https_src' => true, + ); + public function sanitize() { $nodes = $this->dom->getElementsByTagName( self::$tag ); $num_nodes = $nodes->length; @@ -18,9 +22,15 @@ public function sanitize() { } for ( $i = $num_nodes - 1; $i >= 0; $i-- ) { + $orig_src = array(); + $node = $nodes->item( $i ); $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node ); + if ( isset( $old_attributes['src'] ) ) { + $orig_src[] = $old_attributes['src']; + } + $new_attributes = $this->filter_attributes( $old_attributes ); $new_attributes = $this->enforce_fixed_height( $new_attributes ); @@ -32,21 +42,36 @@ public function sanitize() { foreach ( $node->childNodes as $child_node ) { $new_child_node = $child_node->cloneNode( true ); $old_child_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $new_child_node ); + + if ( isset( $old_child_attributes['src'] ) ) { + $orig_src[] = $old_child_attributes['src']; + } + $new_child_attributes = $this->filter_attributes( $old_child_attributes ); // Only append source tags with a valid src attribute if ( ! empty( $new_child_attributes['src'] ) && 'source' === $new_child_node->tagName ) { + AMP_DOM_Utils::add_attributes_to_node( $new_child_node, $new_child_attributes ); $new_node->appendChild( $new_child_node ); } } // If the node has at least one valid source, replace the old node with it. + // If the node has no valid sources, but at least one invalid (http) one, add a fallback element. // Otherwise, just remove the node. - // - // TODO: Add a fallback handler. - // See: https://github.com/ampproject/amphtml/issues/2261 if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) { - $node->parentNode->removeChild( $node ); + if ( ! empty( $orig_src ) ) { + $fallback_node = $this->create_fallback_node( + sprintf( + wp_kses( __( 'Could not load video.', 'amp' ), array( 'a' => array( 'href' => true ) ) ), + esc_url( array_shift( $orig_src ) ) + ) + ); + + $node->parentNode->replaceChild( $fallback_node, $node ); + } else { + $node->parentNode->removeChild( $node ); + } } else { $node->parentNode->replaceChild( $new_node, $node ); } diff --git a/tests/test-amp-audio-converter.php b/tests/test-amp-audio-converter.php index 8552265660e..6fe8f964e21 100644 --- a/tests/test-amp-audio-converter.php +++ b/tests/test-amp-audio-converter.php @@ -70,10 +70,17 @@ public function get_data() { '' ), + /* 'https_not_required' => array( '', '', ), + */ + + 'https_required' => array( + '', + '
Could not load audio.
', + ), ); } @@ -90,7 +97,7 @@ public function test_converter( $source, $expected ) { public function test__https_required() { $source = ''; - $expected = ''; + $expected = '
Could not load audio.
'; $dom = AMP_DOM_Utils::get_dom_from_content( $source ); $sanitizer = new AMP_Audio_Sanitizer( $dom, array( diff --git a/tests/test-amp-iframe-sanitizer.php b/tests/test-amp-iframe-sanitizer.php index ae2e535a47e..2f2cd94c104 100644 --- a/tests/test-amp-iframe-sanitizer.php +++ b/tests/test-amp-iframe-sanitizer.php @@ -15,7 +15,7 @@ public function get_data() { 'force_https' => array( '', - '', + '
Could not load iframe.
', ), 'iframe_without_dimensions' => array( @@ -116,7 +116,7 @@ public function test_converter( $source, $expected ) { public function test__https_required() { $source = ''; - $expected = ''; + $expected = '
Could not load iframe.
'; $dom = AMP_DOM_Utils::get_dom_from_content( $source ); $sanitizer = new AMP_Iframe_Sanitizer( $dom, array( diff --git a/tests/test-amp-video-sanitizer.php b/tests/test-amp-video-sanitizer.php index 481d2d64248..cd31b84d1c9 100644 --- a/tests/test-amp-video-sanitizer.php +++ b/tests/test-amp-video-sanitizer.php @@ -71,10 +71,17 @@ public function get_data() { '' ), + /* 'https_not_required' => array( '', '', - ) + ), + */ + + 'https_required' => array( + '', + '
Could not load video.
', + ), ); } @@ -91,7 +98,7 @@ public function test_converter( $source, $expected ) { public function test__https_required() { $source = ''; - $expected = ''; + $expected = '
Could not load video.
'; $dom = AMP_DOM_Utils::get_dom_from_content( $source ); $sanitizer = new AMP_Video_Sanitizer( $dom, array(