hooks(); } return self::$instance; } /** * Register all necessary hooks for the Privacy module. * * @return void */ public function hooks(): void { add_action( 'adpresso_register_assets', [ $this, 'register_assets' ] ); add_action( 'admin_bar_menu', function( $wp_admin_bar ) { $no_cookies = (bool) Settings::get_setting( 'no_cookies' ); if ( $no_cookies ) { NotificationManager::get()->register( 'adpresso-cookies_disabled', __( 'Cookies are disabled', 'adpresso' ), [ 'location' => 'admin_bar', 'type' => 'warning', ] ); } $method = Settings::get_setting( 'privacy_consent_method' ); NotificationManager::get()->register( 'adpresso-module-privacy-type', /* translators: %s is the selected privacy method */ sprintf( esc_html__( 'Privacy method: %s', 'adpresso' ), $method ), [ 'location' => 'admin_bar', 'context' => 'adpresso-module-privacy', ] ); } ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_frontend_assets' ] ); add_filter( 'adpresso_data_for_js', [ $this, 'add_config_to_localized_data' ] ); add_filter( 'adpresso_requires_frontend_logic', [ $this, 'force_js_rendering_if_active' ], 10, 3 ); } /** * Registers and enqueues the frontend JavaScript for the Privacy module. * * @return void */ public function enqueue_frontend_assets(): void { if ( ! class_exists( ModuleManager::class ) || ! ModuleManager::is_active( self::MODULE_ID ) ) { return; } if ( is_admin() ) { return; } wp_enqueue_script( 'adpresso-privacy' ); if ( adpresso_fs()->can_use_premium_code() ) { wp_enqueue_script( 'adpresso-privacy-pro' ); } } /** * Registers the assets for the Privacy module. * * @param \AdPresso\Core\Assets $assets The Assets service instance. */ public function register_assets( \AdPresso\Core\Assets $assets ): void { if ( ! ModuleManager::is_active( self::MODULE_ID ) ) { return; } $dependencies = [ 'adpresso-core-events', ]; $assets->register_script( 'privacy', 'modules/privacy/assets/js/privacy-manager.js', $dependencies, true ); if ( adpresso_fs()->can_use_premium_code() ) { $assets->register_script( 'privacy-pro', 'pro/core/privacy/assets/js/privacy-pro.js', $dependencies, true ); } } /** * Callback for the filter 'adpresso_requires_frontend_logic'. * Forces JS rendering for all placements when the Privacy module is active, * except for specific ad types (Image, Dummy). * * @param bool $needs_js The initial value indicating whether JS rendering is needed. * @param Placement $placement The current Placement object. * @param mixed $item The placement item (Ad or Group). * * @return bool True, if JS rendering should be forced, false otherwise. */ public function force_js_rendering_if_active( bool $needs_js, Placement $placement, mixed $item ): bool { // If JS is already required for other reasons, do not change it. if ( $needs_js ) { return true; } if ( ! class_exists( ModuleManager::class ) || ! ModuleManager::is_active( self::MODULE_ID ) ) { return false; } if ( ! $item || ( method_exists( $item, 'is_backend_valid' ) && ! $item->is_backend_valid ) ) { return false; } // Exception: Image and Dummy ads can run without JS rendering (SSR), // as they typically do not load external scripts or set tracking pixels // that would require consent. // Also allow ads that have the "Ignore Privacy Settings" toggle enabled. $allowed_privacy_types = apply_filters( 'adpresso_privacy_exceptional_ad_types', [ 'image', 'dummy' ] ); if ( $item instanceof Ad ) { $item_type = $item->get_type(); if ( (bool) $item->get_meta( 'ignore_privacy_settings', 0 ) ) { RuntimeDebugger::add_server_error_log_entry( "Privacy: Ad #{$item->get_id()} has 'ignore_privacy_settings' enabled. Allowing SSR." ); return false; } } elseif ( $item instanceof Group ) { $all_ads_are_safe = true; if ( method_exists( $item, 'get_valid_ads' ) ) { $valid_ads = $item->get_valid_ads(); if ( empty( $valid_ads ) ) { return false; } foreach ( $valid_ads as $ad_in_group ) { if ( $ad_in_group instanceof Ad ) { $ad_type_slug = $ad_in_group->get_type(); $ignore_privacy = (bool) $ad_in_group->get_meta( 'ignore_privacy_settings', 0 ); if ( ! $ignore_privacy && ! in_array( $ad_type_slug, $allowed_privacy_types, true ) ) { $all_ads_are_safe = false; break; } } else { $all_ads_are_safe = false; break; } } } else { $all_ads_are_safe = false; } if ( ! $all_ads_are_safe ) { RuntimeDebugger::add_server_error_log_entry( "Privacy: Forcing JS for Group #{$item->get_id()} in Placement #{$placement->get_id()} due to Privacy Module (contains non-safe ad types)." ); RuntimeDebugger::note_js_forcing_reason( 'privacy', __( 'Privacy module active (forces JS for consent check, except Image/Dummy ads and ads with "Ignore Privacy Settings" enabled)', 'adpresso' ) ); return true; } // All ads in the group are of allowed types or ignore privacy, so we do not force JS rendering. return false; } else { return false; } if ( in_array( $item_type, $allowed_privacy_types, true ) ) { RuntimeDebugger::add_server_error_log_entry( 'Privacy: Ad type ' . $item_type . ' in Placement #{$placement->get_id()} allowed for SSR despite active Privacy Module.' ); return false; } // For all other ad types, JS rendering is forced if the Privacy module is active. RuntimeDebugger::add_server_error_log_entry( 'Privacy: Forcing JS for Placement #{$placement->get_id()} (Item Type: {$item_type}) due to active Privacy Module.' ); RuntimeDebugger::note_js_forcing_reason( 'privacy', __( 'Privacy module active (forces JS for consent check, except Image/Dummy ads and ads with "Ignore Privacy Settings" enabled)', 'adpresso' ) ); return true; } /** * Adds the Privacy module configuration to the localized data * * @param array $data The data array that will be localized for the frontend JS. * * @return array The modified data array with the Privacy configuration added. */ public function add_config_to_localized_data( array $data ): array { if ( ! class_exists( ModuleManager::class ) || ! ModuleManager::is_active( self::MODULE_ID ) ) { return $data; } // Prepare the JavaScript configuration for the Privacy module. $js_config = [ 'consent_method' => Settings::get_setting( 'privacy_consent_method', 'auto' ), 'tcf_purposes_personalized' => apply_filters( 'adpresso_privacy_tcf_purposes_personalized', [ 1, 3, 4 ] ), 'tcf_purposes_npa' => apply_filters( 'adpresso_privacy_tcf_purposes_npa', [ 1 ] ), 'allow_npa_fallback' => (bool) Settings::get_setting( 'tcf_personalized_purpose', 1 ), 'cookie_name' => sanitize_text_field( Settings::get_setting( 'cookie_name', 'cookie_consent' ) ), 'cookie_value_accepted' => sanitize_text_field( Settings::get_setting( 'cookie_value_accepted', 'accepted' ) ), 'cookie_value_partial' => sanitize_text_field( Settings::get_setting( 'cookie_value_partial', 'partial' ) ), 'allowed_privacy_types' => apply_filters( 'adpresso_privacy_exceptional_ad_types', [ 'image', 'dummy' ] ), ]; $data['privacyConfig'] = $js_config; return $data; } /** * * * @return array */ public static function get_tcf_vendor_list_options(): array { return apply_filters( 'adpresso_tcf_vendor_list_options', [ [ 'value' => '0', 'label' => __( 'None / No TCF Vendor', 'adpresso' ) ], [ 'value' => '755', 'label' => 'Google Advertising Products' ], [ 'value' => '102', 'label' => 'Taboola' ], [ 'value' => '10', 'label' => 'Index Exchange' ], ] ); } }