ner loader in // header_begin(); this iframe is the same container by another route, // so it has to go with it. It used to be emitted regardless, which // meant an excluded user still loaded the container (and was counted // in the reports) whenever JavaScript was unavailable - the exact // case the noscript iframe exists for. Mark the code as "written" so // the iframe block below is skipped, exactly like placement OFF and // the kill switch above; the data layer stays active. $GLOBALS['gtm4wp_container_code_written'] = true; if ( ! $no_console_log ) { $_gtm_tag .= $this->disabled_role_warning( $excluded_role ); } } if ( ( array() !== $containers ) && ( ! ( $GLOBALS['gtm4wp_container_code_written'] ?? false ) ) ) { foreach ( $containers as $one_container ) { $one_gtm_id = (string) ( $one_container[ ContainerRows::COLUMN_ID ] ?? '' ); // esc_attr() per PART below, at the point of injection (#225). Each part // is already allow-list validated - the id by the pattern one line down, // the domain by container_domain()'s FILTER_VALIDATE_DOMAIN fallback, the // environment values inside container_environment() - so esc_attr() is // the identity function on everything the validators pass today and // exists for the day a pattern is widened. NOT esc_attr()/esc_url() over // the whole src: the environment fragment joins with raw '&' on purpose // (wp_kses encodes it to & at output, the 1.x byte form), and // gtm4wp_get_the_gtm_tag() returns this string as public 1.x API - // pre-encoding it would change those bytes and hand third-party // consumers an already-escaped value to double-escape (RI-4). if ( preg_match( ContainerRows::GTM_ID_PATTERN, $one_gtm_id ) ) { $_gtm_tag .= ' '; } } $_gtm_tag .= ' '; $GLOBALS['gtm4wp_container_code_written'] = true; } return $_gtm_tag; } /** * Outputs a HTML code that includes the noscript/iframe part of the Google Tag Manager container. * * @return void */ public function the_tag(): void { // The iframe's inline style needs `display` and `visibility`. Whether // safecss_filter_attr() strips those depends on the WordPress version: // core added `display` to the safe_style_css default list in 7.0 and // `visibility` in 7.1, so on 6.3-6.9 both are stripped and on 7.0 // `visibility` still is. While the floor is 6.3 this filter is // load-bearing, not belt-and-braces - drop it only in the change that // raises the floor past 7.1, never as a "core allows it now" cleanup. // // Widen the CSS allow-list around THIS wp_kses() call only, then put it back: // safe_style_css is a global WordPress control, so a filter left // registered would relax it for every other wp_kses()/wp_kses_post() call // in the same request - including wp_filter_post_kses on content saved by // users without unfiltered_html, and a REST save is not is_admin(). One // line of our own markup is not a reason to loosen the rule site-wide. add_filter( 'safe_style_css', array( self::class, 'allow_iframe_hiding_styles' ) ); try { // print_markup_block(), not a bare wp_kses(): this block mixes the noscript // iframe (whose src must keep its & entity form) with the console // warning