*/ private const ALLOWED_VARIABLES = array( 'url', 'encoded_url', 'provider', 'product_id' ); /** * Template string. */ private string $template; /** * @param string $template Template string. * * @throws InvalidArgumentException When the template is invalid. */ public function __construct( string $template ) { $this->template = self::normalizeTemplate( $template ); } /** * Return template string. */ public function template(): string { return $this->template; } /** * Apply routing to a destination URL. * * @param string $url Destination URL. * @param string $provider Provider key. * @param int|null $productId Product id. */ public function apply( string $url, string $provider, ?int $productId = null ): string { $rendered = strtr( $this->template, array( '{url}' => $url, '{encoded_url}' => rawurlencode( $url ), '{provider}' => rawurlencode( $provider ), '{product_id}' => $productId === null ? '' : (string) $productId, ) ); if ( ! self::isSafeHttpUrl( $rendered ) ) { return $url; } return $rendered; } /** * Normalize and validate template. * * @param string $template Template. * * @throws InvalidArgumentException When invalid. */ private static function normalizeTemplate( string $template ): string { $template = trim( $template ); if ( $template === '' ) { throw new InvalidArgumentException( 'Affiliate routing template must be non-empty.' ); } if ( preg_match_all( '/\{([a-z_]+)\}/', $template, $matches ) === 1 ) { foreach ( $matches[1] as $variable ) { if ( ! in_array( $variable, self::ALLOWED_VARIABLES, true ) ) { throw new InvalidArgumentException( 'Unknown affiliate routing template variable.' ); } } } $sample = strtr( $template, array( '{url}' => 'https://example.test/product', '{encoded_url}' => 'https%3A%2F%2Fexample.test%2Fproduct', '{provider}' => 'amazon', '{product_id}' => '123', ) ); if ( ! self::isSafeHttpUrl( $sample ) ) { throw new InvalidArgumentException( 'Affiliate routing template must render to an HTTP(S) URL.' ); } return $template; } /** * Check absolute HTTP(S) URL. * * @param string $url URL. */ private static function isSafeHttpUrl( string $url ): bool { return preg_match( '#\Ahttps?://#i', $url ) === 1; } }