PageSourceSearch

https://www.apfeltalk.de/magazin/wp-content/plugins/image-ai-label…/assets/js/frontend-fallback.js?ver=2.10.3

js apfeltalk.de collected 2026-09-24 19:38:48 UTC 12,749 bytes, 382 lines download raw bytes

1/**
2 * Sitewide-Ergaenzung fuer Bilder, die ohne die WordPress-Bildfunktionen
3 * ausgegeben werden (Pro).
4 *
5 * Die normale Kennzeichnung laeuft serverseitig ueber
6 * wp_get_attachment_image_html() bzw. den "wp-image-<ID>"-Klassenabgleich in
7 * "the_content" (siehe includes/frontend.php). Viele Theme-Builder-Widgets -
8 * allen voran das Beitragsbild- und Posts-Loop-Widget von Elementor - bauen
9 * ihr <img>-Markup aber selbst (z. B. per get_the_post_thumbnail_url()) und
10 * rufen wp_get_attachment_image() nie auf. Weder der Filter noch die
11 * "wp-image-"-Klasse greifen dann, das Bild bleibt unmarkiert - u. a. bei
12 * Beitragsbildern von Blogartikeln.
13 *
14 * Grundlage ist dieselbe Liste markierter Bild-URLs wie bei der
15 * WooCommerce-Ergaenzung (assets/js/frontend-woocommerce.js), hier aber fuer
16 * die gesamte Seite statt nur den Shop. Zusaetzlich wird jedes <img> anhand
17 * der von WordPress selbst vergebenen Klasse "wp-image-<ID>" direkt gegen
18 * die Liste markierter Anhang-IDs geprueft (robuster als der URL-Abgleich,
19 * u. a. bei Full-Page-Caching). Positioniert werden die Labels ueber die
20 * gemeinsame Schnittstelle window.kiimgPosition (frontend-position.js).
21 * Bereits vom Server gekennzeichnete Bilder werden nicht angefasst.
22 *
23 * NEBEN <img>-Elementen werden hier zusaetzlich CSS-Hintergrundbilder
24 * erkannt, die per Inline-style oder ueber ein Lazy-Load-Datenattribut
25 * gesetzt werden (z. B. bei Divi, WPBakery, Beaver Builder, Bricks oder
26 * individuellem Theme-Markup - ueberall dort, wo Elementor's eigene
27 * PHP-Erkennung in includes/elementor.php nicht greift, weil kein
28 * Elementor im Spiel ist). Rein per CSS-Klasse (externes Stylesheet ohne
29 * Inline-Angabe) gesetzte Hintergruende bleiben davon unberuehrt - das
30 * liesse sich nur durch das Durchsuchen der berechneten Stile jedes
31 * einzelnen Elements der Seite erkennen, was auf grossen Seiten spuerbar
32 * Leistung kosten wuerde.
33 */
34( function () {
35	'use strict';
36
37	if ( ! window.kiimgFallback ) {
38		return;
39	}
40
41	var data = window.kiimgFallback;
42	var AUTO_PREFIX = 'kiimg-fb-';
43	var BG_PREFIX = 'kiimg-bgfb-';
44
45	var autoCounter = 0;
46	var autoLabels = {};
47
48	function refresh() {
49		if ( window.kiimgPosition && window.kiimgPosition.refresh ) {
50			window.kiimgPosition.refresh();
51		}
52	}
53
54	function createLabel( instance ) {
55		var label = document.createElement( 'span' );
56		label.className = 'kiimg-label';
57		label.setAttribute( 'data-kiimg-for', instance );
58		label.setAttribute( 'data-kiimg-position', data.position || 'bottom-right' );
59		label.setAttribute( 'data-kiimg-margin', data.margin || '0' );
60		label.setAttribute( 'style', data.labelStyle || '' );
61		label.textContent = data.labelText || '';
62		document.body.appendChild( label );
63		return label;
64	}
65
66	/* ── Abgleich der Bild-URLs (identisch zu frontend-woocommerce.js) ──────── */
67
68	/*
69	 * Die Dateiendung wird bewusst NICHT mitverglichen: wp_get_attachment_url()
70	 * liefert immer das urspruenglich hochgeladene Format (z. B. .jpg/.png),
71	 * viele Hosting-/CDN-/Optimierungs-Loesungen (z. B. automatische
72	 * WebP/AVIF-Auslieferung) liefern dasselbe Bild im Frontend aber unter
73	 * einer anderen Endung aus. Ohne diesen Schritt wuerde ein als KI-Bild
74	 * markiertes Foto auf solchen Seiten nie erkannt.
75	 *
76	 * Manche Plugins haengen das neue Format zudem einfach an die
77	 * bestehende Endung an (z. B. "foto.jpg.webp" statt "foto.webp"),
78	 * deshalb werden alle bekannten Bild-Endungen vom Ende her entfernt,
79	 * nicht nur die letzte einzelne.
80	 */
81	var IMAGE_EXTENSIONS = /\.(?:jpe?g|png|gif|webp|avif|bmp|tiff?|svg|ico|heic|jxl)$/i;
82
83	function stripSize( value ) {
84		var clean = String( value ).split( '?' )[ 0 ].split( '#' )[ 0 ];
85
86		var next = clean.replace( IMAGE_EXTENSIONS, '' );
87		while ( next !== clean ) {
88			clean = next;
89			next = clean.replace( IMAGE_EXTENSIONS, '' );
90		}
91
92		// Falls eine seltene/unbekannte Endung uebrig bleibt, trotzdem einmal
93		// generisch entfernen, damit die Groessen-Angabe (z. B. "-700x401")
94		// direkt am Ende steht und erkannt wird.
95		clean = clean.replace( /\.[a-z0-9]+$/i, '' );
96
97		return clean.replace( /-\d+x\d+$/i, '' ).toLowerCase();
98	}
99
100	function pathKey( url ) {
101		if ( ! url ) {
102			return '';
103		}
104		var clean = stripSize( url );
105		return clean.replace( /^[a-z]+:\/\/[^/]+/i, '' );
106	}
107
108	function fileKey( url ) {
109		var clean = pathKey( url );
110		return clean.substring( clean.lastIndexOf( '/' ) + 1 );
111	}
112
113	/*
114	 * Manche Lazy-Load-Loesungen (aeltere Lazy-Load-Plugins, Cache-/
115	 * Performance-Plugins mit eigenem Lazy Load statt dem nativen
116	 * loading="lazy") tragen die eigentliche Bild-URL erst in "data-src",
117	 * "data-lazy-src" o. ae. ein und setzen "src" zunaechst auf einen
118	 * Platzhalter (1x1-Pixel/Base64). Ohne diese Attribute zu pruefen, wuerde
119	 * das Bild bis zum tatsaechlichen Nachladen (Scroll in die Naehe) nicht
120	 * erkannt - bei kleinen Vorschaubildern in Blog-/Beitragslisten, die oft
121	 * erst spaeter im Viewport erscheinen, faellt das besonders auf.
122	 */
123	var CANDIDATE_ATTRS = [ 'data-src', 'data-lazy-src', 'data-original', 'data-srcset', 'data-lazy-srcset', 'srcset' ];
124
125	/*
126	 * Zusaetzlich zum URL-Abgleich wird die Bild-ID direkt anhand der von
127	 * WordPress selbst vergebenen Klasse "wp-image-<ID>" geprueft (dieselbe
128	 * Klasse, die auch der "the_content"-Filter in includes/frontend.php
129	 * serverseitig ausliest). Das ist zuverlaessiger als der URL-Abgleich,
130	 * u. a. bei Elementor-Theme-Builder-Widgets (z. B. Beitragsbild), bei
131	 * denen der serverseitige "wp_get_attachment_image_html"-Filter durch
132	 * Full-Page-Caching (Bild wurde erst NACH dem Erzeugen der gecachten
133	 * Seite als KI-Bild markiert) keine Wirkung mehr zeigt - die Bild-ID
134	 * selbst bleibt davon unberuehrt.
135	 */
136	var WP_IMAGE_CLASS_REGEX = /\bwp-image-(\d+)\b/;
137	var markedIds = {};
138	( data.markedIds || [] ).forEach( function ( id ) {
139		markedIds[ id ] = true;
140	} );
141
142	function getWpImageId( img ) {
143		var match = WP_IMAGE_CLASS_REGEX.exec( img.className || '' );
144		return match ? match[ 1 ] : '';
145	}
146
147	function isMarkedById( img ) {
148		var id = getWpImageId( img );
149		return !! id && !! markedIds[ id ];
150	}
151
152	function collectCandidateUrls( img ) {
153		var urls = [];
154		var direct = img.currentSrc || img.src;
155		if ( direct ) {
156			urls.push( direct );
157		}
158
159		CANDIDATE_ATTRS.forEach( function ( attr ) {
160			var value = img.getAttribute( attr );
161			if ( ! value ) {
162				return;
163			}
164			// srcset-artige Werte sind kommagetrennte "URL Breitenangabe"-Paare.
165			value.split( ',' ).forEach( function ( part ) {
166				var url = part.trim().split( /\s+/ )[ 0 ];
167				if ( url ) {
168					urls.push( url );
169				}
170			} );
171		} );
172
173		return urls;
174	}
175
176	function isMarkedImage( img ) {
177		if ( isMarkedById( img ) ) {
178			return true;
179		}
180
181		var urls = collectCandidateUrls( img );
182		for ( var i = 0; i < urls.length; i++ ) {
183			if ( isMarkedUrl( urls[ i ] ) ) {
184				return true;
185			}
186		}
187		return false;
188	}
189
190	/*
191	 * Generische Hintergrundbild-Erkennung (nicht Elementor-spezifisch, siehe
192	 * includes/elementor.php fuer den PHP-seitigen Elementor-Weg). Deckt
193	 * Inline-Styles ("style='background-image:url(...)'") sowie die
194	 * gaengigsten Lazy-Load-Datenattribute fuer Hintergrundbilder ab.
195	 */
196	// Deckt sowohl "background-image:url(...)" als auch die Kurzschreibweise
197	// "background:url(...) no-repeat center/cover" ab (beide sind ueblich).
198	var BG_STYLE_REGEX = /background(?:-image)?\s*:[^;]*?url\(\s*(['"]?)([^'")]+)\1\s*\)/i;
199	var BG_ATTRS = [ 'data-bg', 'data-background', 'data-bg-image', 'data-background-image', 'data-bg-src', 'data-lazy-bg', 'data-lazy-background' ];
200	var BG_SELECTOR = '[style*="background"], [data-bg], [data-background], [data-bg-image], [data-background-image], [data-bg-src], [data-lazy-bg], [data-lazy-background]';
201
202	function collectBackgroundUrls( el ) {
203		var urls = [];
204		var style = el.getAttribute( 'style' );
205		if ( style ) {
206			var match = BG_STYLE_REGEX.exec( style );
207			if ( match && match[ 2 ] ) {
208				urls.push( match[ 2 ] );
209			}
210		}
211
212		BG_ATTRS.forEach( function ( attr ) {
213			var value = el.getAttribute( attr );
214			if ( value ) {
215				urls.push( value );
216			}
217		} );
218
219		return urls;
220	}
221
222	function isMarkedBackground( el ) {
223		var urls = collectBackgroundUrls( el );
224		for ( var i = 0; i < urls.length; i++ ) {
225			if ( isMarkedUrl( urls[ i ] ) ) {
226				return true;
227			}
228		}
229		return false;
230	}
231
232	var markedPaths = {};
233	var fileCounts = {};
234
235	( data.markedUrls || [] ).forEach( function ( url ) {
236		var path = pathKey( url );
237		if ( ! path ) {
238			return;
239		}
240		markedPaths[ path ] = true;
241		var file = fileKey( url );
242		fileCounts[ file ] = ( fileCounts[ file ] || 0 ) + 1;
243	} );
244
245	var markedFiles = {};
246	Object.keys( fileCounts ).forEach( function ( file ) {
247		if ( 1 === fileCounts[ file ] ) {
248			markedFiles[ file ] = true;
249		}
250	} );
251
252	var hasMarked = Object.keys( markedPaths ).length > 0 || Object.keys( markedIds ).length > 0;
253
254	function isMarkedUrl( url ) {
255		if ( ! url ) {
256			return false;
257		}
258		return !! markedPaths[ pathKey( url ) ] || !! markedFiles[ fileKey( url ) ];
259	}
260
261	/* ── Bilder ohne Server-Label nachtragen ───────────────────────────────── */
262
263	function removeAutoLabel( instance ) {
264		var label = autoLabels[ instance ];
265		if ( label && label.parentNode ) {
266			label.parentNode.removeChild( label );
267		}
268		delete autoLabels[ instance ];
269	}
270
271	/**
272	 * Gemeinsame Zuordnungslogik fuer <img>-Elemente und Hintergrundbild-
273	 * Container: prueft eine Liste von Kandidaten-Elementen, vergibt/entfernt
274	 * die "data-kiimg-instance"-Markierung samt Label. Der Praefix
275	 * unterscheidet Bild- und Hintergrund-Auto-Labels voneinander (nur fuer
276	 * Debugging/Lesbarkeit relevant, die Logik ist identisch).
277	 */
278	function syncElements( elements, prefix, isMarkedFn ) {
279		var changed = false;
280
281		for ( var i = 0; i < elements.length; i++ ) {
282			var el = elements[ i ];
283			var instance = el.getAttribute( 'data-kiimg-instance' );
284			var isAuto = !! instance && 0 === instance.indexOf( prefix );
285
286			if ( instance && ! isAuto ) {
287				continue;
288			}
289
290			var marked = isMarkedFn( el );
291
292			if ( marked && ! isAuto ) {
293				autoCounter++;
294				var newInstance = prefix + autoCounter;
295				el.setAttribute( 'data-kiimg-instance', newInstance );
296				autoLabels[ newInstance ] = createLabel( newInstance );
297				changed = true;
298			} else if ( ! marked && isAuto ) {
299				el.removeAttribute( 'data-kiimg-instance' );
300				removeAutoLabel( instance );
301				changed = true;
302			}
303		}
304
305		return changed;
306	}
307
308	/**
309	 * Geht alle Bilder und Hintergrundbild-Container der Seite durch und
310	 * ergaenzt bzw. entfernt Labels. Bereits vom Server oder von anderen
311	 * Modulen (z. B. WooCommerce-Ergaenzung, Elementor-Hintergruende)
312	 * gekennzeichnete Elemente bleiben unangetastet.
313	 */
314	function syncAll() {
315		if ( ! hasMarked ) {
316			return;
317		}
318
319		var changed = false;
320
321		changed = syncElements( document.images, AUTO_PREFIX, isMarkedImage ) || changed;
322
323		// Derselbe Schalter wie fuer Elementor-Hintergruende (Tab "Design"),
324		// damit ein Klick an einer Stelle beide Wege gleichermassen steuert.
325		if ( data.bgEnabled ) {
326			changed = syncElements( document.querySelectorAll( BG_SELECTOR ), BG_PREFIX, isMarkedBackground ) || changed;
327		}
328
329		Object.keys( autoLabels ).forEach( function ( instance ) {
330			if ( ! document.querySelector( '[data-kiimg-instance="' + instance + '"]' ) ) {
331				removeAutoLabel( instance );
332				changed = true;
333			}
334		} );
335
336		if ( changed ) {
337			refresh();
338		}
339	}
340
341	/* ── Laufende Beobachtung ──────────────────────────────────────────────── */
342
343	/*
344	 * Slider, Lazy Loading und clientseitig nachgeladene Blockinhalte
345	 * (z. B. Elementor-Posts-Widget mit AJAX-Pagination) fuegen Bilder
346	 * fortlaufend hinzu - ein einmaliger Durchlauf beim Laden reicht nicht.
347	 */
348	var tick = false;
349	function scheduleSync() {
350		if ( tick ) {
351			return;
352		}
353		tick = true;
354		requestAnimationFrame( function () {
355			tick = false;
356			syncAll();
357		} );
358	}
359
360	function bindObserver() {
361		if ( ! ( 'MutationObserver' in window ) ) {
362			return;
363		}
364		new MutationObserver( scheduleSync ).observe( document.body, {
365			childList: true,
366			subtree: true,
367			attributes: true,
368			attributeFilter: [ 'src', 'srcset', 'class', 'style', 'data-src', 'data-lazy-src', 'data-original', 'data-srcset', 'data-lazy-srcset', 'data-bg', 'data-background', 'data-bg-image', 'data-background-image', 'data-bg-src', 'data-lazy-bg', 'data-lazy-background' ]
369		} );
370	}
371
372	function init() {
373		bindObserver();
374		syncAll();
375	}
376
377	if ( 'loading' === document.readyState ) {
378		document.addEventListener( 'DOMContentLoaded', init );
379	} else {
380		init();
381	}
382} )();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.