diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index 135c84c2485..fbecbd21a58 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -853,7 +853,7 @@ public static function add_hooks() { remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); - // Temporary workarounds for . + // The AMP version of the skip link is implemented by AMP_Accessibility_Sanitizer::add_skip_link(). remove_action( 'wp_footer', 'gutenberg_the_skip_link' ); remove_action( 'wp_footer', 'the_block_template_skip_link' ); diff --git a/includes/sanitizers/class-amp-accessibility-sanitizer.php b/includes/sanitizers/class-amp-accessibility-sanitizer.php index 10bb0538a62..365a44513da 100644 --- a/includes/sanitizers/class-amp-accessibility-sanitizer.php +++ b/includes/sanitizers/class-amp-accessibility-sanitizer.php @@ -5,8 +5,10 @@ * @package AmpProject\AmpWP */ +use AmpProject\Dom\Element; use AmpProject\Html\Attribute; use AmpProject\Html\Role; +use AmpProject\Html\Tag; /** * Sanitizes attributes required for AMP accessibility requirements. @@ -21,6 +23,7 @@ class AMP_Accessibility_Sanitizer extends AMP_Base_Sanitizer { */ public function sanitize() { $this->add_role_and_tabindex_to_on_tap_actors(); + $this->add_skip_link(); } /** @@ -66,4 +69,94 @@ static function ( $predicate ) { } } } + + /** + * Add skip link markup and style. + * + * This is the implementation of the non-AMP logic in `the_block_template_skip_link()` which is unhooked in + * `AMP_Theme_Support::add_hooks()` to prevent validation errors from being raised. + * + * @see AMP_Theme_Support::add_hooks() + * @see the_block_template_skip_link() + * @return void + */ + public function add_skip_link() { + + // Early exit if not a block theme. + if ( ! current_theme_supports( 'block-templates' ) ) { + return; + } + + // Early exit if not a block template. + global $_wp_current_template_content; + if ( ! $_wp_current_template_content ) { + return; + } + + $main_tag = $this->dom->getElementsByTagName( Tag::MAIN )->item( 0 ); + if ( ! $main_tag instanceof Element ) { + return; + } + + $skip_link_target = $this->dom->getElementId( $main_tag, 'wp--skip-link--target' ); + + // Style for skip link. + $style_content = ' + .skip-link.screen-reader-text { + border: 0; + clip: rect(1px,1px,1px,1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute !important; + width: 1px; + word-wrap: normal !important; + } + + .skip-link.screen-reader-text:focus { + background-color: #eee; + clip: auto !important; + clip-path: none; + color: #444; + display: block; + font-size: 1em; + height: auto; + left: 5px; + line-height: normal; + padding: 15px 23px 14px; + text-decoration: none; + top: 5px; + width: auto; + z-index: 100000; + } + '; + + $style_node = AMP_DOM_Utils::create_node( + $this->dom, + Tag::STYLE, + [ + Attribute::ID => 'amp-skip-link-styles', + ] + ); + + $style_node->appendChild( $this->dom->createTextNode( $style_content ) ); + + // Skip link node. + $skip_link = AMP_DOM_Utils::create_node( + $this->dom, + Tag::A, + [ + Attribute::CLASS_ => 'skip-link screen-reader-text', + Attribute::HREF => "#$skip_link_target", + ] + ); + + $skip_link->appendChild( $this->dom->createTextNode( __( 'Skip to content', 'amp' ) ) ); + + $body = $this->dom->body; + $body->insertBefore( $skip_link, $body->firstChild ); + $body->insertBefore( $style_node, $body->firstChild ); + } } diff --git a/tests/php/test-class-amp-accessibility-sanitizer.php b/tests/php/test-class-amp-accessibility-sanitizer.php index a8f4bb6268d..287fff2f3e5 100644 --- a/tests/php/test-class-amp-accessibility-sanitizer.php +++ b/tests/php/test-class-amp-accessibility-sanitizer.php @@ -10,11 +10,47 @@ /** * Tests the accessibility sanitizer class. + * + * @coversDefaultClass AMP_Accessibility_Sanitizer */ class AMP_Accessibility_Sanitizer_Test extends TestCase { use MarkupComparison; + /** @var string */ + private $original_wp_current_template_content; + + /** @var array */ + private $original_wp_theme_features; + + /** + * Setup. + * + * @inheritDoc + */ + public function setUp() { + + parent::setUp(); + + global $_wp_current_template_content, $_wp_theme_features; + $this->original_wp_current_template_content = $_wp_current_template_content; + $this->original_wp_theme_features = $_wp_theme_features; + } + + /** + * Tear down. + * + * @inheritDoc + */ + public function tearDown() { + + parent::tearDown(); + + global $_wp_current_template_content, $_wp_theme_features; + $_wp_current_template_content = $this->original_wp_current_template_content; + $_wp_theme_features = $this->original_wp_theme_features; + } + public function get_sanitize_test_data() { return [ 'valid markup remains unchanged' => [ @@ -68,4 +104,118 @@ public function test_sanitize( $source, $expected ) { $this->assertEqualMarkup( $expected, $actual ); } + + /** + * Data provider for $this->test_add_skip_link() + * + * @return string[][] + */ + public function get_skip_link_test_data() { + + $style_element = ''; + + return [ + 'with_id' => [ + 'source' => '
Hello World!
', + 'expected' => $style_element . '
Hello World!
', + ], + 'without_id' => [ + 'source' => '
Hello World!
', + 'expected' => $style_element . '
Hello World!
', + ], + 'without_main_element' => [ + 'source' => '
Hello World!
', + 'expected' => '
Hello World!
', + ], + ]; + + } + + /** + * @dataProvider get_skip_link_test_data + * + * @covers ::add_skip_link() + * + * @param string $source Source HTML. + * @param string $expected Expected target HTML. + */ + public function test_add_skip_link( $source, $expected ) { + + global $_wp_current_template_content, $_wp_theme_features; + + $_wp_current_template_content = true; + $_wp_theme_features['block-templates'] = true; + + $dom = AMP_DOM_Utils::get_dom_from_content( $source ); + + $sanitizer = new AMP_Accessibility_Sanitizer( $dom ); + $sanitizer->add_skip_link(); + + $actual = AMP_DOM_Utils::get_content_from_dom( $dom ); + + $this->assertEqualMarkup( $expected, $actual ); + } + + /** + * @covers ::add_skip_link() + */ + public function test_add_skip_link_for_none_fse_theme() { + + global $_wp_current_template_content, $_wp_theme_features; + + $source = '
Hello World!
'; + $expected = '
Hello World!
'; + + // Test 1: If it's not block theme. + unset( $_wp_theme_features['block-templates'] ); + + $dom = AMP_DOM_Utils::get_dom_from_content( $source ); + $sanitizer = new AMP_Accessibility_Sanitizer( $dom ); + $sanitizer->add_skip_link(); + $actual = AMP_DOM_Utils::get_content_from_dom( $dom ); + + $this->assertEqualMarkup( $expected, $actual ); + + // Test 2: If it's not block template. + $_wp_theme_features['block-templates'] = true; + $_wp_current_template_content = false; + + $dom = AMP_DOM_Utils::get_dom_from_content( $source ); + $sanitizer = new AMP_Accessibility_Sanitizer( $dom ); + $sanitizer->add_skip_link(); + $actual = AMP_DOM_Utils::get_content_from_dom( $dom ); + + $this->assertEqualMarkup( $expected, $actual ); + + } }