From f753c372b7803351991e6013042c7b80c51d3096 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 20 Aug 2026 22:28:33 +0200 Subject: [PATCH 01/65] build(deps-dev): take phpstan 2 and clear the debt it surfaces (supersedes #259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps conduction/hydra-gates to ^1.8.2 alongside phpstan ^2.0; the lockfile pinned v1.8.1 so the fleet migration config from ConductionNL/.github#517 was not in effect here. Seven residual errors, all genuine: 4 catch.neverThrown 'catch (DoesNotExistException|MultipleObjectsReturnedException)' around EncryptionSuiteMapper::findActiveByOwner(). I checked this one twice, because removing a catch is how you turn a handled case into a 500. The first read was that QBMapper::findEntity() DOES declare @throws MultipleObjectsReturnedException, so the catch looked live and the mapper's docblock merely incomplete. Reading the mapper BODY settled it: findActiveByOwner() ends with setMaxResults(1), so findEntity() can never see two rows. That bound is itself the fix for the bug the mapper's own docblock describes — the unbounded version threw mid-migration and this very catch then reported 'No active encryption suite' to a user who had two. The arm is unreachable by construction. Removed from the 4 sites phpstan flags; the OTHER four identical-looking catches in SecretService are left alone, because they wrap calls that are not bounded. 2 notIdentical.alwaysTrue '!== ""' after isset() on the LAST regex group — PHP omits a trailing unmatched group rather than setting it to '', so isset() already covers '{16}' — and 'hasPassword: ($salt !== "")' where the guard above throws on an empty salt. 1 property.unusedType BreachProxyController::$cache declared ?ICache and never assigned null: ICacheFactory::createDistributed() returns a no-op ICache, not null. Made it non-nullable and dropped the two now-pointless ?-> calls. Verified: phpstan 2.2.8 exit 0, phpcs exit 0, php -l clean. PHPUnit: 1031 tests, 1 error (EncryptServiceTest RSA-under-certificate). PRE- EXISTING — reproduces identically with these changes stashed. --- composer.json | 4 +-- composer.lock | 35 ++++++++++++++++-------- lib/Controller/BreachProxyController.php | 15 ++++++---- lib/Service/KeyGeneratorRegexParser.php | 6 +++- lib/Service/LinkShareService.php | 4 ++- lib/Service/SecretRequestPolicy.php | 5 +++- lib/Service/SecretService.php | 18 ++++++++++-- 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index 8579b2942..38ff42f60 100644 --- a/composer.json +++ b/composer.json @@ -22,14 +22,14 @@ }, "require-dev": { "conduction/coding-standard": "^1.0", - "conduction/hydra-gates": "^1.0", + "conduction/hydra-gates": "^1.8.2", "cyclonedx/cyclonedx-php-composer": "^6.2", "edgedesign/phpqa": "^1.27", "nextcloud/ocp": "^34.0", "phpcsstandards/phpcsextra": "^1.4", "phpmd/phpmd": "^2.15", "phpmetrics/phpmetrics": "^2.8", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5", "roave/security-advisories": "dev-latest", "squizlabs/php_codesniffer": "^4.0", diff --git a/composer.lock b/composer.lock index 22699ab15..dcd889201 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": "961992cb09e3b051339639567a5cfad7", + "content-hash": "98c32247bf44ec987ebc460606634b03", "packages": [ { "name": "brick/math", @@ -2568,16 +2568,16 @@ }, { "name": "conduction/hydra-gates", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/ConductionNL/.github.git", - "reference": "8e0e9857e54d6c680e157939e78a468e58d3751a" + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ConductionNL/.github/zipball/8e0e9857e54d6c680e157939e78a468e58d3751a", - "reference": "8e0e9857e54d6c680e157939e78a468e58d3751a", + "url": "https://api.github.com/repos/ConductionNL/.github/zipball/3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", "shasum": "" }, "require": { @@ -2621,9 +2621,9 @@ "support": { "docs": "https://github.com/ConductionNL/.github/blob/main/hydra-gates/README.md", "issues": "https://github.com/ConductionNL/.github/issues", - "source": "https://github.com/ConductionNL/.github/tree/v1.8.1" + "source": "https://github.com/ConductionNL/.github/tree/v1.8.2" }, - "time": "2026-08-20T05:07:22+00:00" + "time": "2026-08-20T09:37:12+00:00" }, { "name": "consolidation/annotated-command", @@ -5226,15 +5226,15 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.33", + "version": "2.2.8", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1", - "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -5253,6 +5253,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -5275,7 +5286,7 @@ "type": "github" } ], - "time": "2026-02-28T20:30:03+00:00" + "time": "2026-08-04T22:21:45+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/lib/Controller/BreachProxyController.php b/lib/Controller/BreachProxyController.php index 345646394..27b5d76a8 100644 --- a/lib/Controller/BreachProxyController.php +++ b/lib/Controller/BreachProxyController.php @@ -71,11 +71,16 @@ class BreachProxyController extends Controller { private const UPSTREAM_TIMEOUT = 5; /** - * The per-prefix response cache (null when no distributed cache is available). + * The per-prefix response cache. * - * @var ICache|null + * NOT nullable. `ICacheFactory::createDistributed()` always returns an + * ICache — it hands back a no-op implementation when no distributed cache + * is configured rather than null — so the `?` this replaces described a + * state that could not occur, and the `null` case was never assigned. + * + * @var ICache */ - private ?ICache $cache; + private ICache $cache; /** * Constructor for BreachProxyController. @@ -155,7 +160,7 @@ public function range(string $prefix): DataResponse { ); } - $cached = $this->cache?->get($prefix); + $cached = $this->cache->get($prefix); if (is_string($cached) === true) { return new DataResponse(data: ['suffixes' => $cached]); } @@ -182,7 +187,7 @@ public function range(string $prefix): DataResponse { ); }//end try - $this->cache?->set($prefix, $body, self::CACHE_TTL); + $this->cache->set($prefix, $body, self::CACHE_TTL); return new DataResponse(data: ['suffixes' => $body]); }//end range() diff --git a/lib/Service/KeyGeneratorRegexParser.php b/lib/Service/KeyGeneratorRegexParser.php index ae8e037a9..1eb3283b9 100644 --- a/lib/Service/KeyGeneratorRegexParser.php +++ b/lib/Service/KeyGeneratorRegexParser.php @@ -120,7 +120,11 @@ public function extractLength(string $regex): array { $min = (int)$matches[1]; $max = $min; - if (isset($matches[2]) === true && $matches[2] !== '') { + // Group 2 is the LAST group in the pattern, and PHP omits a trailing + // unmatched group from $matches rather than setting it to '' — so + // isset() already covers the `{16}` case and the `!== ''` this replaces + // was unreachable. (The '' form only happens for MIDDLE groups.) + if (isset($matches[2]) === true) { $max = (int)$matches[2]; } diff --git a/lib/Service/LinkShareService.php b/lib/Service/LinkShareService.php index 3d27e64ed..eafe18182 100644 --- a/lib/Service/LinkShareService.php +++ b/lib/Service/LinkShareService.php @@ -159,7 +159,9 @@ public function create( $this->auditTrail->recordCreated( userId: $userId, linkShareId: $linkShare->getId(), - hasPassword: ($salt !== ''), + // Always true here: the guard above throws when $salt === '', so a + // link share cannot reach this point without one. + hasPassword: true, expiresAtIso: $expiresAtIso, ); diff --git a/lib/Service/SecretRequestPolicy.php b/lib/Service/SecretRequestPolicy.php index 81956b96d..ea3416ef7 100644 --- a/lib/Service/SecretRequestPolicy.php +++ b/lib/Service/SecretRequestPolicy.php @@ -496,7 +496,10 @@ public function requireApplicationSuiteId(string $applicationId): string { try { $suite = $this->suiteMapper->findActiveByOwner('application', $applicationId); - } catch (DoesNotExistException|MultipleObjectsReturnedException) { + // The mapper bounds this query with maxResults(1), so findEntity() + // can never see two rows and MultipleObjectsReturnedException is + // unreachable here. + } catch (DoesNotExistException) { throw new InvalidArgumentException( message: 'No active EncryptionSuite for application ' . $applicationId, code: 400, diff --git a/lib/Service/SecretService.php b/lib/Service/SecretService.php index 03e957ba4..36dfe12d1 100644 --- a/lib/Service/SecretService.php +++ b/lib/Service/SecretService.php @@ -327,7 +327,12 @@ public function createForApplication(array $data, string $applicationId, string try { $suite = $this->suiteMapper->findActiveByOwner('application', $applicationId); - } catch (DoesNotExistException|MultipleObjectsReturnedException) { + // No MultipleObjectsReturnedException arm: findActiveByOwner() + // sets maxResults(1) precisely so findEntity() can never see two + // rows. That bound IS the fix for the bug its docblock describes — + // the unbounded version threw mid-migration and this catch then + // reported "No active encryption suite" to a user who had two. + } catch (DoesNotExistException) { throw new SuiteBlockedException( message: 'No active EncryptionSuite for application ' . $applicationId ); @@ -427,7 +432,12 @@ public function createByApplication( try { $suite = $this->suiteMapper->findActiveByOwner('application', $applicationId); - } catch (DoesNotExistException|MultipleObjectsReturnedException) { + // No MultipleObjectsReturnedException arm: findActiveByOwner() + // sets maxResults(1) precisely so findEntity() can never see two + // rows. That bound IS the fix for the bug its docblock describes — + // the unbounded version threw mid-migration and this catch then + // reported "No active encryption suite" to a user who had two. + } catch (DoesNotExistException) { throw new SuiteBlockedException( message: 'No active EncryptionSuite for application ' . $applicationId ); @@ -1297,7 +1307,9 @@ public function assertActiveSuite(string $userId): void { private function getActiveSuiteOrBlock(string $userId): EncryptionSuite { try { return $this->suiteMapper->findActiveByOwner('user', $userId); - } catch (DoesNotExistException|MultipleObjectsReturnedException) { + // See the note above: findActiveByOwner() bounds the query with + // maxResults(1), so MultipleObjectsReturnedException is unreachable. + } catch (DoesNotExistException) { throw new SuiteBlockedException(message: 'No active encryption suite — it may be revoked or not yet created'); } }//end getActiveSuiteOrBlock() From 45d983205998452420573bedd66983e8389c3c7b Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 00:26:31 +0200 Subject: [PATCH 02/65] docs(spec): anchor KeyGeneratorRegexParser::extractLength (gate-16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gate-16 spec-coverage failed this PR with "1 changed method(s) missing @spec". The method is `extractLength()`, whose quantifier handling this PR touched; it carried no `@spec` tag, and neither does anything else in the file. Anchored to `openspec/specs/key-generator/spec.md#requirement-regex-override` — the requirement that defines the `{n}` / `{n,m}` length quantifier this method parses. While confirming the anchor I re-checked the claim the changed code rests on, because getting it wrong would silently set `$max` to 0: php > preg_match('/\{(\d+)(?:,(\d+))?\}/', '{16}', $m); // ["{16}","16"] isset($m[2]) === false php > preg_match('/\{(\d+)(?:,(\d+))?\}/', '{8,16}', $m); // ["{8,16}","8","16"] isset($m[2]) === true PHP omits a trailing unmatched group entirely rather than setting it to '', so `isset()` alone is sufficient and the `!== ''` conjunct really was unreachable. Had group 2 been a MIDDLE group it would have been '' and `(int)''` would have made `$max` 0 — which is why this one was worth proving rather than assuming. Locally the gate now reports 6 remaining, all in `src/**` Vue/JS files that are not in this PR's diff; they come from the shallow local base ref, not from this branch. CI, which diffs against the real merge-base, reported exactly the one fixed here. --- lib/Service/KeyGeneratorRegexParser.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Service/KeyGeneratorRegexParser.php b/lib/Service/KeyGeneratorRegexParser.php index 1eb3283b9..65e4ca1b6 100644 --- a/lib/Service/KeyGeneratorRegexParser.php +++ b/lib/Service/KeyGeneratorRegexParser.php @@ -110,6 +110,8 @@ static function (): bool { * @return array{0:int,1:int} The [minLength, maxLength] pair * * @throws InvalidArgumentException When no quantifier is present or the range is invalid + * + * @spec openspec/specs/key-generator/spec.md#requirement-regex-override */ public function extractLength(string $regex): array { if (preg_match('/\{(\d+)(?:,(\d+))?\}/', $regex, $matches) !== 1) { From bc4f73c81229c173cd29feb989e54b722414bc6b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:13:07 +0000 Subject: [PATCH 03/65] chore(release): 0.1.6-unstable.20260821001126 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index d69dc0c1c..e9e0a8a2b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260819081801 + 0.1.6-unstable.20260821001126 EUPL-1.2 Conduction Doriath From a1020d654269031df3932f1910bd62d089d16c0c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:34:41 +0000 Subject: [PATCH 04/65] chore(release): 0.1.6-unstable.20260821003327 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index e9e0a8a2b..a8d1ea2f6 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821001126 + 0.1.6-unstable.20260821003327 EUPL-1.2 Conduction Doriath From 18d0e2ffafe155a13f05496cdc97a2528d1e80f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:52:04 +0000 Subject: [PATCH 05/65] chore(release): 0.1.6-unstable.20260821005034 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index a8d1ea2f6..8c73afcb5 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821003327 + 0.1.6-unstable.20260821005034 EUPL-1.2 Conduction Doriath From d80b1ee29b77ff52290db1f385d46a6667026828 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:04:47 +0000 Subject: [PATCH 06/65] chore(release): 0.1.6-unstable.20260821010303 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 8c73afcb5..19e1cd4d0 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821005034 + 0.1.6-unstable.20260821010303 EUPL-1.2 Conduction Doriath From 109a81c259f0a551a2a0b56231827b5b75729028 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:19:04 +0000 Subject: [PATCH 07/65] chore(release): 0.1.6-unstable.20260821011725 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 19e1cd4d0..b9575b13a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821010303 + 0.1.6-unstable.20260821011725 EUPL-1.2 Conduction Doriath From e90f91679972600818f302b5e2b3248d73f21d15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:39:06 +0000 Subject: [PATCH 08/65] chore(release): 0.1.6-unstable.20260821013736 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index b9575b13a..9984363d9 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821011725 + 0.1.6-unstable.20260821013736 EUPL-1.2 Conduction Doriath From 4a9a12f31387906d0ffc81aa81412d8114777cf4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:54:14 +0000 Subject: [PATCH 09/65] chore(release): 0.1.6-unstable.20260821015235 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 9984363d9..0c5608484 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821013736 + 0.1.6-unstable.20260821015235 EUPL-1.2 Conduction Doriath From fd28236e7b85fb1125ff309b3178d348027a277a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:06:51 +0000 Subject: [PATCH 10/65] chore(release): 0.1.6-unstable.20260821020516 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 0c5608484..675cb10aa 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821015235 + 0.1.6-unstable.20260821020516 EUPL-1.2 Conduction Doriath From 3168d5c7ea9c98a39b8c69f86461b7fbe274cce9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:19:07 +0000 Subject: [PATCH 11/65] chore(release): 0.1.6-unstable.20260821021727 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 675cb10aa..14bc14526 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821020516 + 0.1.6-unstable.20260821021727 EUPL-1.2 Conduction Doriath From 0a53b43d708f0f79a936ee073361497b3ccaba6a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:33:50 +0000 Subject: [PATCH 12/65] chore(release): 0.1.6-unstable.20260821023224 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 14bc14526..788d66f3d 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821021727 + 0.1.6-unstable.20260821023224 EUPL-1.2 Conduction Doriath From f4670f4af8c53fc4e1e80bc91a3845056e8b382f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:49:17 +0000 Subject: [PATCH 13/65] chore(release): 0.1.6-unstable.20260821024747 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 788d66f3d..77be6979f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821023224 + 0.1.6-unstable.20260821024747 EUPL-1.2 Conduction Doriath From 1ac62facfd5b0becda922e4a34feb21314bd4fc3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:01:15 +0000 Subject: [PATCH 14/65] chore(release): 0.1.6-unstable.20260821030005 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 77be6979f..0dc26e68c 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821024747 + 0.1.6-unstable.20260821030005 EUPL-1.2 Conduction Doriath From 22f733c93d3d7142d0d241bf0fcf6e8eaea8b1de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:14:06 +0000 Subject: [PATCH 15/65] chore(release): 0.1.6-unstable.20260821031226 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 0dc26e68c..f67707651 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821030005 + 0.1.6-unstable.20260821031226 EUPL-1.2 Conduction Doriath From c611ebe7f026f5cee18a032e3ebc388ff85b1833 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:27:18 +0000 Subject: [PATCH 16/65] chore(release): 0.1.6-unstable.20260821032616 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index f67707651..3185ee04a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821031226 + 0.1.6-unstable.20260821032616 EUPL-1.2 Conduction Doriath From e858fb269cee5d3fa31f5cdbbef98961b28d2361 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:41:42 +0000 Subject: [PATCH 17/65] chore(release): 0.1.6-unstable.20260821034019 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 3185ee04a..de8442194 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821032616 + 0.1.6-unstable.20260821034019 EUPL-1.2 Conduction Doriath From 709734e122eab9b136eb7e9d356b7b585bcdf737 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:56:34 +0000 Subject: [PATCH 18/65] chore(release): 0.1.6-unstable.20260821035454 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index de8442194..7ad2a70f1 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821034019 + 0.1.6-unstable.20260821035454 EUPL-1.2 Conduction Doriath From 1b7365c6ad49d855c1c336e651f4e9b2d248e14b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:07:48 +0000 Subject: [PATCH 19/65] chore(release): 0.1.6-unstable.20260821040638 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 7ad2a70f1..7637ec64f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821035454 + 0.1.6-unstable.20260821040638 EUPL-1.2 Conduction Doriath From b687c6550eba9982e0241cfc9e5e09980e614046 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:19:20 +0000 Subject: [PATCH 20/65] chore(release): 0.1.6-unstable.20260821041737 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 7637ec64f..cff9a5de6 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821040638 + 0.1.6-unstable.20260821041737 EUPL-1.2 Conduction Doriath From 562170cff54eefe598c9c8ee51a8104a5a3541a9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:30:53 +0000 Subject: [PATCH 21/65] chore(release): 0.1.6-unstable.20260821042945 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index cff9a5de6..7d07f44df 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821041737 + 0.1.6-unstable.20260821042945 EUPL-1.2 Conduction Doriath From f7713b2ac435595e7408cb61dafb17cd03cb7541 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:43:25 +0000 Subject: [PATCH 22/65] chore(release): 0.1.6-unstable.20260821044150 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 7d07f44df..9e1a37e6d 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821042945 + 0.1.6-unstable.20260821044150 EUPL-1.2 Conduction Doriath From 734904d3adf26c7c5399b0306c90cd6e9a482793 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:01:55 +0000 Subject: [PATCH 23/65] chore(release): 0.1.6-unstable.20260821050017 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 9e1a37e6d..a973114ce 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821044150 + 0.1.6-unstable.20260821050017 EUPL-1.2 Conduction Doriath From 6d98a7d3d595ca2b9d2d1782247a497bc8a71b8f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:21:28 +0000 Subject: [PATCH 24/65] chore(release): 0.1.6-unstable.20260821052007 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index a973114ce..5f59581c2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821050017 + 0.1.6-unstable.20260821052007 EUPL-1.2 Conduction Doriath From ea061023760e70a72345cb61c8761701282cb6a1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:36:22 +0000 Subject: [PATCH 25/65] chore(release): 0.1.6-unstable.20260821053440 [skip ci] --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5f59581c2..caad61cd0 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. ]]> - 0.1.6-unstable.20260821052007 + 0.1.6-unstable.20260821053440 EUPL-1.2 Conduction Doriath From 1404912e69c048b15f57aad4091a8e57c757d253 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 09:13:37 +0200 Subject: [PATCH 26/65] chore(deps): web-token/jwt-library 3 -> 4, and fix a test that never ran its assertion (#321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes, both verified against the suite rather than inferred. 1. web-token/jwt-library ^3.4.10 -> ^4.1.7 ------------------------------------------ doriath uses a narrow slice of JOSE — AlgorithmManager, JWK, JWKFactory, the ES/RS signature algorithms, JWS, JWSVerifier, CompactSerializer and JWSSerializerManager — and v4 keeps all of them under the same namespaces. No call-site change was needed. phpcs, phpstan, psalm, phpmd all exit 0 phpunit 1059 tests, same count as development 2. The RSA round-trip test was erroring on every run ---------------------------------------------------- development has a standing error, unrelated to this bump: EncryptServiceTest::testRsaEncryptUnderCertificateDecryptsWithMatchingPrivateKey RuntimeException: RSA encryption failed: error:0200006E:rsa routines::data too large for key size Ruled out as jwt-library fallout before touching it: EncryptService contains no reference to Jose, JWK, JWS or jwt at all — it is plain openssl — and the error reproduces on unmodified development with jwt-library 3 still installed. The cause is a fixture that contradicts the app's own invariant. EncryptService pads to `RSA_BLOCK_SIZE = 512` bytes and encrypts with `OPENSSL_NO_PADDING`, so the key must be exactly 4096 bits. Everything in production agrees: CertificateAuthorityService mints 4096-bit keys in three places, SeedDevelopmentData mints 4096, and ApplicationLifecycleService explicitly rejects a sub-4096 key. Only this test generated its CA and user keys at 2048 — a 256-byte key handed a 512-byte block. Both are now 4096, with a comment saying why the number is load-bearing rather than merely cautious, so the next person does not "simplify" it back for speed. Worth noting what the test was actually doing before. It carries a deliberate escape hatch for constrained-CLI OpenSSL artifacts (RNG re-seed failures, DECODER provider failures), which is legitimate — but "data too large for key size" is not in that list, so it fell through as a hard error instead. The assertion count is the clearest evidence of what was lost: before: Assertions: 2, Errors: 1 (died at the encrypt call) after: Assertions: 3, no error (round-trip actually asserted) The whole point of the test — encrypt under the served certificate, decrypt with the separately stored private key, prove the two halves have not drifted — had never once executed. It failed loudly enough to be visible and quietly enough to be tolerated. doriath suite: 1059 tests, 3316 assertions, 0 errors (development: 1059 tests, 3315 assertions, 1 error) Closes #262 Co-authored-by: Conduction Release Bot --- composer.json | 2 +- composer.lock | 7980 ++++++++++----------- tests/Unit/Service/EncryptServiceTest.php | 13 +- 3 files changed, 3766 insertions(+), 4229 deletions(-) diff --git a/composer.json b/composer.json index 38ff42f60..838124918 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": "^8.3", "phpseclib/phpseclib": "^3.0", "ramsey/uuid": "^4.9", - "web-token/jwt-library": "^3.4.10" + "web-token/jwt-library": "^4.0" }, "require-dev": { "conduction/coding-standard": "^1.0", diff --git a/composer.lock b/composer.lock index dcd889201..590df8c4f 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": "98c32247bf44ec987ebc460606634b03", + "content-hash": "2cda7ef6449907314c668263e4462313", "packages": [ { "name": "brick/math", @@ -185,102 +185,6 @@ }, "time": "2020-10-15T08:29:30+00:00" }, - { - "name": "paragonie/sodium_compat", - "version": "v2.5.1", - "source": { - "type": "git", - "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "e4d4933af9463a96a1a3cbaeb94327b90e3350ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e4d4933af9463a96a1a3cbaeb94327b90e3350ca", - "reference": "e4d4933af9463a96a1a3cbaeb94327b90e3350ca", - "shasum": "" - }, - "require": { - "php": "^8.1", - "php-64bit": "*" - }, - "require-dev": { - "infection/infection": "^0", - "nikic/php-fuzzer": "^0", - "phpunit/phpunit": "^7|^8|^9|^10|^11", - "vimeo/psalm": "^4|^5|^6" - }, - "suggest": { - "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "files": [ - "autoload.php" - ], - "psr-4": { - "ParagonIE\\Sodium\\": "namespaced/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com" - }, - { - "name": "Frank Denis", - "email": "jedisct1@pureftpd.org" - } - ], - "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", - "keywords": [ - "Authentication", - "BLAKE2b", - "ChaCha20", - "ChaCha20-Poly1305", - "Chapoly", - "Curve25519", - "Ed25519", - "EdDSA", - "Edwards-curve Digital Signature Algorithm", - "Elliptic Curve Diffie-Hellman", - "Poly1305", - "Pure-PHP cryptography", - "RFC 7748", - "RFC 8032", - "Salpoly", - "Salsa20", - "X25519", - "XChaCha20-Poly1305", - "XSalsa20-Poly1305", - "Xchacha20", - "Xsalsa20", - "aead", - "cryptography", - "ecdh", - "elliptic curve", - "elliptic curve cryptography", - "encryption", - "libsodium", - "php", - "public-key cryptography", - "secret-key cryptography", - "side-channel resistant" - ], - "support": { - "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v2.5.1" - }, - "time": "2026-08-18T15:39:41+00:00" - }, { "name": "phpseclib/phpseclib", "version": "3.0.56", @@ -391,55 +295,6 @@ ], "time": "2026-08-03T04:36:50+00:00" }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, { "name": "psr/clock", "version": "1.0.0", @@ -489,31 +344,53 @@ "time": "2022-11-25T14:36:26+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "ramsey/collection", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" } }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Ramsey\\Collection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -522,158 +399,150 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "array", + "collection", + "hash", + "map", + "queue", + "set" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { - "name": "psr/http-client", - "version": "1.0.3", + "name": "ramsey/uuid", + "version": "4.9.3", "source": { "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + "url": "https://github.com/ramsey/uuid.git", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" + "brick/math": ">=0.8.16 <=0.18", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" + "captainhook": { + "force-install": true } }, "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { - "Psr\\Http\\Client\\": "src/" + "Ramsey\\Uuid\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "keywords": [ - "http", - "http-client", - "psr", - "psr-18" + "guid", + "identifier", + "uuid" ], "support": { - "source": "https://github.com/php-fig/http-client" + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.3" }, - "time": "2023-09-23T14:17:50+00:00" + "time": "2026-06-18T03:57:49+00:00" }, { - "name": "psr/http-factory", - "version": "1.1.0", + "name": "spomky-labs/pki-framework", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "e0d61661962560c1cedfef02b51b431e720aae78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/e0d61661962560c1cedfef02b51b431e720aae78", + "reference": "e0d61661962560c1cedfef02b51b431e720aae78", "shasum": "" }, "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18", + "ext-mbstring": "*", + "php": ">=8.1" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory" - }, - "time": "2024-04-15T12:06:14+00:00" - }, - { - "name": "psr/http-message", - "version": "2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" + "require-dev": { + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.28|^0.29|^0.31", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3|^2.0", + "phpstan/phpstan": "^1.8|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1|^2.0", + "phpstan/phpstan-strict-rules": "^1.3|^2.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", + "rector/rector": "^1.0|^2.0", + "roave/security-advisories": "dev-latest", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symplify/easy-coding-standard": "^12.0 || ^13.0" }, - "require": { - "php": "^7.2 || ^8.0" + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "SpomkyLabs\\Pki\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -682,51 +551,98 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" + }, + { + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" } ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.5.0" }, - "time": "2023-04-04T09:54:51+00:00" + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-07-16T10:28:45+00:00" }, { - "name": "psr/log", - "version": "3.0.2", + "name": "web-token/jwt-library", + "version": "4.1.7", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "url": "https://github.com/web-token/jwt-library.git", + "reference": "fbcbf2c276d04d8b056f5c2957815abd5dfb704d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/fbcbf2c276d04d8b056f5c2957815abd5dfb704d", + "reference": "fbcbf2c276d04d8b056f5c2957815abd5dfb704d", "shasum": "" }, "require": { - "php": ">=8.0.0" + "brick/math": "^0.12|^0.13|^0.14|^0.15|^0.16|^0.17", + "php": ">=8.2", + "psr/clock": "^1.0", + "spomky-labs/pki-framework": "^1.2.1" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } + "conflict": { + "spomky-labs/jose": "*" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", + "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance", + "ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)", + "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", + "paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", + "spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (AxxxKW, AxxxGCMKW, PBES2-HSxxx+AyyyKW...)", + "symfony/console": "Needed to use console commands", + "symfony/http-client": "To enable JKU/X5U support." }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Jose\\Component\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -735,70 +651,86 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "JWT library", + "homepage": "https://github.com/web-token", "keywords": [ - "log", - "psr", - "psr-3" + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "issues": "https://github.com/web-token/jwt-library/issues", + "source": "https://github.com/web-token/jwt-library/tree/4.1.7" }, - "time": "2024-09-11T13:17:53+00:00" - }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-06-06T18:12:39+00:00" + } + ], + "packages-dev": [ { - "name": "ramsey/collection", - "version": "2.1.1", + "name": "amphp/amp", + "version": "v2.6.5", "source": { "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + "url": "https://github.com/amphp/amp.git", + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "url": "https://api.github.com/repos/amphp/amp/zipball/d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", "shasum": "" }, "require": { - "php": "^8.1" + "php": ">=7.1" }, "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.45", - "fakerphp/faker": "^1.24", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^2.1", - "mockery/mockery": "^1.6", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpspec/prophecy-phpunit": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", - "ramsey/coding-standard": "^2.3", - "ramsey/conventional-commits": "^1.6", - "roave/security-advisories": "dev-latest" + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", - "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" - } - }, "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], "psr-4": { - "Ramsey\\Collection\\": "src/" + "Amp\\": "lib" } }, "notification-url": "https://packagist.org/downloads/", @@ -807,150 +739,158 @@ ], "authors": [ { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP library for representing and manipulating collections.", - "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" ], "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.1" + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.5" }, - "time": "2025-03-22T05:38:12+00:00" + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-09-03T19:41:28+00:00" }, { - "name": "ramsey/uuid", - "version": "4.9.3", + "name": "amphp/byte-stream", + "version": "v1.8.2", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" + "url": "https://github.com/amphp/byte-stream.git", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", - "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { - "brick/math": ">=0.8.16 <=0.18", - "php": "^8.0", - "ramsey/collection": "^1.2 || ^2.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" + "amphp/amp": "^2", + "php": ">=7.1" }, "require-dev": { - "captainhook/captainhook": "^5.25", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "ergebnis/composer-normalize": "^2.47", - "mockery/mockery": "^1.6", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.6", - "php-mock/php-mock-mockery": "^1.5", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpbench/phpbench": "^1.2.14", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "slevomat/coding-standard": "^8.18", - "squizlabs/php_codesniffer": "^3.13" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "captainhook": { - "force-install": true - } - }, "autoload": { "files": [ - "src/functions.php" + "lib/functions.php" ], "psr-4": { - "Ramsey\\Uuid\\": "src/" + "Amp\\ByteStream\\": "lib" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", "keywords": [ - "guid", - "identifier", - "uuid" + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" ], "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.3" + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, - "time": "2026-06-18T03:57:49+00:00" + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-13T18:00:56+00:00" }, { - "name": "spomky-labs/pki-framework", - "version": "1.5.0", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "e0d61661962560c1cedfef02b51b431e720aae78" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/e0d61661962560c1cedfef02b51b431e720aae78", - "reference": "e0d61661962560c1cedfef02b51b431e720aae78", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18", - "ext-mbstring": "*", - "php": ">=8.1" + "php": "^7.4 || ^8.0" }, - "require-dev": { - "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", - "ext-gmp": "*", - "ext-openssl": "*", - "infection/infection": "^0.28|^0.29|^0.31", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/extension-installer": "^1.3|^2.0", - "phpstan/phpstan": "^1.8|^2.0", - "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", - "phpstan/phpstan-phpunit": "^1.1|^2.0", - "phpstan/phpstan-strict-rules": "^1.3|^2.0", - "phpunit/phpunit": "^10.1|^11.0|^12.0", - "rector/rector": "^1.0|^2.0", - "roave/security-advisories": "dev-latest", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symplify/easy-coding-standard": "^12.0 || ^13.0" + "conflict": { + "phpstan/phpstan": "<1.11.10" }, - "suggest": { - "ext-bcmath": "For better performance (or GMP)", - "ext-gmp": "For better performance (or BCMath)", - "ext-openssl": "For OpenSSL based cyphering" + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" }, "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { - "SpomkyLabs\\Pki\\": "src/" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -959,113 +899,69 @@ ], "authors": [ { - "name": "Joni Eskelinen", - "email": "jonieske@gmail.com", - "role": "Original developer" - }, - { - "name": "Florent Morselli", - "email": "florent.morselli@spomky-labs.com", - "role": "Spomky-Labs PKI Framework developer" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", - "homepage": "https://github.com/spomky-labs/pki-framework", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "DER", - "Private Key", - "ac", - "algorithm identifier", - "asn.1", - "asn1", - "attribute certificate", - "certificate", - "certification request", - "cryptography", - "csr", - "decrypt", - "ec", - "encrypt", - "pem", - "pkcs", - "public key", - "rsa", - "sign", - "signature", - "verify", - "x.509", - "x.690", - "x509", - "x690" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.5.0" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { - "url": "https://github.com/Spomky", + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", "type": "github" }, { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "time": "2026-07-16T10:28:45+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { - "name": "symfony/console", - "version": "v7.4.16", + "name": "composer/semver", + "version": "3.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d" + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f4c69c9aed03abf933b294257d618bdd9b30a06d", - "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Composer\\Semver\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1073,76 +969,76 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", + "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ - "cli", - "command-line", - "console", - "terminal" + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.16" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { - "url": "https://symfony.com/sponsor", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2026-07-31T12:37:14+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.7.1", + "name": "composer/spdx-licenses", + "version": "1.5.9", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", "shasum": "" }, "require": { - "php": ">=8.1" + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, "branch-alias": { - "dev-main": "3.7-dev" + "dev-main": "1.x-dev" } }, "autoload": { - "files": [ - "function.php" - ] + "psr-4": { + "Composer\\Spdx\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1150,97 +1046,77 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" } ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" }, "funding": [ { - "url": "https://symfony.com/sponsor", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/composer", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2025-05-12T21:07:07+00:00" }, { - "name": "symfony/http-client", - "version": "v7.4.15", + "name": "composer/xdebug-handler", + "version": "3.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "817bb83ef06717f67ab72a3f03e3b63fbe138de1" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/817bb83ef06717f67ab72a3f03e3b63fbe138de1", - "reference": "817bb83ef06717f67ab72a3f03e3b63fbe138de1", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.4|^3.5.2", - "symfony/polyfill-php83": "^1.29", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "amphp/amp": "<2.5", - "amphp/socket": "<1.1", - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "amphp/http-client": "^4.2.1|^5.0", - "amphp/http-tunnel": "^1.0|^2.0", - "guzzlehttp/promises": "^1.4|^2.0", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Composer\\XdebugHandler\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1248,240 +1124,182 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", + "description": "Restarts a process without Xdebug.", "keywords": [ - "http" + "Xdebug", + "performance" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.15" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/composer", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2026-07-29T07:12:33+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "symfony/http-client-contracts", - "version": "v3.7.1", + "name": "conduction/coding-standard", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "41fc42d276aeff21192465331ebbab7d83a743c0" + "url": "https://github.com/ConductionNL/coding-standard.git", + "reference": "a1854f13cb735e46ecd010767593b4d7bc90d974" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0", - "reference": "41fc42d276aeff21192465331ebbab7d83a743c0", + "url": "https://api.github.com/repos/ConductionNL/coding-standard/zipball/a1854f13cb735e46ecd010767593b4d7bc90d974", + "reference": "a1854f13cb735e46ecd010767593b4d7bc90d974", "shasum": "" }, "require": { - "php": ">=8.1" + "nextcloud/coding-standard": "^1.4", + "php": "^8.1" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] + "Conduction\\CodingStandard\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "EUPL-1.2" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Conduction", + "homepage": "https://conduction.nl" } ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "description": "Conduction coding standards for the PHP CS Fixer. Extends nextcloud/coding-standard — adds rules, never overrides them.", + "homepage": "https://github.com/ConductionNL/coding-standard", + "keywords": [ + "coding-standard", + "conduction", + "dev", + "nextcloud", + "php-cs-fixer" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1" + "docs": "https://docs.conduction.nl/WayOfWork/ci-cd/", + "issues": "https://github.com/ConductionNL/coding-standard/issues", + "source": "https://github.com/ConductionNL/coding-standard/tree/v1.0.0" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-08-12T08:27:21+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.37.0", + "name": "conduction/hydra-gates", + "version": "v1.8.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + "url": "https://github.com/ConductionNL/.github.git", + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "url": "https://api.github.com/repos/ConductionNL/.github/zipball/3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", "shasum": "" }, "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": ">=8.1" }, + "bin": [ + "hydra-gates/bin/hydra-gates" + ], "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "hydra-gates": { + "runner": "hydra-gates/scripts/run-hydra-gates.sh", + "helpers": "hydra-gates/scripts/lib", + "schemas": "hydra-gates/scripts/schemas" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "EUPL-1.2" ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Conduction", + "homepage": "https://conduction.nl" } ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "Hydra's mechanical quality gates, packaged so any repo can run them against its own diff. The exit code is the failure COUNT.", + "homepage": "https://github.com/ConductionNL/.github/tree/main/hydra-gates", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "conduction", + "gates", + "nextcloud", + "quality", + "static-analysis" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + "docs": "https://github.com/ConductionNL/.github/blob/main/hydra-gates/README.md", + "issues": "https://github.com/ConductionNL/.github/issues", + "source": "https://github.com/ConductionNL/.github/tree/v1.8.2" }, - "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-04-10T16:19:22+00:00" + "time": "2026-08-20T09:37:12+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.41.0", + "name": "consolidation/annotated-command", + "version": "4.10.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "78abf3b6853d7ff9044babd2b9c002ff433207d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/78abf3b6853d7ff9044babd2b9c002ff433207d8", + "reference": "78abf3b6853d7ff9044babd2b9c002ff433207d8", "shasum": "" }, "require": { - "php": ">=7.2" + "consolidation/output-formatters": "^4.3.1", + "php": ">=7.1.3", + "psr/log": "^1 || ^2 || ^3", + "symfony/console": "^4.4.8 || ^5 || ^6 || ^7 || ^8", + "symfony/event-dispatcher": "^4.4.8 || ^5 || ^6 || ^7 || ^8", + "symfony/finder": "^4.4.8 || ^5 || ^6 || ^7 || ^8" }, - "suggest": { - "ext-intl": "For best performance" + "require-dev": { + "composer-runtime-api": "^2.0", + "phpunit/phpunit": "^7.5.20 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3", + "yoast/phpunit-polyfills": "^0.2.0" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "4.x-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + "Consolidation\\AnnotatedCommand\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1490,84 +1308,59 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], + "description": "Initialize Symfony Console commands from annotated command class methods.", "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + "issues": "https://github.com/consolidation/annotated-command/issues", + "source": "https://github.com/consolidation/annotated-command/tree/4.10.5" }, - "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-07-28T08:25:59+00:00" + "time": "2026-03-29T00:50:52+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "name": "consolidation/config", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + "url": "https://github.com/consolidation/config.git", + "reference": "adcb989d789f7e0984121492b0377a1def3a6dc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "url": "https://api.github.com/repos/consolidation/config/zipball/adcb989d789f7e0984121492b0377a1def3a6dc8", + "reference": "adcb989d789f7e0984121492b0377a1def3a6dc8", "shasum": "" }, "require": { - "php": ">=7.2" + "dflydev/dot-access-data": "^3", + "grasmash/expander": "^3", + "php": ">=8.2.0", + "symfony/event-dispatcher": "^6 || ^7 || ^8" + }, + "require-dev": { + "ext-json": "*", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3", + "symfony/console": "^7.4 || ^8", + "symfony/yaml": "^7 || ^8", + "yoast/phpunit-polyfills": "^1" }, "suggest": { - "ext-intl": "For best performance" + "symfony/event-dispatcher": "Required to inject configuration into Command options", + "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "3.x-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Consolidation\\Config\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1575,84 +1368,50 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], + "description": "Provide configuration services for a commandline tool.", "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + "issues": "https://github.com/consolidation/config/issues", + "source": "https://github.com/consolidation/config/tree/3.2.1" }, - "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-05-25T13:48:31+00:00" + "time": "2026-03-28T23:38:17+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", + "name": "consolidation/log", + "version": "3.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + "url": "https://github.com/consolidation/log.git", + "reference": "a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "url": "https://api.github.com/repos/consolidation/log/zipball/a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6", + "reference": "a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" + "php": ">=8.0.0", + "psr/log": "^3", + "symfony/console": "^5 || ^6 || ^7 || ^8" }, - "suggest": { - "ext-mbstring": "For best performance" + "require-dev": { + "phpunit/phpunit": "^7.5.20 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3", + "yoast/phpunit-polyfills": "^0.2.0" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "platform": { + "php": "8.2.17" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Consolidation\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1661,80 +1420,53 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" } ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], + "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + "issues": "https://github.com/consolidation/log/issues", + "source": "https://github.com/consolidation/log/tree/3.1.2" }, - "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-05-27T06:59:30+00:00" + "time": "2026-03-28T23:36:49+00:00" }, { - "name": "symfony/polyfill-php83", - "version": "v1.41.0", + "name": "consolidation/output-formatters", + "version": "4.7.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "a112df9a74854c8438b33b334ed619fa43edf31a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", - "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a112df9a74854c8438b33b334ed619fa43edf31a", + "reference": "a112df9a74854c8438b33b334ed619fa43edf31a", "shasum": "" }, "require": { - "php": ">=7.2" + "dflydev/dot-access-data": "^1.1.0 || ^2 || ^3", + "php": ">=7.1.3", + "symfony/console": "^4 || ^5 || ^6 || ^7 || ^8", + "symfony/finder": "^4 || ^5 || ^6 || ^7 || ^8" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "require-dev": { + "php-coveralls/php-coveralls": "^2.4.2", + "phpunit/phpunit": "^7 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3", + "symfony/var-dumper": "^4 || ^5 || ^6 || ^7 || ^8", + "symfony/yaml": "^4 || ^5 || ^6 || ^7 || ^8", + "yoast/phpunit-polyfills": "^1" }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Consolidation\\OutputFormatters\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1742,84 +1474,72 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" } ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], + "description": "Format text by applying transformations provided by plug-in formatters.", "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" + "issues": "https://github.com/consolidation/output-formatters/issues", + "source": "https://github.com/consolidation/output-formatters/tree/4.7.1" }, - "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-07-01T12:47:55+00:00" + "time": "2026-03-28T23:34:39+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.7.1", + "name": "consolidation/robo", + "version": "5.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" + "url": "https://github.com/consolidation/robo.git", + "reference": "6d02c7d800b5e1a3a8977ae74d23bce723486025" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", + "url": "https://api.github.com/repos/consolidation/robo/zipball/6d02c7d800b5e1a3a8977ae74d23bce723486025", + "reference": "6d02c7d800b5e1a3a8977ae74d23bce723486025", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "consolidation/annotated-command": "^4.8.1", + "consolidation/config": "^3", + "consolidation/log": "^3", + "consolidation/output-formatters": "^4.1.2", + "league/container": "^3.3.1 || ^4.0", + "php": ">=8.2", + "phpowermove/docblock": "^4.0", + "symfony/console": "^6 || ^7", + "symfony/event-dispatcher": "^6 || ^7", + "symfony/filesystem": "^6 || ^7", + "symfony/finder": "^6 || ^7", + "symfony/process": "^6 || ^7", + "symfony/yaml": "^6 || ^7" }, "conflict": { - "ext-psr": "<1.1|>=2" + "codegyre/robo": "*" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } + "require-dev": { + "natxet/cssmin": "3.0.4", + "patchwork/jsqueeze": "^2", + "pear/archive_tar": "^1.4.4", + "phpunit/phpunit": "^7.5.20 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.6", + "yoast/phpunit-polyfills": "^0.2.0" }, + "suggest": { + "consolidation/self-update": "For self-updating a phar-based app built with Robo", + "natxet/cssmin": "For minifying CSS files in taskMinify", + "patchwork/jsqueeze": "For minifying JS files in taskMinify", + "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively.", + "totten/lurkerlite": "For monitoring filesystem changes in taskWatch" + }, + "bin": [ + "robo" + ], + "type": "library", "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] + "Robo\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1827,272 +1547,224 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Davert", + "email": "davert.php@resend.cc" } ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], + "description": "Modern task runner", "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" + "issues": "https://github.com/consolidation/robo/issues", + "source": "https://github.com/consolidation/robo/tree/5.1.1" }, - "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" + "time": "2025-11-14T23:30:05+00:00" }, { - "name": "symfony/string", - "version": "v7.4.15", + "name": "cyclonedx/cyclonedx-library", + "version": "v4.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "e394af32256bf9e7bf80849d95e589167c10097b" + "url": "https://github.com/CycloneDX/cyclonedx-php-library.git", + "reference": "c95a371894c4e32bea42bfa024f2ab5092cbb292" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", - "reference": "e394af32256bf9e7bf80849d95e589167c10097b", + "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-library/zipball/c95a371894c4e32bea42bfa024f2ab5092cbb292", + "reference": "c95a371894c4e32bea42bfa024f2ab5092cbb292", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "opis/json-schema": "^2.0", + "php": "^8.1" }, "conflict": { - "symfony/translation-contracts": "<2.5" + "composer/spdx-licenses": "<1.5" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "composer/spdx-licenses": "^1.5", + "ext-simplexml": "*", + "roave/security-advisories": "dev-latest" + }, + "suggest": { + "composer/spdx-licenses": "used in license factory", + "package-url/packageurl-php": "for parsing and crafting PackageURL strings" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + }, + "composer-normalize": { + "indent-size": 4, + "indent-style": "space" + } + }, "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "CycloneDX\\Core\\": "src/Core/", + "CycloneDX\\Contrib\\": "src/Contrib/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jan Kowalleck", + "email": "jan.kowalleck@gmail.com", + "homepage": "https://github.com/jkowalleck" } ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", + "description": "Work with CycloneDX documents.", + "homepage": "https://github.com/CycloneDX/cyclonedx-php-library/#readme", "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" + "CycloneDX", + "HBOM", + "OBOM", + "SBOM", + "SaaSBOM", + "bill-of-materials", + "bom", + "models", + "normalizer", + "owasp", + "package-url", + "purl", + "serializer", + "software-bill-of-materials", + "spdx", + "validator", + "vdr", + "vex" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.15" + "docs": "https://cyclonedx-php-library.readthedocs.io", + "issues": "https://github.com/CycloneDX/cyclonedx-php-library/issues", + "source": "https://github.com/CycloneDX/cyclonedx-php-library/" }, "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" + "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX", + "type": "other" } ], - "time": "2026-07-28T07:33:02+00:00" + "time": "2026-02-17T11:46:50+00:00" }, { - "name": "web-token/jwt-library", - "version": "3.4.10", + "name": "cyclonedx/cyclonedx-php-composer", + "version": "v6.2.0", "source": { "type": "git", - "url": "https://github.com/web-token/jwt-library.git", - "reference": "8c38010d971c2313406fa1376d763cee745a833b" + "url": "https://github.com/CycloneDX/cyclonedx-php-composer.git", + "reference": "934440a5ef7c3c3cdb58c3c3d389d412630ccbf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/8c38010d971c2313406fa1376d763cee745a833b", - "reference": "8c38010d971c2313406fa1376d763cee745a833b", + "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-composer/zipball/934440a5ef7c3c3cdb58c3c3d389d412630ccbf6", + "reference": "934440a5ef7c3c3cdb58c3c3d389d412630ccbf6", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12", - "ext-json": "*", - "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.6|^3.0", - "paragonie/sodium_compat": "^1.20|^2.0", - "php": ">=8.1", - "psr/cache": "^2.0|^3.0", - "psr/clock": "^1.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "spomky-labs/pki-framework": "^1.2.1", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/polyfill-mbstring": "^1.12" + "composer-plugin-api": "^2.3", + "composer/spdx-licenses": "^1.5.7", + "cyclonedx/cyclonedx-library": "^4.0", + "package-url/packageurl-php": "^1.0", + "php": "^8.1" }, - "conflict": { - "spomky-labs/jose": "*" + "require-dev": { + "composer/composer": "^2.3.0", + "marc-mabe/php-enum": "^4.6", + "roave/security-advisories": "dev-latest" }, - "suggest": { - "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", - "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance", - "ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)", - "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW...)", - "symfony/http-client": "To enable JKU/X5U support." + "type": "composer-plugin", + "extra": { + "class": "CycloneDX\\Composer\\Plugin", + "branch-alias": { + "dev-master": "6.x-dev" + }, + "composer-normalize": { + "indent-size": 4, + "indent-style": "space" + } }, - "type": "library", "autoload": { "psr-4": { - "Jose\\Component\\": "" + "CycloneDX\\Composer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-token/jwt-framework/contributors" + "name": "Jan Kowalleck", + "email": "jan.kowalleck@gmail.com", + "homepage": "https://github.com/jkowalleck" } ], - "description": "JWT library", - "homepage": "https://github.com/web-token", + "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from PHP Composer projects", + "homepage": "https://github.com/CycloneDX/cyclonedx-php-composer/#readme", "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" + "CycloneDX", + "SBOM", + "bill-of-materials", + "bom", + "composer", + "package-url", + "purl", + "software-bill-of-materials", + "spdx" ], "support": { - "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.4.10" + "issues": "https://github.com/CycloneDX/cyclonedx-php-composer/issues", + "source": "https://github.com/CycloneDX/cyclonedx-php-composer/" }, "funding": [ { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" + "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX", + "type": "other" } ], - "time": "2026-06-06T16:30:13+00:00" - } - ], - "packages-dev": [ + "time": "2026-02-17T13:23:10+00:00" + }, { - "name": "amphp/amp", - "version": "v2.6.5", + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.2.1", "source": { "type": "git", - "url": "https://github.com/amphp/amp.git", - "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207" + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", - "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", - "shasum": "" + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "shasum": "" }, "require": { - "php": ">=7.1" + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", + "composer/composer": "^2.2", "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "react/promise": "^2", - "vimeo/psalm": "^3.12" + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" }, - "type": "library", "autoload": { - "files": [ - "lib/functions.php", - "lib/Internal/functions.php" - ], "psr-4": { - "Amp\\": "lib" + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2101,81 +1773,93 @@ ], "authors": [ { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.com" + "name": "Franck Nijhof", + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" }, { - "name": "Niklas Keller", - "email": "me@kelunik.com" + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" } ], - "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "https://amphp.org/amp", + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", "keywords": [ - "async", - "asynchronous", - "awaitable", - "concurrency", - "event", - "event-loop", - "future", - "non-blocking", - "promise" + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" ], "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.5" + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" }, "funding": [ { - "url": "https://github.com/amphp", + "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-09-03T19:41:28+00:00" + "time": "2026-05-06T08:26:05+00:00" }, { - "name": "amphp/byte-stream", - "version": "v1.8.2", + "name": "dflydev/dot-access-data", + "version": "v3.0.3", "source": { "type": "git", - "url": "https://github.com/amphp/byte-stream.git", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { - "amphp/amp": "^2", - "php": ">=7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { - "files": [ - "lib/functions.php" - ], "psr-4": { - "Amp\\ByteStream\\": "lib" + "Dflydev\\DotAccessData\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2184,222 +1868,176 @@ ], "authors": [ { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" }, { - "name": "Niklas Keller", - "email": "me@kelunik.com" + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" } ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "https://amphp.org/byte-stream", + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" + "access", + "data", + "dot", + "notation" ], "support": { - "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2024-04-13T18:00:56+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { - "name": "composer/pcre", - "version": "3.3.2", + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", "source": { "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<1.11.10" + "php": ">=5.3.2" }, "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" }, "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { "psr-4": { - "Composer\\Pcre\\": "src" + "XdgBaseDir\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], + "description": "implementation of xdg base directory specification for php", "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2019-12-04T15:06:13+00:00" }, { - "name": "composer/semver", - "version": "3.4.4", + "name": "doctrine/deprecations", + "version": "1.1.6", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" }, + "type": "library", "autoload": { "psr-4": { - "Composer\\Semver\\": "src" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { - "name": "composer/spdx-licenses", - "version": "1.5.9", + "name": "edgedesign/phpqa", + "version": "v1.27.2", "source": { "type": "git", - "url": "https://github.com/composer/spdx-licenses.git", - "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" + "url": "https://github.com/EdgedesignCZ/phpqa.git", + "reference": "ad97e48c8dfe406f3fab5f6a6fb0604035f7a3ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", - "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", + "url": "https://api.github.com/repos/EdgedesignCZ/phpqa/zipball/ad97e48c8dfe406f3fab5f6a6fb0604035f7a3ea", + "reference": "ad97e48c8dfe406f3fab5f6a6fb0604035f7a3ea", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "consolidation/robo": "~0.5|>=1", + "ext-xsl": "*", + "php": ">=5.4", + "twig/twig": "~1.38|~2.7|>=3" }, "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" + "hamcrest/hamcrest-php": ">=2.0.1", + "phpunit/phpunit": ">=4.8.28" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } + "suggest": { + "deptrac/deptrac": "Enforce rules for dependencies between software layers", + "enlightn/security-checker": "Check composer.lock for known security issues", + "friendsofphp/php-cs-fixer": "A tool to automatically fix PHP coding standards issues", + "pdepend/pdepend": "Analyze you the quality of your design in terms of extensibility, reusability and maintainability", + "php-parallel-lint/php-console-highlighter": "Colored output in parallel-lint", + "php-parallel-lint/php-parallel-lint": "Check PHP syntax", + "phploc/phploc": "Abandoned measuring the size of a PHP project", + "phpmd/phpmd": "user friendly metrics from pdepend", + "phpmetrics/phpmetrics": "Metrics about PHP project and classes in HTML report", + "phpstan/phpstan": "PHP Static Analysis Tool - discover bugs in your code without running it!", + "phpunit/phpunit": "The PHP Unit Testing framework", + "psalm/phar": "A static analysis tool for finding errors in PHP applications", + "sebastian/phpcpd": "Abandoned copy-paste detector", + "squizlabs/php_codesniffer": "Detect coding standard violation (phpcs) and fix them (phpcbf)" }, + "bin": [ + "phpqa" + ], + "type": "library", "autoload": { + "files": [ + "src/report.php", + "src/paths.php" + ], "psr-4": { - "Composer\\Spdx\\": "src" + "Edge\\QA\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2408,260 +2046,219 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" + "name": "Zdenek Drahos", + "email": "drahoszdenek@gmail.com" } ], - "description": "SPDX licenses list and validation library.", + "description": "Analyze PHP code with one command.", "keywords": [ - "license", - "spdx", - "validator" + "code analysis", + "qa", + "static analysis" ], "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" + "docs": "https://edgedesigncz.github.io/phpqa/", + "issues": "https://github.com/EdgedesignCZ/phpqa/issues", + "source": "https://github.com/EdgedesignCZ/phpqa" }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2025-05-12T21:07:07+00:00" + "time": "2025-11-22T07:41:40+00:00" }, { - "name": "composer/xdebug-handler", - "version": "3.0.5", + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", "shasum": "" }, "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" }, "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + "phpunit/phpunit": "^7.0 || ^8.0" }, "type": "library", "autoload": { "psr-4": { - "Composer\\XdebugHandler\\": "src" + "AdvancedJsonRpc\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "ISC" ], "authors": [ { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" + "name": "Felix Becker", + "email": "felix.b@outlook.com" } ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], + "description": "A more advanced JSONRPC implementation", "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" + "time": "2021-06-11T22:34:44+00:00" }, { - "name": "conduction/coding-standard", - "version": "v1.0.0", + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.3", "source": { "type": "git", - "url": "https://github.com/ConductionNL/coding-standard.git", - "reference": "a1854f13cb735e46ecd010767593b4d7bc90d974" + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ConductionNL/coding-standard/zipball/a1854f13cb735e46ecd010767593b4d7bc90d974", - "reference": "a1854f13cb735e46ecd010767593b4d7bc90d974", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", "shasum": "" }, "require": { - "nextcloud/coding-standard": "^1.4", - "php": "^8.1" + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, "autoload": { "psr-4": { - "Conduction\\CodingStandard\\": "src" + "LanguageServerProtocol\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "EUPL-1.2" + "ISC" ], "authors": [ { - "name": "Conduction", - "homepage": "https://conduction.nl" + "name": "Felix Becker", + "email": "felix.b@outlook.com" } ], - "description": "Conduction coding standards for the PHP CS Fixer. Extends nextcloud/coding-standard — adds rules, never overrides them.", - "homepage": "https://github.com/ConductionNL/coding-standard", + "description": "PHP classes for the Language Server Protocol", "keywords": [ - "coding-standard", - "conduction", - "dev", - "nextcloud", - "php-cs-fixer" + "language", + "microsoft", + "php", + "server" ], "support": { - "docs": "https://docs.conduction.nl/WayOfWork/ci-cd/", - "issues": "https://github.com/ConductionNL/coding-standard/issues", - "source": "https://github.com/ConductionNL/coding-standard/tree/v1.0.0" + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" }, - "time": "2026-08-12T08:27:21+00:00" + "time": "2024-04-30T00:40:11+00:00" }, { - "name": "conduction/hydra-gates", - "version": "v1.8.2", + "name": "fidry/cpu-core-counter", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/ConductionNL/.github.git", - "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ConductionNL/.github/zipball/3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", - "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { - "php": ">=8.1" + "php": "^7.2 || ^8.0" }, - "bin": [ - "hydra-gates/bin/hydra-gates" - ], - "type": "library", - "extra": { - "hydra-gates": { - "runner": "hydra-gates/scripts/run-hydra-gates.sh", - "helpers": "hydra-gates/scripts/lib", - "schemas": "hydra-gates/scripts/schemas" - } + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" }, + "type": "library", "autoload": { "psr-4": { - "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" + "Fidry\\CpuCoreCounter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "EUPL-1.2" + "MIT" ], "authors": [ { - "name": "Conduction", - "homepage": "https://conduction.nl" + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" } ], - "description": "Hydra's mechanical quality gates, packaged so any repo can run them against its own diff. The exit code is the failure COUNT.", - "homepage": "https://github.com/ConductionNL/.github/tree/main/hydra-gates", + "description": "Tiny utility to get the number of CPU cores.", "keywords": [ - "conduction", - "gates", - "nextcloud", - "quality", - "static-analysis" + "CPU", + "core" ], "support": { - "docs": "https://github.com/ConductionNL/.github/blob/main/hydra-gates/README.md", - "issues": "https://github.com/ConductionNL/.github/issues", - "source": "https://github.com/ConductionNL/.github/tree/v1.8.2" + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, - "time": "2026-08-20T09:37:12+00:00" + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" }, { - "name": "consolidation/annotated-command", - "version": "4.10.5", + "name": "grasmash/expander", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/consolidation/annotated-command.git", - "reference": "78abf3b6853d7ff9044babd2b9c002ff433207d8" + "url": "https://github.com/grasmash/expander.git", + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/78abf3b6853d7ff9044babd2b9c002ff433207d8", - "reference": "78abf3b6853d7ff9044babd2b9c002ff433207d8", + "url": "https://api.github.com/repos/grasmash/expander/zipball/eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4", "shasum": "" }, "require": { - "consolidation/output-formatters": "^4.3.1", - "php": ">=7.1.3", - "psr/log": "^1 || ^2 || ^3", - "symfony/console": "^4.4.8 || ^5 || ^6 || ^7 || ^8", - "symfony/event-dispatcher": "^4.4.8 || ^5 || ^6 || ^7 || ^8", - "symfony/finder": "^4.4.8 || ^5 || ^6 || ^7 || ^8" + "dflydev/dot-access-data": "^3.0.0", + "php": ">=8.0", + "psr/log": "^2 | ^3" }, "require-dev": { - "composer-runtime-api": "^2.0", - "phpunit/phpunit": "^7.5.20 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3", - "yoast/phpunit-polyfills": "^0.2.0" + "greg-1-anderson/composer-test-scenarios": "^1", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "Consolidation\\AnnotatedCommand\\": "src" + "Grasmash\\Expander\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2670,58 +2267,43 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Matthew Grasmick" } ], - "description": "Initialize Symfony Console commands from annotated command class methods.", + "description": "Expands internal property references in PHP arrays file.", "support": { - "issues": "https://github.com/consolidation/annotated-command/issues", - "source": "https://github.com/consolidation/annotated-command/tree/4.10.5" + "issues": "https://github.com/grasmash/expander/issues", + "source": "https://github.com/grasmash/expander/tree/3.0.1" }, - "time": "2026-03-29T00:50:52+00:00" + "time": "2024-11-25T23:28:05+00:00" }, { - "name": "consolidation/config", - "version": "3.2.1", + "name": "kubawerlos/php-cs-fixer-custom-fixers", + "version": "v3.37.2", "source": { "type": "git", - "url": "https://github.com/consolidation/config.git", - "reference": "adcb989d789f7e0984121492b0377a1def3a6dc8" + "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", + "reference": "678df979ce743466b42ddb6eea46b3f4c9a7bade" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/adcb989d789f7e0984121492b0377a1def3a6dc8", - "reference": "adcb989d789f7e0984121492b0377a1def3a6dc8", + "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/678df979ce743466b42ddb6eea46b3f4c9a7bade", + "reference": "678df979ce743466b42ddb6eea46b3f4c9a7bade", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^3", - "grasmash/expander": "^3", - "php": ">=8.2.0", - "symfony/event-dispatcher": "^6 || ^7 || ^8" + "ext-filter": "*", + "ext-tokenizer": "*", + "friendsofphp/php-cs-fixer": "^3.87", + "php": "^7.4 || ^8.0" }, "require-dev": { - "ext-json": "*", - "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^3", - "symfony/console": "^7.4 || ^8", - "symfony/yaml": "^7 || ^8", - "yoast/phpunit-polyfills": "^1" - }, - "suggest": { - "symfony/event-dispatcher": "Required to inject configuration into Command options", - "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { "psr-4": { - "Consolidation\\Config\\": "src" + "PhpCsFixerCustomFixers\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2730,50 +2312,69 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Kuba Werłos", + "email": "werlos@gmail.com" } ], - "description": "Provide configuration services for a commandline tool.", + "description": "A set of custom fixers for PHP CS Fixer", "support": { - "issues": "https://github.com/consolidation/config/issues", - "source": "https://github.com/consolidation/config/tree/3.2.1" + "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", + "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.37.2" }, - "time": "2026-03-28T23:38:17+00:00" + "funding": [ + { + "url": "https://github.com/kubawerlos", + "type": "github" + } + ], + "time": "2026-05-12T16:22:19+00:00" }, { - "name": "consolidation/log", - "version": "3.1.2", + "name": "league/container", + "version": "4.2.5", "source": { "type": "git", - "url": "https://github.com/consolidation/log.git", - "reference": "a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6" + "url": "https://github.com/thephpleague/container.git", + "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6", - "reference": "a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6", + "url": "https://api.github.com/repos/thephpleague/container/zipball/d3cebb0ff4685ff61c749e54b27db49319e2ec00", + "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00", "shasum": "" }, "require": { - "php": ">=8.0.0", - "psr/log": "^3", - "symfony/console": "^5 || ^6 || ^7 || ^8" + "php": "^7.2 || ^8.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" }, "require-dev": { - "phpunit/phpunit": "^7.5.20 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3", - "yoast/phpunit-polyfills": "^0.2.0" + "nette/php-generator": "^3.4", + "nikic/php-parser": "^4.10", + "phpstan/phpstan": "^0.12.47", + "phpunit/phpunit": "^8.5.17", + "roave/security-advisories": "dev-latest", + "scrutinizer/ocular": "^1.8", + "squizlabs/php_codesniffer": "^3.6" }, "type": "library", "extra": { - "platform": { - "php": "8.2.17" + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-master": "4.x-dev" } }, "autoload": { "psr-4": { - "Consolidation\\Log\\": "src" + "League\\Container\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2782,624 +2383,512 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Phil Bennett", + "email": "mail@philbennett.co.uk", + "role": "Developer" } ], - "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], "support": { - "issues": "https://github.com/consolidation/log/issues", - "source": "https://github.com/consolidation/log/tree/3.1.2" + "issues": "https://github.com/thephpleague/container/issues", + "source": "https://github.com/thephpleague/container/tree/4.2.5" }, - "time": "2026-03-28T23:36:49+00:00" + "funding": [ + { + "url": "https://github.com/philipobenito", + "type": "github" + } + ], + "time": "2025-05-20T12:55:37+00:00" }, { - "name": "consolidation/output-formatters", - "version": "4.7.1", + "name": "myclabs/deep-copy", + "version": "1.13.4", "source": { "type": "git", - "url": "https://github.com/consolidation/output-formatters.git", - "reference": "a112df9a74854c8438b33b334ed619fa43edf31a" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a112df9a74854c8438b33b334ed619fa43edf31a", - "reference": "a112df9a74854c8438b33b334ed619fa43edf31a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^1.1.0 || ^2 || ^3", - "php": ">=7.1.3", - "symfony/console": "^4 || ^5 || ^6 || ^7 || ^8", - "symfony/finder": "^4 || ^5 || ^6 || ^7 || ^8" + "php": "^7.1 || ^8.0" }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.4.2", - "phpunit/phpunit": "^7 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3", - "symfony/var-dumper": "^4 || ^5 || ^6 || ^7 || ^8", - "symfony/yaml": "^4 || ^5 || ^6 || ^7 || ^8", - "yoast/phpunit-polyfills": "^1" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, - "suggest": { - "symfony/var-dumper": "For using the var_dump formatter" + "require-dev": { + "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" }, "type": "library", "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { - "Consolidation\\OutputFormatters\\": "src" + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" - } + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" ], - "description": "Format text by applying transformations provided by plug-in formatters.", "support": { - "issues": "https://github.com/consolidation/output-formatters/issues", - "source": "https://github.com/consolidation/output-formatters/tree/4.7.1" + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, - "time": "2026-03-28T23:34:39+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" }, { - "name": "consolidation/robo", - "version": "5.1.1", + "name": "netresearch/jsonmapper", + "version": "v4.5.0", "source": { "type": "git", - "url": "https://github.com/consolidation/robo.git", - "reference": "6d02c7d800b5e1a3a8977ae74d23bce723486025" + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/robo/zipball/6d02c7d800b5e1a3a8977ae74d23bce723486025", - "reference": "6d02c7d800b5e1a3a8977ae74d23bce723486025", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { - "consolidation/annotated-command": "^4.8.1", - "consolidation/config": "^3", - "consolidation/log": "^3", - "consolidation/output-formatters": "^4.1.2", - "league/container": "^3.3.1 || ^4.0", - "php": ">=8.2", - "phpowermove/docblock": "^4.0", - "symfony/console": "^6 || ^7", - "symfony/event-dispatcher": "^6 || ^7", - "symfony/filesystem": "^6 || ^7", - "symfony/finder": "^6 || ^7", - "symfony/process": "^6 || ^7", - "symfony/yaml": "^6 || ^7" - }, - "conflict": { - "codegyre/robo": "*" + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" }, "require-dev": { - "natxet/cssmin": "3.0.4", - "patchwork/jsqueeze": "^2", - "pear/archive_tar": "^1.4.4", - "phpunit/phpunit": "^7.5.20 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3.6", - "yoast/phpunit-polyfills": "^0.2.0" - }, - "suggest": { - "consolidation/self-update": "For self-updating a phar-based app built with Robo", - "natxet/cssmin": "For minifying CSS files in taskMinify", - "patchwork/jsqueeze": "For minifying JS files in taskMinify", - "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively.", - "totten/lurkerlite": "For monitoring filesystem changes in taskWatch" + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" }, - "bin": [ - "robo" - ], "type": "library", "autoload": { - "psr-4": { - "Robo\\": "src" + "psr-0": { + "JsonMapper": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "OSL-3.0" ], "authors": [ { - "name": "Davert", - "email": "davert.php@resend.cc" + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" } ], - "description": "Modern task runner", + "description": "Map nested JSON structures onto PHP classes", "support": { - "issues": "https://github.com/consolidation/robo/issues", - "source": "https://github.com/consolidation/robo/tree/5.1.1" + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2025-11-14T23:30:05+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { - "name": "cyclonedx/cyclonedx-library", - "version": "v4.0.0", + "name": "nextcloud/coding-standard", + "version": "v1.5.0", "source": { "type": "git", - "url": "https://github.com/CycloneDX/cyclonedx-php-library.git", - "reference": "c95a371894c4e32bea42bfa024f2ab5092cbb292" + "url": "https://github.com/nextcloud/coding-standard.git", + "reference": "80547a93236fbb9c783e05f0f0899043851b0dba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-library/zipball/c95a371894c4e32bea42bfa024f2ab5092cbb292", - "reference": "c95a371894c4e32bea42bfa024f2ab5092cbb292", + "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/80547a93236fbb9c783e05f0f0899043851b0dba", + "reference": "80547a93236fbb9c783e05f0f0899043851b0dba", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "opis/json-schema": "^2.0", - "php": "^8.1" - }, - "conflict": { - "composer/spdx-licenses": "<1.5" - }, - "require-dev": { - "composer/spdx-licenses": "^1.5", - "ext-simplexml": "*", - "roave/security-advisories": "dev-latest" - }, - "suggest": { - "composer/spdx-licenses": "used in license factory", - "package-url/packageurl-php": "for parsing and crafting PackageURL strings" + "kubawerlos/php-cs-fixer-custom-fixers": "^3.22", + "php": "^8.0", + "php-cs-fixer/shim": "^3.17" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - }, - "composer-normalize": { - "indent-size": 4, - "indent-style": "space" - } - }, "autoload": { "psr-4": { - "CycloneDX\\Core\\": "src/Core/", - "CycloneDX\\Contrib\\": "src/Contrib/" + "Nextcloud\\CodingStandard\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Jan Kowalleck", - "email": "jan.kowalleck@gmail.com", - "homepage": "https://github.com/jkowalleck" + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at" } ], - "description": "Work with CycloneDX documents.", - "homepage": "https://github.com/CycloneDX/cyclonedx-php-library/#readme", + "description": "Nextcloud coding standards for the php cs fixer", "keywords": [ - "CycloneDX", - "HBOM", - "OBOM", - "SBOM", - "SaaSBOM", - "bill-of-materials", - "bom", - "models", - "normalizer", - "owasp", - "package-url", - "purl", - "serializer", - "software-bill-of-materials", - "spdx", - "validator", - "vdr", - "vex" + "dev" ], "support": { - "docs": "https://cyclonedx-php-library.readthedocs.io", - "issues": "https://github.com/CycloneDX/cyclonedx-php-library/issues", - "source": "https://github.com/CycloneDX/cyclonedx-php-library/" + "issues": "https://github.com/nextcloud/coding-standard/issues", + "source": "https://github.com/nextcloud/coding-standard/tree/v1.5.0" }, - "funding": [ - { - "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX", - "type": "other" - } - ], - "time": "2026-02-17T11:46:50+00:00" + "time": "2026-05-19T18:30:09+00:00" }, { - "name": "cyclonedx/cyclonedx-php-composer", - "version": "v6.2.0", + "name": "nextcloud/ocp", + "version": "v34.0.3", "source": { "type": "git", - "url": "https://github.com/CycloneDX/cyclonedx-php-composer.git", - "reference": "934440a5ef7c3c3cdb58c3c3d389d412630ccbf6" + "url": "https://github.com/nextcloud-deps/ocp.git", + "reference": "3fb764be792476e4dcf1593101d978fc1dc8ac9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-composer/zipball/934440a5ef7c3c3cdb58c3c3d389d412630ccbf6", - "reference": "934440a5ef7c3c3cdb58c3c3d389d412630ccbf6", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/3fb764be792476e4dcf1593101d978fc1dc8ac9a", + "reference": "3fb764be792476e4dcf1593101d978fc1dc8ac9a", "shasum": "" }, "require": { - "composer-plugin-api": "^2.3", - "composer/spdx-licenses": "^1.5.7", - "cyclonedx/cyclonedx-library": "^4.0", - "package-url/packageurl-php": "^1.0", - "php": "^8.1" - }, - "require-dev": { - "composer/composer": "^2.3.0", - "marc-mabe/php-enum": "^4.6", - "roave/security-advisories": "dev-latest" + "php": "~8.2 || ~8.3 || ~8.4 || ~8.5", + "psr/clock": "^1.0", + "psr/container": "^2.0.2", + "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0.3", + "psr/log": "^3.0.2" }, - "type": "composer-plugin", + "type": "library", "extra": { - "class": "CycloneDX\\Composer\\Plugin", "branch-alias": { - "dev-master": "6.x-dev" - }, - "composer-normalize": { - "indent-size": 4, - "indent-style": "space" - } - }, - "autoload": { - "psr-4": { - "CycloneDX\\Composer\\": "src/" + "dev-stable34": "34.0.0-dev" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "AGPL-3.0-or-later" ], "authors": [ { - "name": "Jan Kowalleck", - "email": "jan.kowalleck@gmail.com", - "homepage": "https://github.com/jkowalleck" + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at" + }, + { + "name": "Joas Schilling", + "email": "coding@schilljs.com" } ], - "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from PHP Composer projects", - "homepage": "https://github.com/CycloneDX/cyclonedx-php-composer/#readme", - "keywords": [ - "CycloneDX", - "SBOM", - "bill-of-materials", - "bom", - "composer", - "package-url", - "purl", - "software-bill-of-materials", - "spdx" - ], + "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", "support": { - "issues": "https://github.com/CycloneDX/cyclonedx-php-composer/issues", - "source": "https://github.com/CycloneDX/cyclonedx-php-composer/" + "issues": "https://github.com/nextcloud-deps/ocp/issues", + "source": "https://github.com/nextcloud-deps/ocp/tree/v34.0.3" }, - "funding": [ - { - "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX", - "type": "other" - } - ], - "time": "2026-02-17T13:23:10+00:00" + "time": "2026-08-07T02:03:36+00:00" }, { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.2.1", + "name": "nikic/php-parser", + "version": "v4.19.5", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd", - "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", "shasum": "" }, "require": { - "composer-plugin-api": "^2.2", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" + "ext-tokenizer": "*", + "php": ">=7.1" }, "require-dev": { - "composer/composer": "^2.2", - "ext-json": "*", - "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", - "yoast/phpunit-polyfills": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, + "bin": [ + "bin/php-parse" + ], + "type": "library", "autoload": { "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Franck Nijhof", - "email": "opensource@frenck.dev", - "homepage": "https://frenck.dev", - "role": "Open source developer" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + "name": "Nikita Popov" } ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "description": "A PHP parser written in PHP", "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" + "parser", + "php" ], "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", - "source": "https://github.com/PHPCSStandards/composer-installer" + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.5" }, - "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": "2026-05-06T08:26:05+00:00" + "time": "2025-12-06T11:45:25+00:00" }, { - "name": "dflydev/dot-access-data", - "version": "v3.0.3", + "name": "opis/json-schema", + "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + "url": "https://github.com/opis/json-schema.git", + "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", - "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "url": "https://api.github.com/repos/opis/json-schema/zipball/8458763e0dd0b6baa310e04f1829fc73da4e8c8a", + "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "ext-json": "*", + "opis/string": "^2.1", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.0.0" + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "Dflydev\\DotAccessData\\": "src/" + "Opis\\JsonSchema\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" }, { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" } ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", "keywords": [ - "access", - "data", - "dot", - "notation" + "json", + "json-schema", + "schema", + "validation", + "validator" ], "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.6.0" }, - "time": "2024-07-08T12:26:09+00:00" + "time": "2025-10-17T12:46:48+00:00" }, { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", + "name": "opis/string", + "version": "2.1.0", "source": { "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + "url": "https://github.com/opis/string.git", + "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "url": "https://api.github.com/repos/opis/string/zipball/3e4d2aaff518ac518530b89bb26ed40f4503635e", + "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e", "shasum": "" }, "require": { - "php": ">=5.3.2" + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + "phpunit/phpunit": "^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, "autoload": { "psr-4": { - "XdgBaseDir\\": "src/" + "Opis\\String\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", + "keywords": [ + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" ], - "description": "implementation of xdg base directory specification for php", "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.1.0" }, - "time": "2019-12-04T15:06:13+00:00" + "time": "2025-10-17T12:38:41+00:00" }, { - "name": "doctrine/deprecations", - "version": "1.1.6", + "name": "opis/uri", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", - "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "phpunit/phpunit": "<=7.5 || >=14" + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^14", - "phpstan/phpstan": "1.4.10 || 2.1.30", - "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", - "psr/log": "^1 || ^2 || ^3" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + "phpunit/phpunit": "^9" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "src" + "Opis\\Uri\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" }, - "time": "2026-02-07T07:09:04+00:00" + "time": "2021-05-22T15:57:08+00:00" }, { - "name": "edgedesign/phpqa", - "version": "v1.27.2", + "name": "package-url/packageurl-php", + "version": "1.1.2", "source": { "type": "git", - "url": "https://github.com/EdgedesignCZ/phpqa.git", - "reference": "ad97e48c8dfe406f3fab5f6a6fb0604035f7a3ea" + "url": "https://github.com/package-url/packageurl-php.git", + "reference": "32058ad61f0d8b457fa26e7860bbd8b903196d3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EdgedesignCZ/phpqa/zipball/ad97e48c8dfe406f3fab5f6a6fb0604035f7a3ea", - "reference": "ad97e48c8dfe406f3fab5f6a6fb0604035f7a3ea", + "url": "https://api.github.com/repos/package-url/packageurl-php/zipball/32058ad61f0d8b457fa26e7860bbd8b903196d3f", + "reference": "32058ad61f0d8b457fa26e7860bbd8b903196d3f", "shasum": "" }, "require": { - "consolidation/robo": "~0.5|>=1", - "ext-xsl": "*", - "php": ">=5.4", - "twig/twig": "~1.38|~2.7|>=3" + "php": "^7.3 || ^8.0" }, "require-dev": { - "hamcrest/hamcrest-php": ">=2.0.1", - "phpunit/phpunit": ">=4.8.28" - }, - "suggest": { - "deptrac/deptrac": "Enforce rules for dependencies between software layers", - "enlightn/security-checker": "Check composer.lock for known security issues", - "friendsofphp/php-cs-fixer": "A tool to automatically fix PHP coding standards issues", - "pdepend/pdepend": "Analyze you the quality of your design in terms of extensibility, reusability and maintainability", - "php-parallel-lint/php-console-highlighter": "Colored output in parallel-lint", - "php-parallel-lint/php-parallel-lint": "Check PHP syntax", - "phploc/phploc": "Abandoned measuring the size of a PHP project", - "phpmd/phpmd": "user friendly metrics from pdepend", - "phpmetrics/phpmetrics": "Metrics about PHP project and classes in HTML report", - "phpstan/phpstan": "PHP Static Analysis Tool - discover bugs in your code without running it!", - "phpunit/phpunit": "The PHP Unit Testing framework", - "psalm/phar": "A static analysis tool for finding errors in PHP applications", - "sebastian/phpcpd": "Abandoned copy-paste detector", - "squizlabs/php_codesniffer": "Detect coding standard violation (phpcs) and fix them (phpcbf)" + "ext-json": "*", + "phpunit/phpunit": "9.6.16", + "roave/security-advisories": "dev-latest" }, - "bin": [ - "phpqa" - ], "type": "library", + "extra": { + "composer-normalize": { + "indent-size": 4, + "indent-style": "space" + } + }, "autoload": { - "files": [ - "src/report.php", - "src/paths.php" - ], "psr-4": { - "Edge\\QA\\": "src" + "PackageUrl\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3408,219 +2897,235 @@ ], "authors": [ { - "name": "Zdenek Drahos", - "email": "drahoszdenek@gmail.com" + "name": "Jan Kowalleck", + "email": "jan.kowalleck@gmail.com", + "homepage": "https://github.com/jkowalleck" } ], - "description": "Analyze PHP code with one command.", + "description": "Builder and parser based on the package URL (purl) specification.", + "homepage": "https://github.com/package-url/packageurl-php#readme", "keywords": [ - "code analysis", - "qa", - "static analysis" + "package", + "package-url", + "packageurl", + "purl", + "url" ], "support": { - "docs": "https://edgedesigncz.github.io/phpqa/", - "issues": "https://github.com/EdgedesignCZ/phpqa/issues", - "source": "https://github.com/EdgedesignCZ/phpqa" + "issues": "https://github.com/package-url/packageurl-php/issues", + "source": "https://github.com/package-url/packageurl-php/tree/1.1.2" }, - "time": "2025-11-22T07:41:40+00:00" + "funding": [ + { + "url": "https://github.com/sponsors/jkowalleck", + "type": "github" + } + ], + "time": "2024-02-05T11:20:07+00:00" }, { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", + "name": "pdepend/pdepend", + "version": "2.16.2", "source": { "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", "shasum": "" }, "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" }, + "bin": [ + "src/bin/pdepend" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, "autoload": { "psr-4": { - "AdvancedJsonRpc\\": "lib/" + "PDepend\\": "src/main/php/PDepend" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "ISC" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" ], - "description": "A more advanced JSONRPC implementation", "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" }, - "time": "2021-06-11T22:34:44+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" }, { - "name": "felixfbecker/language-server-protocol", - "version": "v1.5.3", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", - "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/phpstan": "*", - "squizlabs/php_codesniffer": "^3.1", - "vimeo/psalm": "^4.0" + "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": "1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "psr-4": { - "LanguageServerProtocol\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "ISC" + "BSD-3-Clause" ], "authors": [ { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "PHP classes for the Language Server Protocol", - "keywords": [ - "language", - "microsoft", - "php", - "server" - ], - "support": { - "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" + "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" }, - "time": "2024-04-30T00:40:11+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { - "name": "fidry/cpu-core-counter", - "version": "1.3.0", + "name": "phar-io/version", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-deprecation-rules": "^2.0.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, "type": "library", "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" + "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": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], + "description": "Library for handling version information and constraints", "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2025-08-14T07:29:31+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "grasmash/expander", - "version": "3.0.1", + "name": "phootwork/collection", + "version": "v3.2.3", "source": { "type": "git", - "url": "https://github.com/grasmash/expander.git", - "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4" + "url": "https://github.com/phootwork/collection.git", + "reference": "46dde20420fba17766c89200bc3ff91d3e58eafa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grasmash/expander/zipball/eea11b9afb0c32483b18b9009f4ca07b770e39f4", - "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "url": "https://api.github.com/repos/phootwork/collection/zipball/46dde20420fba17766c89200bc3ff91d3e58eafa", + "reference": "46dde20420fba17766c89200bc3ff91d3e58eafa", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^3.0.0", - "php": ">=8.0", - "psr/log": "^2 | ^3" - }, - "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", - "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^3.3" + "phootwork/lang": "^3.0", + "php": ">=8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Grasmash\\Expander\\": "src/" + "phootwork\\collection\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3629,43 +3134,54 @@ ], "authors": [ { - "name": "Matthew Grasmick" + "name": "Thomas Gossmann", + "homepage": "http://gos.si" } ], - "description": "Expands internal property references in PHP arrays file.", + "description": "The phootwork library fills gaps in the php language and provides better solutions than the existing ones php offers.", + "homepage": "https://phootwork.github.io/collection/", + "keywords": [ + "Array object", + "Text object", + "collection", + "collections", + "json", + "list", + "map", + "queue", + "set", + "stack", + "xml" + ], "support": { - "issues": "https://github.com/grasmash/expander/issues", - "source": "https://github.com/grasmash/expander/tree/3.0.1" + "issues": "https://github.com/phootwork/phootwork/issues", + "source": "https://github.com/phootwork/collection/tree/v3.2.3" }, - "time": "2024-11-25T23:28:05+00:00" + "time": "2022-08-27T12:51:24+00:00" }, { - "name": "kubawerlos/php-cs-fixer-custom-fixers", - "version": "v3.37.2", + "name": "phootwork/lang", + "version": "v3.2.3", "source": { "type": "git", - "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", - "reference": "678df979ce743466b42ddb6eea46b3f4c9a7bade" + "url": "https://github.com/phootwork/lang.git", + "reference": "52ec8cce740ce1c424eef02f43b43d5ddfec7b5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/678df979ce743466b42ddb6eea46b3f4c9a7bade", - "reference": "678df979ce743466b42ddb6eea46b3f4c9a7bade", + "url": "https://api.github.com/repos/phootwork/lang/zipball/52ec8cce740ce1c424eef02f43b43d5ddfec7b5e", + "reference": "52ec8cce740ce1c424eef02f43b43d5ddfec7b5e", "shasum": "" }, "require": { - "ext-filter": "*", - "ext-tokenizer": "*", - "friendsofphp/php-cs-fixer": "^3.87", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55" + "php": ">=8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/polyfill-php81": "^1.22" }, "type": "library", "autoload": { "psr-4": { - "PhpCsFixerCustomFixers\\": "src" + "phootwork\\lang\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3674,239 +3190,277 @@ ], "authors": [ { - "name": "Kuba Werłos", - "email": "werlos@gmail.com" + "name": "Thomas Gossmann", + "homepage": "http://gos.si" } ], - "description": "A set of custom fixers for PHP CS Fixer", + "description": "Missing PHP language constructs", + "homepage": "https://phootwork.github.io/lang/", + "keywords": [ + "array", + "comparator", + "comparison", + "string" + ], "support": { - "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", - "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.37.2" + "issues": "https://github.com/phootwork/phootwork/issues", + "source": "https://github.com/phootwork/lang/tree/v3.2.3" }, - "funding": [ - { - "url": "https://github.com/kubawerlos", - "type": "github" - } - ], - "time": "2026-05-12T16:22:19+00:00" + "time": "2024-10-03T13:43:19+00:00" }, { - "name": "league/container", - "version": "4.2.5", + "name": "php-cs-fixer/shim", + "version": "v3.95.18", "source": { "type": "git", - "url": "https://github.com/thephpleague/container.git", - "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00" + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/d3cebb0ff4685ff61c749e54b27db49319e2ec00", - "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/9b815f2ba5c581faaaec1386dcda4c16d511e6bb", + "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.1 || ^2.0" - }, - "provide": { - "psr/container-implementation": "^1.0" + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, "replace": { - "orno/di": "~2.0" - }, - "require-dev": { - "nette/php-generator": "^3.4", - "nikic/php-parser": "^4.10", - "phpstan/phpstan": "^0.12.47", - "phpunit/phpunit": "^8.5.17", - "roave/security-advisories": "dev-latest", - "scrutinizer/ocular": "^1.8", - "squizlabs/php_codesniffer": "^3.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev", - "dev-2.x": "2.x-dev", - "dev-3.x": "3.x-dev", - "dev-4.x": "4.x-dev", - "dev-master": "4.x-dev" - } + "friendsofphp/php-cs-fixer": "self.version" }, - "autoload": { - "psr-4": { - "League\\Container\\": "src" - } + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." }, + "bin": [ + "php-cs-fixer", + "php-cs-fixer.phar" + ], + "type": "application", "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Phil Bennett", - "email": "mail@philbennett.co.uk", - "role": "Developer" - } - ], - "description": "A fast and intuitive dependency injection container.", - "homepage": "https://github.com/thephpleague/container", - "keywords": [ - "container", - "dependency", - "di", - "injection", - "league", - "provider", - "service" - ], - "support": { - "issues": "https://github.com/thephpleague/container/issues", - "source": "https://github.com/thephpleague/container/tree/4.2.5" - }, - "funding": [ + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, { - "url": "https://github.com/philipobenito", - "type": "github" + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" } ], - "time": "2025-05-20T12:55:37+00:00" + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/PHP-CS-Fixer/shim/issues", + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.18" + }, + "time": "2026-07-30T15:46:28+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.13.4", + "name": "phpcsstandards/phpcsextra", + "version": "1.5.1", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/39467533fdb742446d68c1d10ac33d625ee0311c", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.2.3", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" }, "require-dev": { - "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" + "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": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "LGPL-3.0-or-later" ], - "description": "Create deep copies (clones) of your objects", + "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": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" ], "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "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-08-01T08:46:24+00:00" + "time": "2026-07-27T11:13:17+00:00" }, { - "name": "netresearch/jsonmapper", - "version": "v4.5.0", + "name": "phpcsstandards/phpcsutils", + "version": "1.2.3", "source": { "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", - "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", "shasum": "" }, "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" + "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": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", - "squizlabs/php_codesniffer": "~3.5" + "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": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" + "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": [ - "OSL-3.0" + "LGPL-3.0-or-later" ], "authors": [ { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" } ], - "description": "Map nested JSON structures onto PHP classes", + "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": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" + "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" }, - "time": "2024-09-08T10:13:13+00:00" + "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": "2026-07-27T10:28:41+00:00" }, { - "name": "nextcloud/coding-standard", - "version": "v1.5.0", + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", "source": { "type": "git", - "url": "https://github.com/nextcloud/coding-standard.git", - "reference": "80547a93236fbb9c783e05f0f0899043851b0dba" + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/80547a93236fbb9c783e05f0f0899043851b0dba", - "reference": "80547a93236fbb9c783e05f0f0899043851b0dba", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "kubawerlos/php-cs-fixer-custom-fixers": "^3.22", - "php": "^8.0", - "php-cs-fixer/shim": "^3.17" + "php": "^7.2 || ^8.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, "autoload": { "psr-4": { - "Nextcloud\\CodingStandard\\": "src" + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3915,465 +3469,503 @@ ], "authors": [ { - "name": "Christoph Wurst", - "email": "christoph@winzerhof-wurst.at" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "Nextcloud coding standards for the php cs fixer", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", "keywords": [ - "dev" + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" ], "support": { - "issues": "https://github.com/nextcloud/coding-standard/issues", - "source": "https://github.com/nextcloud/coding-standard/tree/v1.5.0" + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "time": "2026-05-19T18:30:09+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { - "name": "nextcloud/ocp", - "version": "v34.0.3", + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.7", "source": { "type": "git", - "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "3fb764be792476e4dcf1593101d978fc1dc8ac9a" + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/3fb764be792476e4dcf1593101d978fc1dc8ac9a", - "reference": "3fb764be792476e4dcf1593101d978fc1dc8ac9a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903", "shasum": "" }, "require": { - "php": "~8.2 || ~8.3 || ~8.4 || ~8.5", - "psr/clock": "^1.0", - "psr/container": "^2.0.2", - "psr/event-dispatcher": "^1.0", - "psr/http-client": "^1.0.3", - "psr/log": "^3.0.2" + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" }, "type": "library", "extra": { "branch-alias": { - "dev-stable34": "34.0.0-dev" + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "AGPL-3.0-or-later" + "MIT" ], "authors": [ { - "name": "Christoph Wurst", - "email": "christoph@winzerhof-wurst.at" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" }, { - "name": "Joas Schilling", - "email": "coding@schilljs.com" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { - "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/v34.0.3" + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7" }, - "time": "2026-08-07T02:03:36+00:00" + "time": "2026-03-18T20:47:46+00:00" }, { - "name": "nikic/php-parser", - "version": "v4.19.5", + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837" + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", - "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=7.1" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, - "bin": [ - "bin/php-parse" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" } ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.5" + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2025-12-06T11:45:25+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { - "name": "opis/json-schema", - "version": "2.6.0", + "name": "phpmd/phpmd", + "version": "2.15.0", "source": { "type": "git", - "url": "https://github.com/opis/json-schema.git", - "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a" + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/json-schema/zipball/8458763e0dd0b6baa310e04f1829fc73da4e8c8a", - "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", "shasum": "" }, "require": { - "ext-json": "*", - "opis/string": "^2.1", - "opis/uri": "^1.0", - "php": "^7.4 || ^8.0" + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" }, "require-dev": { - "ext-bcmath": "*", - "ext-intl": "*", - "phpunit/phpunit": "^9.0" + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" }, + "bin": [ + "src/bin/phpmd" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { - "psr-4": { - "Opis\\JsonSchema\\": "src/" + "psr-0": { + "PHPMD\\": "src/main/php" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "BSD-3-Clause" ], "authors": [ { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" }, { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" } ], - "description": "Json Schema Validator for PHP", - "homepage": "https://opis.io/json-schema", + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", "keywords": [ - "json", - "json-schema", - "schema", - "validation", - "validator" + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" ], "support": { - "issues": "https://github.com/opis/json-schema/issues", - "source": "https://github.com/opis/json-schema/tree/2.6.0" + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" }, - "time": "2025-10-17T12:46:48+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2023-12-11T08:22:20+00:00" }, { - "name": "opis/string", - "version": "2.1.0", + "name": "phpmetrics/phpmetrics", + "version": "v2.11.0", "source": { "type": "git", - "url": "https://github.com/opis/string.git", - "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e" + "url": "https://github.com/phpmetrics/PhpMetrics.git", + "reference": "55c5e23b34afb8fa9b6c6ea95cbd59b710160235" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/string/zipball/3e4d2aaff518ac518530b89bb26ed40f4503635e", - "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e", + "url": "https://api.github.com/repos/phpmetrics/PhpMetrics/zipball/55c5e23b34afb8fa9b6c6ea95cbd59b710160235", + "reference": "55c5e23b34afb8fa9b6c6ea95cbd59b710160235", "shasum": "" }, "require": { - "ext-iconv": "*", - "ext-json": "*", - "php": "^7.4 || ^8.0" + "ext-dom": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^3|^4|^5" + }, + "replace": { + "halleck45/php-metrics": "*", + "halleck45/phpmetrics": "*" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "*" }, + "bin": [ + "bin/phpmetrics" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { - "psr-4": { - "Opis\\String\\": "src/" + "files": [ + "./src/functions.php" + ], + "psr-0": { + "Hal\\": "./src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "Jean-François Lépine", + "email": "lepinejeanfrancois@yahoo.fr", + "homepage": "http://www.lepine.pro", + "role": "Copyright Holder" } ], - "description": "Multibyte strings as objects", - "homepage": "https://opis.io/string", - "keywords": [ - "multi-byte", - "opis", - "string", - "string manipulation", - "utf-8" + "description": "Static analyzer tool for PHP : Coupling, Cyclomatic complexity, Maintainability Index, Halstead's metrics... and more !", + "homepage": "http://www.phpmetrics.org", + "keywords": [ + "analysis", + "qa", + "quality", + "testing" ], "support": { - "issues": "https://github.com/opis/string/issues", - "source": "https://github.com/opis/string/tree/2.1.0" + "issues": "https://github.com/PhpMetrics/PhpMetrics/issues", + "source": "https://github.com/phpmetrics/PhpMetrics/tree/v2.11.0" }, - "time": "2025-10-17T12:38:41+00:00" + "funding": [ + { + "url": "https://github.com/Halleck45", + "type": "github" + } + ], + "time": "2026-08-09T06:58:42+00:00" }, { - "name": "opis/uri", - "version": "1.1.0", + "name": "phpowermove/docblock", + "version": "v4.0", "source": { "type": "git", - "url": "https://github.com/opis/uri.git", - "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + "url": "https://github.com/phpowermove/docblock.git", + "reference": "a73f6e17b7d4e1b92ca5378c248c952c9fae7826" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", - "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "url": "https://api.github.com/repos/phpowermove/docblock/zipball/a73f6e17b7d4e1b92ca5378c248c952c9fae7826", + "reference": "a73f6e17b7d4e1b92ca5378c248c952c9fae7826", "shasum": "" }, "require": { - "opis/string": "^2.0", - "php": "^7.4 || ^8.0" + "phootwork/collection": "^3.0", + "phootwork/lang": "^3.0", + "php": ">=8.0" }, "require-dev": { - "phpunit/phpunit": "^9" + "phootwork/php-cs-fixer-config": "^0.4", + "phpunit/phpunit": "^9.0", + "psalm/phar": "^4.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Opis\\Uri\\": "src/" + "phpowermove\\docblock\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "Thomas Gossmann", + "homepage": "http://gos.si" } ], - "description": "Build, parse and validate URIs and URI-templates", - "homepage": "https://opis.io", + "description": "PHP Docblock parser and generator. An API to read and write Docblocks.", "keywords": [ - "URI Template", - "parse url", - "punycode", - "uri", - "uri components", - "url", - "validate uri" + "docblock", + "generator", + "parser" ], "support": { - "issues": "https://github.com/opis/uri/issues", - "source": "https://github.com/opis/uri/tree/1.1.0" + "issues": "https://github.com/phpowermove/docblock/issues", + "source": "https://github.com/phpowermove/docblock/tree/v4.0" }, - "time": "2021-05-22T15:57:08+00:00" + "time": "2021-09-22T16:57:06+00:00" }, { - "name": "package-url/packageurl-php", - "version": "1.1.2", + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", "source": { "type": "git", - "url": "https://github.com/package-url/packageurl-php.git", - "reference": "32058ad61f0d8b457fa26e7860bbd8b903196d3f" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/package-url/packageurl-php/zipball/32058ad61f0d8b457fa26e7860bbd8b903196d3f", - "reference": "32058ad61f0d8b457fa26e7860bbd8b903196d3f", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "ext-json": "*", - "phpunit/phpunit": "9.6.16", - "roave/security-advisories": "dev-latest" + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" }, "type": "library", - "extra": { - "composer-normalize": { - "indent-size": 4, - "indent-style": "space" - } - }, "autoload": { "psr-4": { - "PackageUrl\\": "src" + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Jan Kowalleck", - "email": "jan.kowalleck@gmail.com", - "homepage": "https://github.com/jkowalleck" - } - ], - "description": "Builder and parser based on the package URL (purl) specification.", - "homepage": "https://github.com/package-url/packageurl-php#readme", - "keywords": [ - "package", - "package-url", - "packageurl", - "purl", - "url" - ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { - "issues": "https://github.com/package-url/packageurl-php/issues", - "source": "https://github.com/package-url/packageurl-php/tree/1.1.2" + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "funding": [ - { - "url": "https://github.com/sponsors/jkowalleck", - "type": "github" - } - ], - "time": "2024-02-05T11:20:07+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { - "name": "pdepend/pdepend", - "version": "2.16.2", - "source": { - "type": "git", - "url": "https://github.com/pdepend/pdepend.git", - "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" - }, + "name": "phpstan/phpstan", + "version": "2.2.8", "dist": { "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", - "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", "shasum": "" }, "require": { - "php": ">=5.3.7", - "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/polyfill-mbstring": "^1.19" + "php": "^7.4|^8.0" }, - "require-dev": { - "easy-doc/easy-doc": "0.0.0|^1.2.3", - "gregwar/rst": "^1.0", - "squizlabs/php_codesniffer": "^2.0.0" + "conflict": { + "phpstan/phpstan-shim": "*" }, "bin": [ - "src/bin/pdepend" + "phpstan", + "phpstan.phar" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { - "psr-4": { - "PDepend\\": "src/main/php/PDepend" - } + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Official version of pdepend to be handled with Composer", + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "PHP Depend", - "PHP_Depend", "dev", - "pdepend" + "static analysis" ], "support": { - "issues": "https://github.com/pdepend/pdepend/issues", - "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", - "type": "tidelift" + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" } ], - "time": "2023-12-17T18:09:59+00:00" + "time": "2026-08-04T22:21:45+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "phpunit/php-code-coverage", + "version": "10.1.16", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "ext-phar": "*", "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "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-master": "2.0.x-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -4386,53 +3978,58 @@ "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" + "role": "lead" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "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/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" + "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/10.1.16" }, "funding": [ { - "url": "https://github.com/theseer", + "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "phpunit/php-file-iterator", + "version": "4.1.0", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -4443,386 +4040,401 @@ "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" + "role": "lead" } ], - "description": "Library for handling version information and constraints", + "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/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, - "time": "2022-02-21T01:04:05+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" }, { - "name": "phootwork/collection", - "version": "v3.2.3", + "name": "phpunit/php-invoker", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/phootwork/collection.git", - "reference": "46dde20420fba17766c89200bc3ff91d3e58eafa" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phootwork/collection/zipball/46dde20420fba17766c89200bc3ff91d3e58eafa", - "reference": "46dde20420fba17766c89200bc3ff91d3e58eafa", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "phootwork/lang": "^3.0", - "php": ">=8.0" + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", - "autoload": { - "psr-4": { - "phootwork\\collection\\": "" + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Thomas Gossmann", - "homepage": "http://gos.si" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "The phootwork library fills gaps in the php language and provides better solutions than the existing ones php offers.", - "homepage": "https://phootwork.github.io/collection/", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "Array object", - "Text object", - "collection", - "collections", - "json", - "list", - "map", - "queue", - "set", - "stack", - "xml" + "process" ], "support": { - "issues": "https://github.com/phootwork/phootwork/issues", - "source": "https://github.com/phootwork/collection/tree/v3.2.3" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, - "time": "2022-08-27T12:51:24+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" }, { - "name": "phootwork/lang", - "version": "v3.2.3", + "name": "phpunit/php-text-template", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/phootwork/lang.git", - "reference": "52ec8cce740ce1c424eef02f43b43d5ddfec7b5e" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phootwork/lang/zipball/52ec8cce740ce1c424eef02f43b43d5ddfec7b5e", - "reference": "52ec8cce740ce1c424eef02f43b43d5ddfec7b5e", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": ">=8.0", - "symfony/polyfill-mbstring": "^1.12", - "symfony/polyfill-php81": "^1.22" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", - "autoload": { - "psr-4": { - "phootwork\\lang\\": "" + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Thomas Gossmann", - "homepage": "http://gos.si" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Missing PHP language constructs", - "homepage": "https://phootwork.github.io/lang/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "array", - "comparator", - "comparison", - "string" + "template" ], "support": { - "issues": "https://github.com/phootwork/phootwork/issues", - "source": "https://github.com/phootwork/lang/tree/v3.2.3" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" }, - "time": "2024-10-03T13:43:19+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" }, { - "name": "php-cs-fixer/shim", - "version": "v3.95.18", + "name": "phpunit/php-timer", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/9b815f2ba5c581faaaec1386dcda4c16d511e6bb", - "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" + "php": ">=8.1" }, - "replace": { - "friendsofphp/php-cs-fixer": "self.version" + "require-dev": { + "phpunit/phpunit": "^10.0" }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] }, - "bin": [ - "php-cs-fixer", - "php-cs-fixer.phar" - ], - "type": "application", "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" ], - "description": "A tool to automatically fix PHP code style", "support": { - "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.18" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, - "time": "2026-07-30T15:46:28+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" }, { - "name": "phpcsstandards/phpcsextra", - "version": "1.5.1", + "name": "phpunit/phpunit", + "version": "10.5.63", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "39467533fdb742446d68c1d10ac33d625ee0311c" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/39467533fdb742446d68c1d10ac33d625ee0311c", - "reference": "39467533fdb742446d68c1d10ac33d625ee0311c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", "shasum": "" }, "require": { - "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.2.3", - "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^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" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" }, - "type": "phpcodesniffer-standard", + "bin": [ + "phpunit" + ], + "type": "library", "extra": { "branch-alias": { - "dev-stable": "1.x-dev", - "dev-develop": "1.x-dev" + "dev-main": "10.5-dev" } }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "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/PHPCSExtra/graphs/contributors" } ], - "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "PHP_CodeSniffer", - "phpcbf", - "phpcodesniffer-standard", - "phpcs", - "standards", - "static analysis" + "phpunit", + "testing", + "xunit" ], "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/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" + "url": "https://phpunit.de/sponsors.html", + "type": "custom" }, { - "url": "https://github.com/jrfnl", + "url": "https://github.com/sebastianbergmann", "type": "github" }, { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://thanks.dev/u/gh/phpcsstandards", + "url": "https://thanks.dev/u/gh/sebastianbergmann", "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2026-07-27T11:13:17+00:00" + "time": "2026-01-27T05:48:37+00:00" }, { - "name": "phpcsstandards/phpcsutils", - "version": "1.2.3", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", - "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "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" + "php": ">=7.4.0" }, - "type": "phpcodesniffer-standard", + "type": "library", "extra": { "branch-alias": { - "dev-stable": "1.x-dev", - "dev-develop": "1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "classmap": [ - "PHPCSUtils/" - ] + "psr-4": { + "Psr\\Container\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "MIT" ], "authors": [ { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl", - "role": "lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "A suite of utility functions for use with PHP_CodeSniffer", - "homepage": "https://phpcsutils.com/", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "PHP_CodeSniffer", - "phpcbf", - "phpcodesniffer-standard", - "phpcs", - "phpcs3", - "phpcs4", - "standards", - "static analysis", - "tokens", - "utility" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "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/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "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": "2026-07-27T10:28:41+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=7.2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-2.x": "2.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src/" + "Psr\\EventDispatcher\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4831,66 +4443,49 @@ ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "Standard interfaces for event handling.", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "events", + "psr", + "psr-14" ], "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" }, - "time": "2020-06-27T09:03:43+00:00" + "time": "2019-01-08T18:20:26+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "5.6.7", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "31a105931bc8ffa3a123383829772e832fd8d903" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903", - "reference": "31a105931bc8ffa3a123383829772e832fd8d903", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.1", - "ext-filter": "*", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1 || ^2" - }, - "require-dev": { - "mockery/mockery": "~1.3.5 || ~1.6.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-webmozart-assert": "^1.2", - "phpunit/phpunit": "^9.5", - "psalm/phar": "^5.26" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4899,60 +4494,49 @@ ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7" + "source": "https://github.com/php-fig/http-client" }, - "time": "2026-03-18T20:47:46+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.12.0", + "name": "psr/http-message", + "version": "2.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.3 || ^8.0", - "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.18|^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4961,825 +4545,87 @@ ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2025-11-21T15:09:14+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { - "name": "phpmd/phpmd", - "version": "2.15.0", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/phpmd/phpmd.git", - "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", - "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", - "ext-xml": "*", - "pdepend/pdepend": "^2.16.1", - "php": ">=5.3.9" - }, - "require-dev": { - "easy-doc/easy-doc": "0.0.0 || ^1.3.2", - "ext-json": "*", - "ext-simplexml": "*", - "gregwar/rst": "^1.0", - "mikey179/vfsstream": "^1.6.8", - "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + "php": ">=8.0.0" }, - "bin": [ - "src/bin/phpmd" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { - "psr-0": { - "PHPMD\\": "src/main/php" + "psr-4": { + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Manuel Pichler", - "email": "github@manuel-pichler.de", - "homepage": "https://github.com/manuelpichler", - "role": "Project Founder" - }, - { - "name": "Marc Würth", - "email": "ravage@bluewin.ch", - "homepage": "https://github.com/ravage84", - "role": "Project Maintainer" - }, - { - "name": "Other contributors", - "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", - "role": "Contributors" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", - "homepage": "https://phpmd.org/", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "dev", - "mess detection", - "mess detector", - "pdepend", - "phpmd", - "pmd" + "log", + "psr", + "psr-3" ], "support": { - "irc": "irc://irc.freenode.org/phpmd", - "issues": "https://github.com/phpmd/phpmd/issues", - "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", - "type": "tidelift" - } - ], - "time": "2023-12-11T08:22:20+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "phpmetrics/phpmetrics", - "version": "v2.11.0", + "name": "roave/security-advisories", + "version": "dev-latest", "source": { "type": "git", - "url": "https://github.com/phpmetrics/PhpMetrics.git", - "reference": "55c5e23b34afb8fa9b6c6ea95cbd59b710160235" + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmetrics/PhpMetrics/zipball/55c5e23b34afb8fa9b6c6ea95cbd59b710160235", - "reference": "55c5e23b34afb8fa9b6c6ea95cbd59b710160235", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "nikic/php-parser": "^3|^4|^5" - }, - "replace": { - "halleck45/php-metrics": "*", - "halleck45/phpmetrics": "*" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "bin": [ - "bin/phpmetrics" - ], - "type": "library", - "autoload": { - "files": [ - "./src/functions.php" - ], - "psr-0": { - "Hal\\": "./src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jean-François Lépine", - "email": "lepinejeanfrancois@yahoo.fr", - "homepage": "http://www.lepine.pro", - "role": "Copyright Holder" - } - ], - "description": "Static analyzer tool for PHP : Coupling, Cyclomatic complexity, Maintainability Index, Halstead's metrics... and more !", - "homepage": "http://www.phpmetrics.org", - "keywords": [ - "analysis", - "qa", - "quality", - "testing" - ], - "support": { - "issues": "https://github.com/PhpMetrics/PhpMetrics/issues", - "source": "https://github.com/phpmetrics/PhpMetrics/tree/v2.11.0" - }, - "funding": [ - { - "url": "https://github.com/Halleck45", - "type": "github" - } - ], - "time": "2026-08-09T06:58:42+00:00" - }, - { - "name": "phpowermove/docblock", - "version": "v4.0", - "source": { - "type": "git", - "url": "https://github.com/phpowermove/docblock.git", - "reference": "a73f6e17b7d4e1b92ca5378c248c952c9fae7826" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpowermove/docblock/zipball/a73f6e17b7d4e1b92ca5378c248c952c9fae7826", - "reference": "a73f6e17b7d4e1b92ca5378c248c952c9fae7826", - "shasum": "" - }, - "require": { - "phootwork/collection": "^3.0", - "phootwork/lang": "^3.0", - "php": ">=8.0" - }, - "require-dev": { - "phootwork/php-cs-fixer-config": "^0.4", - "phpunit/phpunit": "^9.0", - "psalm/phar": "^4.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "phpowermove\\docblock\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Thomas Gossmann", - "homepage": "http://gos.si" - } - ], - "description": "PHP Docblock parser and generator. An API to read and write Docblocks.", - "keywords": [ - "docblock", - "generator", - "parser" - ], - "support": { - "issues": "https://github.com/phpowermove/docblock/issues", - "source": "https://github.com/phpowermove/docblock/tree/v4.0" - }, - "time": "2021-09-22T16:57:06+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "2.3.2", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" - }, - "time": "2026-01-25T14:56:51+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "2.2.8", - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", - "reference": "e285254e60f33c21902efef4a926ca0987c06804", - "shasum": "" - }, - "require": { - "php": "^7.4|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ondřej Mirtes" - }, - { - "name": "Markus Staab" - }, - { - "name": "Vincent Langlet" - } - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2026-08-04T22:21:45+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "10.1.16", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^10.1" - }, - "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": "10.1.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/10.1.16" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-08-22T04:31:57+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "4.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.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", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T06:24:48+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.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": "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/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:56:09+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.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", - "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", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T14:07:24+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.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/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:57:52+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "10.5.63", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33198268dad71e926626b618f3ec3966661e4d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", - "reference": "33198268dad71e926626b618f3ec3966661e4d90", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.5-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/10.5.63" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "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/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2026-01-27T05:48:37+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "roave/security-advisories", - "version": "dev-latest", - "source": { - "type": "git", - "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3c9ad688ad8826203588ec49363f73f4deb590c1", - "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3c9ad688ad8826203588ec49363f73f4deb590c1", + "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1", "shasum": "" }, "conflict": { @@ -6856,68 +5702,607 @@ "zoujingli/thinkadmin": "<=6.1.53", "zumba/json-serializer": "<3.2.3" }, - "default-branch": true, - "type": "metapackage", + "default-branch": true, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" + } + ], + "time": "2026-08-01T00:01:24+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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 parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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": "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/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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": "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/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.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", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, + "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:25:16+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-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", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-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", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "role": "maintainer" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Ilya Tribusean", - "email": "slash3b@gmail.com", - "role": "maintainer" + "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": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ - "dev" + "export", + "exporter" ], "support": { - "issues": "https://github.com/Roave/SecurityAdvisories/issues", - "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" }, "funding": [ { - "url": "https://github.com/Ocramius", + "url": "https://github.com/sebastianbergmann", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "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": "2026-08-01T00:01:24+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { - "name": "sebastian/cli-parser", - "version": "2.0.1", + "name": "sebastian/global-state", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { + "ext-dom": "*", "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6932,16 +6317,18 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, "funding": [ { @@ -6949,23 +6336,24 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { - "name": "sebastian/code-unit", - "version": "2.0.0", + "name": "sebastian/lines-of-code", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { @@ -6993,11 +6381,12 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "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/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" }, "funding": [ { @@ -7005,24 +6394,26 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "name": "sebastian/object-enumerator", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "phpunit/phpunit": "^10.0" @@ -7030,7 +6421,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7048,11 +6439,11 @@ "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/", + "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/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { @@ -7060,36 +6451,32 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { - "name": "sebastian/comparator", - "version": "5.0.5", + "name": "sebastian/object-reflector", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -7105,77 +6492,46 @@ { "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" - ], + "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/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "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:25:16+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { - "name": "sebastian/complexity", - "version": "3.2.0", + "name": "sebastian/recursion-context", + "version": "5.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7190,50 +6546,68 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "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": "2023-12-21T08:37:17+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { - "name": "sebastian/diff", - "version": "5.1.1", + "name": "sebastian/type", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7248,25 +6622,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -7274,35 +6638,29 @@ "type": "github" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { - "name": "sebastian/environment", - "version": "6.1.0", + "name": "sebastian/version", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { "php": ">=8.1" }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-posix": "*" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7317,20 +6675,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], + "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/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -7338,548 +6691,687 @@ "type": "github" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { - "name": "sebastian/exporter", - "version": "5.1.4", + "name": "spatie/array-to-xml", + "version": "3.4.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "ext-dom": "*", + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "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" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", "keywords": [ - "export", - "exporter" + "array", + "convert", + "xml" ], "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" }, "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://spatie.be/open-source/support-us", + "type": "custom" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" + "url": "https://github.com/spatie", + "type": "github" } ], - "time": "2025-09-24T06:09:11+00:00" + "time": "2025-12-15T09:00:41+00:00" }, { - "name": "sebastian/global-state", - "version": "6.0.2", + "name": "squizlabs/php_codesniffer", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "bbdc3d0532623e21838b7041a4364383a8126f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/bbdc3d0532623e21838b7041a4364383a8126f96", + "reference": "bbdc3d0532623e21838b7041a4364383a8126f96", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "ext-libxml": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=7.2.0" }, "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, - "autoload": { - "classmap": [ - "src/" - ] + "suggest": { + "ext-iconv": "For accurate character length calculation when the checked files contain multi-byte characters.", + "ext-pcntl": "For parallel processing support via the --parallel CLI option." }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ - "global state" + "phpcs", + "standards", + "static analysis" ], "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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": "2024-03-02T07:19:19+00:00" + "time": "2026-08-06T02:45:27+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "2.0.2", + "name": "symfony/config", + "version": "v7.4.10", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "url": "https://github.com/symfony/config.git", + "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", + "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1|^8.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/symfony/config/tree/v7.4.10" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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": "2023-12-21T08:38:20+00:00" + "time": "2026-05-03T14:20:49+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "5.0.0", + "name": "symfony/console", + "version": "v7.4.16", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "url": "https://github.com/symfony/console.git", + "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/symfony/console/zipball/f4c69c9aed03abf933b294257d618bdd9b30a06d", + "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "source": "https://github.com/symfony/console/tree/v7.4.16" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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": "2023-02-03T07:08:32+00:00" + "time": "2026-07-31T12:37:14+00:00" }, { - "name": "sebastian/object-reflector", - "version": "3.0.0", + "name": "symfony/dependency-injection", + "version": "v7.4.13", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "f299e20ce983be6c0744952533c6dfeaaa1448e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f299e20ce983be6c0744952533c6dfeaaa1448e2", + "reference": "f299e20ce983be6c0744952533c6dfeaaa1448e2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.13" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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": "2023-02-03T07:06:18+00:00" + "time": "2026-05-20T14:07:29+00:00" }, { - "name": "sebastian/recursion-context", - "version": "5.0.1", + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { "php": ">=8.1" }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-10T07:50:56+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "sebastian/type", - "version": "4.0.0", + "name": "symfony/event-dispatcher", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "336e7f3b9e95aba04f93ea9143920c2186abfbb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/336e7f3b9e95aba04f93ea9143920c2186abfbb9", + "reference": "336e7f3b9e95aba04f93ea9143920c2186abfbb9", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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": "2023-02-03T07:10:45+00:00" + "time": "2026-07-21T15:13:06+00:00" }, { - "name": "sebastian/version", - "version": "4.0.1", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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": "2023-02-07T11:34:05+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "spatie/array-to-xml", - "version": "3.4.4", + "name": "symfony/filesystem", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/spatie/array-to-xml.git", - "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" + "url": "https://github.com/symfony/filesystem.git", + "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", - "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ff16a16bf87fdf264638b8f6995b3515975e3c79", + "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79", "shasum": "" }, "require": { - "ext-dom": "*", - "php": "^8.0" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "mockery/mockery": "^1.2", - "pestphp/pest": "^1.21", - "spatie/pest-plugin-snapshots": "^1.1" + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { "psr-4": { - "Spatie\\ArrayToXml\\": "src" - } + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7887,157 +7379,144 @@ ], "authors": [ { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://freek.dev", - "role": "Developer" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Convert an array to xml", - "homepage": "https://github.com/spatie/array-to-xml", - "keywords": [ - "array", - "convert", - "xml" - ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" + "source": "https://github.com/symfony/filesystem/tree/v7.4.15" }, "funding": [ { - "url": "https://spatie.be/open-source/support-us", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://github.com/spatie", - "type": "github" + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-12-15T09:00:41+00:00" + "time": "2026-07-22T07:36:05+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "4.0.4", + "name": "symfony/finder", + "version": "v7.4.14", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "bbdc3d0532623e21838b7041a4364383a8126f96" + "url": "https://github.com/symfony/finder.git", + "reference": "13b38720174286f55d1761152b575a8d1436fc25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/bbdc3d0532623e21838b7041a4364383a8126f96", - "reference": "bbdc3d0532623e21838b7041a4364383a8126f96", + "url": "https://api.github.com/repos/symfony/finder/zipball/13b38720174286f55d1761152b575a8d1436fc25", + "reference": "13b38720174286f55d1761152b575a8d1436fc25", "shasum": "" }, "require": { - "ext-libxml": "*", - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=7.2.0" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" - }, - "suggest": { - "ext-iconv": "For accurate character length calculation when the checked files contain multi-byte characters.", - "ext-pcntl": "For parallel processing support via the --parallel CLI option." + "symfony/filesystem": "^6.4|^7.0|^8.0" }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + "source": "https://github.com/symfony/finder/tree/v7.4.14" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://github.com/jrfnl", + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2026-08-06T02:45:27+00:00" + "time": "2026-06-27T08:31:18+00:00" }, { - "name": "symfony/config", - "version": "v7.4.10", + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", - "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^7.1|^8.0", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2" }, - "conflict": { - "symfony/finder": "<6.4", - "symfony/service-contracts": "<2.5" + "provide": { + "ext-ctype": "*" }, - "require-dev": { - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8045,18 +7524,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], "support": { - "source": "https://github.com/symfony/config/tree/v7.4.10" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -8076,52 +7561,42 @@ "type": "tidelift" } ], - "time": "2026-05-03T14:20:49+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v7.4.13", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f299e20ce983be6c0744952533c6dfeaaa1448e2" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f299e20ce983be6c0744952533c6dfeaaa1448e2", - "reference": "f299e20ce983be6c0744952533c6dfeaaa1448e2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^3.6", - "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<6.4", - "symfony/finder": "<6.4", - "symfony/yaml": "<6.4" - }, - "provide": { - "psr/container-implementation": "1.1|2.0", - "symfony/service-implementation": "1.1|2.0|3.0" + "php": ">=7.2" }, - "require-dev": { - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8129,18 +7604,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.13" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -8160,52 +7643,44 @@ "type": "tidelift" } ], - "time": "2026-05-20T14:07:29+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v7.4.15", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.38.0", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "336e7f3b9e95aba04f93ea9143920c2186abfbb9" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/336e7f3b9e95aba04f93ea9143920c2186abfbb9", - "reference": "336e7f3b9e95aba04f93ea9143920c2186abfbb9", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/service-contracts": "<2.5" + "php": ">=7.2" }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" + "suggest": { + "ext-intl": "For best performance" }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } }, - "type": "library", "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -8214,18 +7689,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.15" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -8245,39 +7728,45 @@ "type": "tidelift" } ], - "time": "2026-07-21T15:13:06+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.1", + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -8294,18 +7783,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to dispatching event", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -8325,37 +7813,41 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { - "name": "symfony/filesystem", - "version": "v7.4.15", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ff16a16bf87fdf264638b8f6995b3515975e3c79", - "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^6.4|^7.0|^8.0" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -8364,18 +7856,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides basic utilities for the filesystem", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.15" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { @@ -8395,32 +7893,29 @@ "type": "tidelift" } ], - "time": "2026-07-22T07:36:05+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/finder", - "version": "v7.4.14", + "name": "symfony/process", + "version": "v7.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "13b38720174286f55d1761152b575a8d1436fc25" + "url": "https://github.com/symfony/process.git", + "reference": "f5804be144caceb570f6747519999636b664f24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/13b38720174286f55d1761152b575a8d1436fc25", - "reference": "13b38720174286f55d1761152b575a8d1436fc25", + "url": "https://api.github.com/repos/symfony/process/zipball/f5804be144caceb570f6747519999636b664f24c", + "reference": "f5804be144caceb570f6747519999636b664f24c", "shasum": "" }, "require": { "php": ">=8.2" }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" - }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -8440,10 +7935,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.14" + "source": "https://github.com/symfony/process/tree/v7.4.13" }, "funding": [ { @@ -8463,41 +7958,46 @@ "type": "tidelift" } ], - "time": "2026-06-27T08:31:18+00:00" + "time": "2026-05-23T16:05:06+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.38.1", + "name": "symfony/service-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", - "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Contracts\\Service\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -8514,16 +8014,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -8543,29 +8045,47 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:45:58+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { - "name": "symfony/process", - "version": "v7.4.13", + "name": "symfony/string", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "f5804be144caceb570f6747519999636b664f24c" + "url": "https://github.com/symfony/string.git", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f5804be144caceb570f6747519999636b664f24c", - "reference": "f5804be144caceb570f6747519999636b664f24c", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Component\\String\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -8577,18 +8097,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], "support": { - "source": "https://github.com/symfony/process/tree/v7.4.13" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { @@ -8608,7 +8136,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T16:05:06+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { "name": "symfony/var-exporter", diff --git a/tests/Unit/Service/EncryptServiceTest.php b/tests/Unit/Service/EncryptServiceTest.php index 8afe5faf3..b26df4142 100644 --- a/tests/Unit/Service/EncryptServiceTest.php +++ b/tests/Unit/Service/EncryptServiceTest.php @@ -79,15 +79,24 @@ public function testRsaEncryptDecryptLargePayload(): void { * @return void */ public function testRsaEncryptUnderCertificateDecryptsWithMatchingPrivateKey(): void { + // 4096 bits, not 2048, and that is load-bearing rather than cautious. + // EncryptService pads to RSA_BLOCK_SIZE = 512 bytes and encrypts with + // OPENSSL_NO_PADDING, so the key MUST be exactly 4096 bits wide. Under a + // 2048-bit key the 512-byte block does not fit and openssl fails with + // `error:0200006E:rsa routines::data too large for key size` — which this + // test did, on every run, because the fixture contradicted the invariant + // the rest of the app enforces (CertificateAuthorityService mints 4096, + // ApplicationLifecycleService rejects anything below it). + // // CA that issues the suite certificate. - $caKey = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); + $caKey = openssl_pkey_new(['private_key_bits' => 4096, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); $caCsr = openssl_csr_new(['commonName' => 'Doriath Test CA'], $caKey); $caCert = openssl_csr_sign($caCsr, null, $caKey, 365); openssl_x509_export($caCert, $caCertPem); // The user's real key pair. The private half is wrapped + later unwrapped // by the read path; the certificate carries the matching public half. - $userKey = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); + $userKey = openssl_pkey_new(['private_key_bits' => 4096, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); openssl_pkey_export($userKey, $userPrivatePem); $userCsr = openssl_csr_new(['commonName' => 'doriath-user'], $userKey); $userCert = openssl_csr_sign($userCsr, $caCertPem, $caKey, 365); From a571515b68b60247826782cb70296ac7f3f332f5 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 10:05:34 +0200 Subject: [PATCH 27/65] fix(manifest): Dashboard renders CnDashboardPage again (gate-69) (#327) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Dashboard page declared `type: "dashboard"` but carried its eleven widgets in the page-level `widgets[]` array, every one of them targeting the `body` slot. CnPageRenderer renders a body-slot CnWidgetGrid INSTEAD of the typed page component whenever that is true, so the page never mounted CnDashboardPage and silently lost its header, max-width, padding, sidebar and grid discipline. Only `config.widgets` + `config.layout` reach the typed component. hrmq#112 moved 47 pages for exactly this reason; this is doriath's one remaining case, and the failing finding on `Code Quality` (gate-69 page-type-discipline). Note the direction of the trap: the v2 schema documents page-level `widgets[]` as preferred and calls `config.widgets` legacy. For a typed page it is the other way round. All eleven widgets are preserved, in the same order, with the same ids, the same grid coordinates and the same payloads. Three shape differences follow from the move, each verified against the INSTALLED @conduction/nextcloud-vue dist (2.8.2) rather than the schema prose: - banner / stat / text payloads move from `props{}` to `content{}`. CnDashboardPage's registry branch passes the def's `content` both as a `content` prop and spread flat, which is the dual shape those renderers document. - tile fields move to the widget def's TOP LEVEL. `isTile()` is matched before the registry branch and `getTileConfig()` reads title / icon / iconType / backgroundColor / textColor / linkType / linkValue straight off the def — it never looks at `content`, so leaving them nested would have rendered three blank tiles. - `showTitle: false` on the eight widgets that render inside CnWidgetWrapper. Without it a non-card widget grows a header captioned with its own widget id, which the bare CnWidgetGrid never drew. Tiles bypass the wrapper, so they carry no `showTitle`. Verification: gate-69 reported 1 finding before and 0 after (both unscoped and with HYDRA_GATE_BASE_REF=origin/development; the custom-page ratchet stays flat at 13). `npm run check:manifest` passes against schema 2.23.0, `npm run lint` is clean (0 errors), and the vitest suite is 605/605 green across 84 files. --- src/manifest.json | 382 ++++++++++++++++++++++++---------------------- 1 file changed, 202 insertions(+), 180 deletions(-) diff --git a/src/manifest.json b/src/manifest.json index 35ec882db..059e2d68a 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -98,80 +98,63 @@ "title": "Vault overview", "config": { "description": "Your secrets, folders and applications at a glance", - "documentationUrl": "https://doriath.conduction.nl" - }, - "widgets": [ - { - "id": "migration-in-progress-banner", - "widgetKey": "banner", - "slot": "body", - "gridX": 0, - "gridY": 0, - "gridWidth": 12, - "gridHeight": 1, - "_note": "ADR-049 dissolution of the doriath-migration-banner custom widget. The two-state warning/error variant mapping cannot live in ONE banner config (variant is static), so two banner entries carry complementary visibleWhen predicates against GET /api/v1/migrations/status. The legacy widget's click emitted a 'navigate' event that had no consumer (no 'migration' route exists; the migration UI lives in user settings), so no route is configured.", - "props": { - "variant": "warning", - "text": "Compromise recovery in progress — open the migration screen (Settings → Security) for live progress.", - "visibleWhen": { - "endpoint": "/apps/doriath/api/v1/migrations/status", - "field": "status", - "op": "eq", - "value": "in_progress" + "documentationUrl": "https://doriath.conduction.nl", + "_note": "The eleven dashboard widgets live in config.widgets[] + config.layout[], NOT in the page-level widgets[] array. CnPageRenderer renders a bare CnWidgetGrid whenever a typed page carries page-level widgets targeting the body slot, so this type:\"dashboard\" page never mounted CnDashboardPage and lost its header, max-width, padding, sidebar and grid discipline (hydra gate-69 page-type-discipline; hrmq#112 moved 47 pages for the same reason). Only config.widgets/config.layout reach the typed page component. Three shape differences follow from that move, all verified against the installed @conduction/nextcloud-vue dist rather than the schema prose: (1) banner/stat/text payloads move from props{} to content{}, because CnDashboardPage's registry branch passes the widget def's content BOTH as a `content` prop and spread flat, which is the dual shape those renderers document; (2) tile fields move to the widget def's TOP LEVEL, because isTile() is matched before the registry branch and CnDashboardPage.getTileConfig() reads title/icon/iconType/backgroundColor/textColor/linkType/linkValue straight off the def and never looks at content; (3) showTitle:false is set on the eight widgets that render inside CnWidgetWrapper, so they stay header-less exactly as they were under the bare CnWidgetGrid — without it a non-card widget would grow a header captioned with its own widget id. Tiles bypass the wrapper entirely, so they carry no showTitle.", + "widgets": [ + { + "id": "migration-in-progress-banner", + "type": "banner", + "showTitle": false, + "_note": "ADR-049 dissolution of the doriath-migration-banner custom widget. The two-state warning/error variant mapping cannot live in ONE banner config (variant is static), so two banner entries carry complementary visibleWhen predicates against GET /api/v1/migrations/status. The legacy widget's click emitted a 'navigate' event that had no consumer (no 'migration' route exists; the migration UI lives in user settings), so no route is configured.", + "content": { + "variant": "warning", + "text": "Compromise recovery in progress — open the migration screen (Settings → Security) for live progress.", + "visibleWhen": { + "endpoint": "/apps/doriath/api/v1/migrations/status", + "field": "status", + "op": "eq", + "value": "in_progress" + } } - } - }, - { - "id": "migration-errors-banner", - "widgetKey": "banner", - "slot": "body", - "gridX": 0, - "gridY": 1, - "gridWidth": 12, - "gridHeight": 1, - "_note": "Complement of migration-in-progress-banner (error state). NOTE: /api/v1/migrations/status currently only ever returns 'in_progress' or 'none' (MigrationService::getInProgressMigration filters status='in_progress'), so this state is unreachable server-side today — exactly like the legacy widget, whose completed_with_errors branch read summary.migration_status, a key DashboardService::fetchSummary never emits. Fail-safe hidden until the endpoint grows the state.", - "props": { - "variant": "error", - "text": "Compromise recovery completed with errors — open the migration screen (Settings → Security) for details.", - "visibleWhen": { - "endpoint": "/apps/doriath/api/v1/migrations/status", - "field": "status", - "op": "eq", - "value": "completed_with_errors" + }, + { + "id": "migration-errors-banner", + "type": "banner", + "showTitle": false, + "_note": "Complement of migration-in-progress-banner (error state). NOTE: /api/v1/migrations/status currently only ever returns 'in_progress' or 'none' (MigrationService::getInProgressMigration filters status='in_progress'), so this state is unreachable server-side today — exactly like the legacy widget, whose completed_with_errors branch read summary.migration_status, a key DashboardService::fetchSummary never emits. Fail-safe hidden until the endpoint grows the state.", + "content": { + "variant": "error", + "text": "Compromise recovery completed with errors — open the migration screen (Settings → Security) for details.", + "visibleWhen": { + "endpoint": "/apps/doriath/api/v1/migrations/status", + "field": "status", + "op": "eq", + "value": "completed_with_errors" + } } - } - }, - { - "id": "pending-apps-banner", - "widgetKey": "banner", - "slot": "body", - "gridX": 0, - "gridY": 2, - "gridWidth": 12, - "gridHeight": 1, - "_note": "ADR-049 dissolution of the doriath-pending-apps custom widget (admin-only, hidden at zero). Expressed as a banner because hide-when-zero against an app endpoint has no stat-widget equivalent: hideWhenZero exists only on stats-block entries[], which are OR-source-only (register+schema required). visibleWhen pending_apps_count > 0 reproduces both gates in one predicate — non-admins get null (hidden) and admins with an empty queue get 0 (hidden). Routes to ApplicationRegister (the approval queue itself lives in admin settings, which is not a router route).", - "props": { - "variant": "warning", - "text": "Application(s) awaiting approval — open the applications page.", - "visibleWhen": { - "endpoint": "/apps/doriath/api/dashboard/summary", - "field": "pending_apps_count", - "op": "gt", - "value": 0 - }, - "route": "ApplicationRegister" - } - }, - { - "id": "kpi-total-secrets", - "widgetKey": "stat", - "slot": "body", - "gridX": 0, - "gridY": 3, - "gridWidth": 3, - "gridHeight": 2, - "_note": "ADR-049 dissolution of the doriath-kpi-card custom widget — endpoint-bound KPI tiles against GET /api/dashboard/summary. Bound to the payload keys DashboardService::fetchSummary actually emits (total_secrets / shared_with_me_count / folders_count); the legacy view read shared_secrets / folder_count / compromised_count, which the backend never sends. The 'Possibly compromised' card is dropped: compromised_count does not exist in the summary payload, so it was a permanently-zero dead binding.", - "props": { + }, + { + "id": "pending-apps-banner", + "type": "banner", + "showTitle": false, + "_note": "ADR-049 dissolution of the doriath-pending-apps custom widget (admin-only, hidden at zero). Expressed as a banner because hide-when-zero against an app endpoint has no stat-widget equivalent: hideWhenZero exists only on stats-block entries[], which are OR-source-only (register+schema required). visibleWhen pending_apps_count > 0 reproduces both gates in one predicate — non-admins get null (hidden) and admins with an empty queue get 0 (hidden). Routes to ApplicationRegister (the approval queue itself lives in admin settings, which is not a router route).", + "content": { + "variant": "warning", + "text": "Application(s) awaiting approval — open the applications page.", + "visibleWhen": { + "endpoint": "/apps/doriath/api/dashboard/summary", + "field": "pending_apps_count", + "op": "gt", + "value": 0 + }, + "route": "ApplicationRegister" + } + }, + { + "id": "kpi-total-secrets", + "type": "stat", + "showTitle": false, + "_note": "ADR-049 dissolution of the doriath-kpi-card custom widget — endpoint-bound KPI tiles against GET /api/dashboard/summary. Bound to the payload keys DashboardService::fetchSummary actually emits (total_secrets / shared_with_me_count / folders_count); the legacy view read shared_secrets / folder_count / compromised_count, which the backend never sends. The 'Possibly compromised' card is dropped: compromised_count does not exist in the summary payload, so it was a permanently-zero dead binding.", "content": { "label": "Total secrets", "icon": "ClipboardList", @@ -181,17 +164,11 @@ }, "valueField": "total_secrets" } - } - }, - { - "id": "kpi-shared-with-me", - "widgetKey": "stat", - "slot": "body", - "gridX": 3, - "gridY": 3, - "gridWidth": 3, - "gridHeight": 2, - "props": { + }, + { + "id": "kpi-shared-with-me", + "type": "stat", + "showTitle": false, "content": { "label": "Shared with you", "icon": "AccountGroup", @@ -200,17 +177,11 @@ }, "valueField": "shared_with_me_count" } - } - }, - { - "id": "kpi-folders", - "widgetKey": "stat", - "slot": "body", - "gridX": 6, - "gridY": 3, - "gridWidth": 3, - "gridHeight": 2, - "props": { + }, + { + "id": "kpi-folders", + "type": "stat", + "showTitle": false, "content": { "label": "Folders", "icon": "FolderOutline", @@ -219,18 +190,12 @@ }, "valueField": "folders_count" } - } - }, - { - "id": "kpi-rotation-due", - "widgetKey": "stat", - "slot": "body", - "gridX": 9, - "gridY": 3, - "gridWidth": 3, - "gridHeight": 2, - "_note": "Rotation-due count (rotation-expiry-policies §7.2) — open rotation flags for the caller's secrets, from DashboardService::fetchSummary rotation_due_count. Deep-dive detail lives in the password-health report's rotation category.", - "props": { + }, + { + "id": "kpi-rotation-due", + "type": "stat", + "showTitle": false, + "_note": "Rotation-due count (rotation-expiry-policies §7.2) — open rotation flags for the caller's secrets, from DashboardService::fetchSummary rotation_due_count. Deep-dive detail lives in the password-health report's rotation category.", "content": { "label": "Rotation due", "icon": "Autorenew", @@ -240,86 +205,143 @@ }, "valueField": "rotation_due_count" } - } - }, - { - "id": "recent-activity-text", - "widgetKey": "text", - "slot": "body", - "gridX": 0, - "gridY": 5, - "gridWidth": 6, - "gridHeight": 4, - "_note": "ADR-049 dissolution of the doriath-recent-activity static placeholder widget. Replace with a real feed widget once an underlying activity schema lands.", - "props": { + }, + { + "id": "recent-activity-text", + "type": "text", + "showTitle": false, + "_note": "ADR-049 dissolution of the doriath-recent-activity static placeholder widget. Replace with a real feed widget once an underlying activity schema lands.", "content": { "contentMode": "markdown", "text": "**Recent activity**\n\n- Placeholder: user opened a record\n- Placeholder: status changed to Review\n- Placeholder: comment added" } + }, + { + "id": "quick-action-new-secret", + "type": "tile", + "_note": "Union of the bump-ncvue branch's real quick-action tiles with development's ADR-049 placeholder text; wired here in place of the quick-actions-text placeholder since these already point at live deep links. Tile fields sit at the def's top level because CnDashboardPage.getTileConfig() reads them from there, not from content.", + "title": "New secret", + "icon": "Plus", + "iconType": "class", + "backgroundColor": "#21468B", + "textColor": "#ffffff", + "linkType": "app", + "linkValue": "/apps/doriath/secrets" + }, + { + "id": "quick-action-register-application", + "type": "tile", + "title": "Register application", + "icon": "Plus", + "iconType": "class", + "backgroundColor": "#3b82f6", + "textColor": "#ffffff", + "linkType": "app", + "linkValue": "/apps/doriath/applications" + }, + { + "id": "quick-action-documentation", + "type": "tile", + "title": "Documentation", + "icon": "BookOpenVariantOutline", + "iconType": "class", + "backgroundColor": "#6b7280", + "textColor": "#ffffff", + "linkType": "url", + "linkValue": "https://doriath.conduction.nl" } - }, - { - "id": "quick-action-new-secret", - "widgetKey": "tile", - "slot": "body", - "gridX": 6, - "gridY": 5, - "gridWidth": 6, - "gridHeight": 2, - "_note": "Union of the bump-ncvue branch's real quick-action tiles with development's ADR-049 placeholder text; wired here in place of the quick-actions-text placeholder since these already point at live deep links.", - "props": { - "content": { - "title": "New secret", - "icon": "Plus", - "iconType": "class", - "backgroundColor": "#21468B", - "textColor": "#ffffff", - "linkType": "app", - "linkValue": "/apps/doriath/secrets" - } - } - }, - { - "id": "quick-action-register-application", - "widgetKey": "tile", - "slot": "body", - "gridX": 6, - "gridY": 7, - "gridWidth": 3, - "gridHeight": 2, - "props": { - "content": { - "title": "Register application", - "icon": "Plus", - "iconType": "class", - "backgroundColor": "#3b82f6", - "textColor": "#ffffff", - "linkType": "app", - "linkValue": "/apps/doriath/applications" - } - } - }, - { - "id": "quick-action-documentation", - "widgetKey": "tile", - "slot": "body", - "gridX": 9, - "gridY": 7, - "gridWidth": 3, - "gridHeight": 2, - "props": { - "content": { - "title": "Documentation", - "icon": "BookOpenVariantOutline", - "iconType": "class", - "backgroundColor": "#6b7280", - "textColor": "#ffffff", - "linkType": "url", - "linkValue": "https://doriath.conduction.nl" - } + ], + "layout": [ + { + "id": 1, + "widgetId": "migration-in-progress-banner", + "gridX": 0, + "gridY": 0, + "gridWidth": 12, + "gridHeight": 1 + }, + { + "id": 2, + "widgetId": "migration-errors-banner", + "gridX": 0, + "gridY": 1, + "gridWidth": 12, + "gridHeight": 1 + }, + { + "id": 3, + "widgetId": "pending-apps-banner", + "gridX": 0, + "gridY": 2, + "gridWidth": 12, + "gridHeight": 1 + }, + { + "id": 4, + "widgetId": "kpi-total-secrets", + "gridX": 0, + "gridY": 3, + "gridWidth": 3, + "gridHeight": 2 + }, + { + "id": 5, + "widgetId": "kpi-shared-with-me", + "gridX": 3, + "gridY": 3, + "gridWidth": 3, + "gridHeight": 2 + }, + { + "id": 6, + "widgetId": "kpi-folders", + "gridX": 6, + "gridY": 3, + "gridWidth": 3, + "gridHeight": 2 + }, + { + "id": 7, + "widgetId": "kpi-rotation-due", + "gridX": 9, + "gridY": 3, + "gridWidth": 3, + "gridHeight": 2 + }, + { + "id": 8, + "widgetId": "recent-activity-text", + "gridX": 0, + "gridY": 5, + "gridWidth": 6, + "gridHeight": 4 + }, + { + "id": 9, + "widgetId": "quick-action-new-secret", + "gridX": 6, + "gridY": 5, + "gridWidth": 6, + "gridHeight": 2 + }, + { + "id": 10, + "widgetId": "quick-action-register-application", + "gridX": 6, + "gridY": 7, + "gridWidth": 3, + "gridHeight": 2 + }, + { + "id": 11, + "widgetId": "quick-action-documentation", + "gridX": 9, + "gridY": 7, + "gridWidth": 3, + "gridHeight": 2 } - } - ] + ] + } }, { "id": "FeaturesRoadmap", From c083c78af365ea37b08722f960157f65b9cfe836 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Fri, 21 Aug 2026 10:13:43 +0200 Subject: [PATCH 28/65] test(secret-requests): prove the conditional transition against a real database MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The coverage ratchet failed this PR, and it was pointing at something real: `transitionIfPending()` had no test at all. Every service test mocks the mapper, so the method body never ran. I had proved it with a throwaway probe against the dev instance — which is not proof, because nobody runs it again. Database-backed on purpose, and the only such test in the suite. The whole guarantee lives in the SQL: a mocked IQueryBuilder would execute the method and then assert on arguments its own mock recorded, passing just as happily against `UPDATE ... WHERE id = ?` with no status guard — the exact defect this method exists to remove. That test would move a coverage number while measuring nothing. Four cases: a pending row transitions and says so; a second caller loses and writes nothing; a `fulfilled` row cannot be moved to either terminal status (the reported defect, checked for both because the sweeper writes `expired` and an administrator `declined`); an unknown id reports false rather than throwing, so one deleted request cannot abort a sweep. Skips when the suite runs without a bootstrapped Nextcloud. CI runs `occ maintenance:install` against pgsql and `app:enable doriath`, so the migrations have run and these execute there rather than skipping — verified in the workflow log before writing this. Proven to discriminate: removing the `AND status = 'pending'` guard fails exactly the two cases that assert it, and leaves the other two passing, since they do not depend on it. Coverage of the files this PR touches: 203/294 statements, up from 191/294. Assisted-by: ClaudeCode:claude-opus-5 --- tests/Unit/Db/SecretRequestMapperTest.php | 263 ++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 tests/Unit/Db/SecretRequestMapperTest.php diff --git a/tests/Unit/Db/SecretRequestMapperTest.php b/tests/Unit/Db/SecretRequestMapperTest.php new file mode 100644 index 000000000..23659cfd3 --- /dev/null +++ b/tests/Unit/Db/SecretRequestMapperTest.php @@ -0,0 +1,263 @@ + + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @version GIT: + * + * @link https://conduction.nl + */ + +declare(strict_types=1); + +namespace OCA\Doriath\Tests\Unit\Db; + +use DateTime; +use OCA\Doriath\Db\SecretRequest; +use OCA\Doriath\Db\SecretRequestMapper; +use OCP\IDBConnection; +use PHPUnit\Framework\TestCase; + +/** + * Tests for SecretRequestMapper::transitionIfPending(). + */ +class SecretRequestMapperTest extends TestCase { + + /** + * The live database connection, or null when this suite runs without Nextcloud. + * + * @var IDBConnection|null + */ + private ?IDBConnection $db = null; + + /** + * The mapper under test. + * + * @var SecretRequestMapper|null + */ + private ?SecretRequestMapper $mapper = null; + + /** + * Ids inserted by the running test, removed again in tearDown. + * + * @var array + */ + private array $inserted = []; + + /** + * Resolve a real connection, or skip. + * + * The bootstrap loads Nextcloud only when a config with a database exists, so + * this skips rather than fails on a bare checkout. CI runs against an installed + * instance with pgsql, which is where these assertions matter. + * + * @return void + */ + protected function setUp(): void { + parent::setUp(); + + if (class_exists(\OC::class) === false || \OC::$server === null) { + $this->markTestSkipped('needs a bootstrapped Nextcloud with a database'); + } + + $this->db = \OC::$server->get(IDBConnection::class); + + if ($this->db->tableExists('doriath_secret_requests') === false) { + $this->markTestSkipped('doriath migrations have not run on this instance'); + } + + $this->mapper = new SecretRequestMapper(db: $this->db); + }//end setUp() + + /** + * Remove every row this test inserted, whether it passed or failed. + * + * @return void + */ + protected function tearDown(): void { + foreach ($this->inserted as $id) { + $delete = $this->db->getQueryBuilder(); + $delete->delete('doriath_secret_requests') + ->where($delete->expr()->eq('id', $delete->createNamedParameter($id))); + $delete->executeStatement(); + } + + $this->inserted = []; + parent::tearDown(); + }//end tearDown() + + /** + * Insert one request row directly, bypassing the mapper under test. + * + * Written with the query builder rather than `insert()` so the row's starting + * status is stated by the test rather than produced by the code being tested. + * + * @param string $status The status to start the row in + * + * @return string The new row's id + */ + private function insertRequest(string $status): string { + $id = 'test-' . bin2hex(random_bytes(8)); + + $insert = $this->db->getQueryBuilder(); + $insert->insert('doriath_secret_requests')->values([ + 'id' => $insert->createNamedParameter($id), + 'secret_id' => $insert->createNamedParameter('sec-' . $id), + 'encryption_suite_id' => $insert->createNamedParameter('suite-' . $id), + 'token' => $insert->createNamedParameter('tok-' . $id), + 'requested_fields' => $insert->createNamedParameter('["key"]'), + 'status' => $insert->createNamedParameter($status), + 'created_by' => $insert->createNamedParameter('test-actor'), + 'created_at' => $insert->createNamedParameter( + (new DateTime('2026-01-01T00:00:00Z'))->format('Y-m-d H:i:s') + ), + ]); + $insert->executeStatement(); + + $this->inserted[] = $id; + + return $id; + }//end insertRequest() + + /** + * Read a row's status straight from the table. + * + * @param string $id The row to read + * + * @return string|null The status, or null when the row is gone + */ + private function statusOf(string $id): ?string { + $query = $this->db->getQueryBuilder(); + $query->select('status')->from('doriath_secret_requests') + ->where($query->expr()->eq('id', $query->createNamedParameter($id))); + + $result = $query->executeQuery(); + $status = $result->fetchOne(); + $result->closeCursor(); + + return $status === false ? null : (string)$status; + }//end statusOf() + + /** + * A pending row transitions, and the call says it did. + * + * @return void + * + * @spec openspec/specs/secret-requests/spec.md#requirement-optional-expiry + */ + public function testAPendingRequestTransitions(): void { + $id = $this->insertRequest(status: SecretRequest::STATUS_PENDING); + + $this->assertTrue( + $this->mapper->transitionIfPending( + requestId: $id, + toStatus: SecretRequest::STATUS_EXPIRED + ), + 'a pending row must transition' + ); + $this->assertSame(SecretRequest::STATUS_EXPIRED, $this->statusOf($id)); + }//end testAPendingRequestTransitions() + + /** + * The second caller loses, and changes nothing. + * + * This is the race itself: two actors that both read `pending`. Exactly one may + * win, and the loser must be told so — the boolean is what every caller keys its + * cleanup and auditing off, so a false positive here becomes an audit event for + * something that never happened. + * + * @return void + * + * @spec openspec/specs/secret-requests/spec.md#requirement-optional-expiry + */ + public function testTheSecondTransitionLosesAndChangesNothing(): void { + $id = $this->insertRequest(status: SecretRequest::STATUS_PENDING); + + $this->mapper->transitionIfPending(requestId: $id, toStatus: SecretRequest::STATUS_EXPIRED); + + $this->assertFalse( + $this->mapper->transitionIfPending( + requestId: $id, + toStatus: SecretRequest::STATUS_DECLINED + ), + 'a row that is no longer pending must refuse the transition' + ); + $this->assertSame( + SecretRequest::STATUS_EXPIRED, + $this->statusOf($id), + 'the losing caller must not have written its own status' + ); + }//end testTheSecondTransitionLosesAndChangesNothing() + + /** + * A fulfilled row cannot be expired or declined. This is the reported defect. + * + * Filling persists the submitted values BEFORE flipping the status, so an + * overwrite here left the row terminal with `fulfilled_at` still set: the + * requester was told their request had lapsed while the credential was already + * in their vault. Reported by review on PR #282 and #286. + * + * Both terminal statuses are checked because the two callers write different + * ones — the sweeper `expired`, an administrator `declined` — and the guard is + * on the row's CURRENT status, not on what is being written. + * + * @return void + * + * @spec openspec/specs/secret-requests/spec.md#requirement-optional-expiry + */ + public function testAFulfilledRequestIsUntouchable(): void { + $id = $this->insertRequest(status: SecretRequest::STATUS_FULFILLED); + + foreach ([SecretRequest::STATUS_EXPIRED, SecretRequest::STATUS_DECLINED] as $target) { + $this->assertFalse( + $this->mapper->transitionIfPending(requestId: $id, toStatus: $target), + 'a fulfilled request must not be moved to ' . $target + ); + $this->assertSame( + SecretRequest::STATUS_FULFILLED, + $this->statusOf($id), + 'the fulfilled status must survive an attempt to write ' . $target + ); + } + }//end testAFulfilledRequestIsUntouchable() + + /** + * An id that matches nothing reports false rather than throwing. + * + * The sweeper walks a batch; a request deleted mid-sweep must not abort the rest + * of it. + * + * @return void + * + * @spec openspec/specs/secret-requests/spec.md#requirement-optional-expiry + */ + public function testAnUnknownRequestReportsFalse(): void { + $this->assertFalse( + $this->mapper->transitionIfPending( + requestId: 'test-does-not-exist', + toStatus: SecretRequest::STATUS_EXPIRED + ) + ); + }//end testAnUnknownRequestReportsFalse() + +}//end class From 57b99887afb4ee86a426049b7b321f204ba8804e Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Fri, 21 Aug 2026 10:20:06 +0200 Subject: [PATCH 29/65] style(tests): named parameters in the new mapper test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `phpcs.xml` scans only `lib`, so this file is outside CI's phpcs scope — but the tests around it use named arguments and forbid inline ifs, and a file that reads differently from its neighbours for no reason is just noise for the next reader. No behavioural change; the four cases still pass. Assisted-by: ClaudeCode:claude-opus-5 --- tests/Unit/Db/SecretRequestMapperTest.php | 41 +++++++++++++---------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/tests/Unit/Db/SecretRequestMapperTest.php b/tests/Unit/Db/SecretRequestMapperTest.php index 23659cfd3..e2388d782 100644 --- a/tests/Unit/Db/SecretRequestMapperTest.php +++ b/tests/Unit/Db/SecretRequestMapperTest.php @@ -77,13 +77,13 @@ protected function setUp(): void { parent::setUp(); if (class_exists(\OC::class) === false || \OC::$server === null) { - $this->markTestSkipped('needs a bootstrapped Nextcloud with a database'); + $this->markTestSkipped(message: 'needs a bootstrapped Nextcloud with a database'); } $this->db = \OC::$server->get(IDBConnection::class); if ($this->db->tableExists('doriath_secret_requests') === false) { - $this->markTestSkipped('doriath migrations have not run on this instance'); + $this->markTestSkipped(message: 'doriath migrations have not run on this instance'); } $this->mapper = new SecretRequestMapper(db: $this->db); @@ -155,7 +155,11 @@ private function statusOf(string $id): ?string { $status = $result->fetchOne(); $result->closeCursor(); - return $status === false ? null : (string)$status; + if ($status === false) { + return null; + } + + return (string)$status; }//end statusOf() /** @@ -169,13 +173,16 @@ public function testAPendingRequestTransitions(): void { $id = $this->insertRequest(status: SecretRequest::STATUS_PENDING); $this->assertTrue( - $this->mapper->transitionIfPending( + condition: $this->mapper->transitionIfPending( requestId: $id, toStatus: SecretRequest::STATUS_EXPIRED ), - 'a pending row must transition' + message: 'a pending row must transition' + ); + $this->assertSame( + expected: SecretRequest::STATUS_EXPIRED, + actual: $this->statusOf(id: $id) ); - $this->assertSame(SecretRequest::STATUS_EXPIRED, $this->statusOf($id)); }//end testAPendingRequestTransitions() /** @@ -196,16 +203,16 @@ public function testTheSecondTransitionLosesAndChangesNothing(): void { $this->mapper->transitionIfPending(requestId: $id, toStatus: SecretRequest::STATUS_EXPIRED); $this->assertFalse( - $this->mapper->transitionIfPending( + condition: $this->mapper->transitionIfPending( requestId: $id, toStatus: SecretRequest::STATUS_DECLINED ), - 'a row that is no longer pending must refuse the transition' + message: 'a row that is no longer pending must refuse the transition' ); $this->assertSame( - SecretRequest::STATUS_EXPIRED, - $this->statusOf($id), - 'the losing caller must not have written its own status' + expected: SecretRequest::STATUS_EXPIRED, + actual: $this->statusOf(id: $id), + message: 'the losing caller must not have written its own status' ); }//end testTheSecondTransitionLosesAndChangesNothing() @@ -230,13 +237,13 @@ public function testAFulfilledRequestIsUntouchable(): void { foreach ([SecretRequest::STATUS_EXPIRED, SecretRequest::STATUS_DECLINED] as $target) { $this->assertFalse( - $this->mapper->transitionIfPending(requestId: $id, toStatus: $target), - 'a fulfilled request must not be moved to ' . $target + condition: $this->mapper->transitionIfPending(requestId: $id, toStatus: $target), + message: 'a fulfilled request must not be moved to ' . $target ); $this->assertSame( - SecretRequest::STATUS_FULFILLED, - $this->statusOf($id), - 'the fulfilled status must survive an attempt to write ' . $target + expected: SecretRequest::STATUS_FULFILLED, + actual: $this->statusOf(id: $id), + message: 'the fulfilled status must survive an attempt to write ' . $target ); } }//end testAFulfilledRequestIsUntouchable() @@ -253,7 +260,7 @@ public function testAFulfilledRequestIsUntouchable(): void { */ public function testAnUnknownRequestReportsFalse(): void { $this->assertFalse( - $this->mapper->transitionIfPending( + condition: $this->mapper->transitionIfPending( requestId: 'test-does-not-exist', toStatus: SecretRequest::STATUS_EXPIRED ) From cbb06ea2929c76f84c75a3fd6478931aaea5c8dc Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Fri, 21 Aug 2026 11:47:29 +0200 Subject: [PATCH 30/65] feat(secrets-write-ui): let an owner write their own additional fields (#294) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(secrets-write-ui): let an owner write their own additional fields Closes #272. The whole stack supported additional fields — controller, service, store encrypt/decrypt, and SecretDetail rendering them — except the two dialogs an owner actually uses. So an owner could SEE a member on their secret but never write one: the only writers were the write-for-application dialog, import, and an external recipient filling a secret request. Obtaining an additional field on your own secret meant asking a stranger to submit it. Both requirements enumerated the collectable fields and omitted this one, which is how it shipped. **One rule, three call sites.** The reserved-name check (`key`, `login`, `url` address the Secret's own columns, so a member with one of those names is misrouted or shadowed rather than being a second field with that label) now lives in src/utils/additionalFields.js. It was written out inside the request dialog; a second copy in each secret dialog is how `url` ends up accepted in one place and refused in another. Sharing it did cost something, and it is fixed rather than shrugged at: the request dialog's old message said "tick it in the list above", which is right there and wrong in the secret dialogs. The shared message is context-neutral, so neither dialog now sends the user looking for something that is not on screen. **Empty is not absent.** Removing the last member sends `{}`, never null: the store reads an absent field as "leave the stored blob alone", so null would resurrect the member the user just deleted. Pinned at both layers, because the distinction is a JavaScript trap rather than a UI one. **What the tests actually prove.** The store spec mocks rsaEncrypt as ENC(), so its "ciphertext" contains its own plaintext — asserting no plaintext leaks there would be asserting a property of the mock, and it failed for exactly that reason on the first run. That test now claims only what the seam can support (members appear in the blob field and nowhere else), and a separate spec does the real work with actual RSA-4096 keys: round-tripping unicode, punctuation and an equals sign, and asserting neither member names nor values appear in the ciphertext. Member names matter as much as values — that a secret HAS a `recovery-codes` field is itself information. **Two scenarios finally paid for.** The delta re-declares create-a-secret-inside-the-current-folder and name-and-value-are-required, which I moved off PR #270 as a known gap rather than waive with a false exclude. Neither had any coverage, vitest or Playwright. Both now do. Every scenario in the two modified requirements is annotated: two point at existing Playwright tests, six carry excludes that each name the specific tests driving them. No exclude claims coverage that does not exist. Proven to discriminate: removing the reserved-name check fails exactly the four naming tests; removing the edit dialog's diff block fails exactly the three write-path tests. Also found, in the existing harness: `sessionStore.isLocked = false` is a no-op — isLocked is a getter over cryptoKey, and Vue logs "target is readonly". The generator spec does that too, so those tests never exercised an unlocked dialog. My specs set cryptoKey instead. Not fixing the older spec here, but it is worth knowing before someone writes a fourth one. Ten new UI strings translated into all 37 locales, mixed-script scan clean. Sweep: hydra gates 49/49 applicable, vitest 637 (from 594), eslint src 0 errors, prettier clean (my three files had drifted; the baseline was clean, so that was mine), PHPUnit 1046 unchanged, webpack compiled, openspec 48/50 — the same pre-existing encryption-suites SHALL/MUST failure. Assisted-by: ClaudeCode:claude-opus-5 * fix(secrets-write-ui): hide the additional-fields section when there are none /opsx-verify found a scenario in this change's own delta unsatisfied, in the state this change is what makes reachable. `hasAdditionalFields()` tested that the blob was truthy and an object. `{}` is both. So after removing the last member — which now deliberately sends an empty blob, because null would mean "leave the stored blob alone" and resurrect the member — the detail view rendered the "Additional fields" heading with nothing underneath it. The spec says re-opening the secret MUST show no additional fields. Unreachable before now: the only writers were the application dialog, import and a request fill, all of which write at least one member. Nobody could produce an empty blob from the UI until an owner could remove one. The guard now requires at least one entry and returns a real boolean rather than the last truthy operand — it was handing back `null` for a secret with no blob, which is why the second test in the new spec also failed. Four tests, and two of them failed before the fix for the right reasons. SecretDetail was listed as a Non-Goal ("reworking its read-only rendering, which already works"), and this is not a rework: it is a one-line guard for a state that view had never been shown. Also from the same verify: Four methods I touched lacked @spec anchors (gate-16), and prettier flagged two files — those from #271, whose task list did not include prettier while this change's does. Fixed rather than left for the next branch to trip over. `prettier --write` over src/ changed only the six files this work had touched. docs/additional-fields.md documents the feature for the first time: that the field NAMES are encrypted too (so they cannot be searched, and that is the boundary working), that one edit rewrites the whole blob and what that costs with two sessions open, why three names are refused, and that removals are recoverable from version history. Every claim checked against the code. Checked and NOT a finding: the create dialog leaves the editor enabled while the vault is locked. The requirement's "MUST be blocked" is satisfied at the dialog level — a warning card plus canSubmit — and no other field disables itself either, so matching the surrounding pattern is right. Also verified `fieldsChanged()` really does include `getAdditionalFields()`, so a member-only edit does produce a version row, as design.md claims. Sweep: gates 49/49 applicable, vitest 641, eslint src 0 errors, prettier clean, webpack compiled, l10n both exit 0. Assisted-by: ClaudeCode:claude-opus-5 * docs(secrets-write-ui): sync the additional-fields deltas into the main spec Two MODIFIED requirements, and one of them needed care rather than a wholesale replacement: the delta's body for "Edit a Secret from the UI" does not repeat the existing "Edit metadata only" scenario, so copying the delta over the main spec would have deleted it silently. Preserved in its original position, per the sync rule that content the delta does not mention stays. That scenario turned out to have NO coverage at all — no vitest, no Playwright — which the diff-scoped gate would not have told me, because I had not modified the scenario itself. Covered now ("alters no ciquiretext when only the name changes" asserts the diff carries the new name and none of key, login or additionalFields) and annotated, rather than left green on a scoping technicality. The spec also gained the tracking header it never had. Reverted off PR #270 in 67a17709 because it announced a change that branch did not implement; this branch does, so it belongs here. Still uncovered in this spec, and NOT this change's to fix — reported rather than hidden behind the green: modal-opened-via-registry-dispatcher and revoke-a-link-share, both in requirements this change does not touch. gate-19 passes because it scopes to added or modified scenarios, which is the gate working as designed, not the coverage existing. Assisted-by: ClaudeCode:claude-opus-5 * docs(secrets-write-ui): archive owner-editable-additional-fields 12/12 tasks, verified by /opsx-verify. Archived as 2026-08-19-owner-editable-additional-fields, with --skip-specs because the deltas were already synced in 7afd5c62 and openspec refuses to re-apply a MODIFIED requirement it has already applied. Every @spec anchor that pointed into the change directory was retargeted to openspec/specs/secrets-write-ui/spec.md first — 12 files. Archiving moves that directory, so the anchors would have started rotting the moment this landed, which is the same debt #271 uncovered and gate-46 cannot see because it is diff-scoped. Assisted-by: ClaudeCode:claude-opus-5 * fix(ci): format this branch's specs, completing the stack's CI fixes Five spec files this branch added were unformatted: the AdditionalFieldsEditor, SecretCreateDialog/SecretEditDialog additional-fields and secret store specs, and the real-crypto one. CI checks prettier over the whole repo, which is the scope I had not been using locally. This branch also took the merges from feature/271 and fix/development-openspec-chores, so it now carries the whole stack's fixes. **The spec conflict was worth resolving carefully rather than picking a side.** Both branches had written an `@e2e exclude` for the same three scenarios, each citing its own tests — the fix branch's SecretCreateDialog.requiredFields and SecretEditDialog.metadataOnly, this branch's *.additionalFields equivalents. After the merge BOTH test sets exist, so taking either side alone would have left an exclude that named only half of what actually drives the scenario. The resolved text names both. secrets-write-ui is now 19/19 scenarios accounted for — twelve by a Playwright test, seven by an exclude that names the tests actually driving it. A process note, since it is the second time today: my first attempt at this resolution used a DOTALL regex that matched across scenario boundaries, hit an assertion, and the `git add` chained after it in the same command staged the conflict markers regardless. Nothing was committed either time — MERGE_HEAD was still present, so `git checkout --merge` restored the stages — and the redo was line-based with the audit run BEFORE staging. Do not chain a stage onto a script whose success has not been checked. Sweep: gates 49/49 applicable, PHPUnit passing, vitest 653, phpcs 0 errors, php-cs-fixer clean, format/lint/test:l10n exit 0, l10n parity 0, openspec 48/49 (the remaining failure is leaf-integrations, someone else's unfinished change). Assisted-by: ClaudeCode:claude-opus-5 * test(secrets-write-ui): drop a duplicated folder-default case `defaults the folder to the one being viewed, and persists it` asserted exactly what `SecretCreateDialog.requiredFields.spec.js:135-143` already asserts, line for line, and it sat in a file named for additional fields while testing folder defaulting. Padding, not coverage. The remaining assertions are unchanged, so nothing is lost. Assisted-by: ClaudeCode:claude-opus-5 --------- Co-authored-by: Ruben van der Linde Co-authored-by: Ruben van der Linde --- docs/additional-fields.md | 66 +++++ l10n/be.json | 12 +- l10n/bg.json | 10 +- l10n/bs.json | 10 +- l10n/ca.json | 12 +- l10n/cs.json | 12 +- l10n/da.json | 10 +- l10n/de.json | 10 +- l10n/el.json | 12 +- l10n/en.json | 9 +- l10n/es.json | 10 +- l10n/et.json | 10 +- l10n/fi.json | 10 +- l10n/fr.json | 10 +- l10n/ga.json | 10 +- l10n/hr.json | 10 +- l10n/hu.json | 10 +- l10n/is.json | 12 +- l10n/it.json | 10 +- l10n/lb.json | 12 +- l10n/lt.json | 10 +- l10n/lv.json | 10 +- l10n/mk.json | 12 +- l10n/mt.json | 10 +- l10n/nb.json | 12 +- l10n/nl.json | 12 +- l10n/pl.json | 12 +- l10n/pt.json | 10 +- l10n/rm.json | 12 +- l10n/ro.json | 12 +- l10n/ru.json | 10 +- l10n/sk.json | 12 +- l10n/sl.json | 10 +- l10n/sq.json | 12 +- l10n/sr.json | 12 +- l10n/sv.json | 12 +- l10n/tr.json | 10 +- l10n/uk.json | 12 +- .../.openspec.yaml | 0 .../design.md | 0 .../plan.json | 28 +- .../proposal.md | 0 .../specs/secrets-write-ui/spec.md | 6 + .../tasks.md | 24 +- openspec/specs/secrets-write-ui/spec.md | 62 +++- src/components/AdditionalFieldsEditor.vue | 275 ++++++++++++++++++ src/dialogs/SecretCreateDialog.vue | 33 ++- src/dialogs/SecretEditDialog.vue | 44 ++- src/dialogs/SecretRequestCreateDialog.vue | 21 +- src/utils/additionalFields.js | 122 ++++++++ src/views/SecretDetail.vue | 27 +- .../components/AdditionalFieldsEditor.spec.js | 211 ++++++++++++++ ...ecretCreateDialog.additionalFields.spec.js | 178 ++++++++++++ .../SecretEditDialog.additionalFields.spec.js | 212 ++++++++++++++ tests/store/secret-additional-fields.spec.js | 133 +++++++++ .../SecretDetail.additionalFields.spec.js | 118 ++++++++ tests/vitest/additional-fields-crypto.spec.js | 86 ++++++ tests/vitest/additional-fields.spec.js | 112 +++++++ 58 files changed, 2056 insertions(+), 105 deletions(-) create mode 100644 docs/additional-fields.md rename openspec/changes/{owner-editable-additional-fields => archive/2026-08-19-owner-editable-additional-fields}/.openspec.yaml (100%) rename openspec/changes/{owner-editable-additional-fields => archive/2026-08-19-owner-editable-additional-fields}/design.md (100%) rename openspec/changes/{owner-editable-additional-fields => archive/2026-08-19-owner-editable-additional-fields}/plan.json (86%) rename openspec/changes/{owner-editable-additional-fields => archive/2026-08-19-owner-editable-additional-fields}/proposal.md (100%) rename openspec/changes/{owner-editable-additional-fields => archive/2026-08-19-owner-editable-additional-fields}/specs/secrets-write-ui/spec.md (65%) rename openspec/changes/{owner-editable-additional-fields => archive/2026-08-19-owner-editable-additional-fields}/tasks.md (74%) create mode 100644 src/components/AdditionalFieldsEditor.vue create mode 100644 src/utils/additionalFields.js create mode 100644 tests/components/AdditionalFieldsEditor.spec.js create mode 100644 tests/dialogs/SecretCreateDialog.additionalFields.spec.js create mode 100644 tests/dialogs/SecretEditDialog.additionalFields.spec.js create mode 100644 tests/store/secret-additional-fields.spec.js create mode 100644 tests/views/SecretDetail.additionalFields.spec.js create mode 100644 tests/vitest/additional-fields-crypto.spec.js create mode 100644 tests/vitest/additional-fields.spec.js diff --git a/docs/additional-fields.md b/docs/additional-fields.md new file mode 100644 index 000000000..b9efdc178 --- /dev/null +++ b/docs/additional-fields.md @@ -0,0 +1,66 @@ + + +# Additional fields + +A secret has a few fields Doriath knows by name — its value, a login, a URL — and +then whatever else you need. Those extras are **additional fields**: named entries +you add yourself, like `client-id`, `tenant` or `recovery-codes`. + +You can add them when creating a secret and change them afterwards from **Edit**. + +## They are encrypted, names included + +All additional fields live together in one encrypted blob on the secret. The +encryption happens in your browser before anything is sent, so the server stores +ciphertext and cannot read it — the same guarantee as the secret's own value. + +**The field names are inside that blob too.** That a secret has a field called +`recovery-codes` is itself information, so it is not left in the clear. + +Two consequences follow from this, and both are deliberate: + +- **You cannot search for a field name.** Searching happens on the server, which + cannot read the names. This is the encryption boundary working, not a missing + feature. +- **Changing one field rewrites them all.** The blob is the unit of storage, so + saving re-encrypts every field, not just the one you touched. If you have the + same secret open in two places and both save, the later save wins for the whole + set — a field added in the other session is lost. The Edit dialog always loads + the current values first, so the window is small, but it exists. + +## Three names you cannot use + +`key`, `login` and `url` are refused, with a reason shown. + +Those names already belong to the secret's own fields. A field named `url` would +not be a second field that happens to be called that — it would collide with the +real URL, which is stored as searchable plaintext rather than ciphertext. So +instead of quietly putting your value somewhere you did not intend, Doriath asks +you to pick a different name. + +The same rule applies when renaming an existing field, and when asking somebody +else to fill one in via a secret request. + +## Removing them + +Removing a field deletes it from the secret. Remove the last one and the secret +simply has no additional fields any more — the section disappears from the detail +view. + +Every change is recorded in the secret's version history, so a field you removed +by accident can be recovered there. + +## Asking somebody else instead + +You do not have to know the value yourself. A [secret +request](./secret-request-expiry.md) can ask another person to fill in one or more +named fields, and you never see what they submit — that is write-without-read. This +page is about the case where you *do* know the value and simply want to store it. + +## Reference + +- [Architecture & data model](./ARCHITECTURE.md) — where `additional_fields` sits on the Secret. +- [Secret request expiry](./secret-request-expiry.md) — asking someone else to fill a field in. diff --git a/l10n/be.json b/l10n/be.json index 394b55bd0..4cae98242 100644 --- a/l10n/be.json +++ b/l10n/be.json @@ -95,7 +95,7 @@ "Note": "Заметка", "Login": "Логин", "Copy login": "Скопировать логин", - "Additional fields": "Дополнительные поля", + "Additional fields": "Дадатковыя палі", "Back to vault": "Вернуться в хранилище", "Delete secret": "Удалить секрет", "Cannot open secret": "Невозможно открыть секрет", @@ -417,7 +417,15 @@ "Revoking…": "Адкліканне…", "Expired {when}": "Скончыўся {when}", "Expires {when}": "Сканчаецца {when}", - "No expiry": "Без тэрміну дзеяння" + "No expiry": "Без тэрміну дзеяння", + "No additional fields yet.": "Дадатковых палёў пакуль няма.", + "Field name": "Назва поля", + "Value": "Значэнне", + "Add a field": "Дадаць поле", + "Remove": "Выдаліць", + "Remove this field": "Выдаліць гэта поле", + "Give the field a name.": "Дайце полю назву.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» — убудаванае поле, а не дадатковае: выберыце іншую назву." }, "plurals": null } diff --git a/l10n/bg.json b/l10n/bg.json index bb9050bff..511895edc 100644 --- a/l10n/bg.json +++ b/l10n/bg.json @@ -417,7 +417,15 @@ "Revoking…": "Отменяне…", "Expired {when}": "Изтекла {when}", "Expires {when}": "Изтича {when}", - "No expiry": "Без изтичане" + "No expiry": "Без изтичане", + "No additional fields yet.": "Все още няма допълнителни полета.", + "Field name": "Име на полето", + "Value": "Стойност", + "Add a field": "Добавяне на поле", + "Remove": "Премахване", + "Remove this field": "Премахване на това поле", + "Give the field a name.": "Дайте име на полето.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ е вградено поле, а не допълнително — изберете друго име." }, "plurals": null } diff --git a/l10n/bs.json b/l10n/bs.json index 16417106b..733e1feaa 100644 --- a/l10n/bs.json +++ b/l10n/bs.json @@ -417,7 +417,15 @@ "Revoking…": "Povlačenje…", "Expired {when}": "Isteklo {when}", "Expires {when}": "Istječe {when}", - "No expiry": "Bez isteka" + "No expiry": "Bez isteka", + "No additional fields yet.": "Još nema dodatnih polja.", + "Field name": "Naziv polja", + "Value": "Vrijednost", + "Add a field": "Dodaj polje", + "Remove": "Ukloni", + "Remove this field": "Ukloni ovo polje", + "Give the field a name.": "Dajte polju naziv.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ je ugrađeno polje, a ne dodatno — odaberite drugi naziv." }, "plurals": null } diff --git a/l10n/ca.json b/l10n/ca.json index 8125c6024..5d2658af9 100644 --- a/l10n/ca.json +++ b/l10n/ca.json @@ -95,7 +95,7 @@ "Note": "Nota", "Login": "Inicio de sesión", "Copy login": "Copiar inicio de sesión", - "Additional fields": "Campos adicionales", + "Additional fields": "Camps addicionals", "Back to vault": "Volver a la bóveda", "Delete secret": "Eliminar secreto", "Cannot open secret": "No se puede abrir el secreto", @@ -417,7 +417,15 @@ "Revoking…": "S’està revocant…", "Expired {when}": "Caducat {when}", "Expires {when}": "Caduca {when}", - "No expiry": "Sense caducitat" + "No expiry": "Sense caducitat", + "No additional fields yet.": "Encara no hi ha camps addicionals.", + "Field name": "Nom del camp", + "Value": "Valor", + "Add a field": "Afegeix un camp", + "Remove": "Elimina", + "Remove this field": "Elimina aquest camp", + "Give the field a name.": "Doneu un nom al camp.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» és un camp integrat, no addicional: trieu un altre nom." }, "plurals": null } diff --git a/l10n/cs.json b/l10n/cs.json index 4e74de566..9b2f0cf59 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -95,7 +95,7 @@ "Note": "Bilješka", "Login": "Prijava", "Copy login": "Kopiraj prijavu", - "Additional fields": "Dodatna polja", + "Additional fields": "Další pole", "Back to vault": "Natrag na trezor", "Delete secret": "Izbriši tajnu", "Cannot open secret": "Nije moguće otvoriti tajnu", @@ -417,7 +417,15 @@ "Revoking…": "Ruší se…", "Expired {when}": "Vypršelo {when}", "Expires {when}": "Vyprší {when}", - "No expiry": "Bez vypršení" + "No expiry": "Bez vypršení", + "No additional fields yet.": "Zatím žádná další pole.", + "Field name": "Název pole", + "Value": "Hodnota", + "Add a field": "Přidat pole", + "Remove": "Odebrat", + "Remove this field": "Odebrat toto pole", + "Give the field a name.": "Pojmenujte pole.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ je vestavěné pole, nikoli další — zvolte jiný název." }, "plurals": null } diff --git a/l10n/da.json b/l10n/da.json index 91a5bf7d8..9bea80174 100644 --- a/l10n/da.json +++ b/l10n/da.json @@ -417,7 +417,15 @@ "Revoking…": "Tilbagekalder…", "Expired {when}": "Udløbet {when}", "Expires {when}": "Udløber {when}", - "No expiry": "Ingen udløbsdato" + "No expiry": "Ingen udløbsdato", + "No additional fields yet.": "Ingen yderligere felter endnu.", + "Field name": "Feltnavn", + "Value": "Værdi", + "Add a field": "Tilføj et felt", + "Remove": "Fjern", + "Remove this field": "Fjern dette felt", + "Give the field a name.": "Giv feltet et navn.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” er et indbygget felt, ikke et yderligere — vælg et andet navn." }, "plurals": null } diff --git a/l10n/de.json b/l10n/de.json index 8ba47d405..f8df8d8d9 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -417,7 +417,15 @@ "Revoking…": "Widerrufen…", "Expired {when}": "Abgelaufen {when}", "Expires {when}": "Läuft ab {when}", - "No expiry": "Kein Ablauf" + "No expiry": "Kein Ablauf", + "No additional fields yet.": "Noch keine zusätzlichen Felder.", + "Field name": "Feldname", + "Value": "Wert", + "Add a field": "Feld hinzufügen", + "Remove": "Entfernen", + "Remove this field": "Dieses Feld entfernen", + "Give the field a name.": "Geben Sie dem Feld einen Namen.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ ist ein integriertes Feld, kein zusätzliches — wählen Sie einen anderen Namen." }, "plurals": null } diff --git a/l10n/el.json b/l10n/el.json index 4c3d7f527..17deba672 100644 --- a/l10n/el.json +++ b/l10n/el.json @@ -95,7 +95,7 @@ "Note": "Σημείωση", "Login": "Σύνδεση", "Copy login": "Αντιγραφή σύνδεσης", - "Additional fields": "Επιπλέον πεδία", + "Additional fields": "Πρόσθετα πεδία", "Back to vault": "Επιστροφή στο θησαυροφυλάκιο", "Delete secret": "Διαγραφή μυστικού", "Cannot open secret": "Δεν είναι δυνατό το άνοιγμα του μυστικού", @@ -417,7 +417,15 @@ "Revoking…": "Ανάκληση…", "Expired {when}": "Έληξε {when}", "Expires {when}": "Λήγει {when}", - "No expiry": "Χωρίς λήξη" + "No expiry": "Χωρίς λήξη", + "No additional fields yet.": "Δεν υπάρχουν ακόμη πρόσθετα πεδία.", + "Field name": "Όνομα πεδίου", + "Value": "Τιμή", + "Add a field": "Προσθήκη πεδίου", + "Remove": "Αφαίρεση", + "Remove this field": "Αφαίρεση αυτού του πεδίου", + "Give the field a name.": "Δώστε όνομα στο πεδίο.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "Το «{name}» είναι ενσωματωμένο πεδίο, όχι πρόσθετο — επιλέξτε άλλο όνομα." }, "plurals": null } diff --git a/l10n/en.json b/l10n/en.json index 7b37b1638..513afbba8 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -987,7 +987,14 @@ "The fill link stops working immediately, even if someone already has it. If the application is waiting for this credential, its integration may stop working.": "The fill link stops working immediately, even if someone already has it. If the application is waiting for this credential, its integration may stop working.", "This application asked for: {fields}": "This application asked for: {fields}", "Expired {when}": "Expired {when}", - "Expires {when}": "Expires {when}" + "Expires {when}": "Expires {when}", + "Add a field": "Add a field", + "Field name": "Field name", + "Give the field a name.": "Give the field a name.", + "No additional fields yet.": "No additional fields yet.", + "Remove": "Remove", + "Remove this field": "Remove this field", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” is a built-in field, not an additional one — choose a different name." }, "plurals": "" } diff --git a/l10n/es.json b/l10n/es.json index af27301de..8075ca508 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -417,7 +417,15 @@ "Revoking…": "Revocando…", "Expired {when}": "Caducado {when}", "Expires {when}": "Caduca {when}", - "No expiry": "Sin caducidad" + "No expiry": "Sin caducidad", + "No additional fields yet.": "Todavía no hay campos adicionales.", + "Field name": "Nombre del campo", + "Value": "Valor", + "Add a field": "Añadir un campo", + "Remove": "Eliminar", + "Remove this field": "Eliminar este campo", + "Give the field a name.": "Dale un nombre al campo.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» es un campo integrado, no adicional: elige otro nombre." }, "plurals": null } diff --git a/l10n/et.json b/l10n/et.json index 3d84368f5..20463b1b1 100644 --- a/l10n/et.json +++ b/l10n/et.json @@ -417,7 +417,15 @@ "Revoking…": "Tühistamine…", "Expired {when}": "Aegunud {when}", "Expires {when}": "Aegub {when}", - "No expiry": "Aegumiseta" + "No expiry": "Aegumiseta", + "No additional fields yet.": "Lisavälju veel ei ole.", + "Field name": "Välja nimi", + "Value": "Väärtus", + "Add a field": "Lisa väli", + "Remove": "Eemalda", + "Remove this field": "Eemalda see väli", + "Give the field a name.": "Anna väljale nimi.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ on sisseehitatud väli, mitte lisaväli — vali teine nimi." }, "plurals": null } diff --git a/l10n/fi.json b/l10n/fi.json index e6edc4006..d8ca2d77c 100644 --- a/l10n/fi.json +++ b/l10n/fi.json @@ -417,7 +417,15 @@ "Revoking…": "Perutaan…", "Expired {when}": "Vanhentunut {when}", "Expires {when}": "Vanhenee {when}", - "No expiry": "Ei vanhenemista" + "No expiry": "Ei vanhenemista", + "No additional fields yet.": "Ei vielä lisäkenttiä.", + "Field name": "Kentän nimi", + "Value": "Arvo", + "Add a field": "Lisää kenttä", + "Remove": "Poista", + "Remove this field": "Poista tämä kenttä", + "Give the field a name.": "Anna kentälle nimi.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "”{name}” on sisäänrakennettu kenttä, ei lisäkenttä — valitse toinen nimi." }, "plurals": null } diff --git a/l10n/fr.json b/l10n/fr.json index d5c6bc4cc..904846fe0 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -417,7 +417,15 @@ "Revoking…": "Révocation…", "Expired {when}": "Expiré {when}", "Expires {when}": "Expire {when}", - "No expiry": "Pas d’expiration" + "No expiry": "Pas d’expiration", + "No additional fields yet.": "Aucun champ supplémentaire pour le moment.", + "Field name": "Nom du champ", + "Value": "Valeur", + "Add a field": "Ajouter un champ", + "Remove": "Supprimer", + "Remove this field": "Supprimer ce champ", + "Give the field a name.": "Donnez un nom au champ.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "« {name} » est un champ intégré, pas un champ supplémentaire — choisissez un autre nom." }, "plurals": null } diff --git a/l10n/ga.json b/l10n/ga.json index 45d22ab6f..1e794ad7b 100644 --- a/l10n/ga.json +++ b/l10n/ga.json @@ -417,7 +417,15 @@ "Revoking…": "Á chúlghairm…", "Expired {when}": "As feidhm {when}", "Expires {when}": "Téann as feidhm {when}", - "No expiry": "Gan dul as feidhm" + "No expiry": "Gan dul as feidhm", + "No additional fields yet.": "Níl aon réimse breise ann fós.", + "Field name": "Ainm an réimse", + "Value": "Luach", + "Add a field": "Cuir réimse leis", + "Remove": "Bain", + "Remove this field": "Bain an réimse seo", + "Give the field a name.": "Tabhair ainm ar an réimse.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "Is réimse ionsuite é “{name}”, nach ceann breise — roghnaigh ainm eile." }, "plurals": null } diff --git a/l10n/hr.json b/l10n/hr.json index c66eef03a..213004820 100644 --- a/l10n/hr.json +++ b/l10n/hr.json @@ -417,7 +417,15 @@ "Revoking…": "Povlačenje…", "Expired {when}": "Isteklo {when}", "Expires {when}": "Istječe {when}", - "No expiry": "Bez isteka" + "No expiry": "Bez isteka", + "No additional fields yet.": "Još nema dodatnih polja.", + "Field name": "Naziv polja", + "Value": "Vrijednost", + "Add a field": "Dodaj polje", + "Remove": "Ukloni", + "Remove this field": "Ukloni ovo polje", + "Give the field a name.": "Dajte polju naziv.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ je ugrađeno polje, a ne dodatno — odaberite drugi naziv." }, "plurals": null } diff --git a/l10n/hu.json b/l10n/hu.json index 8d2cec3ab..09f8ce0f7 100644 --- a/l10n/hu.json +++ b/l10n/hu.json @@ -417,7 +417,15 @@ "Revoking…": "Visszavonás…", "Expired {when}": "Lejárt {when}", "Expires {when}": "Lejár {when}", - "No expiry": "Nincs lejárat" + "No expiry": "Nincs lejárat", + "No additional fields yet.": "Még nincsenek további mezők.", + "Field name": "Mező neve", + "Value": "Érték", + "Add a field": "Mező hozzáadása", + "Remove": "Eltávolítás", + "Remove this field": "Mező eltávolítása", + "Give the field a name.": "Adjon nevet a mezőnek.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "A(z) „{name}” beépített mező, nem további — válasszon másik nevet." }, "plurals": null } diff --git a/l10n/is.json b/l10n/is.json index 03b237afa..804555f9d 100644 --- a/l10n/is.json +++ b/l10n/is.json @@ -95,7 +95,7 @@ "Note": "Athugasemd", "Login": "Innskráning", "Copy login": "Afrita innskráningu", - "Additional fields": "Viðbótarsvæði", + "Additional fields": "Viðbótarsvið", "Back to vault": "Til baka í hvelfingu", "Delete secret": "Eyða leyndarmáli", "Cannot open secret": "Get ekki opnað leyndarmál", @@ -417,7 +417,15 @@ "Revoking…": "Afturkalla…", "Expired {when}": "Útrunnið {when}", "Expires {when}": "Rennur út {when}", - "No expiry": "Engin lokadagsetning" + "No expiry": "Engin lokadagsetning", + "No additional fields yet.": "Engin viðbótarsvið enn.", + "Field name": "Nafn sviðs", + "Value": "Gildi", + "Add a field": "Bæta við sviði", + "Remove": "Fjarlægja", + "Remove this field": "Fjarlægja þetta svið", + "Give the field a name.": "Gefðu sviðinu nafn.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ er innbyggt svið, ekki viðbótarsvið — veldu annað nafn." }, "plurals": null } diff --git a/l10n/it.json b/l10n/it.json index d77b5c7c5..76106e625 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -417,7 +417,15 @@ "Revoking…": "Revoca in corso…", "Expired {when}": "Scaduta {when}", "Expires {when}": "Scade {when}", - "No expiry": "Nessuna scadenza" + "No expiry": "Nessuna scadenza", + "No additional fields yet.": "Nessun campo aggiuntivo per ora.", + "Field name": "Nome del campo", + "Value": "Valore", + "Add a field": "Aggiungi un campo", + "Remove": "Rimuovi", + "Remove this field": "Rimuovi questo campo", + "Give the field a name.": "Dai un nome al campo.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» è un campo integrato, non aggiuntivo: scegli un altro nome." }, "plurals": null } diff --git a/l10n/lb.json b/l10n/lb.json index 33bf0a0d7..c819b979a 100644 --- a/l10n/lb.json +++ b/l10n/lb.json @@ -95,7 +95,7 @@ "Note": "Notiz", "Login": "Anmeldung", "Copy login": "Anmeldedaten kopieren", - "Additional fields": "Zusätzliche Felder", + "Additional fields": "Zousätzlech Felder", "Back to vault": "Zurück zum Tresor", "Delete secret": "Geheimnis löschen", "Cannot open secret": "Geheimnis kann nicht geöffnet werden", @@ -417,7 +417,15 @@ "Revoking…": "Gëtt zréckgesat…", "Expired {when}": "Verfall {when}", "Expires {when}": "Verfällt {when}", - "No expiry": "Kee Verfall" + "No expiry": "Kee Verfall", + "No additional fields yet.": "Nach keng zousätzlech Felder.", + "Field name": "Feldnumm", + "Value": "Wäert", + "Add a field": "E Feld dobäisetzen", + "Remove": "Ewechhuelen", + "Remove this field": "Dëst Feld ewechhuelen", + "Give the field a name.": "Gitt dem Feld en Numm.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ ass en integréiert Feld, kee zousätzlecht — wielt en anere Numm." }, "plurals": null } diff --git a/l10n/lt.json b/l10n/lt.json index ce2b2df10..8ca9dda62 100644 --- a/l10n/lt.json +++ b/l10n/lt.json @@ -417,7 +417,15 @@ "Revoking…": "Atšaukiama…", "Expired {when}": "Baigėsi {when}", "Expires {when}": "Baigiasi {when}", - "No expiry": "Be galiojimo pabaigos" + "No expiry": "Be galiojimo pabaigos", + "No additional fields yet.": "Papildomų laukų dar nėra.", + "Field name": "Lauko pavadinimas", + "Value": "Reikšmė", + "Add a field": "Pridėti lauką", + "Remove": "Pašalinti", + "Remove this field": "Pašalinti šį lauką", + "Give the field a name.": "Suteikite laukui pavadinimą.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ yra integruotas laukas, o ne papildomas — pasirinkite kitą pavadinimą." }, "plurals": null } diff --git a/l10n/lv.json b/l10n/lv.json index db7bd951e..ca1b86dc9 100644 --- a/l10n/lv.json +++ b/l10n/lv.json @@ -417,7 +417,15 @@ "Revoking…": "Atsauc…", "Expired {when}": "Beidzies {when}", "Expires {when}": "Beidzas {when}", - "No expiry": "Bez termiņa" + "No expiry": "Bez termiņa", + "No additional fields yet.": "Vēl nav papildu lauku.", + "Field name": "Lauka nosaukums", + "Value": "Vērtība", + "Add a field": "Pievienot lauku", + "Remove": "Noņemt", + "Remove this field": "Noņemt šo lauku", + "Give the field a name.": "Piešķir laukam nosaukumu.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” ir iebūvēts lauks, nevis papildu — izvēlies citu nosaukumu." }, "plurals": null } diff --git a/l10n/mk.json b/l10n/mk.json index f2bfb3acd..9febef73b 100644 --- a/l10n/mk.json +++ b/l10n/mk.json @@ -95,7 +95,7 @@ "Note": "Bilješka", "Login": "Prijava", "Copy login": "Kopiraj prijavu", - "Additional fields": "Dodatna polja", + "Additional fields": "Дополнителни полиња", "Back to vault": "Natrag na trezor", "Delete secret": "Izbriši tajnu", "Cannot open secret": "Nije moguće otvoriti tajnu", @@ -417,7 +417,15 @@ "Revoking…": "Отповикување…", "Expired {when}": "Истече {when}", "Expires {when}": "Истекува {when}", - "No expiry": "Без истек" + "No expiry": "Без истек", + "No additional fields yet.": "Сè уште нема дополнителни полиња.", + "Field name": "Име на полето", + "Value": "Вредност", + "Add a field": "Додај поле", + "Remove": "Отстрани", + "Remove this field": "Отстрани го ова поле", + "Give the field a name.": "Дајте му име на полето.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ е вградено поле, а не дополнително — одберете друго име." }, "plurals": null } diff --git a/l10n/mt.json b/l10n/mt.json index e1147af85..70b509728 100644 --- a/l10n/mt.json +++ b/l10n/mt.json @@ -417,7 +417,15 @@ "Revoking…": "Qed jiġi rrevokat…", "Expired {when}": "Skadiet {when}", "Expires {when}": "Tiskadi {when}", - "No expiry": "Bla skadenza" + "No expiry": "Bla skadenza", + "No additional fields yet.": "Għad ma hemmx oqsma addizzjonali.", + "Field name": "Isem il-qasam", + "Value": "Valur", + "Add a field": "Żid qasam", + "Remove": "Neħħi", + "Remove this field": "Neħħi dan il-qasam", + "Give the field a name.": "Agħti isem lill-qasam.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” huwa qasam integrat, mhux addizzjonali — agħżel isem oħra." }, "plurals": null } diff --git a/l10n/nb.json b/l10n/nb.json index 2657f785e..07c4e43ad 100644 --- a/l10n/nb.json +++ b/l10n/nb.json @@ -95,7 +95,7 @@ "Note": "Notat", "Login": "Innlogging", "Copy login": "Kopier innlogging", - "Additional fields": "Tilleggsfelt", + "Additional fields": "Tilleggsfelter", "Back to vault": "Tilbake til hvelvingen", "Delete secret": "Slett hemmelighet", "Cannot open secret": "Kan ikke åpne hemmelighet", @@ -417,7 +417,15 @@ "Revoking…": "Trekker tilbake…", "Expired {when}": "Utløpt {when}", "Expires {when}": "Utløper {when}", - "No expiry": "Ingen utløpsdato" + "No expiry": "Ingen utløpsdato", + "No additional fields yet.": "Ingen tilleggsfelter ennå.", + "Field name": "Feltnavn", + "Value": "Verdi", + "Add a field": "Legg til et felt", + "Remove": "Fjern", + "Remove this field": "Fjern dette feltet", + "Give the field a name.": "Gi feltet et navn.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» er et innebygd felt, ikke et tilleggsfelt — velg et annet navn." }, "plurals": null } diff --git a/l10n/nl.json b/l10n/nl.json index 3faab2589..6e35d96c2 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -182,7 +182,7 @@ "Note": "Notitie", "Login": "Inlog", "Copy login": "Inlog kopiëren", - "Additional fields": "Aanvullende velden", + "Additional fields": "Extra velden", "Back to vault": "Terug naar kluis", "Delete secret": "Geheim verwijderen", "Cannot open secret": "Kan geheim niet openen", @@ -576,7 +576,15 @@ "Revoking…": "Intrekken…", "Expired {when}": "Verlopen {when}", "Expires {when}": "Verloopt {when}", - "No expiry": "Geen vervaldatum" + "No expiry": "Geen vervaldatum", + "No additional fields yet.": "Nog geen extra velden.", + "Field name": "Veldnaam", + "Value": "Waarde", + "Add a field": "Voeg een veld toe", + "Remove": "Verwijderen", + "Remove this field": "Dit veld verwijderen", + "Give the field a name.": "Geef het veld een naam.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” is een ingebouwd veld, geen extra veld — kies een andere naam." }, "plurals": null } diff --git a/l10n/pl.json b/l10n/pl.json index c99c99a30..649f6e3c4 100644 --- a/l10n/pl.json +++ b/l10n/pl.json @@ -95,7 +95,7 @@ "Note": "Notatka", "Login": "Login", "Copy login": "Kopiuj login", - "Additional fields": "Dodatkowe pola", + "Additional fields": "Pola dodatkowe", "Back to vault": "Powrót do skarbca", "Delete secret": "Usuń sekret", "Cannot open secret": "Nie można otworzyć sekretu", @@ -417,7 +417,15 @@ "Revoking…": "Odwoływanie…", "Expired {when}": "Wygasło {when}", "Expires {when}": "Wygasa {when}", - "No expiry": "Bez wygaśnięcia" + "No expiry": "Bez wygaśnięcia", + "No additional fields yet.": "Brak pól dodatkowych.", + "Field name": "Nazwa pola", + "Value": "Wartość", + "Add a field": "Dodaj pole", + "Remove": "Usuń", + "Remove this field": "Usuń to pole", + "Give the field a name.": "Nadaj polu nazwę.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}” to pole wbudowane, a nie dodatkowe — wybierz inną nazwę." }, "plurals": null } diff --git a/l10n/pt.json b/l10n/pt.json index 2cd4bc8dc..2a31494a0 100644 --- a/l10n/pt.json +++ b/l10n/pt.json @@ -417,7 +417,15 @@ "Revoking…": "A revogar…", "Expired {when}": "Expirado {when}", "Expires {when}": "Expira {when}", - "No expiry": "Sem expiração" + "No expiry": "Sem expiração", + "No additional fields yet.": "Ainda não há campos adicionais.", + "Field name": "Nome do campo", + "Value": "Valor", + "Add a field": "Adicionar um campo", + "Remove": "Remover", + "Remove this field": "Remover este campo", + "Give the field a name.": "Dê um nome ao campo.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” é um campo integrado, não adicional — escolha outro nome." }, "plurals": null } diff --git a/l10n/rm.json b/l10n/rm.json index 732f3d70d..abbf536dd 100644 --- a/l10n/rm.json +++ b/l10n/rm.json @@ -95,7 +95,7 @@ "Note": "Nota", "Login": "Accesso", "Copy login": "Copia accesso", - "Additional fields": "Campi aggiuntivi", + "Additional fields": "Champs supplementars", "Back to vault": "Torna alla cassaforte", "Delete secret": "Elimina segreto", "Cannot open secret": "Impossibile aprire il segreto", @@ -417,7 +417,15 @@ "Revoking…": "Vegn revocà…", "Expired {when}": "Scadì {when}", "Expires {when}": "Scada {when}", - "No expiry": "Nagina scadenza" + "No expiry": "Nagina scadenza", + "No additional fields yet.": "Anc nagins champs supplementars.", + "Field name": "Num dal champ", + "Value": "Valur", + "Add a field": "Agiuntar in champ", + "Remove": "Allontanar", + "Remove this field": "Allontanar quest champ", + "Give the field a name.": "Dai in num al champ.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» è in champ integrà, betg in supplementar — tscherna in auter num." }, "plurals": null } diff --git a/l10n/ro.json b/l10n/ro.json index eb06d19cc..abfb0b4d2 100644 --- a/l10n/ro.json +++ b/l10n/ro.json @@ -384,7 +384,7 @@ "Record": "Înregistrare", "Showing {shown} of {total}.": "Se afișează {shown} din {total}.", "Version history entry": "Intrare din istoricul versiunilor", - "Add": "Adaugă", + "Add": "Adăugați", "Also ask for another field": "Solicitați și un alt câmp", "e.g. client-id": "de ex. client-id", "That field is already listed.": "Acest câmp este deja în listă.", @@ -417,7 +417,15 @@ "Revoking…": "Se revocă…", "Expired {when}": "Expirat {when}", "Expires {when}": "Expiră {when}", - "No expiry": "Fără expirare" + "No expiry": "Fără expirare", + "No additional fields yet.": "Încă nu există câmpuri suplimentare.", + "Field name": "Numele câmpului", + "Value": "Valoare", + "Add a field": "Adăugați un câmp", + "Remove": "Eliminați", + "Remove this field": "Eliminați acest câmp", + "Give the field a name.": "Dați un nume câmpului.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}” este un câmp integrat, nu suplimentar — alegeți alt nume." }, "plurals": null } diff --git a/l10n/ru.json b/l10n/ru.json index adec9add7..ee60b8bc0 100644 --- a/l10n/ru.json +++ b/l10n/ru.json @@ -417,7 +417,15 @@ "Revoking…": "Отзыв…", "Expired {when}": "Истёк {when}", "Expires {when}": "Истекает {when}", - "No expiry": "Без срока действия" + "No expiry": "Без срока действия", + "No additional fields yet.": "Дополнительных полей пока нет.", + "Field name": "Имя поля", + "Value": "Значение", + "Add a field": "Добавить поле", + "Remove": "Удалить", + "Remove this field": "Удалить это поле", + "Give the field a name.": "Дайте полю имя.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» — встроенное поле, а не дополнительное: выберите другое имя." }, "plurals": null } diff --git a/l10n/sk.json b/l10n/sk.json index b26ce8f54..23a3e95ca 100644 --- a/l10n/sk.json +++ b/l10n/sk.json @@ -95,7 +95,7 @@ "Note": "Bilješka", "Login": "Prijava", "Copy login": "Kopiraj prijavu", - "Additional fields": "Dodatna polja", + "Additional fields": "Ďalšie polia", "Back to vault": "Natrag na trezor", "Delete secret": "Izbriši tajnu", "Cannot open secret": "Nije moguće otvoriti tajnu", @@ -417,7 +417,15 @@ "Revoking…": "Ruší sa…", "Expired {when}": "Vypršalo {when}", "Expires {when}": "Vyprší {when}", - "No expiry": "Bez vypršania" + "No expiry": "Bez vypršania", + "No additional fields yet.": "Zatiaľ žiadne ďalšie polia.", + "Field name": "Názov poľa", + "Value": "Hodnota", + "Add a field": "Pridať pole", + "Remove": "Odobrať", + "Remove this field": "Odobrať toto pole", + "Give the field a name.": "Pomenujte pole.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ je vstavané pole, nie ďalšie — zvoľte iný názov." }, "plurals": null } diff --git a/l10n/sl.json b/l10n/sl.json index 962c0b7d1..6d3a52996 100644 --- a/l10n/sl.json +++ b/l10n/sl.json @@ -417,7 +417,15 @@ "Revoking…": "Preklic…", "Expired {when}": "Poteklo {when}", "Expires {when}": "Poteče {when}", - "No expiry": "Brez poteka" + "No expiry": "Brez poteka", + "No additional fields yet.": "Dodatnih polj še ni.", + "Field name": "Ime polja", + "Value": "Vrednost", + "Add a field": "Dodaj polje", + "Remove": "Odstrani", + "Remove this field": "Odstrani to polje", + "Give the field a name.": "Poimenujte polje.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ je vgrajeno polje, ne dodatno — izberite drugo ime." }, "plurals": null } diff --git a/l10n/sq.json b/l10n/sq.json index a78c9d12e..ad3e33df6 100644 --- a/l10n/sq.json +++ b/l10n/sq.json @@ -95,7 +95,7 @@ "Note": "Shënim", "Login": "Hyrja", "Copy login": "Kopjo hyrjen", - "Additional fields": "Fushat shtesë", + "Additional fields": "Fusha shtesë", "Back to vault": "Kthehu te kasaforta", "Delete secret": "Fshi sekretin", "Cannot open secret": "Nuk mund të hapet sekreti", @@ -417,7 +417,15 @@ "Revoking…": "Po revokohet…", "Expired {when}": "Skaduar {when}", "Expires {when}": "Skadon {when}", - "No expiry": "Pa skadencë" + "No expiry": "Pa skadencë", + "No additional fields yet.": "Nuk ka ende fusha shtesë.", + "Field name": "Emri i fushës", + "Value": "Vlera", + "Add a field": "Shto një fushë", + "Remove": "Hiq", + "Remove this field": "Hiq këtë fushë", + "Give the field a name.": "Jepni emër fushës.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” është fushë e integruar, jo shtesë — zgjidhni emër tjetër." }, "plurals": null } diff --git a/l10n/sr.json b/l10n/sr.json index 75bae3458..d873ae8ca 100644 --- a/l10n/sr.json +++ b/l10n/sr.json @@ -95,7 +95,7 @@ "Note": "Bilješka", "Login": "Prijava", "Copy login": "Kopiraj prijavu", - "Additional fields": "Dodatna polja", + "Additional fields": "Додатна поља", "Back to vault": "Natrag na trezor", "Delete secret": "Izbriši tajnu", "Cannot open secret": "Nije moguće otvoriti tajnu", @@ -417,7 +417,15 @@ "Revoking…": "Повлачење…", "Expired {when}": "Истекло {when}", "Expires {when}": "Истиче {when}", - "No expiry": "Без истека" + "No expiry": "Без истека", + "No additional fields yet.": "Још нема додатних поља.", + "Field name": "Назив поља", + "Value": "Вредност", + "Add a field": "Додај поље", + "Remove": "Уклони", + "Remove this field": "Уклони ово поље", + "Give the field a name.": "Дајте полју назив.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "„{name}“ је уграђено поље, а не додатно — одаберите други назив." }, "plurals": null } diff --git a/l10n/sv.json b/l10n/sv.json index 5eeda7a2a..7bb33b103 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -95,7 +95,7 @@ "Note": "Note", "Login": "Log ind", "Copy login": "Kopiér login", - "Additional fields": "Yderligere felter", + "Additional fields": "Ytterligare fält", "Back to vault": "Tilbage til boksen", "Delete secret": "Slet hemmelighed", "Cannot open secret": "Kan ikke åbne hemmelighed", @@ -417,7 +417,15 @@ "Revoking…": "Återkallar…", "Expired {when}": "Utgången {when}", "Expires {when}": "Går ut {when}", - "No expiry": "Inget slutdatum" + "No expiry": "Inget slutdatum", + "No additional fields yet.": "Inga ytterligare fält ännu.", + "Field name": "Fältnamn", + "Value": "Värde", + "Add a field": "Lägg till ett fält", + "Remove": "Ta bort", + "Remove this field": "Ta bort det här fältet", + "Give the field a name.": "Ge fältet ett namn.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "”{name}” är ett inbyggt fält, inte ett ytterligare — välj ett annat namn." }, "plurals": null } diff --git a/l10n/tr.json b/l10n/tr.json index 2710b9c15..b12621632 100644 --- a/l10n/tr.json +++ b/l10n/tr.json @@ -417,7 +417,15 @@ "Revoking…": "İptal ediliyor…", "Expired {when}": "Süresi doldu {when}", "Expires {when}": "Süresi doluyor {when}", - "No expiry": "Süre sınırı yok" + "No expiry": "Süre sınırı yok", + "No additional fields yet.": "Henüz ek alan yok.", + "Field name": "Alan adı", + "Value": "Değer", + "Add a field": "Alan ekle", + "Remove": "Kaldır", + "Remove this field": "Bu alanı kaldır", + "Give the field a name.": "Alana bir ad verin.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "“{name}” yerleşik bir alandır, ek alan değil — başka bir ad seçin." }, "plurals": null } diff --git a/l10n/uk.json b/l10n/uk.json index 7e8d569fc..196af0e5a 100644 --- a/l10n/uk.json +++ b/l10n/uk.json @@ -95,7 +95,7 @@ "Note": "Заметка", "Login": "Логин", "Copy login": "Скопировать логин", - "Additional fields": "Дополнительные поля", + "Additional fields": "Додаткові поля", "Back to vault": "Вернуться в хранилище", "Delete secret": "Удалить секрет", "Cannot open secret": "Невозможно открыть секрет", @@ -417,7 +417,15 @@ "Revoking…": "Відкликання…", "Expired {when}": "Закінчився {when}", "Expires {when}": "Закінчується {when}", - "No expiry": "Без терміну дії" + "No expiry": "Без терміну дії", + "No additional fields yet.": "Додаткових полів ще немає.", + "Field name": "Назва поля", + "Value": "Значення", + "Add a field": "Додати поле", + "Remove": "Видалити", + "Remove this field": "Видалити це поле", + "Give the field a name.": "Дайте полю назву.", + "“{name}” is a built-in field, not an additional one — choose a different name.": "«{name}» — вбудоване поле, а не додаткове: виберіть іншу назву." }, "plurals": null } diff --git a/openspec/changes/owner-editable-additional-fields/.openspec.yaml b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/.openspec.yaml similarity index 100% rename from openspec/changes/owner-editable-additional-fields/.openspec.yaml rename to openspec/changes/archive/2026-08-19-owner-editable-additional-fields/.openspec.yaml diff --git a/openspec/changes/owner-editable-additional-fields/design.md b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/design.md similarity index 100% rename from openspec/changes/owner-editable-additional-fields/design.md rename to openspec/changes/archive/2026-08-19-owner-editable-additional-fields/design.md diff --git a/openspec/changes/owner-editable-additional-fields/plan.json b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/plan.json similarity index 86% rename from openspec/changes/owner-editable-additional-fields/plan.json rename to openspec/changes/archive/2026-08-19-owner-editable-additional-fields/plan.json index 865655f7f..87afd8587 100644 --- a/openspec/changes/owner-editable-additional-fields/plan.json +++ b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/plan.json @@ -2,7 +2,7 @@ "change": "owner-editable-additional-fields", "project": "doriath", "repo": "ConductionNL/doriath", - "base_branch": "development", + "base_branch": "feature/271/admin-application-request-visibility", "feature_branch": "feature/272/owner-editable-additional-fields", "created": "2026-08-18", "tracking_issue": 272, @@ -15,7 +15,7 @@ "number": "1.1", "section": "Shared member editor", "title": "Extract the named-member editor used by `SecretRequestCreateDialog.addCustomField()` into one reusable piece (component or composable) carrying the name/value list plus its validation.", - "status": "pending", + "status": "done", "acceptance_criteria": [ "Refuses reserved names (`key`, `login`, `url`) with a reason, duplicates, and blanks", "Extract the VALIDATION at minimum; the request dialog's \"name a member that does not exist yet\" affordance is request-only and must not be forced onto the secret dialogs" @@ -26,7 +26,7 @@ "number": "1.2", "section": "Shared member editor", "title": "Point `SecretRequestCreateDialog` at the extracted piece so there is one implementation, not two.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { @@ -34,7 +34,7 @@ "number": "2.1", "section": "Create", "title": "Add additional fields to `SecretCreateDialog`: add and remove named members before the secret exists.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { @@ -42,7 +42,7 @@ "number": "2.2", "section": "Create", "title": "Include the members in the payload so the store encrypts them as one blob.", - "status": "pending", + "status": "done", "acceptance_criteria": [ "The server must still receive only ciphertext; no member name or value in plaintext" ] @@ -52,7 +52,7 @@ "number": "3.1", "section": "Edit", "title": "Pre-fill `SecretEditDialog` from the decrypted blob so existing members are shown with their names and values.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { @@ -60,7 +60,7 @@ "number": "3.2", "section": "Edit", "title": "Allow renaming a member, changing its value, adding another and removing one, writing the whole blob back.", - "status": "pending", + "status": "done", "acceptance_criteria": [ "Removing the last member yields an EMPTY blob, not null, so \"none\" stays distinguishable from \"not loaded\"" ] @@ -70,7 +70,7 @@ "number": "4.1", "section": "Tests", "title": "Vitest for the shared editor: reserved names, duplicates and blanks refused with a reason; add and remove work.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { @@ -78,7 +78,7 @@ "number": "4.2", "section": "Tests", "title": "Vitest for create: members reach the payload, and the payload carries no plaintext member the server should not see.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { @@ -86,7 +86,7 @@ "number": "4.3", "section": "Tests", "title": "Vitest for edit: existing members are pre-filled from the decrypted secret; rename, re-value, add and remove all round-trip; removing the last member sends an empty blob.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { @@ -94,7 +94,7 @@ "number": "4.4", "section": "Tests", "title": "Verify each new/modified spec scenario is driven by a test or carries a reason-bearing `@e2e exclude`.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { @@ -102,15 +102,15 @@ "number": "5.1", "section": "Quality", "title": "Translate new UI strings into all 36 locales so the parity ratchet stays green.", - "status": "pending", + "status": "done", "acceptance_criteria": [] }, { "id": 12, "number": "5.2", "section": "Quality", - "title": "Run the full sweep — hydra gates, vitest, eslint, prettier, `openspec validate --strict` — and confirm a `@spec` anchor or reason-bearing exclude on every changed method.", - "status": "pending", + "title": "Run the full sweep \u2014 hydra gates, vitest, eslint, prettier, `openspec validate --strict` \u2014 and confirm a `@spec` anchor or reason-bearing exclude on every changed method.", + "status": "done", "acceptance_criteria": [] } ] diff --git a/openspec/changes/owner-editable-additional-fields/proposal.md b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/proposal.md similarity index 100% rename from openspec/changes/owner-editable-additional-fields/proposal.md rename to openspec/changes/archive/2026-08-19-owner-editable-additional-fields/proposal.md diff --git a/openspec/changes/owner-editable-additional-fields/specs/secrets-write-ui/spec.md b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/specs/secrets-write-ui/spec.md similarity index 65% rename from openspec/changes/owner-editable-additional-fields/specs/secrets-write-ui/spec.md rename to openspec/changes/archive/2026-08-19-owner-editable-additional-fields/specs/secrets-write-ui/spec.md index df8b92a15..1c82928a2 100644 --- a/openspec/changes/owner-editable-additional-fields/specs/secrets-write-ui/spec.md +++ b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/specs/secrets-write-ui/spec.md @@ -28,20 +28,24 @@ The dialog MUST be blocked (disabled) while the vault is locked. - **THEN** the new secret MUST appear in the vault list and its value MUST round-trip: opening it and decrypting MUST return the exact value entered #### Scenario: Create a secret with additional fields +@e2e exclude Proven without a browser, and more strongly than a browser could: SecretCreateDialog.additionalFields "sends the members the user added, as one object" pins that the members reach the store, secret-additional-fields "encrypts the members as ONE blob on create" pins the single-blob shape, and additional-fields-crypto "round-trips every member name and value exactly as entered" does it with REAL RSA-4096 keys, including unicode and punctuation a serializer could mangle. - **WHEN** the user adds one or more named additional fields and submits - **THEN** they MUST be stored inside the encrypted `additional_fields` blob - **AND** opening the secret and decrypting MUST return every member name and value exactly as entered #### Scenario: A reserved name is refused as an additional field +@e2e exclude Dialog-level validation with one shared implementation: additional-fields "refuses every reserved name, with a reason" and "refuses a reserved name whatever its casing" cover the rule, AdditionalFieldsEditor "refuses a reserved name WITH a reason, and emits nothing" and "flags a rename onto a reserved or duplicate name" cover both entry points. Removing the check fails exactly those four and nothing else — verified by injecting it. - **WHEN** the user tries to add an additional field named `key`, `login` or `url` - **THEN** the dialog MUST refuse it and say why - **AND** MUST NOT write that value into the Secret's corresponding column #### Scenario: Create a secret inside the current folder +@e2e exclude Had NO coverage at all until this change (it was carried off PR #270 as a known gap rather than waived). Now driven by SecretCreateDialog.additionalFields "defaults the folder to the one being viewed, and persists it", which asserts both the default and the folderId in the payload. - **WHEN** the user is viewing a folder and creates a secret - **THEN** the dialog's folder field MUST default to that folder and the created secret MUST persist that `folderId` #### Scenario: Name and value are required +@e2e exclude Also previously uncovered, and also from the #270 gap. Driven by SecretCreateDialog.additionalFields "requires a name AND a value before anything is sent", which walks every partial state including whitespace-only and asserts no request is made. - **WHEN** the user submits with an empty name or empty value - **THEN** the submit control MUST be disabled and no request MUST be sent @@ -72,12 +76,14 @@ your own secret meant asking a stranger to submit it. - **THEN** re-opening the secret and decrypting MUST return the updated value #### Scenario: Edit an existing additional field +@e2e exclude Driven by SecretEditDialog.additionalFields "pre-fills the existing members from the decrypted blob" and "sends the whole blob when one member is renamed" / "when a value changes or a member is added"; the decrypt round-trip itself is proven with real keys in additional-fields-crypto. Removing the diff block fails exactly the three write-path tests. - **GIVEN** a secret whose encrypted blob holds a member - **WHEN** the user opens Edit - **THEN** that member MUST be shown with its decrypted name and value - **AND** changing either and saving MUST round-trip through decryption #### Scenario: Remove the last additional field +@e2e exclude Driven at three layers, because the distinction is a JavaScript trap rather than a UI one. The write half: SecretEditDialog.additionalFields "sends an EMPTY object when the last member is removed", plus secret-additional-fields "sends an EMPTY blob, not null" and "leaves the stored blob alone when the caller sends nothing" — that pair is what makes empty-versus-absent meaningful. The read half ("re-opening MUST show no additional fields"): SecretDetail.additionalFields "shows NO additional-fields section for an empty blob", added after /opsx-verify found the view rendered the heading over an empty list, since `{}` is truthy AND an object. That state was unreachable before owners could remove a member. - **WHEN** the user removes the only remaining member and saves - **THEN** the stored blob MUST be empty rather than null - **AND** re-opening the secret MUST show no additional fields and no error diff --git a/openspec/changes/owner-editable-additional-fields/tasks.md b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/tasks.md similarity index 74% rename from openspec/changes/owner-editable-additional-fields/tasks.md rename to openspec/changes/archive/2026-08-19-owner-editable-additional-fields/tasks.md index 06e45d51e..3491c9cc3 100644 --- a/openspec/changes/owner-editable-additional-fields/tasks.md +++ b/openspec/changes/archive/2026-08-19-owner-editable-additional-fields/tasks.md @@ -1,33 +1,33 @@ ## 1. Shared member editor -- [ ] 1.1 Extract the named-member editor used by `SecretRequestCreateDialog.addCustomField()` into one reusable piece (component or composable) carrying the name/value list plus its validation. +- [x] 1.1 Extract the named-member editor used by `SecretRequestCreateDialog.addCustomField()` into one reusable piece (component or composable) carrying the name/value list plus its validation. - Refuses reserved names (`key`, `login`, `url`) with a reason, duplicates, and blanks - Extract the VALIDATION at minimum; the request dialog's "name a member that does not exist yet" affordance is request-only and must not be forced onto the secret dialogs -- [ ] 1.2 Point `SecretRequestCreateDialog` at the extracted piece so there is one implementation, not two. +- [x] 1.2 Point `SecretRequestCreateDialog` at the extracted piece so there is one implementation, not two. ## 2. Create -- [ ] 2.1 Add additional fields to `SecretCreateDialog`: add and remove named members before the secret exists. -- [ ] 2.2 Include the members in the payload so the store encrypts them as one blob. +- [x] 2.1 Add additional fields to `SecretCreateDialog`: add and remove named members before the secret exists. +- [x] 2.2 Include the members in the payload so the store encrypts them as one blob. - The server must still receive only ciphertext; no member name or value in plaintext ## 3. Edit -- [ ] 3.1 Pre-fill `SecretEditDialog` from the decrypted blob so existing members are shown with their names and values. -- [ ] 3.2 Allow renaming a member, changing its value, adding another and removing one, writing the whole blob back. +- [x] 3.1 Pre-fill `SecretEditDialog` from the decrypted blob so existing members are shown with their names and values. +- [x] 3.2 Allow renaming a member, changing its value, adding another and removing one, writing the whole blob back. - Removing the last member yields an EMPTY blob, not null, so "none" stays distinguishable from "not loaded" ## 4. Tests -- [ ] 4.1 Vitest for the shared editor: reserved names, duplicates and blanks refused with a reason; add and remove work. -- [ ] 4.2 Vitest for create: members reach the payload, and the payload carries no plaintext member the server should not see. -- [ ] 4.3 Vitest for edit: existing members are pre-filled from the decrypted secret; rename, re-value, add and remove all round-trip; removing the last member sends an empty blob. -- [ ] 4.4 Verify each new/modified spec scenario is driven by a test or carries a reason-bearing `@e2e exclude`. +- [x] 4.1 Vitest for the shared editor: reserved names, duplicates and blanks refused with a reason; add and remove work. +- [x] 4.2 Vitest for create: members reach the payload, and the payload carries no plaintext member the server should not see. +- [x] 4.3 Vitest for edit: existing members are pre-filled from the decrypted secret; rename, re-value, add and remove all round-trip; removing the last member sends an empty blob. +- [x] 4.4 Verify each new/modified spec scenario is driven by a test or carries a reason-bearing `@e2e exclude`. ## 5. Quality -- [ ] 5.1 Translate new UI strings into all 36 locales so the parity ratchet stays green. -- [ ] 5.2 Run the full sweep — hydra gates, vitest, eslint, prettier, `openspec validate --strict` — and confirm a `@spec` anchor or reason-bearing exclude on every changed method. +- [x] 5.1 Translate new UI strings into all 36 locales so the parity ratchet stays green. +- [x] 5.2 Run the full sweep — hydra gates, vitest, eslint, prettier, `openspec validate --strict` — and confirm a `@spec` anchor or reason-bearing exclude on every changed method. ## Acceptance criteria diff --git a/openspec/specs/secrets-write-ui/spec.md b/openspec/specs/secrets-write-ui/spec.md index 032016431..85179c377 100644 --- a/openspec/specs/secrets-write-ui/spec.md +++ b/openspec/specs/secrets-write-ui/spec.md @@ -1,5 +1,11 @@ # secrets-write-ui Specification +**Status**: in-progress + +**OpenSpec changes:** +- `implement-secrets-write-ui` (2026-06-11) — Original write UI: create/edit dialogs, folder create + move, public-link share, modal isolation, keyboard-operable rows +- `owner-editable-additional-fields` (2026-08-19) — An owner could SEE additional fields but never write one: the whole stack supported them (controller, service, store encrypt/decrypt, SecretDetail rendering) except the two dialogs an owner uses, so the only writers were the write-for-application dialog, import, and an external recipient filling a secret request. Both requirements below enumerated the collectable fields and omitted this one, which is how it shipped. Adds a shared member editor with the reserved-name rule (`key`, `login`, `url`) in one place, refused with a reason rather than silently misrouted, and an empty blob rather than null when the last member is removed + ## Purpose TBD - created by archiving change implement-secrets-write-ui. Update Purpose after archive. ## Requirements @@ -10,10 +16,18 @@ create a new secret. The dialog MUST collect a name (required), a secret type and login fields. The optional target folder MUST default to the folder the user is currently viewing. +The dialog MUST also let the user add ADDITIONAL FIELDS: named members carried +inside the Secret's single encrypted `additional_fields` blob. A member name that +collides with a reserved field name (`key`, `login`, `url`) MUST be refused with a +reason rather than accepted, because those names address the Secret's own columns +and a member bearing one would be misrouted or shadowed. Duplicate and blank +names MUST be refused. + On submit, the browser MUST encrypt the secret value (and the login, when present) with the owner's suite certificate via the existing `importPublicKey` + `rsaEncrypt` crypto BEFORE the request leaves the page, then POST the ciphertext -through `secret.createSecret`. The server MUST never receive the plaintext value. +through `secret.createSecret`. The additional fields MUST be encrypted as ONE blob +by the same path. The server MUST never receive the plaintext value. The dialog MUST be blocked (disabled) while the vault is locked. #### Scenario: Create a secret with required fields @@ -22,16 +36,29 @@ The dialog MUST be blocked (disabled) while the vault is locked. - **THEN** the system MUST POST the ciphertext and create the secret (HTTP 201) - **THEN** the new secret MUST appear in the vault list and its value MUST round-trip: opening it and decrypting MUST return the exact value entered +#### Scenario: Create a secret with additional fields +@e2e exclude Proven without a browser, and more strongly than a browser could: SecretCreateDialog.additionalFields "sends the members the user added, as one object" pins that the members reach the store, secret-additional-fields "encrypts the members as ONE blob on create" pins the single-blob shape, and additional-fields-crypto "round-trips every member name and value exactly as entered" does it with REAL RSA-4096 keys, including unicode and punctuation a serializer could mangle. +- **WHEN** the user adds one or more named additional fields and submits +- **THEN** they MUST be stored inside the encrypted `additional_fields` blob +- **AND** opening the secret and decrypting MUST return every member name and value exactly as entered + +#### Scenario: A reserved name is refused as an additional field +@e2e exclude Dialog-level validation with one shared implementation: additional-fields "refuses every reserved name, with a reason" and "refuses a reserved name whatever its casing" cover the rule, AdditionalFieldsEditor "refuses a reserved name WITH a reason, and emits nothing" and "flags a rename onto a reserved or duplicate name" cover both entry points. Removing the check fails exactly those four and nothing else — verified by injecting it. +- **WHEN** the user tries to add an additional field named `key`, `login` or `url` +- **THEN** the dialog MUST refuse it and say why +- **AND** MUST NOT write that value into the Secret's corresponding column + #### Scenario: Create a secret inside the current folder -@e2e exclude Had NO coverage of any kind until now — carried across PR #270 and #282 as a known gap rather than waived. Driven by SecretCreateDialog.requiredFields "defaults the folder to the one being viewed, and persists it", plus "sends a null folder when created at the vault root" for the other half. +@e2e exclude Had NO coverage of any kind until now — carried across PR #270, #282 and #286 as a known gap rather than waived with an exclude claiming coverage that did not exist. Two specs drive it, both real after this merge: SecretCreateDialog.requiredFields "defaults the folder to the one being viewed, and persists it" plus its null-folder-at-the-root counterpart, and SecretCreateDialog.additionalFields "defaults the folder to the one being viewed, and persists it". - **WHEN** the user is viewing a folder and creates a secret - **THEN** the dialog's folder field MUST default to that folder and the created secret MUST persist that `folderId` #### Scenario: Name and value are required -@e2e exclude Also previously uncovered, and from the same gap. Driven by SecretCreateDialog.requiredFields "requires a name AND a value before anything is sent", which walks every partial state including whitespace-only and asserts no request is made, plus "stays blocked while the vault is locked, however complete the form is" — the requirement enforces that at the dialog rather than by disabling each field. +@e2e exclude Also previously uncovered, from the same gap. Driven by SecretCreateDialog.requiredFields "requires a name AND a value before anything is sent" and "stays blocked while the vault is locked, however complete the form is", and by SecretCreateDialog.additionalFields "requires a name AND a value before anything is sent". Each walks every partial state including whitespace-only and asserts no request is made. - **WHEN** the user submits with an empty name or empty value - **THEN** the submit control MUST be disabled and no request MUST be sent + ### Requirement: Edit a Secret from the UI The system MUST provide an "Edit" affordance on the secret detail view that opens a dialog pre-filled with the secret's current (decrypted) fields. The user MUST be @@ -40,15 +67,42 @@ sensitive fields (value / login) MUST be re-encrypted client-side before the PUT via `secret.updateSecret`; metadata-only edits (name / type / URL) MUST NOT require re-encryption. +The dialog MUST also present the secret's existing ADDITIONAL FIELDS, pre-filled +from the decrypted blob, and MUST let the user rename a member, change its value, +add another and remove one. The same reserved-name, duplicate and blank rules +apply as on creation. Because the members share one blob, any member change MUST +re-encrypt and write the whole blob; removing the last member MUST produce an +empty blob rather than a null value, so that "no additional fields" stays +distinguishable from "not loaded". + +An owner MUST NOT have to ask another party to write an additional field for them. +Until this requirement, the only writers were the write-for-application dialog, +import, and an external recipient filling a secret request — so obtaining one on +your own secret meant asking a stranger to submit it. + #### Scenario: Edit a secret's value - **WHEN** the user edits a secret, changes the value, and saves - **THEN** the browser MUST RSA-encrypt the new value and PUT it - **THEN** re-opening the secret and decrypting MUST return the updated value + #### Scenario: Edit metadata only -@e2e exclude Previously uncovered. Driven by SecretEditDialog.metadataOnly: only the name is sent when only the name changed, nothing is sent when nothing changed, and the counterpart — the value IS re-encrypted when it actually changes, since the rule is "only CHANGED sensitive fields", not "never". +@e2e exclude Previously uncovered. Driven by SecretEditDialog.metadataOnly — only the name is sent when only the name changed, nothing is sent when nothing changed, and the counterpart that the value IS re-encrypted when it really changes, since the rule is "only CHANGED sensitive fields" rather than "never" — and by SecretEditDialog.additionalFields "alters no ciphertext when only the name changes". Same diff logic: a rename that re-encrypted the blob would also be a rename that loses a concurrent session's members. - **WHEN** the user changes only the name and saves - **THEN** the system MUST persist the new name and MUST NOT alter the stored ciphertext +#### Scenario: Edit an existing additional field +@e2e exclude Driven by SecretEditDialog.additionalFields "pre-fills the existing members from the decrypted blob" and "sends the whole blob when one member is renamed" / "when a value changes or a member is added"; the decrypt round-trip itself is proven with real keys in additional-fields-crypto. Removing the diff block fails exactly the three write-path tests. +- **GIVEN** a secret whose encrypted blob holds a member +- **WHEN** the user opens Edit +- **THEN** that member MUST be shown with its decrypted name and value +- **AND** changing either and saving MUST round-trip through decryption + +#### Scenario: Remove the last additional field +@e2e exclude Driven at three layers, because the distinction is a JavaScript trap rather than a UI one. The write half: SecretEditDialog.additionalFields "sends an EMPTY object when the last member is removed", plus secret-additional-fields "sends an EMPTY blob, not null" and "leaves the stored blob alone when the caller sends nothing" — that pair is what makes empty-versus-absent meaningful. The read half ("re-opening MUST show no additional fields"): SecretDetail.additionalFields "shows NO additional-fields section for an empty blob", added after /opsx-verify found the view rendered the heading over an empty list, since `{}` is truthy AND an object. That state was unreachable before owners could remove a member. +- **WHEN** the user removes the only remaining member and saves +- **THEN** the stored blob MUST be empty rather than null +- **AND** re-opening the secret MUST show no additional fields and no error + ### Requirement: Create a Folder and Move a Secret The system MUST provide a "New folder" affordance in the vault view that creates a diff --git a/src/components/AdditionalFieldsEditor.vue b/src/components/AdditionalFieldsEditor.vue new file mode 100644 index 000000000..c778ffd96 --- /dev/null +++ b/src/components/AdditionalFieldsEditor.vue @@ -0,0 +1,275 @@ + + + + + + diff --git a/src/dialogs/SecretCreateDialog.vue b/src/dialogs/SecretCreateDialog.vue index 716284bb1..02e35b9ae 100644 --- a/src/dialogs/SecretCreateDialog.vue +++ b/src/dialogs/SecretCreateDialog.vue @@ -28,8 +28,8 @@ @@ -107,6 +107,11 @@ + + } + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui */ async load() { this.loading = true @@ -312,6 +327,12 @@ export default { this.value = secret.key || '' this.url = secret.url || '' this.login = secret.login || '' + // From the DECRYPTED blob the store already parsed. Pre-filling from + // the current decrypted copy is also what bounds the known + // last-writer-wins window: the whole blob is rewritten on save, so an + // edit begun from a stale copy would drop members another session + // added meanwhile. + this.additionalFields = objectToMembers(secret.additionalFields) // Seed the per-type composite fields from the decrypted // payload (card-identity-items §3.1); a legacy plain value @@ -377,6 +398,8 @@ export default { * Compute the changed-fields diff and PUT it via the store. * * @return {Promise} + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui */ async submit() { if (!this.canSubmit) { @@ -420,6 +443,19 @@ export default { diff.login = this.login } + // One blob is the storage unit, so ANY member change rewrites all of + // it. Sent only when something actually changed, and sent as `{}` + // rather than null when the last member is removed: null would mean + // "not provided", which the store reads as "leave the stored blob + // alone" — the opposite of what removing the last field means. + const nextMembers = membersToObject(this.additionalFields) + const priorMembers = membersToObject( + objectToMembers(o.additionalFields), + ) + if (JSON.stringify(nextMembers) !== JSON.stringify(priorMembers)) { + diff.additionalFields = nextMembers + } + let updated = this.original if (Object.keys(diff).length > 0) { updated = await useSecretStore().updateSecret( diff --git a/src/dialogs/SecretRequestCreateDialog.vue b/src/dialogs/SecretRequestCreateDialog.vue index 1bcea8e74..59b209bde 100644 --- a/src/dialogs/SecretRequestCreateDialog.vue +++ b/src/dialogs/SecretRequestCreateDialog.vue @@ -135,6 +135,7 @@ import { NcDialog } from '@nextcloud/vue' import { useFolderStore } from '../store/modules/folder.js' import { useSecretRequestStore } from '../store/modules/secretRequest.js' +import { memberNameError } from '../utils/additionalFields.js' import { fillLinkFor } from '../utils/fillLink.js' /** @@ -459,16 +460,16 @@ export default { return } - if (['key', 'login', 'url'].includes(name) === true) { - this.customFieldError = t( - 'doriath', - 'That is a built-in field — tick it in the list above instead.', - ) - return - } - - if (this.availableFields.some((f) => f.key === name) === true) { - this.customFieldError = t('doriath', 'That field is already listed.') + // The reserved-name and duplicate rules are SHARED with the secret + // create/edit dialogs (src/utils/additionalFields.js). They were written + // out here first; a second copy in those dialogs is how `url` ends up + // accepted in one place and refused in another. + const error = memberNameError( + name, + this.availableFields.map((f) => f.key), + ) + if (error !== '') { + this.customFieldError = error return } diff --git a/src/utils/additionalFields.js b/src/utils/additionalFields.js new file mode 100644 index 000000000..4ea6cf74d --- /dev/null +++ b/src/utils/additionalFields.js @@ -0,0 +1,122 @@ +/** + * SPDX-License-Identifier: EUPL-1.2 + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * + * The naming rules for a Secret's additional fields, in one place. + * + * `additional_fields` is a single encrypted blob holding named members (ADR-003). + * Three names are NOT members: `key`, `login` and `url` address the Secret's own + * columns. A member bearing one of those names is not a second field with the same + * label — it is a value that will be misrouted or shadowed by the column, so the + * user would store something other than what they typed. + * + * Extracted because the rule now has three call sites: the secret-create dialog, + * the secret-edit dialog, and the request dialog that asks someone ELSE to fill a + * member in. Three copies of a validation rule is how one of them ends up + * accepting `url`. + * + * Only the VALIDATION is shared. The request dialog additionally lets a requester + * name a member that does not exist yet, which is meaningless on a secret you are + * editing yourself, so that affordance stays where it belongs. + */ + +import { t } from '@nextcloud/l10n' + +/** + * Names that address the Secret's own columns and can never be members. + * + * @type {Array} + */ +export const RESERVED_MEMBER_NAMES = ['key', 'login', 'url'] + +/** + * Why a member name cannot be used, or an empty string when it can. + * + * Returns a REASON rather than a boolean: "refused with a reason" is what the spec + * requires, and a silent rejection on a name the user just typed reads as the + * dialog being broken. + * + * @param {string} name The name the user typed. + * @param {Array} taken Names already present, whatever their source. + * + * @return {string} A translated reason, or '' when the name is acceptable. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-create-a-secret-from-the-ui + */ +export function memberNameError(name, taken = []) { + const trimmed = (name || '').trim() + + if (trimmed === '') { + return t('doriath', 'Give the field a name.') + } + + // Case-insensitive: `Key` reaches the same column as `key`, so accepting it + // would produce exactly the misrouting this rule exists to prevent. + if (RESERVED_MEMBER_NAMES.includes(trimmed.toLowerCase()) === true) { + // Context-neutral wording on purpose. This message is now shared with the + // REQUEST dialog, where the reserved fields are tickboxes rather than fields + // above the input — telling that user to "use the field above" would send + // them looking for something that is not there. + return t( + 'doriath', + '“{name}” is a built-in field, not an additional one — choose a different name.', + { name: trimmed.toLowerCase() }, + ) + } + + if (taken.some((existing) => (existing || '').trim() === trimmed) === true) { + return t('doriath', 'That field is already listed.') + } + + return '' +} + +/** + * Turn a name/value member list into the object the store encrypts. + * + * Returns `{}` for an empty list rather than null, and that distinction is load + * bearing: an empty blob means "this secret has no additional fields", while null + * means "nothing was sent", which the store reads as "leave whatever is stored + * alone". Removing the last member has to say the former. + * + * @param {Array<{name: string, value: string}>} members The edited member list. + * + * @return {object} A plain object of name → value, possibly empty. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui + */ +export function membersToObject(members = []) { + return members.reduce((acc, member) => { + const name = (member?.name || '').trim() + if (name !== '') { + acc[name] = member?.value ?? '' + } + + return acc + }, {}) +} + +/** + * Turn a decrypted blob back into an editable member list. + * + * Tolerates the shapes the store can hand over: a parsed object (the normal case), + * or a string when the ciphertext did not contain JSON. A string is deliberately + * NOT guessed at — it becomes no members, so an edit cannot silently discard + * something unparseable by writing a blob that omits it. + * + * @param {object|string|null|undefined} blob The decrypted `additionalFields`. + * + * @return {Array<{name: string, value: string}>} The editable member list. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui + */ +export function objectToMembers(blob) { + if (blob === null || blob === undefined || typeof blob !== 'object') { + return [] + } + + return Object.entries(blob).map(([name, value]) => ({ + name, + value: value === null || value === undefined ? '' : String(value), + })) +} diff --git a/src/views/SecretDetail.vue b/src/views/SecretDetail.vue index a6d7a757d..2c8937204 100644 --- a/src/views/SecretDetail.vue +++ b/src/views/SecretDetail.vue @@ -427,11 +427,32 @@ export default { return this.$route.params.id }, + /** + * Whether this secret has at least one additional field to show. + * + * The count matters, not just the presence of an object. `{}` is truthy AND + * an object, so a guard testing only those two rendered the "Additional + * fields" heading with nothing beneath it. That state only became reachable + * when owners could edit members: removing the last one now sends an empty + * blob (deliberately, so "none" stays distinguishable from "not loaded"), + * and the spec says re-opening the secret must then show NO additional + * fields — not an empty section. + * + * Returns a real boolean rather than the last truthy operand, so callers and + * tests get `false` instead of `null` or `undefined`. + * + * @return {boolean} True when there is at least one member to render. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui + */ hasAdditionalFields() { + const blob = this.secret?.additionalFields + return ( - this.secret - && this.secret.additionalFields - && typeof this.secret.additionalFields === 'object' + blob !== null + && blob !== undefined + && typeof blob === 'object' + && Object.keys(blob).length > 0 ) }, diff --git a/tests/components/AdditionalFieldsEditor.spec.js b/tests/components/AdditionalFieldsEditor.spec.js new file mode 100644 index 000000000..7fb637c2a --- /dev/null +++ b/tests/components/AdditionalFieldsEditor.spec.js @@ -0,0 +1,211 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction / Doriath Contributors + * SPDX-License-Identifier: EUPL-1.2 + * + * Component test for `src/components/AdditionalFieldsEditor.vue`. + * + * The editor is shared by the secret-create and secret-edit dialogs, and its + * validation is shared further still with the request dialog. So the behaviour + * pinned here is what all three depend on: a reserved name is refused WITH A + * REASON, duplicates and blanks are refused, and add / rename / re-value / remove + * emit the list the caller will turn into one encrypted blob. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-create-a-secret-from-the-ui + */ + +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' + +import AdditionalFieldsEditor from '../../src/components/AdditionalFieldsEditor.vue' + +const stubs = { + NcTextField: { + props: ['modelValue', 'label', 'placeholder', 'disabled'], + emits: ['update:modelValue'], + template: + '', + }, + NcButton: { + props: ['disabled', 'ariaLabel', 'variant'], + template: + '', + }, +} + +const mountEditor = (members = []) => + mount(AdditionalFieldsEditor, { + propsData: { members }, + global: { stubs }, + }) + +describe('AdditionalFieldsEditor', () => { + it('says there are none rather than showing an empty list', () => { + const wrapper = mountEditor([]) + + expect( + wrapper.find('[data-testid="additional-fields-empty"]').exists(), + ).toBe(true) + }) + + it('renders one row per member, with name and value', () => { + const wrapper = mountEditor([ + { name: 'client-id', value: 'abc' }, + { name: 'tenant', value: 'acme' }, + ]) + + expect(wrapper.find('[data-testid="additional-field-row-0"]').exists()).toBe( + true, + ) + expect(wrapper.find('[data-testid="additional-field-row-1"]').exists()).toBe( + true, + ) + expect( + wrapper.find('[data-testid="additional-field-name-0"]').element.value, + ).toBe('client-id') + expect( + wrapper.find('[data-testid="additional-field-value-1"]').element.value, + ).toBe('acme') + }) + + it('adds a member and emits the new list', async () => { + const wrapper = mountEditor([]) + + await wrapper + .find('[data-testid="additional-field-new-name"]') + .setValue('client-id') + await wrapper.find('[data-testid="additional-field-add"]').trigger('click') + + expect(wrapper.emitted('update:members')[0][0]).toEqual([ + { name: 'client-id', value: '' }, + ]) + // The input clears, so the next name does not have to be deleted first. + expect(wrapper.vm.newName).toBe('') + }) + + it('refuses a reserved name WITH a reason, and emits nothing', async () => { + // `key` addresses the Secret's own column. Accepting it would look like a + // second field with that label while the value is misrouted or shadowed. + const wrapper = mountEditor([]) + + await wrapper + .find('[data-testid="additional-field-new-name"]') + .setValue('key') + await wrapper.find('[data-testid="additional-field-add"]').trigger('click') + + const error = wrapper.find('[data-testid="additional-field-error"]') + expect(error.exists()).toBe(true) + expect(error.text().length).toBeGreaterThan(0) + expect(wrapper.emitted('update:members')).toBeUndefined() + }) + + it('refuses a duplicate and a blank, and emits nothing', async () => { + const wrapper = mountEditor([{ name: 'tenant', value: 'acme' }]) + + await wrapper + .find('[data-testid="additional-field-new-name"]') + .setValue('tenant') + await wrapper.find('[data-testid="additional-field-add"]').trigger('click') + expect(wrapper.find('[data-testid="additional-field-error"]').exists()).toBe( + true, + ) + + await wrapper + .find('[data-testid="additional-field-new-name"]') + .setValue(' ') + await wrapper.find('[data-testid="additional-field-add"]').trigger('click') + expect(wrapper.find('[data-testid="additional-field-error"]').exists()).toBe( + true, + ) + + expect(wrapper.emitted('update:members')).toBeUndefined() + }) + + it('renames a member in place rather than replacing it', async () => { + // Position matters: the rows are keyed by index precisely so a rename stays + // one edit instead of becoming a remove plus an add, which would lose focus + // and reorder the list under the user. + const wrapper = mountEditor([ + { name: 'old', value: 'keepme' }, + { name: 'other', value: '2' }, + ]) + + await wrapper.find('[data-testid="additional-field-name-0"]').setValue('new') + + expect(wrapper.emitted('update:members')[0][0]).toEqual([ + { name: 'new', value: 'keepme' }, + { name: 'other', value: '2' }, + ]) + }) + + it('flags a rename onto a reserved or duplicate name', async () => { + const wrapper = mountEditor([ + { name: 'a', value: '1' }, + { name: 'b', value: '2' }, + ]) + + await wrapper.find('[data-testid="additional-field-name-0"]').setValue('url') + expect(wrapper.find('[data-testid="additional-field-error"]').exists()).toBe( + true, + ) + + await wrapper.find('[data-testid="additional-field-name-0"]').setValue('b') + expect(wrapper.find('[data-testid="additional-field-error"]').exists()).toBe( + true, + ) + }) + + it('does not flag a member for colliding with ITSELF', async () => { + // Renaming `a` to `a` (or editing around it) must not report a duplicate: + // the row is compared against the OTHERS, not the whole list. + const wrapper = mountEditor([{ name: 'a', value: '1' }]) + + await wrapper.find('[data-testid="additional-field-name-0"]').setValue('a') + + expect(wrapper.find('[data-testid="additional-field-error"]').exists()).toBe( + false, + ) + }) + + it('changes a value without touching the name', async () => { + const wrapper = mountEditor([{ name: 'client-id', value: 'old' }]) + + await wrapper + .find('[data-testid="additional-field-value-0"]') + .setValue('new') + + expect(wrapper.emitted('update:members')[0][0]).toEqual([ + { name: 'client-id', value: 'new' }, + ]) + }) + + it('removes a member and can empty the list entirely', async () => { + const wrapper = mountEditor([{ name: 'only', value: '1' }]) + + await wrapper + .find('[data-testid="additional-field-remove-0"]') + .trigger('click') + + // An EMPTY list, which the caller turns into an empty blob rather than null. + expect(wrapper.emitted('update:members')[0][0]).toEqual([]) + }) + + it('disables every control while editing is blocked', () => { + const wrapper = mountEditor([{ name: 'a', value: '1' }]) + expect( + wrapper.find('[data-testid="additional-field-name-0"]').element.disabled, + ).toBe(false) + + const locked = mount(AdditionalFieldsEditor, { + propsData: { members: [{ name: 'a', value: '1' }], disabled: true }, + global: { stubs }, + }) + + expect( + locked.find('[data-testid="additional-field-name-0"]').element.disabled, + ).toBe(true) + expect( + locked.find('[data-testid="additional-field-new-name"]').element + .disabled, + ).toBe(true) + }) +}) diff --git a/tests/dialogs/SecretCreateDialog.additionalFields.spec.js b/tests/dialogs/SecretCreateDialog.additionalFields.spec.js new file mode 100644 index 000000000..6592284a0 --- /dev/null +++ b/tests/dialogs/SecretCreateDialog.additionalFields.spec.js @@ -0,0 +1,178 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction / Doriath Contributors + * SPDX-License-Identifier: EUPL-1.2 + * + * Additional fields on the create dialog. + * + * Before this change the dialog referenced additional fields nowhere, so an owner + * could only obtain one by asking a stranger to fill a secret request. What is + * pinned here is that the members the user typed actually reach the store call — + * and that a secret created WITHOUT any does not get an empty blob written onto it + * for no reason. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-create-a-secret-from-the-ui + */ + +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import SecretCreateDialog from '../../src/dialogs/SecretCreateDialog.vue' +import { useFolderStore } from '../../src/store/modules/folder.js' +import { useSecretStore } from '../../src/store/modules/secret.js' +import { useSecretTypeStore } from '../../src/store/modules/secretType.js' +import { useSessionStore } from '../../src/store/modules/session.js' + +const stubs = { + NcDialog: { + props: ['name', 'open', 'size'], + template: '
', + }, + NcButton: { + props: ['disabled', 'variant', 'ariaLabel', 'title'], + template: + '', + }, + NcSelect: { + props: ['options', 'reduce', 'inputLabel', 'clearable', 'modelValue'], + template: '
', + }, + NcTextField: { + props: ['modelValue', 'label', 'placeholder', 'disabled', 'required'], + emits: ['update:modelValue'], + template: + '', + }, + NcPasswordField: { + props: ['modelValue', 'label'], + template: '', + }, + NcNoteCard: { props: ['type'], template: '
' }, + NcLoadingIcon: { template: '' }, + Plus: { template: '' }, + Dice5: { template: '' }, + KeyGeneratorModal: { props: ['open'], template: '
' }, +} + +const mountDialog = async () => { + const wrapper = mount(SecretCreateDialog, { propsData: {}, global: { stubs } }) + await wrapper.vm.$nextTick() + + return wrapper +} + +describe('SecretCreateDialog — additional fields', () => { + beforeEach(() => { + setActivePinia(createPinia()) + vi.restoreAllMocks() + + const typeStore = useSecretTypeStore() + typeStore.types = [{ id: 'login', name: 'login', label: 'Login' }] + typeStore.fetchTypes = vi.fn().mockResolvedValue() + const folderStore = useFolderStore() + folderStore.folders = [] + folderStore.fetchFolders = vi.fn().mockResolvedValue() + // isLocked is a GETTER over cryptoKey — assigning it is a no-op (Vue logs + // "target is readonly"), so the vault would stay locked and canSubmit would + // never become true. Set the state the getter actually reads. + const session = useSessionStore() + session.cryptoKey = 'UNLOCKED' + session.certificate = 'PEM' + }) + + it('offers the editor', async () => { + const wrapper = await mountDialog() + + expect( + wrapper.find('[data-testid="additional-fields-editor"]').exists(), + ).toBe(true) + }) + + it('sends the members the user added, as one object', async () => { + const wrapper = await mountDialog() + const create = vi + .spyOn(useSecretStore(), 'createSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.name = 'Supplier API' + wrapper.vm.value = 'Xk9#mQ2$vL7@pR4!zT6&' + wrapper.vm.additionalFields = [ + { name: 'client-id', value: 'acme-4711' }, + { name: 'tenant', value: 'acme' }, + ] + await wrapper.vm.submit() + + expect(create).toHaveBeenCalledTimes(1) + expect(create.mock.calls[0][0].additionalFields).toEqual({ + 'client-id': 'acme-4711', + tenant: 'acme', + }) + }) + + it('omits the field entirely when the user added none', async () => { + // Not `{}`: the store encrypts whatever it is handed, so sending an empty + // object unconditionally would write a pointless ciphertext blob onto every + // secret created here. + const wrapper = await mountDialog() + const create = vi + .spyOn(useSecretStore(), 'createSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.name = 'Plain secret' + wrapper.vm.value = 'Xk9#mQ2$vL7@pR4!zT6&' + await wrapper.vm.submit() + + expect('additionalFields' in create.mock.calls[0][0]).toBe(false) + }) + + it('drops a member whose name was left blank', async () => { + const wrapper = await mountDialog() + const create = vi + .spyOn(useSecretStore(), 'createSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.name = 'Supplier API' + wrapper.vm.value = 'Xk9#mQ2$vL7@pR4!zT6&' + wrapper.vm.additionalFields = [ + { name: '', value: 'orphan' }, + { name: 'kept', value: '1' }, + ] + await wrapper.vm.submit() + + expect(create.mock.calls[0][0].additionalFields).toEqual({ kept: '1' }) + }) + + // --------------------------------------------------------------------------- + // Two scenarios this change's delta re-declares which nothing covered before. + // They were moved off PR #270 as a known gap rather than waived with a false + // exclude; this is where they get paid for. Both are dialog-level rules, so + // vitest is the honest home — a browser adds nothing to "the button is + // disabled" that this does not already prove. + // --------------------------------------------------------------------------- + + it('requires a name AND a value before anything is sent', async () => { + const wrapper = await mountDialog() + const create = vi + .spyOn(useSecretStore(), 'createSecret') + .mockResolvedValue({ id: 's1' }) + + expect(wrapper.vm.canSubmit).toBe(false) + + wrapper.vm.name = 'Has a name' + expect(wrapper.vm.canSubmit).toBe(false) + + wrapper.vm.name = '' + wrapper.vm.value = 'Xk9#mQ2$vL7@pR4!zT6&' + expect(wrapper.vm.canSubmit).toBe(false) + + // Whitespace is not a name. + wrapper.vm.name = ' ' + expect(wrapper.vm.canSubmit).toBe(false) + + await wrapper.vm.submit() + expect(create).not.toHaveBeenCalled() + + wrapper.vm.name = 'Both present' + expect(wrapper.vm.canSubmit).toBe(true) + }) +}) diff --git a/tests/dialogs/SecretEditDialog.additionalFields.spec.js b/tests/dialogs/SecretEditDialog.additionalFields.spec.js new file mode 100644 index 000000000..695d8b0fa --- /dev/null +++ b/tests/dialogs/SecretEditDialog.additionalFields.spec.js @@ -0,0 +1,212 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction / Doriath Contributors + * SPDX-License-Identifier: EUPL-1.2 + * + * Additional fields on the edit dialog. + * + * The blob is the storage unit, so there is no per-member update: any change + * rewrites every member. Two things therefore have to hold, and both are pinned + * here — the dialog must start from the CURRENT decrypted members (otherwise a save + * silently drops the ones it never loaded), and removing the last member must send + * an empty object rather than nothing at all, since "nothing" means "leave the + * stored blob alone". + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui + */ + +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import SecretEditDialog from '../../src/dialogs/SecretEditDialog.vue' +import { useSecretStore } from '../../src/store/modules/secret.js' +import { useSecretTypeStore } from '../../src/store/modules/secretType.js' +import { useSessionStore } from '../../src/store/modules/session.js' + +const stubs = { + NcDialog: { + props: ['name', 'open', 'size'], + template: '
', + }, + NcButton: { + props: ['disabled', 'variant', 'ariaLabel', 'title'], + template: + '', + }, + NcSelect: { + props: ['options', 'reduce', 'inputLabel', 'clearable', 'modelValue'], + template: '
', + }, + NcTextField: { + props: ['modelValue', 'label', 'placeholder', 'disabled', 'required'], + emits: ['update:modelValue'], + template: + '', + }, + NcPasswordField: { + props: ['modelValue', 'label'], + template: '', + }, + NcNoteCard: { props: ['type'], template: '
' }, + NcLoadingIcon: { template: '' }, + Plus: { template: '' }, + Dice5: { template: '' }, + KeyGeneratorModal: { props: ['open'], template: '
' }, +} + +/** + * Mount the edit dialog over a secret whose decrypted blob holds `members`. + * + * @param {object|null} members The decrypted additionalFields, or null. + * + * @return {Promise} The mounted wrapper. + */ +const mountOver = async (members) => { + const store = useSecretStore() + store.fetchSecret = vi.fn().mockResolvedValue({ + id: 's1', + name: 'Supplier API', + typeId: 'login', + key: 'Xk9#mQ2$vL7@pR4!zT6&', + url: 'https://supplier.example', + login: 'svc-acct', + additionalFields: members, + }) + + const wrapper = mount(SecretEditDialog, { + propsData: { secretId: 's1' }, + global: { stubs }, + }) + // mounted() → fetchPolicy + fetchTypes + load(); two ticks let all of it settle. + await wrapper.vm.$nextTick() + await new Promise((resolve) => setTimeout(resolve, 0)) + await wrapper.vm.$nextTick() + + return wrapper +} + +describe('SecretEditDialog — additional fields', () => { + beforeEach(() => { + setActivePinia(createPinia()) + vi.restoreAllMocks() + + const typeStore = useSecretTypeStore() + typeStore.types = [{ id: 'login', name: 'login', label: 'Login' }] + typeStore.fetchTypes = vi.fn().mockResolvedValue() + + // isLocked is a getter over cryptoKey; assigning it is a no-op. + const session = useSessionStore() + session.cryptoKey = 'UNLOCKED' + session.certificate = 'PEM' + }) + + it('pre-fills the existing members from the decrypted blob', async () => { + const wrapper = await mountOver({ 'client-id': 'acme-4711', tenant: 'acme' }) + + expect(wrapper.vm.additionalFields).toEqual([ + { name: 'client-id', value: 'acme-4711' }, + { name: 'tenant', value: 'acme' }, + ]) + expect( + wrapper.find('[data-testid="additional-field-name-0"]').element.value, + ).toBe('client-id') + }) + + it('shows no members for a secret that has none', async () => { + const wrapper = await mountOver(null) + + expect(wrapper.vm.additionalFields).toEqual([]) + expect( + wrapper.find('[data-testid="additional-fields-empty"]').exists(), + ).toBe(true) + }) + + it('sends the whole blob when one member is renamed', async () => { + const wrapper = await mountOver({ old: 'keepme', tenant: 'acme' }) + const update = vi + .spyOn(useSecretStore(), 'updateSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.additionalFields = [ + { name: 'new', value: 'keepme' }, + { name: 'tenant', value: 'acme' }, + ] + await wrapper.vm.submit() + + // Every member, not just the renamed one — the blob is rewritten whole. + expect(update.mock.calls[0][1].additionalFields).toEqual({ + new: 'keepme', + tenant: 'acme', + }) + }) + + it('sends the blob when a value changes or a member is added', async () => { + const wrapper = await mountOver({ tenant: 'acme' }) + const update = vi + .spyOn(useSecretStore(), 'updateSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.additionalFields = [ + { name: 'tenant', value: 'globex' }, + { name: 'client-id', value: 'new-one' }, + ] + await wrapper.vm.submit() + + expect(update.mock.calls[0][1].additionalFields).toEqual({ + tenant: 'globex', + 'client-id': 'new-one', + }) + }) + + it('sends an EMPTY object when the last member is removed', async () => { + // Not null, and not omitted: either would be read as "leave the stored blob + // alone", so the member the user just deleted would survive the save. + const wrapper = await mountOver({ tenant: 'acme' }) + const update = vi + .spyOn(useSecretStore(), 'updateSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.additionalFields = [] + await wrapper.vm.submit() + + expect(update.mock.calls[0][1].additionalFields).toEqual({}) + }) + + it('does not touch the blob when nothing about it changed', async () => { + // A metadata-only edit must not re-encrypt and rewrite the members, both + // because it is pointless work and because rewriting is what loses a + // concurrent session's additions. + const wrapper = await mountOver({ tenant: 'acme' }) + const update = vi + .spyOn(useSecretStore(), 'updateSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.name = 'Renamed only' + await wrapper.vm.submit() + + expect(update).toHaveBeenCalledTimes(1) + expect('additionalFields' in update.mock.calls[0][1]).toBe(false) + }) + + it('alters no ciphertext when only the name changes', async () => { + // "Edit metadata only" — a scenario in this requirement that nothing covered, + // vitest or Playwright. It is not about additional fields as such, but it is + // the same diff logic: a metadata-only save must carry NO sensitive field, or + // every rename would re-encrypt the value, the login and the whole member + // blob for nothing — and rewriting the blob is exactly what loses a + // concurrent session's additions. + const wrapper = await mountOver({ tenant: 'acme' }) + const update = vi + .spyOn(useSecretStore(), 'updateSecret') + .mockResolvedValue({ id: 's1' }) + + wrapper.vm.name = 'Renamed' + await wrapper.vm.submit() + + const diff = update.mock.calls[0][1] + expect(diff.name).toBe('Renamed') + expect('key' in diff).toBe(false) + expect('login' in diff).toBe(false) + expect('additionalFields' in diff).toBe(false) + }) +}) diff --git a/tests/store/secret-additional-fields.spec.js b/tests/store/secret-additional-fields.spec.js new file mode 100644 index 000000000..71ff13a77 --- /dev/null +++ b/tests/store/secret-additional-fields.spec.js @@ -0,0 +1,133 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction / Doriath Contributors + * SPDX-License-Identifier: EUPL-1.2 + * + * How the secret store carries additional fields to the server. + * + * This is the seam where the zero-knowledge property either holds or does not. + * Member NAMES and values both live inside the single encrypted blob (ADR-003), so + * a leak here is not a cosmetic bug: it would put "client-secret" and its value in + * the request body in plaintext, on a server that must never be able to read it. + * + * Also pins the empty-blob rule. `{}` and null look interchangeable in JavaScript + * and are not: `{}` says "this secret has no additional fields", null says "nothing + * was sent", which the update path reads as "leave the stored blob alone". Removing + * the last member has to mean the former. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui + */ + +import axios from '@nextcloud/axios' +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +vi.mock('../../src/crypto/index.js', () => ({ + importPublicKey: vi.fn(async () => 'PUBKEY_HANDLE'), + rsaEncrypt: vi.fn(async (value) => `ENC(${value})`), + rsaDecrypt: vi.fn(async (value) => String(value).replace(/^ENC\((.*)\)$/, '$1')), +})) + +import { useSecretStore } from '../../src/store/modules/secret.js' +import { useSessionStore } from '../../src/store/modules/session.js' + +describe('secret store — additional fields', () => { + beforeEach(() => { + setActivePinia(createPinia()) + vi.restoreAllMocks() + useSessionStore().certificate = 'PEM' + }) + + it('encrypts the members as ONE blob on create', async () => { + const post = vi + .spyOn(axios, 'post') + .mockResolvedValue({ data: { id: 's1' } }) + + await useSecretStore().createSecret({ + name: 'Supplier API', + key: 'topsecret', + additionalFields: { 'client-id': 'abc', tenant: 'acme' }, + }) + + const body = post.mock.calls[0][1] + expect(body.additionalFields).toBe( + 'ENC({"client-id":"abc","tenant":"acme"})', + ) + }) + + it('puts the members ONLY inside the blob, nowhere else in the body', async () => { + const post = vi + .spyOn(axios, 'post') + .mockResolvedValue({ data: { id: 's1' } }) + + await useSecretStore().createSecret({ + name: 'Supplier API', + key: 'topsecret', + additionalFields: { 'client-secret': 'shhh' }, + }) + + // Note what this can and cannot prove. `rsaEncrypt` is mocked here as + // ENC(), so the "ciphertext" contains its own plaintext by + // construction — asserting the body holds no 'shhh' anywhere would be + // asserting a property of the mock, and it fails for that reason alone. + // + // What IS provable at this seam: the members reach the server in the + // additionalFields field and are NOT duplicated into any other field, so + // nothing carries a member name or value outside the blob. That the blob is + // genuinely unreadable is proved with real keys in + // tests/vitest/additional-fields-crypto.spec.js. + const body = { ...post.mock.calls[0][1] } + delete body.additionalFields + const withoutBlob = JSON.stringify(body) + + expect(withoutBlob).not.toContain('client-secret') + expect(withoutBlob).not.toContain('shhh') + }) + + it('rewrites the whole blob when one member changes', async () => { + // The blob IS the storage unit, so there is no per-member update: an edit + // sends every member the dialog held. + const put = vi.spyOn(axios, 'put').mockResolvedValue({ data: { id: 's1' } }) + + await useSecretStore().updateSecret('s1', { + additionalFields: { 'client-id': 'CHANGED', tenant: 'acme' }, + }) + + expect(put.mock.calls[0][1].additionalFields).toBe( + 'ENC({"client-id":"CHANGED","tenant":"acme"})', + ) + }) + + it('sends an EMPTY blob, not null, when the last member is removed', async () => { + const put = vi.spyOn(axios, 'put').mockResolvedValue({ data: { id: 's1' } }) + + await useSecretStore().updateSecret('s1', { additionalFields: {} }) + + const body = put.mock.calls[0][1] + expect(body.additionalFields).toBe('ENC({})') + expect(body.additionalFields).not.toBeNull() + }) + + it('leaves the stored blob alone when the caller sends nothing', async () => { + // The distinction that makes the empty-blob rule meaningful: omitting the + // field must NOT be read as "remove them all". + const put = vi.spyOn(axios, 'put').mockResolvedValue({ data: { id: 's1' } }) + + await useSecretStore().updateSecret('s1', { name: 'Renamed only' }) + + expect('additionalFields' in put.mock.calls[0][1]).toBe(false) + }) + + it('refuses to encrypt anything while the vault is locked', async () => { + useSessionStore().certificate = null + const post = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} }) + + await expect( + useSecretStore().createSecret({ + name: 'x', + key: 'y', + additionalFields: { a: '1' }, + }), + ).rejects.toThrow(/locked/i) + expect(post).not.toHaveBeenCalled() + }) +}) diff --git a/tests/views/SecretDetail.additionalFields.spec.js b/tests/views/SecretDetail.additionalFields.spec.js new file mode 100644 index 000000000..a8ecc55d2 --- /dev/null +++ b/tests/views/SecretDetail.additionalFields.spec.js @@ -0,0 +1,118 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction / Doriath Contributors + * SPDX-License-Identifier: EUPL-1.2 + * + * How the detail view renders additional fields — including none. + * + * The empty case only became reachable with owner-editable additional fields. + * Before that the sole writers were the application dialog, import, and a request + * fill, all of which write at least one member; nobody could produce an EMPTY blob + * from the UI. Removing the last member now does exactly that, and the spec says + * re-opening the secret must show no additional fields and no error. + * + * @spec openspec/specs/secrets-write-ui/spec.md#requirement-edit-a-secret-from-the-ui + */ + +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import SecretDetail from '../../src/views/SecretDetail.vue' +import { useSecretStore } from '../../src/store/modules/secret.js' +import { useSecretTypeStore } from '../../src/store/modules/secretType.js' + +const stubAll = { + NcButton: { template: '' }, + NcEmptyContent: { template: '
' }, + NcLoadingIcon: { template: '' }, + NcNoteCard: { template: '
' }, + NcActions: { template: '
' }, + NcActionButton: { template: '' }, + ArrowLeft: { template: '' }, + Delete: { template: '' }, + Lock: { template: '' }, + Pencil: { template: '' }, + FolderMove: { template: '' }, + ShareVariant: { template: '' }, + CopyButton: { template: '