feat: lokales Google-Fonts-Hosting + "Lizenz upgraden"-button (v1.3.0)

- Lizenz-Tab: bei vollen Plaetzen erscheint ein "Lizenz upgraden"-Button
  (https://lucas-orth.de/wp-plugins/gdpr-content-blocker/) zusaetzlich zur
  domain-freigabe.
- Neues Feature "Schriften": Google Fonts lokal hosten (button-modell).
  Admin klickt "Jetzt herunterladen & einbinden" -> plugin scannt die seiten,
  laedt googleapis-CSS + gstatic-schriftdateien einmalig in den uploads-ordner,
  schreibt die pfade lokal um und tauscht sie per output-buffer im quelltext.
  Abgedeckt: <link>, inline-@import/url() UND @import/url() in gleicher-origin
  externen CSS-dateien (mit absolutierung der restlichen relativen pfade).
  preconnect/dns-prefetch zu google-font-hosts werden entfernt (kein IP-leak).
  Toggle zum aus/einschalten + "Lokale Schriften loeschen".
- i18n-pipeline robuster: build-en-mo.py nutzt jetzt translations-en.json
  (lookup per quell-string, reihenfolge-unabhaengig) und bricht mit liste ab,
  falls eine uebersetzung fehlt. Alle neuen strings DE/EN ergaenzt (139).
- version 1.3.0 (header + CB_VERSION).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-06-08 07:16:19 +02:00
parent ca0f2f2c32
commit 377c64f8d0
10 changed files with 990 additions and 152 deletions

View File

@@ -250,6 +250,40 @@
$out.append( $table );
}
/* ── Local Google Fonts: download & embed ── */
$( '#cb-fonts-fetch' ).on( 'click', function () {
const i18n = cbAdmin.i18n || {};
const $btn = $( this ).prop( 'disabled', true );
const $st = $( '#cb-fonts-status' ).text( i18n.fontsFetching || 'Downloading…' );
$.post( cbAdmin.ajaxUrl, { action: 'cb_fonts_fetch', nonce: cbAdmin.fontsNonce } )
.done( function ( resp ) {
if ( ! resp || ! resp.success ) {
const msg = resp && resp.data && resp.data.message ? resp.data.message : 'Error';
$st.text( ( i18n.fontsError || 'Error:' ) + ' ' + msg );
return;
}
$st.text( resp.data.message || 'OK' );
setTimeout( function () { window.location.reload(); }, 900 );
} )
.fail( function ( xhr ) {
$st.text( ( i18n.fontsError || 'Error:' ) + ' ' + xhr.status );
} )
.always( function () {
$btn.prop( 'disabled', false );
} );
} );
$( '#cb-fonts-clear' ).on( 'click', function () {
const i18n = cbAdmin.i18n || {};
if ( ! window.confirm( i18n.fontsClearConfirm || 'Delete local fonts?' ) ) {
return;
}
$( this ).prop( 'disabled', true );
$.post( cbAdmin.ajaxUrl, { action: 'cb_fonts_clear', nonce: cbAdmin.fontsNonce } )
.always( function () { window.location.reload(); } );
} );
/* ── Activate a tab by key ── */
function activateTab( tab ) {
$( '.cb-tab-content' ).hide();

View File

@@ -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.2.0
* Version: 1.3.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.2.0' );
define( 'CB_VERSION', '1.3.0' );
define( 'CB_FILE', __FILE__ );
define( 'CB_DIR', plugin_dir_path( __FILE__ ) );
define( 'CB_URL', plugin_dir_url( __FILE__ ) );
@@ -37,6 +37,7 @@ require_once CB_DIR . 'includes/class-renderer.php';
require_once CB_DIR . 'includes/class-autodetect.php';
require_once CB_DIR . 'includes/class-license.php';
require_once CB_DIR . 'includes/class-updater.php';
require_once CB_DIR . 'includes/class-fonts.php';
// Language policy: German source strings for any German locale (de_*), English
// for everything else. We map the plugin's locale accordingly and ship an
@@ -59,6 +60,7 @@ function cb_init(): void {
CB_Autodetect::init();
CB_License::init();
CB_Updater::init();
CB_Fonts::init();
}
// Schedule / clear the daily license re-check.

View File

@@ -0,0 +1,529 @@
<?php
defined( 'ABSPATH' ) || exit;
/**
* Optional: host Google Fonts locally to avoid the connection to Google's CDN
* (privacy + performance).
*
* Trigger model: "button". The admin clicks "download & embed"; the plugin
* visits the configured pages, finds every Google Fonts reference and downloads
* everything once:
* - <link href="…fonts.googleapis.com/css…"> (also protocol-relative)
* - @import / direct gstatic URLs inside inline <style> blocks
* - @import / url(…gstatic…) inside SAME-ORIGIN external CSS files
* The CSS + font files are stored under wp-content/uploads, all paths are
* rewritten to the local copies, and a remote→local URL map is saved. On the
* front end a lightweight output-buffer pass swaps the mapped URLs (no network).
*
* Anything not covered by the scanned pages simply stays remote until the next
* "download & embed" run (predictable, no per-visitor latency).
*/
class CB_Fonts {
const OPTION = 'cb_fonts';
const DIR = 'gdpr-content-blocker-fonts';
// Modern browser UA so fonts.googleapis.com returns woff2 (not legacy ttf).
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
const MAX_CSS = 40;
const MAX_FONTS = 200;
/* Per-build memoisation (avoid re-downloading across pages). */
private static array $css_done = [];
private static array $font_done = [];
private static array $map = [];
private static int $font_count = 0;
private static int $css_count = 0;
public static function init(): void {
add_action( 'template_redirect', [ __CLASS__, 'start_buffer' ], 5 );
add_action( 'admin_post_cb_save_fonts', [ __CLASS__, 'handle_save' ] );
add_action( 'wp_ajax_cb_fonts_fetch', [ __CLASS__, 'ajax_fetch' ] );
add_action( 'wp_ajax_cb_fonts_clear', [ __CLASS__, 'ajax_clear' ] );
}
public static function get_settings(): array {
$s = get_option( self::OPTION, [] );
$s = wp_parse_args( is_array( $s ) ? $s : [], [
'enabled' => false,
'map' => [],
'updated_at' => 0,
'count' => 0,
'css_count' => 0,
] );
if ( ! is_array( $s['map'] ) ) {
$s['map'] = [];
}
return $s;
}
public static function is_enabled(): bool {
$s = self::get_settings();
return ! empty( $s['enabled'] ) && ! empty( $s['map'] );
}
/* ───────────────────────── front-end rewrite ───────────────────────── */
public static function start_buffer(): void {
if ( is_admin() || wp_doing_ajax() || wp_doing_cron() ) {
return;
}
if ( ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) ) {
return;
}
if ( ! self::is_enabled() ) {
return;
}
ob_start( [ __CLASS__, 'rewrite' ] );
}
public static function rewrite( string $html ): string {
if ( $html === '' ) {
return $html;
}
$map = self::get_settings()['map'];
if ( empty( $map ) ) {
return $html;
}
// Longest keys first so a shorter URL can't pre-empt a longer one.
uksort( $map, fn( $a, $b ) => strlen( (string) $b ) <=> strlen( (string) $a ) );
$html = str_replace( array_keys( $map ), array_values( $map ), $html );
// Drop now-pointless resource hints to Google's font hosts — a preconnect
// would still open a connection and leak the visitor's IP. Match any
// attribute order via look-aheads.
$out = preg_replace(
'#<link\b(?=[^>]*\brel=["\'](?:preconnect|dns-prefetch|preload)["\'])(?=[^>]*(?:fonts\.googleapis\.com|fonts\.gstatic\.com))[^>]*>\s*#i',
'',
$html
);
return $out === null ? $html : $out;
}
/* ───────────────────────── admin actions ───────────────────────── */
public static function handle_save(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Keine Berechtigung.', 'gdpr-content-blocker' ) );
}
check_admin_referer( 'cb_fonts', 'cb_fonts_nonce' );
$s = self::get_settings();
$s['enabled'] = ! empty( $_POST['cb_fonts_enabled'] );
update_option( self::OPTION, $s );
wp_safe_redirect( admin_url( 'options-general.php?page=gdpr-content-blocker&cb_tab=fonts&cb_saved=1' ) );
exit;
}
public static function ajax_fetch(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'Keine Berechtigung.', 'gdpr-content-blocker' ) ], 403 );
}
check_ajax_referer( 'cb_fonts', 'nonce' );
$res = self::build_cache();
if ( is_wp_error( $res ) ) {
wp_send_json_error( [ 'message' => $res->get_error_message() ] );
}
wp_send_json_success( $res );
}
public static function ajax_clear(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'Keine Berechtigung.', 'gdpr-content-blocker' ) ], 403 );
}
check_ajax_referer( 'cb_fonts', 'nonce' );
self::delete_dir();
$s = self::get_settings();
$s['map'] = [];
$s['count'] = 0;
$s['css_count'] = 0;
$s['updated_at'] = 0;
update_option( self::OPTION, $s );
wp_send_json_success( [ 'message' => __( 'Lokale Schriften gelöscht.', 'gdpr-content-blocker' ) ] );
}
/* ───────────────────────── download / cache ───────────────────────── */
private static function build_cache(): array|WP_Error {
self::$css_done = [];
self::$font_done = [];
self::$map = [];
self::$font_count = 0;
self::$css_count = 0;
$upload = wp_upload_dir();
if ( ! empty( $upload['error'] ) ) {
return new WP_Error( 'cb_upload', (string) $upload['error'] );
}
$dir = trailingslashit( $upload['basedir'] ) . self::DIR;
if ( ! wp_mkdir_p( $dir ) ) {
return new WP_Error( 'cb_mkdir', __( 'Upload-Verzeichnis konnte nicht erstellt werden.', 'gdpr-content-blocker' ) );
}
$home_host = self::host( home_url() );
$pages = CB_Settings::scan_urls();
foreach ( $pages as $page ) {
$html = self::http_get( $page );
if ( $html === null ) {
continue;
}
// 1) Google Fonts stylesheets referenced directly (link href / @import).
if ( preg_match_all( '#(?:https?:)?//fonts\.googleapis\.com/[^"\'()\s<>]+#i', $html, $g ) ) {
foreach ( array_unique( $g[0] ) as $found ) {
if ( isset( self::$map[ $found ] ) ) {
continue;
}
$local = self::process_googleapis_css( self::abs_url( $found ) );
if ( $local ) {
self::$map[ $found ] = $local;
}
}
}
// 2) Direct gstatic font URLs in the HTML (e.g. inline @font-face).
if ( preg_match_all( '#(?:https?:)?//fonts\.gstatic\.com/[^"\'()\s<>]+#i', $html, $gs ) ) {
foreach ( array_unique( $gs[0] ) as $found ) {
if ( isset( self::$map[ $found ] ) ) {
continue;
}
$local = self::download_font( self::abs_url( $found ) );
if ( $local ) {
self::$map[ $found ] = $local;
}
}
}
// 3) Same-origin external CSS files that themselves reference Google Fonts.
if ( preg_match_all( '#<link\b[^>]*>#i', $html, $links ) ) {
foreach ( $links[0] as $tag ) {
if ( self::$css_count >= self::MAX_CSS ) {
break;
}
$rel = self::attr( $tag, 'rel' );
$href = self::attr( $tag, 'href' );
if ( $href === '' || stripos( $rel, 'stylesheet' ) === false ) {
continue;
}
$abs = self::abs_url( $href );
$h = self::host( $abs );
if ( $h === 'fonts.googleapis.com' || $h !== $home_host ) {
continue; // googleapis handled in (1); only our own CSS files here
}
$cache_key = 'ext:' . $abs;
if ( isset( self::$css_done[ $cache_key ] ) ) {
continue;
}
self::$css_done[ $cache_key ] = true;
$css = self::http_get( $abs, true );
if ( $css === null
|| ( stripos( $css, 'fonts.googleapis.com' ) === false
&& stripos( $css, 'fonts.gstatic.com' ) === false ) ) {
continue;
}
$localized = self::localize_css( $css, $abs, true );
if ( $localized !== $css ) {
$url = self::save( 'css', $localized );
if ( $url ) {
self::$map[ $href ] = $url;
self::$css_count++;
}
}
}
}
}
$s = self::get_settings();
$s['map'] = self::$map;
$s['count'] = self::$font_count;
$s['css_count'] = self::$css_count;
$s['updated_at'] = time();
$s['enabled'] = true; // a successful build implies the feature is wanted
update_option( self::OPTION, $s );
return [
'fonts' => self::$font_count,
'css' => self::$css_count,
'mapped' => count( self::$map ),
'message' => sprintf(
/* translators: 1: number of stylesheets, 2: number of font files */
__( '%1$d Schrift-Stylesheet(s) und %2$d Schriftdatei(en) lokal gespeichert.', 'gdpr-content-blocker' ),
self::$css_count,
self::$font_count
),
];
}
/** Fetch a googleapis CSS, localise its gstatic URLs, store it; return local URL. */
private static function process_googleapis_css( string $absUrl ): ?string {
if ( array_key_exists( $absUrl, self::$css_done ) ) {
return self::$css_done[ $absUrl ];
}
if ( self::$css_count >= self::MAX_CSS || self::host( $absUrl ) !== 'fonts.googleapis.com' ) {
return null;
}
$css = self::http_get( $absUrl, true );
if ( $css === null ) {
self::$css_done[ $absUrl ] = null;
return null;
}
$css = self::localize_css( $css, $absUrl, false );
$url = self::save( 'css', $css );
if ( $url ) {
self::$css_count++;
}
self::$css_done[ $absUrl ] = $url;
return $url;
}
/** Download a single gstatic font file; return its local URL. */
private static function download_font( string $absUrl ): ?string {
if ( array_key_exists( $absUrl, self::$font_done ) ) {
return self::$font_done[ $absUrl ];
}
if ( self::$font_count >= self::MAX_FONTS || self::host( $absUrl ) !== 'fonts.gstatic.com' ) {
return null;
}
$bin = self::http_get( $absUrl );
if ( $bin === null ) {
self::$font_done[ $absUrl ] = null;
return null;
}
$url = self::save( self::ext_from_url( $absUrl ), $bin );
if ( $url ) {
self::$font_count++;
}
self::$font_done[ $absUrl ] = $url;
return $url;
}
/**
* Rewrite a CSS body: googleapis @import → local; gstatic url() → local file.
* When $absolutize is true (external theme CSS we copy elsewhere), all other
* relative url()s are made absolute against $baseUrl so they keep working.
*/
private static function localize_css( string $css, string $baseUrl, bool $absolutize ): string {
// @import url("…googleapis…") or @import "…googleapis…"
$css = preg_replace_callback(
'#@import\s+(?:url\(\s*)?([\'"]?)([^\'"\)\s]+)\1\s*\)?\s*;#i',
function ( array $m ): string {
$abs = self::abs_url( $m[2] );
if ( self::host( $abs ) !== 'fonts.googleapis.com' ) {
return $m[0];
}
$local = self::process_googleapis_css( $abs );
return $local ? '@import url("' . $local . '");' : $m[0];
},
$css
) ?? $css;
// url(…gstatic…woff2) → local
$css = preg_replace_callback(
'#url\(\s*([\'"]?)([^\'"\)\s]+)\1\s*\)#i',
function ( array $m ) use ( $baseUrl, $absolutize ): string {
$raw = $m[2];
$abs = self::abs_url( $raw, $baseUrl );
if ( self::host( $abs ) === 'fonts.gstatic.com' ) {
$local = self::download_font( $abs );
return $local ? 'url("' . $local . '")' : $m[0];
}
// Keep other resources working when we relocate the CSS file.
if ( $absolutize && ! preg_match( '#^(data:|https?://|//)#i', $raw ) ) {
return 'url("' . $abs . '")';
}
return $m[0];
},
$css
) ?? $css;
return $css;
}
/* ───────────────────────── small helpers ───────────────────────── */
/** GET a URL with a browser UA. Returns the body (string) or null on failure. */
private static function http_get( string $url, bool $css = false ): ?string {
$args = [
'timeout' => 20,
'redirection' => 3,
'user-agent' => self::UA,
];
if ( $css ) {
$args['headers'] = [ 'Accept' => 'text/css,*/*;q=0.1' ];
}
$resp = wp_remote_get( $url, $args );
if ( is_wp_error( $resp ) || wp_remote_retrieve_response_code( $resp ) !== 200 ) {
return null;
}
$body = wp_remote_retrieve_body( $resp );
return $body === '' ? null : $body;
}
/** Save bytes to the uploads font dir (deduped by content hash); return URL. */
private static function save( string $ext, string $contents ): ?string {
$upload = wp_upload_dir();
if ( ! empty( $upload['error'] ) ) {
return null;
}
$dir = trailingslashit( $upload['basedir'] ) . self::DIR;
if ( ! wp_mkdir_p( $dir ) ) {
return null;
}
$ext = preg_replace( '/[^a-z0-9]/', '', strtolower( $ext ) ) ?: 'bin';
$name = md5( $contents ) . '.' . $ext;
$path = trailingslashit( $dir ) . $name;
if ( ! file_exists( $path ) && file_put_contents( $path, $contents ) === false ) {
return null;
}
return trailingslashit( $upload['baseurl'] ) . self::DIR . '/' . $name;
}
private static function delete_dir(): void {
$upload = wp_upload_dir();
if ( ! empty( $upload['error'] ) ) {
return;
}
$dir = trailingslashit( $upload['basedir'] ) . self::DIR;
if ( ! is_dir( $dir ) ) {
return;
}
foreach ( (array) glob( trailingslashit( $dir ) . '*' ) as $file ) {
if ( is_file( $file ) ) {
@unlink( $file );
}
}
}
/** Resolve protocol-relative / relative URLs (and decode HTML entities). */
private static function abs_url( string $url, string $base = '' ): string {
$url = html_entity_decode( trim( $url ), ENT_QUOTES );
if ( str_starts_with( $url, '//' ) ) {
return 'https:' . $url;
}
if ( preg_match( '#^(data:|https?://)#i', $url ) ) {
return $url;
}
if ( $base === '' ) {
return $url;
}
// Relative → absolute against $base.
$b = wp_parse_url( $base );
$scheme = $b['scheme'] ?? 'https';
$host = $b['host'] ?? '';
$port = isset( $b['port'] ) ? ':' . $b['port'] : '';
$path = $b['path'] ?? '/';
if ( str_starts_with( $url, '/' ) ) {
$full = $url;
} else {
$full = preg_replace( '#/[^/]*$#', '/', $path ) . $url;
}
$segs = [];
foreach ( explode( '/', $full ) as $seg ) {
if ( $seg === '..' ) {
array_pop( $segs );
} elseif ( $seg !== '.' && $seg !== '' ) {
$segs[] = $seg;
}
}
return $scheme . '://' . $host . $port . '/' . implode( '/', $segs );
}
private static function host( string $url ): string {
return strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) );
}
private static function ext_from_url( string $url ): string {
$path = (string) wp_parse_url( $url, PHP_URL_PATH );
$ext = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) );
return in_array( $ext, [ 'woff2', 'woff', 'ttf', 'otf', 'eot', 'svg' ], true ) ? $ext : 'woff2';
}
/** Read a single attribute value out of an HTML tag string. */
private static function attr( string $tag, string $name ): string {
$re = '#\b' . preg_quote( $name, '#' ) . '\s*=\s*("([^"]*)"|\'([^\']*)\'|([^\s>]+))#i';
if ( preg_match( $re, $tag, $m ) ) {
if ( ( $m[2] ?? '' ) !== '' ) {
return $m[2];
}
if ( ( $m[3] ?? '' ) !== '' ) {
return $m[3];
}
return $m[4] ?? '';
}
return '';
}
/* ───────────────────────── settings tab ───────────────────────── */
public static function render_tab(): void {
$s = self::get_settings();
$has_cache = ! empty( $s['map'] );
$when = $s['updated_at']
? wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $s['updated_at'] )
: '';
?>
<p class="description" style="max-width:760px;">
<?php esc_html_e( 'Bindet Google Fonts lokal ein: Das Plugin lädt die Schrift-Stylesheets und Schriftdateien einmalig herunter, speichert sie in Ihrem Uploads-Ordner und ersetzt die Pfade im Seitenquelltext. So wird beim Seitenaufruf keine Verbindung mehr zu Google aufgebaut (kein IP-Transfer) und die Schriften laden schneller.', 'gdpr-content-blocker' ); ?>
</p>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'cb_fonts', 'cb_fonts_nonce' ); ?>
<input type="hidden" name="action" value="cb_save_fonts">
<table class="form-table" role="presentation"><tbody>
<tr>
<th scope="row"><?php esc_html_e( 'Lokales Hosting', 'gdpr-content-blocker' ); ?></th>
<td>
<label class="cb-switch" title="<?php esc_attr_e( 'Lokale Schriften ein/aus', 'gdpr-content-blocker' ); ?>">
<input type="checkbox" name="cb_fonts_enabled" value="1" <?php checked( ! empty( $s['enabled'] ) ); ?>>
<span class="cb-switch__slider"></span>
</label>
<p class="description"><?php esc_html_e( 'Wenn aktiv, werden auf der Webseite die heruntergeladenen lokalen Schriften statt der Google-Pfade ausgeliefert.', 'gdpr-content-blocker' ); ?></p>
</td>
</tr>
</tbody></table>
<?php submit_button( __( 'Speichern', 'gdpr-content-blocker' ) ); ?>
</form>
<hr>
<h2><?php esc_html_e( 'Schriften herunterladen', 'gdpr-content-blocker' ); ?></h2>
<p>
<button type="button" id="cb-fonts-fetch" class="button button-primary">
<?php esc_html_e( 'Jetzt herunterladen & einbinden', 'gdpr-content-blocker' ); ?>
</button>
<?php if ( $has_cache ) : ?>
<button type="button" id="cb-fonts-clear" class="button" style="margin-left:6px;">
<?php esc_html_e( 'Lokale Schriften löschen', 'gdpr-content-blocker' ); ?>
</button>
<?php endif; ?>
<span id="cb-fonts-status" style="margin-left:10px;"></span>
</p>
<?php if ( $has_cache ) : ?>
<p class="description">
<?php
printf(
/* translators: 1: stylesheet count, 2: font file count, 3: date/time */
esc_html__( 'Aktuell lokal: %1$d Stylesheet(s), %2$d Schriftdatei(en) – zuletzt aktualisiert am %3$s.', 'gdpr-content-blocker' ),
(int) ( $s['css_count'] ?? 0 ),
(int) ( $s['count'] ?? 0 ),
esc_html( $when )
);
?>
</p>
<?php endif; ?>
<p class="description" style="max-width:760px;">
<?php esc_html_e( 'Hinweis: Gescannt werden die Startseite und einige Unterseiten. Falls auf einzelnen Seiten weitere Google Fonts vorkommen, führen Sie die Aktion nach Inhaltsänderungen erneut aus.', 'gdpr-content-blocker' ); ?>
</p>
<?php
}
}

