diff --git a/admin/class-ajax.php b/admin/class-ajax.php index 9b446083c..bfaa8fdd9 100644 --- a/admin/class-ajax.php +++ b/admin/class-ajax.php @@ -484,25 +484,10 @@ function ( $a, $b ) { $html .= '
'; - $landmark = isset( $row['landmark'] ) ? esc_html( $row['landmark'] ) : ''; + $landmark = isset( $row['landmark'] ) ? $row['landmark'] : ''; $landmark_selector = isset( $row['landmark_selector'] ) ? $row['landmark_selector'] : ''; - if ( $landmark && $landmark_selector ) { - $landmark_url = add_query_arg( - [ - 'edac_landmark' => base64_encode( $landmark_selector ), - 'edac_nonce' => wp_create_nonce( 'edac_highlight' ), - ], - get_the_permalink( $postid ) - ); - - // translators: %s is the landmark type (e.g., "Header", "Navigation", "Main"). - $landmark_aria_label = sprintf( __( 'View %s landmark on website, opens a new window', 'accessibility-checker' ), ucwords( $landmark ) ); - // translators: %s is the landmark type (e.g., "Header", "Navigation", "Main"). - $html .= '' . ucwords( $landmark ) . ''; - } elseif ( $landmark ) { - $html .= ucwords( $landmark ); - } + $html .= edac_generate_landmark_link( $landmark, $landmark_selector, $postid ); $html .= '
'; diff --git a/includes/helper-functions.php b/includes/helper-functions.php index 22cdb24d6..dd52bc51b 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -749,3 +749,50 @@ function edac_remove_corrected_posts( $post_ID, $type, $pre = 1, $ruleset = 'php ) ); } + +/** + * Generate a landmark link with proper URL and ARIA label + * + * @param string $landmark The landmark type (e.g., "header", "navigation", "main"). + * @param string $landmark_selector The CSS selector for the landmark. + * @param int $post_id The post ID to link to. + * @param string $css_class Optional CSS class for the link. Default 'edac-details-rule-records-record-landmark-link'. + * @param bool $target_blank Whether to open link in new window. Default true. + * + * @return string The HTML for the landmark link or just the landmark text if no selector. + */ +function edac_generate_landmark_link( $landmark, $landmark_selector, $post_id, $css_class = 'edac-details-rule-records-record-landmark-link', $target_blank = true ) { + if ( empty( $landmark ) ) { + return ''; + } + $landmark = ucwords( $landmark ); + $landmark = esc_html( $landmark ); + + // If we have both landmark and selector, create a link. + if ( ! empty( $landmark_selector ) ) { + $landmark_url = add_query_arg( + [ + 'edac_landmark' => base64_encode( $landmark_selector ), + 'edac_nonce' => wp_create_nonce( 'edac_highlight' ), + ], + get_the_permalink( $post_id ) + ); + + // translators: %s is the landmark type (e.g., "Header", "Navigation", "Main"). + $landmark_aria_label = sprintf( __( 'View %s landmark on website, opens a new window', 'accessibility-checker' ), $landmark ); + + $target_attr = $target_blank ? ' target="_blank"' : ''; + + return sprintf( + '%s', + esc_url( $landmark_url ), + esc_attr( $css_class ), + $target_attr, + esc_attr( $landmark_aria_label ), + $landmark + ); + } + + // If we only have landmark text, return it formatted. + return $landmark; +} diff --git a/src/frontendHighlighterApp/sass/app.scss b/src/frontendHighlighterApp/sass/app.scss index b840a9e7e..6975d23f1 100644 --- a/src/frontendHighlighterApp/sass/app.scss +++ b/src/frontendHighlighterApp/sass/app.scss @@ -35,6 +35,7 @@ body { outline-offset: 5px !important; outline-color: magenta !important; max-width: calc(100vw - 30px) !important; + margin-left: 15px !important; box-sizing: border-box !important; &-min-width { diff --git a/tests/phpunit/helper-functions/GenerateLandmarkLinkTest.php b/tests/phpunit/helper-functions/GenerateLandmarkLinkTest.php new file mode 100644 index 000000000..4f95577df --- /dev/null +++ b/tests/phpunit/helper-functions/GenerateLandmarkLinkTest.php @@ -0,0 +1,269 @@ +test_post_id = $this->factory->post->create( + [ + 'post_title' => 'Test Post', + 'post_content' => 'Test content', + 'post_status' => 'publish', + ] + ); + } + + /** + * Clean up test environment. + */ + public function tearDown(): void { + // Clean up the test post. + wp_delete_post( $this->test_post_id, true ); + parent::tearDown(); + } + + /** + * Tests the edac_generate_landmark_link function with valid landmark and selector. + */ + public function test_edac_generate_landmark_link_with_selector() { + $landmark = 'header'; + $landmark_selector = 'header.site-header'; + + $result = edac_generate_landmark_link( $landmark, $landmark_selector, $this->test_post_id ); + + // Check that result contains an anchor tag. + $this->assertStringContainsString( 'assertStringContainsString( 'target="_blank"', $result ); + $this->assertStringContainsString( '>Header', $result ); + + // Check that the URL contains the expected query parameters. + $this->assertStringContainsString( 'edac_landmark=', $result ); + $this->assertStringContainsString( 'edac_nonce=', $result ); + + // Check aria-label. + $this->assertStringContainsString( 'aria-label="View Header landmark on website, opens a new window"', $result ); + + // Verify the landmark selector is base64 encoded in the URL. + $encoded_selector = base64_encode( $landmark_selector ); + $this->assertStringContainsString( "edac_landmark={$encoded_selector}", $result ); + } + + /** + * Tests the edac_generate_landmark_link function with only landmark (no selector). + */ + public function test_edac_generate_landmark_link_without_selector() { + $landmark = 'navigation'; + $landmark_selector = ''; + + $result = edac_generate_landmark_link( $landmark, $landmark_selector, $this->test_post_id ); + + // Should return just the formatted landmark text, not a link. + $this->assertEquals( 'Navigation', $result ); + $this->assertStringNotContainsString( 'test_post_id ); + + // Should return empty string. + $this->assertEquals( '', $result ); + } + + /** + * Tests the edac_generate_landmark_link function with custom CSS class. + */ + public function test_edac_generate_landmark_link_with_custom_css_class() { + $landmark = 'main'; + $landmark_selector = 'main.content'; + $custom_class = 'my-custom-landmark-class'; + + $result = edac_generate_landmark_link( $landmark, $landmark_selector, $this->test_post_id, $custom_class ); + + $this->assertStringContainsString( "class=\"{$custom_class}\"", $result ); + $this->assertStringNotContainsString( 'class="edac-details-rule-records-record-landmark-link"', $result ); + } + + /** + * Tests the edac_generate_landmark_link function with target_blank disabled. + */ + public function test_edac_generate_landmark_link_without_target_blank() { + $landmark = 'footer'; + $landmark_selector = 'footer.site-footer'; + + $result = edac_generate_landmark_link( $landmark, $landmark_selector, $this->test_post_id, 'edac-details-rule-records-record-landmark-link', false ); + + $this->assertStringContainsString( 'assertStringContainsString( '>Footer', $result ); + } + + /** + * Tests the edac_generate_landmark_link function with various landmark types. + * + * @dataProvider landmark_types_data_provider + * + * @param string $landmark The landmark type. + * @param string $expected_display The expected display text. + */ + public function test_edac_generate_landmark_link_landmark_types( $landmark, $expected_display ) { + $landmark_selector = 'div.test'; + + $result = edac_generate_landmark_link( $landmark, $landmark_selector, $this->test_post_id ); + + $this->assertStringContainsString( ">{$expected_display}", $result ); + $this->assertStringContainsString( "View {$expected_display} landmark on website", $result ); + } + + /** + * Data provider for landmark types testing. + * + * @return array + */ + public function landmark_types_data_provider() { + return [ + 'header landmark' => [ 'header', 'Header' ], + 'navigation landmark' => [ 'navigation', 'Navigation' ], + 'main landmark' => [ 'main', 'Main' ], + 'footer landmark' => [ 'footer', 'Footer' ], + 'aside landmark' => [ 'aside', 'Aside' ], + 'section landmark' => [ 'section', 'Section' ], + 'search landmark' => [ 'search', 'Search' ], + 'banner landmark' => [ 'banner', 'Banner' ], + 'contentinfo landmark' => [ 'contentinfo', 'Contentinfo' ], + 'complementary landmark' => [ 'complementary', 'Complementary' ], + 'form landmark' => [ 'form', 'Form' ], + 'region landmark' => [ 'region', 'Region' ], + ]; + } + + /** + * Tests the edac_generate_landmark_link function with special characters in landmark. + */ + public function test_edac_generate_landmark_link_with_special_characters() { + $landmark = ''; + $landmark_selector = 'div.test'; + + $result = edac_generate_landmark_link( $landmark, $landmark_selector, $this->test_post_id ); + + // Check that dangerous script tags are properly escaped. + $this->assertStringNotContainsString( '