false, 'matched_container' => false, 'method' => 'none', 'other_ids' => [], 'source' => 'local', 'error' => null, ]; if ( ! preg_match( '/^GTM-[A-Z0-9]+$/', $container_id ) ) { $result['error'] = 'invalid_container_id'; return $result; } $html = self::fetch_home_html(); if ( null === $html ) { $result['error'] = 'fetch_failed'; return $result; } $html = self::strip_adbot_snippet( $html ); // Every GTM-XXXX id present on the page (after removing Adbot's own). preg_match_all( '/GTM-[A-Z0-9]+/', $html, $matches ); $ids = array_values( array_unique( $matches[0] ) ); $id_re = preg_quote( $container_id, '/' ); $method = 'none'; if ( preg_match( '/\/gtm\.js\?[^"\'<>]*\bid=' . $id_re . '\b/i', $html ) ) { $method = 'loader'; } elseif ( preg_match( '/\/ns\.html\?[^"\'<>]*\bid=' . $id_re . '\b/i', $html ) ) { $method = 'noscript'; } elseif ( preg_match( '/[\'"]' . $id_re . '[\'"]/', $html ) ) { $method = 'inline'; } elseif ( in_array( $container_id, $ids, true ) ) { $method = 'other'; } $found = ( 'none' !== $method ); $result['found'] = $found; $result['matched_container'] = $found; $result['method'] = $method; $result['other_ids'] = array_values( array_diff( $ids, [ $container_id ] ) ); return $result; } /** * Fetch the site's home page HTML the way an anonymous visitor sees it. * Returns null on any transport / non-2xx-3xx error so the caller can treat * detection as inconclusive rather than "no snippet". */ private static function fetch_home_html(): ?string { $response = wp_remote_get( home_url( '/' ), [ 'timeout' => 15, 'redirection' => 3, // A staging / self-hosted site may serve a self-signed cert; we // are only fetching our own front end, so this is safe here. 'sslverify' => false, // No cookies => never the logged-in admin variant of the page. 'cookies' => [], 'headers' => [ 'Cache-Control' => 'no-cache', ], 'user-agent' => 'Adbot-WP-SnippetScan/' . ( defined( 'ADBOT_VERSION' ) ? ADBOT_VERSION : '0.0.0' ), ] ); if ( is_wp_error( $response ) ) { return null; } $code = (int) wp_remote_retrieve_response_code( $response ); if ( $code < 200 || $code >= 400 ) { return null; } return (string) wp_remote_retrieve_body( $response ); } /** * Remove Adbot's own GTM snippet from the HTML so it is never mistaken for a * pre-existing third-party install. Mirrors the exact markers emitted by * Snippet_Injector. */ private static function strip_adbot_snippet( string $html ): string { $html = preg_replace( '/.*?/s', '', $html ); $html = preg_replace( '/.*?/s', '', (string) $html ); // Defensive: drop any element still carrying the data-adbot="gtm" marker. $html = preg_replace( '/<(script|noscript)[^>]*data-adbot=["\']gtm["\'][^>]*>.*?<\/\1>/is', '', (string) $html ); return null === $html ? '' : $html; } }