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.' );
+ }
}