From cce518005c1076aff5871033c9967f61f373a88d Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Sat, 20 Dec 2025 19:52:38 +0000 Subject: [PATCH] feat: add wrapper div with CSS classes for improved ad styling This addresses a long-standing request from 2013 for better styling control of ad output. Previously, themes needed to target ads by numeric IDs that could change, making consistent styling difficult. The implementation wraps all ad output in a div with descriptive CSS classes: a generic 'acm-wrapper' class for broad styling rules, and a tag-specific 'acm-tag-{tag_id}' class for targeted control. This enables straightforward centering and other layout adjustments without depending on fragile numeric IDs. A new 'acm_wrapper_classes' filter allows developers to customise the classes or disable the wrapper entirely by returning an empty array, ensuring backwards compatibility for sites that need to opt out. Fixes #71 --- src/class-ad-code-manager.php | 24 +++++ tests/Integration/AdCodeManagerTest.php | 111 ++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/src/class-ad-code-manager.php b/src/class-ad-code-manager.php index 557a7f2..324e956 100644 --- a/src/class-ad-code-manager.php +++ b/src/class-ad-code-manager.php @@ -797,6 +797,30 @@ function get_acm_tag( $tag_id ): string { */ $output_html = apply_filters( 'acm_output_html_after_tokens_processed', $output_html, $tag_id ); + /** + * Configuration filter: acm_wrapper_classes + * Filter the CSS classes applied to the ad wrapper div. + * + * @since 0.8.0 + * + * @param array $classes Array of CSS class names. Default: 'acm-wrapper', 'acm-tag-{tag_id}'. + * @param string $tag_id The ad tag ID being rendered. + */ + $wrapper_classes = apply_filters( + 'acm_wrapper_classes', + array( 'acm-wrapper', 'acm-tag-' . sanitize_html_class( $tag_id ) ), + $tag_id + ); + + // Allow disabling the wrapper by returning an empty array. + if ( ! empty( $wrapper_classes ) && is_array( $wrapper_classes ) ) { + $output_html = sprintf( + '
%s
', + esc_attr( implode( ' ', array_map( 'sanitize_html_class', $wrapper_classes ) ) ), + $output_html + ); + } + return $output_html; } diff --git a/tests/Integration/AdCodeManagerTest.php b/tests/Integration/AdCodeManagerTest.php index 6271986..42ec5f9 100644 --- a/tests/Integration/AdCodeManagerTest.php +++ b/tests/Integration/AdCodeManagerTest.php @@ -66,4 +66,115 @@ private function mock_ad_code() { private function create_ad_code_and_return() { return $this->acm->create_ad_code( $this->mock_ad_code() ); } + + /** + * Test that ad output includes wrapper div with default classes. + * + * @covers Ad_Code_Manager::get_acm_tag + */ + public function test_get_acm_tag_includes_wrapper_with_default_classes(): void { + // Create an ad code and register it. + $ad_code_id = $this->create_ad_code_and_return(); + $this->acm->flush_cache(); + $this->acm->register_ad_codes( $this->acm->get_ad_codes() ); + + // Get the first available tag. + $test_tag = null; + foreach ( $this->acm->ad_tag_ids as $tag ) { + $test_tag = $tag['tag']; + break; + } + + if ( ! $test_tag ) { + $this->markTestSkipped( 'No ad tags available for testing.' ); + } + + // Enable display of ad codes without conditionals. + add_filter( 'acm_display_ad_codes_without_conditionals', '__return_true' ); + + $output = $this->acm->get_acm_tag( $test_tag ); + + remove_filter( 'acm_display_ad_codes_without_conditionals', '__return_true' ); + $this->acm->delete_ad_code( $ad_code_id ); + + $this->assertStringContainsString( 'class="acm-wrapper', $output, 'Output should contain acm-wrapper class.' ); + $this->assertStringContainsString( 'acm-tag-' . sanitize_html_class( $test_tag ), $output, 'Output should contain tag-specific class.' ); + } + + /** + * Test that acm_wrapper_classes filter can modify wrapper classes. + * + * @covers Ad_Code_Manager::get_acm_tag + */ + public function test_acm_wrapper_classes_filter_modifies_classes(): void { + // Create an ad code and register it. + $ad_code_id = $this->create_ad_code_and_return(); + $this->acm->flush_cache(); + $this->acm->register_ad_codes( $this->acm->get_ad_codes() ); + + // Get the first available tag. + $test_tag = null; + foreach ( $this->acm->ad_tag_ids as $tag ) { + $test_tag = $tag['tag']; + break; + } + + if ( ! $test_tag ) { + $this->markTestSkipped( 'No ad tags available for testing.' ); + } + + // Filter to add custom classes. + $filter_callback = function ( $classes, $tag_id ) { + $classes[] = 'custom-ad-class'; + $classes[] = 'another-class'; + return $classes; + }; + + add_filter( 'acm_wrapper_classes', $filter_callback, 10, 2 ); + add_filter( 'acm_display_ad_codes_without_conditionals', '__return_true' ); + + $output = $this->acm->get_acm_tag( $test_tag ); + + remove_filter( 'acm_wrapper_classes', $filter_callback, 10 ); + remove_filter( 'acm_display_ad_codes_without_conditionals', '__return_true' ); + $this->acm->delete_ad_code( $ad_code_id ); + + $this->assertStringContainsString( 'custom-ad-class', $output, 'Output should contain custom class.' ); + $this->assertStringContainsString( 'another-class', $output, 'Output should contain another custom class.' ); + } + + /** + * Test that acm_wrapper_classes filter can disable wrapper. + * + * @covers Ad_Code_Manager::get_acm_tag + */ + public function test_acm_wrapper_classes_filter_can_disable_wrapper(): void { + // Create an ad code and register it. + $ad_code_id = $this->create_ad_code_and_return(); + $this->acm->flush_cache(); + $this->acm->register_ad_codes( $this->acm->get_ad_codes() ); + + // Get the first available tag. + $test_tag = null; + foreach ( $this->acm->ad_tag_ids as $tag ) { + $test_tag = $tag['tag']; + break; + } + + if ( ! $test_tag ) { + $this->markTestSkipped( 'No ad tags available for testing.' ); + } + + // Filter to return empty array (disables wrapper). + add_filter( 'acm_wrapper_classes', '__return_empty_array' ); + add_filter( 'acm_display_ad_codes_without_conditionals', '__return_true' ); + + $output = $this->acm->get_acm_tag( $test_tag ); + + remove_filter( 'acm_wrapper_classes', '__return_empty_array' ); + remove_filter( 'acm_display_ad_codes_without_conditionals', '__return_true' ); + $this->acm->delete_ad_code( $ad_code_id ); + + $this->assertStringNotContainsString( 'acm-wrapper', $output, 'Output should not contain wrapper when filter returns empty array.' ); + } }