admin Avatar

Sendgrid Bypass

Download

Adds a simple settings page that allows you to set patterns for email subjects. Uses fnmatch, so this allows the use of * wildcards

sendgrid-clicktrack-bypass.php php
<?php
/**
 * Plugin Name: SendGrid Click-Track Bypass
 * Description: Disables SendGrid click tracking on outbound emails whose subject matches an admin-managed pattern list. Useful for password resets, recovery mode notices, and other security-sensitive mail where rewritten URLs cause confusion.
 * Version:     1.0.0
 * Author:      Team51
 * License:     GPLv3
 */

namespace Drinkmaster\SendGridClickTrackBypass;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

const OPTION_NAME    = 'drinkmaster_sendgrid_clicktrack_bypass_rules';
const SETTINGS_GROUP = 'drinkmaster_sendgrid_clicktrack_bypass';
const PAGE_SLUG      = 'drinkmaster-sendgrid-clicktrack-bypass';
const SMTPAPI_HEADER = 'X-SMTPAPI: {"filters":{"clicktrack":{"settings":{"enable":0}}}}';

/**
 * Default seed list — used when the option has never been saved (or was wiped).
 * If an admin clears the list and saves, an empty array is stored and respected;
 * the seeds only re-appear if the option row itself is missing.
 *
 * @return string[]
 */
function get_default_rules() {
	return array(
		// Fatal error and recovery mode emails.
		'Technical Issue',
		'Recovery Mode',
		'Your Site is Experiencing',

		// Security-sensitive emails.
		'Password Reset',
		'Reset Password',
		'[Password Reset]',
		'Password Changed',

		// Account verification emails.
		'Confirm your email',
		'Verify your email',
		'Email Change Request',
		'New Admin Email Address',

		// Privacy and data requests.
		'Personal Data Export',
		'Personal Data Erasure',
		'Action Confirmed',

		// Two-factor and login security.
		'Login Confirmation',
		'Two-Factor',
		'2FA',

		// Auto-update and site health notifications.
		'Background Update',
		'Some plugins have failed to update',
		'Site Health',
	);
}

/**
 * Returns the saved rule list as a plain array of pattern strings.
 * Falls back to get_default_rules() when the option row doesn't exist.
 *
 * @return string[]
 */
function get_rules() {
	$raw = get_option( OPTION_NAME, '' );

	// Option row missing → seed with defaults.
	if ( '' === $raw || null === $raw ) {
		return get_default_rules();
	}

	$decoded = json_decode( (string) $raw, true );

	// Corrupted JSON → fall back to defaults rather than failing silently.
	if ( ! is_array( $decoded ) ) {
		return get_default_rules();
	}

	// Saved as [] (admin explicitly cleared) → respect that and return empty.
	return array_values( array_filter( array_map( 'strval', $decoded ), 'strlen' ) );
}

/**
 * Sanitize callback for the option. Accepts the repeater POST array and stores
 * it as a JSON-encoded list of trimmed, deduped, non-empty pattern strings.
 *
 * @param mixed $input Raw POSTed value.
 *
 * @return string JSON-encoded array.
 */
function sanitize_rules( $input ) {
	// WP calls sanitize_option twice on first save (update_option → add_option),
	// so on the 2nd pass we receive our own JSON-encoded string. Decode it back
	// so this function is idempotent.
	if ( is_string( $input ) ) {
		$decoded = json_decode( $input, true );
		$input   = is_array( $decoded ) ? $decoded : array();
	}

	if ( ! is_array( $input ) ) {
		return wp_json_encode( array() );
	}

	$clean = array_map( 'sanitize_text_field', array_map( 'wp_unslash', $input ) );
	$clean = array_values( array_unique( array_filter( $clean, 'strlen' ) ) );

	return wp_json_encode( $clean );
}

/**
 * Determine whether a given email subject matches any saved rule.
 * Auto-detects matching mode per rule:
 *   - Wildcards (`*` or `?`) → fnmatch-style match.
 *   - Otherwise              → case-insensitive substring match.
 *
 * @param string   $subject Email subject.
 * @param string[] $rules   Pattern list.
 *
 * @return boolean
 */
function subject_matches( $subject, array $rules ) {
	if ( '' === $subject || empty( $rules ) ) {
		return false;
	}

	$subject_lc = strtolower( $subject );

	foreach ( $rules as $pattern ) {
		$pattern_lc = strtolower( $pattern );

		if ( false !== strpos( $pattern_lc, '*' ) || false !== strpos( $pattern_lc, '?' ) ) {
			if ( function_exists( 'fnmatch' ) && fnmatch( $pattern_lc, $subject_lc ) ) {
				return true;
			}
			continue;
		}

		if ( false !== strpos( $subject_lc, $pattern_lc ) ) {
			return true;
		}
	}

	return false;
}

/**
 * Filter on wp_mail — injects the SendGrid header that disables click tracking
 * when the outgoing subject matches a configured rule.
 *
 * @param array $args Arguments passed to wp_mail().
 *
 * @return array
 */
