hooks(); } return self::$instance; } /** * Register all necessary hooks for the Privacy module. * * @return void */ public function hooks(): void { add_action( 'admin_bar_menu', function( $wp_admin_bar ) { $no_cookies = (bool) Settings::get_setting( 'no_cookies' ); if ( $no_cookies ) { Notification_Center::get()->register( [ 'slug' => 'adpresso-cookies_disabled', 'message' => 'Cookies are disabled', 'location' => 'admin_bar', 'type' => 'warning', ] ); } $method = Settings::get_setting( 'privacy_consent_method' ); Notification_Center::get()->register( [ 'slug' => 'adpresso-module-privacy-type', 'message' => 'Privacy method: ' . $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; } $script_handle = 'adpresso-privacy'; $dependencies = [ 'adpresso-core-events', // Handle of the event bus script. ]; wp_register_script( $script_handle, plugin_dir_url( __FILE__ ) . 'assets/js/privacy-manager.js', $dependencies, '1.0', true ); wp_enqueue_script( $script_handle ); } /** * 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, $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. // TODO: Add filter to allow other ad types to be added here. $allowed_privacy_types = [ 'image', 'dummy' ]; $item_type = null; if ( $item instanceof Ad ) { $item_type = $item->get_type(); } 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(); if ( ! 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 ) { // error_log("AdPresso Privacy: Forcing JS for Group #{$item->get_id()} in Placement #{$placement->get_id()} due to Privacy Module (contains non-safe ad types)."); return true; } // All ads in the group are of allowed types, so we do not force JS rendering. return false; } else { return false; } if ( in_array( $item_type, $allowed_privacy_types, true ) ) { // error_log("AdPresso 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. // error_log("AdPresso Privacy: Forcing JS for Placement #{$placement->get_id()} (Item Type: {$item_type}) due to active Privacy Module."); 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( 'consent_method', 'auto' ), 'tcf_require_vendors' => ! empty( Settings::get_setting( 'tcf_require_vendors' ) ), 'tcf_vendor_ids' => array_map( 'intval', explode( ',', Settings::get_setting( 'tcf_vendor_ids', '' ) ) ), 'tcf_purposes_personalized' => array_map( 'intval', Settings::get_setting( 'tcf_purposes_personalized', [ 1, 3, 4 ] ) ), 'tcf_purposes_npa' => array_map( 'intval', Settings::get_setting( 'tcf_purposes_npa', [ 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' ) ), 'allow_npa_fallback' => ! empty( Settings::get_setting( 'allow_npa_fallback' ) ), ]; // Adds the privacy configuration to the global data object. // TODO: Chose the correct place to store this data. if ( ! isset( $data['adpressoProModules'] ) ) { $data['adpressoProModules'] = []; } $data['adpressoProModules']['privacy'] = $js_config; return $data; } }