Files
GDPR-Content-Blocker/gdpr-content-blocker/assets/admin.js
s4luorth b6df4c64af revert: lokales Google-Fonts-Hosting entfernt + scan zeigt nur hinweis
- Feature "Schriften lokal hosten" komplett zurueckgebaut (class-fonts.php,
  tab, ajax-endpunkte, output-buffer, admin-js-handler, optionen).
- Scan: wird fonts.googleapis.com / fonts.gstatic.com erkannt, steht in der
  Aktion-spalte jetzt der nicht-klickbare hinweis "Schriftart fuer
  DSGVO-konformen Betrieb lokal hosten." statt eines buttons.
- i18n entsprechend bereinigt (123 strings).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 07:39:34 +02:00

292 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Content Blocker – admin.js
* Color-picker init + service repeater (add / remove rows).
*/
( function ( $ ) {
'use strict';
/* ── Color pickers ── */
function initColorPickers( ctx ) {
$( '.cb-color-picker', ctx ).wpColorPicker();
}
/* ── Live header update: provider name ── */
function syncTitle( box ) {
const name = ( $( '.cb-input-name', box ).val() || '' ).trim();
$( '.cb-service-title', box ).first().text( name || ( cbAdmin.newServiceLbl || 'Neuer Dienst' ) );
}
function bindNameSync( box ) {
$( '.cb-input-name', box ).on( 'input', function () {
syncTitle( box );
} );
}
/* ── Expand / collapse a service box ── */
function setExpanded( box, open ) {
$( '.cb-service-grid', box ).toggle( open );
const $btn = $( '.cb-service-toggle', box );
$btn.attr( 'aria-expanded', open ? 'true' : 'false' ).text( open ? '▾' : '▸' );
}
/* Delegated once on document → works for existing AND dynamically added rows,
* and is resilient to load-order issues. */
$( document ).on( 'click', '.cb-service-toggle, .cb-service-title', function () {
const box = $( this ).closest( '.cb-service-box' );
const open = $( '.cb-service-toggle', box ).attr( 'aria-expanded' ) !== 'true';
setExpanded( box, open );
} );
function bindToggle( box ) {
// No-op kept for call sites; toggling is handled by delegation above.
}
/* ── Re-index a row's field names after a remove ── */
function reindexRows() {
$( '#cb-services-list .cb-service-box' ).each( function ( i ) {
$( this ).attr( 'data-cb-index', i );
$( this ).find( '[name]' ).each( function () {
const n = $( this ).attr( 'name' );
$( this ).attr( 'name', n.replace( /cb_services\[\d+\]/, 'cb_services[' + i + ']' ) );
} );
} );
}
/* ── Add a new (optionally prefilled) service row ── */
function addServiceRow( preset ) {
const index = $( '#cb-services-list .cb-service-box' ).length;
const template = $( '#cb-service-template' ).html();
const newHtml = template.replace( /__INDEX__/g, index );
const $box = $( newHtml );
$( '#cb-services-list' ).append( $box );
bindNameSync( $box );
bindToggle( $box );
$box.find( '.cb-remove-service' ).on( 'click', handleRemove );
if ( preset ) {
fillPreset( $box, preset );
}
setExpanded( $box, true ); // new rows start expanded for editing
syncTitle( $box );
return $box;
}
/* ── Fill a row from a preset object ── */
function fillPreset( $box, p ) {
const setText = function ( field, val ) {
$box.find( '[name$="[' + field + ']"]' ).val( val != null ? val : '' );
};
const setBool = function ( field, val ) {
$box.find( '[name$="[' + field + ']"]' ).prop( 'checked', !! val );
};
setText( 'id', p.id );
setText( 'name', p.name );
setText( 'match_pattern', p.match_pattern );
setText( 'recipient', p.recipient );
setText( 'privacy_url', p.privacy_url );
setText( 'purpose', p.purpose );
setBool( 'third_country', p.third_country );
setBool( 'sets_cookie', p.sets_cookie );
setBool( 'loads_script', p.loads_script );
$box.find( '.cb-service-title' ).text( p.name || cbAdmin.newServiceLbl );
}
/* ── Add empty service ── */
$( '#cb-add-service' ).on( 'click', function () {
addServiceRow( null );
} );
/* ── Insert preset ── */
$( '#cb-preset-select' ).on( 'change', function () {
const key = $( this ).val();
if ( ! key || ! cbAdmin.presets || ! cbAdmin.presets[ key ] ) {
return;
}
addServiceRow( cbAdmin.presets[ key ] );
$( this ).val( '' ); // reset so the same preset can be added again
} );
/* ── Remove service ── */
function handleRemove() {
if ( ! window.confirm( cbAdmin.confirmRemove ) ) {
return;
}
$( this ).closest( '.cb-service-box' ).remove();
reindexRows();
}
/* ── Tab switching ── */
$( '[data-cb-tab]' ).on( 'click', function ( e ) {
e.preventDefault();
activateTab( $( this ).data( 'cb-tab' ) );
} );
/* ── Website scan ── */
$( '#cb-scan-btn' ).on( 'click', function () {
const $btn = $( this ).prop( 'disabled', true );
const i18n = cbAdmin.i18n || {};
$( '#cb-scan-status' ).text( i18n.scanning || 'Scanning…' );
$( '#cb-scan-results' ).empty();
$.post( cbAdmin.ajaxUrl, { action: 'cb_scan', nonce: cbAdmin.scanNonce } )
.done( function ( resp ) {
if ( ! resp || ! resp.success ) {
const msg = resp && resp.data && resp.data.message ? resp.data.message : 'Error';
$( '#cb-scan-status' ).text( ( i18n.scanError || 'Scan failed:' ) + ' ' + msg );
return;
}
$( '#cb-scan-status' ).text( '' );
renderScan( resp.data );
} )
.fail( function ( xhr ) {
$( '#cb-scan-status' ).text( ( i18n.scanError || 'Scan failed:' ) + ' ' + xhr.status );
} )
.always( function () {
$btn.prop( 'disabled', false );
} );
} );
function renderScan( data ) {
const i18n = cbAdmin.i18n || {};
const findings = ( data && data.findings ) || [];
const $out = $( '#cb-scan-results' ).empty();
// Scanned pages summary
if ( data && data.scanned && data.scanned.length ) {
const $p = $( '<p class="description"></p>' ).text( ( i18n.scannedPages || 'Scanned:' ) + ' ' );
data.scanned.forEach( function ( s, i ) {
if ( i > 0 ) $p.append( ', ' );
$p.append( $( '<code></code>' ).text( s.url + ( s.error ? ' (!)' : '' ) ) );
} );
$out.append( $p );
}
if ( ! findings.length ) {
$out.append( $( '<p></p>' ).text( i18n.noFindings || 'No external resources found.' ) );
return;
}
const $table = $( '<table class="widefat striped"></table>' );
const $head = $( '<tr></tr>' );
[ i18n.host || 'Host', i18n.type || 'Type', i18n.count || 'Count', i18n.foundOn || 'Found on', i18n.example || 'Example', i18n.status || 'Status', i18n.action || 'Action' ]
.forEach( function ( h ) { $head.append( $( '<th></th>' ).text( h ) ); } );
$table.append( $( '<thead></thead>' ).append( $head ) );
const $body = $( '<tbody></tbody>' );
findings.forEach( function ( f ) {
const $tr = $( '<tr></tr>' );
// Host (+ party badge)
const $host = $( '<td></td>' );
$host.append( $( '<strong></strong>' ).text( f.host ) );
$host.append( document.createElement( 'br' ) );
$host.append( $( '<span></span>' )
.css( { fontSize: '11px', color: f.third_party ? '#b32d2e' : '#1a7f37' } )
.text( f.third_party ? ( i18n.thirdParty || 'third-party' ) : ( i18n.firstParty || 'first-party' ) ) );
$tr.append( $host );
// Types
$tr.append( $( '<td></td>' ).text( ( f.types || [] ).join( ', ' ) ) );
// Count
$tr.append( $( '<td></td>' ).text( f.count ) );
// Found on which pages
const $pages = $( '<td></td>' ).css( { fontSize: '11px' } );
( f.pages || [] ).forEach( function ( pageUrl, i ) {
if ( i > 0 ) $pages.append( document.createElement( 'br' ) );
let label = pageUrl;
try { label = new URL( pageUrl ).pathname || pageUrl; } catch ( e ) {}
$pages.append( $( '<a></a>' )
.attr( { href: pageUrl, target: '_blank', rel: 'noopener noreferrer', title: pageUrl } )
.text( label ) );
} );
$tr.append( $pages );
// Example URL
const $ex = $( '<td></td>' );
const sample = ( f.sample_urls || [] )[ 0 ] || '';
$ex.append( $( '<code></code>' ).css( { fontSize: '11px', wordBreak: 'break-all' } ).text( sample ) );
$tr.append( $ex );
// Does a ready-made template exist for this finding?
const preset = ( f.preset && cbAdmin.presets && cbAdmin.presets[ f.preset ] )
? cbAdmin.presets[ f.preset ]
: null;
// Status
const $st = $( '<td></td>' );
if ( f.covered ) {
$st.append( $( '<span></span>' ).css( { color: '#1a7f37', fontWeight: '600' } ).text( '✓ ' + ( i18n.covered || 'covered' ) ) );
} else if ( preset ) {
$st.append( $( '<span></span>' ).css( { color: '#2043B7', fontWeight: '600' } ).text( '★ ' + ( i18n.templateAvail || 'Vorlage verfügbar' ) ) );
}
$tr.append( $st );
// Action column.
const $act = $( '<td></td>' );
const host = ( f.host || '' ).toLowerCase();
const isGoogleFont = host === 'fonts.googleapis.com' || host === 'fonts.gstatic.com';
if ( isGoogleFont ) {
// Google Fonts can't be consent-gated meaningfully — the right fix is
// hosting them locally. Show a non-clickable hint, no button.
$act.append( $( '<span></span>' )
.css( { color: '#8a6d3b', fontStyle: 'italic' } )
.text( i18n.fontHint || 'Host this font locally for GDPR-compliant operation.' ) );
} else if ( f.third_party && ! f.covered ) {
// Prefill from the matching preset (full data) if there is one,
// otherwise just seed host + pattern.
const data = preset
? preset
: { name: f.host, match_pattern: f.suggested_pattern || f.host, third_country: true };
$( '<button type="button" class="button button-small"></button>' )
.text( preset ? ( i18n.useTemplate || 'Vorlage übernehmen' ) : ( i18n.addService || 'Add as service' ) )
.on( 'click', function () {
activateTab( 'services' );
const $box = addServiceRow( data );
if ( $box && $box.length ) {
$box[ 0 ].scrollIntoView( { behavior: 'smooth', block: 'center' } );
$box.find( '.cb-input-name' ).trigger( 'focus' );
}
} )
.appendTo( $act );
}
$tr.append( $act );
$body.append( $tr );
} );
$table.append( $body );
$out.append( $table );
}
/* ── Activate a tab by key ── */
function activateTab( tab ) {
$( '.cb-tab-content' ).hide();
$( '#cb-tab-' + tab ).show();
$( '[data-cb-tab]' ).removeClass( 'nav-tab-active' );
$( '[data-cb-tab="' + tab + '"]' ).addClass( 'nav-tab-active' );
}
/* ── Bootstrap ── */
$( document ).ready( function () {
initColorPickers( document );
$( '#cb-services-list .cb-service-box' ).each( function () {
bindNameSync( this );
bindToggle( this );
$( '.cb-remove-service', this ).on( 'click', handleRemove );
} );
// Honor ?cb_tab=… so license redirects land on the right tab.
const params = new URLSearchParams( window.location.search );
const tab = params.get( 'cb_tab' );
if ( tab && $( '#cb-tab-' + tab ).length ) {
activateTab( tab );
}
} );
// Expose strings from wp_localize_script if needed.
window.cbAdmin = window.cbAdmin || { confirmRemove: 'Dienst wirklich entfernen?' };
} )( jQuery );