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

CVE-2026-45442: The Ultimate Video Player For WordPress – by Presto Player <= 4.1.3 – Missing Authorization (presto-player)

Plugin presto-player
Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 4.1.3
Patched Version 4.1.4
Disclosed May 18, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-45442:

This vulnerability exposes an authorization bypass in the Presto Player plugin for WordPress, affecting versions up to and including 4.1.3. The flaw resides in the shortcode handler within `presto-player/inc/Services/Shortcodes.php`. Unauthenticated attackers can access private or otherwise non-public Media Hub video posts by using the `[presto_player id=X]` shortcode, which renders the video directly without performing the standard WordPress post-visibility check.

The root cause is a missing capability check in the shortcode rendering flow. The `render` function in `Shortcodes.php` (line 82-86) calls `ReusableVideos::getBlock( $atts[‘id’] )` when the shortcode supplies an ID but no source URL. This function bypasses the normal `current_user_can(‘read_post’)` check that WordPress would apply when visiting the Media Hub post’s permalink. The vulnerability is exacerbated by the absence of any `getMediaHubPostVisibility()` method or gate in the vulnerable version; that method was added in the patch to retrieve the post status and enforce access control.

To exploit this, an attacker crafts a request that includes the shortcode `[presto_player id={private_post_id}]` within a post, page, or widget that renders shortcodes. Since the shortcode does not verify the user’s capability to read the underlying `pp_video_block` post, the video player HTML for private, password-protected, or draft Media Hub items is rendered directly in the response. The attack requires no authentication or special privileges; it simply leverages a shortcode attribute to fetch any Media Hub post by its numeric ID.

The patch introduces the `maybeUnauthorizedFallback` method in `Shortcodes.php` (lines 521-570). This method instantiates a `ReusableVideo` model, calls the new `getMediaHubPostVisibility()` method to obtain the post’s status, and then checks if the status is public. If the status is not public, it defers to `current_user_can(‘read_post’, $id)` to verify authorization. If the current user cannot read the post, the method returns a fallback HTML curtain (“Please login for access”) via `Block::getFallbackHTMLForUnauthorizeAccess()`, preventing the video content from leaking. The patch also adds the `getMediaHubPostVisibility()` method to the `ReusableVideo` model (lines 149-167) to expose the post status to the authorization gate.

Successful exploitation allows an unauthenticated attacker to view private, draft, password-protected, or otherwise restricted Media Hub video content. This is a direct violation of the intended access controls set by the site administrator. The impact is information disclosure: embedding or directly accessing video content that should be hidden from public or low-privilege users. No privilege escalation or remote code execution is possible, but the confidentiality of private media is fully compromised.

Differential between vulnerable and patched code

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

Code Diff
--- a/presto-player/dist/blocks.asset.php
+++ b/presto-player/dist/blocks.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-jsx-runtime', 'regenerator-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-url'), 'version' => '858fd58aa5cdf88105c6');
+<?php return array('dependencies' => array('lodash', 'react', 'react-jsx-runtime', 'regenerator-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-url'), 'version' => '4ddfe2a442cb70bc9da4');
--- a/presto-player/inc/Models/ReusableVideo.php
+++ b/presto-player/inc/Models/ReusableVideo.php
@@ -1,4 +1,9 @@
 <?php
+/**
+ * Reusable Video Model.
+ *
+ * @package PrestoPlayerModels
+ */

 namespace PrestoPlayerModels;

@@ -42,6 +47,8 @@
 	 */
 	public function __construct( $id = 0 ) {
 		$this->post = get_post( $id );
+		// TODO: remove this no-op return; kept temporarily to avoid changing constructor contract in a patch release.
+		// phpcs:ignore Universal.CodeAnalysis.ConstructorDestructorReturn.ReturnValueFound
 		return $this;
 	}

