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', $tag, $item_attributes, self::resolve_placeholders( $ad, $inner_content, $context ) ); } if ( empty( $url ) ) { return sprintf( '<%1$s %2$s>%3$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 ); } }