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:
529
gdpr-content-blocker/includes/class-fonts.php
Normal file
529
gdpr-content-blocker/includes/class-fonts.php
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user