plugin = $plugin; } /** * Register hooks. * * @return void */ public function register() { $frontend_enabled = ( $this->plugin->is_power_enabled( 'dismissible' ) || $this->plugin->is_power_enabled( 'trigger_target' ) || $this->plugin->is_power_enabled( 'content_toggle' ) || $this->plugin->is_power_enabled( 'motion_3d' ) || $this->plugin->is_power_enabled( 'media_scroller' ) || $this->plugin->is_power_enabled( 'mailchimp_tags' ) ); $power_map_enabled = $this->plugin->is_power_enabled( 'power_map' ); $builder_bridge_enabled = $this->plugin->is_power_enabled( 'builder_bridge' ); $design_library_enabled = $this->plugin->is_power_enabled( 'design_library' ); if ( ! $frontend_enabled && ! $power_map_enabled && ! $builder_bridge_enabled && ! $design_library_enabled ) { return; } add_action( 'divi_visual_builder_assets_before_enqueue_scripts', array( $this, 'register_builder_assets' ) ); if ( ! $frontend_enabled ) { return; } // Preserve the exact single-attribute registration path proven in v0.1.2. add_filter( 'block_type_metadata_settings', array( $this, 'register_server_attributes' ), 20, 2 ); add_filter( 'register_block_type_args', array( $this, 'register_block_type_arguments' ), 20, 2 ); // Keep Dismissible on Divi's normal wrapper pipeline, exactly as v0.1.2. add_filter( 'divi_module_wrapper_render', array( $this, 'filter_frontend_wrapper' ), 20, 2 ); // WordPress block-render fallback handles Section, Image, and Trigger & Target. add_filter( 'render_block', array( $this, 'filter_rendered_block' ), 99, 3 ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ), 30 ); add_action( 'wp_footer', array( $this, 'enqueue_late_frontend_assets' ), 1 ); } /** * Register builder extension script through Divi 5's package manager. * * @return void */ public function register_builder_assets() { if ( ! function_exists( 'et_builder_d5_enabled' ) || ! function_exists( 'et_core_is_fb_enabled' ) || ! et_builder_d5_enabled() || ! et_core_is_fb_enabled() || ! class_exists( '\ET\Builder\VisualBuilder\Assets\PackageBuildManager' ) ) { return; } \ET\Builder\VisualBuilder\Assets\PackageBuildManager::register_package_build( array( 'name' => 'divi-powers-confirmation-dialog', 'version' => DIVI_POWERS_VERSION, 'script' => array( 'src' => DIVI_POWERS_URL . 'assets/js/confirmation-dialog.js', 'deps' => array(), 'enqueue_top_window' => true, 'enqueue_app_window' => true, 'args' => array( 'in_footer' => false, ), ), ) ); \ET\Builder\VisualBuilder\Assets\PackageBuildManager::register_package_build( array( 'name' => 'divi-powers-builder', 'version' => DIVI_POWERS_VERSION, 'script' => array( 'src' => DIVI_POWERS_URL . 'assets/js/divi-builder.js', 'deps' => array( 'divi-powers-confirmation-dialog', 'lodash', 'divi-modal-library', 'divi-module-library', 'divi-vendor-wp-hooks', 'divi-vendor-wp-element', 'wp-i18n', ), 'enqueue_top_window' => false, 'enqueue_app_window' => true, 'args' => array( 'in_footer' => false, ), ), ) ); wp_set_script_translations( 'divi-powers-builder', '5d-powers-for-divi' ); if ( $this->plugin->is_power_enabled( 'builder_bridge' ) ) { \ET\Builder\VisualBuilder\Assets\PackageBuildManager::register_package_build( array( 'name' => 'divi-powers-chatgpt-guide', 'version' => DIVI_POWERS_VERSION, 'script' => array( 'src' => DIVI_POWERS_URL . 'assets/js/chatgpt-guide.js', 'deps' => array( 'wp-i18n' ), 'enqueue_top_window' => true, 'enqueue_app_window' => false, 'args' => array( 'in_footer' => true ), ), ) ); wp_set_script_translations( 'divi-powers-chatgpt-guide', '5d-powers-for-divi' ); \ET\Builder\VisualBuilder\Assets\PackageBuildManager::register_package_build( array( 'name' => 'divi-powers-power-map', 'version' => DIVI_POWERS_VERSION, 'script' => array( 'src' => DIVI_POWERS_URL . 'assets/js/power-map.js', 'deps' => array( 'divi-powers-confirmation-dialog', 'divi-powers-chatgpt-guide', 'wp-i18n' ), 'enqueue_top_window' => true, 'enqueue_app_window' => false, 'args' => array( 'in_footer' => true, ), ), ) ); wp_set_script_translations( 'divi-powers-power-map', '5d-powers-for-divi' ); } } /** * Make the one proven custom attribute available to Divi's PHP renderer. * * @param array $settings Block metadata settings. * @param array $metadata Original block metadata. * @return array */ public function register_server_attributes( $settings, $metadata = array() ) { $settings = is_array( $settings ) ? $settings : array(); $metadata = is_array( $metadata ) ? $metadata : array(); $module_name = ''; if ( isset( $settings['name'] ) ) { $module_name = (string) $settings['name']; } elseif ( isset( $metadata['name'] ) ) { $module_name = (string) $metadata['name']; } if ( ! $this->is_divi_block_name( $module_name ) ) { return $settings; } if ( ! isset( $settings['attributes'] ) || ! is_array( $settings['attributes'] ) ) { $settings['attributes'] = array(); } $settings['attributes']['diviPowersDismissible'] = $this->server_attribute_definition(); return $settings; } /** * Add the one proven custom attribute after block arguments are normalized. * * @param array $args Registered block arguments. * @param string $block_type Block type name. * @return array */ public function register_block_type_arguments( $args, $block_type ) { $args = is_array( $args ) ? $args : array(); $block_type = is_string( $block_type ) ? $block_type : ''; if ( ! $this->is_divi_block_name( $block_type ) ) { return $args; } if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) { $args['attributes'] = array(); } if ( ! isset( $args['attributes']['diviPowersDismissible'] ) ) { $args['attributes']['diviPowersDismissible'] = $this->server_attribute_definition(); } return $args; } /** * Apply Powers through Divi's native wrapper renderer on public requests. * * This is the primary frontend path. The WordPress render_block hook remains * as a fallback for structural/special elements that bypass this wrapper. * The Visual Builder receives only non-destructive 3D preview markup. Other * interactive Powers remain frontend-only so they cannot obstruct editing. * * @param string $module_wrapper Module HTML. * @param array $args Divi render arguments. * @return string */ public function filter_frontend_wrapper( $module_wrapper, $args ) { if ( '' === trim( (string) $module_wrapper ) ) { return $module_wrapper; } $attrs = array(); if ( isset( $args['attrs'] ) && is_array( $args['attrs'] ) ) { $attrs = $args['attrs']; } elseif ( is_array( $args ) ) { $attrs = $args; } if ( $this->is_non_public_render_request() ) { if ( $this->plugin->is_power_enabled( 'motion_3d' ) ) { $module_wrapper = $this->decorate_motion_3d_wrapper( $module_wrapper, $this->get_motion_3d_config( $attrs ) ); } return $module_wrapper; } if ( $this->plugin->is_power_enabled( 'dismissible' ) ) { $module_wrapper = $this->decorate_dismissible_wrapper( $module_wrapper, $this->get_dismissible_config( $attrs ) ); $module_wrapper = $this->decorate_dismiss_reset_wrapper( $module_wrapper, $this->get_dismiss_reset_config( $attrs ) ); } $trigger_target_config = $this->get_trigger_target_config( $attrs ); $trigger_enabled = $this->plugin->is_power_enabled( 'trigger_target' ); $content_toggle_enabled = $this->plugin->is_power_enabled( 'content_toggle' ); if ( $trigger_enabled ) { $presentation_config = $this->get_modal_config( $attrs ); $module_wrapper = $this->decorate_modal_wrapper( $module_wrapper, $presentation_config, $trigger_target_config ); $module_wrapper = $this->decorate_floating_wrapper( $module_wrapper, $this->get_floating_config( $attrs ), $trigger_target_config ); } if ( $trigger_enabled || $content_toggle_enabled ) { $target_engine_config = $trigger_target_config; if ( ! $trigger_enabled ) { $target_engine_config['action'] = 'none'; $target_engine_config['target_id'] = ''; } $module_wrapper = $this->decorate_trigger_target_wrapper( $module_wrapper, $target_engine_config ); } if ( $content_toggle_enabled ) { $module_wrapper = $this->decorate_content_toggle_wrapper( $module_wrapper, $this->get_content_toggle_config( $attrs ) ); } if ( $this->plugin->is_power_enabled( 'motion_3d' ) ) { $module_wrapper = $this->decorate_motion_3d_wrapper( $module_wrapper, $this->get_motion_3d_config( $attrs ) ); } if ( $this->plugin->is_power_enabled( 'media_scroller' ) ) { $module_wrapper = $this->decorate_media_scroller_wrapper( $module_wrapper, $this->get_media_scroller_config( $attrs ) ); } if ( $this->plugin->is_power_enabled( 'mailchimp_tags' ) ) { $module_wrapper = $this->decorate_mailchimp_tags_wrapper( $module_wrapper, $this->get_mailchimp_tags_config( $attrs ) ); } return $module_wrapper; } /** * Frontend-only block renderer for structural/special modules and Trigger & Target. * * @param string $block_content Rendered block HTML. * @param array $block Parsed block data. * @param WP_Block|null $instance Block instance. * @return string */ public function filter_rendered_block( $block_content, $block, $instance = null ) { unset( $instance ); if ( ! is_array( $block ) ) { return $block_content; } $block_name = isset( $block['blockName'] ) ? (string) $block['blockName'] : ''; if ( ! $this->is_divi_block_name( $block_name ) ) { return $block_content; } $attrs = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array(); if ( $this->is_non_public_render_request() ) { if ( $this->plugin->is_power_enabled( 'motion_3d' ) ) { $block_content = $this->decorate_motion_3d_wrapper( $block_content, $this->get_motion_3d_config( $attrs ) ); } return $block_content; } // Divi 5 can retain a stale signup custom-field child after Custom Fields // is disabled. Mailchimp audiences can also expose empty choice groups. // Suppress only fields that explicitly render or store zero options. if ( $this->plugin->is_power_enabled( 'mailchimp_tags' ) && 'divi/signup-custom-field' === $block_name && $this->is_empty_signup_choice_field( $attrs, $block_content ) ) { return ''; } if ( $this->plugin->is_power_enabled( 'dismissible' ) && false === strpos( $block_content, 'data-divi-powers-dismissible="1"' ) ) { $block_content = $this->decorate_dismissible_wrapper( $block_content, $this->get_dismissible_config( $attrs ) ); } if ( $this->plugin->is_power_enabled( 'dismissible' ) && false === strpos( $block_content, 'data-divi-powers-dismiss-reset="1"' ) ) { $block_content = $this->decorate_dismiss_reset_wrapper( $block_content, $this->get_dismiss_reset_config( $attrs ) ); } $trigger_target_config = $this->get_trigger_target_config( $attrs ); $trigger_enabled = $this->plugin->is_power_enabled( 'trigger_target' ); $content_toggle_enabled = $this->plugin->is_power_enabled( 'content_toggle' ); if ( $trigger_enabled ) { $presentation_config = $this->get_modal_config( $attrs ); $block_content = $this->decorate_modal_wrapper( $block_content, $presentation_config, $trigger_target_config ); $block_content = $this->decorate_floating_wrapper( $block_content, $this->get_floating_config( $attrs ), $trigger_target_config ); } if ( $trigger_enabled || $content_toggle_enabled ) { $target_engine_config = $trigger_target_config; if ( ! $trigger_enabled ) { $target_engine_config['action'] = 'none'; $target_engine_config['target_id'] = ''; } $block_content = $this->decorate_trigger_target_wrapper( $block_content, $target_engine_config ); } if ( $content_toggle_enabled ) { $block_content = $this->decorate_content_toggle_wrapper( $block_content, $this->get_content_toggle_config( $attrs ) ); } if ( $this->plugin->is_power_enabled( 'motion_3d' ) && false === strpos( $block_content, 'data-divi-powers-motion-3d=' ) ) { $block_content = $this->decorate_motion_3d_wrapper( $block_content, $this->get_motion_3d_config( $attrs ) ); } if ( $this->plugin->is_power_enabled( 'media_scroller' ) && false === strpos( $block_content, 'data-divi-powers-media-scroller="1"' ) ) { $block_content = $this->decorate_media_scroller_wrapper( $block_content, $this->get_media_scroller_config( $attrs ) ); } if ( $this->plugin->is_power_enabled( 'mailchimp_tags' ) && 'divi/signup' === $block_name && false === strpos( $block_content, 'data-divi-powers-mailchimp-tags=' ) ) { $block_content = $this->decorate_mailchimp_tags_wrapper( $block_content, $this->get_mailchimp_tags_config( $attrs ) ); } return $block_content; } /** * Enqueue the small frontend runtime on public pages. * * Earlier alpha builds tried to detect the serialized Power attribute before * enqueueing. Divi can store page and Theme Builder content outside the current * post_content string, so that optimization could leave valid powered elements * without JavaScript. Reliability takes priority during alpha; conditional * loading can return later using a Divi-aware usage index. * * @return void */ public function enqueue_frontend_assets() { if ( $this->is_non_public_render_request() ) { return; } $this->enqueue_frontend_asset_handles(); } /** * Catch powered elements from Theme Builder templates or custom loops. * * @return void */ public function enqueue_late_frontend_assets() { if ( ! $this->frontend_power_rendered || $this->is_non_public_render_request() ) { return; } $this->enqueue_frontend_asset_handles(); if ( ! wp_style_is( 'divi-powers-frontend', 'done' ) ) { wp_print_styles( array( 'divi-powers-frontend' ) ); } } /** * Register and enqueue frontend handles. * * @return void */ private function enqueue_frontend_asset_handles() { wp_enqueue_style( 'divi-powers-frontend', DIVI_POWERS_URL . 'assets/css/frontend.css', array(), DIVI_POWERS_VERSION ); wp_enqueue_script( 'divi-powers-frontend', DIVI_POWERS_URL . 'assets/js/frontend.js', array( 'wp-i18n' ), DIVI_POWERS_VERSION, array( 'in_footer' => true, 'strategy' => 'defer', ) ); wp_set_script_translations( 'divi-powers-frontend', '5d-powers-for-divi' ); if ( $this->plugin->is_power_enabled( 'motion_3d' ) ) { wp_enqueue_style( 'divi-powers-motion-3d', DIVI_POWERS_URL . 'assets/css/motion-3d.css', array(), DIVI_POWERS_VERSION ); wp_enqueue_script( 'divi-powers-motion-3d', DIVI_POWERS_URL . 'assets/js/motion-3d.js', array(), DIVI_POWERS_VERSION, array( 'in_footer' => true, 'strategy' => 'defer', ) ); } } /** * Apply dismissible markup exactly as the working v0.1.2 baseline. * * @param string $html Wrapper HTML. * @param array $config Power configuration. * @return string */ private function decorate_dismissible_wrapper( $html, $config ) { if ( 'on' !== $config['enabled'] || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-dismissible', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; $button = sprintf( '', esc_attr( $config['position'] ), esc_attr( $config['label'] ) ); $last_closing_tag = strrpos( $html, '%3$s%4$s', esc_attr( $config['persistence'] ), absint( $config['days'] ), $html, $button ); } $html = $this->add_dismissible_wrapper_attributes( $html, $config ); $last_closing_tag = strrpos( $html, ' $config Reset configuration. * @return string */ private function decorate_dismiss_reset_wrapper( $html, $config ) { if ( 'on' !== $config['enabled'] || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-dismiss-reset', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; return $this->add_attributes_to_first_tag( $html, array( 'divi-powers-dismiss-reset' ), array( 'data-divi-powers-dismiss-reset' => '1' ) ); } /** Add behavior-only data attributes for a 3D motion scene or layer. */ private function decorate_motion_3d_wrapper( $html, $config ) { if ( 'on' !== $config['enabled'] || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-motion-3d', 'scene' ) || $this->first_tag_has_attribute( $html, 'data-divi-powers-motion-3d', 'layer' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; $attributes = array( 'data-divi-powers-motion-3d' => $config['role'], 'data-divi-powers-motion-depth' => (string) $config['depth'], ); if ( 'scene' === $config['role'] ) { $attributes = array_merge( $attributes, array( 'data-divi-powers-motion-preset' => $config['preset'], 'data-divi-powers-motion-mode' => $config['mode'], 'data-divi-powers-motion-target' => $config['target'], 'data-divi-powers-motion-max-x' => (string) $config['max_x'], 'data-divi-powers-motion-max-y' => (string) $config['max_y'], 'data-divi-powers-motion-perspective' => (string) $config['perspective'], 'data-divi-powers-motion-smoothing' => (string) $config['smoothing'], 'data-divi-powers-motion-reverse' => $config['reverse'], 'data-divi-powers-motion-scale' => (string) $config['scale'], 'data-divi-powers-motion-glare' => $config['glare'], 'data-divi-powers-motion-glare-max' => (string) $config['glare_max'], 'data-divi-powers-motion-reset' => $config['reset'], ) ); } return $this->add_attributes_to_first_tag( $html, array( 'divi-powers-motion-3d-' . $config['role'] ), $attributes ); } /** Add behavior-only data attributes for a continuously scrolling container. */ private function decorate_media_scroller_wrapper( $html, $config ) { if ( 'on' !== $config['enabled'] || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-media-scroller', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; return $this->add_attributes_to_first_tag( $html, array( 'divi-powers-media-scroller' ), array( 'data-divi-powers-media-scroller' => '1', 'data-divi-powers-scroller-direction' => $config['direction'], 'data-divi-powers-scroller-duration' => (string) $config['duration'], 'data-divi-powers-scroller-gap' => (string) $config['gap'], 'data-divi-powers-scroller-pause-hover' => 'on' === $config['pause_hover'] ? '1' : '0', 'data-divi-powers-scroller-pause-focus' => 'on' === $config['pause_focus'] ? '1' : '0', 'data-divi-powers-scroller-reduced' => $config['reduced_motion'], 'data-divi-powers-scroller-label' => $config['label'], ) ); } /** Add signed Mailchimp tagging metadata to a configured native Email Optin wrapper. */ private function decorate_mailchimp_tags_wrapper( $html, $config ) { if ( empty( $config['enabled'] ) || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-mailchimp-tags' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; return $this->add_attributes_to_first_tag( $html, array( 'divi-powers-mailchimp-tags' ), array( 'data-divi-powers-mailchimp-tags' => wp_json_encode( $config['tags'] ), 'data-divi-powers-mailchimp-account' => $config['account'], 'data-divi-powers-mailchimp-list' => $config['list_id'], 'data-divi-powers-mailchimp-signature' => $config['signature'], ) ); } /** * Add Trigger & Target data attributes on published frontend output only. * * @param string $html Wrapper HTML. * @param array $config Trigger & Target configuration. * @return string */ private function decorate_trigger_target_wrapper( $html, $config ) { $uses_target = '' !== $config['power_id']; $uses_trigger = 'none' !== $config['action'] && '' !== $config['target_id']; if ( ( ! $uses_target && ! $uses_trigger ) || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-trigger-target', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; $classes = array(); $attributes = array( 'data-divi-powers-trigger-target' => '1', ); if ( $uses_target ) { $classes[] = 'divi-powers-target'; $attributes['data-divi-powers-target'] = '1'; $attributes['data-divi-powers-id'] = $config['power_id']; $attributes['data-divi-powers-start-hidden'] = 'on' === $config['start_hidden'] ? '1' : '0'; $attributes['data-divi-powers-transition'] = $config['transition']; $attributes['data-divi-powers-scroll-on-show'] = 'on' === $config['scroll_on_show'] ? '1' : '0'; $attributes['data-divi-powers-scroll-behavior'] = $config['scroll_behavior']; $attributes['data-divi-powers-scroll-offset'] = (string) $config['scroll_offset']; if ( 'on' === $config['auto_enabled'] ) { $classes[] = 'divi-powers-auto-trigger-target'; $attributes['data-divi-powers-auto-trigger'] = '1'; $attributes['data-divi-powers-auto-event'] = $config['auto_event']; $attributes['data-divi-powers-auto-action'] = $config['auto_action']; $attributes['data-divi-powers-auto-delay'] = (string) $config['auto_delay']; $attributes['data-divi-powers-auto-scroll'] = (string) $config['auto_scroll']; $attributes['data-divi-powers-auto-watch-id'] = $config['auto_watch_id']; $attributes['data-divi-powers-auto-inactivity'] = (string) $config['auto_inactivity']; $attributes['data-divi-powers-auto-hash'] = $config['auto_hash']; $attributes['data-divi-powers-auto-custom'] = $config['auto_custom']; $attributes['data-divi-powers-auto-frequency'] = $config['auto_frequency']; $attributes['data-divi-powers-auto-days'] = (string) $config['auto_days']; $attributes['data-divi-powers-auto-max'] = (string) $config['auto_max']; $attributes['data-divi-powers-auto-devices'] = $config['auto_devices']; } if ( 'on' === $config['start_hidden'] ) { $attributes['hidden'] = 'hidden'; $attributes['aria-hidden'] = 'true'; } } if ( $uses_trigger ) { $classes[] = 'divi-powers-trigger'; $attributes['data-divi-powers-trigger'] = '1'; $attributes['data-divi-powers-action'] = $config['action']; $attributes['data-divi-powers-target-id'] = $config['target_id']; $attributes['data-divi-powers-trigger-event'] = $config['trigger_event']; } return $this->add_attributes_to_first_tag( $html, $classes, $attributes ); } /** * Add Content Toggle data attributes to a native Divi element. * * The toggle control reuses normal Trigger & Target Power IDs, but its * switching behavior remains independently feature-gated. * * @param string $html Wrapper HTML. * @param array $config Content Toggle configuration. * @return string */ /** * Return frontend data attributes for optional Content Switcher active styling. * * @param array $config Content Switcher configuration. * @return array */ private function content_active_style_attributes( $config ) { if ( 'on' !== ( $config['active_style'] ?? 'off' ) ) { return array(); } return array( 'data-divi-powers-active-style' => '1', 'data-divi-powers-active-color' => (string) $config['active_text_color'], 'data-divi-powers-active-background' => (string) $config['active_background'], 'data-divi-powers-active-border-color' => (string) $config['active_border_color'], 'data-divi-powers-active-border-width' => (string) $config['active_border_width'], 'data-divi-powers-active-radius' => (string) $config['active_border_radius'], 'data-divi-powers-active-shadow' => (string) $config['active_box_shadow'], 'data-divi-powers-active-font-weight' => (string) $config['active_font_weight'], 'data-divi-powers-active-transform' => (string) $config['active_transform'], 'data-divi-powers-active-transition' => (string) $config['active_transition'], ); } private function decorate_content_toggle_wrapper( $html, $config ) { if ( 'on' !== $config['enabled'] || '' === trim( (string) $html ) ) { return $html; } if ( 'scroll' === $config['mode'] ) { $requires_target = 'activate' === $config['scroll_action']; if ( '' === $config['scroll_group_id'] || ( $requires_target && '' === $config['scroll_target'] ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-scroll-nav', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; return $this->add_attributes_to_first_tag( $html, array( 'divi-powers-scroll-nav-trigger' ), array_merge( array( 'data-divi-powers-scroll-nav' => '1', 'data-divi-powers-scroll-nav-group-id' => $config['scroll_group_id'], 'data-divi-powers-scroll-nav-action' => $config['scroll_action'], 'data-divi-powers-scroll-nav-target' => $config['scroll_target'], 'data-divi-powers-scroll-nav-behavior' => $config['scroll_behavior'], 'data-divi-powers-scroll-nav-offset' => (string) $config['scroll_offset'], 'data-divi-powers-scroll-nav-active-mode' => $config['scroll_active_mode'], 'data-divi-powers-scroll-nav-active-offset' => (string) $config['scroll_active_offset'], 'data-divi-powers-scroll-nav-visibility' => (string) $config['scroll_visibility'], 'data-divi-powers-scroll-nav-hash' => $config['scroll_hash'], 'data-divi-powers-scroll-nav-open-hash' => $config['scroll_open_hash'], 'data-divi-powers-scroll-nav-progress' => $config['scroll_progress'], 'data-divi-powers-scroll-nav-progress-label' => $config['scroll_progress_label'], ), $this->content_active_style_attributes( $config ) ) ); } if ( 'group' === $config['mode'] ) { $requires_target = 'activate' === $config['group_action']; if ( '' === $config['group_id'] || ( $requires_target && '' === $config['group_target'] ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-content-group', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; return $this->add_attributes_to_first_tag( $html, array( 'divi-powers-content-group-trigger' ), array_merge( array( 'data-divi-powers-content-group' => '1', 'data-divi-powers-content-group-id' => $config['group_id'], 'data-divi-powers-content-group-action' => $config['group_action'], 'data-divi-powers-content-group-target' => $config['group_target'], 'data-divi-powers-content-group-default' => $config['group_default'], 'data-divi-powers-content-group-transition' => $config['group_transition'], 'data-divi-powers-content-group-remember' => $config['group_remember'], 'data-divi-powers-content-group-hash' => $config['group_hash'], 'data-divi-powers-content-group-focus' => $config['group_focus'], 'data-divi-powers-content-group-loop' => $config['group_loop'], 'data-divi-powers-content-group-progress' => $config['group_progress'], 'data-divi-powers-content-group-progress-label' => $config['group_progress_label'], ), $this->content_active_style_attributes( $config ) ) ); } if ( '' === $config['target_a'] || '' === $config['target_b'] || $config['target_a'] === $config['target_b'] ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-content-toggle', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; return $this->add_attributes_to_first_tag( $html, array( 'divi-powers-content-toggle' ), array_merge( array( 'data-divi-powers-content-toggle' => '1', 'data-divi-powers-content-a' => $config['target_a'], 'data-divi-powers-content-b' => $config['target_b'], 'data-divi-powers-content-default' => $config['default_content'], 'data-divi-powers-content-transition' => $config['transition'], 'data-divi-powers-content-remember' => $config['remember'], 'data-divi-powers-content-active-state' => $config['active_state'], 'data-divi-powers-content-label-a' => $config['label_a'], 'data-divi-powers-content-label-b' => $config['label_b'], ), $this->content_active_style_attributes( $config ) ) ); } /** * Turn a native Divi target into an anchored tooltip or popover. * * The target remains native Divi content. The frontend runtime temporarily * moves it into a fixed-position portal so it can escape overflow clipping, * track its trigger, and automatically reposition near viewport edges. * * @param string $html Wrapper HTML. * @param array $config Floating presentation configuration. * @param array $target_config Trigger & Target configuration. * @return string */ private function decorate_floating_wrapper( $html, $config, $target_config ) { if ( ! in_array( $config['presentation'], array( 'tooltip', 'popover' ), true ) || '' === $target_config['power_id'] || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-floating', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; if ( false === strrpos( $html, '' . $html . ''; } $is_tooltip = 'tooltip' === $config['presentation']; $attributes = array( 'data-divi-powers-floating' => '1', 'data-divi-powers-floating-kind' => $config['presentation'], 'data-divi-powers-floating-placement' => $config['placement'], 'data-divi-powers-floating-alignment' => $config['alignment'], 'data-divi-powers-floating-offset' => (string) $config['offset'], 'data-divi-powers-floating-arrow' => 'on' === $config['show_arrow'] ? '1' : '0', 'data-divi-powers-floating-open-delay' => (string) $config['open_delay'], 'data-divi-powers-floating-close-delay' => (string) $config['close_delay'], 'data-divi-powers-floating-hover-content' => 'on' === $config['hover_content'] ? '1' : '0', 'data-divi-powers-floating-close-outside' => 'on' === $config['close_outside'] ? '1' : '0', 'data-divi-powers-floating-close-escape' => 'on' === $config['close_escape'] ? '1' : '0', 'data-divi-powers-floating-max-width' => (string) $config['max_width'], 'role' => $is_tooltip ? 'tooltip' : 'dialog', 'aria-hidden' => 'true', 'hidden' => 'hidden', ); if ( ! $is_tooltip ) { $attributes['aria-label'] = $config['label']; $attributes['tabindex'] = '-1'; } $html = $this->add_attributes_to_first_tag( $html, array( 'divi-powers-floating', 'divi-powers-floating--' . $config['presentation'] ), $attributes ); $extras = ''; if ( 'on' === $config['show_arrow'] ) { $extras .= ''; } if ( ! $is_tooltip && 'on' === $config['show_close_button'] ) { $extras .= sprintf( '', esc_attr( $config['close_label'] ) ); } if ( '' === $extras ) { return $html; } $last_closing_tag = strrpos( $html, ' $config Modal configuration. * @param array $target_config Trigger & Target configuration. * @return string */ private function decorate_modal_wrapper( $html, $config, $target_config ) { if ( ! in_array( $config['presentation'], array( 'modal', 'drawer-left', 'drawer-right', 'bottom-sheet' ), true ) || '' === $target_config['power_id'] || '' === trim( (string) $html ) ) { return $html; } if ( $this->first_tag_has_attribute( $html, 'data-divi-powers-modal', '1' ) ) { $this->frontend_power_rendered = true; return $html; } $this->frontend_power_rendered = true; // Void output such as a bare Image needs a real container so the dialog can // own a close button and receive focus without altering the image itself. if ( false === strrpos( $html, '' . $html . ''; } $attributes = array( 'data-divi-powers-modal' => '1', 'data-divi-powers-presentation' => $config['presentation'], 'data-divi-powers-modal-backdrop' => 'on' === $config['show_backdrop'] ? '1' : '0', 'data-divi-powers-modal-close-backdrop' => 'on' === $config['close_backdrop'] ? '1' : '0', 'data-divi-powers-modal-close-escape' => 'on' === $config['close_escape'] ? '1' : '0', 'data-divi-powers-modal-lock-scroll' => 'on' === $config['lock_scroll'] ? '1' : '0', 'data-divi-powers-modal-animation' => $config['animation'], 'role' => 'dialog', 'aria-modal' => 'true', 'aria-label' => $config['label'], 'aria-hidden' => 'true', 'hidden' => 'hidden', 'tabindex' => '-1', ); $html = $this->add_attributes_to_first_tag( $html, array( 'divi-powers-modal' ), $attributes ); if ( 'on' !== $config['show_close_button'] ) { return $html; } $button = sprintf( '', esc_attr( $config['close_position'] ), esc_attr( $config['close_label'] ) ); $last_closing_tag = strrpos( $html, 'next_tag() ) { $actual = $processor->get_attribute( $attribute ); if ( null === $actual ) { return false; } return null === $value || (string) $actual === (string) $value; } } if ( ! preg_match( '/^<[^>]+>/', ltrim( $html ), $matches ) ) { return false; } $first_tag = $matches[0]; $pattern = '/\s' . preg_quote( $attribute, '/' ) . '(?:\s*=\s*(["\'])(.*?)\1)?(?=\s|\/?>)/i'; if ( ! preg_match( $pattern, $first_tag, $attribute_match ) ) { return false; } if ( null === $value ) { return true; } $actual = isset( $attribute_match[2] ) ? html_entity_decode( $attribute_match[2], ENT_QUOTES, 'UTF-8' ) : ''; return (string) $actual === (string) $value; } /** * Add dismissible attributes to the first root tag. * * @param string $html Wrapper HTML. * @param array $config Power configuration. * @return string */ private function add_dismissible_wrapper_attributes( $html, $config ) { if ( class_exists( 'WP_HTML_Tag_Processor' ) ) { $processor = new WP_HTML_Tag_Processor( $html ); if ( $processor->next_tag() ) { $processor->add_class( 'divi-powers-dismissible' ); $processor->set_attribute( 'data-divi-powers-dismissible', '1' ); $processor->set_attribute( 'data-divi-powers-persistence', $config['persistence'] ); $processor->set_attribute( 'data-divi-powers-days', (string) $config['days'] ); return $processor->get_updated_html(); } } $replacement = sprintf( '$1 class="$2 divi-powers-dismissible" data-divi-powers-dismissible="1" data-divi-powers-persistence="%1$s" data-divi-powers-days="%2$d"$3>', esc_attr( $config['persistence'] ), absint( $config['days'] ) ); return (string) preg_replace( '/^(<[^\s>]+)(?:\s+class="([^"]*)")?([^>]*)>/', $replacement, $html, 1 ); } /** * Add classes and attributes to the first root tag. * * @param string $html Wrapper HTML. * @param array $classes CSS classes. * @param array $attributes HTML attributes. * @return string */ private function add_attributes_to_first_tag( $html, $classes, $attributes ) { if ( class_exists( 'WP_HTML_Tag_Processor' ) ) { $processor = new WP_HTML_Tag_Processor( $html ); if ( $processor->next_tag() ) { foreach ( $classes as $class_name ) { $processor->add_class( $class_name ); } foreach ( $attributes as $name => $value ) { $processor->set_attribute( $name, $value ); } return $processor->get_updated_html(); } } return (string) preg_replace_callback( '/^<([a-zA-Z][^\s\/>]*)([^>]*)>/', static function ( $matches ) use ( $classes, $attributes ) { $tag_name = $matches[1]; $tail = $matches[2]; if ( preg_match( '/\sclass=("|\')(.*?)\1/i', $tail, $class_match ) ) { $existing = preg_split( '/\s+/', trim( $class_match[2] ) ); $merged = array_values( array_unique( array_filter( array_merge( $existing ?: array(), $classes ) ) ) ); $tail = preg_replace( '/\sclass=("|\')(.*?)\1/i', ' class="' . esc_attr( implode( ' ', $merged ) ) . '"', $tail, 1 ); } elseif ( ! empty( $classes ) ) { $tail .= ' class="' . esc_attr( implode( ' ', array_unique( $classes ) ) ) . '"'; } foreach ( $attributes as $name => $value ) { $tail .= ' ' . esc_attr( $name ) . '="' . esc_attr( $value ) . '"'; } return '<' . $tag_name . $tail . '>'; }, $html, 1 ); } /** * Read the single Divi Powers desktop value object. * * @param array $attrs Module attributes. * @return array */ private function get_power_value( $attrs ) { if ( ! is_array( $attrs ) ) { return array(); } $root = $attrs['diviPowersDismissible'] ?? array(); if ( is_string( $root ) ) { $decoded = json_decode( $root, true ); $root = is_array( $decoded ) ? $decoded : array(); } $candidates = array( $root['innerContent']['desktop']['value'] ?? null, $root['innerContent']['value'] ?? null, $root['desktop']['value'] ?? null, $root['value'] ?? null, $root, ); foreach ( $candidates as $candidate ) { if ( is_string( $candidate ) ) { $decoded = json_decode( $candidate, true ); $candidate = is_array( $decoded ) ? $decoded : null; } if ( ! is_array( $candidate ) ) { continue; } foreach ( array( 'enabled', 'dismissResetEnabled', 'triggerPowerId', 'triggerStartHidden', 'triggerScrollOnShow', 'triggerClickAction', 'triggerEvent', 'triggerTargetPowerId', 'modalPresentation', 'floatingPlacement', 'autoTriggerEnabled', 'contentToggleEnabled', 'contentSwitcherMode', 'contentGroupId', 'motion3dEnabled', 'mediaScrollerEnabled', 'mailchimpTagsEnabled' ) as $known_key ) { if ( array_key_exists( $known_key, $candidate ) ) { return $candidate; } } } return array(); } /** Detect a Divi signup choice field that has no choices. */ private function is_empty_signup_choice_field( $attrs, $block_content ) { $rendered_text = trim( wp_strip_all_tags( (string) $block_content ) ); if ( false !== stripos( $rendered_text, 'No options added' ) ) { return true; } $field_item = is_array( $attrs['fieldItem'] ?? null ) ? $attrs['fieldItem'] : array(); $advanced = is_array( $field_item['advanced'] ?? null ) ? $field_item['advanced'] : array(); $type = ''; foreach ( array( $advanced['type'] ?? null, $advanced['fieldType'] ?? null, $field_item['type'] ?? null, $attrs['type'] ?? null ) as $candidate ) { $type = sanitize_key( (string) $this->unwrap_responsive_attribute( $candidate ) ); if ( '' !== $type ) { break; } } if ( ! in_array( $type, array( 'checkbox', 'radio', 'select' ), true ) ) { return false; } return $this->has_explicit_empty_options( $field_item ); } /** Unwrap a Divi responsive attribute down to its saved value. */ private function unwrap_responsive_attribute( $value ) { for ( $depth = 0; $depth < 5 && is_array( $value ); $depth++ ) { if ( array_key_exists( 'desktop', $value ) ) { $value = $value['desktop']; continue; } if ( array_key_exists( 'value', $value ) ) { $value = $value['value']; continue; } break; } return $value; } /** Find an explicit empty options collection anywhere in a field item. */ private function has_explicit_empty_options( $node ) { if ( ! is_array( $node ) ) { return false; } foreach ( $node as $key => $value ) { $normalized_key = strtolower( preg_replace( '/[^a-z0-9]/i', '', (string) $key ) ); if ( 'options' === $normalized_key || 'checkboxoptions' === $normalized_key || 'radiooptions' === $normalized_key || 'selectoptions' === $normalized_key ) { $options = $this->unwrap_responsive_attribute( $value ); if ( is_string( $options ) ) { $decoded = json_decode( $options, true ); $options = is_array( $decoded ) ? $decoded : $options; } if ( is_array( $options ) && empty( $options ) ) { return true; } } if ( is_array( $value ) && $this->has_explicit_empty_options( $value ) ) { return true; } } return false; } /** Read Mailchimp tag configuration from a native Divi Email Optin module. */ private function get_mailchimp_tags_config( $attrs ) { $value = $this->get_power_value( $attrs ); $enabled = $this->normalize_toggle( $value['mailchimpTagsEnabled'] ?? 'off', 'off' ); if ( 'on' !== $enabled ) { return array( 'enabled' => false ); } $account_value = $attrs['module']['advanced']['emailService']['desktop']['value']['account'] ?? ''; $account_value = is_string( $account_value ) ? trim( $account_value ) : ''; $separator = strrpos( $account_value, '|' ); if ( false === $separator ) { return array( 'enabled' => false ); } $account = trim( substr( $account_value, 0, $separator ) ); $list_id = trim( substr( $account_value, $separator + 1 ) ); if ( '' === $account || '' === $list_id || ! Divi_Powers_Mailchimp_Tags::is_mailchimp_account( $account, $list_id ) ) { return array( 'enabled' => false ); } $selected = array(); for ( $i = 1; $i <= 5; $i++ ) { $key = 'mailchimpTag' . $i; if ( isset( $value[ $key ] ) && '' !== trim( (string) $value[ $key ] ) ) { $selected[] = sanitize_text_field( (string) $value[ $key ] ); } } $selected = Divi_Powers_Mailchimp_Tags::filter_existing_tag_names( $account, $list_id, $selected ); // beta.35 stored manually entered names/IDs in mailchimpTags. Keep that // field as the optional advanced fallback so existing modules continue to work. $manual = isset( $value['mailchimpTags'] ) ? sanitize_textarea_field( (string) $value['mailchimpTags'] ) : ''; $manual = Divi_Powers_Mailchimp_Tags::normalize_tag_tokens( $manual ); $tags = Divi_Powers_Mailchimp_Tags::normalize_tag_tokens( array_merge( $selected, $manual ) ); if ( empty( $tags ) ) { return array( 'enabled' => false ); } return array( 'enabled' => true, 'account' => $account, 'list_id' => $list_id, 'tags' => $tags, 'signature' => Divi_Powers_Mailchimp_Tags::sign_config( $account, $list_id, $tags ), ); } /** Read 3D Motion configuration. */ private function get_motion_3d_config( $attrs ) { $value = $this->get_power_value( $attrs ); $role = isset( $value['motion3dRole'] ) ? sanitize_key( (string) $value['motion3dRole'] ) : 'scene'; if ( ! in_array( $role, array( 'scene', 'layer' ), true ) ) { $role = 'scene'; } $presets = array( 'gentle-hero' => array( 'mode' => 'layered', 'target' => 'first-child', 'max_x' => 3, 'max_y' => 5, 'perspective' => 1400, 'smoothing' => 220, 'reverse' => 'on', 'scale' => 1, 'glare' => 'off', 'glare_max' => 0.10, 'reset' => 'on' ), 'interactive-card' => array( 'mode' => 'surface', 'target' => 'first-child', 'max_x' => 7, 'max_y' => 9, 'perspective' => 900, 'smoothing' => 140, 'reverse' => 'on', 'scale' => 1.02, 'glare' => 'on', 'glare_max' => 0.18, 'reset' => 'on' ), 'product-spotlight' => array( 'mode' => 'layered', 'target' => 'first-child', 'max_x' => 5, 'max_y' => 8, 'perspective' => 1100, 'smoothing' => 180, 'reverse' => 'on', 'scale' => 1.01, 'glare' => 'on', 'glare_max' => 0.12, 'reset' => 'on' ), 'ambient-viewport' => array( 'mode' => 'viewport', 'target' => 'first-child', 'max_x' => 2, 'max_y' => 4, 'perspective' => 1600, 'smoothing' => 300, 'reverse' => 'on', 'scale' => 1, 'glare' => 'off', 'glare_max' => 0.10, 'reset' => 'on' ), ); $preset = isset( $value['motion3dPreset'] ) ? sanitize_key( (string) $value['motion3dPreset'] ) : 'gentle-hero'; if ( ! isset( $presets[ $preset ] ) ) { $preset = 'gentle-hero'; } $defaults = $presets[ $preset ]; // Scenes saved before presets existed retain their explicitly configured // values. New scenes use the selected preset until Advanced is enabled. $legacy_custom_keys = array( 'motion3dMode', 'motion3dTarget', 'motion3dMaxX', 'motion3dMaxY', 'motion3dPerspective', 'motion3dSmoothing', 'motion3dReverse', 'motion3dScale', 'motion3dGlare', 'motion3dGlareMax', 'motion3dReset' ); $legacy_custom = ! isset( $value['motion3dPreset'] ) && (bool) array_intersect( $legacy_custom_keys, array_keys( $value ) ); $advanced = isset( $value['motion3dAdvanced'] ) ? 'on' === $this->normalize_toggle( $value['motion3dAdvanced'], 'off' ) : $legacy_custom; $has_custom = static function ( $key ) use ( $advanced, $value ) { return $advanced && isset( $value[ $key ] ) && '' !== trim( (string) $value[ $key ] ) && 'preset' !== $value[ $key ]; }; $custom_toggle = static function ( $key, $fallback ) use ( $has_custom, $value ) { if ( ! $has_custom( $key ) || ! in_array( $value[ $key ], array( 'on', 'off' ), true ) ) { return $fallback; } return $value[ $key ]; }; $mode = $has_custom( 'motion3dMode' ) ? sanitize_key( (string) $value['motion3dMode'] ) : $defaults['mode']; if ( ! in_array( $mode, array( 'surface', 'viewport', 'layered' ), true ) ) { $mode = $defaults['mode']; } $target = $has_custom( 'motion3dTarget' ) ? sanitize_key( (string) $value['motion3dTarget'] ) : $defaults['target']; if ( ! in_array( $target, array( 'first-child', 'self' ), true ) ) { $target = $defaults['target']; } return array( 'enabled' => $this->normalize_toggle( $value['motion3dEnabled'] ?? 'off', 'off' ), 'role' => $role, 'preset' => $preset, 'mode' => $mode, 'target' => $target, 'max_x' => max( 0, min( 30, $has_custom( 'motion3dMaxX' ) ? (float) $value['motion3dMaxX'] : $defaults['max_x'] ) ), 'max_y' => max( 0, min( 30, $has_custom( 'motion3dMaxY' ) ? (float) $value['motion3dMaxY'] : $defaults['max_y'] ) ), 'perspective' => max( 300, min( 2400, $has_custom( 'motion3dPerspective' ) ? absint( $value['motion3dPerspective'] ) : $defaults['perspective'] ) ), 'smoothing' => max( 0, min( 1000, $has_custom( 'motion3dSmoothing' ) ? absint( $value['motion3dSmoothing'] ) : $defaults['smoothing'] ) ), 'reverse' => $custom_toggle( 'motion3dReverse', $defaults['reverse'] ), 'scale' => max( 1, min( 1.2, $has_custom( 'motion3dScale' ) ? (float) $value['motion3dScale'] : $defaults['scale'] ) ), 'glare' => $custom_toggle( 'motion3dGlare', $defaults['glare'] ), 'glare_max' => max( 0, min( 0.6, $has_custom( 'motion3dGlareMax' ) ? (float) $value['motion3dGlareMax'] : $defaults['glare_max'] ) ), 'depth' => max( -300, min( 300, isset( $value['motion3dDepth'] ) ? (int) $value['motion3dDepth'] : 60 ) ), 'reset' => $custom_toggle( 'motion3dReset', $defaults['reset'] ), ); } /** Read Logo & Image Scroller configuration. */ private function get_media_scroller_config( $attrs ) { $value = $this->get_power_value( $attrs ); $direction = isset( $value['mediaScrollerDirection'] ) ? sanitize_key( (string) $value['mediaScrollerDirection'] ) : 'left'; if ( ! in_array( $direction, array( 'left', 'right' ), true ) ) { $direction = 'left'; } $reduced = isset( $value['mediaScrollerReducedMotion'] ) ? sanitize_key( (string) $value['mediaScrollerReducedMotion'] ) : 'static'; if ( ! in_array( $reduced, array( 'static', 'manual' ), true ) ) { $reduced = 'static'; } $label = sanitize_text_field( (string) ( $value['mediaScrollerLabel'] ?? '' ) ); if ( '' === $label ) { $label = __( 'Scrolling content', '5d-powers-for-divi' ); } return array( 'enabled' => $this->normalize_toggle( $value['mediaScrollerEnabled'] ?? 'off', 'off' ), 'direction' => $direction, 'duration' => max( 5, min( 300, isset( $value['mediaScrollerDuration'] ) ? absint( $value['mediaScrollerDuration'] ) : 30 ) ), 'gap' => max( 0, min( 240, isset( $value['mediaScrollerGap'] ) ? absint( $value['mediaScrollerGap'] ) : 24 ) ), 'pause_hover' => $this->normalize_toggle( $value['mediaScrollerPauseHover'] ?? 'on', 'on' ), 'pause_focus' => $this->normalize_toggle( $value['mediaScrollerPauseFocus'] ?? 'on', 'on' ), 'reduced_motion' => $reduced, 'label' => substr( $label, 0, 160 ), ); } /** * Read Dismissible configuration. * * @param array $attrs Module attributes. * @return array */ private function get_dismissible_config( $attrs ) { $value = $this->get_power_value( $attrs ); $persistence = isset( $value['persistence'] ) ? sanitize_key( (string) $value['persistence'] ) : 'session'; if ( ! in_array( $persistence, array( 'page', 'session', 'days', 'forever' ), true ) ) { $persistence = 'session'; } $position = isset( $value['position'] ) ? sanitize_key( (string) $value['position'] ) : 'top-right'; if ( ! in_array( $position, array( 'top-right', 'top-left' ), true ) ) { $position = 'top-right'; } $label = isset( $value['label'] ) ? sanitize_text_field( (string) $value['label'] ) : ''; if ( '' === $label ) { $label = __( 'Dismiss this element', '5d-powers-for-divi' ); } $days = isset( $value['days'] ) ? absint( $value['days'] ) : 7; $days = max( 1, min( 3650, $days ) ); return array( 'enabled' => isset( $value['enabled'] ) ? sanitize_key( (string) $value['enabled'] ) : 'off', 'persistence' => $persistence, 'days' => $days, 'position' => $position, 'label' => $label, ); } /** * Read the remembered-dismissal reset action. * * @param array $attrs Module attributes. * @return array */ private function get_dismiss_reset_config( $attrs ) { $value = $this->get_power_value( $attrs ); return array( 'enabled' => $this->normalize_toggle( $value['dismissResetEnabled'] ?? 'off', 'off' ), ); } /** * Read Trigger & Target configuration from the same proven attribute object. * * @param array $attrs Module attributes. * @return array */ private function get_trigger_target_config( $attrs ) { $value = $this->get_power_value( $attrs ); $action = isset( $value['triggerClickAction'] ) ? sanitize_key( (string) $value['triggerClickAction'] ) : 'none'; if ( ! in_array( $action, array( 'none', 'toggle', 'show', 'hide' ), true ) ) { $action = 'none'; } $trigger_event = isset( $value['triggerEvent'] ) ? sanitize_key( (string) $value['triggerEvent'] ) : 'click'; if ( ! in_array( $trigger_event, array( 'click', 'hover-focus', 'hover', 'focus' ), true ) ) { $trigger_event = 'click'; } $transition = isset( $value['triggerTransition'] ) ? sanitize_key( (string) $value['triggerTransition'] ) : 'fade-slide'; if ( ! in_array( $transition, array( 'none', 'fade', 'fade-slide' ), true ) ) { $transition = 'fade-slide'; } $scroll_behavior = isset( $value['triggerScrollBehavior'] ) ? sanitize_key( (string) $value['triggerScrollBehavior'] ) : 'smooth'; if ( ! in_array( $scroll_behavior, array( 'smooth', 'instant' ), true ) ) { $scroll_behavior = 'smooth'; } $scroll_offset = max( 0, min( 2000, isset( $value['triggerScrollOffset'] ) ? absint( $value['triggerScrollOffset'] ) : 0 ) ); $auto_event = isset( $value['autoTriggerEvent'] ) ? sanitize_key( (string) $value['autoTriggerEvent'] ) : 'load'; if ( ! in_array( $auto_event, array( 'load', 'delay', 'scroll', 'viewport', 'pass', 'inactivity', 'exit', 'hash', 'custom' ), true ) ) { $auto_event = 'load'; } $auto_action = isset( $value['autoTriggerAction'] ) ? sanitize_key( (string) $value['autoTriggerAction'] ) : 'show'; if ( ! in_array( $auto_action, array( 'show', 'toggle', 'hide' ), true ) ) { $auto_action = 'show'; } $auto_frequency = isset( $value['autoTriggerFrequency'] ) ? sanitize_key( (string) $value['autoTriggerFrequency'] ) : 'page'; if ( ! in_array( $auto_frequency, array( 'page', 'session', 'days', 'forever' ), true ) ) { $auto_frequency = 'page'; } $auto_devices = isset( $value['autoTriggerDevices'] ) ? sanitize_key( (string) $value['autoTriggerDevices'] ) : 'all'; if ( ! in_array( $auto_devices, array( 'all', 'desktop', 'tablet', 'phone', 'desktop-tablet', 'tablet-phone' ), true ) ) { $auto_devices = 'all'; } $auto_custom = preg_replace( '/[^A-Za-z0-9_.:-]/', '', sanitize_text_field( (string) ( $value['autoTriggerCustomEvent'] ?? '' ) ) ); return array( 'power_id' => $this->normalize_power_id( $value['triggerPowerId'] ?? '' ), 'start_hidden' => isset( $value['triggerStartHidden'] ) ? sanitize_key( (string) $value['triggerStartHidden'] ) : 'off', 'transition' => $transition, 'scroll_on_show' => $this->normalize_toggle( $value['triggerScrollOnShow'] ?? 'off', 'off' ), 'scroll_behavior' => $scroll_behavior, 'scroll_offset' => $scroll_offset, 'action' => $action, 'trigger_event' => $trigger_event, 'target_id' => $this->normalize_power_id( $value['triggerTargetPowerId'] ?? '' ), 'auto_enabled' => $this->normalize_toggle( $value['autoTriggerEnabled'] ?? 'off', 'off' ), 'auto_event' => $auto_event, 'auto_action' => $auto_action, 'auto_delay' => max( 0, min( 3600, isset( $value['autoTriggerDelay'] ) ? absint( $value['autoTriggerDelay'] ) : 3 ) ), 'auto_scroll' => max( 1, min( 100, isset( $value['autoTriggerScrollPercent'] ) ? absint( $value['autoTriggerScrollPercent'] ) : 50 ) ), 'auto_watch_id' => $this->normalize_power_id( $value['autoTriggerWatchPowerId'] ?? '' ), 'auto_inactivity' => max( 1, min( 3600, isset( $value['autoTriggerInactivity'] ) ? absint( $value['autoTriggerInactivity'] ) : 10 ) ), 'auto_hash' => sanitize_title( ltrim( sanitize_text_field( (string) ( $value['autoTriggerHash'] ?? '' ) ), '#' ) ), 'auto_custom' => substr( (string) $auto_custom, 0, 100 ), 'auto_frequency' => $auto_frequency, 'auto_days' => max( 1, min( 3650, isset( $value['autoTriggerDays'] ) ? absint( $value['autoTriggerDays'] ) : 7 ) ), 'auto_max' => max( 0, min( 1000, isset( $value['autoTriggerMaxDisplays'] ) ? absint( $value['autoTriggerMaxDisplays'] ) : 0 ) ), 'auto_devices' => $auto_devices, ); } /** * Read Content Toggle configuration from the shared Divi Powers attribute. * * @param array $attrs Module attributes. * @return array */ private function get_content_toggle_config( $attrs ) { $value = $this->get_power_value( $attrs ); $mode = isset( $value['contentSwitcherMode'] ) ? sanitize_key( (string) $value['contentSwitcherMode'] ) : 'toggle'; if ( ! in_array( $mode, array( 'toggle', 'group', 'scroll' ), true ) ) { $mode = 'toggle'; } $default_content = isset( $value['contentToggleDefault'] ) ? sanitize_key( (string) $value['contentToggleDefault'] ) : 'a'; if ( ! in_array( $default_content, array( 'a', 'b' ), true ) ) { $default_content = 'a'; } $transition = isset( $value['contentToggleTransition'] ) ? sanitize_key( (string) $value['contentToggleTransition'] ) : 'fade'; if ( ! in_array( $transition, array( 'none', 'fade', 'fade-slide' ), true ) ) { $transition = 'fade'; } $remember = isset( $value['contentToggleRemember'] ) ? sanitize_key( (string) $value['contentToggleRemember'] ) : 'page'; if ( ! in_array( $remember, array( 'page', 'session', 'forever' ), true ) ) { $remember = 'page'; } $active_state = isset( $value['contentToggleActiveState'] ) ? sanitize_key( (string) $value['contentToggleActiveState'] ) : 'b'; if ( ! in_array( $active_state, array( 'a', 'b' ), true ) ) { $active_state = 'b'; } $group_action = isset( $value['contentGroupAction'] ) ? sanitize_key( (string) $value['contentGroupAction'] ) : 'activate'; if ( ! in_array( $group_action, array( 'activate', 'previous', 'next' ), true ) ) { $group_action = 'activate'; } $group_transition = isset( $value['contentGroupTransition'] ) ? sanitize_key( (string) $value['contentGroupTransition'] ) : 'fade'; if ( ! in_array( $group_transition, array( 'none', 'fade', 'fade-slide' ), true ) ) { $group_transition = 'fade'; } $group_remember = isset( $value['contentGroupRemember'] ) ? sanitize_key( (string) $value['contentGroupRemember'] ) : 'page'; if ( ! in_array( $group_remember, array( 'page', 'session', 'forever' ), true ) ) { $group_remember = 'page'; } $progress_label = sanitize_text_field( (string) ( $value['contentGroupProgressLabel'] ?? '' ) ); if ( '' === $progress_label ) { $progress_label = __( 'Step {current} of {total}', '5d-powers-for-divi' ); } $scroll_action = isset( $value['scrollNavAction'] ) ? sanitize_key( (string) $value['scrollNavAction'] ) : 'activate'; if ( ! in_array( $scroll_action, array( 'activate', 'previous', 'next' ), true ) ) { $scroll_action = 'activate'; } $scroll_behavior = isset( $value['scrollNavBehavior'] ) ? sanitize_key( (string) $value['scrollNavBehavior'] ) : 'smooth'; if ( ! in_array( $scroll_behavior, array( 'smooth', 'instant' ), true ) ) { $scroll_behavior = 'smooth'; } $scroll_active_mode = isset( $value['scrollNavActiveMode'] ) ? sanitize_key( (string) $value['scrollNavActiveMode'] ) : 'top'; if ( ! in_array( $scroll_active_mode, array( 'top', 'center', 'visible', 'custom' ), true ) ) { $scroll_active_mode = 'top'; } $scroll_progress = isset( $value['scrollNavProgress'] ) ? sanitize_key( (string) $value['scrollNavProgress'] ) : 'none'; if ( ! in_array( $scroll_progress, array( 'none', 'text', 'bar', 'both' ), true ) ) { $scroll_progress = 'none'; } $scroll_progress_label = sanitize_text_field( (string) ( $value['scrollNavProgressLabel'] ?? '' ) ); if ( '' === $scroll_progress_label ) { $scroll_progress_label = __( 'Section {current} of {total}: {label}', '5d-powers-for-divi' ); } $active_text_color = sanitize_text_field( (string) ( $value['contentActiveTextColor'] ?? '' ) ); if ( '' === $active_text_color ) { $active_text_color = '#ffffff'; } $active_background = sanitize_text_field( (string) ( $value['contentActiveBackground'] ?? '' ) ); if ( '' === $active_background ) { $active_background = '#8d5cff'; } $active_border_color = sanitize_text_field( (string) ( $value['contentActiveBorderColor'] ?? '' ) ); if ( '' === $active_border_color ) { $active_border_color = '#8d5cff'; } $active_box_shadow = sanitize_text_field( (string) ( $value['contentActiveBoxShadow'] ?? '' ) ); if ( '' === $active_box_shadow ) { $active_box_shadow = '0 8px 22px rgba(141, 92, 255, 0.32)'; } $active_font_weight = isset( $value['contentActiveFontWeight'] ) ? sanitize_key( (string) $value['contentActiveFontWeight'] ) : '700'; if ( ! in_array( $active_font_weight, array( 'inherit', '400', '500', '600', '700', '800', '900' ), true ) ) { $active_font_weight = '700'; } $active_transform = isset( $value['contentActiveTransform'] ) ? sanitize_key( (string) $value['contentActiveTransform'] ) : 'none'; if ( ! in_array( $active_transform, array( 'none', 'lift', 'scale', 'lift-scale' ), true ) ) { $active_transform = 'none'; } return array( 'enabled' => $this->normalize_toggle( $value['contentToggleEnabled'] ?? 'off', 'off' ), 'mode' => $mode, 'target_a' => $this->normalize_power_id( $value['contentToggleTargetA'] ?? '' ), 'target_b' => $this->normalize_power_id( $value['contentToggleTargetB'] ?? '' ), 'default_content' => $default_content, 'transition' => $transition, 'remember' => $remember, 'active_state' => $active_state, 'label_a' => sanitize_text_field( (string) ( $value['contentToggleLabelA'] ?? '' ) ), 'label_b' => sanitize_text_field( (string) ( $value['contentToggleLabelB'] ?? '' ) ), 'group_id' => $this->normalize_power_id( $value['contentGroupId'] ?? '' ), 'group_action' => $group_action, 'group_target' => $this->normalize_power_id( $value['contentGroupTargetPowerId'] ?? '' ), 'group_default' => $this->normalize_toggle( $value['contentGroupDefault'] ?? 'off', 'off' ), 'group_transition' => $group_transition, 'group_remember' => $group_remember, 'group_hash' => $this->normalize_toggle( $value['contentGroupHash'] ?? 'off', 'off' ), 'group_focus' => $this->normalize_toggle( $value['contentGroupFocusPanel'] ?? 'off', 'off' ), 'group_loop' => $this->normalize_toggle( $value['contentGroupLoop'] ?? 'off', 'off' ), 'group_progress' => $this->normalize_toggle( $value['contentGroupProgress'] ?? 'off', 'off' ), 'group_progress_label' => substr( $progress_label, 0, 160 ), 'scroll_group_id' => $this->normalize_power_id( $value['scrollNavGroupId'] ?? '' ), 'scroll_action' => $scroll_action, 'scroll_target' => $this->normalize_power_id( $value['scrollNavTargetPowerId'] ?? '' ), 'scroll_behavior' => $scroll_behavior, 'scroll_offset' => max( 0, min( 2000, isset( $value['scrollNavOffset'] ) ? absint( $value['scrollNavOffset'] ) : 80 ) ), 'scroll_active_mode' => $scroll_active_mode, 'scroll_active_offset' => max( 0, min( 2000, isset( $value['scrollNavActiveOffset'] ) ? absint( $value['scrollNavActiveOffset'] ) : 120 ) ), 'scroll_visibility' => max( 1, min( 100, isset( $value['scrollNavVisibilityPercent'] ) ? absint( $value['scrollNavVisibilityPercent'] ) : 50 ) ), 'scroll_hash' => $this->normalize_toggle( $value['scrollNavHash'] ?? 'off', 'off' ), 'scroll_open_hash' => $this->normalize_toggle( $value['scrollNavOpenHash'] ?? 'off', 'off' ), 'scroll_progress' => $scroll_progress, 'scroll_progress_label' => substr( $scroll_progress_label, 0, 160 ), 'active_style' => $this->normalize_toggle( $value['contentActiveStyleEnabled'] ?? 'off', 'off' ), 'active_text_color' => substr( $active_text_color, 0, 120 ), 'active_background' => substr( $active_background, 0, 300 ), 'active_border_color' => substr( $active_border_color, 0, 120 ), 'active_border_width' => max( 0, min( 20, isset( $value['contentActiveBorderWidth'] ) ? absint( $value['contentActiveBorderWidth'] ) : 1 ) ), 'active_border_radius' => max( 0, min( 200, isset( $value['contentActiveBorderRadius'] ) ? absint( $value['contentActiveBorderRadius'] ) : 12 ) ), 'active_box_shadow' => substr( $active_box_shadow, 0, 300 ), 'active_font_weight' => $active_font_weight, 'active_transform' => $active_transform, 'active_transition' => max( 0, min( 2000, isset( $value['contentActiveTransition'] ) ? absint( $value['contentActiveTransition'] ) : 180 ) ), ); } /** * Read Modal & Drawer configuration from the stable Divi Powers attribute object. * * @param array $attrs Module attributes. * @return array */ private function get_modal_config( $attrs ) { $value = $this->get_power_value( $attrs ); $presentation = isset( $value['modalPresentation'] ) ? sanitize_key( (string) $value['modalPresentation'] ) : 'none'; if ( ! in_array( $presentation, array( 'none', 'modal', 'drawer-left', 'drawer-right', 'bottom-sheet', 'tooltip', 'popover' ), true ) ) { $presentation = 'none'; } $animation = isset( $value['modalAnimation'] ) ? sanitize_key( (string) $value['modalAnimation'] ) : 'fade-scale'; if ( ! in_array( $animation, array( 'none', 'fade', 'fade-scale' ), true ) ) { $animation = 'fade-scale'; } $close_position = isset( $value['modalClosePosition'] ) ? sanitize_key( (string) $value['modalClosePosition'] ) : 'top-right'; if ( ! in_array( $close_position, array( 'top-right', 'top-left' ), true ) ) { $close_position = 'top-right'; } $label = isset( $value['modalDialogLabel'] ) ? sanitize_text_field( (string) $value['modalDialogLabel'] ) : ''; if ( '' === $label ) { $label = __( 'Dialog', '5d-powers-for-divi' ); } $close_label = isset( $value['modalCloseLabel'] ) ? sanitize_text_field( (string) $value['modalCloseLabel'] ) : ''; if ( '' === $close_label ) { $close_label = __( 'Close dialog', '5d-powers-for-divi' ); } return array( 'presentation' => $presentation, 'show_backdrop' => $this->normalize_toggle( $value['modalShowBackdrop'] ?? 'on', 'on' ), 'close_backdrop' => $this->normalize_toggle( $value['modalCloseBackdrop'] ?? 'on', 'on' ), 'close_escape' => $this->normalize_toggle( $value['modalCloseEscape'] ?? 'on', 'on' ), 'show_close_button' => $this->normalize_toggle( $value['modalShowCloseButton'] ?? 'on', 'on' ), 'close_position' => $close_position, 'label' => $label, 'close_label' => $close_label, 'lock_scroll' => $this->normalize_toggle( $value['modalLockScroll'] ?? 'on', 'on' ), 'animation' => $animation, ); } /** * Normalize Divi toggle values. * * @param mixed $value Raw toggle value. * @param string $default Default value. * @return string */ private function normalize_toggle( $value, $default = 'off' ) { $value = sanitize_key( (string) $value ); return in_array( $value, array( 'on', 'off' ), true ) ? $value : $default; } /** * Read Tooltip & Popover configuration from the shared Divi Powers attribute. * * @param array $attrs Module attributes. * @return array */ private function get_floating_config( $attrs ) { $value = $this->get_power_value( $attrs ); $presentation = isset( $value['modalPresentation'] ) ? sanitize_key( (string) $value['modalPresentation'] ) : 'none'; if ( ! in_array( $presentation, array( 'tooltip', 'popover' ), true ) ) { $presentation = 'none'; } $placement = isset( $value['floatingPlacement'] ) ? sanitize_key( (string) $value['floatingPlacement'] ) : 'auto'; if ( ! in_array( $placement, array( 'auto', 'top', 'bottom', 'left', 'right' ), true ) ) { $placement = 'auto'; } $alignment = isset( $value['floatingAlignment'] ) ? sanitize_key( (string) $value['floatingAlignment'] ) : 'center'; if ( ! in_array( $alignment, array( 'start', 'center', 'end' ), true ) ) { $alignment = 'center'; } $label = isset( $value['floatingLabel'] ) ? sanitize_text_field( (string) $value['floatingLabel'] ) : ''; if ( '' === $label ) { $label = __( 'Popover', '5d-powers-for-divi' ); } $close_label = isset( $value['floatingCloseLabel'] ) ? sanitize_text_field( (string) $value['floatingCloseLabel'] ) : ''; if ( '' === $close_label ) { $close_label = __( 'Close popover', '5d-powers-for-divi' ); } $default_max_width = 'tooltip' === $presentation ? 320 : 420; return array( 'presentation' => $presentation, 'placement' => $placement, 'alignment' => $alignment, 'offset' => max( 0, min( 100, isset( $value['floatingOffset'] ) ? absint( $value['floatingOffset'] ) : 12 ) ), 'show_arrow' => $this->normalize_toggle( $value['floatingShowArrow'] ?? 'on', 'on' ), 'open_delay' => max( 0, min( 5000, isset( $value['floatingOpenDelay'] ) ? absint( $value['floatingOpenDelay'] ) : 120 ) ), 'close_delay' => max( 0, min( 5000, isset( $value['floatingCloseDelay'] ) ? absint( $value['floatingCloseDelay'] ) : 160 ) ), 'hover_content' => $this->normalize_toggle( $value['floatingHoverContent'] ?? ( 'popover' === $presentation ? 'on' : 'off' ), 'popover' === $presentation ? 'on' : 'off' ), 'close_outside' => $this->normalize_toggle( $value['floatingCloseOutside'] ?? 'on', 'on' ), 'close_escape' => $this->normalize_toggle( $value['floatingCloseEscape'] ?? 'on', 'on' ), 'show_close_button' => $this->normalize_toggle( $value['floatingShowCloseButton'] ?? ( 'popover' === $presentation ? 'on' : 'off' ), 'popover' === $presentation ? 'on' : 'off' ), 'label' => $label, 'close_label' => $close_label, 'max_width' => max( 160, min( 1200, isset( $value['floatingMaxWidth'] ) ? absint( $value['floatingMaxWidth'] ) : $default_max_width ) ), ); } /** * Normalize a Power ID. * * @param mixed $value Raw value. * @return string */ private function normalize_power_id( $value ) { $value = sanitize_text_field( (string) $value ); $value = ltrim( $value, '#.' ); return sanitize_title( $value ); } /** * Single server attribute definition used by both Powers. * * @return array */ private function server_attribute_definition() { return array( 'type' => 'object', 'settings' => array( 'innerContent' => array( 'groupType' => 'group-items', 'items' => array( 'enabled' => $this->server_field( 'enabled', __( 'Dismissible Element', '5d-powers-for-divi' ), 'divi/toggle', 10 ), 'persistence' => $this->server_field( 'persistence', __( 'Remember Dismissal', '5d-powers-for-divi' ), 'divi/select', 20 ), 'days' => $this->server_field( 'days', __( 'Remember For Days', '5d-powers-for-divi' ), 'divi/text', 30 ), 'position' => $this->server_field( 'position', __( 'Close Button Position', '5d-powers-for-divi' ), 'divi/select', 40 ), 'label' => $this->server_field( 'label', __( 'Accessible Button Label', '5d-powers-for-divi' ), 'divi/text', 50 ), 'dismissResetEnabled' => $this->server_field( 'dismissResetEnabled', __( 'Reset Dismissed Elements', '5d-powers-for-divi' ), 'divi/toggle', 60 ), 'triggerPowerId' => $this->server_field( 'triggerPowerId', __( 'Power ID', '5d-powers-for-divi' ), 'divi/text', 100 ), 'triggerStartHidden' => $this->server_field( 'triggerStartHidden', __( 'Start Hidden', '5d-powers-for-divi' ), 'divi/toggle', 110 ), 'triggerTransition' => $this->server_field( 'triggerTransition', __( 'Target Transition', '5d-powers-for-divi' ), 'divi/select', 120 ), 'triggerScrollOnShow' => $this->server_field( 'triggerScrollOnShow', __( 'Scroll to Target After Show', '5d-powers-for-divi' ), 'divi/toggle', 125 ), 'triggerScrollBehavior' => $this->server_field( 'triggerScrollBehavior', __( 'Scroll Behavior', '5d-powers-for-divi' ), 'divi/select', 126 ), 'triggerScrollOffset' => $this->server_field( 'triggerScrollOffset', __( 'Scroll Offset (px)', '5d-powers-for-divi' ), 'divi/text', 127 ), 'triggerClickAction' => $this->server_field( 'triggerClickAction', __( 'Trigger Action', '5d-powers-for-divi' ), 'divi/select', 130 ), 'triggerEvent' => $this->server_field( 'triggerEvent', __( 'Trigger Event', '5d-powers-for-divi' ), 'divi/select', 135 ), 'triggerTargetPowerId' => $this->server_field( 'triggerTargetPowerId', __( 'Target Power ID', '5d-powers-for-divi' ), 'divi/text', 140 ), 'modalPresentation' => $this->server_field( 'modalPresentation', __( 'Presentation Mode', '5d-powers-for-divi' ), 'divi/select', 200 ), 'modalShowBackdrop' => $this->server_field( 'modalShowBackdrop', __( 'Show Backdrop', '5d-powers-for-divi' ), 'divi/toggle', 210 ), 'modalCloseBackdrop' => $this->server_field( 'modalCloseBackdrop', __( 'Close on Backdrop Click', '5d-powers-for-divi' ), 'divi/toggle', 220 ), 'modalCloseEscape' => $this->server_field( 'modalCloseEscape', __( 'Close on Escape', '5d-powers-for-divi' ), 'divi/toggle', 230 ), 'modalShowCloseButton' => $this->server_field( 'modalShowCloseButton', __( 'Show Close Button', '5d-powers-for-divi' ), 'divi/toggle', 240 ), 'modalClosePosition' => $this->server_field( 'modalClosePosition', __( 'Presentation Close Button Position', '5d-powers-for-divi' ), 'divi/select', 250 ), 'modalDialogLabel' => $this->server_field( 'modalDialogLabel', __( 'Dialog Label', '5d-powers-for-divi' ), 'divi/text', 260 ), 'modalCloseLabel' => $this->server_field( 'modalCloseLabel', __( 'Close Button Label', '5d-powers-for-divi' ), 'divi/text', 270 ), 'modalLockScroll' => $this->server_field( 'modalLockScroll', __( 'Lock Page Scroll', '5d-powers-for-divi' ), 'divi/toggle', 280 ), 'modalAnimation' => $this->server_field( 'modalAnimation', __( 'Presentation Animation', '5d-powers-for-divi' ), 'divi/select', 290 ), 'floatingPlacement' => $this->server_field( 'floatingPlacement', __( 'Tooltip / Popover Placement', '5d-powers-for-divi' ), 'divi/select', 291 ), 'floatingAlignment' => $this->server_field( 'floatingAlignment', __( 'Tooltip / Popover Alignment', '5d-powers-for-divi' ), 'divi/select', 292 ), 'floatingOffset' => $this->server_field( 'floatingOffset', __( 'Anchor Spacing (px)', '5d-powers-for-divi' ), 'divi/text', 293 ), 'floatingShowArrow' => $this->server_field( 'floatingShowArrow', __( 'Show Pointer Arrow', '5d-powers-for-divi' ), 'divi/toggle', 294 ), 'floatingOpenDelay' => $this->server_field( 'floatingOpenDelay', __( 'Open Delay (ms)', '5d-powers-for-divi' ), 'divi/text', 295 ), 'floatingCloseDelay' => $this->server_field( 'floatingCloseDelay', __( 'Close Delay (ms)', '5d-powers-for-divi' ), 'divi/text', 296 ), 'floatingHoverContent' => $this->server_field( 'floatingHoverContent', __( 'Keep Open While Hovering Content', '5d-powers-for-divi' ), 'divi/toggle', 297 ), 'floatingCloseOutside' => $this->server_field( 'floatingCloseOutside', __( 'Close on Outside Click', '5d-powers-for-divi' ), 'divi/toggle', 298 ), 'floatingCloseEscape' => $this->server_field( 'floatingCloseEscape', __( 'Close on Escape', '5d-powers-for-divi' ), 'divi/toggle', 299 ), 'floatingShowCloseButton' => $this->server_field( 'floatingShowCloseButton', __( 'Show Popover Close Button', '5d-powers-for-divi' ), 'divi/toggle', 2991 ), 'floatingLabel' => $this->server_field( 'floatingLabel', __( 'Popover Label', '5d-powers-for-divi' ), 'divi/text', 2992 ), 'floatingCloseLabel' => $this->server_field( 'floatingCloseLabel', __( 'Popover Close Button Label', '5d-powers-for-divi' ), 'divi/text', 2993 ), 'floatingMaxWidth' => $this->server_field( 'floatingMaxWidth', __( 'Maximum Width (px)', '5d-powers-for-divi' ), 'divi/text', 2994 ), 'autoTriggerEnabled' => $this->server_field( 'autoTriggerEnabled', __( 'Automatic Trigger', '5d-powers-for-divi' ), 'divi/toggle', 300 ), 'autoTriggerEvent' => $this->server_field( 'autoTriggerEvent', __( 'Automatic Trigger Event', '5d-powers-for-divi' ), 'divi/select', 310 ), 'autoTriggerAction' => $this->server_field( 'autoTriggerAction', __( 'Automatic Action', '5d-powers-for-divi' ), 'divi/select', 320 ), 'autoTriggerDelay' => $this->server_field( 'autoTriggerDelay', __( 'Delay (seconds)', '5d-powers-for-divi' ), 'divi/text', 330 ), 'autoTriggerScrollPercent' => $this->server_field( 'autoTriggerScrollPercent', __( 'Scroll Percentage', '5d-powers-for-divi' ), 'divi/text', 340 ), 'autoTriggerWatchPowerId' => $this->server_field( 'autoTriggerWatchPowerId', __( 'Watch Power ID', '5d-powers-for-divi' ), 'divi/text', 350 ), 'autoTriggerInactivity' => $this->server_field( 'autoTriggerInactivity', __( 'Inactivity Time (seconds)', '5d-powers-for-divi' ), 'divi/text', 360 ), 'autoTriggerHash' => $this->server_field( 'autoTriggerHash', __( 'URL Hash', '5d-powers-for-divi' ), 'divi/text', 370 ), 'autoTriggerCustomEvent' => $this->server_field( 'autoTriggerCustomEvent', __( 'Custom Event Name', '5d-powers-for-divi' ), 'divi/text', 380 ), 'autoTriggerFrequency' => $this->server_field( 'autoTriggerFrequency', __( 'Trigger Frequency', '5d-powers-for-divi' ), 'divi/select', 390 ), 'autoTriggerDays' => $this->server_field( 'autoTriggerDays', __( 'Frequency in Days', '5d-powers-for-divi' ), 'divi/text', 391 ), 'autoTriggerMaxDisplays' => $this->server_field( 'autoTriggerMaxDisplays', __( 'Maximum Activations', '5d-powers-for-divi' ), 'divi/text', 392 ), 'autoTriggerDevices' => $this->server_field( 'autoTriggerDevices', __( 'Automatic Trigger Devices', '5d-powers-for-divi' ), 'divi/select', 393 ), 'contentToggleEnabled' => $this->server_field( 'contentToggleEnabled', __( 'Content Switcher', '5d-powers-for-divi' ), 'divi/toggle', 400 ), 'contentToggleTargetA' => $this->server_field( 'contentToggleTargetA', __( 'Content A Power ID', '5d-powers-for-divi' ), 'divi/text', 410 ), 'contentToggleTargetB' => $this->server_field( 'contentToggleTargetB', __( 'Content B Power ID', '5d-powers-for-divi' ), 'divi/text', 420 ), 'contentToggleDefault' => $this->server_field( 'contentToggleDefault', __( 'Default Active Content', '5d-powers-for-divi' ), 'divi/select', 430 ), 'contentToggleTransition' => $this->server_field( 'contentToggleTransition', __( 'Toggle Transition', '5d-powers-for-divi' ), 'divi/select', 440 ), 'contentToggleRemember' => $this->server_field( 'contentToggleRemember', __( 'Remember Selection', '5d-powers-for-divi' ), 'divi/select', 450 ), 'contentToggleActiveState' => $this->server_field( 'contentToggleActiveState', __( 'Active Trigger State', '5d-powers-for-divi' ), 'divi/select', 460 ), 'contentToggleLabelA' => $this->server_field( 'contentToggleLabelA', __( 'Label While Showing Content A', '5d-powers-for-divi' ), 'divi/text', 470 ), 'contentToggleLabelB' => $this->server_field( 'contentToggleLabelB', __( 'Label While Showing Content B', '5d-powers-for-divi' ), 'divi/text', 480 ), 'contentSwitcherMode' => $this->server_field( 'contentSwitcherMode', __( 'Content Switcher Mode', '5d-powers-for-divi' ), 'divi/select', 405 ), 'contentGroupId' => $this->server_field( 'contentGroupId', __( 'Content Group ID', '5d-powers-for-divi' ), 'divi/text', 490 ), 'contentGroupAction' => $this->server_field( 'contentGroupAction', __( 'Content Group Action', '5d-powers-for-divi' ), 'divi/select', 500 ), 'contentGroupTargetPowerId' => $this->server_field( 'contentGroupTargetPowerId', __( 'Panel Power ID', '5d-powers-for-divi' ), 'divi/text', 510 ), 'contentGroupDefault' => $this->server_field( 'contentGroupDefault', __( 'Set as Default Panel', '5d-powers-for-divi' ), 'divi/toggle', 520 ), 'contentGroupTransition' => $this->server_field( 'contentGroupTransition', __( 'Group Transition', '5d-powers-for-divi' ), 'divi/select', 530 ), 'contentGroupRemember' => $this->server_field( 'contentGroupRemember', __( 'Remember Active Panel', '5d-powers-for-divi' ), 'divi/select', 540 ), 'contentGroupHash' => $this->server_field( 'contentGroupHash', __( 'Sync URL Hash', '5d-powers-for-divi' ), 'divi/toggle', 550 ), 'contentGroupFocusPanel' => $this->server_field( 'contentGroupFocusPanel', __( 'Focus Active Panel', '5d-powers-for-divi' ), 'divi/toggle', 560 ), 'contentGroupLoop' => $this->server_field( 'contentGroupLoop', __( 'Loop Previous / Next Navigation', '5d-powers-for-divi' ), 'divi/toggle', 570 ), 'contentGroupProgress' => $this->server_field( 'contentGroupProgress', __( 'Show Progress Indicator', '5d-powers-for-divi' ), 'divi/toggle', 580 ), 'contentGroupProgressLabel' => $this->server_field( 'contentGroupProgressLabel', __( 'Progress Label Template', '5d-powers-for-divi' ), 'divi/text', 590 ), 'scrollNavGroupId' => $this->server_field( 'scrollNavGroupId', __( 'Scroll Navigation Group ID', '5d-powers-for-divi' ), 'divi/text', 600 ), 'scrollNavAction' => $this->server_field( 'scrollNavAction', __( 'Scroll Navigation Action', '5d-powers-for-divi' ), 'divi/select', 610 ), 'scrollNavTargetPowerId' => $this->server_field( 'scrollNavTargetPowerId', __( 'Section Power ID', '5d-powers-for-divi' ), 'divi/text', 620 ), 'scrollNavBehavior' => $this->server_field( 'scrollNavBehavior', __( 'Scroll Behavior', '5d-powers-for-divi' ), 'divi/select', 630 ), 'scrollNavOffset' => $this->server_field( 'scrollNavOffset', __( 'Scroll Offset (px)', '5d-powers-for-divi' ), 'divi/text', 640 ), 'scrollNavActiveMode' => $this->server_field( 'scrollNavActiveMode', __( 'Active Section Detection', '5d-powers-for-divi' ), 'divi/select', 650 ), 'scrollNavActiveOffset' => $this->server_field( 'scrollNavActiveOffset', __( 'Activation Offset (px)', '5d-powers-for-divi' ), 'divi/text', 660 ), 'scrollNavVisibilityPercent' => $this->server_field( 'scrollNavVisibilityPercent', __( 'Visible Percentage', '5d-powers-for-divi' ), 'divi/text', 670 ), 'scrollNavHash' => $this->server_field( 'scrollNavHash', __( 'Update URL Hash', '5d-powers-for-divi' ), 'divi/toggle', 680 ), 'scrollNavOpenHash' => $this->server_field( 'scrollNavOpenHash', __( 'Open Section from URL Hash', '5d-powers-for-divi' ), 'divi/toggle', 690 ), 'scrollNavProgress' => $this->server_field( 'scrollNavProgress', __( 'Reading Progress', '5d-powers-for-divi' ), 'divi/select', 700 ), 'scrollNavProgressLabel' => $this->server_field( 'scrollNavProgressLabel', __( 'Reading Progress Label', '5d-powers-for-divi' ), 'divi/text', 710 ), 'contentActiveStyleEnabled' => $this->server_field( 'contentActiveStyleEnabled', __( 'Active State Styling', '5d-powers-for-divi' ), 'divi/toggle', 720 ), 'contentActiveTextColor' => $this->server_field( 'contentActiveTextColor', __( 'Active Text Color', '5d-powers-for-divi' ), 'divi/text', 730 ), 'contentActiveBackground' => $this->server_field( 'contentActiveBackground', __( 'Active Background', '5d-powers-for-divi' ), 'divi/text', 740 ), 'contentActiveBorderColor' => $this->server_field( 'contentActiveBorderColor', __( 'Active Border Color', '5d-powers-for-divi' ), 'divi/text', 750 ), 'contentActiveBorderWidth' => $this->server_field( 'contentActiveBorderWidth', __( 'Active Border Width (px)', '5d-powers-for-divi' ), 'divi/text', 760 ), 'contentActiveBorderRadius' => $this->server_field( 'contentActiveBorderRadius', __( 'Active Border Radius (px)', '5d-powers-for-divi' ), 'divi/text', 770 ), 'contentActiveBoxShadow' => $this->server_field( 'contentActiveBoxShadow', __( 'Active Box Shadow', '5d-powers-for-divi' ), 'divi/text', 780 ), 'contentActiveFontWeight' => $this->server_field( 'contentActiveFontWeight', __( 'Active Font Weight', '5d-powers-for-divi' ), 'divi/select', 790 ), 'contentActiveTransform' => $this->server_field( 'contentActiveTransform', __( 'Active Motion', '5d-powers-for-divi' ), 'divi/select', 800 ), 'contentActiveTransition' => $this->server_field( 'contentActiveTransition', __( 'Active Transition Duration (ms)', '5d-powers-for-divi' ), 'divi/text', 810 ), 'motion3dEnabled' => $this->server_field( 'motion3dEnabled', __( '3D Motion', '5d-powers-for-divi' ), 'divi/toggle', 820 ), 'motion3dRole' => $this->server_field( 'motion3dRole', __( '3D Motion Role', '5d-powers-for-divi' ), 'divi/select', 830 ), 'motion3dPreset' => $this->server_field( 'motion3dPreset', __( '3D Style', '5d-powers-for-divi' ), 'divi/select', 840 ), 'motion3dAdvanced' => $this->server_field( 'motion3dAdvanced', __( 'Advanced Customization', '5d-powers-for-divi' ), 'divi/toggle', 845 ), 'motion3dMode' => $this->server_field( 'motion3dMode', __( 'Motion Mode', '5d-powers-for-divi' ), 'divi/select', 850 ), 'motion3dTarget' => $this->server_field( 'motion3dTarget', __( 'Moving Target', '5d-powers-for-divi' ), 'divi/select', 851 ), 'motion3dMaxX' => $this->server_field( 'motion3dMaxX', __( 'Maximum X Tilt (deg)', '5d-powers-for-divi' ), 'divi/text', 860 ), 'motion3dMaxY' => $this->server_field( 'motion3dMaxY', __( 'Maximum Y Tilt (deg)', '5d-powers-for-divi' ), 'divi/text', 861 ), 'motion3dPerspective' => $this->server_field( 'motion3dPerspective', __( 'Perspective (px)', '5d-powers-for-divi' ), 'divi/text', 862 ), 'motion3dSmoothing' => $this->server_field( 'motion3dSmoothing', __( 'Smoothing (ms)', '5d-powers-for-divi' ), 'divi/text', 863 ), 'motion3dReverse' => $this->server_field( 'motion3dReverse', __( 'Reverse Direction', '5d-powers-for-divi' ), 'divi/select', 864 ), 'motion3dScale' => $this->server_field( 'motion3dScale', __( 'Active Scale', '5d-powers-for-divi' ), 'divi/text', 865 ), 'motion3dGlare' => $this->server_field( 'motion3dGlare', __( 'Glare Highlight', '5d-powers-for-divi' ), 'divi/select', 866 ), 'motion3dGlareMax' => $this->server_field( 'motion3dGlareMax', __( 'Maximum Glare Opacity', '5d-powers-for-divi' ), 'divi/text', 867 ), 'motion3dDepth' => $this->server_field( 'motion3dDepth', __( 'Layer Depth (px)', '5d-powers-for-divi' ), 'divi/text', 868 ), 'motion3dReset' => $this->server_field( 'motion3dReset', __( 'Reset When Pointer Leaves', '5d-powers-for-divi' ), 'divi/select', 869 ), 'mediaScrollerEnabled' => $this->server_field( 'mediaScrollerEnabled', __( 'Logo & Image Scroller', '5d-powers-for-divi' ), 'divi/toggle', 900 ), 'mediaScrollerDirection' => $this->server_field( 'mediaScrollerDirection', __( 'Scroll Direction', '5d-powers-for-divi' ), 'divi/select', 910 ), 'mediaScrollerDuration' => $this->server_field( 'mediaScrollerDuration', __( 'Loop Duration (seconds)', '5d-powers-for-divi' ), 'divi/text', 920 ), 'mediaScrollerGap' => $this->server_field( 'mediaScrollerGap', __( 'Item Gap (px)', '5d-powers-for-divi' ), 'divi/text', 930 ), 'mediaScrollerPauseHover' => $this->server_field( 'mediaScrollerPauseHover', __( 'Pause on Hover', '5d-powers-for-divi' ), 'divi/toggle', 940 ), 'mediaScrollerPauseFocus' => $this->server_field( 'mediaScrollerPauseFocus', __( 'Pause for Keyboard Focus', '5d-powers-for-divi' ), 'divi/toggle', 950 ), 'mediaScrollerReducedMotion' => $this->server_field( 'mediaScrollerReducedMotion', __( 'Reduced Motion Experience', '5d-powers-for-divi' ), 'divi/select', 960 ), 'mediaScrollerLabel' => $this->server_field( 'mediaScrollerLabel', __( 'Accessible Region Label', '5d-powers-for-divi' ), 'divi/text', 970 ), 'mailchimpTagsEnabled' => $this->server_field( 'mailchimpTagsEnabled', __( 'Mailchimp Tags', '5d-powers-for-divi' ), 'divi/toggle', 980 ), 'mailchimpTags' => $this->server_field( 'mailchimpTags', __( 'Tag Names or IDs', '5d-powers-for-divi' ), 'divi/text', 990 ), ), ), ), ); } /** * Create a server-side field definition. * * @param string $sub_name Sub-name. * @param string $label Label. * @param string $component Component name. * @param int $priority Priority. * @return array */ private function server_field( $sub_name, $label, $component, $priority ) { $descriptions = array( 'enabled' => __( 'Turn this on to add a close button to the selected Divi element. The element can also remember the visitor’s dismissal using the options below.', '5d-powers-for-divi' ), 'persistence' => __( 'Choose how long 5D Powers for Divi remembers the dismissal. Page View resets on refresh, Browser Session resets when the session ends, Number of Days uses the duration below, and Browser Data keeps it hidden until site storage is cleared.', '5d-powers-for-divi' ), 'days' => __( 'Enter how many days the dismissed element should stay hidden. This field is used only when Remember Dismissal is set to Number of Days. Allowed range: 1–3650.', '5d-powers-for-divi' ), 'position' => __( 'Choose whether the dismiss button is placed in the top-left or top-right corner of this element.', '5d-powers-for-divi' ), 'label' => __( 'Enter a clear label for assistive technology, such as “Dismiss announcement.” This text is not shown visually.', '5d-powers-for-divi' ), 'dismissResetEnabled' => __( 'Turn this element into a reset control. Activating it clears every 5D Powers for Divi dismissal remembered in this browser and immediately restores dismissed elements on the current page.', '5d-powers-for-divi' ), 'triggerPowerId' => __( 'Give this element a unique Power ID so another element can control it. Use lowercase words and hyphens, such as pricing-details. Enter the same ID in a trigger’s Target Power ID field; do not include # or a period.', '5d-powers-for-divi' ), 'triggerScrollOnShow' => __( 'Scroll the page to this target whenever a trigger reveals it. This applies only when Presentation Mode is None; modals, drawers, and bottom sheets manage their own viewport position.', '5d-powers-for-divi' ), 'triggerScrollBehavior' => __( 'Choose Smooth to animate the page to the revealed target, or Instant to jump there immediately. Visitors who prefer reduced motion always receive an instant scroll.', '5d-powers-for-divi' ), 'triggerScrollOffset' => __( 'Reserve space above the target after scrolling, which is useful for sticky headers. Enter a whole number from 0 to 2000; for example, use 80 for an 80px fixed header.', '5d-powers-for-divi' ), 'triggerStartHidden' => __( 'Hide this target on the published frontend until a trigger opens it. The element remains visible while editing in the Visual Builder. Turn this on for modals, drawers, bottom sheets, and initially collapsed content.', '5d-powers-for-divi' ), 'triggerTransition' => __( 'Choose the reveal effect for a normal hidden target. Presentation modes such as modals and drawers use the separate Presentation Animation setting instead.', '5d-powers-for-divi' ), 'triggerClickAction' => __( 'Choose how this element controls its target: Toggle alternates open and closed, Show only opens it, and Hide only closes it. Hover and focus events automatically show while active and close when the interaction ends.', '5d-powers-for-divi' ), 'triggerEvent' => __( 'Choose Click, Hover + Keyboard Focus, Hover Only, or Keyboard Focus Only. Hover-based triggers automatically use click-to-toggle on touch devices.', '5d-powers-for-divi' ), 'triggerTargetPowerId' => __( 'Enter the exact Power ID assigned to the target element. For example, a trigger using contact-panel controls a target whose Power ID is also contact-panel.', '5d-powers-for-divi' ), 'modalPresentation' => __( 'Choose how this target should appear: inline, modal, drawer, bottom sheet, anchored tooltip, or interactive popover. Tooltip and Popover targets track the trigger and automatically reposition near viewport edges.', '5d-powers-for-divi' ), 'modalShowBackdrop' => __( 'Show a full-screen overlay behind the presentation. This helps visually separate a modal, drawer, or bottom sheet from the page beneath it.', '5d-powers-for-divi' ), 'modalCloseBackdrop' => __( 'Allow visitors to close the presentation by clicking its backdrop. This option is available only when Show Backdrop is enabled.', '5d-powers-for-divi' ), 'modalCloseEscape' => __( 'Allow visitors to close the presentation by pressing the Escape key. Keeping this enabled is recommended for accessible dialogs.', '5d-powers-for-divi' ), 'modalShowCloseButton' => __( 'Add a visible close button inside the modal, drawer, or bottom sheet. This provides a clear way to exit even when backdrop closing is disabled.', '5d-powers-for-divi' ), 'modalClosePosition' => __( 'Choose whether the presentation’s close button appears in its top-left or top-right corner.', '5d-powers-for-divi' ), 'modalDialogLabel' => __( 'Enter a short accessible name that describes the presentation, such as “Contact form,” “Site menu,” or “Shopping cart.” This becomes the dialog’s aria-label.', '5d-powers-for-divi' ), 'modalCloseLabel' => __( 'Enter the assistive label for the close button, such as “Close contact form” or “Close menu.” This text is not shown visually.', '5d-powers-for-divi' ), 'modalLockScroll' => __( 'Prevent the background page from scrolling while this presentation is open. This is recommended for modals, drawers, and bottom sheets.', '5d-powers-for-divi' ), 'modalAnimation' => __( 'Choose the opening and closing motion. Contextual Motion scales a modal and slides drawers or bottom sheets from their selected edge. Fade uses opacity only; None removes motion.', '5d-powers-for-divi' ), 'floatingPlacement' => __( 'Choose Auto for edge-aware placement, or prefer Top, Bottom, Left, or Right. 5D Powers for Divi flips the presentation when the preferred side does not fit.', '5d-powers-for-divi' ), 'floatingAlignment' => __( 'Align the floating target to the start, center, or end of its trigger.', '5d-powers-for-divi' ), 'floatingOffset' => __( 'Set the distance between the trigger and floating target from 0–100 pixels.', '5d-powers-for-divi' ), 'floatingShowArrow' => __( 'Display a small pointer that visually connects the tooltip or popover to its trigger.', '5d-powers-for-divi' ), 'floatingOpenDelay' => __( 'Wait 0–5000 milliseconds before opening. A short delay helps prevent accidental hover tooltips.', '5d-powers-for-divi' ), 'floatingCloseDelay' => __( 'Wait 0–5000 milliseconds before closing after hover or focus leaves.', '5d-powers-for-divi' ), 'floatingHoverContent' => __( 'Keep the floating target open while the pointer or keyboard focus is inside its content. Recommended for interactive popovers.', '5d-powers-for-divi' ), 'floatingCloseOutside' => __( 'Close an open tooltip or popover when the visitor clicks elsewhere on the page.', '5d-powers-for-divi' ), 'floatingCloseEscape' => __( 'Allow the Escape key to close the floating presentation.', '5d-powers-for-divi' ), 'floatingShowCloseButton' => __( 'Add a visible close button to popovers. Tooltips remain lightweight and never receive a close button.', '5d-powers-for-divi' ), 'floatingLabel' => __( 'Enter an accessible name for an interactive popover, such as “Pricing details.”', '5d-powers-for-divi' ), 'floatingCloseLabel' => __( 'Enter the assistive label for the popover close button.', '5d-powers-for-divi' ), 'floatingMaxWidth' => __( 'Limit the floating target width from 160–1200 pixels. The viewport always provides the final maximum.', '5d-powers-for-divi' ), 'autoTriggerEnabled' => __( 'Automatically activate this Power ID target from a page or visitor event.', '5d-powers-for-divi' ), 'autoTriggerEvent' => __( 'Choose page load, delay, scroll percentage, watched Power ID, inactivity, exit intent, URL hash, or a custom event.', '5d-powers-for-divi' ), 'autoTriggerAction' => __( 'Choose whether the automatic event shows, toggles, or hides this target.', '5d-powers-for-divi' ), 'autoTriggerDelay' => __( 'Wait 0–3600 seconds before activation.', '5d-powers-for-divi' ), 'autoTriggerScrollPercent' => __( 'Activate at 1–100 percent of the available page scroll.', '5d-powers-for-divi' ), 'autoTriggerWatchPowerId' => __( 'Enter the Power ID to observe for viewport entry or passing.', '5d-powers-for-divi' ), 'autoTriggerInactivity' => __( 'Activate after 1–3600 seconds without visitor activity.', '5d-powers-for-divi' ), 'autoTriggerHash' => __( 'Enter the URL hash without #, or leave blank to use the target Power ID.', '5d-powers-for-divi' ), 'autoTriggerCustomEvent' => __( 'Listen for a custom event dispatched on document.', '5d-powers-for-divi' ), 'autoTriggerFrequency' => __( 'Choose page-view, session, days, or browser-data frequency.', '5d-powers-for-divi' ), 'autoTriggerDays' => __( 'Set a 1–3650 day interval.', '5d-powers-for-divi' ), 'autoTriggerMaxDisplays' => __( 'Set 0 for unlimited or a 1–1000 lifetime browser cap.', '5d-powers-for-divi' ), 'autoTriggerDevices' => __( 'Restrict automatic activation by viewport size.', '5d-powers-for-divi' ), 'contentToggleEnabled' => __( 'Turn this element into a control that switches between two normal Divi targets. Assign each target a Power ID, then enter those IDs below.', '5d-powers-for-divi' ), 'contentToggleTargetA' => __( 'Enter the Power ID for the first content state. This target is shown when Content A is active and must use Presentation Mode None.', '5d-powers-for-divi' ), 'contentToggleTargetB' => __( 'Enter the Power ID for the second content state. This target is shown when Content B is active and must use Presentation Mode None.', '5d-powers-for-divi' ), 'contentToggleDefault' => __( 'Choose which content state should be visible when no remembered visitor selection exists.', '5d-powers-for-divi' ), 'contentToggleTransition' => __( 'Choose the reveal effect used when switching to the newly active target. The inactive target is hidden immediately.', '5d-powers-for-divi' ), 'contentToggleRemember' => __( 'Choose whether the selected content resets on every page load, lasts for the browser session, or remains until browser data is cleared.', '5d-powers-for-divi' ), 'contentToggleActiveState' => __( 'Choose which content side applies aria-pressed=true and the is-active CSS class to this toggle control. Content B is a natural choice for an on/off-style switch.', '5d-powers-for-divi' ), 'contentToggleLabelA' => __( 'Optional. Replace the toggle control’s visible text while Content A is active. For example: “Show annual pricing.” Leave blank to keep the original label.', '5d-powers-for-divi' ), 'contentToggleLabelB' => __( 'Optional. Replace the toggle control’s visible text while Content B is active. For example: “Show monthly pricing.” Leave blank to keep the original label.', '5d-powers-for-divi' ), 'contentSwitcherMode' => __( 'Choose A/B Toggle for two-state switching, Content Group for coordinated panels, or Scroll Navigation for active one-page navigation and reading progress.', '5d-powers-for-divi' ), 'contentGroupId' => __( 'Enter a shared group ID for every control that belongs to this content experience.', '5d-powers-for-divi' ), 'contentGroupAction' => __( 'Activate a specific panel, move to the previous panel, or move to the next panel in the group.', '5d-powers-for-divi' ), 'contentGroupTargetPowerId' => __( 'Enter the normal Power ID of the panel this control activates.', '5d-powers-for-divi' ), 'contentGroupDefault' => __( 'Use this panel when no valid remembered selection or URL hash exists. If several controls are marked default, the first one wins.', '5d-powers-for-divi' ), 'contentGroupTransition' => __( 'Choose the reveal effect for newly activated group panels.', '5d-powers-for-divi' ), 'contentGroupRemember' => __( 'Choose whether the active panel resets on refresh, lasts for the browser session, or remains until browser data is cleared.', '5d-powers-for-divi' ), 'contentGroupHash' => __( 'Open panels from matching URL hashes and update the hash when visitors select a panel.', '5d-powers-for-divi' ), 'contentGroupFocusPanel' => __( 'Move keyboard focus into the newly active panel after click, Previous, or Next activation.', '5d-powers-for-divi' ), 'contentGroupLoop' => __( 'Allow Previous and Next controls plus arrow-key navigation to wrap from the last panel to the first and back again.', '5d-powers-for-divi' ), 'contentGroupProgress' => __( 'Insert a small live progress indicator before the first panel, useful for steppers and guided processes.', '5d-powers-for-divi' ), 'contentGroupProgressLabel' => __( 'Customize the progress text with {current}, {total}, and {label}. Example: Step {current} of {total}: {label}.', '5d-powers-for-divi' ), 'scrollNavGroupId' => __( 'Enter a shared group ID for every navigation control that tracks the same ordered page sections.', '5d-powers-for-divi' ), 'scrollNavAction' => __( 'Scroll directly to a section, or move to the previous or next section in this navigation group.', '5d-powers-for-divi' ), 'scrollNavTargetPowerId' => __( 'Enter the normal Power ID assigned to the visible Section, Row, Column, or Module this control navigates to.', '5d-powers-for-divi' ), 'scrollNavBehavior' => __( 'Choose smooth animated scrolling or an instant jump. Reduced-motion visitors always receive an instant jump.', '5d-powers-for-divi' ), 'scrollNavOffset' => __( 'Reserve 0–2000 pixels above the destination, useful for fixed or sticky headers.', '5d-powers-for-divi' ), 'scrollNavActiveMode' => __( 'Choose how 5D Powers for Divi decides which section is active: crossing the top line, containing the viewport center, greatest visible percentage, or a custom line from the top.', '5d-powers-for-divi' ), 'scrollNavActiveOffset' => __( 'For Custom Activation Line, enter its distance from the viewport top from 0–2000 pixels.', '5d-powers-for-divi' ), 'scrollNavVisibilityPercent' => __( 'For Percentage Visible detection, enter the preferred visible threshold from 1–100 percent.', '5d-powers-for-divi' ), 'scrollNavHash' => __( 'Replace the URL hash as the active section changes, without adding browser-history entries.', '5d-powers-for-divi' ), 'scrollNavOpenHash' => __( 'On page load or hash changes, scroll to a matching section Power ID using the configured offset.', '5d-powers-for-divi' ), 'scrollNavProgress' => __( 'Optionally insert progress text, a reading-progress bar, or both before the first navigation control.', '5d-powers-for-divi' ), 'scrollNavProgressLabel' => __( 'Customize progress text with {current}, {total}, and {label}. Example: Section {current} of {total}: {label}.', '5d-powers-for-divi' ), 'contentActiveStyleEnabled' => __( 'Override the normal Divi preset only while this Content Switcher control is active. Inactive styling still comes entirely from Divi.', '5d-powers-for-divi' ), 'contentActiveTextColor' => __( 'Enter any CSS color, such as #ffffff, rgb(255 255 255), or var(--my-color).', '5d-powers-for-divi' ), 'contentActiveBackground' => __( 'Enter a CSS color or gradient for the active background.', '5d-powers-for-divi' ), 'contentActiveBorderColor' => __( 'Enter the CSS border color applied while active.', '5d-powers-for-divi' ), 'contentActiveBorderWidth' => __( 'Set the active border width from 0–20 pixels.', '5d-powers-for-divi' ), 'contentActiveBorderRadius' => __( 'Set the active corner radius from 0–200 pixels.', '5d-powers-for-divi' ), 'contentActiveBoxShadow' => __( 'Enter a valid CSS box-shadow value, or use none.', '5d-powers-for-divi' ), 'contentActiveFontWeight' => __( 'Choose the font weight applied while active.', '5d-powers-for-divi' ), 'contentActiveTransform' => __( 'Optionally lift, scale, or lift and scale the active control.', '5d-powers-for-divi' ), 'contentActiveTransition' => __( 'Set the active-state transition duration from 0–2000 milliseconds.', '5d-powers-for-divi' ), 'motion3dEnabled' => __( 'Add pointer-responsive 3D motion to this native Divi element. Motion is automatically disabled for reduced-motion visitors and touch/coarse-pointer devices.', '5d-powers-for-divi' ), 'motion3dRole' => __( 'Choose Scene for the moving container, or Layer for a child element that sits at a custom depth inside a Layered Depth scene.', '5d-powers-for-divi' ), 'motion3dPreset' => __( 'Choose a complete, balanced 3D style. Gentle Hero is the recommended default for large interactive sections.', '5d-powers-for-divi' ), 'motion3dAdvanced' => __( 'Reveal the technical motion controls when you want to override the selected style.', '5d-powers-for-divi' ), 'motion3dMode' => __( 'Surface Tilt follows the pointer over the element, Viewport Follow responds across the browser window, and Layered Depth also separates configured child layers.', '5d-powers-for-divi' ), 'motion3dTarget' => __( 'Keep this Divi element stationary as the interaction surface and move only its first direct child. This preserves button, link, form, and text interactions. Use This Element only for legacy or decorative effects.', '5d-powers-for-divi' ), 'motion3dMaxX' => __( 'Maximum vertical tilt from 0–30 degrees.', '5d-powers-for-divi' ), 'motion3dMaxY' => __( 'Maximum horizontal tilt from 0–30 degrees.', '5d-powers-for-divi' ), 'motion3dPerspective' => __( 'Set the virtual viewing distance from 300–2400 pixels. Lower values feel more dramatic.', '5d-powers-for-divi' ), 'motion3dSmoothing' => __( 'Set motion smoothing from 0–1000 milliseconds. Around 220 feels polished for a layered hero.', '5d-powers-for-divi' ), 'motion3dReverse' => __( 'Invert the direction of the tilt response.', '5d-powers-for-divi' ), 'motion3dScale' => __( 'Set the active scale from 1–1.2. Small values such as 1.01–1.03 avoid clipped edges.', '5d-powers-for-divi' ), 'motion3dGlare' => __( 'Add a pointer-tracking light reflection over the scene.', '5d-powers-for-divi' ), 'motion3dGlareMax' => __( 'Set the strongest glare opacity from 0–0.6. A subtle 0.08–0.16 is recommended.', '5d-powers-for-divi' ), 'motion3dDepth' => __( 'For a Layer role, set its Z depth from -300 to 300 pixels.', '5d-powers-for-divi' ), 'motion3dReset' => __( 'Return the element to its resting position when the pointer leaves.', '5d-powers-for-divi' ), 'mediaScrollerEnabled' => __( 'Continuously scroll the direct child Rows, Columns, or Modules inside this Divi element. Apply it to the container, then design every item normally with Divi.', '5d-powers-for-divi' ), 'mediaScrollerDirection' => __( 'Move the content continuously to the left or right.', '5d-powers-for-divi' ), 'mediaScrollerDuration' => __( 'Set the time for one complete loop from 5–300 seconds. A larger number moves more slowly.', '5d-powers-for-divi' ), 'mediaScrollerGap' => __( 'Set the repeated space between items from 0–240 pixels.', '5d-powers-for-divi' ), 'mediaScrollerPauseHover' => __( 'Pause the motion while a pointer is over the scroller so links and detailed content are easier to use.', '5d-powers-for-divi' ), 'mediaScrollerPauseFocus' => __( 'Pause whenever keyboard focus enters the scroller. Keeping this enabled is strongly recommended.', '5d-powers-for-divi' ), 'mediaScrollerReducedMotion' => __( 'For visitors requesting reduced motion, show a static row or allow manual horizontal scrolling instead of automatic animation.', '5d-powers-for-divi' ), 'mediaScrollerLabel' => __( 'Give the scrolling region a short accessible name, such as “Customer logos” or “Featured testimonials.”', '5d-powers-for-divi' ), 'mailchimpTagsEnabled' => __( 'For a native Divi Email Optin connected to Mailchimp, apply campaign or source tags after Divi submits the contact.', '5d-powers-for-divi' ), 'mailchimpTags' => __( 'Enter one or more Mailchimp tag names or numeric tag IDs, separated by commas. Existing numeric IDs from Mailchimp embed forms are resolved to their saved tag names.', '5d-powers-for-divi' ), ); return array( 'attrName' => 'diviPowersDismissible.innerContent', 'subName' => $sub_name, 'label' => $label, 'description' => isset( $descriptions[ $sub_name ] ) ? $descriptions[ $sub_name ] : '', 'groupSlug' => 'diviPowers', 'priority' => $priority, 'render' => true, 'features' => array( 'hover' => false, 'sticky' => false, 'responsive' => false, 'preset' => 'content', ), 'component' => array( 'type' => 'field', 'name' => $component, ), ); } /** * Determine whether a block name belongs to Divi. * * @param string $block_name Block name. * @return bool */ private function is_divi_block_name( $block_name ) { return is_string( $block_name ) && 0 === strpos( $block_name, 'divi/' ); } /** * Block every known non-public/builder rendering path. * Trigger & Target markup is never required inside those requests. * * @return bool */ private function is_non_public_render_request() { if ( is_admin() ) { return true; } if ( function_exists( 'et_core_is_fb_enabled' ) && et_core_is_fb_enabled() ) { return true; } if ( defined( 'ET_FB_ENABLED' ) && ET_FB_ENABLED ) { return true; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return true; } if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) { return true; } $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; foreach ( array( 'et_fb=1', 'et_fb=true', 'et_bfb=1', 'et_pb_preview=1' ) as $marker ) { if ( false !== strpos( $request_uri, $marker ) ) { return true; } } return false; } /** * Lightweight serialized-content detection for conditional assets. * Both Powers share the same proven serialized root attribute. * * @return bool */ private function request_uses_power() { $uses_power = false; $post = get_post(); if ( $post instanceof WP_Post ) { $uses_power = false !== strpos( (string) $post->post_content, 'diviPowersDismissible' ); } return (bool) apply_filters( 'divi_powers_request_uses_power', $uses_power ); } }