/**
* aBlocks — Query Param Passthrough (frontend).
*
* Reads tracked query params (ref, utm_*, …) from the current URL and appends
* whichever are present to every "tagged" link on the page. A link is tagged
* when it (or a wrapping block) carries the configured CSS class — so it works
* both when the class sits on the and when Gutenberg puts it on the block
* wrapper with the nested inside.
*
* Config is injected before this file via wp_add_inline_script as
* window.ABlocksLinkPassthrough = { keys: string[], linkClass: string }.
*/
( function () {
'use strict';
var config = window.ABlocksLinkPassthrough;
if ( ! config || ! Array.isArray( config.keys ) || ! config.keys.length ) {
return;
}
if (
typeof window.URL !== 'function' ||
typeof window.URLSearchParams !== 'function'
) {
return; // Very old browser — bail rather than throw.
}
var mode = config.mode || 'class';
var linkClass = ( config.linkClass || 'aff-link' ).trim();
var keywords = ( Array.isArray( config.keywords ) ? config.keywords : [] )
.map( function ( word ) {
return String( word ).toLowerCase();
} )
.filter( Boolean );
// Nothing can match — bail before touching the DOM.
if ( mode === 'class' && ! linkClass ) {
return;
}
if ( mode === 'keyword' && ! keywords.length ) {
return;
}
var persist = config.persist !== false;
var cookieDays = parseInt( config.cookieDays, 10 );
if ( isNaN( cookieDays ) ) {
cookieDays = 30;
}
var COOKIE = 'ablocks_pt';
function readCookie( name ) {
var parts = ( document.cookie || '' ).split( '; ' );
for ( var i = 0; i < parts.length; i++ ) {
var eq = parts[ i ].indexOf( '=' );
if ( eq > -1 && parts[ i ].slice( 0, eq ) === name ) {
return decodeURIComponent( parts[ i ].slice( eq + 1 ) );
}
}
return '';
}
function writeCookie( name, value, days ) {
var expires = '';
if ( days > 0 ) {
var d = new Date();
d.setTime( d.getTime() + days * 24 * 60 * 60 * 1000 );
expires = '; expires=' + d.toUTCString();
}
document.cookie =
name +
'=' +
encodeURIComponent( value ) +
expires +
'; path=/; SameSite=Lax';
}
// Fresh values come from the current URL; remembered values come from the
// cookie. The URL always wins (a newer ?ref= overrides a stored one).
var current = new URLSearchParams( window.location.search );
var fresh = {};
config.keys.forEach( function ( key ) {
var value = current.get( key );
if ( value !== null && value !== '' ) {
fresh[ key ] = value;
}
} );
var stored = {};
if ( persist ) {
var raw = readCookie( COOKIE );
if ( raw ) {
try {
new URLSearchParams( raw ).forEach( function ( value, key ) {
if (
config.keys.indexOf( key ) !== -1 &&
value !== '' &&
fresh[ key ] === undefined
) {
stored[ key ] = value;
}
} );
} catch ( e ) {}
}
}
// Effective set = remembered values overlaid by anything fresh in the URL.
var effective = {};
config.keys.forEach( function ( key ) {
if ( fresh[ key ] !== undefined ) {
effective[ key ] = fresh[ key ];
} else if ( stored[ key ] !== undefined ) {
effective[ key ] = stored[ key ];
}
} );
var present = Object.keys( effective ).map( function ( key ) {
return [ key, effective[ key ] ];
} );
if ( ! present.length ) {
return;
}
// Persist whenever the URL brought fresh params, so later pages that drop
// the query string can still recover them from the cookie.
if ( persist && Object.keys( fresh ).length ) {
var out = new URLSearchParams();
present.forEach( function ( pair ) {
out.set( pair[ 0 ], pair[ 1 ] );
} );
writeCookie( COOKIE, out.toString(), cookieDays );
}
// In 'class' mode, match the directly or any inside a tagged
// wrapper; other modes start from every link and filter per anchor.
var selector =
mode === 'class'
? 'a.' + linkClass + ', .' + linkClass + ' a'
: 'a[href]';
function decorate( root ) {
var anchors;
try {
anchors = ( root || document ).querySelectorAll( selector );
} catch ( e ) {
return;
}
anchors.forEach( function ( anchor ) {
var href = anchor.getAttribute( 'href' );
if ( ! href ) {
return;
}
var lowered = href.toLowerCase().replace( /^\s+/, '' );
if (
lowered.charAt( 0 ) === '#' ||
lowered.indexOf( 'mailto:' ) === 0 ||
lowered.indexOf( 'tel:' ) === 0 ||
lowered.indexOf( 'javascript:' ) === 0 || // eslint-disable-line no-script-url
lowered.indexOf( 'data:' ) === 0
) {
return;
}
var url;
try {
url = new URL( href, window.location.href );
} catch ( e ) {
return;
}
if ( url.protocol !== 'http:' && url.protocol !== 'https:' ) {
return;
}
// 'all' mode is same-site only, so the ref is never leaked to
// unrelated external domains.
if ( mode === 'all' && url.origin !== window.location.origin ) {
return;
}
// 'keyword' mode only touches links whose URL contains a word.
if ( mode === 'keyword' ) {
var haystack = url.href.toLowerCase();
var matched = keywords.some( function ( word ) {
return haystack.indexOf( word ) !== -1;
} );
if ( ! matched ) {
return;
}
}
var changed = false;
present.forEach( function ( pair ) {
if ( url.searchParams.get( pair[ 0 ] ) === pair[ 1 ] ) {
return; // Already carries this exact value.
}
url.searchParams.set( pair[ 0 ], pair[ 1 ] );
changed = true;
} );
if ( changed ) {
anchor.setAttribute( 'href', url.toString() );
}
} );
}
function run() {
decorate( document );
// Re-decorate links injected later (interactive blocks, AJAX content…).
if ( typeof window.MutationObserver === 'function' ) {
var observer = new MutationObserver( function ( mutations ) {
for ( var i = 0; i < mutations.length; i++ ) {
if ( mutations[ i ].addedNodes.length ) {
decorate( document );
return;
}
}
} );
observer.observe( document.body, {
childList: true,
subtree: true,
} );
}
}
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', run );
} else {
run();
}
} )();