diff --git a/gdpr-content-blocker/assets/frontend.css b/gdpr-content-blocker/assets/frontend.css index f3e444e..af867d8 100644 --- a/gdpr-content-blocker/assets/frontend.css +++ b/gdpr-content-blocker/assets/frontend.css @@ -20,6 +20,7 @@ /* ── Wrapper / placeholder ── */ .cb-blocker { + position: relative; /* anchor for the bottom-right badge */ display: flex; align-items: flex-start; /* pin content to the top */ justify-content: center; @@ -27,12 +28,29 @@ background-color: var(--cb-bg); color: var(--cb-text); box-sizing: border-box; - padding: 24px 8px 20px; /* min 20px top/bottom, 8px sides */ + padding: 24px 8px 22px; /* min 20px top/bottom, 8px sides */ font-family: inherit; border-radius: 4px; /* No reserved height: the placeholder is exactly as tall as its content. */ } +/* ── Promo badge (bottom-right) ── */ +.cb-blocker .cb-blocker__badge { + position: absolute; + right: 8px; + bottom: 5px; + font-size: 0.6875rem; + line-height: 1; + color: var(--cb-text); + opacity: 0.55; + text-decoration: none; + white-space: nowrap; +} +.cb-blocker .cb-blocker__badge:hover { + opacity: 1; + text-decoration: underline; +} + /* ── Inner content ── */ .cb-blocker .cb-blocker__inner { width: 100%; diff --git a/gdpr-content-blocker/assets/frontend.js b/gdpr-content-blocker/assets/frontend.js index efd51fe..8b0343b 100644 --- a/gdpr-content-blocker/assets/frontend.js +++ b/gdpr-content-blocker/assets/frontend.js @@ -319,6 +319,18 @@ inner.appendChild( label ); wrap.appendChild( inner ); + + // Promo badge (bottom-right), mirrors the server-rendered placeholder. + if ( CFG.showBadge ) { + const badge = document.createElement( 'a' ); + badge.className = 'cb-blocker__badge'; + badge.href = 'https://lucas-orth.de/wp-plugins/gdpr-content-blocker/'; + badge.target = '_blank'; + badge.rel = 'noopener noreferrer nofollow sponsored'; + badge.textContent = 'GDPR Content Blocker'; + wrap.appendChild( badge ); + } + return wrap; } diff --git a/gdpr-content-blocker/gdpr-content-blocker.php b/gdpr-content-blocker/gdpr-content-blocker.php index b067506..77543df 100644 --- a/gdpr-content-blocker/gdpr-content-blocker.php +++ b/gdpr-content-blocker/gdpr-content-blocker.php @@ -3,7 +3,7 @@ * Plugin Name: GDPR Content Blocker * Plugin URI: https://lucas-orth.de * Description: DSGVO-konformer Consent-Blocker für externe iframes. Lädt Drittinhalte erst nach aktiver Einwilligung. - * Version: 1.4.9 + * Version: 1.5.0 * Author: Lucas Orth * Author URI: https://lucas-orth.de * Text Domain: gdpr-content-blocker @@ -15,7 +15,7 @@ defined( 'ABSPATH' ) || exit; -define( 'CB_VERSION', '1.4.9' ); +define( 'CB_VERSION', '1.5.0' ); define( 'CB_FILE', __FILE__ ); define( 'CB_DIR', plugin_dir_path( __FILE__ ) ); define( 'CB_URL', plugin_dir_url( __FILE__ ) ); diff --git a/gdpr-content-blocker/includes/class-license.php b/gdpr-content-blocker/includes/class-license.php index 10a30de..8e40da8 100644 --- a/gdpr-content-blocker/includes/class-license.php +++ b/gdpr-content-blocker/includes/class-license.php @@ -64,6 +64,7 @@ class CB_License { 'last_check' => 0, 'message' => '', 'pending_domains' => [], + 'max_activations' => 0, // -1 = unlimited (from the backend) ] ); if ( ! is_array( $lic['pending_domains'] ) ) { $lic['pending_domains'] = []; @@ -75,6 +76,12 @@ class CB_License { return self::get_license()['status'] === 'active'; } + /** True only for an active, unlimited-seat (max_activations = -1) license. */ + public static function is_unlimited(): bool { + $lic = self::get_license(); + return $lic['status'] === 'active' && (int) ( $lic['max_activations'] ?? 0 ) === -1; + } + /** Mask a key for display: keep dashes + last 4 chars, hide the rest. */ public static function mask_key( string $key ): string { $n = strlen( $key ); @@ -148,7 +155,7 @@ class CB_License { } if ( ! empty( $result['ok'] ) && ( $result['status'] ?? '' ) === 'valid' ) { - self::store( $key, 'active', __( 'Lizenz erfolgreich aktiviert.', 'gdpr-content-blocker' ) ); + self::store( $key, 'active', __( 'Lizenz erfolgreich aktiviert.', 'gdpr-content-blocker' ), [], isset( $result['max_activations'] ) ? (int) $result['max_activations'] : null ); } elseif ( ( $result['code'] ?? '' ) === 'limit_reached' ) { // No free slots: keep the key and offer to release one of the bound domains. $domains = is_array( $result['domains'] ?? null ) ? $result['domains'] : []; @@ -216,7 +223,7 @@ class CB_License { /* translators: %s: released domain */ __( 'Domain %s freigegeben und diese Seite aktiviert.', 'gdpr-content-blocker' ), $release - ) ); + ), [], isset( $result['max_activations'] ) ? (int) $result['max_activations'] : null ); } else { $domains = is_array( $result['domains'] ?? null ) ? $result['domains'] : $lic['pending_domains']; $msg = $result['error'] ?? __( 'Aktivierung fehlgeschlagen.', 'gdpr-content-blocker' ); @@ -269,14 +276,18 @@ class CB_License { } if ( ! empty( $result['ok'] ) && ( $result['status'] ?? '' ) === 'valid' ) { - self::store( $lic['key'], 'active', '' ); + self::store( $lic['key'], 'active', '', [], isset( $result['max_activations'] ) ? (int) $result['max_activations'] : null ); } else { $msg = $result['error'] ?? __( 'Lizenz nicht mehr gültig.', 'gdpr-content-blocker' ); self::store( $lic['key'], 'invalid', $msg ); } } - private static function store( string $key, string $status, string $message, array $pending_domains = [] ): void { + private static function store( string $key, string $status, string $message, array $pending_domains = [], ?int $max = null ): void { + // Preserve the previously stored seat count unless a fresh one is given. + $prev = get_option( CB_LICENSE_OPTION, [] ); + $prev_max = is_array( $prev ) && isset( $prev['max_activations'] ) ? (int) $prev['max_activations'] : 0; + update_option( CB_LICENSE_OPTION, [ 'key' => $key, 'status' => $status, @@ -284,6 +295,7 @@ class CB_License { 'last_check' => time(), 'message' => $message, 'pending_domains' => array_values( array_filter( array_map( 'strval', $pending_domains ) ) ), + 'max_activations' => $max !== null ? $max : $prev_max, ] ); // License state changed → invalidate cached update info and force WP to diff --git a/gdpr-content-blocker/includes/class-renderer.php b/gdpr-content-blocker/includes/class-renderer.php index 75bd872..d5c6027 100644 --- a/gdpr-content-blocker/includes/class-renderer.php +++ b/gdpr-content-blocker/includes/class-renderer.php @@ -107,8 +107,9 @@ class CB_Renderer { // 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(), - 'i18n' => [ + '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' ), @@ -366,9 +367,25 @@ class CB_Renderer { . esc_html__( 'Diesen Dienst künftig immer laden', 'gdpr-content-blocker' ) . '' . '' + . ( 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. diff --git a/gdpr-content-blocker/includes/class-settings.php b/gdpr-content-blocker/includes/class-settings.php index 03e6e60..244e0a8 100644 --- a/gdpr-content-blocker/includes/class-settings.php +++ b/gdpr-content-blocker/includes/class-settings.php @@ -50,6 +50,9 @@ class CB_Settings { $clean['custom_css'] = isset( $input['custom_css'] ) ? wp_strip_all_tags( $input['custom_css'] ) : ''; + // Hiding the promo badge only ever takes effect with an active unlimited + // licence (enforced again at render time), but we store the choice as-is. + $clean['hide_badge'] = ! empty( $input['hide_badge'] ); return $clean; } @@ -69,6 +72,7 @@ class CB_Settings { 'button_hover_bg' => '#1a369a', 'button_hover_text' => '#ffffff', 'custom_css' => '', + 'hide_badge' => false, ]; } @@ -666,6 +670,18 @@ class CB_Settings { echo '
' . esc_html__( 'Das Ausblenden ist nur mit einer aktiven Unlimited-Lizenz möglich.', 'gdpr-content-blocker' ) . '
'; + } + echo '' . esc_html__( 'Wird nach den CSS-Variablen eingebunden und kann diese überschreiben. Tipp: denselben Präfix verwenden, z. B. .cb-blocker .cb-blocker__button { … }', 'gdpr-content-blocker' ) . '
'; diff --git a/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.mo b/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.mo index 6674634..f60b23b 100644 Binary files a/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.mo and b/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.mo differ diff --git a/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.po b/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.po index d01ce0b..fd4229e 100644 --- a/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.po +++ b/gdpr-content-blocker/languages/gdpr-content-blocker-en_US.po @@ -312,6 +312,15 @@ msgstr "Button hover: background color" msgid "Button Hover: Textfarbe" msgstr "Button hover: text color" +msgid "Werbe-Badge" +msgstr "Promo badge" + +msgid "Badge „GDPR Content Blocker\" im Platzhalter ausblenden" +msgstr "Hide the \"GDPR Content Blocker\" badge in the placeholder" + +msgid "Das Ausblenden ist nur mit einer aktiven Unlimited-Lizenz möglich." +msgstr "Hiding it is only possible with an active unlimited licence." + msgid "Custom CSS" msgstr "Custom CSS" diff --git a/hilfsdaten/strings.txt b/hilfsdaten/strings.txt index 7877f18..64cc64a 100644 --- a/hilfsdaten/strings.txt +++ b/hilfsdaten/strings.txt @@ -1,4 +1,4 @@ -# 131 unique strings +# 134 unique strings MSGID Ungültige Antwort vom Lizenzserver. MSGID Keine Berechtigung. MSGID Bitte einen Lizenzschlüssel eingeben. @@ -101,6 +101,9 @@ MSGID Button: Hintergrundfarbe MSGID Button: Textfarbe MSGID Button Hover: Hintergrundfarbe MSGID Button Hover: Textfarbe +MSGID Werbe-Badge +MSGID Badge „GDPR Content Blocker" im Platzhalter ausblenden +MSGID Das Ausblenden ist nur mit einer aktiven Unlimited-Lizenz möglich. MSGID Custom CSS MSGID Wird nach den CSS-Variablen eingebunden und kann diese überschreiben. Tipp: denselben Präfix verwenden, z. B. .cb-blocker .cb-blocker__button { … } MSGID CSS-Klassen: diff --git a/hilfsdaten/translations-en.json b/hilfsdaten/translations-en.json index 9e642db..3b9c9a4 100644 --- a/hilfsdaten/translations-en.json +++ b/hilfsdaten/translations-en.json @@ -130,5 +130,8 @@ "Erteilt die Einwilligung für einen Dienst (z. B. einen reinen Skript-Blocker ohne sichtbaren Platzhalter) und lädt die Seite neu, sodass das Skript freigegeben wird.": "Grants consent for a service (e.g. a script-only blocker with no visible placeholder) and reloads the page so the script is released.", "Pflicht. Die ID des Dienstes (siehe Tab „Dienste\", klein unter dem Namen), z. B. eigenes-matomo.": "Required. The service ID (see the \"Services\" tab, shown small under the name), e.g. eigenes-matomo.", "Die Beschriftung der Schaltfläche bzw. des Links, z. B. text=\"Statistik aktivieren\".": "The label of the button or link, e.g. text=\"Enable statistics\".", - "style=\"button\" zeigt eine Schaltfläche (Standard), style=\"link\" einen schlichten Textlink.": "style=\"button\" shows a button (default), style=\"link\" a plain text link." + "style=\"button\" zeigt eine Schaltfläche (Standard), style=\"link\" einen schlichten Textlink.": "style=\"button\" shows a button (default), style=\"link\" a plain text link.", + "Werbe-Badge": "Promo badge", + "Badge „GDPR Content Blocker\" im Platzhalter ausblenden": "Hide the \"GDPR Content Blocker\" badge in the placeholder", + "Das Ausblenden ist nur mit einer aktiven Unlimited-Lizenz möglich.": "Hiding it is only possible with an active unlimited licence." } \ No newline at end of file