=' ); } // Shared CSS sanitization helper (used by animation/custom CSS even without AI). require_once plugin_dir_path( __FILE__ ) . 'includes/class-security.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/class-elementor-atomic-animation.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/class-custom-javascript-block.php'; new \AdvancedAddons\Custom_Javascript_Block(); // Welcome / settings menu — load before AI modules so the parent admin page exists first. require_once plugin_dir_path( __FILE__ ) . 'includes/class-admin-promo.php'; new WPAddons_Admin_Promo(); register_activation_hook( __FILE__, array( 'WPAddons_Admin_Promo', 'set_activation_redirect' ) ); // Optional AI Builder — WordPress 7.0+ only (AI Client / Connectors APIs). if ( advanced_addons_ai_supported() ) { require_once plugin_dir_path( __FILE__ ) . 'wp-ai-builder.php'; define( 'ADVANCED_ADDONS_AI_ENABLED', true ); } else { define( 'ADVANCED_ADDONS_AI_ENABLED', false ); } function wpaddons_enqueue_block_assets() { if ( is_admin() ) { $asset_file = include plugin_dir_path( __FILE__ ) . 'build/blockScript.asset.php'; wp_enqueue_script( 'block-scripts', plugins_url( 'build/blockScript.js', __FILE__ ), $asset_file['dependencies'], $asset_file['version'], true ); } } add_action( 'enqueue_block_assets', 'wpaddons_enqueue_block_assets' ); /** * Enqueue the Custom JavaScript block independently of the optional AI Builder. * * The block is discoverable for every editor, but executable code can only be * authorized by users with the administrator-level manage_options capability. */ function wpaddons_enqueue_custom_javascript_block_editor_assets() { $asset_path = plugin_dir_path( __FILE__ ) . 'build/custom-javascript-block.asset.php'; if ( ! file_exists( $asset_path ) ) { return; } $asset = include $asset_path; wp_enqueue_script( 'advanced-addons-custom-javascript', plugins_url( 'build/custom-javascript-block.js', __FILE__ ), $asset['dependencies'], $asset['version'], true ); wp_localize_script( 'advanced-addons-custom-javascript', 'AdvancedAddonsCustomJavaScript', array( 'canEditCustomJavaScript' => (bool) current_user_can( 'manage_options' ), 'version' => defined( 'ADVANCED_ADDONS_VERSION' ) ? ADVANCED_ADDONS_VERSION : '0.0.0', ) ); } add_action( 'enqueue_block_editor_assets', 'wpaddons_enqueue_custom_javascript_block_editor_assets', 20 ); function wpaddons_fe_script() { global $post; if ( ! $post instanceof WP_Post ) { return; } $post_id = $post->ID; $animation = get_post_meta( $post_id, '_advanced_animation_props', true ); $custom_css = get_post_meta( $post_id, '_advanced_custom_css', true ); $fe_script = require plugin_dir_path( __FILE__ ) . 'build/frontend.asset.php'; wp_enqueue_script( 'frontend-script', plugin_dir_url( __FILE__ ) . 'build/frontend.js', $fe_script['dependencies'], $fe_script['version'], true ); wp_localize_script( 'frontend-script', 'animationGlobal', array( 'props' => $animation ) ); wp_register_style( 'wp-addon-custom-style', false, array(), ADVANCED_ADDONS_VERSION ); wp_enqueue_style( 'wp-addon-custom-style' ); if ( is_string( $custom_css ) && '' !== $custom_css ) { $safe_css = class_exists( '\AdvancedAddons\Security' ) ? \AdvancedAddons\Security::sanitize_css( $custom_css ) : wp_strip_all_tags( $custom_css ); wp_add_inline_style( 'wp-addon-custom-style', $safe_css ); } } add_action( 'wp_enqueue_scripts', 'wpaddons_fe_script' ); function wpaddons_post_meta() { $types = array( 'post', 'page', 'wp_block' ); foreach ( $types as $type ) { register_post_meta( $type, '_advanced_animation_props', array( 'single' => true, 'type' => 'string', 'show_in_rest' => true, 'auth_callback' => function ( $allowed, $meta_key, $post_id ) { return current_user_can( 'edit_post', $post_id ); }, ) ); register_post_meta( $type, '_advanced_custom_css', array( 'single' => true, 'type' => 'string', 'show_in_rest' => true, 'auth_callback' => function ( $allowed, $meta_key, $post_id ) { return current_user_can( 'edit_post', $post_id ); }, ) ); } } add_action( 'init', 'wpaddons_post_meta' ); function wpaddons_render_block($block_content, $block){ if(!isset($block['attrs'])){ return $block_content; } $attrs = $block['attrs']; $class = ""; // Add animation class if(isset($attrs["animationSettings"]) && isset($attrs["animationSettings"]["classAnimation"]) && count($attrs["animationSettings"]["classAnimation"]) && !empty($attrs["animationSettings"]["id"])){ $animationId = $attrs["animationSettings"]["id"]; $class = $class ." animation-". $animationId; } // Add custom CSS class if(isset($attrs["customCSSSettings"]) && !empty($attrs["customCSSSettings"]["customCSSClass"])){ $customCSSClass = $attrs["customCSSSettings"]["customCSSClass"]; $class = $class ." ". $customCSSClass; } if(!empty($class)){ $class = trim($class); $pos = strpos($block_content, $class); $class = 'class="'. $class . " "; if($pos === false){ $pos = strpos($block_content, "class"); if ($pos !== false) { $block_content = substr_replace($block_content, $class, $pos, 7); } } } return $block_content; } add_filter( 'render_block', 'wpaddons_render_block', 10, 2); /** * Reusable function to register animation controls for Elementor elements */ function wpaddons_register_animation_controls($element) { $element->start_controls_section( 'advanced_animation_section', [ 'label' => __('Advanced Animation', 'advanced-animation'), 'tab' => \Elementor\Controls_Manager::TAB_ADVANCED, ] ); // Enable Animation $element->add_control( 'advanced_animation_enable', [ 'label' => __('Enable Animation', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'default' => '', ] ); // Animation Settings (above keyframes) $element->add_control( 'advanced_animation_trigger_type', [ 'label' => __('Trigger Type', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'onVisible' => __('On Visible', 'advanced-animation'), 'onScroll' => __('On Scroll', 'advanced-animation'), ], 'default' => 'onVisible', 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); $element->add_control( 'advanced_animation_trigger_once', [ 'label' => __('Run Once', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'default' => 'yes', 'condition' => [ 'advanced_animation_enable' => 'yes', 'advanced_animation_trigger_type' => 'onVisible', ], ] ); $element->add_control( 'advanced_animation_trigger_start', [ 'label' => __('Scroll Trigger Start', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'start end' => __('Top of target at screen bottom', 'advanced-animation'), 'start start' => __('Top of target at screen top', 'advanced-animation'), 'start center' => __('Top of target at screen center', 'advanced-animation'), 'center center' => __('Center of target at screen center', 'advanced-animation'), 'end end' => __('Bottom of target at screen bottom', 'advanced-animation'), 'end start' => __('Bottom of target at screen top', 'advanced-animation'), 'end center' => __('Bottom of target at screen center', 'advanced-animation'), ], 'default' => 'start start', 'condition' => [ 'advanced_animation_enable' => 'yes', 'advanced_animation_trigger_type' => 'onScroll', ], ] ); $element->add_control( 'advanced_animation_trigger_end', [ 'label' => __('Scroll Trigger End', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'end start' => __('Bottom of target at screen top', 'advanced-animation'), 'end end' => __('Bottom of target at screen bottom', 'advanced-animation'), 'end center' => __('Bottom of target at screen center', 'advanced-animation'), 'center center' => __('Center of target at screen center', 'advanced-animation'), 'start start' => __('Top of target at screen top', 'advanced-animation'), 'start end' => __('Top of target at screen bottom', 'advanced-animation'), 'start center' => __('Top of target at screen center', 'advanced-animation'), ], 'default' => 'end end', 'condition' => [ 'advanced_animation_enable' => 'yes', 'advanced_animation_trigger_type' => 'onScroll', ], ] ); $element->add_control( 'advanced_animation_custom_trigger', [ 'label' => __('Custom Scroll Trigger Element', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => '', 'placeholder' => __('#element-id or .class-name', 'advanced-animation'), 'description' => __('Enter a CSS selector (ID or class) for the element to use as scroll trigger reference. Leave empty to use the animated element itself.', 'advanced-animation'), 'condition' => [ 'advanced_animation_enable' => 'yes', 'advanced_animation_trigger_type' => 'onScroll', ], ] ); $element->add_control( 'advanced_animation_duration', [ 'label' => __('Duration (seconds)', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::NUMBER, 'default' => 0.5, 'step' => 0.1, 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); $element->add_control( 'advanced_animation_delay', [ 'label' => __('Delay (seconds)', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::NUMBER, 'default' => 0, 'step' => 0.1, 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); $element->add_control( 'advanced_animation_iteration_count', [ 'label' => __('Playing Iterations', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 1 => __('One time', 'advanced-animation'), 'infinite' => __('Infinite', 'advanced-animation'), ], 'default' => 1, 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); $element->add_control( 'advanced_animation_direction', [ 'label' => __('Direction', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'normal' => __('Normal', 'advanced-animation'), 'reverse' => __('Reverse', 'advanced-animation'), 'alternate' => __('Alternate', 'advanced-animation'), 'alternate-reverse' => __('Alternate Reverse', 'advanced-animation'), ], 'default' => 'normal', 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); $element->add_control( 'advanced_animation_easing', [ 'label' => __('Easing', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'linear' => __('Linear', 'advanced-animation'), 'easeInOut' => __('Ease In Out', 'advanced-animation'), 'easeIn' => __('Ease In', 'advanced-animation'), 'easeOut' => __('Ease Out', 'advanced-animation'), 'backInOut' => __('Back In Out', 'advanced-animation'), 'backIn' => __('Back In', 'advanced-animation'), 'backOut' => __('Back Out', 'advanced-animation'), 'circInOut' => __('Circ In Out', 'advanced-animation'), 'circIn' => __('Circ In', 'advanced-animation'), 'circOut' => __('Circ Out', 'advanced-animation'), // Add more as needed ], 'default' => 'easeOut', 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); // --- Dynamic Keyframe Controls --- $repeater = new \Elementor\Repeater(); $repeater->add_control( 'property', [ 'label' => __('Property', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'x', 'options' => [ 'x' => __('Translate X', 'advanced-animation'), 'y' => __('Translate Y', 'advanced-animation'), 'opacity' => __('Opacity', 'advanced-animation'), 'scale' => __('Scale', 'advanced-animation'), 'rotate' => __('Rotate', 'advanced-animation'), 'rotateX' => __('Rotate X', 'advanced-animation'), 'rotateY' => __('Rotate Y', 'advanced-animation'), 'transformPerspective' => __('Perspective', 'advanced-animation'), 'skewX' => __('Skew X', 'advanced-animation'), 'skewY' => __('Skew Y', 'advanced-animation'), 'scaleX' => __('Scale X', 'advanced-animation'), 'scaleY' => __('Scale Y', 'advanced-animation'), ], ] ); $repeater->add_control( 'start_value', [ 'label' => __('Start Value', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::NUMBER, 'default' => 0, 'step' => 0.01, ] ); $repeater->add_control( 'end_value', [ 'label' => __('End Value', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::NUMBER, 'default' => 1, 'step' => 0.01, ] ); $element->add_control( 'advanced_animation_keyframes', [ 'label' => __('Keyframe Properties', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'default' => [ [ 'property' => 'opacity', 'start_value' => 0, 'end_value' => 1, ], ], 'title_field' => '{{{ property }}} : {{{ start_value }}} → {{{ end_value }}}', 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); // Custom Keyframes JSON $element->add_control( 'advanced_animation_custom_keyframes', [ 'label' => __('Custom Keyframes (JSON)', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::TEXTAREA, 'default' => '', 'description' => __('Add custom keyframe attributes as JSON. Example: { "backgroundColor": { "start": "#fff", "end": "#000" } }', 'advanced-animation'), 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); // Play Animation Button $element->add_control( 'advanced_animation_play_button', [ 'label' => __('Preview Animation', 'advanced-animation'), 'type' => \Elementor\Controls_Manager::BUTTON, 'text' => __('Play Animation', 'advanced-animation'), 'event' => 'advanced_animation:play', 'condition' => [ 'advanced_animation_enable' => 'yes', ], ] ); $element->end_controls_section(); } /** * Build animation attributes from Elementor settings */ function wpaddons_build_animation_attributes($settings) { $settings = array_merge( array( 'advanced_animation_trigger_type' => 'onVisible', 'advanced_animation_trigger_once' => 'yes', 'advanced_animation_trigger_start' => 'start start', 'advanced_animation_trigger_end' => 'end end', 'advanced_animation_duration' => 0.5, 'advanced_animation_delay' => 0, 'advanced_animation_iteration_count' => 1, 'advanced_animation_direction' => 'normal', 'advanced_animation_easing' => 'easeOut', 'advanced_animation_keyframes' => array( array( 'property' => 'opacity', 'start_value' => 0, 'end_value' => 1 ) ), ), is_array( $settings ) ? $settings : array() ); $keyframes = []; // Build keyframes from repeater data if (is_array($settings['advanced_animation_keyframes'])) { foreach ($settings['advanced_animation_keyframes'] as $item) { if (is_array($item) && !empty($item['property']) && is_string($item['property'])) { $keyframes[$item['property']] = [ 'start' => $item['start_value'] ?? 0, 'end' => $item['end_value'] ?? 1, ]; } } } // Merge custom keyframes JSON if present if (!empty($settings['advanced_animation_custom_keyframes']) && is_string($settings['advanced_animation_custom_keyframes'])) { $json = json_decode($settings['advanced_animation_custom_keyframes'], true); if (is_array($json) && is_object(json_decode($settings['advanced_animation_custom_keyframes']))) { $keyframes = array_merge($keyframes, $json); } } return [ 'triggerType' => $settings['advanced_animation_trigger_type'], 'triggerOnce' => $settings['advanced_animation_trigger_once'], 'triggerStart' => $settings['advanced_animation_trigger_start'], 'triggerEnd' => $settings['advanced_animation_trigger_end'], 'customTrigger' => !empty($settings['advanced_animation_custom_trigger']) ? $settings['advanced_animation_custom_trigger'] : '', 'duration' => $settings['advanced_animation_duration'], 'delay' => $settings['advanced_animation_delay'], 'iterationCount' => $settings['advanced_animation_iteration_count'], 'direction' => $settings['advanced_animation_direction'], 'easing' => $settings['advanced_animation_easing'], 'keyframes' => $keyframes, ]; } // Elementor: Register Animation Controls for Widgets add_action('elementor/element/common/_section_style/after_section_end', function($element, $args) { wpaddons_register_animation_controls($element); }, 10, 2); // Add animation attributes to Elementor widget wrapper add_filter('elementor/frontend/widget/should_render', function($should_render, $widget) { $settings = $widget->get_settings_for_display(); if (!empty($settings['advanced_animation_enable'])) { $animation_settings = wpaddons_build_animation_attributes($settings); $widget->add_render_attribute('_wrapper', [ 'data-advanced-animation' => 'yes', 'data-animation-settings' => htmlspecialchars(json_encode($animation_settings), ENT_QUOTES, 'UTF-8'), ]); } return $should_render; }, 10, 2); // Elementor: Register Animation Controls for Containers add_action('elementor/element/container/section_layout/after_section_end', function($element, $args) { wpaddons_register_animation_controls($element); }, 10, 2); // Add animation attributes to Elementor container wrapper add_filter('elementor/frontend/container/should_render', function($should_render, $container) { $settings = $container->get_settings_for_display(); if (!empty($settings['advanced_animation_enable'])) { $animation_settings = wpaddons_build_animation_attributes($settings); $container->add_render_attribute('_wrapper', [ 'data-advanced-animation' => 'yes', 'data-animation-settings' => htmlspecialchars(json_encode($animation_settings), ENT_QUOTES, 'UTF-8'), ]); } return $should_render; }, 10, 2); /** * Whether Elementor's Atomic Widgets editor experiment is active. * * AI Builder UI depends on this; do not hard-depend on its script handle when off. */ function wpaddons_is_elementor_atomic_editor_active() { return class_exists( '\\Elementor\\Modules\\AtomicWidgets\\Module' ) && \Elementor\Modules\AtomicWidgets\Module::is_active(); } /** Return the status of Elementor's official server-side MCP endpoint. */ function wpaddons_get_elementor_mcp_status() { $active = false; if ( class_exists( '\\Elementor\\Modules\\Mcp\\Module' ) && method_exists( '\\Elementor\\Modules\\Mcp\\Module', 'is_active' ) ) { try { $active = (bool) \Elementor\Modules\Mcp\Module::is_active(); } catch ( \Throwable $error ) { $active = false; } } return array( 'active' => $active, 'endpoint' => rest_url( 'elementor/mcp' ), 'message' => $active ? __( 'Elementor official MCP server is active.', 'advanced-animation' ) : __( 'Elementor editor MCP tools will be detected automatically. For external clients, enable Elementor MCP in Elementor when that experiment is required by your version.', 'advanced-animation' ), ); } /** * Enqueue Elementor editor animation preview scripts */ function wpaddons_enqueue_elementor_editor_scripts() { $elementor_editor_asset = include(plugin_dir_path(__FILE__) . 'build/elementor-editor.asset.php'); $atomic_active = wpaddons_is_elementor_atomic_editor_active(); $deps = array( 'jquery' ); if ( wp_script_is( 'elementor-editor', 'registered' ) ) { $deps[] = 'elementor-editor'; } if ( $atomic_active ) { foreach ( array( 'elementor-atomic-widgets-editor', 'elementor-v2-editor-editing-panel', 'elementor-v2-editor-controls', 'elementor-v2-editor-props' ) as $handle ) { if ( wp_script_is( $handle, 'registered' ) ) { $deps[] = $handle; } } if ( wp_script_is( 'elementor-v2-editor-mcp', 'registered' ) ) { $deps[] = 'elementor-v2-editor-mcp'; } if ( wp_script_is( 'elementor-v2-schema', 'registered' ) ) { $deps[] = 'elementor-v2-schema'; } } wp_enqueue_script( 'wpaddons-elementor-editor', plugins_url('build/elementor-editor.js', __FILE__), array_merge( $deps, $elementor_editor_asset['dependencies'] ), $elementor_editor_asset['version'], true ); // AI sidebar styles — only when Atomic Widgets editor is available. if ( $atomic_active ) { $elementor_editor_style = plugin_dir_path(__FILE__) . 'build/elementor-editor.css'; if ( file_exists( $elementor_editor_style ) ) { wp_enqueue_style( 'wpaddons-elementor-editor', plugins_url('build/elementor-editor.css', __FILE__), array(), $elementor_editor_asset['version'] ); } } // AI chat panel config — WP 7.0+ and Atomic Widgets editor required. if ( $atomic_active && defined( 'ADVANCED_ADDONS_AI_ENABLED' ) && ADVANCED_ADDONS_AI_ENABLED ) { $wpaib_data = array( 'restUrl' => rest_url( 'wp-ai-builder/v1/generate' ), 'elementorRestUrl' => rest_url( 'wp-ai-builder/v1/elementor/generate' ), 'modelsUrl' => rest_url( 'wp-ai-builder/v1/models' ), 'selectionUrl' => rest_url( 'wp-ai-builder/v1/selection' ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'userId' => get_current_user_id(), 'aiEnabled' => true, 'elementorMcp' => wpaddons_get_elementor_mcp_status(), ); if ( class_exists( '\\AdvancedAddons\\Tailwind_Theme_Settings' ) ) { $wpaib_data['tailwindTheme'] = \AdvancedAddons\Tailwind_Theme_Settings::get_theme(); } wp_localize_script( 'wpaddons-elementor-editor', 'WPAIBuilder', $wpaib_data ); } } add_action('elementor/editor/after_enqueue_scripts', 'wpaddons_enqueue_elementor_editor_scripts'); //include custom css file with the plugin or theme add_action('elementor/init', function () { if (defined('ELEMENTOR_VERSION')) { require_once plugin_dir_path(__FILE__) . 'elementor-custom-css.php'; } });