UI:
- Ein-/Ausklappen jetzt mit grossem +/- Icon statt kleinem Pfeil.
- "Entfernen" ist ein Papierkorb-Symbol (dashicon).
- Aktiver Tab klar gekennzeichnet (Akzent-Unterstrich + Farbe).
- 20px Abstand zwischen Tabs und Inhalt.
Funktionen:
- Scan erkennt Anbieter, fuer die es eine Vorlage gibt ("Vorlage verfuegbar"),
und "Vorlage uebernehmen" fuellt die komplette Vorlage statt nur Host/Pattern.
- Platzhalter: Checkbox "Diesen Dienst kuenftig immer laden" (Standard AN).
Abgewaehlt -> Inhalt wird nur einmal geladen, keine dauerhafte Einwilligung.
i18n:
- Sprachumschaltung: Deutsch fuer alle de_* Locales, Englisch fuer alle anderen
(plugin_locale-Filter). Vollstaendige englische Uebersetzung (126 Strings,
inkl. Vorlagentexte/Empfaenger) als gdpr-content-blocker-en_US.po/.mo.
- Helper-Skripte (extract/build) in hilfsdaten/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
826 lines
32 KiB
PHP
826 lines
32 KiB
PHP
<?php
|
||
defined( 'ABSPATH' ) || exit;
|
||
|
||
class CB_Settings {
|
||
|
||
public static function init(): void {
|
||
add_action( 'admin_menu', [ __CLASS__, 'add_menu' ] );
|
||
add_action( 'admin_init', [ __CLASS__, 'register_settings' ] );
|
||
add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_admin_assets' ] );
|
||
add_action( 'admin_post_cb_save_services', [ __CLASS__, 'save_services' ] );
|
||
add_action( 'wp_ajax_cb_scan', [ __CLASS__, 'ajax_scan' ] );
|
||
}
|
||
|
||
public static function add_menu(): void {
|
||
add_options_page(
|
||
__( 'GDPR Content Blocker', 'gdpr-content-blocker' ),
|
||
__( 'GDPR Content Blocker', 'gdpr-content-blocker' ),
|
||
'manage_options',
|
||
'gdpr-content-blocker',
|
||
[ __CLASS__, 'render_page' ]
|
||
);
|
||
}
|
||
|
||
public static function register_settings(): void {
|
||
register_setting(
|
||
'cb_style_group',
|
||
CB_STYLE_OPTION,
|
||
[ 'sanitize_callback' => [ __CLASS__, 'sanitize_style' ] ]
|
||
);
|
||
}
|
||
|
||
public static function sanitize_style( mixed $input ): array {
|
||
$defaults = self::get_style_defaults();
|
||
if ( ! is_array( $input ) ) {
|
||
return $defaults;
|
||
}
|
||
$clean = [];
|
||
$color_keys = [
|
||
'text_color',
|
||
'bg_color',
|
||
'button_bg',
|
||
'button_text',
|
||
'button_hover_bg',
|
||
'button_hover_text',
|
||
];
|
||
foreach ( $color_keys as $key ) {
|
||
$val = isset( $input[ $key ] ) ? sanitize_text_field( $input[ $key ] ) : '';
|
||
$clean[ $key ] = self::sanitize_hex_color( $val ) ?? $defaults[ $key ];
|
||
}
|
||
$clean['custom_css'] = isset( $input['custom_css'] )
|
||
? wp_strip_all_tags( $input['custom_css'] )
|
||
: '';
|
||
return $clean;
|
||
}
|
||
|
||
private static function sanitize_hex_color( string $color ): ?string {
|
||
if ( preg_match( '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $color ) ) {
|
||
return $color;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public static function get_style_defaults(): array {
|
||
return [
|
||
'text_color' => '#ffffff',
|
||
'bg_color' => '#111111',
|
||
'button_bg' => '#2043B7',
|
||
'button_text' => '#ffffff',
|
||
'button_hover_bg' => '#1a369a',
|
||
'button_hover_text' => '#ffffff',
|
||
'custom_css' => '',
|
||
];
|
||
}
|
||
|
||
public static function get_style(): array {
|
||
$saved = get_option( CB_STYLE_OPTION, [] );
|
||
return wp_parse_args( is_array( $saved ) ? $saved : [], self::get_style_defaults() );
|
||
}
|
||
|
||
/** Returns all services from the option, always as a list. */
|
||
public static function get_services(): array {
|
||
$raw = get_option( CB_OPTION, [] );
|
||
return is_array( $raw ) ? array_values( $raw ) : [];
|
||
}
|
||
|
||
/** Returns a single service by id, or null. */
|
||
public static function get_service( string $id ): ?array {
|
||
foreach ( self::get_services() as $svc ) {
|
||
if ( isset( $svc['id'] ) && $svc['id'] === $id ) {
|
||
return $svc;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public static function save_services(): void {
|
||
if ( ! current_user_can( 'manage_options' ) ) {
|
||
wp_die( esc_html__( 'Keine Berechtigung.', 'gdpr-content-blocker' ) );
|
||
}
|
||
check_admin_referer( 'cb_save_services', 'cb_nonce' );
|
||
|
||
$raw_services = isset( $_POST['cb_services'] ) && is_array( $_POST['cb_services'] )
|
||
? $_POST['cb_services']
|
||
: [];
|
||
|
||
$services = [];
|
||
foreach ( $raw_services as $item ) {
|
||
if ( ! is_array( $item ) ) {
|
||
continue;
|
||
}
|
||
$id = sanitize_key( $item['id'] ?? '' );
|
||
if ( $id === '' ) {
|
||
continue;
|
||
}
|
||
$services[] = [
|
||
'id' => $id,
|
||
'name' => sanitize_text_field( $item['name'] ?? '' ),
|
||
'enabled' => ! empty( $item['enabled'] ),
|
||
'match_pattern' => sanitize_text_field( $item['match_pattern'] ?? '' ),
|
||
'recipient' => sanitize_text_field( $item['recipient'] ?? '' ),
|
||
'third_country' => ! empty( $item['third_country'] ),
|
||
'sets_cookie' => ! empty( $item['sets_cookie'] ),
|
||
'loads_script' => ! empty( $item['loads_script'] ),
|
||
'purpose' => sanitize_textarea_field( $item['purpose'] ?? '' ),
|
||
'privacy_url' => esc_url_raw( $item['privacy_url'] ?? '' ),
|
||
'placeholder_text' => sanitize_textarea_field( $item['placeholder_text'] ?? '' ),
|
||
];
|
||
}
|
||
|
||
update_option( CB_OPTION, $services );
|
||
wp_safe_redirect( admin_url( 'options-general.php?page=gdpr-content-blocker&cb_saved=1' ) );
|
||
exit;
|
||
}
|
||
|
||
public static function enqueue_admin_assets( string $hook ): void {
|
||
if ( $hook !== 'settings_page_gdpr-content-blocker' ) {
|
||
return;
|
||
}
|
||
wp_enqueue_style( 'wp-color-picker' );
|
||
wp_enqueue_style( 'dashicons' );
|
||
wp_enqueue_script(
|
||
'cb-admin',
|
||
CB_URL . 'assets/admin.js',
|
||
[ 'wp-color-picker', 'jquery' ],
|
||
CB_VERSION,
|
||
true
|
||
);
|
||
wp_localize_script( 'cb-admin', 'cbAdmin', [
|
||
'confirmRemove' => __( 'Dienst wirklich entfernen?', 'gdpr-content-blocker' ),
|
||
'presets' => self::get_presets(),
|
||
'newServiceLbl' => __( 'Neuer Dienst', 'gdpr-content-blocker' ),
|
||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||
'scanNonce' => wp_create_nonce( 'cb_scan' ),
|
||
'i18n' => [
|
||
'scanning' => __( 'Scanne Webseite …', 'gdpr-content-blocker' ),
|
||
'scanError' => __( 'Scan fehlgeschlagen:', 'gdpr-content-blocker' ),
|
||
'noFindings' => __( 'Keine externen Einbindungen gefunden.', 'gdpr-content-blocker' ),
|
||
'host' => __( 'Anbieter / Host', 'gdpr-content-blocker' ),
|
||
'type' => __( 'Typ', 'gdpr-content-blocker' ),
|
||
'count' => __( 'Anzahl', 'gdpr-content-blocker' ),
|
||
'example' => __( 'Beispiel-URL', 'gdpr-content-blocker' ),
|
||
'status' => __( 'Status', 'gdpr-content-blocker' ),
|
||
'action' => __( 'Aktion', 'gdpr-content-blocker' ),
|
||
'covered' => __( 'abgedeckt', 'gdpr-content-blocker' ),
|
||
'thirdParty' => __( 'Drittanbieter', 'gdpr-content-blocker' ),
|
||
'firstParty' => __( 'eigene Domain', 'gdpr-content-blocker' ),
|
||
'addService' => __( 'Als Dienst übernehmen', 'gdpr-content-blocker' ),
|
||
'useTemplate' => __( 'Vorlage übernehmen', 'gdpr-content-blocker' ),
|
||
'templateAvail' => __( 'Vorlage verfügbar', 'gdpr-content-blocker' ),
|
||
'scannedPages'=> __( 'Gescannte Seiten:', 'gdpr-content-blocker' ),
|
||
'foundOn' => __( 'Gefunden auf', 'gdpr-content-blocker' ),
|
||
],
|
||
] );
|
||
}
|
||
|
||
/**
|
||
* Ready-made templates for the most common third-party iframes.
|
||
* Everything stays editable after insertion.
|
||
*/
|
||
public static function get_presets(): array {
|
||
return [
|
||
'google-maps' => [
|
||
'id' => 'google-maps',
|
||
'name' => 'Google Maps',
|
||
'match_pattern' => 'google.com/maps',
|
||
'recipient' => __( 'Google Ireland Ltd., Irland / Google LLC, USA', 'gdpr-content-blocker' ),
|
||
'third_country' => true,
|
||
'sets_cookie' => true,
|
||
'loads_script' => true,
|
||
'purpose' => __( 'Darstellung interaktiver Karten und Standortinformationen.', 'gdpr-content-blocker' ),
|
||
'privacy_url' => 'https://policies.google.com/privacy',
|
||
],
|
||
'youtube' => [
|
||
'id' => 'youtube',
|
||
'name' => 'YouTube',
|
||
'match_pattern' => 'youtube',
|
||
'recipient' => __( 'Google Ireland Ltd., Irland / Google LLC, USA', 'gdpr-content-blocker' ),
|
||
'third_country' => true,
|
||
'sets_cookie' => true,
|
||
'loads_script' => true,
|
||
'purpose' => __( 'Einbettung und Wiedergabe von Videos.', 'gdpr-content-blocker' ),
|
||
'privacy_url' => 'https://policies.google.com/privacy',
|
||
],
|
||
'openstreetmap' => [
|
||
'id' => 'openstreetmap',
|
||
'name' => 'OpenStreetMap',
|
||
'match_pattern' => 'openstreetmap.org',
|
||
'recipient' => __( 'OpenStreetMap Foundation, Großbritannien', 'gdpr-content-blocker' ),
|
||
'third_country' => true,
|
||
'sets_cookie' => false,
|
||
'loads_script' => true,
|
||
'purpose' => __( 'Darstellung interaktiver Karten.', 'gdpr-content-blocker' ),
|
||
'privacy_url' => 'https://wiki.osmfoundation.org/wiki/Privacy_Policy',
|
||
],
|
||
'vimeo' => [
|
||
'id' => 'vimeo',
|
||
'name' => 'Vimeo',
|
||
'match_pattern' => 'player.vimeo.com',
|
||
'recipient' => __( 'Vimeo LLC, USA', 'gdpr-content-blocker' ),
|
||
'third_country' => true,
|
||
'sets_cookie' => true,
|
||
'loads_script' => true,
|
||
'purpose' => __( 'Einbettung und Wiedergabe von Videos.', 'gdpr-content-blocker' ),
|
||
'privacy_url' => 'https://vimeo.com/privacy',
|
||
],
|
||
];
|
||
}
|
||
|
||
public static function render_page(): void {
|
||
if ( ! current_user_can( 'manage_options' ) ) {
|
||
return;
|
||
}
|
||
$services = self::get_services();
|
||
$style = self::get_style();
|
||
$saved = isset( $_GET['cb_saved'] ) && $_GET['cb_saved'] === '1';
|
||
$settings_saved = isset( $_GET['settings-updated'] ) && $_GET['settings-updated'] === 'true';
|
||
?>
|
||
<div class="wrap cb-admin-wrap">
|
||
<h1><?php esc_html_e( 'GDPR Content Blocker', 'gdpr-content-blocker' ); ?></h1>
|
||
|
||
<?php if ( $saved || $settings_saved ) : ?>
|
||
<div class="notice notice-success is-dismissible">
|
||
<p><?php esc_html_e( 'Einstellungen gespeichert.', 'gdpr-content-blocker' ); ?></p>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<nav class="nav-tab-wrapper">
|
||
<a href="#cb-tab-services" class="nav-tab nav-tab-active" data-cb-tab="services">
|
||
<?php esc_html_e( 'Dienste', 'gdpr-content-blocker' ); ?>
|
||
</a>
|
||
<a href="#cb-tab-scan" class="nav-tab" data-cb-tab="scan">
|
||
<?php esc_html_e( 'Scan', 'gdpr-content-blocker' ); ?>
|
||
</a>
|
||
<a href="#cb-tab-style" class="nav-tab" data-cb-tab="style">
|
||
<?php esc_html_e( 'Darstellung', 'gdpr-content-blocker' ); ?>
|
||
</a>
|
||
<a href="#cb-tab-license" class="nav-tab" data-cb-tab="license">
|
||
<?php esc_html_e( 'Lizenz', 'gdpr-content-blocker' ); ?>
|
||
</a>
|
||
<a href="#cb-tab-about" class="nav-tab" data-cb-tab="about">
|
||
<?php esc_html_e( 'Über das Plugin', 'gdpr-content-blocker' ); ?>
|
||
</a>
|
||
</nav>
|
||
|
||
<!-- Services Tab -->
|
||
<div id="cb-tab-services" class="cb-tab-content">
|
||
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
|
||
<input type="hidden" name="action" value="cb_save_services">
|
||
<?php wp_nonce_field( 'cb_save_services', 'cb_nonce' ); ?>
|
||
|
||
<div id="cb-services-list">
|
||
<?php foreach ( $services as $i => $svc ) : ?>
|
||
<?php self::render_service_row( $i, $svc ); ?>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<div class="cb-add-row">
|
||
<button type="button" id="cb-add-service" class="button">
|
||
<?php esc_html_e( '+ Leeren Dienst hinzufügen', 'gdpr-content-blocker' ); ?>
|
||
</button>
|
||
|
||
<select id="cb-preset-select">
|
||
<option value=""><?php esc_html_e( '— Vorlage einfügen —', 'gdpr-content-blocker' ); ?></option>
|
||
<?php foreach ( self::get_presets() as $key => $preset ) : ?>
|
||
<option value="<?php echo esc_attr( $key ); ?>">
|
||
<?php echo esc_html( $preset['name'] ); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<?php submit_button( __( 'Dienste speichern', 'gdpr-content-blocker' ) ); ?>
|
||
</form>
|
||
|
||
<!-- Hidden template for JS -->
|
||
<template id="cb-service-template">
|
||
<?php self::render_service_row( '__INDEX__', [] ); ?>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Scan Tab -->
|
||
<div id="cb-tab-scan" class="cb-tab-content" style="display:none;">
|
||
<p class="description" style="max-width:760px;">
|
||
<?php esc_html_e( 'Der Lizenzserver besucht Ihre Webseite und listet alle eingebundenen Drittanbieter-Ressourcen (iframes, Skripte, Schriften, Bilder, …) auf. So sehen Sie auf einen Blick, welche externen Dienste Sie blockieren sollten. Erfordert eine aktive Lizenz.', 'gdpr-content-blocker' ); ?>
|
||
</p>
|
||
<p>
|
||
<button type="button" id="cb-scan-btn" class="button button-primary">
|
||
<?php esc_html_e( 'Webseite scannen', 'gdpr-content-blocker' ); ?>
|
||
</button>
|
||
<span id="cb-scan-status" style="margin-left:10px;"></span>
|
||
</p>
|
||
<div id="cb-scan-results"></div>
|
||
</div>
|
||
|
||
<!-- Style Tab -->
|
||
<div id="cb-tab-style" class="cb-tab-content" style="display:none;">
|
||
<form method="post" action="<?php echo esc_url( admin_url( 'options.php' ) ); ?>">
|
||
<?php
|
||
settings_fields( 'cb_style_group' );
|
||
self::render_style_fields( $style );
|
||
submit_button( __( 'Darstellung speichern', 'gdpr-content-blocker' ) );
|
||
?>
|
||
</form>
|
||
|
||
<?php self::render_usage_help(); ?>
|
||
</div>
|
||
|
||
<!-- License Tab -->
|
||
<div id="cb-tab-license" class="cb-tab-content" style="display:none;">
|
||
<?php CB_License::render_tab(); ?>
|
||
</div>
|
||
|
||
<!-- About Tab -->
|
||
<div id="cb-tab-about" class="cb-tab-content" style="display:none;">
|
||
<?php self::render_about(); ?>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.cb-admin-wrap .cb-service-box {
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 10px 16px;
|
||
margin: 8px 0;
|
||
}
|
||
.cb-admin-wrap .cb-service-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
.cb-admin-wrap .cb-service-toggle {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
line-height: 1;
|
||
padding: 4px 6px;
|
||
color: #50575e;
|
||
}
|
||
.cb-admin-wrap .cb-service-title {
|
||
flex: 1;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.cb-admin-wrap .cb-service-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px 24px;
|
||
margin-top: 14px;
|
||
padding-top: 14px;
|
||
border-top: 1px solid #eee;
|
||
}
|
||
/* toggle switch */
|
||
.cb-admin-wrap .cb-switch {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 40px;
|
||
height: 22px;
|
||
flex: 0 0 auto;
|
||
}
|
||
.cb-admin-wrap .cb-switch input {
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
.cb-admin-wrap .cb-switch__slider {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: #c3c4c7;
|
||
border-radius: 22px;
|
||
transition: background 0.2s;
|
||
}
|
||
.cb-admin-wrap .cb-switch__slider::before {
|
||
content: "";
|
||
position: absolute;
|
||
height: 16px;
|
||
width: 16px;
|
||
left: 3px;
|
||
top: 3px;
|
||
background: #fff;
|
||
border-radius: 50%;
|
||
transition: transform 0.2s;
|
||
}
|
||
.cb-admin-wrap .cb-switch input:checked + .cb-switch__slider {
|
||
background: #2043B7;
|
||
}
|
||
.cb-admin-wrap .cb-switch input:checked + .cb-switch__slider::before {
|
||
transform: translateX(18px);
|
||
}
|
||
/* expand/collapse +/- icon */
|
||
.cb-admin-wrap .cb-service-toggle {
|
||
width: 30px;
|
||
height: 30px;
|
||
font-size: 22px;
|
||
line-height: 1;
|
||
font-weight: 400;
|
||
color: #2043B7;
|
||
background: #f0f3fc;
|
||
border: 1px solid #c7d2f5;
|
||
border-radius: 6px;
|
||
padding: 0;
|
||
}
|
||
.cb-admin-wrap .cb-service-toggle:hover {
|
||
background: #e3e9fb;
|
||
}
|
||
/* trash remove button */
|
||
.cb-admin-wrap .cb-remove-service {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 4px;
|
||
color: #888;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
.cb-admin-wrap .cb-remove-service:hover {
|
||
color: #b32d2e;
|
||
}
|
||
.cb-admin-wrap .cb-remove-service .dashicons {
|
||
font-size: 20px;
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
/* active tab indicator */
|
||
.cb-admin-wrap .nav-tab-wrapper {
|
||
margin-bottom: 0;
|
||
}
|
||
.cb-admin-wrap .nav-tab-active,
|
||
.cb-admin-wrap .nav-tab-active:focus,
|
||
.cb-admin-wrap .nav-tab-active:hover {
|
||
background: #fff;
|
||
color: #2043B7;
|
||
border-bottom: 3px solid #2043B7;
|
||
font-weight: 600;
|
||
margin-bottom: -1px;
|
||
}
|
||
/* 20px breathing room between tabs and content */
|
||
.cb-admin-wrap .cb-tab-content {
|
||
padding-top: 20px;
|
||
}
|
||
.cb-admin-wrap .cb-field label {
|
||
display: block;
|
||
font-weight: 600;
|
||
margin-bottom: 4px;
|
||
}
|
||
.cb-admin-wrap .cb-field input[type="text"],
|
||
.cb-admin-wrap .cb-field input[type="url"],
|
||
.cb-admin-wrap .cb-field textarea {
|
||
width: 100%;
|
||
}
|
||
.cb-admin-wrap .cb-field-full {
|
||
grid-column: 1 / -1;
|
||
}
|
||
.cb-admin-wrap .cb-checks {
|
||
display: flex;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.cb-admin-wrap .cb-add-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
margin: 12px 0;
|
||
}
|
||
.cb-admin-wrap .cb-add-row select {
|
||
max-width: 240px;
|
||
}
|
||
</style>
|
||
<?php
|
||
}
|
||
|
||
public static function render_service_row( int|string $index, array $svc ): void {
|
||
$f = fn( string $key, string $default = '' ) => esc_attr( $svc[ $key ] ?? $default );
|
||
$b = fn( string $key ) => ! empty( $svc[ $key ] );
|
||
$idx = esc_attr( (string) $index );
|
||
$name = $svc['name'] ?? '';
|
||
$id = $svc['id'] ?? '';
|
||
$enabled = $svc['enabled'] ?? true; // default ON (new + legacy services)
|
||
// Header label: "interner-name — Anbietername"
|
||
$headname = trim( ( $id !== '' ? $id : __( 'neu', 'gdpr-content-blocker' ) ) . ( $name !== '' ? ' — ' . $name : '' ) );
|
||
?>
|
||
<div class="cb-service-box" data-cb-index="<?php echo $idx; ?>">
|
||
<div class="cb-service-head">
|
||
<button type="button" class="cb-service-toggle" aria-expanded="false" aria-label="<?php esc_attr_e( 'Details anzeigen/ausblenden', 'gdpr-content-blocker' ); ?>">+</button>
|
||
<span class="cb-service-title"><?php echo esc_html( $headname ); ?></span>
|
||
|
||
<label class="cb-switch" title="<?php esc_attr_e( 'Blocker aktiv/inaktiv', 'gdpr-content-blocker' ); ?>">
|
||
<input type="checkbox" class="cb-input-enabled" name="cb_services[<?php echo $idx; ?>][enabled]"
|
||
value="1" <?php checked( $enabled ); ?>>
|
||
<span class="cb-switch__slider"></span>
|
||
</label>
|
||
|
||
<button type="button" class="cb-remove-service" aria-label="<?php esc_attr_e( 'Dienst entfernen', 'gdpr-content-blocker' ); ?>" title="<?php esc_attr_e( 'Dienst entfernen', 'gdpr-content-blocker' ); ?>">
|
||
<span class="dashicons dashicons-trash"></span>
|
||
</button>
|
||
</div>
|
||
<div class="cb-service-grid" style="display:none;">
|
||
<div class="cb-field">
|
||
<label><?php esc_html_e( 'Interner Name (Slug)', 'gdpr-content-blocker' ); ?> *</label>
|
||
<input type="text" name="cb_services[<?php echo $idx; ?>][id]"
|
||
value="<?php echo $f( 'id' ); ?>" required
|
||
placeholder="google-maps" pattern="[a-z0-9\-]+" class="cb-input-id">
|
||
</div>
|
||
<div class="cb-field">
|
||
<label><?php esc_html_e( 'Anbietername', 'gdpr-content-blocker' ); ?> *</label>
|
||
<input type="text" name="cb_services[<?php echo $idx; ?>][name]"
|
||
value="<?php echo $f( 'name' ); ?>" required class="cb-input-name">
|
||
</div>
|
||
<div class="cb-field">
|
||
<label><?php esc_html_e( 'Erkennungsmuster (Domain/Pfad)', 'gdpr-content-blocker' ); ?></label>
|
||
<input type="text" name="cb_services[<?php echo esc_attr( (string) $index ); ?>][match_pattern]"
|
||
value="<?php echo $f( 'match_pattern' ); ?>"
|
||
placeholder="google.com/maps">
|
||
</div>
|
||
<div class="cb-field">
|
||
<label><?php esc_html_e( 'Empfänger (inkl. Land)', 'gdpr-content-blocker' ); ?> *</label>
|
||
<input type="text" name="cb_services[<?php echo esc_attr( (string) $index ); ?>][recipient]"
|
||
value="<?php echo $f( 'recipient' ); ?>"
|
||
placeholder="Google Ireland Ltd. / USA" required>
|
||
</div>
|
||
<div class="cb-field">
|
||
<label><?php esc_html_e( 'Datenschutz-URL des Anbieters', 'gdpr-content-blocker' ); ?></label>
|
||
<input type="url" name="cb_services[<?php echo esc_attr( (string) $index ); ?>][privacy_url]"
|
||
value="<?php echo esc_attr( esc_url( $svc['privacy_url'] ?? '' ) ); ?>"
|
||
placeholder="https://policies.google.com/privacy">
|
||
</div>
|
||
<div class="cb-field cb-field-full">
|
||
<label><?php esc_html_e( 'Verarbeitungszweck', 'gdpr-content-blocker' ); ?> *</label>
|
||
<textarea name="cb_services[<?php echo esc_attr( (string) $index ); ?>][purpose]"
|
||
rows="2" required><?php echo esc_textarea( $svc['purpose'] ?? '' ); ?></textarea>
|
||
</div>
|
||
<div class="cb-field cb-field-full">
|
||
<label><?php esc_html_e( 'Individueller Platzhaltertext (leer = Standard)', 'gdpr-content-blocker' ); ?></label>
|
||
<textarea name="cb_services[<?php echo esc_attr( (string) $index ); ?>][placeholder_text]"
|
||
rows="2"><?php echo esc_textarea( $svc['placeholder_text'] ?? '' ); ?></textarea>
|
||
</div>
|
||
<div class="cb-field cb-field-full cb-checks">
|
||
<label>
|
||
<input type="checkbox" name="cb_services[<?php echo esc_attr( (string) $index ); ?>][third_country]"
|
||
value="1" <?php checked( $b( 'third_country' ) ); ?>>
|
||
<?php esc_html_e( 'Datenübermittlung in Drittland (außerhalb EU/EWR)', 'gdpr-content-blocker' ); ?>
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" name="cb_services[<?php echo esc_attr( (string) $index ); ?>][sets_cookie]"
|
||
value="1" <?php checked( $b( 'sets_cookie' ) ); ?>>
|
||
<?php esc_html_e( 'Setzt Cookies', 'gdpr-content-blocker' ); ?>
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" name="cb_services[<?php echo esc_attr( (string) $index ); ?>][loads_script]"
|
||
value="1" <?php checked( $b( 'loads_script' ) ); ?>>
|
||
<?php esc_html_e( 'Lädt externe Skripte', 'gdpr-content-blocker' ); ?>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
private static function render_style_fields( array $style ): void {
|
||
$fields = [
|
||
'text_color' => __( 'Textfarbe Platzhalter', 'gdpr-content-blocker' ),
|
||
'bg_color' => __( 'Hintergrundfarbe Platzhalter', 'gdpr-content-blocker' ),
|
||
'button_bg' => __( 'Button: Hintergrundfarbe', 'gdpr-content-blocker' ),
|
||
'button_text' => __( 'Button: Textfarbe', 'gdpr-content-blocker' ),
|
||
'button_hover_bg' => __( 'Button Hover: Hintergrundfarbe', 'gdpr-content-blocker' ),
|
||
'button_hover_text' => __( 'Button Hover: Textfarbe', 'gdpr-content-blocker' ),
|
||
];
|
||
echo '<table class="form-table" role="presentation"><tbody>';
|
||
foreach ( $fields as $key => $label ) {
|
||
$val = esc_attr( $style[ $key ] );
|
||
echo '<tr><th scope="row"><label>' . esc_html( $label ) . '</label></th>';
|
||
echo '<td><input type="text" class="cb-color-picker" name="' . esc_attr( CB_STYLE_OPTION . '[' . $key . ']' ) . '" value="' . $val . '" data-default-color="' . $val . '"></td></tr>';
|
||
}
|
||
echo '<tr><th scope="row"><label>' . esc_html__( 'Custom CSS', 'gdpr-content-blocker' ) . '</label></th>';
|
||
echo '<td><textarea name="' . esc_attr( CB_STYLE_OPTION . '[custom_css]' ) . '" rows="8" class="large-text code">' . esc_textarea( $style['custom_css'] ) . '</textarea>';
|
||
echo '<p class="description">' . 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' ) . '</p></td></tr>';
|
||
echo '</tbody></table>';
|
||
}
|
||
|
||
/**
|
||
* Help box explaining shortcodes, revoke, auto-detection and CSS classes.
|
||
* Shown below "Darstellung speichern".
|
||
*/
|
||
private static function render_usage_help(): void {
|
||
$code = static fn( string $s ): string => '<code>' . esc_html( $s ) . '</code>';
|
||
?>
|
||
<hr style="margin:28px 0 18px;">
|
||
<h2><?php esc_html_e( 'Funktionen & Shortcodes', 'gdpr-content-blocker' ); ?></h2>
|
||
<table class="widefat striped" style="max-width:900px;">
|
||
<tbody>
|
||
<tr>
|
||
<td style="width:280px;vertical-align:top;"><strong><?php esc_html_e( 'Widerruf-Button (Pflicht in der Datenschutzerklärung)', 'gdpr-content-blocker' ); ?></strong><br>
|
||
<?php echo $code( '[content_blocker_revoke]' ); ?></td>
|
||
<td><?php esc_html_e( 'Rendert einen gut sichtbaren Link, der die Einwilligung für externe Einbettungen widerruft und die Seite neu lädt (Art. 7 Abs. 3 DSGVO). Betrifft NICHT die Cookie-Einwilligung eines separaten Cookie-Plugins. Optionen: text="…", style="link|button", note="yes|no". In die Datenschutzerklärung einfügen.', 'gdpr-content-blocker' ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="vertical-align:top;"><strong><?php esc_html_e( 'Inhalt manuell blockieren', 'gdpr-content-blocker' ); ?></strong><br>
|
||
<?php echo $code( '[content_blocker id="google-maps"]…iframe…[/content_blocker]' ); ?></td>
|
||
<td><?php esc_html_e( 'Umschließt ein iframe und ersetzt es durch den Platzhalter des angegebenen Dienstes. Verwenden Sie den internen Namen aus dem Tab „Dienste". Ideal für Inhalte, die nicht automatisch erkannt werden (z. B. per JavaScript nachgeladene iframes).', 'gdpr-content-blocker' ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="vertical-align:top;"><strong><?php esc_html_e( 'Dienste-Übersicht (für die Datenschutzerklärung)', 'gdpr-content-blocker' ); ?></strong><br>
|
||
<?php echo $code( '[content_blocker_services]' ); ?></td>
|
||
<td><?php esc_html_e( 'Listet alle konfigurierten Dienste mit Empfänger, Drittland-Hinweis, Zweck und Datenschutz-Link auf. Ideal zum Einbinden in die Datenschutzerklärung (Art. 13 DSGVO).', 'gdpr-content-blocker' ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="vertical-align:top;"><strong><?php esc_html_e( 'Automatische Erkennung', 'gdpr-content-blocker' ); ?></strong></td>
|
||
<td><?php esc_html_e( 'iframes im Server-HTML, deren URL auf das „Erkennungsmuster" eines Dienstes passt, werden automatisch blockiert. Greift nur bei statischem HTML – für JS-iframes den Shortcode oben nutzen.', 'gdpr-content-blocker' ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="vertical-align:top;"><strong><?php esc_html_e( 'Webseiten-Scan', 'gdpr-content-blocker' ); ?></strong></td>
|
||
<td><?php esc_html_e( 'Im Tab „Scan" listet der Lizenzserver alle eingebundenen Drittanbieter auf. Erfordert eine aktive Lizenz.', 'gdpr-content-blocker' ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="vertical-align:top;"><strong><?php esc_html_e( 'CSS-Klassen', 'gdpr-content-blocker' ); ?></strong></td>
|
||
<td>
|
||
<?php echo $code( '.cb-blocker' ); ?>,
|
||
<?php echo $code( '.cb-blocker__text' ); ?>,
|
||
<?php echo $code( '.cb-blocker__recipient' ); ?>,
|
||
<?php echo $code( '.cb-blocker__purpose' ); ?>,
|
||
<?php echo $code( '.cb-blocker__privacy-link' ); ?>,
|
||
<?php echo $code( '.cb-blocker__button' ); ?>,
|
||
<?php echo $code( '.cb-revoke-btn' ); ?>
|
||
<p class="description" style="margin-top:6px;"><?php esc_html_e( 'Eigene Regeln im Custom-CSS mit Präfix .cb-blocker schreiben, damit sie das Theme überschreiben.', 'gdpr-content-blocker' ); ?></p>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<?php
|
||
}
|
||
|
||
/** "Über das Plugin" tab: description + legal links. */
|
||
private static function render_about(): void {
|
||
?>
|
||
<div style="max-width:760px;">
|
||
<h2>GDPR Content Blocker</h2>
|
||
<p class="description" style="font-size:13px;">
|
||
<?php
|
||
printf(
|
||
/* translators: %s: version */
|
||
esc_html__( 'Version %s', 'gdpr-content-blocker' ),
|
||
esc_html( CB_VERSION )
|
||
);
|
||
?>
|
||
</p>
|
||
|
||
<p>
|
||
<?php esc_html_e( 'GDPR Content Blocker lädt externe Einbindungen (z. B. Google Maps, YouTube, OpenStreetMap, Vimeo) erst nach aktiver Einwilligung der Besucher. Vor dem Klick wird keine Verbindung zum Drittanbieter aufgebaut – es werden also keine personenbezogenen Daten (z. B. die IP-Adresse) übertragen. So lassen sich externe Inhalte DSGVO-konform einbinden, ohne ein schweres Consent-Tool.', 'gdpr-content-blocker' ); ?>
|
||
</p>
|
||
|
||
<h3><?php esc_html_e( 'Funktionen', 'gdpr-content-blocker' ); ?></h3>
|
||
<ul style="list-style:disc;padding-left:20px;">
|
||
<li><?php esc_html_e( 'Echtes Click-to-Load (kein vorab geladenes iframe)', 'gdpr-content-blocker' ); ?></li>
|
||
<li><?php esc_html_e( 'Granulare Einwilligung pro Dienst', 'gdpr-content-blocker' ); ?></li>
|
||
<li><?php esc_html_e( 'Art.-13-konformer Platzhalter mit Empfänger, Zweck, Drittland-Hinweis und Datenschutz-Link', 'gdpr-content-blocker' ); ?></li>
|
||
<li><?php esc_html_e( 'Einfacher Widerruf per Shortcode (Art. 7 Abs. 3 DSGVO)', 'gdpr-content-blocker' ); ?></li>
|
||
<li><?php esc_html_e( 'Automatische Erkennung gängiger Anbieter + manueller Shortcode', 'gdpr-content-blocker' ); ?></li>
|
||
</ul>
|
||
|
||
<h3><?php esc_html_e( 'Rechtliches', 'gdpr-content-blocker' ); ?></h3>
|
||
<p>
|
||
<a href="https://lucas-orth.de/impressum" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Impressum', 'gdpr-content-blocker' ); ?></a><br>
|
||
<a href="https://lucas-orth.de/datenschutz" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Datenschutzerklärung', 'gdpr-content-blocker' ); ?></a>
|
||
</p>
|
||
|
||
<p class="description">
|
||
<?php esc_html_e( 'Hinweis: Dieses Plugin ist ein technisches Hilfsmittel und ersetzt keine Rechtsberatung. Für die rechtskonforme Konfiguration (Empfänger, Zwecke, Datenschutzerklärung) ist der Seitenbetreiber verantwortlich.', 'gdpr-content-blocker' ); ?>
|
||
</p>
|
||
|
||
<p style="margin-top:20px;">
|
||
<?php
|
||
printf(
|
||
/* translators: %s: author link */
|
||
esc_html__( 'Entwickelt von %s', 'gdpr-content-blocker' ),
|
||
'<a href="https://lucas-orth.de" target="_blank" rel="noopener noreferrer">Lucas Orth</a>'
|
||
);
|
||
?>
|
||
</p>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/* ───────────────────────── Scan (AJAX) ───────────────────────── */
|
||
|
||
/**
|
||
* Build the list of URLs to scan: the home page plus a few published
|
||
* pages/posts, so the scan covers more than just the front page.
|
||
*/
|
||
private static function scan_urls(): array {
|
||
$urls = [ home_url( '/' ) ];
|
||
|
||
$posts = get_posts( [
|
||
'post_type' => [ 'page', 'post' ],
|
||
'post_status' => 'publish',
|
||
'numberposts' => 4,
|
||
'orderby' => 'comment_count', // roughly "most visited"
|
||
'order' => 'DESC',
|
||
'fields' => 'ids',
|
||
'no_found_rows' => true,
|
||
] );
|
||
foreach ( $posts as $pid ) {
|
||
$link = get_permalink( $pid );
|
||
if ( $link ) {
|
||
$urls[] = $link;
|
||
}
|
||
}
|
||
|
||
return array_values( array_unique( $urls ) );
|
||
}
|
||
|
||
public static function ajax_scan(): void {
|
||
if ( ! current_user_can( 'manage_options' ) ) {
|
||
wp_send_json_error( [ 'message' => __( 'Keine Berechtigung.', 'gdpr-content-blocker' ) ], 403 );
|
||
}
|
||
check_ajax_referer( 'cb_scan', 'nonce' );
|
||
|
||
$lic = CB_License::get_license();
|
||
if ( ( $lic['status'] ?? '' ) !== 'active' ) {
|
||
wp_send_json_error( [ 'message' => __( 'Bitte zuerst eine Lizenz aktivieren (Tab „Lizenz").', 'gdpr-content-blocker' ) ] );
|
||
}
|
||
|
||
$response = wp_remote_post( CB_License::api_url() . '/api/v1/scan', [
|
||
'timeout' => 45,
|
||
'headers' => [ 'Content-Type' => 'application/json', 'Accept' => 'application/json' ],
|
||
'body' => wp_json_encode( [
|
||
'key' => $lic['key'],
|
||
'product' => CB_PRODUCT_SLUG,
|
||
'domain' => CB_License::domain(),
|
||
'urls' => self::scan_urls(),
|
||
] ),
|
||
] );
|
||
|
||
if ( is_wp_error( $response ) ) {
|
||
wp_send_json_error( [ 'message' => $response->get_error_message() ] );
|
||
}
|
||
|
||
$data = json_decode( wp_remote_retrieve_body( $response ), true );
|
||
if ( ! is_array( $data ) || empty( $data['ok'] ) ) {
|
||
$msg = is_array( $data ) && ! empty( $data['error'] )
|
||
? $data['error']
|
||
: __( 'Unerwartete Antwort vom Lizenzserver.', 'gdpr-content-blocker' );
|
||
wp_send_json_error( [ 'message' => $msg ] );
|
||
}
|
||
|
||
// Annotate each finding: already covered by a configured service? And is
|
||
// there a ready-made template (preset) for it?
|
||
$services = self::get_services();
|
||
$presets = self::get_presets();
|
||
$findings = is_array( $data['findings'] ?? null ) ? $data['findings'] : [];
|
||
foreach ( $findings as &$f ) {
|
||
$f['covered'] = self::is_covered( $f, $services );
|
||
$f['preset'] = self::match_preset( $f, $presets ); // preset key or ''
|
||
}
|
||
unset( $f );
|
||
|
||
wp_send_json_success( [
|
||
'findings' => $findings,
|
||
'scanned' => $data['scanned'] ?? [],
|
||
] );
|
||
}
|
||
|
||
/** Collect host + sample URLs of a finding for substring matching. */
|
||
private static function finding_haystacks( array $finding ): array {
|
||
return array_merge(
|
||
[ (string) ( $finding['host'] ?? '' ) ],
|
||
array_map( 'strval', (array) ( $finding['sample_urls'] ?? [] ) )
|
||
);
|
||
}
|
||
|
||
/** True if any configured service's match_pattern matches this finding. */
|
||
private static function is_covered( array $finding, array $services ): bool {
|
||
$haystacks = self::finding_haystacks( $finding );
|
||
foreach ( $services as $svc ) {
|
||
$pattern = $svc['match_pattern'] ?? '';
|
||
if ( $pattern === '' ) {
|
||
continue;
|
||
}
|
||
foreach ( $haystacks as $h ) {
|
||
if ( $h !== '' && str_contains( $h, $pattern ) ) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/** Return the key of a preset whose match_pattern fits this finding, or ''. */
|
||
private static function match_preset( array $finding, array $presets ): string {
|
||
$haystacks = self::finding_haystacks( $finding );
|
||
foreach ( $presets as $key => $preset ) {
|
||
$pattern = $preset['match_pattern'] ?? '';
|
||
if ( $pattern === '' ) {
|
||
continue;
|
||
}
|
||
foreach ( $haystacks as $h ) {
|
||
if ( $h !== '' && str_contains( $h, $pattern ) ) {
|
||
return (string) $key;
|
||
}
|
||
}
|
||
}
|
||
return '';
|
||
}
|
||
}
|