function maybe_disable_clicktrack( $args ) {
	if ( empty( $args['subject'] ) || ! is_string( $args['subject'] ) ) {
		return $args;
	}

	$rules = get_rules();

	if ( empty( $rules ) ) {
		return $args;
	}

	if ( ! subject_matches( $args['subject'], $rules ) ) {
		return $args;
	}

	if ( empty( $args['headers'] ) ) {
		$args['headers'] = array();
	} elseif ( is_string( $args['headers'] ) ) {
		$args['headers'] = explode( "\n", str_replace( "\r\n", "\n", $args['headers'] ) );
	} elseif ( ! is_array( $args['headers'] ) ) {
		$args['headers'] = array( (string) $args['headers'] );
	}

	// Avoid duplicating the header if some other code already added it.
	foreach ( $args['headers'] as $existing ) {
		if ( is_string( $existing ) && 0 === stripos( ltrim( $existing ), 'X-SMTPAPI:' ) ) {
			return $args;
		}
	}

	$args['headers'][] = SMTPAPI_HEADER;

	return $args;
}
add_filter( 'wp_mail', __NAMESPACE__ . '\\maybe_disable_clicktrack' );

/**
 * Register the option with the Settings API.
 *
 * @return void
 */
function register_settings() {
	register_setting(
		SETTINGS_GROUP,
		OPTION_NAME,
		array(
			'type'              => 'string',
			'sanitize_callback' => __NAMESPACE__ . '\\sanitize_rules',
			'default'           => wp_json_encode( array() ),
			'show_in_rest'      => false,
		)
	);
}
add_action( 'admin_init', __NAMESPACE__ . '\\register_settings' );

/**
 * Add the settings page under Settings.
 *
 * @return void
 */
function register_settings_page() {
	add_options_page(
		__( 'SendGrid Click-Track Bypass', 'drinkmaster' ),
		__( 'SendGrid Tracking', 'drinkmaster' ),
		'manage_options',
		PAGE_SLUG,
		__NAMESPACE__ . '\\render_settings_page'
	);
}
add_action( 'admin_menu', __NAMESPACE__ . '\\register_settings_page' );

/**
 * Render the settings screen with a JS-driven repeater.
 *
 * @return void
 */
function render_settings_page() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	$rules = get_rules();
	if ( empty( $rules ) ) {
		$rules = array( '' );
	}
	?>
	<div class="wrap">
		<h1><?php esc_html_e( 'SendGrid Click-Track Bypass', 'drinkmaster' ); ?></h1>

		<p>
			<?php
			esc_html_e(
				'Outgoing emails whose subject matches any rule below will have SendGrid click tracking disabled via the X-SMTPAPI header. Use a plain phrase for a case-insensitive substring match (e.g. "Password Reset"), or include * / ? for wildcard matching (e.g. "*Recovery Mode*").',
				'drinkmaster'
			);
			?>
		</p>

		<form method="post" action="options.php">
			<?php settings_fields( SETTINGS_GROUP ); ?>

			<table class="widefat striped" id="drinkmaster-scb-rules" style="max-width:780px;">
				<thead>
					<tr>
						<th style="width:100%;"><?php esc_html_e( 'Subject pattern', 'drinkmaster' ); ?></th>
						<th><?php esc_html_e( 'Remove', 'drinkmaster' ); ?></th>
					</tr>
				</thead>
				<tbody>
					<?php foreach ( $rules as $rule ) : ?>
						<tr class="drinkmaster-scb-row">
							<td>
								<input
									type="text"
									name="<?php echo esc_attr( OPTION_NAME ); ?>[]"
									value="<?php echo esc_attr( $rule ); ?>"
									class="regular-text"
									style="width:100%;"
								/>
							</td>
							<td>
								<button type="button" class="button drinkmaster-scb-remove">×</button>
							</td>
						</tr>
					<?php endforeach; ?>
				</tbody>
			</table>

			<p>
				<button type="button" class="button" id="drinkmaster-scb-add">
					<?php esc_html_e( '+ Add rule', 'drinkmaster' ); ?>
				</button>
			</p>

			<?php submit_button(); ?>
		</form>

		<template id="drinkmaster-scb-row-template">
			<tr class="drinkmaster-scb-row">
				<td>
					<input
						type="text"
						name="<?php echo esc_attr( OPTION_NAME ); ?>[]"
						value=""
						class="regular-text"
						style="width:100%;"
					/>
				</td>
				<td>
					<button type="button" class="button drinkmaster-scb-remove">×</button>
				</td>
			</tr>
		</template>

		<script>
		( function () {
			var table    = document.getElementById( 'drinkmaster-scb-rules' );
			var tbody    = table ? table.querySelector( 'tbody' ) : null;
			var addBtn   = document.getElementById( 'drinkmaster-scb-add' );
			var template = document.getElementById( 'drinkmaster-scb-row-template' );

			if ( ! tbody || ! addBtn || ! template ) {
				return;
			}

			addBtn.addEventListener( 'click', function () {
				var clone = template.content.firstElementChild.cloneNode( true );
				tbody.appendChild( clone );
				var input = clone.querySelector( 'input' );
				if ( input ) {
					input.focus();
				}
			} );

			tbody.addEventListener( 'click', function ( e ) {
				var btn = e.target.closest( '.drinkmaster-scb-remove' );
				if ( ! btn ) {
					return;
				}
				var row = btn.closest( '.drinkmaster-scb-row' );
				if ( ! row ) {
					return;
				}
				if ( tbody.querySelectorAll( '.drinkmaster-scb-row' ).length > 1 ) {
					row.remove();
				} else {
					var input = row.querySelector( 'input' );
					if ( input ) {
						input.value = '';
					}
				}
			} );
		} )();
		</script>
	</div>
	<?php
}

Developer Discussions