diff --git a/composer.json b/composer.json index 2596608f..9756d609 100644 --- a/composer.json +++ b/composer.json @@ -54,7 +54,6 @@ "remotelyliving/php-dns": "^4.3.0", "jasny/sso": "^0.4.2", "nyholm/psr7": "^1.8.0", - "symfony/cache": "^5.4.29", "scssphp/scssphp": "^1.11.1", "cweagans/composer-patches": "^1.7", "woocommerce/action-scheduler": "^3.9.1", diff --git a/composer.lock b/composer.lock index bfcdcda1..10d2c6eb 100644 --- a/composer.lock +++ b/composer.lock @@ -2722,54 +2722,6 @@ }, "time": "2016-08-06T20:24:11+00:00" }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, { "name": "psr/event-dispatcher", "version": "1.0.0", @@ -3550,182 +3502,6 @@ }, "time": "2025-08-27T19:32:42+00:00" }, - { - "name": "symfony/cache", - "version": "v5.4.46", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", - "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^4.4|^5.0|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" - }, - "provide": { - "psr/cache-implementation": "1.0|2.0", - "psr/simple-cache-implementation": "1.0|2.0", - "symfony/cache-implementation": "1.0|2.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6|^2.0", - "doctrine/dbal": "^2.13.1|^3|^4", - "predis/predis": "^1.1|^2.0", - "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.46" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-04T11:43:55+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v2.5.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/517c3a3619dadfa6952c4651767fcadffb4df65e", - "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0|^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "2.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, { "name": "symfony/deprecation-contracts", "version": "v2.5.4", @@ -3957,86 +3733,6 @@ ], "time": "2024-09-25T14:11:13+00:00" }, - { - "name": "symfony/polyfill-php73", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, { "name": "symfony/polyfill-php80", "version": "v1.33.0", @@ -4351,170 +4047,7 @@ "type": "github" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-07-08T02:45:35+00:00" - }, - { - "name": "symfony/polyfill-php84", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-24T13:30:11+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "2.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -4522,36 +4055,41 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { - "name": "symfony/var-exporter", - "version": "v5.4.45", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "862700068db0ddfd8c5b850671e029a90246ec75" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/862700068db0ddfd8c5b850671e029a90246ec75", - "reference": "862700068db0ddfd8c5b850671e029a90246ec75", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\VarExporter\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4568,18 +4106,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.4.45" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -4590,12 +4126,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { "name": "webmozart/assert", @@ -7534,6 +7074,54 @@ ], "time": "2026-01-27T05:45:00+00:00" }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, { "name": "react/promise", "version": "v3.3.0", @@ -9494,6 +9082,169 @@ ], "time": "2024-12-23T08:48:59+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, { "name": "symfony/string", "version": "v5.4.47", diff --git a/inc/class-maintenance-mode.php b/inc/class-maintenance-mode.php index 1c70c437..a87df21e 100644 --- a/inc/class-maintenance-mode.php +++ b/inc/class-maintenance-mode.php @@ -136,9 +136,9 @@ public static function check_maintenance_mode() { * Callback button admin toggle maintenance mode. * * @since 2.0.0 - * @return mixed + * @return void */ - public function toggle_maintenance_mode() { + public function toggle_maintenance_mode(): void { if ( ! check_ajax_referer('wu_toggle_maintenance_mode', '_wpnonce', false)) { wp_send_json_error( diff --git a/inc/sso/class-sso.php b/inc/sso/class-sso.php index 333c4ea6..819978e5 100644 --- a/inc/sso/class-sso.php +++ b/inc/sso/class-sso.php @@ -24,8 +24,6 @@ use Jasny\SSO\Server\BrokerException; use Jasny\SSO\Broker\NotAttachedException; use Nyholm\Psr7\Factory\Psr17Factory; -use Symfony\Component\Cache\Adapter\FilesystemAdapter; -use Symfony\Component\Cache\Psr16Cache; use WP_Ultimo\SSO\Exception\SSO_Exception; use WP_Ultimo\SSO\Exception\SSO_Session_Exception; @@ -46,7 +44,7 @@ class SSO { * The cache system for sessions. * * @since 2.0.11 - * @var Psr16Cache + * @var \Psr\SimpleCache\CacheInterface */ protected $cache; @@ -917,10 +915,8 @@ public function salt() { public function cache() { if (null === $this->cache) { - // the PSR-6 cache object that you want to use - $psr6_cache = new FilesystemAdapter(); - - $this->cache = new Psr16Cache($psr6_cache); + // Use WordPress transients-based PSR-16 cache implementation + $this->cache = new WordPress_Simple_Cache('wu_sso_'); } return apply_filters('wu_sso_cache', $this->cache, $this); diff --git a/inc/sso/class-wordpress-simple-cache.php b/inc/sso/class-wordpress-simple-cache.php new file mode 100644 index 00000000..3720825e --- /dev/null +++ b/inc/sso/class-wordpress-simple-cache.php @@ -0,0 +1,239 @@ +prefix = $prefix; + } + + /** + * Fetches a value from the cache. + * + * @param string $key The unique key of this item in the cache. + * @param mixed $default Default value to return if the key does not exist. + * @return mixed The value of the item from the cache, or $default in case of cache miss. + */ + public function get($key, $default = null) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound + $this->validate_key($key); + $raw = get_site_transient($this->prefix . $key); + + if (false === $raw || ! is_array($raw) || ! array_key_exists('v', $raw)) { + return $default; + } + + return $raw['v']; + } + + /** + * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. + * + * @param string $key The key of the item to store. + * @param mixed $value The value of the item to store. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. + * @return bool True on success and false on failure. + */ + public function set($key, $value, $ttl = null) { + $this->validate_key($key); + $expiration = $this->convert_ttl_to_seconds($ttl); + + // Non-null TTL <= 0 means item should expire immediately (PSR-16). + if (null !== $ttl && $expiration <= 0) { + $expiration = 1; + } + + return set_site_transient($this->prefix . $key, ['v' => $value], $expiration); + } + + /** + * Delete an item from the cache by its unique key. + * + * @param string $key The unique cache key of the item to delete. + * @return bool True if the item was successfully removed. False if there was an error. + */ + public function delete($key) { + $this->validate_key($key); + return delete_site_transient($this->prefix . $key); + } + + /** + * Wipes clean the entire cache's keys. + * + * @return bool True on success and false on failure. + */ + public function clear() { + global $wpdb; + + // Get all transient keys with our prefix and delete each via the WordPress API, + // which handles both DB and object cache correctly. + $like_pattern = $wpdb->esc_like('_site_transient_' . $this->prefix) . '%'; + $meta_keys = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->prepare( + "SELECT meta_key FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s", + $like_pattern + ) + ); + + foreach ($meta_keys as $meta_key) { + $transient_name = substr($meta_key, strlen('_site_transient_')); + delete_site_transient($transient_name); + } + + return true; + } + + /** + * Obtains multiple cache items by their unique keys. + * + * @param iterable $keys A list of keys that can be obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * @return iterable A list of key => value pairs. + */ + public function getMultiple($keys, $default = null) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound + $this->validate_iterable($keys); + $values = array(); + + foreach ($keys as $key) { + $values[ $key ] = $this->get($key, $default); + } + + return $values; + } + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. + * @return bool True on success and false on failure. + */ + public function setMultiple($values, $ttl = null) { + $this->validate_iterable($values); + $success = true; + + foreach ($values as $key => $value) { + if (! $this->set($key, $value, $ttl)) { + $success = false; + } + } + + return $success; + } + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * @return bool True if the items were successfully removed. False if there was an error. + */ + public function deleteMultiple($keys) { + $this->validate_iterable($keys); + $success = true; + + foreach ($keys as $key) { + if (! $this->delete($key)) { + $success = false; + } + } + + return $success; + } + + /** + * Determines whether an item is present in the cache. + * + * @param string $key The cache item key. + * @return bool + */ + public function has($key) { + $this->validate_key($key); + $raw = get_site_transient($this->prefix . $key); + return false !== $raw && is_array($raw) && array_key_exists('v', $raw); + } + + // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing, WordPress.Security.EscapeOutput.ExceptionNotEscaped + /** + * Validate a cache key per PSR-16 requirements. + * + * @param mixed $key Key to validate. + * @throws \Psr\SimpleCache\InvalidArgumentException If key is invalid. + */ + protected function validate_key($key): void { + if (! is_string($key) || '' === $key) { + throw new class('Cache key must be a non-empty string') extends \RuntimeException implements InvalidArgumentException {}; } + + if (strlen($key) > 64) { + throw new class('Cache key must not exceed 64 characters') extends \RuntimeException implements InvalidArgumentException {}; } + + if (strpbrk($key, self::RESERVED_KEY_CHARS) !== false) { + throw new class('Cache key contains reserved characters') extends \RuntimeException implements InvalidArgumentException {}; } + } + + /** + * Validate that an argument is iterable (array or Traversable) per PSR-16. + * + * @param mixed $items Value to validate. + * @throws \Psr\SimpleCache\InvalidArgumentException If argument is not iterable. + */ + protected function validate_iterable($items): void { + if (! is_array($items) && ! ($items instanceof \Traversable)) { + throw new class('Argument must be an array or Traversable') extends \RuntimeException implements InvalidArgumentException {}; } + } + + // phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.Missing, WordPress.Security.EscapeOutput.ExceptionNotEscaped + + /** + * Convert TTL to seconds. + * + * @param null|int|\DateInterval $ttl The TTL value. + * @return int Expiration time in seconds. + */ + protected function convert_ttl_to_seconds($ttl) { + if (null === $ttl) { + return 0; // No expiration. + } + + if ($ttl instanceof \DateInterval) { + $now = new \DateTime(); + $future = $now->add($ttl); + return $future->getTimestamp() - $now->getTimestamp(); + } + + return (int) $ttl; + } +} diff --git a/tests/WP_Ultimo/SSO/WordPress_Simple_Cache_Integration_Test.php b/tests/WP_Ultimo/SSO/WordPress_Simple_Cache_Integration_Test.php new file mode 100644 index 00000000..dedb3c89 --- /dev/null +++ b/tests/WP_Ultimo/SSO/WordPress_Simple_Cache_Integration_Test.php @@ -0,0 +1,357 @@ +clear(); + + $result = $cache->set('key1', 'value1'); + self::assert(true === $result, 'set() returns true'); + + $value = $cache->get('key1'); + self::assert('value1' === $value, 'get() returns correct value'); + + $cache->clear(); + } + + /** + * Test get with default value. + */ + private static function test_get_with_default() { + $cache = new WordPress_Simple_Cache('test_'); + $cache->clear(); + + $value = $cache->get('nonexistent', 'default'); + self::assert('default' === $value, 'get() returns default for nonexistent key'); + + $cache->clear(); + } + + /** + * Test delete operation. + */ + private static function test_delete() { + $cache = new WordPress_Simple_Cache('test_'); + $cache->clear(); + + $cache->set('delete_key', 'delete_value'); + $result = $cache->delete('delete_key'); + self::assert(true === $result, 'delete() returns true'); + + $value = $cache->get('delete_key'); + self::assert(null === $value, 'Deleted key returns null'); + + $cache->clear(); + } + + /** + * Test has operation. + */ + private static function test_has() { + $cache = new WordPress_Simple_Cache('test_'); + $cache->clear(); + + $has = $cache->has('has_key'); + self::assert(false === $has, 'has() returns false for nonexistent key'); + + $cache->set('has_key', 'has_value'); + $has = $cache->has('has_key'); + self::assert(true === $has, 'has() returns true for existing key'); + + $cache->clear(); + } + + /** + * Test clear operation. + */ + private static function test_clear() { + $cache = new WordPress_Simple_Cache('test_'); + $cache->clear(); + + $cache->set('clear1', 'value1'); + $cache->set('clear2', 'value2'); + + $result = $cache->clear(); + self::assert(true === $result, 'clear() returns true'); + + $has1 = $cache->has('clear1'); + $has2 = $cache->has('clear2'); + self::assert(false === $has1 && false === $has2, 'clear() removes all keys'); + + $cache->clear(); + } + + /** + * Test multiple operations. + */ + private static function test_multiple_operations() { + $cache = new WordPress_Simple_Cache('test_'); + $cache->clear(); + + // Set multiple. + $values = array( + 'm1' => 'v1', + 'm2' => 'v2', + ); + $result = $cache->setMultiple($values); + self::assert(true === $result, 'setMultiple() returns true'); + + // Get multiple. + $retrieved = $cache->getMultiple(array('m1', 'm2', 'm3'), 'default'); + self::assert( + 'v1' === $retrieved['m1'] && 'v2' === $retrieved['m2'] && 'default' === $retrieved['m3'], + 'getMultiple() returns correct values' + ); + + // Delete multiple. + $result = $cache->deleteMultiple(array('m1', 'm2')); + self::assert(true === $result, 'deleteMultiple() returns true'); + + $cache->clear(); + } + + /** + * Test different data types. + */ + private static function test_data_types() { + $cache = new WordPress_Simple_Cache('test_'); + $cache->clear(); + + // String. + $cache->set('string', 'test'); + self::assert('test' === $cache->get('string'), 'Stores and retrieves string'); + + // Integer. + $cache->set('int', 42); + self::assert(42 === $cache->get('int'), 'Stores and retrieves integer'); + + // Float. + $cache->set('float', 3.14); + self::assert(3.14 === $cache->get('float'), 'Stores and retrieves float'); + + // Boolean. + $cache->set('bool', true); + self::assert(true === $cache->get('bool'), 'Stores and retrieves boolean'); + + // Array. + $array = array('foo' => 'bar'); + $cache->set('array', $array); + self::assert($array === $cache->get('array'), 'Stores and retrieves array'); + + $cache->clear(); + } + + /** + * Test prefix isolation. + */ + private static function test_prefix_isolation() { + $cache1 = new WordPress_Simple_Cache('prefix1_'); + $cache2 = new WordPress_Simple_Cache('prefix2_'); + + $cache1->clear(); + $cache2->clear(); + + $cache1->set('key', 'value1'); + $cache2->set('key', 'value2'); + + self::assert( + 'value1' === $cache1->get('key') && 'value2' === $cache2->get('key'), + 'Different prefixes isolate cache data' + ); + + $cache1->clear(); + $cache2->clear(); + } + + /** + * Test TTL handling. + */ + private static function test_ttl_handling() { + $cache = new WordPress_Simple_Cache('test_'); + $cache->clear(); + + // Integer TTL. + $result = $cache->set('ttl_int', 'value', 60); + self::assert(true === $result, 'set() with integer TTL returns true'); + + // Null TTL. + $result = $cache->set('ttl_null', 'value', null); + self::assert(true === $result, 'set() with null TTL returns true'); + + // DateInterval TTL. + $interval = new \DateInterval('PT1H'); + $result = $cache->set('ttl_interval', 'value', $interval); + self::assert(true === $result, 'set() with DateInterval TTL returns true'); + + $cache->clear(); + } +} + +// Auto-run if this file is executed directly (not included by PHPUnit). +if (php_sapi_name() === 'cli' && ! defined('PHPUNIT_COMPOSER_INSTALL')) { + // Load autoloader. + require_once dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php'; + require_once dirname(dirname(dirname(__DIR__))) . '/inc/sso/class-wordpress-simple-cache.php'; + + WordPress_Simple_Cache_Integration_Test::run_all_tests(); +} diff --git a/tests/WP_Ultimo/SSO/WordPress_Simple_Cache_Test.php b/tests/WP_Ultimo/SSO/WordPress_Simple_Cache_Test.php new file mode 100644 index 00000000..df6aef48 --- /dev/null +++ b/tests/WP_Ultimo/SSO/WordPress_Simple_Cache_Test.php @@ -0,0 +1,381 @@ +cache = new WordPress_Simple_Cache($this->prefix); + + // Clean up any existing test cache entries. + $this->cache->clear(); + } + + /** + * Tear down test fixtures. + */ + protected function tearDown(): void { + // Clean up test cache entries. + if ($this->cache) { + $this->cache->clear(); + } + + parent::tearDown(); + } + + /** + * Test that the cache implements PSR-16 CacheInterface. + */ + public function test_implements_psr16_cache_interface(): void { + $this->assertInstanceOf(CacheInterface::class, $this->cache); + } + + /** + * Test basic get/set operations. + */ + public function test_get_and_set(): void { + $key = 'test_key'; + $value = 'test_value'; + + // Set a value. + $result = $this->cache->set($key, $value); + $this->assertTrue($result, 'set() should return true on success'); + + // Get the value. + $retrieved = $this->cache->get($key); + $this->assertSame($value, $retrieved, 'Retrieved value should match set value'); + } + + /** + * Test get with default value when key doesn't exist. + */ + public function test_get_with_default_value(): void { + $key = 'nonexistent_key'; + $default = 'default_value'; + + $result = $this->cache->get($key, $default); + $this->assertSame($default, $result, 'Should return default value for nonexistent key'); + } + + /** + * Test delete operation. + */ + public function test_delete(): void { + $key = 'test_delete_key'; + $value = 'test_value'; + + // Set and verify. + $this->cache->set($key, $value); + $this->assertSame($value, $this->cache->get($key)); + + // Delete and verify. + $result = $this->cache->delete($key); + $this->assertTrue($result, 'delete() should return true on success'); + + // Verify it's gone. + $this->assertNull($this->cache->get($key), 'Deleted key should return null'); + } + + /** + * Test has() method. + */ + public function test_has(): void { + $key = 'test_has_key'; + $value = 'test_value'; + + // Key shouldn't exist initially. + $this->assertFalse($this->cache->has($key), 'has() should return false for nonexistent key'); + + // Set value. + $this->cache->set($key, $value); + + // Now it should exist. + $this->assertTrue($this->cache->has($key), 'has() should return true for existing key'); + } + + /** + * Test clear operation. + */ + public function test_clear(): void { + // Set multiple values. + $this->cache->set('key1', 'value1'); + $this->cache->set('key2', 'value2'); + $this->cache->set('key3', 'value3'); + + // Verify they exist. + $this->assertTrue($this->cache->has('key1')); + $this->assertTrue($this->cache->has('key2')); + $this->assertTrue($this->cache->has('key3')); + + // Clear cache. + $result = $this->cache->clear(); + $this->assertTrue($result, 'clear() should return true on success'); + + // Verify all are gone. + $this->assertFalse($this->cache->has('key1')); + $this->assertFalse($this->cache->has('key2')); + $this->assertFalse($this->cache->has('key3')); + } + + /** + * Test getMultiple operation. + */ + public function test_get_multiple(): void { + // Set multiple values. + $this->cache->set('key1', 'value1'); + $this->cache->set('key2', 'value2'); + $this->cache->set('key3', 'value3'); + + // Get multiple. + $keys = array('key1', 'key2', 'nonexistent'); + $values = $this->cache->getMultiple($keys, 'default'); + + $this->assertIsArray($values); + $this->assertSame('value1', $values['key1']); + $this->assertSame('value2', $values['key2']); + $this->assertSame('default', $values['nonexistent']); + } + + /** + * Test setMultiple operation. + */ + public function test_set_multiple(): void { + $values = array( + 'multi1' => 'value1', + 'multi2' => 'value2', + 'multi3' => 'value3', + ); + + // Set multiple. + $result = $this->cache->setMultiple($values); + $this->assertTrue($result, 'setMultiple() should return true on success'); + + // Verify all were set. + $this->assertSame('value1', $this->cache->get('multi1')); + $this->assertSame('value2', $this->cache->get('multi2')); + $this->assertSame('value3', $this->cache->get('multi3')); + } + + /** + * Test deleteMultiple operation. + */ + public function test_delete_multiple(): void { + // Set multiple values. + $this->cache->set('del1', 'value1'); + $this->cache->set('del2', 'value2'); + $this->cache->set('del3', 'value3'); + + // Delete multiple. + $keys = array('del1', 'del2'); + $result = $this->cache->deleteMultiple($keys); + $this->assertTrue($result, 'deleteMultiple() should return true on success'); + + // Verify deleted keys are gone. + $this->assertNull($this->cache->get('del1')); + $this->assertNull($this->cache->get('del2')); + + // Verify remaining key still exists. + $this->assertSame('value3', $this->cache->get('del3')); + } + + /** + * Test TTL with integer seconds. + */ + public function test_ttl_with_integer_seconds(): void { + $key = 'ttl_test_key'; + $value = 'ttl_test_value'; + $ttl = 60; // 60 seconds. + + $result = $this->cache->set($key, $value, $ttl); + $this->assertTrue($result); + + // Value should be retrievable. + $retrieved = $this->cache->get($key); + $this->assertSame($value, $retrieved); + + // Note: We can't easily test expiration in unit tests without time manipulation. + // The transient is set with the correct expiration time. + } + + /** + * Test TTL with null (no expiration). + */ + public function test_ttl_with_null(): void { + $key = 'no_ttl_key'; + $value = 'no_ttl_value'; + + $result = $this->cache->set($key, $value, null); + $this->assertTrue($result); + + // Value should be retrievable. + $retrieved = $this->cache->get($key); + $this->assertSame($value, $retrieved); + } + + /** + * Test TTL with DateInterval. + */ + public function test_ttl_with_date_interval(): void { + $key = 'dateinterval_key'; + $value = 'dateinterval_value'; + $interval = new \DateInterval('PT1H'); // 1 hour. + + $result = $this->cache->set($key, $value, $interval); + $this->assertTrue($result); + + // Value should be retrievable. + $retrieved = $this->cache->get($key); + $this->assertSame($value, $retrieved); + } + + /** + * Test storing different data types. + */ + public function test_stores_different_data_types(): void { + // String. + $this->cache->set('string', 'test string'); + $this->assertSame('test string', $this->cache->get('string')); + + // Integer. + $this->cache->set('integer', 42); + $this->assertSame(42, $this->cache->get('integer')); + + // Float. + $this->cache->set('float', 3.14); + $this->assertSame(3.14, $this->cache->get('float')); + + // Boolean. + $this->cache->set('bool_true', true); + $this->assertTrue($this->cache->get('bool_true')); + + $this->cache->set('bool_false', false); + $this->assertFalse($this->cache->get('bool_false')); + + // Array. + $array = array('foo' => 'bar', 'baz' => 123); + $this->cache->set('array', $array); + $this->assertSame($array, $this->cache->get('array')); + + // Object. + $object = new \stdClass(); + $object->prop = 'value'; + $this->cache->set('object', $object); + $retrieved = $this->cache->get('object'); + $this->assertInstanceOf(\stdClass::class, $retrieved); + $this->assertSame('value', $retrieved->prop); + } + + /** + * Test cache prefix isolation. + */ + public function test_cache_prefix_isolation(): void { + $cache1 = new WordPress_Simple_Cache('prefix1_'); + $cache2 = new WordPress_Simple_Cache('prefix2_'); + + // Set same key in both caches with different values. + $cache1->set('test', 'value1'); + $cache2->set('test', 'value2'); + + // Each should retrieve its own value. + $this->assertSame('value1', $cache1->get('test')); + $this->assertSame('value2', $cache2->get('test')); + + // Clear one cache shouldn't affect the other. + $cache1->clear(); + $this->assertNull($cache1->get('test')); + $this->assertSame('value2', $cache2->get('test')); + + // Cleanup. + $cache2->clear(); + } + + /** + * Test that cache works with SSO integration. + */ + public function test_cache_integration_with_sso(): void { + // Create cache instance with SSO prefix. + $sso_cache = new WordPress_Simple_Cache('wu_sso_'); + + // Simulate SSO session data. + $broker_id = 'test_broker_123'; + $session_id = 'session_abc_456'; + $user_id = 42; + + // Store session mapping (broker + token -> session_id). + $cache_key = $broker_id . '_' . 'test_token'; + $sso_cache->set($cache_key, $session_id, 3600); // 1 hour TTL. + + // Verify retrieval. + $retrieved = $sso_cache->get($cache_key); + $this->assertSame($session_id, $retrieved); + + // Simulate cleanup. + $sso_cache->delete($cache_key); + $this->assertNull($sso_cache->get($cache_key)); + } + + /** + * Test compatibility with jasny/sso CacheInterface usage. + */ + public function test_jasny_sso_compatibility(): void { + // Jasny SSO uses PSR-16 CacheInterface methods. + $cache = new WordPress_Simple_Cache('jasny_test_'); + + // Simulate broker token storage. + $broker_id = 'broker_123'; + $token = 'token_abc'; + $key = 'sso_' . $broker_id . '_' . $token; + $value = 'session_xyz'; + $ttl = 180; // 3 minutes, typical for SSO. + + // Store. + $result = $cache->set($key, $value, $ttl); + $this->assertTrue($result); + + // Retrieve. + $retrieved = $cache->get($key); + $this->assertSame($value, $retrieved); + + // Verify has(). + $this->assertTrue($cache->has($key)); + + // Delete. + $cache->delete($key); + $this->assertFalse($cache->has($key)); + + // Cleanup. + $cache->clear(); + } +}