tags. */ public static function script_services(): array { $out = []; foreach ( CB_Settings::get_services() as $svc ) { if ( empty( $svc['match_pattern'] ) || empty( $svc['loads_script'] ) || ! ( $svc['enabled'] ?? true ) ) { continue; } $out[] = [ 'id' => $svc['id'], 'match' => $svc['match_pattern'] ]; } return $out; } /** * Inline guard printed very early in . It blocks third-party scripts * (both already in the DOM and dynamically injected) until the matching * service has consent, by neutralising the \n"; } public static function enqueue_assets(): void { wp_enqueue_style( 'cb-frontend', CB_URL . 'assets/frontend.css', [], CB_VERSION ); $style = CB_Settings::get_style(); // Build one inline block: CSS variables first, the user's Custom-CSS LAST // so it always overrides. Emitted as a single wp_add_inline_style call. $align = in_array( $style['text_align'] ?? 'center', [ 'left', 'center', 'right' ], true ) ? $style['text_align'] : 'center'; $justify = [ 'left' => 'flex-start', 'center' => 'center', 'right' => 'flex-end' ][ $align ]; $inline = sprintf( ':root{--cb-text:%s;--cb-bg:%s;--cb-btn-bg:%s;--cb-btn-text:%s;--cb-btn-hover-bg:%s;--cb-btn-hover-text:%s;--cb-align:%s;--cb-justify:%s;}', esc_attr( $style['text_color'] ), esc_attr( $style['bg_color'] ), esc_attr( $style['button_bg'] ), esc_attr( $style['button_text'] ), esc_attr( $style['button_hover_bg'] ), esc_attr( $style['button_hover_text'] ), esc_attr( $align ), esc_attr( $justify ) ); if ( trim( (string) $style['custom_css'] ) !== '' ) { $inline .= "\n/* gdpr-content-blocker custom css */\n" . $style['custom_css']; } wp_add_inline_style( 'cb-frontend', $inline ); wp_enqueue_script( 'cb-frontend', CB_URL . 'assets/frontend.js', [], CB_VERSION, true ); // Service data + i18n for client-side detection (JS-injected iframes such // as Elementor video widgets, sliders, …). wp_localize_script( 'cb-frontend', 'cbConfig', [ 'services' => self::services_for_js(), 'showBadge' => self::show_badge(), 'i18n' => [ 'recipient' => __( 'Empfänger:', 'gdpr-content-blocker' ), 'purpose' => __( 'Zweck:', 'gdpr-content-blocker' ), 'thirdCountry' => __( 'Datenübermittlung in ein Drittland außerhalb der EU/des EWR', 'gdpr-content-blocker' ), 'privacy' => __( 'Datenschutzerklärung des Anbieters', 'gdpr-content-blocker' ), 'load' => __( '%s jetzt laden', 'gdpr-content-blocker' ), 'remember' => __( 'Diesen Dienst künftig immer laden', 'gdpr-content-blocker' ), 'defaultText' => __( 'Um diesen Inhalt von %s zu laden, ist Ihre Einwilligung erforderlich. Dabei werden personenbezogene Daten (z. B. Ihre IP-Adresse) an den Anbieter übertragen.', 'gdpr-content-blocker' ), ], ] ); } /** Minimal, public service data for client-side detection. */ private static function services_for_js(): array { $out = []; foreach ( CB_Settings::get_services() as $svc ) { if ( empty( $svc['match_pattern'] ) || ! ( $svc['enabled'] ?? true ) ) { continue; } $out[] = [ 'id' => $svc['id'], 'name' => $svc['name'], 'match' => $svc['match_pattern'], 'recipient' => $svc['recipient'], 'third_country' => ! empty( $svc['third_country'] ), 'purpose' => $svc['purpose'], 'privacy_url' => $svc['privacy_url'] ?? '', 'placeholder' => $svc['placeholder_text'] ?? '', ]; } return $out; } /** * Shortcode: [content_blocker id="google-maps"][/content_blocker] */ public static function shortcode( array $atts, ?string $content = null ): string { $atts = shortcode_atts( [ 'id' => '' ], $atts, 'content_blocker' ); $id = sanitize_key( $atts['id'] ); if ( $id === '' ) { return ''; } $svc = CB_Settings::get_service( $id ); if ( $svc === null ) { return ''; } // Disabled blocker → let the embedded content load normally (no blocking). if ( ! ( $svc['enabled'] ?? true ) ) { return $content ?? ''; } // Extract src + dimensions from the inner iframe $src = ''; $dims = []; if ( $content !== null && $content !== '' ) { $attrs = self::extract_iframe_attrs( $content ); $src = $attrs['src']; $dims = [ 'width' => $attrs['width'], 'height' => $attrs['height'] ]; } return self::render_placeholder( $svc, $src, $dims ); } /** * Shortcode: [content_blocker_revoke] * Renders, by default, a visible text link (not a button) so it doesn't get * confused with a cookie banner's revoke control. Attributes: * text="…" custom link label * style="link|button" default "link" * note="yes|no" show the clarifying hint (default "yes") */ public static function revoke_shortcode( array|string $atts = [] ): string { $atts = shortcode_atts( [ 'text' => __( 'Einwilligung für externe Inhalte widerrufen', 'gdpr-content-blocker' ), 'style' => 'link', 'note' => 'yes', ], $atts, 'content_blocker_revoke' ); $class = $atts['style'] === 'button' ? 'cb-revoke-btn' : 'cb-revoke-link'; $out = '' . esc_html( $atts['text'] ) . ''; if ( $atts['note'] === 'yes' ) { $out .= '' . esc_html__( 'Betrifft nur die Freigabe externer Einbettungen (z. B. Karten, Videos). Cookie-Einstellungen werden separat verwaltet.', 'gdpr-content-blocker' ) . ''; } return $out; } /** * Shortcode: [content_blocker_consent id="youtube" text="…" style="button|link"] * A consent control for services that only block scripts (no visible embed * to click). Grants consent for the given service id and reloads so the * blocked scripts/embeds load. */ public static function consent_shortcode( array|string $atts = [] ): string { $atts = shortcode_atts( [ 'id' => '', 'text' => __( 'Externe Inhalte aktivieren', 'gdpr-content-blocker' ), 'style' => 'button', ], $atts, 'content_blocker_consent' ); $id = sanitize_key( $atts['id'] ); if ( $id === '' ) { return ''; } $class = $atts['style'] === 'link' ? 'cb-revoke-link' : 'cb-revoke-btn'; return '' . esc_html( $atts['text'] ) . ''; } /** * Shortcode: [content_blocker_services] * Lists every configured third-party service with the Art. 13 details * (provider, recipient, third-country note, purpose, privacy link). * Meant for embedding in the privacy policy. */ public static function services_shortcode(): string { $services = CB_Settings::get_services(); if ( empty( $services ) ) { return ''; } $out = ''; return $out; } /** * Build the placeholder HTML for a given service. * $src is the real iframe URL (empty string if unknown). * $dims may contain 'width' and 'height' (numbers or e.g. "100%") taken * from the original iframe so the placeholder reserves the same height. */ public static function render_placeholder( array $svc, string $src = '', array $dims = [] ): string { $id = esc_attr( $svc['id'] ); $name = esc_html( $svc['name'] ); $recipient = esc_html( $svc['recipient'] ); $purpose = esc_html( $svc['purpose'] ); $privacy_url = esc_url( $svc['privacy_url'] ?? '' ); $third = ! empty( $svc['third_country'] ); $custom_text = $svc['placeholder_text'] ?? ''; if ( $custom_text !== '' ) { $info_text = esc_html( $custom_text ); } else { $info_text = sprintf( /* translators: %s: provider name */ esc_html__( 'Um diesen Inhalt von %s zu laden, ist Ihre Einwilligung erforderlich. Dabei werden personenbezogene Daten (z. B. Ihre IP-Adresse) an den Anbieter übertragen.', 'gdpr-content-blocker' ), '' . $name . '' ); } $third_note = ''; if ( $third ) { $third_note = '' . esc_html__( 'Datenübermittlung in ein Drittland außerhalb der EU/des EWR', 'gdpr-content-blocker' ) . ''; } $privacy_link = ''; if ( $privacy_url !== '' ) { $privacy_link = '' . esc_html__( 'Datenschutzerklärung des Anbieters', 'gdpr-content-blocker' ) . ''; } $data_src = $src !== '' ? ' data-src="' . esc_attr( $src ) . '"' : ''; // Remember the original dimensions for the iframe that gets created on // consent. The placeholder itself is sized to its own content (no reserved // height), so there is no empty space below it. $width = isset( $dims['width'] ) ? (string) $dims['width'] : ''; $height = isset( $dims['height'] ) ? (string) $dims['height'] : ''; $style_at = ''; $data_dims = ''; if ( $width !== '' ) { $data_dims .= ' data-width="' . esc_attr( $width ) . '"'; } if ( $height !== '' ) { $data_dims .= ' data-height="' . esc_attr( $height ) . '"'; } return '
' . '
' . '