View File

@@ -358,6 +358,18 @@ class CB_License {
</tr>
</tbody></table>
<?php if ( $status === 'limit' ) : ?>
<!-- Not enough seats to activate this site: offer an upgrade. -->
<p style="margin:8px 0 16px;">
<a href="https://lucas-orth.de/wp-plugins/gdpr-content-blocker/" target="_blank" rel="noopener noreferrer" class="button button-primary">
<?php esc_html_e( 'Lizenz upgraden', 'gdpr-content-blocker' ); ?>
</a>
<span class="description" style="margin-left:8px;">
<?php esc_html_e( 'Schalten Sie weitere Domains frei, um diese Seite zusätzlich zu aktivieren – oder geben Sie unten eine bestehende Domain frei.', 'gdpr-content-blocker' ); ?>
</span>
</p>
<?php endif; ?>
<?php if ( $status === 'limit' && ! empty( $lic['pending_domains'] ) ) : ?>
<!-- No free slots: list every bound domain with a trash button to free it. -->
<h2><?php esc_html_e( 'Belegte Domains', 'gdpr-content-blocker' ); ?></h2>

View File

@@ -170,6 +170,7 @@ class CB_Settings {
'newServiceLbl' => __( 'Neuer Dienst', 'gdpr-content-blocker' ),
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'scanNonce' => wp_create_nonce( 'cb_scan' ),
'fontsNonce' => wp_create_nonce( 'cb_fonts' ),
'i18n' => [
'scanning' => __( 'Scanne Webseite …', 'gdpr-content-blocker' ),
'scanError' => __( 'Scan fehlgeschlagen:', 'gdpr-content-blocker' ),
@@ -188,6 +189,9 @@ class CB_Settings {
'templateAvail' => __( 'Vorlage verfügbar', 'gdpr-content-blocker' ),
'scannedPages'=> __( 'Gescannte Seiten:', 'gdpr-content-blocker' ),
'foundOn' => __( 'Gefunden auf', 'gdpr-content-blocker' ),
'fontsFetching' => __( 'Schriften werden heruntergeladen …', 'gdpr-content-blocker' ),
'fontsError' => __( 'Fehler beim Herunterladen:', 'gdpr-content-blocker' ),
'fontsClearConfirm'=> __( 'Lokale Schriften wirklich löschen?', 'gdpr-content-blocker' ),
],
] );
}
@@ -273,6 +277,9 @@ class CB_Settings {
<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-fonts" class="nav-tab" data-cb-tab="fonts">
<?php esc_html_e( 'Schriften', 'gdpr-content-blocker' ); ?>
</a>
<a href="#cb-tab-shortcodes" class="nav-tab" data-cb-tab="shortcodes">
<?php esc_html_e( 'Shortcodes', 'gdpr-content-blocker' ); ?>
</a>
@@ -345,6 +352,11 @@ class CB_Settings {
</form>
</div>
<!-- Fonts Tab -->
<div id="cb-tab-fonts" class="cb-tab-content" style="display:none;">
<?php CB_Fonts::render_tab(); ?>
</div>
<!-- Shortcodes Tab -->
<div id="cb-tab-shortcodes" class="cb-tab-content" style="display:none;">
<?php self::render_shortcodes_help(); ?>
@@ -735,7 +747,7 @@ class CB_Settings {
* 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 {
public static function scan_urls(): array {
$urls = [ home_url( '/' ) ];
$posts = get_posts( [

View File

@@ -6,12 +6,51 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Language: en_US\n"
msgid "Ungültige Antwort vom Lizenzserver."
msgstr "Invalid response from the license server."
msgid "Keine Berechtigung."
msgstr "Insufficient permissions."
msgid "Lokale Schriften gelöscht."
msgstr "Local fonts deleted."
msgid "Upload-Verzeichnis konnte nicht erstellt werden."
msgstr "The upload directory could not be created."
msgid "%1$d Schrift-Stylesheet(s) und %2$d Schriftdatei(en) lokal gespeichert."
msgstr "%1$d font stylesheet(s) and %2$d font file(s) stored locally."
msgid "Bindet Google Fonts lokal ein: Das Plugin lädt die Schrift-Stylesheets und Schriftdateien einmalig herunter, speichert sie in Ihrem Uploads-Ordner und ersetzt die Pfade im Seitenquelltext. So wird beim Seitenaufruf keine Verbindung mehr zu Google aufgebaut (kein IP-Transfer) und die Schriften laden schneller."
msgstr "Hosts Google Fonts locally: the plugin downloads the font stylesheets and font files once, stores them in your uploads folder and replaces the paths in the page source. As a result no connection to Google is made on page load (no IP transfer) and fonts load faster."
msgid "Lokales Hosting"
msgstr "Local hosting"
msgid "Lokale Schriften ein/aus"
msgstr "Local fonts on/off"
msgid "Wenn aktiv, werden auf der Webseite die heruntergeladenen lokalen Schriften statt der Google-Pfade ausgeliefert."
msgstr "When enabled, the website serves the downloaded local fonts instead of the Google paths."
msgid "Speichern"
msgstr "Save"
msgid "Schriften herunterladen"
msgstr "Download fonts"
msgid "Jetzt herunterladen & einbinden"
msgstr "Download & embed now"
msgid "Lokale Schriften löschen"
msgstr "Delete local fonts"
msgid "Aktuell lokal: %1$d Stylesheet(s), %2$d Schriftdatei(en) – zuletzt aktualisiert am %3$s."
msgstr "Currently local: %1$d stylesheet(s), %2$d font file(s) — last updated on %3$s."
msgid "Hinweis: Gescannt werden die Startseite und einige Unterseiten. Falls auf einzelnen Seiten weitere Google Fonts vorkommen, führen Sie die Aktion nach Inhaltsänderungen erneut aus."
msgstr "Note: the home page and a few sub-pages are scanned. If individual pages contain additional Google Fonts, run the action again after content changes."
msgid "Ungültige Antwort vom Lizenzserver."
msgstr "Invalid response from the license server."
msgid "Bitte einen Lizenzschlüssel eingeben."
msgstr "Please enter a license key."
@@ -57,6 +96,12 @@ msgstr "Status"
msgid "Domain"
msgstr "Domain"
msgid "Lizenz upgraden"
msgstr "Upgrade license"
msgid "Schalten Sie weitere Domains frei, um diese Seite zusätzlich zu aktivieren – oder geben Sie unten eine bestehende Domain frei."
msgstr "Unlock more domains to additionally activate this site — or release an existing domain below."
msgid "Belegte Domains"
msgstr "Bound domains"
@@ -186,6 +231,15 @@ msgstr "Scanned pages:"
msgid "Gefunden auf"
msgstr "Found on"
msgid "Schriften werden heruntergeladen …"
msgstr "Downloading fonts …"
msgid "Fehler beim Herunterladen:"
msgstr "Download error:"
msgid "Lokale Schriften wirklich löschen?"
msgstr "Really delete the local fonts?"
msgid "Google Ireland Ltd., Irland / Google LLC, USA"
msgstr "Google Ireland Ltd., Ireland / Google LLC, USA"
@@ -216,6 +270,9 @@ msgstr "Scan"
msgid "Darstellung"
msgstr "Appearance"
msgid "Schriften"
msgstr "Fonts"
msgid "Shortcodes"
msgstr "Shortcodes"