| 1 |
<?php |
| 2 |
/** |
| 3 |
* Query Param Passthrough. |
| 4 |
* |
| 5 |
* Carries incoming affiliate / UTM query parameters (e.g. ?ref=xyz) onto |
| 6 |
* "tagged" links on the page so a visitor who lands with a tracking param keeps |
| 7 |
* it as they click through the site. It is fully inert unless a tracked param |
| 8 |
* exists in the current request URL AND a tagged link is present on the page. |
| 9 |
* |
| 10 |
* Because tagged links then carry the param forward, it persists across page |
| 11 |
* views without any cookie. |
| 12 |
* |
| 13 |
* @package ABlocks |
| 14 |
*/ |
| 15 |
|
| 16 |
namespace ABlocks\Frontend; |
| 17 |
|
| 18 |
use ABlocks\Helper; |
| 19 |
|
| 20 |
if ( ! defined( 'ABSPATH' ) ) { |
| 21 |
exit; // Exit if accessed directly. |
| 22 |
} |
| 23 |
|
| 24 |
class LinkPassthrough { |
| 25 |
|
| 26 |
public static function init() { |
| 27 |
$self = new self(); |
| 28 |
add_action( 'wp_enqueue_scripts', [ $self, 'enqueue_assets' ] ); |
| 29 |
} |
| 30 |
|
| 31 |
/** |
| 32 |
* The class a link (or a wrapping block) must carry to receive the params. |
| 33 |
* |
| 34 |
* @return string |
| 35 |
*/ |
| 36 |
public function get_link_class() : string { |
| 37 |
$class = (string) Helper::get_settings( 'param_passthrough_class', 'aff-link' ); |
| 38 |
$class = trim( $class ); |
| 39 |
// Keep it a safe, single CSS class token. |
| 40 |
$class = preg_replace( '/[^A-Za-z0-9_-]/', '', $class ); |
| 41 |
|
| 42 |
return '' === $class ? 'aff-link' : $class; |
| 43 |
} |
| 44 |
|
| 45 |
/** |
| 46 |
* Which links receive the params: 'class', 'all' or 'keyword'. |
| 47 |
* |
| 48 |
* @return string |
| 49 |
*/ |
| 50 |
public function get_match_mode() : string { |
| 51 |
$mode = (string) Helper::get_settings( 'param_passthrough_match', 'class' ); |
| 52 |
|
| 53 |
return in_array( $mode, [ 'class', 'all', 'keyword' ], true ) ? $mode : 'class'; |
| 54 |
} |
| 55 |
|
| 56 |
/** |
| 57 |
* Lower-cased list of words a link URL must contain in 'keyword' mode. |
| 58 |
* |
| 59 |
* @return string[] |
| 60 |
*/ |
| 61 |
public function get_keywords() : array { |
| 62 |
$raw = (string) Helper::get_settings( 'param_passthrough_keyword', '' ); |
| 63 |
|
| 64 |
$words = array_filter( |
| 65 |
array_map( |
| 66 |
static function ( $word ) { |
| 67 |
return strtolower( trim( $word ) ); |
| 68 |
}, |
| 69 |
explode( ',', $raw ) |
| 70 |
) |
| 71 |
); |
| 72 |
|
| 73 |
return array_values( array_unique( $words ) ); |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Sanitized list of query-parameter keys to carry. |
| 78 |
* |
| 79 |
* @return string[] |
| 80 |
*/ |
| 81 |
public function get_param_keys() : array { |
| 82 |
$raw = (string) Helper::get_settings( |
| 83 |
'param_passthrough_keys', |
| 84 |
'ref,utm_source,utm_medium,utm_campaign,utm_term,utm_content' |
| 85 |
); |
| 86 |
|
| 87 |
$keys = array_filter( |
| 88 |
array_map( |
| 89 |
static function ( $key ) { |
| 90 |
// Query-string keys are word-ish; strip anything unexpected. |
| 91 |
return preg_replace( '/[^A-Za-z0-9_\-.]/', '', trim( $key ) ); |
| 92 |
}, |
| 93 |
explode( ',', $raw ) |
| 94 |
) |
| 95 |
); |
| 96 |
|
| 97 |
return array_values( array_unique( $keys ) ); |
| 98 |
} |
| 99 |
|
| 100 |
public function enqueue_assets() { |
| 101 |
if ( ! (bool) Helper::get_settings( 'param_passthrough_enabled', false ) ) { |
| 102 |
return; |
| 103 |
} |
| 104 |
|
| 105 |
// Nothing to do in the admin / editor context. |
| 106 |
if ( is_admin() ) { |
| 107 |
return; |
| 108 |
} |
| 109 |
|
| 110 |
$keys = $this->get_param_keys(); |
| 111 |
if ( empty( $keys ) ) { |
| 112 |
return; |
| 113 |
} |
| 114 |
|
| 115 |
$path = ABLOCKS_ASSETS_PATH . 'js/link-passthrough.js'; |
| 116 |
if ( ! file_exists( $path ) ) { |
| 117 |
return; |
| 118 |
} |
| 119 |
|
| 120 |
// The payload is sub-1KB gzipped and already needs an inline config |
| 121 |
// block, so it's printed inline rather than as an extra HTTP request. |
| 122 |
// The readable source file stays the single source of truth. |
| 123 |
$handle = 'ablocks-link-passthrough'; |
| 124 |
wp_register_script( $handle, false, [], ABLOCKS_VERSION, [ 'in_footer' => true ] ); |
| 125 |
wp_enqueue_script( $handle ); |
| 126 |
|
| 127 |
$config = [ |
| 128 |
'keys' => $keys, |
| 129 |
'mode' => $this->get_match_mode(), |
| 130 |
'linkClass' => $this->get_link_class(), |
| 131 |
'keywords' => $this->get_keywords(), |
| 132 |
'persist' => (bool) Helper::get_settings( 'param_passthrough_persist', true ), |
| 133 |
'cookieDays' => max( 0, (int) Helper::get_settings( 'param_passthrough_cookie_days', 30 ) ), |
| 134 |
]; |
| 135 |
|
| 136 |
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents |
| 137 |
$body = (string) file_get_contents( $path ); |
| 138 |
|
| 139 |
wp_add_inline_script( |
| 140 |
$handle, |
| 141 |
'window.ABlocksLinkPassthrough = ' . wp_json_encode( $config ) . ";\n" . $body, |
| 142 |
'after' |
| 143 |
); |
| 144 |
} |
| 145 |
} |
| 146 |
|