' . $info_text . '

' . '

' . '' . esc_html__( 'Empfänger:', 'gdpr-content-blocker' ) . ' ' . $recipient . $third_note . '

' . '

' . '' . esc_html__( 'Zweck:', 'gdpr-content-blocker' ) . ' ' . $purpose . '

' . ( $privacy_link !== '' ? '

' . $privacy_link . '

' : '' ) . '' . '' . '
' . ( self::show_badge() ? self::badge_html() : '' ) . '
'; } /** * Whether to show the promo badge. Always shown, EXCEPT when the site owner * has an active unlimited licence AND ticked "hide badge" in Appearance. */ public static function show_badge(): bool { $style = CB_Settings::get_style(); return ! ( ! empty( $style['hide_badge'] ) && CB_License::is_unlimited() ); } /** Small "GDPR Content Blocker" badge in the bottom-right of the placeholder. */ private static function badge_html(): string { return 'GDPR Content Blocker'; } /** * Pull src + width + height out of an iframe string. * Height is also read from an inline style="height:NNNpx" if no attribute. * Returns [ 'src' => string, 'width' => string, 'height' => string ]. */ public static function extract_iframe_attrs( string $html ): array { $src = ''; if ( preg_match( '/\bsrc=["\']([^"\']+)["\']/i', $html, $m ) ) { $src = esc_url_raw( $m[1] ); } $width = ''; if ( preg_match( '/\bwidth=["\']?(\d+(?:%|px)?)["\']?/i', $html, $m ) ) { $width = $m[1]; } $height = ''; if ( preg_match( '/\bheight=["\']?(\d+(?:%|px)?)["\']?/i', $html, $m ) ) { $height = $m[1]; } elseif ( preg_match( '/height\s*:\s*(\d+)px/i', $html, $m ) ) { $height = $m[1]; } // Normalise "450px" → "450" so it can be used as a numeric attribute. $width = preg_replace( '/px$/', '', $width ); $height = preg_replace( '/px$/', '', $height ); return [ 'src' => $src, 'width' => $width, 'height' => $height ]; } }