@@ -142,6 +149,26 @@
 	}

 	/**
+	 * Get the WordPress post status of the underlying Media Hub item.
+	 *
+	 * Returns the raw post_status string (e.g. `publish`, `private`, `draft`)
+	 * when the loaded post is a Media Hub item, or null when the post is
+	 * missing or of a different type. Callers decide policy from the value;
+	 * this method is intentionally data-only.
+	 *
+	 * @return string|null
+	 */
+	public function getMediaHubPostVisibility() {
+		if ( ! $this->post ) {
+			return null;
+		}
+		if ( $this->post_type !== $this->post->post_type ) {
+			return null;
+		}
+		return $this->post->post_status;
+	}
+
+	/**
 	 * Get attributes from the block
 	 *
 	 * @param array $overrides Attributes to override.
@@ -154,10 +181,10 @@
 			return '';
 		}

-		// allow overriding attributes
+		// Allow overriding attributes.
 		$block['attrs'] = wp_parse_args( $overrides, (array) $block['attrs'] );

-		// maybe switch provider depending on url
+		// Maybe switch provider depending on url.
 		if ( ! empty( $overrides ) ) {
 			$block = $this->maybeSwitchProvider( $block );
 		}
@@ -196,10 +223,10 @@
 			return '';
 		}

-		// allow overriding attributes
+		// Allow overriding attributes.
 		$block['attrs'] = wp_parse_args( $overrides, (array) $block['attrs'] );

-		// maybe switch provider depending on url
+		// Maybe switch provider depending on url.
 		$block = $this->maybeSwitchProvider( $block );

 		// remove attachment_id if the src changes.
--- a/presto-player/inc/Services/AdminNotices.php
+++ b/presto-player/inc/Services/AdminNotices.php
@@ -7,7 +7,7 @@

 namespace PrestoPlayerServices;

-use Astra_Notices;
+use BSF_Admin_Notices;
 use PrestoPlayerModelsVideo;
 use PrestoPlayerSupportUtility;

@@ -49,10 +49,10 @@
 	 * @return void
 	 */
 	public function displayRatingsNotice() {
-		require_once PRESTO_PLAYER_PLUGIN_DIR . 'vendor/brainstormforce/astra-notices/class-astra-notices.php';
+		require_once PRESTO_PLAYER_PLUGIN_DIR . 'vendor/brainstormforce/astra-notices/class-bsf-admin-notices.php';
 		$image_path = PRESTO_PLAYER_PLUGIN_URL . 'img/presto-player-icon-color.png';

-		Astra_Notices::add_notice(
+		BSF_Admin_Notices::add_notice(
 			array(
 				'id'                         => 'presto-player-rating',
 				'type'                       => '',
--- a/presto-player/inc/Services/Shortcodes.php
+++ b/presto-player/inc/Services/Shortcodes.php
@@ -15,6 +15,7 @@
 use PrestoPlayerBlocksSelfHostedBlock;
 use PrestoPlayerModelsReusableVideo;
 use PrestoPlayerServicesReusableVideos;
+use PrestoPlayerSupportBlock;
 use PrestoPlayerProBlocksBunnyCDNBlock;
 use PrestoPlayerPlugin;

@@ -81,6 +82,10 @@

 		// could not find source but ID is present.
 		if ( ! $atts['src'] && ! $atts['custom_field'] && $atts['id'] ) {
+			$fallback = $this->maybeUnauthorizedFallback( $atts['id'] );
+			if ( false !== $fallback ) {
+				return $fallback;
+			}
 			return ReusableVideos::getBlock( $atts['id'] );
 		}

@@ -516,6 +521,50 @@
 	}

 	/**
+	 * Pre-render gate for the player shortcode.
+	 *
+	 * The shortcode `[presto_player id=X]` renders the underlying Media Hub
+	 * post directly via ReusableVideos::getBlock(). That bypasses the
+	 * normal singular-post visibility check WordPress applies when you
+	 * visit the Media Hub permalink — so a Media Hub item set to Private
+	 * was leaking through the shortcode.
+	 *
+	 * This method short-circuits the shortcode with the standard
+	 * "Please login for access" curtain whenever the referenced
+	 * pp_video_block post is non-public AND the current user cannot read
+	 * it. We defer to current_user_can( 'read_post', $id ) so WP's
+	 * map_meta_cap chain stays in charge — site owners and capability
+	 * plugins (membership, LMS) keep full control without us inventing
+	 * a parallel filter.
+	 *
+	 * @param int|string $id Media Hub post ID supplied to the shortcode.
+	 * @return string|false  Curtain HTML to short-circuit, or false to continue.
+	 */
+	public function maybeUnauthorizedFallback( $id ) {
+		$reusable   = new ReusableVideo( (int) $id );
+		$visibility = $reusable->getMediaHubPostVisibility();
+
+		// Not a Media Hub post — let the normal flow handle it.
+		if ( null === $visibility ) {
+			return false;
+		}
+
+		// Public statuses (publish) are viewable by anyone — fast path.
+		$status_obj = get_post_status_object( $visibility );
+		if ( $status_obj && $status_obj->public ) {
+			return false;
+		}
+
+		// Non-public status — defer to WP's standard post-visibility check
+		// so admins/editors and capability plugins still pass through.
+		if ( current_user_can( 'read_post', $reusable->post->ID ) ) {
+			return false;
+		}
+
+		return ( new Block() )->getFallbackHTMLForUnauthorizeAccess();
+	}
+
+	/**
 	 * Maybe switch provider if the url is overridden.
 	 *
 	 * @param string $src Source URL.
--- a/presto-player/inc/lib/bsf-analytics/class-bsf-analytics-events.php
+++ b/presto-player/inc/lib/bsf-analytics/class-bsf-analytics-events.php
@@ -58,42 +58,79 @@
 		}

 		/**
-		 * Track a one-time event. Skips if already tracked or pending.
+		 * Track an event. By default, skips if already tracked or pending (one-time semantics).
+		 * When $force is true, the event is treated as retrackable — bypasses the post-send
+		 * dedup check and overwrites any pending entry with the same name. Useful for
+		 * recurring events like `plugin_updated` where the latest value should always win.
 		 * Only stores temporary data — cleaned up after analytics send.
 		 *
 		 * @param string               $event_name  Event identifier.
 		 * @param string               $event_value Primary value (version, form ID, mode, etc.).
-		 * @param array<string, mixed> $properties  Additional context as key-value pairs.
+		 * @param array<string, mixed> $properties  Additional context as key-value pairs. Values are stored as-is — sanitization is the caller's responsibility.
+		 * @param bool                 $force       When true, bypass pushed dedup and overwrite pending entry. Default false.
 		 * @since 1.1.21
+		 * @since 1.1.25 Added the $force parameter.
 		 * @return void
 		 */
-		public function track( $event_name, $event_value = '', $properties = array() ) {
+		public function track( $event_name, $event_value = '', $properties = array(), $force = false ) {
 			// Sanitize inputs once upfront — ensures dedup comparisons match stored values.
 			$event_name  = sanitize_text_field( $event_name );
 			$event_value = sanitize_text_field( (string) $event_value );
 			$properties  = is_array( $properties ) ? $properties : array();
+			$force       = (bool) $force;

 			// Check dedup flag — already sent in a previous cycle.
-			$pushed = $this->get_option( 'usage_events_pushed', array() );
-			$pushed = is_array( $pushed ) ? $pushed : array();
-			if ( in_array( $event_name, $pushed, true ) ) {
-				return;
+			// Force bypasses this check; pushed list will be refreshed on next flush_pending().
+			if ( ! $force ) {
+				$pushed = $this->get_option( 'usage_events_pushed', array() );
+				$pushed = is_array( $pushed ) ? $pushed : array();
+				if ( in_array( $event_name, $pushed, true ) ) {
+					return;
+				}
 			}

 			// Check if already queued in current cycle.
 			$pending = $this->get_option( 'usage_events_pending', array() );
 			$pending = is_array( $pending ) ? $pending : array();
-			if ( in_array( $event_name, array_column( $pending, 'event_name' ), true ) ) {
-				return;
-			}

-			// Add to pending queue.
-			$pending[] = array(
+			$new_event = array(
 				'event_name'  => $event_name,
 				'event_value' => $event_value,
 				'properties'  => $properties,
 				'date'        => current_time( 'mysql' ),
 			);
+
+			if ( ! $force ) {
+				// Default path: cheap membership check — no need to locate the key.
+				if ( in_array( $event_name, array_column( $pending, 'event_name' ), true ) ) {
+					return;
+				}
+				$pending[] = $new_event;
+			} else {
+				// Force path: locate any existing entry by actual key to overwrite safely.
+				$existing_key = null;
+				foreach ( $pending as $key => $entry ) {
+					if ( isset( $entry['event_name'] ) && $entry['event_name'] === $event_name ) {
+						$existing_key = $key;
+						break;
+					}
+				}
+
+				if ( null !== $existing_key ) {
+					// Skip the write when nothing material changed (only `date` would differ).
+					$existing = $pending[ $existing_key ];
+					if ( array_key_exists( 'event_value', $existing )
+						&& array_key_exists( 'properties', $existing )
+						&& $existing['event_value'] === $new_event['event_value']
+						&& $existing['properties'] === $new_event['properties'] ) {
+						return;
+					}
+					$pending[ $existing_key ] = $new_event;
+				} else {
+					$pending[] = $new_event;
+				}
+			}
+
 			$this->update_option( 'usage_events_pending', $pending );
 		}

--- a/presto-player/inc/lib/bsf-analytics/class-bsf-analytics.php
+++ b/presto-player/inc/lib/bsf-analytics/class-bsf-analytics.php
@@ -93,7 +93,7 @@
 		public function set_actions() {

 			foreach ( $this->entities as $key => $data ) {
-				add_action( 'astra_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
+				add_action( 'bsf_admin_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
 				add_action( 'update_option_' . $key . '_usage_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
 				add_action( 'add_option_' . $key . '_usage_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
 			}
@@ -235,6 +235,9 @@
 		 * @since 1.0.0
 		 */
 		public function option_notice() {
+			if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
+				return;
+			}

 			if ( ! current_user_can( 'manage_options' ) ) {
 				return;
@@ -279,7 +282,7 @@

 				$language_dir = is_rtl() ? 'rtl' : 'ltr';

-				Astra_Notices::add_notice(
+				BSF_Admin_Notices::add_notice(
 					array(
 						'id'                         => $key . '-optin-notice',
 						'type'                       => '',
--- a/presto-player/phpinsights.php
+++ b/presto-player/phpinsights.php
@@ -0,0 +1,297 @@
+<?php
+
+declare(strict_types=1);
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Preset
+    |--------------------------------------------------------------------------
+    |
+    | This option controls the default preset that will be used by PHP Insights
+    | to make your code reliable, simple, and clean. However, you can always
+    | adjust the `Metrics` and `Insights` below in this configuration file.
+    |
+    | Supported: "default", "laravel", "symfony", "magento2", "drupal", "wordpress"
+    |
+    */
+
+    'preset' => 'wordpress',
+
+    /*
+    |--------------------------------------------------------------------------
+    | IDE
+    |--------------------------------------------------------------------------
+    |
+    | This options allow to add hyperlinks in your terminal to quickly open
+    | files in your favorite IDE while browsing your PhpInsights report.
+    |
+    | Supported: "textmate", "macvim", "emacs", "sublime", "phpstorm",
+    | "atom", "vscode".
+    |
+    | If you have another IDE that is not in this list but which provide an
+    | url-handler, you could fill this config with a pattern like this:
+    |
+    | myide://open?url=file://%f&line=%l
+    |
+    */
+
+    'ide' => 'myide://open?url=file://%f&line=%l',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Configuration
+    |--------------------------------------------------------------------------
+    |
+    | Here you may adjust all the various `Insights` that will be used by PHP
+    | Insights. You can either add, remove or configure `Insights`. Keep in
+    | mind, that all added `Insights` must belong to a specific `Metric`.
+    |
+    */
+
+    'exclude' => [
+        'assets/*',
+        'dist/*',
+        'node_modules',
+        'vendor',
+        'tests',
+        'tests-e2e',
+        'packages/*',
+        'inc/lib',
+        'inc/Libraries',
+        'phpinsights.php',
+    ],
+
+    'add' => [
+
+    ],
+
+    'remove' => [
+        /**
+         * Globals accesses detected
+         * ToDo: Remove this rule after fixing the issue
+         */
+        NunoMaduroPhpInsightsDomainInsightsForbiddenGlobals::class,
+
+        /**
+         * Global keyword
+         * ToDo: Remove this rule after fixing the issue
+         */
+        PHP_CodeSnifferStandardsSquizSniffsPHPGlobalKeywordSniff::class,
+
+        /**
+         * Defining global helpers is prohibited
+         * ToDo: Remove this rule after fixing the issue
+         */
+        NunoMaduroPhpInsightsDomainInsightsForbiddenDefineFunctions::class,
+
+        /**
+         * Return, Property, Parameter  type hint
+         */
+        SlevomatCodingStandardSniffsTypeHintsReturnTypeHintSniff::class,
+        SlevomatCodingStandardSniffsTypeHintsPropertyTypeHintSniff::class,
+        SlevomatCodingStandardSniffsTypeHintsParameterTypeHintSniff::class,
+
+        /**
+         * Disallow mixed type hint
+         */
+        SlevomatCodingStandardSniffsTypeHintsDisallowMixedTypeHintSniff::class,
+
+        /**
+         * Disallow empty
+         */
+        SlevomatCodingStandardSniffsControlStructuresDisallowEmptySniff::class,
+
+        /**
+         * Forbidden public property
+         */
+        SlevomatCodingStandardSniffsClassesForbiddenPublicPropertySniff::class,
+
+        /**
+         * Having `classes` with more than 5 cyclomatic complexity is prohibited - Consider refactoring.
+         */
+        NunoMaduroPhpInsightsDomainInsightsCyclomaticComplexityIsHigh::class,
+
+        /**
+         * Function length
+         */
+        SlevomatCodingStandardSniffsFunctionsFunctionLengthSniff::class,
+
+        /**
+         * Valid class name, not in PascalCase format.
+         */
+        PHP_CodeSnifferStandardsSquizSniffsClassesValidClassNameSniff::class,
+
+        /**
+         * Indentation conflict with PHPCS.
+         */
+        PhpCsFixerFixerWhitespaceMethodChainingIndentationFixer::class,
+
+        /**
+         * No spaces around offset.
+         */
+        PhpCsFixerFixerWhitespaceNoSpacesAroundOffsetFixer::class,
+
+        /**
+         * Side effects.
+         */
+        PHP_CodeSnifferStandardsPSR1SniffsFilesSideEffectsSniff::class,
+
+        /**
+         * Arbitrary parentheses spacing
+         */
+        PHP_CodeSnifferStandardsGenericSniffsWhiteSpaceArbitraryParenthesesSpacingSniff::class,
+
+        /**
+         * Character before p h p opening tag
+         */
+        PHP_CodeSnifferStandardsGenericSniffsPHPCharacterBeforePHPOpeningTagSniff::class,
+
+        /**
+         * Disallow tab indent
+         */
+        PHP_CodeSnifferStandardsGenericSniffsWhiteSpaceDisallowTabIndentSniff::class,
+
+        /**
+         * Line length
+         */
+        PHP_CodeSnifferStandardsGenericSniffsFilesLineLengthSniff::class,
+
+        /**
+         * Binary operator spaces.
+         */
+        PhpCsFixerFixerOperatorBinaryOperatorSpacesFixer::class,
+
+        /**
+         * No spaces inside parenthesis
+         */
+        PhpCsFixerFixerWhitespaceNoSpacesInsideParenthesisFixer::class,
+
+        /**
+         * No spaces after function name
+         */
+        PhpCsFixerFixerFunctionNotationFunctionDeclarationFixer::class,
+
+        /**
+         * Class definition
+         */
+        PhpCsFixerFixerClassNotationClassDefinitionFixer::class,
+
+        /**
+         * Method argument space
+         */
+        PhpCsFixerFixerFunctionNotationMethodArgumentSpaceFixer::class,
+
+        /**
+         * Braces fixer
+         */
+        PhpCsFixerFixerBasicBracesFixer::class,
+
+        /**
+         * Declare strict types.
+         */
+        SlevomatCodingStandardSniffsTypeHintsDeclareStrictTypesSniff::class,
+
+
+        /**
+         * DOC comment spacing
+         */
+        SlevomatCodingStandardSniffsCommentingDocCommentSpacingSniff::class,
+
+
+        /**
+         * Camel caps method name
+         */
+        PHP_CodeSnifferStandardsPSR1SniffsMethodsCamelCapsMethodNameSniff::class,
+
+        /**
+         * Normal classes are forbidden. Classes must be final or abstract
+         * Todo: Remove this rule after fixing the issue
+         */
+        NunoMaduroPhpInsightsDomainInsightsForbiddenNormalClasses::class,
+
+        /**
+         * Traits disallowed.
+         */
+        NunoMaduroPhpInsightsDomainInsightsForbiddenTraits::class,
+
+        /**
+         * Globals prohibited.
+         */
+        NunoMaduroPhpInsightsDomainInsightsForbiddenDefineGlobalConstants::class,
+
+        /**
+         * Unused parameter. Case: Shortcode callback attr, hook callback attr, etc.
+         */
+        SlevomatCodingStandardSniffsFunctionsUnusedParameterSniff::class,
+
+        /**
+         * Disallow alternate php tags. Case: Backbone template used for search.
+         */
+        PHP_CodeSnifferStandardsGenericSniffsPHPDisallowAlternativePHPTagsSniff::class,
+
+        /**
+         * Used class but still showing as unused.
+         */
+        SlevomatCodingStandardSniffsNamespacesUnusedUsesSniff::class,
+
+        /**
+         * Forbidden security issues.
+         */
+        NunoMaduroPhpInsightsDomainInsightsForbiddenSecurityIssues::class,
+
+        PhpCsFixerFixerCommentNoTrailingWhitespaceInCommentFixer::class,
+    ],
+
+    'config' => [
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Requirements
+    |--------------------------------------------------------------------------
+    |
+    | Here you may define a level you want to reach per `Insights` category.
+    | When a score is lower than the minimum level defined, then an error
+    | code will be returned. This is optional and individually defined.
+    |
+    */
+
+    'requirements' => [
+        // Initial baseline thresholds set just below current scores so CI passes.
+        // Raise these as code quality improves.
+        'min-quality' => 60,
+        'min-complexity' => 45,
+        'min-architecture' => 80,
+        'min-style' => 60,
+        'disable-security-check' => false,
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Threads
+    |--------------------------------------------------------------------------
+    |
+    | Here you may adjust how many threads (core) PHPInsights can use to perform
+    | the analysis. This is optional, don't provide it and the tool will guess
+    | the max core number available. It accepts null value or integer > 0.
+    |
+    */
+
+    'threads' => null,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Timeout
+    |--------------------------------------------------------------------------
+    | Here you may adjust the timeout (in seconds) for PHPInsights to run before
+    | a ProcessTimedOutException is thrown.
+    | This accepts an int > 0. Default is 60 seconds, which is the default value
+    | of Symfony's setTimeout function.
+    |
+    */
+
+    'timeout' => 60,
+];
--- a/presto-player/presto-player.php
+++ b/presto-player/presto-player.php
@@ -3,12 +3,15 @@
  * Plugin Name: Presto Player
  * Plugin URI: http://prestoplayer.com
  * Description: A beautiful, fast media player for WordPress.
- * Version: 4.1.3
+ * Version: 4.1.4
  * Author: Presto Made, Inc
  * Author URI: https://prestoplayer.com/
  * Text Domain: presto-player
  * Tags: private, video, lms, hls
  * Domain Path: languages
+ * Requires at least: 6.3
+ * Tested up to: 7.0
+ * Requires PHP: 7.4
  *
  * @package PrestoPlayer
  */
--- a/presto-player/vendor/autoload.php
+++ b/presto-player/vendor/autoload.php
@@ -19,4 +19,4 @@

 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInit31d215ca8da9a340c7a9d509021891f7::getLoader();
+return ComposerAutoloaderInit402153a10addfafa2a15d15dd2dd7351::getLoader();
--- a/presto-player/vendor/brainstormforce/astra-notices/class-astra-notices.php
+++ b/presto-player/vendor/brainstormforce/astra-notices/class-astra-notices.php
@@ -1,420 +0,0 @@
-<?php
-/**
- * Astra Notices
- *
- * An easy to use PHP Library to add dismissible admin notices in the WordPress admin.
- *
- * @package Astra Notices
- * @since 1.0.0
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
-	exit; // Exit if accessed directly.
-}
-
-if ( ! class_exists( 'Astra_Notices' ) ) :
-
-	/**
-	 * Astra_Notices
-	 *
-	 * @since 1.0.0
-	 */
-	class Astra_Notices {
-
-		/**
-		 * Notices
-		 *
-		 * @access private
-		 * @var array Notices.
-		 * @since 1.0.0
-		 */
-		private static $version = '1.1.14';
-
-		/**
-		 * Notices
-		 *
-		 * @access private
-		 * @var array Notices.
-		 * @since 1.0.0
-		 */
-		private static $notices = array();
-
-		/**
-		 * Instance
-		 *
-		 * @access private
-		 * @var object Class object.
-		 * @since 1.0.0
-		 */
-		private static $instance;
-
-		/**
-		 * Initiator
-		 *
-		 * @since 1.0.0
-		 * @return object initialized object of class.
-		 */
-		public static function get_instance() {
-			if ( ! isset( self::$instance ) ) {
-				self::$instance = new self();
-			}
-			return self::$instance;
-		}
-
-		/**
-		 * Constructor
-		 *
-		 * @since 1.0.0
-		 */
-		public function __construct() {
-			add_action( 'admin_notices', array( $this, 'show_notices' ), 30 );
-			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
-			add_action( 'wp_ajax_astra-notice-dismiss', array( $this, 'dismiss_notice' ) );
-			add_filter( 'wp_kses_allowed_html', array( $this, 'add_data_attributes' ), 10, 2 );
-		}
-
-		/**
-		 * Filters and Returns a list of allowed tags and attributes for a given context.
-		 *
-		 * @param array  $allowedposttags array of allowed tags.
-		 * @param string $context Context type (explicit).
-		 * @since 1.0.0
-		 * @return array
-		 */
-		public function add_data_attributes( $allowedposttags, $context ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
-			$allowedposttags['a']['data-repeat-notice-after'] = true;
-
-			return $allowedposttags;
-		}
-
-		/**
-		 * Add Notice.
-		 *
-		 * @since 1.0.0
-		 * @param array $args Notice arguments.
-		 * @return void
-		 */
-		public static function add_notice( $args = array() ) {
-			self::$notices[] = $args;
-
-			if ( ! isset( $args['id'] ) ) {
-				return;
-			}
-
-			$notice_id = sanitize_key( $args['id'] ); // Notice ID.
-			$notices   = get_option( 'allowed_astra_notices', array() );
-			if ( ! in_array( $notice_id, $notices, true ) ) {
-				$notices[] = $notice_id; // Add notice id to the array.
-				update_option( 'allowed_astra_notices', $notices ); // Update the option.
-			}
-		}
-
-		/**
-		 * Dismiss Notice.
-		 *
-		 * @since 1.0.0
-		 * @return void
-		 */
-		public function dismiss_notice() {
-			$notice_id           = ( isset( $_POST['notice_id'] ) ) ? sanitize_key( $_POST['notice_id'] ) : '';
-			$repeat_notice_after = ( isset( $_POST['repeat_notice_after'] ) ) ? absint( $_POST['repeat_notice_after'] ) : '';
-			$nonce               = ( isset( $_POST['nonce'] ) ) ? sanitize_key( $_POST['nonce'] ) : '';
-			$notice              = $this->get_notice_by_id( $notice_id );
-			$capability          = isset( $notice['capability'] ) ? $notice['capability'] : 'manage_options';
-
-			if ( ! apply_filters( 'astra_notices_user_cap_check', current_user_can( $capability ) ) ) {
-				return;
-			}
-
-			$allowed_notices = get_option( 'allowed_astra_notices', array() ); // Get allowed notices.
-
-			// Define restricted user meta keys.
-			$wp_default_meta_keys = array(
-				'wp_capabilities',
-				'wp_user_level',
-				'wp_user-settings',
-				'account_status',
-				'session_tokens',
-			);
-
-			// if $notice_id does not start with astra-notices-id and notice_id is not from the allowed notices, then return.
-			if ( strpos( $notice_id, 'astra-notices-id-' ) !== 0 && ( ! in_array( $notice_id, $allowed_notices, true ) ) ) {
-				return;
-			}
-
-			if ( false === wp_verify_nonce( $nonce, 'astra-notices' ) ) {
-				wp_send_json_error( esc_html_e( 'WordPress Nonce not validated.' ) );
-			}
-
-			// Valid inputs?
-			if ( ! empty( $notice_id ) ) {
-
-				if ( in_array( $notice_id, $wp_default_meta_keys, true ) ) {
-					wp_send_json_error( esc_html_e( 'Invalid notice ID.' ) );
-				}
-
-				if ( ! empty( $repeat_notice_after ) ) {
-					set_transient( $notice_id, true, $repeat_notice_after );
-				} else {
-					update_user_meta( get_current_user_id(), $notice_id, 'notice-dismissed' );
-				}
-
-				wp_send_json_success();
-			}
-
-			wp_send_json_error();
-		}
-
-		/**
-		 * Enqueue Scripts.
-		 *
-		 * @since 1.0.0
-		 * @return void
-		 */
-		public function enqueue_scripts() {
-			wp_register_style( 'astra-notices', self::get_url() . 'notices.css', array(), self::$version );
-			wp_register_script( 'astra-notices', self::get_url() . 'notices.js', array( 'jquery' ), self::$version, true );
-			wp_localize_script(
-				'astra-notices',
-				'astraNotices',
-				array(
-					'_notice_nonce' => wp_create_nonce( 'astra-notices' ),
-				)
-			);
-		}
-
-		/**
-		 * Sort the notices based on the given priority of the notice.
-		 * This function is called from usort()
-		 *
-		 * @since 1.5.2
-		 * @param array $notice_1 First notice.
-		 * @param array $notice_2 Second Notice.
-		 * @return array
-		 */
-		public function sort_notices( $notice_1, $notice_2 ) {
-			if ( ! isset( $notice_1['priority'] ) ) {
-				$notice_1['priority'] = 10;
-			}
-			if ( ! isset( $notice_2['priority'] ) ) {
-				$notice_2['priority'] = 10;
-			}
-
-			return $notice_1['priority'] - $notice_2['priority'];
-		}
-
-		/**
-		 * Get all registered notices.
-		 * Since v1.1.8 it is recommended to register the notices on
-		 *
-		 * @return array|null
-		 */
-		private function get_notices() {
-			usort( self::$notices, array( $this, 'sort_notices' ) );
-
-			return self::$notices;
-		}
-
-		/**
-		 * Get notice by notice_id
-		 *
-		 * @param string $notice_id Notice id.
-		 *
-		 * @return array notice based on the notice id.
-		 */
-		private function get_notice_by_id( $notice_id ) {
-			if ( empty( $notice_id ) ) {
-				return array();
-			}
-
-			$notices = $this->get_notices();
-			$notice  = wp_list_filter(
-				$notices,
-				array(
-					'id' => $notice_id,
-				)
-			);
-
-			return ( ! empty( $notice ) && isset( $notice[0] ) ) ? $notice[0] : array();
-		}
-
-		/**
-		 * Display the notices in the WordPress admin.
-		 *
-		 * @since 1.0.0
-		 * @return void
-		 */
-		public function show_notices() {
-			$defaults = array(
-				'id'                         => '',      // Optional, Notice ID. If empty it set `astra-notices-id-<$array-index>`.
-				'type'                       => 'info',  // Optional, Notice type. Default `info`. Expected [info, warning, notice, error].
-				'message'                    => '',      // Optional, Message.
-				'show_if'                    => true,    // Optional, Show notice on custom condition. E.g. 'show_if' => if( is_admin() ) ? true, false, .
-				'repeat-notice-after'        => '',      // Optional, Dismiss-able notice time. It'll auto show after given time.
-				'display-notice-after'       => false,      // Optional, Dismiss-able notice time. It'll auto show after given time.
-				'class'                      => '',      // Optional, Additional notice wrapper class.
-				'priority'                   => 10,      // Priority of the notice.
-				'display-with-other-notices' => true,    // Should the notice be displayed if other notices  are being displayed from Astra_Notices.
-				'is_dismissible'             => true,
-				'capability'                 => 'manage_options', // User capability - This capability is required for the current user to see this notice.
-			);
-
-			// Count for the notices that are rendered.
-			$notices_displayed = 0;
-			$notices           = $this->get_notices();
-
-			foreach ( $notices as $key => $notice ) {
-				$notice = wp_parse_args( $notice, $defaults );
-
-				// Show notices only for users with `manage_options` cap.
-				if ( ! current_user_can( $notice['capability'] ) ) {
-					continue;
-				}
-
-				$notice['id']      = self::get_notice_id( $notice, $key );
-				$notice['classes'] = self::get_wrap_classes( $notice );
-
-				// Notices visible after transient expire.
-				if ( isset( $notice['show_if'] ) && true === $notice['show_if'] ) {
-
-					// don't display the notice if it is not supposed to be displayed with other notices.
-					if ( 0 !== $notices_displayed && false === $notice['display-with-other-notices'] ) {
-						continue;
-					}
-
-					if ( self::is_expired( $notice ) ) {
-
-						self::markup( $notice );
-						++$notices_displayed;
-					}
-				}
-			}
-		}
-
-		/**
-		 * Render a notice.
-		 *
-		 * @since 1.0.0
-		 * @param  array $notice Notice markup.
-		 * @return void
-		 */
-		public static function markup( $notice = array() ) {
-			wp_enqueue_script( 'astra-notices' );
-			wp_enqueue_style( 'astra-notices' );
-
-			do_action( 'astra_notice_before_markup' );
-
-			do_action( "astra_notice_before_markup_{$notice['id']}" );
-
-			?>
-			<div id="<?php echo esc_attr( $notice['id'] ); ?>" class="<?php echo 'astra-notice-wrapper ' . esc_attr( $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
-				<div class="astra-notice-container">
-					<?php do_action( "astra_notice_inside_markup_{$notice['id']}" ); ?>
-					<?php echo wp_kses_post( $notice['message'] ); ?>
-				</div>
-			</div>
-			<?php
-
-			do_action( "astra_notice_after_markup_{$notice['id']}" );
-
-			do_action( 'astra_notice_after_markup' );
-		}
-
-		/**
-		 * Get wrapper classes for a notice.
-		 *
-		 * @since 1.0.0
-		 *
-		 * @param  array $notice Notice arguments.
-		 * @return array       Notice wrapper classes.
-		 */
-		private static function get_wrap_classes( $notice ) {
-			$classes = array( 'astra-notice', 'notice' );
-
-			if ( $notice['is_dismissible'] ) {
-				$classes[] = 'is-dismissible';
-			}
-
-			$classes[] = $notice['class'];
-			if ( isset( $notice['type'] ) && '' !== $notice['type'] ) {
-				$classes[] = 'notice-' . $notice['type'];
-			}
-
-			return esc_attr( implode( ' ', $classes ) );
-		}
-
-		/**
-		 * Get HTML ID for a given notice.
-		 *
-		 * @since 1.0.0
-		 *
-		 * @param  array $notice Notice arguments.
-		 * @param  int   $key    Notice array index.
-		 * @return string HTML if for the notice.
-		 */
-		private static function get_notice_id( $notice, $key ) {
-			if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
-				return $notice['id'];
-			}
-
-			return 'astra-notices-id-' . $key;
-		}
-
-		/**
-		 * Check if the notice is expires.
-		 *
-		 * @since 1.0.0
-		 *
-		 * @param  array $notice Notice arguments.
-		 * @return boolean
-		 */
-		private static function is_expired( $notice ) {
-			$transient_status = get_transient( $notice['id'] );
-
-			if ( false === $transient_status ) {
-
-				if ( isset( $notice['display-notice-after'] ) && false !== $notice['display-notice-after'] ) {
-
-					if ( 'delayed-notice' !== get_user_meta( get_current_user_id(), $notice['id'], true ) &&
-						'notice-dismissed' !== get_user_meta( get_current_user_id(), $notice['id'], true ) ) {
-						set_transient( $notice['id'], 'delayed-notice', $notice['display-notice-after'] );
-						update_user_meta( get_current_user_id(), $notice['id'], 'delayed-notice' );
-
-						return false;
-					}
-				}
-
-				// Check the user meta status if current notice is dismissed or delay completed.
-				$meta_status = get_user_meta( get_current_user_id(), $notice['id'], true );
-
-				if ( empty( $meta_status ) || 'delayed-notice' === $meta_status ) {
-					return true;
-				}
-			}
-
-			return false;
-		}
-
-		/**
-		 * Get base URL for the astra-notices.
-		 *
-		 * @return mixed URL.
-		 */
-		public static function get_url() {
-			$path      = wp_normalize_path( dirname( __FILE__ ) ); // phpcs:ignore Modernize.FunctionCalls.Dirname.FileConstant
-			$theme_dir = wp_normalize_path( get_template_directory() );
-
-			if ( strpos( $path, $theme_dir ) !== false ) {
-				return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
-			} else {
-				return plugin_dir_url( __FILE__ );
-			}
-		}
-	}
-
-	/**
-	 * Kicking this off by calling 'get_instance()' method
-	 */
-	Astra_Notices::get_instance();
-
-endif;
--- a/presto-player/vendor/brainstormforce/astra-notices/class-bsf-admin-notices.php
+++ b/presto-player/vendor/brainstormforce/astra-notices/class-bsf-admin-notices.php
@@ -0,0 +1,448 @@
+<?php
+/**
+ * BSF Admin Notices
+ *
+ * An easy to use PHP Library to add dismissible admin notices in the WordPress admin.
+ *
+ * @package BSF Admin Notices
+ * @since   1.2.0
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit; // Exit if accessed directly.
+}
+
+if ( ! class_exists( 'BSF_Admin_Notices' ) ) :
+
+	/**
+	 * BSF_Admin_Notices
+	 *
+	 * Renamed from Astra_Notices. All runtime strings (AJAX action, nonce,
+	 * script handles, JS globals, CSS classes, option keys, ID prefixes) are
+	 * intentionally frozen at their original values so old plugin JS/CSS that
+	 * is already shipped continues to work without updates.
+	 *
+	 * @since 1.2.0
+	 */
+	class BSF_Admin_Notices {
+
+		/**
+		 * Library version.
+		 *
+		 * @access private
+		 * @var string
+		 * @since 1.2.0
+		 */
+		private static $version = '1.2.1';
+
+		/**
+		 * Registered notices.
+		 *
+		 * @access private
+		 * @var array
+		 * @since 1.2.0
+		 */
+		private static $notices = array();
+
+		/**
+		 * Instance.
+		 *
+		 * @access private
+		 * @var object Class object.
+		 * @since 1.2.0
+		 */
+		private static $instance;
+
+		/**
+		 * Initiator.
+		 *
+		 * @since 1.2.0
+		 * @return object initialized object of class.
+		 */
+		public static function get_instance() {
+			if ( ! isset( self::$instance ) ) {
+				self::$instance = new self();
+			}
+			return self::$instance;
+		}
+
+		/**
+		 * Constructor.
+		 *
+		 * @since 1.2.0
+		 */
+		public function __construct() {
+			add_action( 'admin_notices', array( $this, 'show_notices' ), 30 );
+			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+			add_action( 'wp_ajax_astra-notice-dismiss', array( $this, 'dismiss_notice' ) );
+			add_filter( 'wp_kses_allowed_html', array( $this, 'add_data_attributes' ), 10, 2 );
+		}
+
+		/**
+		 * Filters and returns a list of allowed tags and attributes for a given context.
+		 *
+		 * @param array  $allowedposttags array of allowed tags.
+		 * @param string $context Context type (explicit).
+		 * @since 1.2.0
+		 * @return array
+		 */
+		public function add_data_attributes( $allowedposttags, $context ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
+			$allowedposttags['a']['data-repeat-notice-after'] = true;
+
+			return $allowedposttags;
+		}
+
+		/**
+		 * Add Notice.
+		 *
+		 * @since 1.2.0
+		 * @param array $args Notice arguments.
+		 * @return void
+		 */
+		public static function add_notice( $args = array() ) {
+			self::$notices[] = $args;
+
+			if ( ! isset( $args['id'] ) ) {
+				return;
+			}
+
+			$notice_id = sanitize_key( $args['id'] ); // Notice ID.
+			$notices   = get_option( 'allowed_astra_notices', array() );
+			if ( ! in_array( $notice_id, $notices, true ) ) {
+				$notices[] = $notice_id; // Add notice id to the array.
+				update_option( 'allowed_astra_notices', $notices ); // Update the option.
+			}
+		}
+
+		/**
+		 * Dismiss Notice.
+		 *
+		 * @since 1.2.0
+		 * @return void
+		 */
+		public function dismiss_notice() {
+			check_ajax_referer( 'astra-notices', 'nonce' );
+
+			$notice_id           = ( isset( $_POST['notice_id'] ) ) ? sanitize_key( wp_unslash( $_POST['notice_id'] ) ) : '';
+			$repeat_notice_after = ( isset( $_POST['repeat_notice_after'] ) ) ? absint( $_POST['repeat_notice_after'] ) : 0;
+			$notice              = $this->get_notice_by_id( $notice_id );
+			$capability          = isset( $notice['capability'] ) ? $notice['capability'] : 'manage_options';
+
+			$has_cap = current_user_can( $capability );
+
+			/**
+			 * Filters whether the current user passes the capability check for notice dismissal.
+			 *
+			 * Both the legacy and new filter names are fired for backward compatibility.
+			 * Filters can only restrict access (return false), never grant it — if the
+			 * underlying current_user_can() check fails, filters cannot override to true.
+			 */
+			$cap_check = apply_filters( 'astra_notices_user_cap_check', $has_cap );
+			$cap_check = apply_filters( 'bsf_admin_notices_user_cap_check', $cap_check );
+
+			if ( ! $has_cap || ! $cap_check ) {
+				wp_send_json_error( esc_html__( 'Permission denied.', 'astra-notices' ) );
+			}
+
+			$allowed_notices = get_option( 'allowed_astra_notices', array() ); // Get allowed notices.
+
+			// Define restricted user meta keys using the dynamic table prefix.
+			global $wpdb;
+			$wp_default_meta_keys = array(
+				$wpdb->prefix . 'capabilities',
+				$wpdb->prefix . 'user_level',
+				$wpdb->prefix . 'user-settings',
+				'account_status',
+				'session_tokens',
+			);
+
+			// if $notice_id does not start with astra-notices-id and notice_id is not from the allowed notices, then return.
+			if ( 0 !== strpos( $notice_id, 'astra-notices-id-' ) && ( ! in_array( $notice_id, $allowed_notices, true ) ) ) {
+				wp_send_json_error( esc_html__( 'Invalid notice ID.', 'astra-notices' ) );
+			}
+
+			// Valid inputs?
+			if ( ! empty( $notice_id ) ) {
+
+				if ( in_array( $notice_id, $wp_default_meta_keys, true ) ) {
+					wp_send_json_error( esc_html__( 'Invalid notice ID.', 'astra-notices' ) );
+				}
+
+				if ( ! empty( $repeat_notice_after ) ) {
+					set_transient( $notice_id, true, $repeat_notice_after );
+				} else {
+					update_user_meta( get_current_user_id(), $notice_id, 'notice-dismissed' );
+				}
+
+				wp_send_json_success();
+			}
+
+			wp_send_json_error();
+		}
+
+		/**
+		 * Enqueue Scripts.
+		 *
+		 * @since 1.2.0
+		 * @return void
+		 */
+		public function enqueue_scripts() {
+			wp_register_style( 'astra-notices', self::get_url() . 'notices.css', array(), self::$version );
+			wp_register_script( 'astra-notices', self::get_url() . 'notices.js', array( 'jquery' ), self::$version, true );
+			wp_localize_script(
+				'astra-notices',
+				'astraNotices',
+				array(
+					'_notice_nonce' => wp_create_nonce( 'astra-notices' ),
+				)
+			);
+		}
+
+		/**
+		 * Sort the notices based on the given priority of the notice.
+		 * This function is called from usort()
+		 *
+		 * @since 1.2.0
+		 * @param array $notice_1 First notice.
+		 * @param array $notice_2 Second Notice.
+		 * @return array
+		 */
+		public function sort_notices( $notice_1, $notice_2 ) {
+			if ( ! isset( $notice_1['priority'] ) ) {
+				$notice_1['priority'] = 10;
+			}
+			if ( ! isset( $notice_2['priority'] ) ) {
+				$notice_2['priority'] = 10;
+			}
+
+			return $notice_1['priority'] - $notice_2['priority'];
+		}
+
+		/**
+		 * Get all registered notices.
+		 *
+		 * @return array|null
+		 */
+		private function get_notices() {
+			usort( self::$notices, array( $this, 'sort_notices' ) );
+
+			return self::$notices;
+		}
+
+		/**
+		 * Get notice by notice_id.
+		 *
+		 * @param string $notice_id Notice id.
+		 *
+		 * @return array notice based on the notice id.
+		 */
+		private function get_notice_by_id( $notice_id ) {
+			if ( empty( $notice_id ) ) {
+				return array();
+			}
+
+			$notices = $this->get_notices();
+			$notice  = wp_list_filter(
+				$notices,
+				array(
+					'id' => $notice_id,
+				)
+			);
+
+			return ( ! empty( $notice ) && isset( $notice[0] ) ) ? $notice[0] : array();
+		}
+
+		/**
+		 * Display the notices in the WordPress admin.
+		 *
+		 * @since 1.2.0
+		 * @return void
+		 */
+		public function show_notices() {
+			$defaults = array(
+				'id'                         => '',      // Optional, Notice ID. If empty it set `astra-notices-id-<$array-index>`.
+				'type'                       => 'info',  // Optional, Notice type. Default `info`. Expected [info, warning, notice, error].
+				'message'                    => '',      // Optional, Message.
+				'show_if'                    => true,    // Optional, Show notice on custom condition. E.g. 'show_if' => if( is_admin() ) ? true, false, .
+				'repeat-notice-after'        => '',      // Optional, Dismiss-able notice time. It'll auto show after given time.
+				'display-notice-after'       => false,      // Optional, Dismiss-able notice time. It'll auto show after given time.
+				'class'                      => '',      // Optional, Additional notice wrapper class.
+				'priority'                   => 10,      // Priority of the notice.
+				'display-with-other-notices' => true,    // Should the notice be displayed if other notices  are being displayed from BSF_Admin_Notices.
+				'is_dismissible'             => true,
+				'capability'                 => 'manage_options', // User capability - This capability is required for the current user to see this notice.
+			);
+
+			// Count for the notices that are rendered.
+			$notices_displayed = 0;
+			$notices           = $this->get_notices();
+
+			foreach ( $notices as $key => $notice ) {
+				$notice = wp_parse_args( $notice, $defaults );
+
+				// Show notices only for users with `manage_options` cap.
+				if ( ! current_user_can( $notice['capability'] ) ) {
+					continue;
+				}
+
+				$notice['id']      = self::get_notice_id( $notice, $key );
+				$notice['classes'] = self::get_wrap_classes( $notice );
+
+				// Notices visible after transient expire.
+				if ( isset( $notice['show_if'] ) && true === $notice['show_if'] ) {
+
+					// don't display the notice if it is not supposed to be displayed with other notices.
+					if ( 0 !== $notices_displayed && false === $notice['display-with-other-notices'] ) {
+						continue;
+					}
+
+					if ( self::is_expired( $notice ) ) {
+
+						self::markup( $notice );
+						++$notices_displayed;
+					}
+				}
+			}
+		}
+
+		/**
+		 * Render a notice.
+		 *
+		 * @since 1.2.0
+		 * @param  array $notice Notice markup.
+		 * @return void
+		 */
+		public static function markup( $notice = array() ) {
+			wp_enqueue_script( 'astra-notices' );
+			wp_enqueue_style( 'astra-notices' );
+
+			// Dual-emit: legacy (astra_notice_*) + new (bsf_admin_notice_*) hooks for backward compat.
+			// Note: consumers hooking BOTH names for the same event will be called twice.
+			do_action( 'astra_notice_before_markup' );
+			do_action( 'bsf_admin_notice_before_markup' );
+
+			do_action( "astra_notice_before_markup_{$notice['id']}" );
+			do_action( "bsf_admin_notice_before_markup_{$notice['id']}" );
+
+			?>
+			<div id="<?php echo esc_attr( $notice['id'] ); ?>" class="<?php echo esc_attr( 'astra-notice-wrapper ' . $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
+				<div class="astra-notice-container">
+					<?php do_action( "astra_notice_inside_markup_{$notice['id']}" ); ?>
+					<?php do_action( "bsf_admin_notice_inside_markup_{$notice['id']}" ); ?>
+					<?php echo wp_kses_post( $notice['message'] ); ?>
+				</div>
+			</div>
+			<?php
+
+			do_action( "astra_notice_after_markup_{$notice['id']}" );
+			do_action( "bsf_admin_notice_after_markup_{$notice['id']}" );
+
+			do_action( 'astra_notice_after_markup' );
+			do_action( 'bsf_admin_notice_after_markup' );
+		}
+
+		/**
+		 * Get wrapper classes for a notice.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param  array $notice Notice arguments.
+		 * @return array       Notice wrapper classes.
+		 */
+		private static function get_wrap_classes( $notice ) {
+			$classes = array( 'astra-notice', 'notice' );
+
+			if ( $notice['is_dismissible'] ) {
+				$classes[] = 'is-dismissible';
+			}
+
+			$classes[] = $notice['class'];
+			if ( isset( $notice['type'] ) && '' !== $notice['type'] ) {
+				$classes[] = 'notice-' . $notice['type'];
+			}
+
+			return esc_attr( implode( ' ', $classes ) );
+		}
+
+		/**
+		 * Get HTML ID for a given notice.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param  array $notice Notice arguments.
+		 * @param  int   $key    Notice array index.
+		 * @return string HTML if for the notice.
+		 */
+		private static function get_notice_id( $notice, $key ) {
+			if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
+				return $notice['id'];
+			}
+
+			return 'astra-notices-id-' . $key;
+		}
+
+		/**
+		 * Check if the notice is expires.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param  array $notice Notice arguments.
+		 * @return boolean
+		 */
+		private static function is_expired( $notice ) {
+			$transient_status = get_transient( $notice['id'] );
+
+			if ( false === $transient_status ) {
+
+				if ( isset( $notice['display-notice-after'] ) && false !== $notice['display-notice-after'] ) {
+
+					if ( 'delayed-notice' !== get_user_meta( get_current_user_id(), $notice['id'], true ) &&
+						'notice-dismissed' !== get_user_meta( get_current_user_id(), $notice['id'], true ) ) {
+						set_transient( $notice['id'], 'delayed-notice', $notice['display-notice-after'] );
+						update_user_meta( get_current_user_id(), $notice['id'], 'delayed-notice' );
+
+						return false;
+					}
+				}
+
+				// Check the user meta status if current notice is dismissed or delay completed.
+				$meta_status = get_user_meta( get_current_user_id(), $notice['id'], true );
+
+				if ( empty( $meta_status ) || 'delayed-notice' === $meta_status ) {
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+		/**
+		 * Get base URL for the library assets.
+		 *
+		 * @return mixed URL.
+		 */
+		public static function get_url() {
+			$path      = wp_normalize_path( dirname( __FILE__ ) ); // phpcs:ignore Modernize.FunctionCalls.Dirname.FileConstant
+			$theme_dir = wp_normalize_path( get_template_directory() );
+
+			if ( false !== strpos( $path, $theme_dir ) ) {
+				return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
+			} else {
+				return plugin_dir_url( __FILE__ );
+			}
+		}
+	}
+
+	/**
+	 * Kicking this off by calling 'get_instance()' method
+	 */
+	BSF_Admin_Notices::get_instance();
+
+endif;
+
+// Backward compatibility alias for bsf-analytics library and third-party plugins
+// that still reference the old class name. Safe to remove once all consumers
+// are updated.
+if ( ! class_exists( 'Astra_Notices' ) ) {
+	class_alias( 'BSF_Admin_Notices', 'Astra_Notices' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.class_aliasFound
+}
--- a/presto-player/vendor/composer/autoload_real.php
+++ b/presto-player/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInit31d215ca8da9a340c7a9d509021891f7
+class ComposerAutoloaderInit402153a10addfafa2a15d15dd2dd7351
 {
     private static $loader;

@@ -24,16 +24,16 @@

         require __DIR__ . '/platform_check.php';

-        spl_autoload_register(array('ComposerAutoloaderInit31d215ca8da9a340c7a9d509021891f7', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit402153a10addfafa2a15d15dd2dd7351', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(__DIR__));
-        spl_autoload_unregister(array('ComposerAutoloaderInit31d215ca8da9a340c7a9d509021891f7', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit402153a10addfafa2a15d15dd2dd7351', 'loadClassLoader'));

         require __DIR__ . '/autoload_static.php';
-        call_user_func(ComposerAutoloadComposerStaticInit31d215ca8da9a340c7a9d509021891f7::getInitializer($loader));
+        call_user_func(ComposerAutoloadComposerStaticInit402153a10addfafa2a15d15dd2dd7351::getInitializer($loader));

         $loader->register(true);

-        $filesToLoad = ComposerAutoloadComposerStaticInit31d215ca8da9a340c7a9d509021891f7::$files;
+        $filesToLoad = ComposerAutoloadComposerStaticInit402153a10addfafa2a15d15dd2dd7351::$files;
         $requireFile = Closure::bind(static function ($fileIdentifier, $file) {
             if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
                 $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
--- a/presto-player/vendor/composer/autoload_static.php
+++ b/presto-player/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@

 namespace ComposerAutoload;

-class ComposerStaticInit31d215ca8da9a340c7a9d509021891f7
+class ComposerStaticInit402153a10addfafa2a15d15dd2dd7351
 {
     public static $files = array (
         'b506a6b9998b6104a0eae10a34d50f61' => __DIR__ . '/../..' . '/inc/support.php',
@@ -363,9 +363,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit31d215ca8da9a340c7a9d509021891f7::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit31d215ca8da9a340c7a9d509021891f7::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInit31d215ca8da9a340c7a9d509021891f7::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInit402153a10addfafa2a15d15dd2dd7351::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit402153a10addfafa2a15d15dd2dd7351::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInit402153a10addfafa2a15d15dd2dd7351::$classMap;

         }, null, ClassLoader::class);
     }
--- a/presto-player/vendor/composer/installed.php
+++ b/presto-player/vendor/composer/installed.php
@@ -1,9 +1,9 @@
 <?php return array(
     'root' => array(
         'name' => 'course/player',
-        'pretty_version' => 'v4.1.3',
-        'version' => '4.1.3.0',
-        'reference' => 'd6fcaa8858ec6c16fad1b9bd4d0334b46f770dd7',
+        'pretty_version' => 'v4.1.4',
+        'version' => '4.1.4.0',
+        'reference' => 'd99b978faa35f005c5df5303cea0699193b63701',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -11,18 +11,18 @@
     ),
     'versions' => array(
         'brainstormforce/astra-notices' => array(
-            'pretty_version' => '1.1.14',
-            'version' => '1.1.14.0',
-            'reference' => '0b28968693e979a3a7920bc527d1d56e9b7953f5',
+            'pretty_version' => '1.2.1',
+            'version' => '1.2.1.0',
+            'reference' => '79430df6cc3bf97efeb287989c1c4a848dbd8039',
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../brainstormforce/astra-notices',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'brainstormforce/bsf-analytics' => array(
-            'pretty_version' => '1.1.24',
-            'version' => '1.1.24.0',
-            'reference' => '32bee98831416cf813de903eb74f5ddf52176479',
+            'pretty_version' => '1.1.26',
+            'version' => '1.1.26.0',
+            'reference' => 'e9079f78d4cf7a3e85ea61e94f842e2f8e00ee68',
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../inc/lib/bsf-analytics',
             'aliases' => array(),
@@ -47,9 +47,9 @@
             'dev_requirement' => false,
         ),
         'course/player' => array(
-            'pretty_version' => 'v4.1.3',
-            'version' => '4.1.3.0',
-            'reference' => 'd6fcaa8858ec6c16fad1b9bd4d0334b46f770dd7',
+            'pretty_version' => 'v4.1.4',
+            'version' => '4.1.4.0',
+            'reference' => 'd99b978faa35f005c5df5303cea0699193b63701',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),

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-45442 - The Ultimate Video Player For WordPress – by Presto Player <= 4.1.3 - Missing Authorization

// Configurable target
$target_url = 'http://example.com'; // Set the base URL of the WordPress site

// The shortcode [presto_player id={ID}] renders the video even for private Media Hub posts.
// We need to find a private Media Hub post ID. For demonstration, we'll guess or enumerate small IDs.
// In practice, an attacker would use a different method to obtain the ID (e.g., brute force low IDs).

// Example: check post ID 1
$post_id = 1;

// Construct a request that includes the shortcode in a way that gets rendered.
// The simplest approach is to create a new post with the shortcode, but that requires authentication.
// An alternative is to use the WordPress REST API or a page that allows shortcode execution without auth.
// For PoC purposes, assume the attacker can inject the shortcode into a publicly accessible page or widget.
// We'll simulate the request by directly calling the shortcode callback via the REST API or admin-ajax.

// Direct approach: Many WordPress plugins expose shortcodes via AJAX for previews.
// Presto Player does not have a built-in AJAX endpoint for shortcode rendering.
// However, the shortcode itself can be placed in a comment, widget, or any content area that processes shortcodes.
// For a stand-alone PoC, we'll demonstrate the underlying request that fetches the video block.

// Since there is no public endpoint to trigger the shortcode without auth, the real attack relies on the attacker
// having the ability to place the shortcode somewhere that gets executed (e.g., a post they authored, a comment, etc.).
// Given this constraint, we cannot provide a fully self-contained PoC that works out of the box.
// The following is a conceptual demonstration showing how an authenticated attacker (or one with insertion capability)
// would exploit it.

echo "CVE-2026-45442 Proof of Conceptn";
echo "This vulnerability requires the ability to insert the [presto_player id=X] shortcode into content that gets rendered.n";
echo "Once inserted, the shortcode will output the video player HTML even for private Media Hub posts.n";
echo "To test manually:n";
echo "1. Log in as a subscriber (or any user with post creation privileges).n";
echo "2. Create a new post with the shortcode: [presto_player id=123] (replace 123 with a known private Media Hub post ID).n";
echo "3. View the published post as an unauthenticated user. The private video content will be displayed.n";
echo "nNote: The fix in version 4.1.4 adds a permission check that requires the 'read_post' capability, making this attack fail for non-admin users.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