… block (it does NOT parse attributes). * 2. Each block is parsed individually with DOMDocument to read the src reliably. * 3. Only the matched iframe block is replaced in the original HTML via callback. * * The rest of the page HTML is never re-serialized, so themes, page builders, * inline JS and JSON-LD stay byte-for-byte intact. * * Known limitation: Only iframes present in the initial server-rendered HTML are * covered here. For iframes injected later by JavaScript, use the manual shortcode * [content_blocker id="…"] around the embed. */ class CB_Autodetect { public static function init(): void { add_action( 'template_redirect', [ __CLASS__, 'start_buffer' ] ); } public static function start_buffer(): void { if ( is_admin() || wp_doing_ajax() || wp_doing_cron() ) { return; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return; } if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { return; } $services = CB_Settings::get_services(); $active = array_values( array_filter( $services, fn( $s ) => ! empty( $s['match_pattern'] ) && ( $s['enabled'] ?? true ) ) ); if ( empty( $active ) ) { return; } ob_start( fn( string $html ) => self::process( $html, $active ) ); } public static function process( string $html, array $services ): string { if ( $html === '' || stripos( $html, ']*>.*?#is', function ( array $m ) use ( $services ): string { return self::maybe_replace_iframe( $m[0], $services ); }, $html ); } /** * Parse a single iframe block with DOMDocument, read its src, and return either * the consent placeholder (on match) or the unchanged original block. */ private static function maybe_replace_iframe( string $iframe_html, array $services ): string { $src = self::get_src( $iframe_html ); if ( $src === '' ) { return $iframe_html; } foreach ( $services as $svc ) { $pattern = $svc['match_pattern'] ?? ''; if ( $pattern !== '' && str_contains( $src, $pattern ) ) { $attrs = CB_Renderer::extract_iframe_attrs( $iframe_html ); $dims = [ 'width' => $attrs['width'], 'height' => $attrs['height'] ]; return CB_Renderer::render_placeholder( $svc, $src, $dims ); } } return $iframe_html; } /** Reliably extract the src attribute of a single iframe via DOMDocument. */ private static function get_src( string $iframe_html ): string { $dom = new DOMDocument(); libxml_use_internal_errors( true ); // Wrap so loadHTML has a clean context; force UTF-8 so umlauts survive. $dom->loadHTML( '
' . $iframe_html . '
', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); libxml_clear_errors(); $node = $dom->getElementsByTagName( 'iframe' )->item( 0 ); if ( ! $node instanceof DOMElement ) { return ''; } $src = trim( $node->getAttribute( 'src' ) ); // Normalise protocol-relative and HTML-entity-encoded ampersands. $src = str_replace( '&', '&', $src ); return $src; } }