get_meta( 'target_url', '' ) );
if ( empty( $raw_link ) ) {
return '';
}
/**
* Filter to enable link masking/cloaking for a specific ad.
*
* @param bool $is_cloaked Whether the ad's link should be cloaked. Default false.
* @param Ad $ad The ad entity.
*/
$is_cloaked = (bool) apply_filters( 'adpresso_ad_link_masking_enabled', false, $ad );
if ( ! $is_cloaked ) {
return $raw_link;
}
$global_base = Settings::get_setting( 'linkbase', Constants::LINK_MASKING_LINKBASE );
$ad_base = trim( (string) $ad->get_meta( 'linkbase_override', '' ) );
$base = ! empty( $ad_base ) ? $ad_base : $global_base;
$custom_slug = trim( (string) $ad->get_meta( 'custom_link_slug', '' ) );
$identifier = ! empty( $custom_slug ) ? $custom_slug : $ad->get_id();
return home_url( '/' . trailingslashit( sanitize_title( $base ) ) . sanitize_title( $identifier ) );
}
/**
* The link's target attribute value.
*
* @param Ad $ad The ad entity.
*
* @return string
*/
public static function get_target( Ad $ad ): string {
return Util::get_effective_setting( $ad, 'target_blank' ) ? '_blank' : '_self';
}
/**
* The link's rel attribute value.
*
* @param Ad $ad The ad entity.
*
* @return string
*/
public static function get_rel( Ad $ad ): string {
$sponsored = self::adds_link_attribute( $ad, 'add_sponsored' ) ? ' sponsored' : '';
$nofollow = self::adds_link_attribute( $ad, 'add_nofollow' ) ? ' nofollow' : '';
return 'noopener noreferrer' . $sponsored . $nofollow;
}
/**
* The ad's own Yes/No, else whether the global "Add Link Attributes" list contains the attribute.
*
* @param Ad $ad
* @param string $key 'add_sponsored' or 'add_nofollow'.
*
* @return bool
*/
private static function adds_link_attribute( Ad $ad, string $key ): bool {
$own = (string) $ad->get_meta( $key );
if ( $own === '1' || $own === '0' ) {
return $own === '1';
}
$global = Settings::get_setting( 'add_link_attributes', [ 'add_nofollow' ] );
return in_array( $key, Util::get_checked_keys( $global ), true );
}
/**
* Builds the dynamic link attributes (target, rel).
*
* @param Ad $ad The ad entity.
*
* @return string The link attributes.
*/
public static function get_link_attributes( Ad $ad ): string {
return sprintf(
'target="%s" rel="%s"',
self::get_target( $ad ),
self::get_rel( $ad )
);
}
/**
* Whether this ad's click should be counted at the redirect itself rather than by the frontend's click listener.
*
* @param Ad $ad
* @param string $final_url The already-resolved return value of get_final_url().
*
* @return bool
*/
public static function is_redirect_tracked( Ad $ad, string $final_url ): bool {
if ( ! (bool) $ad->get_meta( 'redirect_click_tracking', false ) ) {
return false;
}
$raw_link = trim( (string) $ad->get_meta( 'target_url', '' ) );
return $final_url !== '' && $final_url !== $raw_link;
}
/**
* Adds the placement/group context to a redirect-tracked link, so the redirect can store it with the click.
*
* @param string $url The masked URL.
* @param array $context Render context (placement_id, group_id).
*
* @return string
*/
public static function with_click_context( string $url, array $context ): string {
$placement_id = (int) ( $context['placement_id'] ?? 0 );
$group_id = (int) ( $context['group_id'] ?? 0 );
if ( $placement_id <= 0 && $group_id <= 0 ) {
return $url;
}
return add_query_arg( self::CONTEXT_PARAM, max( 0, $placement_id ) . '-' . max( 0, $group_id ), $url );
}
/**
* Replaces %link% (and %link_attributes%) in author-written content with the ad's final URL.
*
* @param Ad $ad The ad entity.
* @param string $content The ad content.
* @param array $context Render context (placement_id, group_id) for redirect-tracked links.
*
* @return string
*/
public static function resolve_placeholders( Ad $ad, string $content, array $context = [] ): string {
if ( ! str_contains( $content, '%link%' ) ) {
return $content;
}
$url = self::get_final_url( $ad );
if ( empty( $url ) ) {
return $content;
}
if ( self::is_redirect_tracked( $ad, $url ) ) {
// The click context joins an author query after the placeholder (%link%?utm=x) instead of breaking it.
$content = (string) preg_replace_callback(
'/%link%(\?[^"\'\s<>#]*)?/',
static fn( array $m ): string => esc_url( self::with_click_context( $url . ( $m[1] ?? '' ), $context ) ),
$content
);
} else {
$content = str_replace( '%link%', esc_url( $url ), $content );
}
if ( str_contains( $content, '%link_attributes%' ) ) {
$content = str_replace( '%link_attributes%', self::get_link_attributes( $ad ), $content );
}
return $content;
}
/**
* The magic wrapper. Takes the content and wraps it based on the settings.
*
* @param Ad $ad The ad entity.
* @param string $inner_content The inner content of the ad.
* @param string $item_attributes The attributes of the ad item.
* @param bool $is_inline True for an inline-overridden placement/shortcode
* @param array $context Render context (placement_id, group_id) for redirect-tracked links.
*
* @return string The wrapped content.
*/
public static function wrap( Ad $ad, string $inner_content, string $item_attributes = '', bool $is_inline = false, array $context = [] ): string {
$url = self::get_final_url( $ad );
$tag = $is_inline ? 'span' : 'div';
$is_tracked = self::is_redirect_tracked( $ad, $url );
$prefix = sanitize_html_class( Settings::get_setting( 'frontend_prefix', Constants::DEFAULT_FRONTEND_PREFIX ) );
if ( ! empty( $url ) && str_contains( $inner_content, '%link%' ) ) {
// The author wrote the , so tracking.js recognises the redirect link by this URL instead of an attribute on it.
if ( $is_tracked ) {
$item_attributes .= sprintf( ' data-%s-rurl="%s"', $prefix, esc_url( $url ) );
}
return sprintf( '<%1$s %2$s>%3$s%1$s>', $tag, $item_attributes, self::resolve_placeholders( $ad, $inner_content, $context ) );
}
if ( empty( $url ) ) {
return sprintf( '<%1$s %2$s>%3$s%1$s>', $tag, $item_attributes, $inner_content );
}
$click_src_attr = '';
if ( $is_tracked ) {
$click_src_attr = sprintf( ' data-%s-click-src="redirect"', $prefix );
$url = self::with_click_context( $url, $context );
}
// display:block goes into the item's own style attribute; a second style attribute would be dropped.
// Prepended, so the ad's own inline CSS can still override it.
if ( ! $is_inline ) {
$item_attributes = str_contains( $item_attributes, 'style="' )
? preg_replace( '/\bstyle="/', 'style="display:block; ', $item_attributes, 1 )
: trim( $item_attributes . ' style="display:block;"' );
}
return sprintf(
'%s',
esc_url( $url ),
self::get_link_attributes( $ad ),
$item_attributes,
$click_src_attr,
$inner_content
);
}
}