admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'eppro_popup_view' ), ] ); // Load each popup's Elementor CSS in (we are inside // wp_enqueue_scripts), so the footer markup is styled on first paint. if ( class_exists( '\Elementor\Core\Files\CSS\Post' ) ) { foreach ( $popups as $popup ) { try { $css = \Elementor\Core\Files\CSS\Post::create( $popup->ID ); if ( $css && method_exists( $css, 'enqueue' ) ) { $css->enqueue(); } } catch ( \Throwable $e ) { eppro_log( 'EPP popup CSS enqueue error for popup ' . $popup->ID . ': ' . $e->getMessage() ); } } } } // ========================================================================= // Analytics: record a popup view // ========================================================================= public static function ajax_record_view(): void { if ( ! check_ajax_referer( 'eppro_popup_view', '_wpnonce', false ) ) { wp_send_json_error( [], 403 ); } $post_id = absint( $_POST['post_id'] ?? 0 ); if ( ! $post_id || get_post_type( $post_id ) !== EPPRO_Popup_CPT::POST_TYPE ) { wp_send_json_error( [], 400 ); } // Pages cached before 1.0.6 still call this action: count it as a view. if ( class_exists( 'EPPRO_Popup_Analytics' ) ) { EPPRO_Popup_Analytics::record( $post_id, 'view' ); } wp_send_json_success(); } // ========================================================================= // Footer render // ========================================================================= public static function render(): void { if ( is_admin() ) return; if ( ! class_exists( 'Elementor\Plugin' ) ) return; $frontend = \Elementor\Plugin::instance()->frontend ?? null; if ( ! $frontend ) return; $popups = self::get_active_popups_for_page(); if ( empty( $popups ) ) return; $configs = []; foreach ( $popups as $popup ) { $meta = EPPRO_Popup_Settings::get( $popup->ID ); $design = EPPRO_Popup_Design::get( $popup->ID ); // Build content via Elementor (CSS was enqueued in by maybe_enqueue()). $content = EPPRO_Elementor_Helper::get_template_content( $popup->ID, false ); if ( empty( trim( $content ) ) ) continue; // Sizes, colours and the close button's look come from the popup's // Elementor page settings, written into its post CSS file (see // EPPRO_Popup_Design), so no inline styles are printed here. EPPRO_Popup_Design::print_frame_open( $popup->ID, $popup->post_title, $design ); echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Elementor builder output; every widget escapes its own markup (kses would strip icons, form fields and iframes). EPPRO_Popup_Design::print_frame_close(); $configs[] = [ 'id' => 'eppro-popup-' . $popup->ID, 'postId' => $popup->ID, 'triggers' => array_values( (array) ( $meta['triggers'] ?? [] ) ), 'delay' => absint( $meta['page_load_delay'] ?? 0 ), 'scrollDepth' => absint( $meta['scroll_depth'] ?? 50 ), 'clickSelector' => sanitize_text_field( $meta['click_selector'] ?? '' ), 'inactivityTime' => absint( $meta['inactivity_time'] ?? 30 ), 'formSelector' => sanitize_text_field( $meta['form_selector'] ?? '' ), 'redirectOnClose' => $design['redirect_on_close'], 'position' => $design['position'], 'showOnce' => ! empty( $meta['show_once'] ), 'showOnceSession' => ! empty( $meta['show_once_session'] ), 'cookieDays' => absint( $meta['cookie_days'] ?? 30 ), 'maxPerSession' => ! empty( $meta['max_per_session'] ) ? absint( $meta['max_per_session'] ) : null, 'afterPageViews' => ! empty( $meta['after_page_views'] ) ? absint( $meta['after_page_views'] ) : null, 'lifetimeCap' => ! empty( $meta['lifetime_cap'] ) ? absint( $meta['lifetime_cap'] ) : null, 'closeOverlay' => $design['close_overlay'], 'closeEsc' => $design['close_esc'], 'closePosition' => $design['close_position'], 'hideCloseFor' => $design['hide_close_for'], 'autoCloseAfter' => $design['auto_close_after'], 'bodyScrollLock' => $design['scroll_lock'], 'visitorType' => sanitize_key( $meta['visitor_type'] ?? 'all' ), 'animSpeed' => $design['anim_speed'], ]; } if ( ! empty( $configs ) ) { wp_add_inline_script( 'eppro-popup', // Full hex-flag set escapes <, >, &, ', " as \uXXXX so the JSON is // safe inside an inline