… 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 === '' ) {
return $html;
}
// Pass 1 — iframes: replace matched embeds with a consent placeholder.
if ( stripos( $html, '#is',
function ( array $m ) use ( $services ): string {
return self::maybe_replace_iframe( $m[0], $services );
},
$html
);
// On a PCRE error (e.g. backtrack/recursion limit on a huge page),
// preg_replace_callback returns null. Never blank the page.
if ( $out !== null ) {
$html = $out;
}
}
// Pass 2 — scripts: neutralise