diff --git a/.gitignore b/.gitignore
index 037b890..26e7770 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,14 @@ composer.phar
/.idea/
/build
+# PHPUnit
+.phpunit.result.cache
+
+# Playwright E2E artifacts
+tests/pw/test-results/
+tests/pw/playwright-report/
+tests/pw/.last-run.json
+
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
diff --git a/CONTEXT.md b/CONTEXT.md
index a10131b..e1e1b55 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -27,3 +27,12 @@ A glossary of domain terms for this plugin. Keep implementation details out.
(getdokan/plugin-ui). Components render inside a `ThemeProvider` scope.
TryAura consumes it with default tokens: no custom theme, no dark mode, and
no Shadow DOM for now.
+
+## Features
+
+- **Enhancer** — The admin feature that turns a store's existing product photos into
+ new or improved visuals from inside the WordPress media modal: AI images for all
+ users, and AI videos for Pro. Not the customer-facing feature — that is Try-On.
+- **Try-On** — The customer-facing feature on WooCommerce single-product pages where a
+ shopper supplies a photo (upload or camera) and sees themselves composited wearing or
+ using the product. Avoid "virtual mirror," a marketing phrase for the same thing.
diff --git a/composer.json b/composer.json
index a5e9d5c..5ad5ba8 100644
--- a/composer.json
+++ b/composer.json
@@ -9,6 +9,11 @@
"Dokan\\TryAura\\ThirdParty\\Packages\\": "lib/packages/"
}
},
+ "autoload-dev": {
+ "psr-4": {
+ "Dokan\\TryAura\\Test\\": "tests/php/src"
+ }
+ },
"authors": [
{
"name": "Dokan Inc.",
@@ -27,7 +32,11 @@
"squizlabs/php_codesniffer": "*",
"dealerdirect/phpcodesniffer-composer-installer": "*",
"league/container": "^4.2",
- "coenjacobs/mozart": "^0.7"
+ "coenjacobs/mozart": "^0.7",
+ "phpunit/phpunit": "^9.6",
+ "yoast/phpunit-polyfills": "^1.1",
+ "wp-phpunit/wp-phpunit": "^6.6",
+ "brain/monkey": "^2.6"
},
"config": {
"allow-plugins": {
@@ -51,6 +60,12 @@
],
"phpcbf": [
"vendor/bin/phpcbf -p"
+ ],
+ "test": [
+ "phpunit"
+ ],
+ "test-f": [
+ "phpunit --filter"
]
},
"extra": {
diff --git a/composer.lock b/composer.lock
index 340d251..6ac4f5b 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d40b5210142b6521e50448a3702701d8",
+ "content-hash": "762a4496a706a4d77a6180218c296369",
"packages": [
{
"name": "appsero/client",
@@ -62,6 +62,54 @@
}
],
"packages-dev": [
+ {
+ "name": "antecedent/patchwork",
+ "version": "2.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antecedent/patchwork.git",
+ "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antecedent/patchwork/zipball/8b6b235f405af175259c8f56aea5fc23ab9f03ce",
+ "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=4"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignas Rudaitis",
+ "email": "ignas.rudaitis@gmail.com"
+ }
+ ],
+ "description": "Method redefinition (monkey-patching) functionality for PHP.",
+ "homepage": "https://antecedent.github.io/patchwork/",
+ "keywords": [
+ "aop",
+ "aspect",
+ "interception",
+ "monkeypatching",
+ "redefinition",
+ "runkit",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/antecedent/patchwork/issues",
+ "source": "https://github.com/antecedent/patchwork/tree/2.2.3"
+ },
+ "time": "2025-09-17T09:00:56+00:00"
+ },
{
"name": "automattic/vipwpcs",
"version": "3.0.1",
@@ -116,6 +164,76 @@
},
"time": "2024-05-10T20:31:09+00:00"
},
+ {
+ "name": "brain/monkey",
+ "version": "2.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Brain-WP/BrainMonkey.git",
+ "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/ea3aeb3d559ba3c0930b3f4d210b665a4c044d83",
+ "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83",
+ "shasum": ""
+ },
+ "require": {
+ "antecedent/patchwork": "^2.1.17",
+ "mockery/mockery": "~1.3.6 || ~1.4.4 || ~1.5.1 || ^1.6.10",
+ "php": ">=5.6.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
+ "phpcompatibility/php-compatibility": "^9.3.0",
+ "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.49 || ^9.6.30"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev",
+ "dev-version/1": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "inc/api.php"
+ ],
+ "psr-4": {
+ "Brain\\Monkey\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Giuseppe Mazzapica",
+ "email": "giuseppe.mazzapica@gmail.com",
+ "homepage": "https://gmazzap.me",
+ "role": "Developer"
+ }
+ ],
+ "description": "Mocking utility for PHP functions and WordPress plugin API",
+ "keywords": [
+ "Monkey Patching",
+ "interception",
+ "mock",
+ "mock functions",
+ "mockery",
+ "patchwork",
+ "redefinition",
+ "runkit",
+ "test",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/Brain-WP/BrainMonkey/issues",
+ "source": "https://github.com/Brain-WP/BrainMonkey"
+ },
+ "time": "2026-02-05T09:22:14+00:00"
+ },
{
"name": "coenjacobs/mozart",
"version": "0.7.1",
@@ -270,6 +388,126 @@
],
"time": "2026-05-06T08:26:05+00:00"
},
+ {
+ "name": "doctrine/instantiator",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7",
+ "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.4"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^14",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5.58"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/instantiator/issues",
+ "source": "https://github.com/doctrine/instantiator/tree/2.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-05T06:47:08+00:00"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
+ "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "support": {
+ "issues": "https://github.com/hamcrest/hamcrest-php/issues",
+ "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1"
+ },
+ "time": "2025-04-30T06:54:44+00:00"
+ },
{
"name": "league/container",
"version": "4.2.5",
@@ -448,16 +686,16 @@
},
{
"name": "league/mime-type-detection",
- "version": "1.16.0",
+ "version": "1.17.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
- "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9"
+ "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9",
- "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9",
+ "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76",
+ "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76",
"shasum": ""
},
"require": {
@@ -467,7 +705,7 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"phpstan/phpstan": "^0.12.68",
- "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0"
+ "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0"
},
"type": "library",
"autoload": {
@@ -488,7 +726,7 @@
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
- "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0"
+ "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0"
},
"funding": [
{
@@ -500,200 +738,443 @@
"type": "tidelift"
}
],
- "time": "2024-09-21T08:32:55+00:00"
+ "time": "2026-07-09T11:49:27+00:00"
},
{
- "name": "phpcompatibility/php-compatibility",
- "version": "9.3.5",
+ "name": "mockery/mockery",
+ "version": "1.6.12",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ "url": "https://github.com/mockery/mockery.git",
+ "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699",
+ "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699",
"shasum": ""
},
"require": {
- "php": ">=5.3",
- "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ "hamcrest/hamcrest-php": "^2.0.1",
+ "lib-pcre": ">=7.0",
+ "php": ">=7.3"
},
"conflict": {
- "squizlabs/php_codesniffer": "2.6.2"
+ "phpunit/phpunit": "<8.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ "phpunit/phpunit": "^8.5 || ^9.6.17",
+ "symplify/easy-coding-standard": "^12.1.14"
},
- "suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ "type": "library",
+ "autoload": {
+ "files": [
+ "library/helpers.php",
+ "library/Mockery.php"
+ ],
+ "psr-4": {
+ "Mockery\\": "library/Mockery"
+ }
},
- "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL-3.0-or-later"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Wim Godden",
- "homepage": "https://github.com/wimg",
- "role": "lead"
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "https://github.com/padraic",
+ "role": "Author"
},
{
- "name": "Juliette Reinders Folmer",
- "homepage": "https://github.com/jrfnl",
- "role": "lead"
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "https://davedevelopment.co.uk",
+ "role": "Developer"
},
{
- "name": "Contributors",
- "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ "name": "Nathanael Esayeas",
+ "email": "nathanael.esayeas@protonmail.com",
+ "homepage": "https://github.com/ghostwriter",
+ "role": "Lead Developer"
}
],
- "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
- "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "description": "Mockery is a simple yet flexible PHP mock object framework",
+ "homepage": "https://github.com/mockery/mockery",
"keywords": [
- "compatibility",
- "phpcs",
- "standards"
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
],
"support": {
- "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
- "source": "https://github.com/PHPCompatibility/PHPCompatibility"
+ "docs": "https://docs.mockery.io/",
+ "issues": "https://github.com/mockery/mockery/issues",
+ "rss": "https://github.com/mockery/mockery/releases.atom",
+ "security": "https://github.com/mockery/mockery/security/advisories",
+ "source": "https://github.com/mockery/mockery"
},
- "time": "2019-12-27T09:44:58+00:00"
+ "time": "2024-05-16T03:13:13+00:00"
},
{
- "name": "phpcompatibility/phpcompatibility-paragonie",
- "version": "1.3.4",
+ "name": "myclabs/deep-copy",
+ "version": "1.13.4",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
- "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf"
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
- "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
"require": {
- "phpcompatibility/php-compatibility": "^9.0"
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
- "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
- "paragonie/random_compat": "dev-master",
- "paragonie/sodium_compat": "dev-master"
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
- "suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
},
- "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "Wim Godden",
- "role": "lead"
- },
- {
- "name": "Juliette Reinders Folmer",
- "role": "lead"
- }
+ "MIT"
],
- "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
- "homepage": "http://phpcompatibility.com/",
+ "description": "Create deep copies (clones) of your objects",
"keywords": [
- "compatibility",
- "paragonie",
- "phpcs",
- "polyfill",
- "standards",
- "static analysis"
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
],
"support": {
- "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
- "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy",
- "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
},
"funding": [
{
- "url": "https://github.com/PHPCompatibility",
- "type": "github"
- },
- {
- "url": "https://github.com/jrfnl",
- "type": "github"
- },
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "open_collective"
- },
- {
- "url": "https://thanks.dev/u/gh/phpcompatibility",
- "type": "thanks_dev"
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
}
],
- "time": "2025-09-19T17:43:28+00:00"
+ "time": "2025-08-01T08:46:24+00:00"
},
{
- "name": "phpcompatibility/phpcompatibility-wp",
- "version": "2.1.8",
+ "name": "nikic/php-parser",
+ "version": "v5.8.0",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
- "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa"
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa",
- "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
+ "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"shasum": ""
},
"require": {
- "phpcompatibility/php-compatibility": "^9.0",
- "phpcompatibility/phpcompatibility-paragonie": "^1.0",
- "squizlabs/php_codesniffer": "^3.3"
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
},
"require-dev": {
- "dealerdirect/phpcodesniffer-composer-installer": "^1.0"
- },
- "suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
},
- "type": "phpcodesniffer-standard",
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "LGPL-3.0-or-later"
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0"
+ },
+ "time": "2026-07-04T14:30:18+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpcompatibility/php-compatibility",
+ "version": "9.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ },
+ "conflict": {
+ "squizlabs/php_codesniffer": "2.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
],
"authors": [
{
"name": "Wim Godden",
+ "homepage": "https://github.com/wimg",
"role": "lead"
},
{
"name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
"role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
}
],
- "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibility"
+ },
+ "time": "2019-12-27T09:44:58+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-paragonie",
+ "version": "1.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+ "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
+ "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "paragonie/random_compat": "dev-master",
+ "paragonie/sodium_compat": "dev-master"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
"homepage": "http://phpcompatibility.com/",
"keywords": [
"compatibility",
+ "paragonie",
"phpcs",
+ "polyfill",
"standards",
- "static analysis",
- "wordpress"
+ "static analysis"
],
"support": {
- "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
- "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy",
- "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
+ "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
},
"funding": [
{
@@ -713,235 +1194,1735 @@
"type": "thanks_dev"
}
],
- "time": "2025-10-18T00:05:59+00:00"
+ "time": "2025-09-19T17:43:28+00:00"
},
{
- "name": "phpcsstandards/phpcsextra",
- "version": "1.5.0",
+ "name": "phpcompatibility/phpcompatibility-wp",
+ "version": "2.1.8",
"source": {
"type": "git",
- "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
- "reference": "b598aa890815b8df16363271b659d73280129101"
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
+ "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101",
- "reference": "b598aa890815b8df16363271b659d73280129101",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa",
+ "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa",
"shasum": ""
},
"require": {
- "php": ">=5.4",
- "phpcsstandards/phpcsutils": "^1.2.0",
- "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0",
+ "squizlabs/php_codesniffer": "^3.3"
},
"require-dev": {
- "php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.4.0",
- "phpcsstandards/phpcsdevcs": "^1.2.0",
- "phpcsstandards/phpcsdevtools": "^1.2.1",
- "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0"
},
- "type": "phpcodesniffer-standard",
- "extra": {
- "branch-alias": {
- "dev-stable": "1.x-dev",
- "dev-develop": "1.x-dev"
- }
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
},
+ "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
- "name": "Juliette Reinders Folmer",
- "homepage": "https://github.com/jrfnl",
+ "name": "Wim Godden",
"role": "lead"
},
{
- "name": "Contributors",
- "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
}
],
- "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
+ "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
+ "homepage": "http://phpcompatibility.com/",
"keywords": [
- "PHP_CodeSniffer",
- "phpcbf",
- "phpcodesniffer-standard",
+ "compatibility",
+ "phpcs",
+ "standards",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
+ "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCompatibility",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcompatibility",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-10-18T00:05:59+00:00"
+ },
+ {
+ "name": "phpcsstandards/phpcsextra",
+ "version": "1.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
+ "reference": "b598aa890815b8df16363271b659d73280129101"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101",
+ "reference": "b598aa890815b8df16363271b659d73280129101",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4",
+ "phpcsstandards/phpcsutils": "^1.2.0",
+ "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpcsstandards/phpcsdevcs": "^1.2.0",
+ "phpcsstandards/phpcsdevtools": "^1.2.1",
+ "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-stable": "1.x-dev",
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
+ }
+ ],
+ "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
+ "keywords": [
+ "PHP_CodeSniffer",
+ "phpcbf",
+ "phpcodesniffer-standard",
"phpcs",
"standards",
"static analysis"
],
"support": {
- "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
- "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
- "source": "https://github.com/PHPCSStandards/PHPCSExtra"
+ "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
+ "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHPCSExtra"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-11-12T23:06:57+00:00"
+ },
+ {
+ "name": "phpcsstandards/phpcsutils",
+ "version": "1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
+ "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55",
+ "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55",
+ "shasum": ""
+ },
+ "require": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
+ },
+ "require-dev": {
+ "ext-filter": "*",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpcsstandards/phpcsdevcs": "^1.2.0",
+ "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-stable": "1.x-dev",
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "PHPCSUtils/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
+ }
+ ],
+ "description": "A suite of utility functions for use with PHP_CodeSniffer",
+ "homepage": "https://phpcsutils.com/",
+ "keywords": [
+ "PHP_CodeSniffer",
+ "phpcbf",
+ "phpcodesniffer-standard",
+ "phpcs",
+ "phpcs3",
+ "phpcs4",
+ "standards",
+ "static analysis",
+ "tokens",
+ "utility"
+ ],
+ "support": {
+ "docs": "https://phpcsutils.com/",
+ "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
+ "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHPCSUtils"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-12-08T14:27:58+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "9.2.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5",
+ "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.19.1 || ^5.1.0",
+ "php": ">=7.3",
+ "phpunit/php-file-iterator": "^3.0.6",
+ "phpunit/php-text-template": "^2.0.4",
+ "sebastian/code-unit-reverse-lookup": "^2.0.3",
+ "sebastian/complexity": "^2.0.3",
+ "sebastian/environment": "^5.1.5",
+ "sebastian/lines-of-code": "^1.0.4",
+ "sebastian/version": "^3.0.2",
+ "theseer/tokenizer": "^1.2.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "9.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-22T04:23:01+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-12-02T12:48:52+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:58:55+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T05:33:50+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:16:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "9.6.35",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0edba2f3a0c48df3553cb9b640810b30df60302b",
+ "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.5.0 || ^2",
+ "ext-dom": "*",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.13.4",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=7.3",
+ "phpunit/php-code-coverage": "^9.2.32",
+ "phpunit/php-file-iterator": "^3.0.6",
+ "phpunit/php-invoker": "^3.1.1",
+ "phpunit/php-text-template": "^2.0.4",
+ "phpunit/php-timer": "^5.0.3",
+ "sebastian/cli-parser": "^1.0.2",
+ "sebastian/code-unit": "^1.0.8",
+ "sebastian/comparator": "^4.0.10",
+ "sebastian/diff": "^4.0.6",
+ "sebastian/environment": "^5.1.5",
+ "sebastian/exporter": "^4.0.8",
+ "sebastian/global-state": "^5.0.8",
+ "sebastian/object-enumerator": "^4.0.4",
+ "sebastian/resource-operations": "^3.0.4",
+ "sebastian/type": "^3.2.1",
+ "sebastian/version": "^3.0.2"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.35"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsoring.html",
+ "type": "other"
+ }
+ ],
+ "time": "2026-07-06T14:48:07+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "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/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T06:27:43+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:08:54+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:30:19+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "4.0.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d",
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/diff": "^4.0",
+ "sebastian/exporter": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-24T09:22:56+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
+ "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-22T06:19:30+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "4.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T06:30:58+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "5.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+ "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:03:51+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "4.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-09-24T06:03:27+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "5.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+ "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-10T07:10:35+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "1.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+ "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-22T06:20:34+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:12:34+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:14:26+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "4.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
+ "reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-10T06:57:39+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "3.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "support": {
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4"
},
"funding": [
{
- "url": "https://github.com/PHPCSStandards",
- "type": "github"
- },
- {
- "url": "https://github.com/jrfnl",
+ "url": "https://github.com/sebastianbergmann",
"type": "github"
- },
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "open_collective"
- },
- {
- "url": "https://thanks.dev/u/gh/phpcsstandards",
- "type": "thanks_dev"
}
],
- "time": "2025-11-12T23:06:57+00:00"
+ "time": "2024-03-14T16:00:52+00:00"
},
{
- "name": "phpcsstandards/phpcsutils",
- "version": "1.2.2",
+ "name": "sebastian/type",
+ "version": "3.2.1",
"source": {
"type": "git",
- "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
- "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55"
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55",
- "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+ "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
"shasum": ""
},
"require": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
- "php": ">=5.4",
- "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
+ "php": ">=7.3"
},
"require-dev": {
- "ext-filter": "*",
- "php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.4.0",
- "phpcsstandards/phpcsdevcs": "^1.2.0",
- "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
+ "phpunit/phpunit": "^9.5"
},
- "type": "phpcodesniffer-standard",
+ "type": "library",
"extra": {
"branch-alias": {
- "dev-stable": "1.x-dev",
- "dev-develop": "1.x-dev"
+ "dev-master": "3.2-dev"
}
},
"autoload": {
"classmap": [
- "PHPCSUtils/"
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL-3.0-or-later"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Juliette Reinders Folmer",
- "homepage": "https://github.com/jrfnl",
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
"role": "lead"
- },
- {
- "name": "Contributors",
- "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
}
],
- "description": "A suite of utility functions for use with PHP_CodeSniffer",
- "homepage": "https://phpcsutils.com/",
- "keywords": [
- "PHP_CodeSniffer",
- "phpcbf",
- "phpcodesniffer-standard",
- "phpcs",
- "phpcs3",
- "phpcs4",
- "standards",
- "static analysis",
- "tokens",
- "utility"
- ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
"support": {
- "docs": "https://phpcsutils.com/",
- "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
- "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
- "source": "https://github.com/PHPCSStandards/PHPCSUtils"
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
},
"funding": [
{
- "url": "https://github.com/PHPCSStandards",
- "type": "github"
- },
- {
- "url": "https://github.com/jrfnl",
+ "url": "https://github.com/sebastianbergmann",
"type": "github"
- },
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "open_collective"
- },
- {
- "url": "https://thanks.dev/u/gh/phpcsstandards",
- "type": "thanks_dev"
}
],
- "time": "2025-12-08T14:27:58+00:00"
+ "time": "2023-02-03T06:13:03+00:00"
},
{
- "name": "psr/container",
- "version": "2.0.2",
+ "name": "sebastian/version",
+ "version": "3.0.2",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/container.git",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c",
"shasum": ""
},
"require": {
- "php": ">=7.4.0"
+ "php": ">=7.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
- "psr-4": {
- "Psr\\Container\\": "src/"
- }
+ "classmap": [
+ "src/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
}
],
- "description": "Common Container Interface (PHP FIG PSR-11)",
- "homepage": "https://github.com/php-fig/container",
- "keywords": [
- "PSR-11",
- "container",
- "container-interface",
- "container-interop",
- "psr"
- ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
"support": {
- "issues": "https://github.com/php-fig/container/issues",
- "source": "https://github.com/php-fig/container/tree/2.0.2"
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
},
- "time": "2021-11-05T16:47:00+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:39:44+00:00"
},
{
"name": "sirbrillig/phpcs-variable-analysis",
@@ -1179,20 +3160,20 @@
},
{
"name": "symfony/deprecation-contracts",
- "version": "v2.5.4",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918"
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918",
- "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=8.1"
},
"type": "library",
"extra": {
@@ -1201,7 +3182,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "2.5-dev"
+ "dev-main": "3.7-dev"
}
},
"autoload": {
@@ -1226,7 +3207,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -1237,12 +3218,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": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/finder",
@@ -1808,35 +3793,43 @@
},
{
"name": "symfony/service-contracts",
- "version": "v1.1.2",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0"
+ "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0",
- "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0",
+ "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0",
"shasum": ""
},
"require": {
- "php": "^7.1.3"
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
},
- "suggest": {
- "psr/container": "",
- "symfony/service-implementation": ""
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
},
"type": "library",
"extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
"branch-alias": {
- "dev-master": "1.1-dev"
+ "dev-main": "3.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -1863,40 +3856,57 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v1.1.2"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.7.1"
},
- "time": "2019-05-28T07:50:59+00:00"
+ "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": "2026-06-16T09:55:08+00:00"
},
{
"name": "symfony/string",
- "version": "v5.4.47",
+ "version": "v6.4.39",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "136ca7d72f72b599f2631aca474a4f8e26719799"
+ "reference": "62e3c927de664edadb5bef260987eb047a17a113"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799",
- "reference": "136ca7d72f72b599f2631aca474a4f8e26719799",
+ "url": "https://api.github.com/repos/symfony/string/zipball/62e3c927de664edadb5bef260987eb047a17a113",
+ "reference": "62e3c927de664edadb5bef260987eb047a17a113",
"shasum": ""
},
"require": {
- "php": ">=7.2.5",
+ "php": ">=8.1",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
- "symfony/polyfill-mbstring": "~1.0",
- "symfony/polyfill-php80": "~1.15"
+ "symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
- "symfony/translation-contracts": ">=3.0"
+ "symfony/translation-contracts": "<2.5"
},
"require-dev": {
- "symfony/error-handler": "^4.4|^5.0|^6.0",
- "symfony/http-client": "^4.4|^5.0|^6.0",
- "symfony/translation-contracts": "^1.1|^2",
- "symfony/var-exporter": "^4.4|^5.0|^6.0"
+ "symfony/http-client": "^5.4|^6.0|^7.0",
+ "symfony/intl": "^6.2|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^5.4|^6.0|^7.0"
},
"type": "library",
"autoload": {
@@ -1935,7 +3945,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v5.4.47"
+ "source": "https://github.com/symfony/string/tree/v6.4.39"
},
"funding": [
{
@@ -1946,25 +3956,79 @@
"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-11-10T20:33:58+00:00"
+ "time": "2026-05-12T11:44:19+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2025-11-17T20:03:58+00:00"
},
{
"name": "wp-coding-standards/wpcs",
- "version": "3.3.0",
+ "version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
- "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6"
+ "reference": "469c18ceab4d642b15bad4c65ebf3b307bfd55ab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7795ec6fa05663d716a549d0b44e47ffc8b0d4a6",
- "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/469c18ceab4d642b15bad4c65ebf3b307bfd55ab",
+ "reference": "469c18ceab4d642b15bad4c65ebf3b307bfd55ab",
"shasum": ""
},
"require": {
@@ -1974,8 +4038,8 @@
"ext-xmlreader": "*",
"php": ">=7.2",
"phpcsstandards/phpcsextra": "^1.5.0",
- "phpcsstandards/phpcsutils": "^1.1.0",
- "squizlabs/php_codesniffer": "^3.13.4"
+ "phpcsstandards/phpcsutils": "^1.2.2",
+ "squizlabs/php_codesniffer": "^3.13.5"
},
"require-dev": {
"php-parallel-lint/php-console-highlighter": "^1.0.0",
@@ -2017,7 +4081,118 @@
"type": "custom"
}
],
- "time": "2025-11-25T12:08:04+00:00"
+ "time": "2026-07-16T13:05:29+00:00"
+ },
+ {
+ "name": "wp-phpunit/wp-phpunit",
+ "version": "6.9.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-phpunit/wp-phpunit.git",
+ "reference": "15fd216bf6516670d8d07b938675925bfa5c15b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/15fd216bf6516670d8d07b938675925bfa5c15b0",
+ "reference": "15fd216bf6516670d8d07b938675925bfa5c15b0",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "__loaded.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Evan Mattson",
+ "email": "me@aaemnnost.tv"
+ },
+ {
+ "name": "WordPress Community",
+ "homepage": "https://wordpress.org/about/"
+ }
+ ],
+ "description": "WordPress core PHPUnit library",
+ "homepage": "https://github.com/wp-phpunit",
+ "keywords": [
+ "phpunit",
+ "test",
+ "wordpress"
+ ],
+ "support": {
+ "docs": "https://github.com/wp-phpunit/docs",
+ "issues": "https://github.com/wp-phpunit/issues",
+ "source": "https://github.com/wp-phpunit/wp-phpunit"
+ },
+ "time": "2026-02-04T01:48:23+00:00"
+ },
+ {
+ "name": "yoast/phpunit-polyfills",
+ "version": "1.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Yoast/PHPUnit-Polyfills.git",
+ "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/41aaac462fbd80feb8dd129e489f4bbc53fe26b0",
+ "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4",
+ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "yoast/yoastcs": "^3.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "phpunitpolyfills-autoload.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Team Yoast",
+ "email": "support@yoast.com",
+ "homepage": "https://yoast.com"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors"
+ }
+ ],
+ "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests",
+ "homepage": "https://github.com/Yoast/PHPUnit-Polyfills",
+ "keywords": [
+ "phpunit",
+ "polyfill",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues",
+ "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy",
+ "source": "https://github.com/Yoast/PHPUnit-Polyfills"
+ },
+ "time": "2025-08-10T04:54:36+00:00"
}
],
"aliases": [],
diff --git a/docs/adr/0002-token-free-test-strategy.md b/docs/adr/0002-token-free-test-strategy.md
new file mode 100644
index 0000000..7727f56
--- /dev/null
+++ b/docs/adr/0002-token-free-test-strategy.md
@@ -0,0 +1,41 @@
+# Token-free test strategy
+
+TryAura generates through Google Gemini, which bills per token, and it calls Gemini from
+two places — the admin **Enhancer** (in the browser via `@google/genai`) and the customer
+**Try-On** (server-side via `wp_remote_post` in `GenerateController`). We want a test
+suite, modelled on dokan-lite, that can run unlimited times — locally and in CI — at zero
+API cost.
+
+## Decision
+
+No automated test ever calls the real Gemini API. Two tiers, both fully stubbed:
+
+- **PHPUnit** (dokan-style, boots real WordPress + WooCommerce) stubs the server-side
+ Try-On call with a `pre_http_request` filter on the base TestCase.
+- **Playwright E2E** (on `@wordpress/env`) stubs the browser Enhancer call with
+ `page.route('**/generativelanguage.googleapis.com/**')` and the server Try-On call with
+ a `pre_http_request` mu-plugin loaded into the test environment.
+
+Fixtures are **100% synthetic and hand-authored** from the response shapes already visible
+in the code (a 1×1 base64 PNG inside the image JSON; a few-KB stub MP4 plus a "done"
+operation JSON with a rewritten fake `uri` for video). There is **no live-smoke tier**. If
+a real sample is ever needed to shape a fixture, a human captures it out-of-band and hands
+over the data — the test code still never calls the API.
+
+## Considered options
+
+- **Record-once, replay-forever** (spend a handful of real calls one time, commit the real
+ responses, replay from then on). Rejected: even a one-time cost was declined, and the
+ response shapes are simple enough to hand-author accurately from the code.
+- **Record-once + a nightly opt-in live-smoke** to catch Gemini response-shape drift.
+ Rejected for the same cost reason; drift is accepted as a known risk.
+
+## Consequences
+
+- If Google changes a Gemini response shape, the suite won't notice until production —
+ mitigated by keeping fixtures minimal and refreshing them whenever a real sample is
+ supplied.
+- Video generation is Pro-only (Veo), so the fake-`uri` / stub-MP4 fixtures belong to the
+ **Pro** suite. The free suite only asserts the **locked** Video tab, never a Veo flow.
+- This is the one place the suite deliberately deviates from dokan-lite, which does no HTTP
+ stubbing at all — the stub layer is ours to introduce and maintain.
diff --git a/docs/testing-plan.md b/docs/testing-plan.md
new file mode 100644
index 0000000..b053169
--- /dev/null
+++ b/docs/testing-plan.md
@@ -0,0 +1,246 @@
+# TryAura — Testing Plan (PHPUnit + E2E, token-free)
+
+> Goal: stand up a PHPUnit + Playwright E2E suite for the TryAura free plugin,
+> modelled on how **dokan-lite** and **WooCommerce** structure their tests, while
+> spending **zero Gemini API tokens** on every automated run.
+>
+> Strategy locked in [ADR 0002 — Token-free test strategy](adr/0002-token-free-test-strategy.md).
+
+---
+
+## 1. Findings
+
+### 1.1 Current state of TryAura
+
+- **No test suite exists.** No `tests/` directory, no `phpunit.xml`, no E2E tooling.
+- `composer.json` `require-dev` has only the PHPCS toolchain + Mozart — **no PHPUnit**.
+- `package.json` has build/lint/format scripts — **no Playwright/Cypress/Jest**.
+- Quality today is enforced only by PHPCS, ESLint, Prettier, StyleLint.
+
+### 1.2 The token surface — there are **two** generation paths
+
+"Reduce tokens while testing" is not one lever. Gemini is called from two places, and
+**both must be stubbed** for a run to cost nothing:
+
+| # | Path | Where | How Gemini is reached |
+|---|------|-------|-----------------------|
+| 1 | Admin enhancer (image/video) | **Browser** — `src/admin/enhancer/PreviewModal.tsx` | `new GoogleGenAI({apiKey}).models.generateContent(...)` (lines 198, 207, 405, 600) |
+| 2 | Customer virtual try-on | **PHP** — `inc/Rest/GenerateController.php::handle_generation()` | `wp_remote_post()` to `https://generativelanguage.googleapis.com/...` (lines 111, 142) |
+
+Everything else on the PHP side never touches Gemini and is therefore **inherently
+token-free**: settings, container/provider wiring, promotion dismissal, media
+sideload, product meta, REST permissions.
+
+### 1.3 TryAura's testable REST surface
+
+| Controller | Route(s) | Notes |
+|---|---|---|
+| `SettingsController` | `tryaura/v1/settings`, `.../settings/bulk-try-on` | option CRUD + sanitize; WC-guarded bulk toggle |
+| `GenerateController` | `generate/v1` (try-on) | **the PHP Gemini call** — stub target |
+| `PromotionController` | `tryaura/v1/promotion/dismiss-banner` | writes per-user dismissal timestamp |
+| `VideoThumbnailController` | media save | `wp_upload_bits` / `media_handle_sideload` / `wp_insert_attachment` |
+| `ProductController` | product data | WooCommerce product info |
+| `DashboardController` | stats / `log-usage` | usage analytics aggregation |
+
+### 1.4 How dokan-lite / WooCommerce structure tests (the template)
+
+> The bundled `woocommerce` copy is the built plugin and ships **no** tests. All
+> conventions below come from **dokan-lite** (richest, most current), with
+> **dokan-pro** noted where it differs.
+
+**PHPUnit**
+- Layout: `tests/php/` — `phpunit.xml`, `bootstrap.php`, `phpunit-wp-config.php`, and
+ test classes under `tests/php/src/` PSR-4-mapped to `WeDevs\Dokan\Test\`
+ (`autoload-dev`). **Feature-grouped**, not unit-vs-integration folders; the
+ unit/integration split is a per-test boolean (`$is_unit_test`) on the base class.
+- `phpunit.xml`: `bootstrap="tests/php/bootstrap.php"`, env
+ `WP_PHPUNIT__TESTS_CONFIG`, single testsuite scanning `tests/php`. No coverage block.
+- `bootstrap.php`: loads Composer autoload → resolves WP test lib (`WP_TESTS_DIR` →
+ `WP_PHPUNIT__DIR` → temp) → loads WooCommerce then the plugin on `muplugins_loaded`
+ → installs WC (with HPOS) on `setup_theme` → boots WP test scaffolding.
+- `require-dev`: `phpunit/phpunit ^9.6`, `wp-phpunit/wp-phpunit dev-master`,
+ `yoast/phpunit-polyfills ^1.0`, `brain/monkey ^2.0` (brings Mockery).
+- DB install: standard `bin/install-wp-tests.sh`.
+- Base class: `DokanTestCase extends WP_UnitTestCase` mixing Brain Monkey + Mockery +
+ DB/array assertion traits; spins up a fresh `WP_REST_Server`, seeds users, exposes
+ `get/post/put/delete_request()` helpers and order builders. Factories subclass
+ `WP_UnitTest_Factory` (`product`, `order`, `seller`, ...). WooCommerce-style
+ `WC_Helper_*` fixtures under `tests/php/src/Helpers/`.
+- **HTTP mocking:** dokan's PHPUnit has **no** `pre_http_request`/`wp_remote_*` stub —
+ they mock PHP functions/objects with Brain Monkey + Mockery and exercise real WC/DB.
+ For a third-party API you **introduce** the `pre_http_request` filter (they use it
+ only in their E2E mu-plugin — see below).
+- Runner: `composer test` → `phpunit`; `composer test-f` → `phpunit --filter`.
+
+**E2E**
+- **Playwright** (`@playwright/test`), no Cypress/Puppeteer. Specs under `tests/pw/`
+ (`tests/pw/tests/e2e/` UI + page objects, `tests/pw/tests/api/` API).
+- Environment: **`@wordpress/env`** via `.wp-env.json` (WP + WooCommerce + Basic-Auth +
+ helpers; maps the plugin and an mu-plugins dir into the container).
+- `playwright.config.ts`: staged **projects with dependencies** —
+ `site_setup → auth_setup → e2e_setup → e2e_tests → coverage/teardown`; tag filtering
+ (`@lite`, `@pro`). Auth via persisted `storageState`; data seeded via REST + direct DB.
+- **Network interception:** heavy use of `page.route(...)`:
+ - `route.abort()` for real third-party hosts (Stripe, hCaptcha).
+ - `route.fulfill({...})` with canned JSON to stub success/error/loading paths
+ (e.g. `adminChangelogPage.ts` stubbing a REST endpoint).
+ - A **server-side guard mu-plugin** (`tests/pw/mu-plugins/disable-updates-and-external-http.php`)
+ adds a `pre_http_request` filter returning `WP_Error` for `wordpress.org`, so the
+ site makes no slow outbound calls during tests.
+- npm scripts in `tests/pw/package.json`: `start:env`/`stop:env`/`reset:env`, `test`,
+ `test:e2e`, `test:api`, `test:ui`, `test:debug`.
+
+---
+
+## 2. The token-reduction strategy
+
+**Rule: no test ever holds a real Gemini key or lets a request reach
+`generativelanguage.googleapis.com`.** Stub at both layers and every test costs zero
+tokens.
+
+### 2.1 PHPUnit — stub `wp_remote_post` via `pre_http_request`
+
+Add to the base TestCase (hangs off the Brain Monkey `set_up()` slot):
+
+```php
+protected function fake_gemini( array $response = null ) {
+ add_filter( 'pre_http_request', function ( $pre, $args, $url ) use ( $response ) {
+ if ( str_contains( $url, 'generativelanguage.googleapis.com' ) ) {
+ return array(
+ 'response' => array( 'code' => 200 ),
+ 'body' => wp_json_encode( $response ?? $this->gemini_image_fixture() ),
+ );
+ }
+ return $pre; // non-Gemini calls pass through (there are none in tests)
+ }, 10, 3 );
+}
+```
+
+Covers the **try-on** PHP path. Assert the request body TryAura *builds* (prompt +
+`inline_data` parts) and how it *maps* the stubbed response — plus the error branches
+(`is_wp_error`, `data['error']`, bad nonce).
+
+### 2.2 E2E — intercept the browser call (Playwright)
+
+```ts
+await page.route('**/generativelanguage.googleapis.com/**', route =>
+ route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify(geminiImageFixture), // fixture embeds a 1×1 base64 PNG
+ }));
+```
+
+Covers the **admin enhancer** path. Same `page.route().fulfill()` pattern dokan-lite
+uses in `adminChangelogPage.ts`.
+
+### 2.3 E2E — server-side guard mu-plugin
+
+Copy dokan-lite's `disable-updates-and-external-http.php` into `tests/pw/mu-plugins/`
+and add a `pre_http_request` branch that stubs Gemini (covers the **try-on** path
+inside the browser-driven flow) and blocks `wordpress.org`.
+
+### 2.4 Fixtures are 100% synthetic — no live tier
+
+**Decision:** the harness never calls Gemini, not even once to record fixtures, and there
+is **no live-smoke tier.** All fixtures are **hand-authored** from the response shapes
+already visible in the code — a 1×1 base64 PNG in the image JSON; a few-KB stub MP4 plus a
+"done" operation JSON with a rewritten fake `uri` for video. If a real sample is ever
+needed to shape a fixture, a human captures it out-of-band and hands over the data; the
+test code still never calls the API. Accepted trade-off: fixtures can drift from a changed
+Gemini response shape without the suite noticing — see
+[ADR 0002](adr/0002-token-free-test-strategy.md). Note the **video** fixtures exercise a
+**Pro-only** flow (Veo), so they live in the Pro suite; the free suite only asserts the
+**locked** Video tab.
+
+---
+
+## 3. PHPUnit implementation plan (mirror dokan-lite)
+
+**Scaffold**
+- `tests/php/` → `phpunit.xml`, `bootstrap.php`, `phpunit-wp-config.php`.
+- Test classes under `tests/php/src/`, PSR-4 `Dokan\TryAura\Test\` via `autoload-dev`,
+ grouped by feature: `Rest/`, `Admin/`, `Settings/`, `Promotion/`, `WooCommerce/`.
+- `composer require-dev`: `phpunit/phpunit ^9.6`, `wp-phpunit/wp-phpunit`,
+ `yoast/phpunit-polyfills ^1.0`, `brain/monkey ^2.0`.
+- `bin/install-wp-tests.sh` (standard WP scaffold installer).
+- `composer test` → `phpunit`; `composer test-f` → `phpunit --filter`.
+- `bootstrap.php` loads WooCommerce then TryAura on `muplugins_loaded`.
+- Base `TryAuraTestCase extends WP_UnitTestCase` (Brain Monkey + Mockery + REST request
+ helpers + `fake_gemini()` + a `gemini_image_fixture()`), plus a `WP_UnitTest_Factory`
+ subclass for products.
+
+**Coverage (all token-free)**
+
+| Target | Assertions |
+|---|---|
+| `GenerateController` (try-on) | with `fake_gemini()`: correct request body (prompt + `inline_data`); response mapping; nonce/permission + `is_wp_error` / `data['error']` branches |
+| `SettingsController` | GET/POST settings, `sanitize_settings`, `bulk-try-on` (WC-guarded), `manage_options` permission |
+| `PromotionController` + `Promotion` | dismissal writes the user-meta timestamp; `should_show_upgrade_banner()` true/false across pro-active + 30-day expiry; `plugin_action_links` only when pro absent |
+| `VideoThumbnailController` | media sideload creates an attachment |
+| Container / providers | each provider registers services; `is_pro_exists` gating |
+| `ProductController` / `DashboardController` | product data shape; stats aggregation + `log-usage` |
+
+---
+
+## 4. E2E implementation plan (Playwright + @wordpress/env)
+
+**Scaffold**
+- `tests/pw/` → `.wp-env.json` (WP + WooCommerce + Basic-Auth + the guard mu-plugin
+ mapped in), `playwright.config.ts` with staged projects
+ `site_setup → auth_setup → e2e_setup → e2e_tests`, page objects, `utils/`, fixtures.
+- Auth via persisted `storageState`; seed data via REST + `wp-cli`.
+- npm scripts in `tests/pw/package.json`: `start:env`, `stop:env`, `reset:env`,
+ `test:e2e`, `test:api`, `test:ui`, `test:debug`.
+
+**Flows (each with Gemini stubbed)**
+
+_Free-usable:_
+- Admin enhancer: open media modal → generate **image** (free options) → assert result saved to Media Library.
+- Settings: connect + save Gemini settings.
+- Frontend try-on: upload photo → generate → result renders.
+- Products-list: try-on toggle column.
+
+_Pro-locked states (assert the gating, not the feature):_
+- Video + Edit tabs render locked (crown); aspect-ratios beyond 1:1 locked; custom/negative prompt disabled.
+- Promo banner / sidebar Upgrade / "Upgrade to Pro" link appear only when `hasPro` is false; dismissal persists.
+
+---
+
+## 5. Suggested phasing
+
+1. **Scaffold + token guard first** — `install-wp-tests.sh`, `phpunit.xml`,
+ `bootstrap.php`, base TestCase with `fake_gemini()`, and one green test (settings
+ CRUD). Proves the zero-token harness before writing volume.
+2. **PHP breadth** — the controller / promotion / media table in §3.
+3. **E2E harness** — wp-env + guard mu-plugin + one stubbed enhancer flow end-to-end.
+4. **E2E breadth** — try-on, settings, promo, products-list.
+5. **CI** — GitHub Actions, lean matrix (PHP 7.4 + current 8.x, latest WP+WC, plus a
+ WP 6.6 min job): `composer test` on the install-wp-tests scaffold, then a Playwright
+ job on wp-env. No live-API job — the suite is fully stubbed.
+
+---
+
+## 6. Resolved decisions
+
+Settled in the grilling session; recorded in
+[ADR 0002](adr/0002-token-free-test-strategy.md):
+
+1. **Template** — mirror **dokan-lite** (the bundled `woocommerce` ships no tests).
+2. **Two tiers** — dokan-style **PHPUnit** (boots real WP + WooCommerce via
+ `install-wp-tests.sh`) + **Playwright E2E** on **`@wordpress/env`**.
+3. **No real Gemini, ever** — 100% synthetic, hand-authored fixtures; no live-smoke tier.
+4. **Coverage scope** — both free-usable flows **and** Pro-locked states (the gating).
+5. **CI** — GitHub Actions, lean matrix (PHP 7.4 + current 8.x, latest WP+WC, WP 6.6 min).
+
+---
+
+## 7. Why this is zero-token
+
+- **PHPUnit:** the only Gemini caller is the try-on `wp_remote_post`, intercepted by
+ `pre_http_request`. Every other PHP test never touches Gemini.
+- **E2E:** the browser SDK call is intercepted by `page.route`, and the server-side
+ try-on call is intercepted by the guard mu-plugin. No real key is ever configured in
+ the test environment.
+- **Net:** every run — local or CI — makes **no** real Gemini requests → **0 tokens**,
+ always. There is no live-API path anywhere in the suite.
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..820af78
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ ./tests/php/src
+
+
+
diff --git a/tests/php/bootstrap.php b/tests/php/bootstrap.php
new file mode 100644
index 0000000..7e5ce6d
--- /dev/null
+++ b/tests/php/bootstrap.php
@@ -0,0 +1,61 @@
+assertTrue( $container->has( $service ), "Container is missing service: {$service}" );
+ }
+ }
+
+ public function test_admin_services_are_registered(): void {
+ $container = TryAura::container();
+
+ foreach ( array( 'admin', 'enhancer', 'promotion' ) as $service ) {
+ $this->assertTrue( $container->has( $service ), "Container is missing service: {$service}" );
+ }
+ }
+
+ public function test_a_service_resolves_to_its_class(): void {
+ $this->assertInstanceOf(
+ PromotionController::class,
+ TryAura::container()->get( 'promotion_controller' )
+ );
+ }
+}
diff --git a/tests/php/src/GeminiFixtures.php b/tests/php/src/GeminiFixtures.php
new file mode 100644
index 0000000..ab25146
--- /dev/null
+++ b/tests/php/src/GeminiFixtures.php
@@ -0,0 +1,73 @@
+ array(
+ array(
+ 'content' => array(
+ 'parts' => array(
+ array(
+ 'inlineData' => array(
+ 'mimeType' => 'image/png',
+ 'data' => self::PNG_1X1,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'usageMetadata' => array(
+ 'promptTokenCount' => 12,
+ 'candidatesTokenCount' => 34,
+ 'totalTokenCount' => 46,
+ ),
+ );
+ }
+
+ /**
+ * An error response, matching Gemini's `{ "error": { ... } }` shape.
+ *
+ * @param int $code Error code.
+ * @param string $message Error message.
+ * @param string $status Error status string.
+ *
+ * @return array
+ */
+ public static function error( int $code = 400, string $message = 'TryAura test error', string $status = 'INVALID_ARGUMENT' ): array {
+ return array(
+ 'error' => array(
+ 'code' => $code,
+ 'message' => $message,
+ 'status' => $status,
+ ),
+ );
+ }
+}
diff --git a/tests/php/src/Promotion/PromotionControllerTest.php b/tests/php/src/Promotion/PromotionControllerTest.php
new file mode 100644
index 0000000..3339785
--- /dev/null
+++ b/tests/php/src/Promotion/PromotionControllerTest.php
@@ -0,0 +1,58 @@
+admin = self::factory()->user->create( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $this->admin );
+ delete_user_meta( $this->admin, Promotion::BANNER_DISMISSED_META_KEY );
+ }
+
+ public function test_dismiss_records_a_timestamp(): void {
+ $before = time();
+
+ $response = $this->dispatch( 'POST', '/tryaura/v1/promotion/dismiss-banner' );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertTrue( $response->get_data()['dismissed'] );
+
+ $stored = (int) get_user_meta( $this->admin, Promotion::BANNER_DISMISSED_META_KEY, true );
+ $this->assertGreaterThanOrEqual( $before, $stored );
+ }
+
+ public function test_dismiss_hides_the_banner(): void {
+ $this->assertTrue( Promotion::should_show_upgrade_banner() );
+
+ $this->dispatch( 'POST', '/tryaura/v1/promotion/dismiss-banner' );
+
+ $this->assertFalse( Promotion::should_show_upgrade_banner() );
+ }
+
+ public function test_dismiss_requires_manage_options(): void {
+ wp_set_current_user( self::factory()->user->create( array( 'role' => 'subscriber' ) ) );
+
+ $response = $this->dispatch( 'POST', '/tryaura/v1/promotion/dismiss-banner' );
+
+ $this->assertSame( 403, $response->get_status() );
+ }
+}
diff --git a/tests/php/src/Promotion/PromotionTest.php b/tests/php/src/Promotion/PromotionTest.php
new file mode 100644
index 0000000..cbdd7d2
--- /dev/null
+++ b/tests/php/src/Promotion/PromotionTest.php
@@ -0,0 +1,83 @@
+admin = self::factory()->user->create( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $this->admin );
+ delete_user_meta( $this->admin, Promotion::BANNER_DISMISSED_META_KEY );
+ }
+
+ public function tear_down(): void {
+ // Always drop the pro-exists override, even if an assertion above threw,
+ // so it can't leak into sibling tests.
+ remove_filter( 'tryaura_is_pro_exists', '__return_true' );
+ parent::tear_down();
+ }
+
+ public function test_banner_shows_when_never_dismissed(): void {
+ $this->assertTrue( Promotion::should_show_upgrade_banner() );
+ }
+
+ public function test_banner_hidden_after_recent_dismissal(): void {
+ update_user_meta( $this->admin, Promotion::BANNER_DISMISSED_META_KEY, time() );
+
+ $this->assertFalse( Promotion::should_show_upgrade_banner() );
+ }
+
+ public function test_banner_reappears_after_30_days(): void {
+ update_user_meta( $this->admin, Promotion::BANNER_DISMISSED_META_KEY, time() - ( 31 * DAY_IN_SECONDS ) );
+
+ $this->assertTrue( Promotion::should_show_upgrade_banner() );
+ }
+
+ public function test_banner_hidden_when_pro_active(): void {
+ add_filter( 'tryaura_is_pro_exists', '__return_true' );
+
+ $this->assertFalse( Promotion::should_show_upgrade_banner() );
+
+ remove_filter( 'tryaura_is_pro_exists', '__return_true' );
+ }
+
+ public function test_action_link_added_when_pro_absent(): void {
+ $promotion = new Promotion();
+
+ $links = $promotion->add_upgrade_action_link( array( 'deactivate' => 'Deactivate' ) );
+ $joined = implode( '', $links );
+
+ $this->assertStringContainsString( 'Upgrade to Pro', $joined );
+ $this->assertStringContainsString( Promotion::UPGRADE_URL, $joined );
+ }
+
+ public function test_hooks_registered_only_when_pro_absent(): void {
+ $absent = new Promotion();
+ $this->assertNotFalse( has_action( 'admin_menu', array( $absent, 'register_upgrade_menu' ) ) );
+
+ add_filter( 'tryaura_is_pro_exists', '__return_true' );
+ $active = new Promotion();
+ $this->assertFalse( has_action( 'admin_menu', array( $active, 'register_upgrade_menu' ) ) );
+ remove_filter( 'tryaura_is_pro_exists', '__return_true' );
+ }
+}
diff --git a/tests/php/src/Rest/DashboardControllerTest.php b/tests/php/src/Rest/DashboardControllerTest.php
new file mode 100644
index 0000000..dfb446b
--- /dev/null
+++ b/tests/php/src/Rest/DashboardControllerTest.php
@@ -0,0 +1,70 @@
+user->create( array( 'role' => 'administrator' ) ) );
+ }
+
+ public function test_stats_aggregate_logged_usage(): void {
+ $manager = new UsageManager();
+ $manager->log_usage(
+ array(
+ 'type' => 'image',
+ 'model' => 'gemini-2.5-flash-image',
+ 'total_tokens' => 100,
+ 'status' => 'success',
+ 'created_at' => current_time( 'mysql' ),
+ )
+ );
+ $manager->log_usage(
+ array(
+ 'type' => 'image',
+ 'model' => 'gemini-2.5-flash-image',
+ 'total_tokens' => 50,
+ 'status' => 'success',
+ 'created_at' => current_time( 'mysql' ),
+ )
+ );
+
+ $response = $this->dispatch( 'GET', '/tryaura/v1/stats' );
+
+ $this->assertSame( 200, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertSame( 2, $data['image_count'] );
+ $this->assertSame( 150, $data['total_tokens'] );
+ }
+
+ public function test_log_usage_endpoint_persists_a_row(): void {
+ $response = $this->dispatch(
+ 'POST',
+ '/tryaura/v1/log-usage',
+ array(
+ 'type' => 'image',
+ 'model' => 'gemini-2.5-flash-image',
+ 'total_tokens' => 10,
+ 'status' => 'success',
+ )
+ );
+
+ $this->assertSame( 200, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertTrue( $data['success'] );
+ $this->assertGreaterThan( 0, $data['id'] );
+ }
+}
diff --git a/tests/php/src/Rest/GenerateControllerTest.php b/tests/php/src/Rest/GenerateControllerTest.php
new file mode 100644
index 0000000..28870e4
--- /dev/null
+++ b/tests/php/src/Rest/GenerateControllerTest.php
@@ -0,0 +1,84 @@
+user->create( array( 'role' => 'administrator' ) ) );
+ update_option(
+ 'tryaura_settings',
+ array(
+ 'google' => array(
+ 'apiKey' => 'test-key',
+ 'imageModel' => 'gemini-2.5-flash-image',
+ ),
+ )
+ );
+ }
+
+ private function valid_nonce(): string {
+ return wp_create_nonce( 'tryon_nonce' );
+ }
+
+ public function test_bad_nonce_is_rejected(): void {
+ $response = $this->dispatch(
+ 'POST',
+ '/generate/v1/image',
+ array(
+ 'tryonNonce' => 'not-a-real-nonce',
+ 'prompt' => 'a red cap',
+ )
+ );
+
+ $this->assertSame( 401, $response->get_status() );
+ }
+
+ public function test_gemini_error_is_surfaced(): void {
+ $this->fake_gemini_error( 400 );
+
+ $response = $this->dispatch(
+ 'POST',
+ '/generate/v1/image',
+ array(
+ 'tryonNonce' => $this->valid_nonce(),
+ 'prompt' => 'a red cap',
+ )
+ );
+
+ $this->assertSame( 400, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertSame( 'INVALID_ARGUMENT', $data['status'] );
+ }
+
+ public function test_successful_generation_returns_image(): void {
+ $this->fake_gemini_success();
+
+ $response = $this->dispatch(
+ 'POST',
+ '/generate/v1/image',
+ array(
+ 'tryonNonce' => $this->valid_nonce(),
+ 'prompt' => 'a red cap',
+ )
+ );
+
+ $this->assertSame( 200, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertSame( GeminiFixtures::PNG_1X1, $data['image'] );
+ }
+}
diff --git a/tests/php/src/Rest/ProductControllerTest.php b/tests/php/src/Rest/ProductControllerTest.php
new file mode 100644
index 0000000..5dc968b
--- /dev/null
+++ b/tests/php/src/Rest/ProductControllerTest.php
@@ -0,0 +1,49 @@
+user->create( array( 'role' => 'administrator' ) ) );
+ }
+
+ public function test_missing_product_returns_404(): void {
+ $response = $this->dispatch( 'GET', '/tryaura/v1/product/999999/images' );
+
+ $this->assertSame( 404, $response->get_status() );
+ }
+
+ public function test_returns_the_featured_image(): void {
+ $attachment_id = self::factory()->attachment->create_object(
+ 'featured.jpg',
+ 0,
+ array( 'post_mime_type' => 'image/jpeg' )
+ );
+ update_post_meta( $attachment_id, '_wp_attached_file', 'featured.jpg' );
+
+ $product = new WC_Product_Simple();
+ $product->set_name( 'Red Cap' );
+ $product->set_status( 'publish' );
+ $product->set_image_id( $attachment_id );
+ $product_id = $product->save();
+
+ $response = $this->dispatch( 'GET', "/tryaura/v1/product/{$product_id}/images" );
+
+ $this->assertSame( 200, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertArrayHasKey( (string) $product_id, $data );
+ $this->assertStringContainsString( 'featured.jpg', (string) $data[ (string) $product_id ] );
+ }
+}
diff --git a/tests/php/src/Rest/VideoThumbnailControllerTest.php b/tests/php/src/Rest/VideoThumbnailControllerTest.php
new file mode 100644
index 0000000..98f2256
--- /dev/null
+++ b/tests/php/src/Rest/VideoThumbnailControllerTest.php
@@ -0,0 +1,72 @@
+user->create( array( 'role' => 'administrator' ) ) );
+
+ // Keep uploaded files out of the dev site.
+ add_filter( 'upload_dir', array( $this, 'redirect_uploads' ) );
+ }
+
+ public function redirect_uploads( array $dirs ): array {
+ $base = sys_get_temp_dir() . '/tryaura-test-uploads';
+ wp_mkdir_p( $base );
+
+ $dirs['path'] = $base;
+ $dirs['basedir'] = $base;
+ $dirs['url'] = 'http://example.org/wp-content/uploads';
+ $dirs['baseurl'] = 'http://example.org/wp-content/uploads';
+ $dirs['subdir'] = '';
+
+ return $dirs;
+ }
+
+ public function test_saves_base64_thumbnail_as_attachment(): void {
+ $response = $this->dispatch(
+ 'POST',
+ '/tryaura/v1/generate-thumbnail',
+ array(
+ 'platform' => 'site',
+ 'image' => 'data:image/png;base64,' . GeminiFixtures::PNG_1X1,
+ 'url' => 'https://example.com/video.mp4',
+ )
+ );
+
+ $this->assertSame( 200, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertGreaterThan( 0, $data['attachment_id'] );
+ $this->assertSame( 'attachment', get_post_type( $data['attachment_id'] ) );
+ }
+
+ public function test_requires_edit_products_capability(): void {
+ wp_set_current_user( self::factory()->user->create( array( 'role' => 'subscriber' ) ) );
+
+ $response = $this->dispatch(
+ 'POST',
+ '/tryaura/v1/generate-thumbnail',
+ array(
+ 'platform' => 'site',
+ 'image' => 'data:image/png;base64,' . GeminiFixtures::PNG_1X1,
+ 'url' => 'https://example.com/video.mp4',
+ )
+ );
+
+ $this->assertSame( 403, $response->get_status() );
+ }
+}
diff --git a/tests/php/src/Settings/BulkTryOnTest.php b/tests/php/src/Settings/BulkTryOnTest.php
new file mode 100644
index 0000000..163b02e
--- /dev/null
+++ b/tests/php/src/Settings/BulkTryOnTest.php
@@ -0,0 +1,47 @@
+user->create( array( 'role' => 'administrator' ) ) );
+ }
+
+ private function create_published_product( string $name ): void {
+ $product = new WC_Product_Simple();
+ $product->set_name( $name );
+ $product->set_status( 'publish' );
+ $product->save();
+ }
+
+ public function test_reports_no_products_when_catalog_empty(): void {
+ $response = $this->dispatch( 'POST', '/tryaura/v1/settings/bulk-try-on', array( 'enabled' => true ) );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertStringContainsString( 'No products', $response->get_data()['message'] );
+ }
+
+ public function test_queues_all_published_products(): void {
+ $this->create_published_product( 'Cap' );
+ $this->create_published_product( 'Shirt' );
+
+ $response = $this->dispatch( 'POST', '/tryaura/v1/settings/bulk-try-on', array( 'enabled' => true ) );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertStringContainsString( '2 products', $response->get_data()['message'] );
+ }
+}
diff --git a/tests/php/src/Settings/SettingsControllerTest.php b/tests/php/src/Settings/SettingsControllerTest.php
new file mode 100644
index 0000000..04de4ad
--- /dev/null
+++ b/tests/php/src/Settings/SettingsControllerTest.php
@@ -0,0 +1,82 @@
+admin = self::factory()->user->create( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $this->admin );
+ delete_option( 'tryaura_settings' );
+ }
+
+ public function test_get_returns_saved_settings(): void {
+ update_option( 'tryaura_settings', array( 'google' => array( 'apiKey' => 'saved-key' ) ) );
+
+ $response = $this->dispatch( 'GET', '/tryaura/v1/settings' );
+
+ $this->assertSame( 200, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertSame( 'saved-key', $data['tryaura_settings']['google']['apiKey'] );
+ }
+
+ public function test_post_saves_settings(): void {
+ $response = $this->dispatch(
+ 'POST',
+ '/tryaura/v1/settings',
+ array(
+ 'tryaura_settings' => array(
+ 'google' => array(
+ 'apiKey' => 'new-key',
+ 'imageModel' => 'gemini-2.5-flash-image',
+ ),
+ ),
+ )
+ );
+
+ $this->assertSame( 200, $response->get_status() );
+ $saved = get_option( 'tryaura_settings' );
+ $this->assertSame( 'new-key', $saved['google']['apiKey'] );
+ $this->assertSame( 'gemini-2.5-flash-image', $saved['google']['imageModel'] );
+ }
+
+ public function test_post_sanitizes_input(): void {
+ $this->dispatch(
+ 'POST',
+ '/tryaura/v1/settings',
+ array(
+ 'tryaura_settings' => array(
+ 'google' => array( 'apiKey' => " key