From a81b28e29c58c9e40a14f3d52a1a06d3a53186b1 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 10 Jul 2025 12:28:16 -0400
Subject: [PATCH 01/28] [test] Upgrade PHPUnit to the latest release
---
composer.json | 2 +-
composer.lock | 619 +++++++++++++++++++++++++++++---------------------
2 files changed, 359 insertions(+), 262 deletions(-)
diff --git a/composer.json b/composer.json
index 841af8656b4..16a49d7b354 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,7 @@
},
"require-dev" : {
"squizlabs/php_codesniffer" : "^3.5",
- "phpunit/phpunit" : "12.0",
+ "phpunit/phpunit" : "^12.2",
"phan/phan": "^5.0",
"phpstan/phpstan": "^1.4",
"slevomat/coding-standard": "^6.4",
diff --git a/composer.lock b/composer.lock
index cb72c4768a5..307294b0aec 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": "962186512cf6f4ef19d97b2738569257",
+ "content-hash": "b2d623d7391c8e620aff8c05988950f1",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.337.3",
+ "version": "3.359.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6"
+ "reference": "40543e3993fc5094094ac9f9bdc4434bf81cca2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/06dfc8f76423b49aaa181debd25bbdc724c346d6",
- "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/40543e3993fc5094094ac9f9bdc4434bf81cca2d",
+ "reference": "40543e3993fc5094094ac9f9bdc4434bf81cca2d",
"shasum": ""
},
"require": {
@@ -79,31 +79,30 @@
"ext-json": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
- "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5",
- "guzzlehttp/promises": "^1.4.0 || ^2.0",
- "guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
- "mtdowling/jmespath.php": "^2.6",
- "php": ">=7.2.5",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "guzzlehttp/promises": "^2.0",
+ "guzzlehttp/psr7": "^2.4.5",
+ "mtdowling/jmespath.php": "^2.8.0",
+ "php": ">=8.1",
"psr/http-message": "^1.0 || ^2.0"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
- "composer/composer": "^1.10.22",
+ "composer/composer": "^2.7.8",
"dms/phpunit-arraysubset-asserts": "^0.4.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-sockets": "*",
- "nette/neon": "^2.3",
- "paragonie/random_compat": ">= 2",
"phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5",
- "psr/cache": "^1.0 || ^2.0 || ^3.0",
- "psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
- "sebastian/comparator": "^1.2.3 || ^4.0",
- "yoast/phpunit-polyfills": "^1.0"
+ "psr/cache": "^2.0 || ^3.0",
+ "psr/simple-cache": "^2.0 || ^3.0",
+ "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0",
+ "symfony/filesystem": "^v6.4.0 || ^v7.1.0",
+ "yoast/phpunit-polyfills": "^2.0"
},
"suggest": {
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
@@ -152,24 +151,24 @@
"sdk"
],
"support": {
- "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
+ "forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.337.3"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.359.1"
},
- "time": "2025-01-21T19:10:05+00:00"
+ "time": "2025-10-29T20:13:06+00:00"
},
{
"name": "bjeavons/zxcvbn-php",
- "version": "1.4.1",
+ "version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/bjeavons/zxcvbn-php.git",
- "reference": "603e015f2c81118a8f42930140311d125eba6f8a"
+ "reference": "426f664501a0747beb8f3ee17ac30c7dd6327ffa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bjeavons/zxcvbn-php/zipball/603e015f2c81118a8f42930140311d125eba6f8a",
- "reference": "603e015f2c81118a8f42930140311d125eba6f8a",
+ "url": "https://api.github.com/repos/bjeavons/zxcvbn-php/zipball/426f664501a0747beb8f3ee17ac30c7dd6327ffa",
+ "reference": "426f664501a0747beb8f3ee17ac30c7dd6327ffa",
"shasum": ""
},
"require": {
@@ -210,22 +209,22 @@
],
"support": {
"issues": "https://github.com/bjeavons/zxcvbn-php/issues",
- "source": "https://github.com/bjeavons/zxcvbn-php/tree/1.4.1"
+ "source": "https://github.com/bjeavons/zxcvbn-php/tree/1.4.2"
},
- "time": "2024-11-21T22:10:41+00:00"
+ "time": "2025-02-24T16:47:20+00:00"
},
{
"name": "firebase/php-jwt",
- "version": "v6.11.0",
+ "version": "v6.11.1",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
- "reference": "8f718f4dfc9c5d5f0c994cdfd103921b43592712"
+ "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/firebase/php-jwt/zipball/8f718f4dfc9c5d5f0c994cdfd103921b43592712",
- "reference": "8f718f4dfc9c5d5f0c994cdfd103921b43592712",
+ "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
+ "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
"shasum": ""
},
"require": {
@@ -273,22 +272,22 @@
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
- "source": "https://github.com/firebase/php-jwt/tree/v6.11.0"
+ "source": "https://github.com/firebase/php-jwt/tree/v6.11.1"
},
- "time": "2025-01-23T05:11:06+00:00"
+ "time": "2025-04-09T20:32:01+00:00"
},
{
"name": "google/recaptcha",
- "version": "1.3.0",
+ "version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/google/recaptcha.git",
- "reference": "d59a801e98a4e9174814a6d71bbc268dff1202df"
+ "reference": "56522c261d2e8c58ba416c90f81a4cd9f2ed89b9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/google/recaptcha/zipball/d59a801e98a4e9174814a6d71bbc268dff1202df",
- "reference": "d59a801e98a4e9174814a6d71bbc268dff1202df",
+ "url": "https://api.github.com/repos/google/recaptcha/zipball/56522c261d2e8c58ba416c90f81a4cd9f2ed89b9",
+ "reference": "56522c261d2e8c58ba416c90f81a4cd9f2ed89b9",
"shasum": ""
},
"require": {
@@ -327,26 +326,26 @@
"issues": "https://github.com/google/recaptcha/issues",
"source": "https://github.com/google/recaptcha"
},
- "time": "2023-02-18T17:41:46+00:00"
+ "time": "2025-06-26T22:21:57+00:00"
},
{
"name": "guzzlehttp/guzzle",
- "version": "7.9.2",
+ "version": "7.10.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
+ "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
- "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
+ "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
"shasum": ""
},
"require": {
"ext-json": "*",
- "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
- "guzzlehttp/psr7": "^2.7.0",
+ "guzzlehttp/promises": "^2.3",
+ "guzzlehttp/psr7": "^2.8",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
@@ -437,7 +436,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
+ "source": "https://github.com/guzzle/guzzle/tree/7.10.0"
},
"funding": [
{
@@ -453,20 +452,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-24T11:22:20+00:00"
+ "time": "2025-08-23T22:36:01+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "2.0.4",
+ "version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
+ "reference": "481557b130ef3790cf82b713667b43030dc9c957"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
- "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
+ "reference": "481557b130ef3790cf82b713667b43030dc9c957",
"shasum": ""
},
"require": {
@@ -474,7 +473,7 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"type": "library",
"extra": {
@@ -520,7 +519,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/2.0.4"
+ "source": "https://github.com/guzzle/promises/tree/2.3.0"
},
"funding": [
{
@@ -536,20 +535,20 @@
"type": "tidelift"
}
],
- "time": "2024-10-17T10:06:22+00:00"
+ "time": "2025-08-22T14:34:08+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "2.7.0",
+ "version": "2.8.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
+ "reference": "21dc724a0583619cd1652f673303492272778051"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
- "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
+ "reference": "21dc724a0583619cd1652f673303492272778051",
"shasum": ""
},
"require": {
@@ -565,7 +564,7 @@
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0",
- "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@@ -636,7 +635,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.7.0"
+ "source": "https://github.com/guzzle/psr7/tree/2.8.0"
},
"funding": [
{
@@ -652,24 +651,24 @@
"type": "tidelift"
}
],
- "time": "2024-07-18T11:15:46+00:00"
+ "time": "2025-08-23T21:21:41+00:00"
},
{
"name": "laminas/laminas-diactoros",
- "version": "3.5.0",
+ "version": "3.8.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-diactoros.git",
- "reference": "143a16306602ce56b8b092a7914fef03c37f9ed2"
+ "reference": "60c182916b2749480895601649563970f3f12ec4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/143a16306602ce56b8b092a7914fef03c37f9ed2",
- "reference": "143a16306602ce56b8b092a7914fef03c37f9ed2",
+ "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/60c182916b2749480895601649563970f3f12ec4",
+ "reference": "60c182916b2749480895601649563970f3f12ec4",
"shasum": ""
},
"require": {
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
+ "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"psr/http-factory": "^1.1",
"psr/http-message": "^1.1 || ^2.0"
},
@@ -686,11 +685,11 @@
"ext-gd": "*",
"ext-libxml": "*",
"http-interop/http-factory-tests": "^2.2.0",
- "laminas/laminas-coding-standard": "~2.5.0",
+ "laminas/laminas-coding-standard": "~3.1.0",
"php-http/psr7-integration-tests": "^1.4.0",
"phpunit/phpunit": "^10.5.36",
- "psalm/plugin-phpunit": "^0.19.0",
- "vimeo/psalm": "^5.26.1"
+ "psalm/plugin-phpunit": "^0.19.5",
+ "vimeo/psalm": "^6.13"
},
"type": "library",
"extra": {
@@ -740,7 +739,7 @@
"type": "community_bridge"
}
],
- "time": "2024-10-14T11:59:49+00:00"
+ "time": "2025-10-12T15:31:36+00:00"
},
{
"name": "mtdowling/jmespath.php",
@@ -1345,16 +1344,16 @@
},
{
"name": "smarty/smarty",
- "version": "v4.5.5",
+ "version": "v4.5.6",
"source": {
"type": "git",
"url": "https://github.com/smarty-php/smarty.git",
- "reference": "c4851c12e34ff80073ddeb7d98b059d57dea9de2"
+ "reference": "a8d77c86660ca0562ec2fb781fbbda737fb7a62b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/smarty-php/smarty/zipball/c4851c12e34ff80073ddeb7d98b059d57dea9de2",
- "reference": "c4851c12e34ff80073ddeb7d98b059d57dea9de2",
+ "url": "https://api.github.com/repos/smarty-php/smarty/zipball/a8d77c86660ca0562ec2fb781fbbda737fb7a62b",
+ "reference": "a8d77c86660ca0562ec2fb781fbbda737fb7a62b",
"shasum": ""
},
"require": {
@@ -1405,22 +1404,28 @@
"support": {
"forum": "https://github.com/smarty-php/smarty/discussions",
"issues": "https://github.com/smarty-php/smarty/issues",
- "source": "https://github.com/smarty-php/smarty/tree/v4.5.5"
+ "source": "https://github.com/smarty-php/smarty/tree/v4.5.6"
},
- "time": "2024-11-21T22:06:22+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/wisskid",
+ "type": "github"
+ }
+ ],
+ "time": "2025-08-26T08:37:46+00:00"
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.5.1",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
- "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": ""
},
"require": {
@@ -1433,7 +1438,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.5-dev"
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -1458,7 +1463,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -1474,23 +1479,24 @@
"type": "tidelift"
}
],
- "time": "2024-09-25T14:20:29+00:00"
+ "time": "2024-09-25T14:21:43+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.31.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
+ "ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
@@ -1538,7 +1544,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
},
"funding": [
{
@@ -1549,12 +1555,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2024-12-23T08:48:59+00:00"
}
],
"packages-dev": [
@@ -1639,16 +1649,16 @@
},
{
"name": "composer/semver",
- "version": "3.4.3",
+ "version": "3.4.4",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
- "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
+ "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
- "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
+ "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
+ "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"shasum": ""
},
"require": {
@@ -1700,7 +1710,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
- "source": "https://github.com/composer/semver/tree/3.4.3"
+ "source": "https://github.com/composer/semver/tree/3.4.4"
},
"funding": [
{
@@ -1710,13 +1720,9 @@
{
"url": "https://github.com/composer",
"type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/composer/composer",
- "type": "tidelift"
}
],
- "time": "2024-09-19T14:15:21+00:00"
+ "time": "2025-08-20T19:15:30+00:00"
},
{
"name": "composer/xdebug-handler",
@@ -2006,16 +2012,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.13.3",
+ "version": "1.13.4",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
- "reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
"require": {
@@ -2054,7 +2060,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
},
"funding": [
{
@@ -2062,7 +2068,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-05T12:25:42+00:00"
+ "time": "2025-08-01T08:46:24+00:00"
},
{
"name": "netresearch/jsonmapper",
@@ -2117,16 +2123,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.5.0",
+ "version": "v5.6.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
"shasum": ""
},
"require": {
@@ -2145,7 +2151,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.0-dev"
+ "dev-master": "5.x-dev"
}
},
"autoload": {
@@ -2169,22 +2175,22 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
},
- "time": "2025-05-31T08:24:38+00:00"
+ "time": "2025-10-21T19:32:17+00:00"
},
{
"name": "phan/phan",
- "version": "5.4.5",
+ "version": "5.5.2",
"source": {
"type": "git",
"url": "https://github.com/phan/phan.git",
- "reference": "2b15302175931a0629a85c57d0c1f91d68b26a4d"
+ "reference": "25d7e8d185a4c78e7423c188fb28bba5dbde20c1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phan/phan/zipball/2b15302175931a0629a85c57d0c1f91d68b26a4d",
- "reference": "2b15302175931a0629a85c57d0c1f91d68b26a4d",
+ "url": "https://api.github.com/repos/phan/phan/zipball/25d7e8d185a4c78e7423c188fb28bba5dbde20c1",
+ "reference": "25d7e8d185a4c78e7423c188fb28bba5dbde20c1",
"shasum": ""
},
"require": {
@@ -2195,7 +2201,7 @@
"ext-tokenizer": "*",
"felixfbecker/advanced-json-rpc": "^3.0.4",
"microsoft/tolerant-php-parser": "0.1.2",
- "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0",
+ "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0|^5.0",
"php": "^7.2.0|^8.0.0",
"sabre/event": "^5.1.3",
"symfony/console": "^3.2|^4.0|^5.0|^6.0|^7.0",
@@ -2249,9 +2255,9 @@
],
"support": {
"issues": "https://github.com/phan/phan/issues",
- "source": "https://github.com/phan/phan/tree/5.4.5"
+ "source": "https://github.com/phan/phan/tree/5.5.2"
},
- "time": "2024-08-13T21:41:35+00:00"
+ "time": "2025-10-04T18:04:38+00:00"
},
{
"name": "phar-io/manifest",
@@ -2377,12 +2383,12 @@
"source": {
"type": "git",
"url": "https://github.com/php-webdriver/php-webdriver.git",
- "reference": "998e499b786805568deaf8cbf06f4044f05d91bf"
+ "reference": "898f0be8267680fbf962e371ea39b7f7f6411bdb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf",
- "reference": "998e499b786805568deaf8cbf06f4044f05d91bf",
+ "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/898f0be8267680fbf962e371ea39b7f7f6411bdb",
+ "reference": "898f0be8267680fbf962e371ea39b7f7f6411bdb",
"shasum": ""
},
"require": {
@@ -2434,9 +2440,9 @@
],
"support": {
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
- "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2"
+ "source": "https://github.com/php-webdriver/php-webdriver/tree/main"
},
- "time": "2024-11-21T15:12:59+00:00"
+ "time": "2025-02-24T19:21:58+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@@ -2658,16 +2664,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.12.16",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "e0bb5cb78545aae631220735aa706eac633a6be9"
- },
+ "version": "1.12.32",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0bb5cb78545aae631220735aa706eac633a6be9",
- "reference": "e0bb5cb78545aae631220735aa706eac633a6be9",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8",
+ "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8",
"shasum": ""
},
"require": {
@@ -2712,38 +2713,38 @@
"type": "github"
}
],
- "time": "2025-01-21T14:50:05+00:00"
+ "time": "2025-09-30T10:16:31+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "12.3.1",
+ "version": "12.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "ddec29dfc128eba9c204389960f2063f3b7fa170"
+ "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ddec29dfc128eba9c204389960f2063f3b7fa170",
- "reference": "ddec29dfc128eba9c204389960f2063f3b7fa170",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c",
+ "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^5.4.0",
+ "nikic/php-parser": "^5.6.1",
"php": ">=8.3",
"phpunit/php-file-iterator": "^6.0",
"phpunit/php-text-template": "^5.0",
"sebastian/complexity": "^5.0",
- "sebastian/environment": "^8.0",
+ "sebastian/environment": "^8.0.3",
"sebastian/lines-of-code": "^4.0",
"sebastian/version": "^6.0",
"theseer/tokenizer": "^1.2.3"
},
"require-dev": {
- "phpunit/phpunit": "^12.1"
+ "phpunit/phpunit": "^12.3.7"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
@@ -2752,7 +2753,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "12.3.x-dev"
+ "dev-main": "12.4.x-dev"
}
},
"autoload": {
@@ -2781,7 +2782,7 @@
"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/12.3.1"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.4.0"
},
"funding": [
{
@@ -2801,7 +2802,7 @@
"type": "tidelift"
}
],
- "time": "2025-06-18T08:58:13+00:00"
+ "time": "2025-09-24T13:44:41+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -3050,16 +3051,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.0.0",
+ "version": "12.4.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "9912c83b5207ab3730fcadc42992e95bdb02dad8"
+ "reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9912c83b5207ab3730fcadc42992e95bdb02dad8",
- "reference": "9912c83b5207ab3730fcadc42992e95bdb02dad8",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a94ea4d26d865875803b23aaf78c3c2c670ea2ea",
+ "reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea",
"shasum": ""
},
"require": {
@@ -3069,23 +3070,23 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.12.1",
+ "myclabs/deep-copy": "^1.13.4",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
- "phpunit/php-code-coverage": "^12.0.0",
+ "phpunit/php-code-coverage": "^12.4.0",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
"phpunit/php-timer": "^8.0.0",
- "sebastian/cli-parser": "^4.0.0",
- "sebastian/comparator": "^7.0.0",
+ "sebastian/cli-parser": "^4.2.0",
+ "sebastian/comparator": "^7.1.3",
"sebastian/diff": "^7.0.0",
- "sebastian/environment": "^8.0.0",
- "sebastian/exporter": "^7.0.0",
- "sebastian/global-state": "^8.0.0",
+ "sebastian/environment": "^8.0.3",
+ "sebastian/exporter": "^7.0.2",
+ "sebastian/global-state": "^8.0.2",
"sebastian/object-enumerator": "^7.0.0",
- "sebastian/type": "^6.0.0",
+ "sebastian/type": "^6.0.3",
"sebastian/version": "^6.0.0",
"staabm/side-effects-detector": "^1.0.5"
},
@@ -3095,7 +3096,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "12.0-dev"
+ "dev-main": "12.4-dev"
}
},
"autoload": {
@@ -3127,7 +3128,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.0.0"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.2"
},
"funding": [
{
@@ -3138,12 +3139,20 @@
"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": "2025-02-07T05:03:33+00:00"
+ "time": "2025-10-30T08:41:39+00:00"
},
{
"name": "psr/container",
@@ -3266,16 +3275,16 @@
},
{
"name": "sebastian/cli-parser",
- "version": "4.0.0",
+ "version": "4.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c"
+ "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/6d584c727d9114bcdc14c86711cd1cad51778e7c",
- "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04",
+ "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04",
"shasum": ""
},
"require": {
@@ -3287,7 +3296,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "4.0-dev"
+ "dev-main": "4.2-dev"
}
},
"autoload": {
@@ -3311,28 +3320,40 @@
"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/4.0.0"
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.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/cli-parser",
+ "type": "tidelift"
}
],
- "time": "2025-02-07T04:53:50+00:00"
+ "time": "2025-09-14T09:36:45+00:00"
},
{
"name": "sebastian/comparator",
- "version": "7.1.0",
+ "version": "7.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "03d905327dccc0851c9a08d6a979dfc683826b6f"
+ "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/03d905327dccc0851c9a08d6a979dfc683826b6f",
- "reference": "03d905327dccc0851c9a08d6a979dfc683826b6f",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148",
+ "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148",
"shasum": ""
},
"require": {
@@ -3391,7 +3412,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.0"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3"
},
"funding": [
{
@@ -3411,7 +3432,7 @@
"type": "tidelift"
}
],
- "time": "2025-06-17T07:41:58+00:00"
+ "time": "2025-08-20T11:27:00+00:00"
},
{
"name": "sebastian/complexity",
@@ -3540,16 +3561,16 @@
},
{
"name": "sebastian/environment",
- "version": "8.0.2",
+ "version": "8.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "d364b9e5d0d3b18a2573351a1786fbf96b7e0792"
+ "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d364b9e5d0d3b18a2573351a1786fbf96b7e0792",
- "reference": "d364b9e5d0d3b18a2573351a1786fbf96b7e0792",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
+ "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
"shasum": ""
},
"require": {
@@ -3592,7 +3613,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
- "source": "https://github.com/sebastianbergmann/environment/tree/8.0.2"
+ "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3"
},
"funding": [
{
@@ -3612,20 +3633,20 @@
"type": "tidelift"
}
],
- "time": "2025-05-21T15:05:44+00:00"
+ "time": "2025-08-12T14:11:56+00:00"
},
{
"name": "sebastian/exporter",
- "version": "7.0.0",
+ "version": "7.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "76432aafc58d50691a00d86d0632f1217a47b688"
+ "reference": "016951ae10980765e4e7aee491eb288c64e505b7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/76432aafc58d50691a00d86d0632f1217a47b688",
- "reference": "76432aafc58d50691a00d86d0632f1217a47b688",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7",
+ "reference": "016951ae10980765e4e7aee491eb288c64e505b7",
"shasum": ""
},
"require": {
@@ -3682,28 +3703,40 @@
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy",
- "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.0"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
}
],
- "time": "2025-02-07T04:56:42+00:00"
+ "time": "2025-09-24T06:16:11+00:00"
},
{
"name": "sebastian/global-state",
- "version": "8.0.0",
+ "version": "8.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc"
+ "reference": "ef1377171613d09edd25b7816f05be8313f9115d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/570a2aeb26d40f057af686d63c4e99b075fb6cbc",
- "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d",
+ "reference": "ef1377171613d09edd25b7816f05be8313f9115d",
"shasum": ""
},
"require": {
@@ -3744,15 +3777,27 @@
"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/8.0.0"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+ "type": "tidelift"
}
],
- "time": "2025-02-07T04:56:59+00:00"
+ "time": "2025-08-29T11:29:25+00:00"
},
{
"name": "sebastian/lines-of-code",
@@ -3928,16 +3973,16 @@
},
{
"name": "sebastian/recursion-context",
- "version": "7.0.0",
+ "version": "7.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c"
+ "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c405ae3a63e01b32eb71577f8ec1604e39858a7c",
- "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c",
+ "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c",
"shasum": ""
},
"require": {
@@ -3980,28 +4025,40 @@
"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/7.0.0"
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.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": "2025-02-07T05:00:01+00:00"
+ "time": "2025-08-13T04:44:59+00:00"
},
{
"name": "sebastian/type",
- "version": "6.0.2",
+ "version": "6.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
- "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069"
+ "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/1d7cd6e514384c36d7a390347f57c385d4be6069",
- "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d",
+ "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d",
"shasum": ""
},
"require": {
@@ -4037,15 +4094,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"security": "https://github.com/sebastianbergmann/type/security/policy",
- "source": "https://github.com/sebastianbergmann/type/tree/6.0.2"
+ "source": "https://github.com/sebastianbergmann/type/tree/6.0.3"
},
"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/type",
+ "type": "tidelift"
}
],
- "time": "2025-03-18T13:37:31+00:00"
+ "time": "2025-08-09T06:57:12+00:00"
},
{
"name": "sebastian/version",
@@ -4164,16 +4233,16 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.11.3",
+ "version": "3.13.4",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
- "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10"
+ "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10",
- "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119",
+ "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119",
"shasum": ""
},
"require": {
@@ -4240,11 +4309,11 @@
"type": "open_collective"
},
{
- "url": "https://thanks.dev/phpcsstandards",
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
"type": "thanks_dev"
}
],
- "time": "2025-01-23T17:04:15+00:00"
+ "time": "2025-09-05T05:47:09+00:00"
},
{
"name": "staabm/side-effects-detector",
@@ -4300,23 +4369,24 @@
},
{
"name": "symfony/console",
- "version": "v7.2.1",
+ "version": "v7.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
+ "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
- "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+ "url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7",
+ "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7",
"shasum": ""
},
"require": {
"php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^6.4|^7.0"
+ "symfony/string": "^7.2"
},
"conflict": {
"symfony/dependency-injection": "<6.4",
@@ -4373,7 +4443,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.2.1"
+ "source": "https://github.com/symfony/console/tree/v7.3.5"
},
"funding": [
{
@@ -4384,16 +4454,20 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-12-11T03:49:26+00:00"
+ "time": "2025-10-14T15:46:26+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.31.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -4452,7 +4526,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
},
"funding": [
{
@@ -4463,6 +4537,10 @@
"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"
@@ -4472,16 +4550,16 @@
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.31.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
- "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
"shasum": ""
},
"require": {
@@ -4530,7 +4608,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
},
"funding": [
{
@@ -4541,16 +4619,20 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2025-06-27T09:58:17+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.31.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@@ -4611,7 +4693,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
},
"funding": [
{
@@ -4622,6 +4704,10 @@
"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"
@@ -4631,16 +4717,16 @@
},
{
"name": "symfony/polyfill-php80",
- "version": "v1.31.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
- "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
"shasum": ""
},
"require": {
@@ -4691,7 +4777,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
},
"funding": [
{
@@ -4702,25 +4788,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2025-01-02T08:10:11+00:00"
},
{
"name": "symfony/process",
- "version": "v7.2.0",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
+ "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
- "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
+ "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
+ "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
"shasum": ""
},
"require": {
@@ -4752,7 +4842,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.2.0"
+ "source": "https://github.com/symfony/process/tree/v7.3.4"
},
"funding": [
{
@@ -4763,25 +4853,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-11-06T14:24:19+00:00"
+ "time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.5.1",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
- "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
"shasum": ""
},
"require": {
@@ -4799,7 +4893,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.5-dev"
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -4835,7 +4929,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -4851,20 +4945,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-25T14:20:29+00:00"
+ "time": "2025-04-25T09:37:31+00:00"
},
{
"name": "symfony/string",
- "version": "v7.2.0",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
+ "reference": "f96476035142921000338bad71e5247fbc138872"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
- "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
+ "reference": "f96476035142921000338bad71e5247fbc138872",
"shasum": ""
},
"require": {
@@ -4879,7 +4973,6 @@
},
"require-dev": {
"symfony/emoji": "^7.1",
- "symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
@@ -4922,7 +5015,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.2.0"
+ "source": "https://github.com/symfony/string/tree/v7.3.4"
},
"funding": [
{
@@ -4933,12 +5026,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-11-13T13:31:26+00:00"
+ "time": "2025-09-11T14:36:48+00:00"
},
{
"name": "theseer/tokenizer",
@@ -5054,28 +5151,28 @@
},
{
"name": "webmozart/assert",
- "version": "1.11.0",
+ "version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
- "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
+ "reference": "9be6926d8b485f55b9229203f962b51ed377ba68"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
- "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68",
+ "reference": "9be6926d8b485f55b9229203f962b51ed377ba68",
"shasum": ""
},
"require": {
"ext-ctype": "*",
+ "ext-date": "*",
+ "ext-filter": "*",
"php": "^7.2 || ^8.0"
},
- "conflict": {
- "phpstan/phpstan": "<0.12.20",
- "vimeo/psalm": "<4.6.1 || 4.6.2"
- },
- "require-dev": {
- "phpunit/phpunit": "^8.5.13"
+ "suggest": {
+ "ext-intl": "",
+ "ext-simplexml": "",
+ "ext-spl": ""
},
"type": "library",
"extra": {
@@ -5106,9 +5203,9 @@
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
- "source": "https://github.com/webmozarts/assert/tree/1.11.0"
+ "source": "https://github.com/webmozarts/assert/tree/1.12.1"
},
- "time": "2022-06-03T18:03:27+00:00"
+ "time": "2025-10-29T15:56:20+00:00"
}
],
"aliases": [],
@@ -5121,6 +5218,6 @@
"platform": {
"ext-json": "*"
},
- "platform-dev": [],
+ "platform-dev": {},
"plugin-api-version": "2.6.0"
}
From 8e437395072313822a667c88a8d8b1e0c154563c Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 10 Jul 2025 16:16:44 -0400
Subject: [PATCH 02/28] Add classes for generating TOTP and HOTP codes
---
src/Security/OTP/HOTP.php | 50 ++++++++++
src/Security/OTP/TOTP.php | 18 ++++
test/unittests/security/HOTP_Test.php | 126 ++++++++++++++++++++++++++
test/unittests/security/TOTP_Test.php | 119 ++++++++++++++++++++++++
4 files changed, 313 insertions(+)
create mode 100644 src/Security/OTP/HOTP.php
create mode 100644 src/Security/OTP/TOTP.php
create mode 100644 test/unittests/security/HOTP_Test.php
create mode 100644 test/unittests/security/TOTP_Test.php
diff --git a/src/Security/OTP/HOTP.php b/src/Security/OTP/HOTP.php
new file mode 100644
index 00000000000..918afc6b32e
--- /dev/null
+++ b/src/Security/OTP/HOTP.php
@@ -0,0 +1,50 @@
+algorithm, $counter, $this->secret);
+ }
+
+ public function getTruncatedDecimal(int $counter) : int {
+ $hash = $this->getHash($counter);
+ // sha1 is 20 bytes (40 chars as hex) but other algorithms
+ // can be longer for TOTP.
+ $hashlen = strlen($hash);
+ assert($hashlen >= 40);
+ // RFC4226 is ambiguous about whether it should be the last
+ // nibble of the hash or the last nibble of byte 20 because
+ // it only deals with SHA1 (20 bytes long). RFC6238's tests
+ // that use SHA256 and SHA512 only work with the last nibble
+ // of the hash, hash-size dependent, so we use that interpretation.
+ $offset = hexdec($hash[$hashlen-1]);
+
+ // Convert from a byte offset to an offset into the string by
+ // multiplying by 2, and then take next 4 bytes (or 8 characters
+ // when encoded as a hex string)
+ $truncated = substr($hash, $offset*2, 8);
+
+ $decimal = hexdec($truncated);
+
+ // Clear the top bit as per RFC4226
+ $nosign = $decimal & ~(1<<31);
+
+ return $nosign;
+ }
+
+ public function getCode(int $counter, int $len) : string {
+ $dec = $this->getTruncatedDecimal($counter);
+ return str_pad(strval($dec % pow(10, $len)), $len, "0", STR_PAD_LEFT);
+ }
+}
diff --git a/src/Security/OTP/TOTP.php b/src/Security/OTP/TOTP.php
new file mode 100644
index 00000000000..4cb3e68f651
--- /dev/null
+++ b/src/Security/OTP/TOTP.php
@@ -0,0 +1,18 @@
+getTimestamp();
+ $count = (int )floor($ut / $this->timestep);
+ return $count;
+ }
+}
diff --git a/test/unittests/security/HOTP_Test.php b/test/unittests/security/HOTP_Test.php
new file mode 100644
index 00000000000..1da37f40537
--- /dev/null
+++ b/test/unittests/security/HOTP_Test.php
@@ -0,0 +1,126 @@
+
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
+ * @link https://www.github.com/aces/Loris/
+ */
+namespace LORIS\Security;
+
+require_once __DIR__ . '/../../../vendor/autoload.php';
+
+use \PHPUnit\Framework\TestCase;
+use \LORIS\Security\OTP\HOTP;
+
+/**
+ * Unit test class for the CandID value object
+ *
+ * PHP Version 7
+ *
+ * @category Tests
+ * @package StudyEntities
+ * @author Xavier Lecours
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
+ * @link https://www.github.com/aces/Loris/
+ */
+class HOTP_Test extends TestCase
+{
+
+ function testGetCounter() : void
+ {
+ $validValues = [
+ // Values used by the RFC4226 tests
+ 0 => hex2bin('0000000000000000'),
+ 1 => hex2bin('0000000000000001'),
+ 2 => hex2bin('0000000000000002'),
+ 3 => hex2bin('0000000000000003'),
+ 4 => hex2bin('0000000000000004'),
+ 5 => hex2bin('0000000000000005'),
+ 6 => hex2bin('0000000000000006'),
+ 7 => hex2bin('0000000000000007'),
+ 8 => hex2bin('0000000000000008'),
+ 9 => hex2bin('0000000000000009'),
+
+ // not used by RFC4226, but test non-decimal
+ 10 => hex2bin('000000000000000a'),
+ // test padding > 1 byte should be
+ // big endian
+ 256 => hex2bin('0000000000000100')
+ ];
+ foreach($validValues as $i => $padded) {
+ $this->assertEquals(HOTP::getPaddedCounter($i), $padded);
+ }
+ }
+
+ function testRFC4226GetHash() : void
+ {
+ // Secret and algorithm for RFC4226 test suite
+ $hotp = new HOTP("12345678901234567890", "sha1");
+ $validValues = [
+ 0 => "cc93cf18508d94934c64b65d8ba7667fb7cde4b0",
+ 1 => "75a48a19d4cbe100644e8ac1397eea747a2d33ab",
+ 2 => "0bacb7fa082fef30782211938bc1c5e70416ff44",
+ 3 => "66c28227d03a2d5529262ff016a1e6ef76557ece",
+ 4 => "a904c900a64b35909874b33e61c5938a8e15ed1c",
+ 5 => "a37e783d7b7233c083d4f62926c7a25f238d0316",
+ 6 => "bc9cd28561042c83f219324d3c607256c03272ae",
+ 7 => "a4fb960c0bc06e1eabb804e5b397cdc4b45596fa",
+ 8 => "1b3c89f65e6c9e883012052823443f048b4332db",
+ 9 => "1637409809a679dc698207310c8c7fc07290d9e5"
+ ];
+ foreach($validValues as $i => $hashstr) {
+ $this->assertEquals($hotp->getHash($i), $hashstr);
+ }
+
+ }
+
+ function testRFC4226Truncation() : void
+ {
+ // Secret and algorithm for RFC4226 test suite
+ $hotp = new HOTP("12345678901234567890", "sha1");
+ $validValues = [
+ 0 => 1284755224,
+ 1 => 1094287082,
+ 2 => 137359152,
+ 3 => 1726969429,
+ 4 => 1640338314,
+ 5 => 868254676,
+ 6 => 1918287922,
+ 7 => 82162583,
+ 8 => 673399871,
+ 9 => 645520489
+ ];
+ foreach($validValues as $i => $decval) {
+ $this->assertEquals($hotp->getTruncatedDecimal($i), $decval);
+ }
+
+ }
+
+ function testRFC4226Code() : void
+ {
+ // Secret and algorithm for RFC4226 test suite
+ $hotp = new HOTP("12345678901234567890", "sha1");
+ $validValues = [
+ 0 => "755224",
+ 1 => "287082",
+ 2 => "359152",
+ 3 => "969429",
+ 4 => "338314",
+ 5 => "254676",
+ 6 => "287922",
+ 7 => "162583",
+ 8 => "399871",
+ 9 => "520489"
+ ];
+ foreach($validValues as $i => $code) {
+ $this->assertEquals($hotp->getCode($i, 6), $code);
+ }
+
+ }
+}
diff --git a/test/unittests/security/TOTP_Test.php b/test/unittests/security/TOTP_Test.php
new file mode 100644
index 00000000000..f491daba32c
--- /dev/null
+++ b/test/unittests/security/TOTP_Test.php
@@ -0,0 +1,119 @@
+
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
+ * @link https://www.github.com/aces/Loris/
+ */
+namespace LORIS\Security;
+
+require_once __DIR__ . '/../../../vendor/autoload.php';
+
+use \PHPUnit\Framework\TestCase;
+use \LORIS\Security\OTP\TOTP;
+
+/**
+ * Unit test class for the CandID value object
+ *
+ * PHP Version 7
+ *
+ * @category Tests
+ * @package StudyEntities
+ * @author Xavier Lecours
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
+ * @link https://www.github.com/aces/Loris/
+ */
+class TOTP_Test extends TestCase
+{
+
+ function testRFC6238Counters() : void
+ {
+ $totp = new TOTP("abc", timestep: 30);
+ // Unix time => RFC6238 time based counter for HOTP
+ $validValues = [
+ 59 => hexdec("0000000000000001"),
+ 1111111109 => hexdec("00000000023523EC"),
+ 1111111111 => hexdec("00000000023523ED"),
+ 1234567890 => hexdec("000000000273EF07"),
+ 2000000000 => hexdec("0000000003F940AA"),
+ 20000000000 => hexdec("0000000027BC86AA"),
+ ];
+ foreach($validValues as $i => $counter) {
+ $this->assertEquals($totp->getTimeCounter(\DateTimeImmutable::createFromFormat("U", strval($i))), $counter);
+ }
+ }
+
+ public function testRFC6238Sha1Codes() : void {
+
+ $totp = new TOTP("12345678901234567890", timestep: 30, algorithm: 'sha1');
+ $validValues = [
+ 59 => "94287082",
+ 1111111109 => "07081804",
+ 1111111111 => "14050471",
+ 1234567890 => "89005924",
+ 2000000000 => "69279037",
+ 20000000000 => "65353130"
+ ];
+
+ foreach($validValues as $i => $code) {
+ $counter = $totp->getTimeCounter(\DateTimeImmutable::createFromFormat("U", strval($i)));
+ var_dump($counter);
+ $this->assertEquals($totp->getCode($counter, 8), $code);
+ }
+ }
+
+ public function testRFC6238Sha256Codes() : void {
+ // RFC6238's test values in Appendix B say the shared secret is
+ // 12345678901234567890, but the reference implementation in
+ // Appendix A uses different hash-size dependent shared keys. The
+ // test vectors only work when we use the secrets from the reference
+ // implementation.
+ $totp = new TOTP("12345678901234567890123456789012", timestep: 30, algorithm: 'sha256');
+ $validValues = [
+ 59 => "46119246",
+ 1111111109 => "68084774",
+ 1111111111 => "67062674",
+ 1234567890 => "91819424",
+ 2000000000 => "90698825",
+ 20000000000 => "77737706"
+ ];
+
+ foreach($validValues as $i => $code) {
+ $counter = $totp->getTimeCounter(\DateTimeImmutable::createFromFormat("U", strval($i)));
+ var_dump($counter);
+ $this->assertEquals($totp->getCode($counter, 8), $code);
+ }
+ }
+
+ public function testRFC6238Sha512Codes() : void {
+ // RFC6238's test values in Appendix B say the shared secret is
+ // 12345678901234567890, but the reference implementation in
+ // Appendix A uses different hash-size dependent shared keys. The
+ // test vectors only work when we use the secrets from the reference
+ // implementation.
+ $totp = new TOTP("12345678901234567890".
+ "12345678901234567890"
+ ."123456789012345678901234"
+ , timestep: 30, algorithm: 'sha512');
+ $validValues = [
+ 59 => "90693936",
+ 1111111109 => "25091201",
+ 1111111111 => "99943326",
+ 1234567890 => "93441116",
+ 2000000000 => "38618901",
+ 20000000000 => "47863826"
+ ];
+
+ foreach($validValues as $i => $code) {
+ $counter = $totp->getTimeCounter(\DateTimeImmutable::createFromFormat("U", strval($i)));
+ var_dump($counter);
+ $this->assertEquals($totp->getCode($counter, 8), $code);
+ }
+ }
+}
From afb47fa3e94842ad06a9898ad4630d0015949277 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 24 Jul 2025 08:42:06 -0400
Subject: [PATCH 03/28] WIP -- add middleware to require MFA token
---
htdocs/index.php | 1 +
.../php/my_preferences.class.inc | 3 +
.../templates/form_my_preferences.tpl | 7 ++
php/libraries/NDB_Client.class.inc | 3 +
php/libraries/SinglePointLogin.class.inc | 9 +++
php/libraries/UserPermissions.class.inc | 4 ++
src/Middleware/MFA.php | 57 +++++++++++++++
test/phpunit.xml | 71 +++++++++++++++++++
8 files changed, 155 insertions(+)
create mode 100644 src/Middleware/MFA.php
diff --git a/htdocs/index.php b/htdocs/index.php
index a11bd96d962..a0b1fa8d3ab 100644
--- a/htdocs/index.php
+++ b/htdocs/index.php
@@ -77,6 +77,7 @@ function array_find(array $array, callable $callback)
->withMiddleware(new \LORIS\Middleware\ContentLength())
->withMiddleware(new \LORIS\Middleware\AWS())
->withMiddleware(new \LORIS\Middleware\ContentSecurityPolicy())
+ ->withMiddleware(new \LORIS\Middleware\MFA())
->withMiddleware(new \LORIS\Middleware\ResponseGenerator());
$serverrequest = \Laminas\Diactoros\ServerRequestFactory::fromGlobals();
diff --git a/modules/my_preferences/php/my_preferences.class.inc b/modules/my_preferences/php/my_preferences.class.inc
index f1ac6509250..21981788431 100644
--- a/modules/my_preferences/php/my_preferences.class.inc
+++ b/modules/my_preferences/php/my_preferences.class.inc
@@ -384,6 +384,9 @@ class My_Preferences extends \NDB_Form
unset($nGroup);
}
}
+ $this->tpl_data['mfa_secret'] = $user->getMFASecret();
+ $config = \NDB_Factory::singleton()->config();
+ $this->tpl_data['study_title'] = $config->getSetting("title");
$this->tpl_data['notification_rows'] = $notification_rows;
//------------------------------------------------------------
diff --git a/modules/my_preferences/templates/form_my_preferences.tpl b/modules/my_preferences/templates/form_my_preferences.tpl
index 437bf525ba9..ff9c2de6b31 100644
--- a/modules/my_preferences/templates/form_my_preferences.tpl
+++ b/modules/my_preferences/templates/form_my_preferences.tpl
@@ -63,6 +63,13 @@
+ {if $mfa_secret == ""}
+
+ Enable MFA
+
+ {else}
+otpauth://totp/{urlencode($study_title)}:{urlencode($form.UserID.html)}?secret={$mfa_secret}&period=30&digits=6&issuer={urlencode($study_title)}
+ {/if}
- {if $mfa_secret == ""}
+
- {else}
-otpauth://totp/{urlencode($study_title)}:{urlencode($form.UserID.html)}?secret={$mfa_secret}&period=30&digits=6&issuer={urlencode($study_title)}
- {/if}
+
+ );
}
/**
*
diff --git a/modules/my_preferences/php/mfa.class.inc b/modules/my_preferences/php/mfa.class.inc
index 0690274f59f..5707e441ba5 100644
--- a/modules/my_preferences/php/mfa.class.inc
+++ b/modules/my_preferences/php/mfa.class.inc
@@ -94,7 +94,7 @@ class MFA extends \NDB_Page
$wantCode = $validator->getCode($counter, 6);
if ($wantCode !== strval($values['code'])) {
return new \LORIS\Http\Response\JSON\BadRequest(
- 'Code does not match expected value'
+ 'Invalid code provided. MFA not registered.'
);
}
$db = $this->loris->getDatabaseConnection();
From d1c3b8a419d4f0078bb5164988ce02860e5d6d3c Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Tue, 26 Aug 2025 14:07:20 -0400
Subject: [PATCH 13/28] Fix space between words
---
modules/my_preferences/jsx/mfa.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index c9c3424355b..1e2e66ed240 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -86,8 +86,8 @@ function MFAIndex(): React.ReactElement {
Scan the following QR code below in your MFA authenticator and
enter the code to validate.
- Note that this will overwrite
- any previously setup MFA in LORIS!
+ Note that this will overwrite any previously
+ setup MFA in LORIS!
Can't scan the QR code?
From 7ba70f44942d0de3584932ae14de67783ab0b515 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Tue, 26 Aug 2025 14:15:21 -0400
Subject: [PATCH 14/28] Fix comments in tests
---
test/unittests/security/HOTP_Test.php | 22 +++-------------------
test/unittests/security/TOTP_Test.php | 25 +++++--------------------
2 files changed, 8 insertions(+), 39 deletions(-)
diff --git a/test/unittests/security/HOTP_Test.php b/test/unittests/security/HOTP_Test.php
index dd2234c1d27..505668af1ba 100644
--- a/test/unittests/security/HOTP_Test.php
+++ b/test/unittests/security/HOTP_Test.php
@@ -1,16 +1,5 @@
- * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
- * @link https://www.github.com/aces/Loris/
- */
namespace LORIS\Security;
require_once __DIR__ . '/../../../vendor/autoload.php';
@@ -19,15 +8,10 @@
use \LORIS\Security\OTP\HOTP;
/**
- * Unit test class for the CandID value object
- *
- * PHP Version 7
+ * Group of tests for HMAC-based One Time Passwords (HOTP)
+ * primarily based on RFC4226 test vectors.
*
- * @category Tests
- * @package StudyEntities
- * @author Xavier Lecours
- * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
- * @link https://www.github.com/aces/Loris/
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
*/
class HOTP_Test extends TestCase
{
diff --git a/test/unittests/security/TOTP_Test.php b/test/unittests/security/TOTP_Test.php
index 77cd0ffe58d..569df9f3a28 100644
--- a/test/unittests/security/TOTP_Test.php
+++ b/test/unittests/security/TOTP_Test.php
@@ -1,16 +1,5 @@
- * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
- * @link https://www.github.com/aces/Loris/
- */
namespace LORIS\Security;
require_once __DIR__ . '/../../../vendor/autoload.php';
@@ -19,15 +8,10 @@
use \LORIS\Security\OTP\TOTP;
/**
- * Unit test class for the CandID value object
- *
- * PHP Version 7
+ * Unit test class for Time-based One Time Passwords (TOTP)
+ * primarily based on RFC6238
*
- * @category Tests
- * @package StudyEntities
- * @author Xavier Lecours
- * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
- * @link https://www.github.com/aces/Loris/
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
*/
class TOTP_Test extends TestCase
{
@@ -40,7 +24,8 @@ class TOTP_Test extends TestCase
function testRFC6238Counters() : void
{
$totp = new TOTP("abc", timestep: 30);
- // Unix time => RFC6238 time based counter for HOTP
+ // Unix time => RFC6238 time based counter to pass to HOTP
+ // algorithm.
$validValues = [
59 => hexdec("0000000000000001"),
1111111109 => hexdec("00000000023523EC"),
From d258537f04d4590293f9cef8038ae26724ffc720 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Wed, 27 Aug 2025 12:20:35 -0400
Subject: [PATCH 15/28] Add jefferson's suggestions
Co-authored-by: jeffersoncasimir <15801528+jeffersoncasimir@users.noreply.github.com>
---
jsx/MFAPrompt.tsx | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/jsx/MFAPrompt.tsx b/jsx/MFAPrompt.tsx
index 3c62a2dd357..03d94696fa7 100644
--- a/jsx/MFAPrompt.tsx
+++ b/jsx/MFAPrompt.tsx
@@ -65,18 +65,20 @@ function MFAPrompt(props: {validate:
const digitCallback = useCallback(
(index: number, value: number): boolean => {
if (value >= 0 && value <= 9) {
- code[index] = value;
- setCode([...code]);
- return true;
+ setCode(prev => {
+ const newCode = [...prev];
+ newCode[index] = value;
+ return newCode;
+ });
}
return false;
},
- [code, setCode]
+ []
);
const errorCallback = useCallback( (msg: string) => {
swal.fire('Error', msg, 'error');
setCode([null, null, null, null, null, null]);
- }, [setCode]);
+ }, []);
useEffect( () => {
if (code.indexOf(null) >= 0) {
return;
From bf65e9e4f34e871ece9532bf1080920b52dad2b8 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Wed, 27 Aug 2025 12:28:34 -0400
Subject: [PATCH 16/28] fix compile and explicitly name acronym in link
---
jsx/MFAPrompt.tsx | 22 ++++++++++---------
modules/my_preferences/jsx/mfa.tsx | 4 ++--
.../templates/form_my_preferences.tpl | 2 +-
3 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/jsx/MFAPrompt.tsx b/jsx/MFAPrompt.tsx
index 03d94696fa7..f46f3d24dc2 100644
--- a/jsx/MFAPrompt.tsx
+++ b/jsx/MFAPrompt.tsx
@@ -43,9 +43,17 @@ function Digit(props: {
}
type errorCallback = (msg: string) => void;
+type MFACode = [
+ number|null,
+ number|null,
+ number|null,
+ number|null,
+ number|null,
+ number|null];
+
/**
- * Prompt for a multi-factor authentication code and call onValidate
- * after a valid code has been entered.
+ * Prompt for a multi-factor authentication code and call validate
+ * callback to validate the code after all 6 digits have been entered.
*
* @param props - React props
* @param props.validate - Callback when a code is entered to validate it.
@@ -55,18 +63,12 @@ type errorCallback = (msg: string) => void;
function MFAPrompt(props: {validate:
(code: string, onError: errorCallback) => void
}) {
- const [code, setCode] = useState<[
- number|null,
- number|null,
- number|null,
- number|null,
- number|null,
- number|null]>([null, null, null, null, null, null]);
+ const [code, setCode] = useState([null, null, null, null, null, null]);
const digitCallback = useCallback(
(index: number, value: number): boolean => {
if (value >= 0 && value <= 9) {
setCode(prev => {
- const newCode = [...prev];
+ const newCode: MFACode = [...prev];
newCode[index] = value;
return newCode;
});
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index 1e2e66ed240..0586375930b 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -90,8 +90,8 @@ function MFAIndex(): React.ReactElement {
setup MFA in LORIS!
- Can't scan the QR code?
- setShowModal(true)}>Setup manually.
+
Can't scan the QR code? setShowModal(true)}>
+ Setup manually.
;
diff --git a/modules/my_preferences/templates/form_my_preferences.tpl b/modules/my_preferences/templates/form_my_preferences.tpl
index ff2479e9b59..b93e8c8568d 100644
--- a/modules/my_preferences/templates/form_my_preferences.tpl
+++ b/modules/my_preferences/templates/form_my_preferences.tpl
@@ -70,7 +70,7 @@
in a different form element with a smarty template and it's easier
to create a new "fresh" page with modern react/etc than rewrite
the whole page or do a hybrid here *}
- Configure MFA
+ Configure multi-factor authentication (MFA)
From a6829e9cb6c860aa8a31383fd569339ba36d3fdc Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Wed, 27 Aug 2025 13:05:44 -0400
Subject: [PATCH 17/28] Fix manual code input
---
jsx/MFAPrompt.tsx | 15 +++++++++------
modules/my_preferences/jsx/mfa.tsx | 2 +-
test/unittests/security/TOTP_Test.php | 4 ++--
3 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/jsx/MFAPrompt.tsx b/jsx/MFAPrompt.tsx
index f46f3d24dc2..1d72f216df9 100644
--- a/jsx/MFAPrompt.tsx
+++ b/jsx/MFAPrompt.tsx
@@ -63,15 +63,18 @@ type MFACode = [
function MFAPrompt(props: {validate:
(code: string, onError: errorCallback) => void
}) {
- const [code, setCode] = useState([null, null, null, null, null, null]);
+ const [code, setCode] = useState(
+ [null, null, null, null, null, null]
+ );
const digitCallback = useCallback(
(index: number, value: number): boolean => {
if (value >= 0 && value <= 9) {
- setCode(prev => {
- const newCode: MFACode = [...prev];
- newCode[index] = value;
- return newCode;
- });
+ setCode((prev) => {
+ const newCode: MFACode = [...prev];
+ newCode[index] = value;
+ return newCode;
+ });
+ return true;
}
return false;
},
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index 0586375930b..3217b62290d 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -68,8 +68,8 @@ function CodeValidator(props: {
*/
function MFAIndex(): React.ReactElement {
const [showModal, setShowModal] = useState(false);
+ const [key] = useState(genPotentialSecret());
const studyTitle = loris.config('studyTitle');
- const key = genPotentialSecret();
const mfaUrl = 'otpauth://totp/'
+ encodeURI(studyTitle)
+ ':' + encodeURI(loris.user.username)
diff --git a/test/unittests/security/TOTP_Test.php b/test/unittests/security/TOTP_Test.php
index 569df9f3a28..78a4fb348eb 100644
--- a/test/unittests/security/TOTP_Test.php
+++ b/test/unittests/security/TOTP_Test.php
@@ -24,8 +24,8 @@ class TOTP_Test extends TestCase
function testRFC6238Counters() : void
{
$totp = new TOTP("abc", timestep: 30);
- // Unix time => RFC6238 time based counter to pass to HOTP
- // algorithm.
+ // Unix time => RFC6238 time based counter to pass to HOTP
+ // algorithm.
$validValues = [
59 => hexdec("0000000000000001"),
1111111109 => hexdec("00000000023523EC"),
From b1fe0c38f7c636026030b907dfb6a585634a3daa Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Wed, 27 Aug 2025 13:16:03 -0400
Subject: [PATCH 18/28] Fix breadcrumb links
---
modules/my_preferences/php/mfa.class.inc | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/modules/my_preferences/php/mfa.class.inc b/modules/my_preferences/php/mfa.class.inc
index 5707e441ba5..bb308f7cab6 100644
--- a/modules/my_preferences/php/mfa.class.inc
+++ b/modules/my_preferences/php/mfa.class.inc
@@ -23,10 +23,12 @@ class MFA extends \NDB_Page
{
return new \LORIS\BreadcrumbTrail(
new \LORIS\Breadcrumb(
- dgettext("loris", "My Preferences")
+ dgettext("loris", "My Preferences"),
+ '/my_preferences',
),
new \LORIS\Breadcrumb(
- dgettext("my_preferences", "Configure 2FA")
+ dgettext("my_preferences", "Configure MFA"),
+ '/my_preferences/mfa',
),
);
}
From d3f1d1f747b738a195010bb0033f08e817ff1a39 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 08:23:13 -0400
Subject: [PATCH 19/28] Restore phpunit version after rebase
---
composer.json | 2 +-
composer.lock | 56 ++++++++++++++++----------------------
test/phpunit.xml | 71 ------------------------------------------------
3 files changed, 25 insertions(+), 104 deletions(-)
diff --git a/composer.json b/composer.json
index c9b0c618120..5ea57eca61d 100644
--- a/composer.json
+++ b/composer.json
@@ -21,7 +21,7 @@
},
"require-dev" : {
"squizlabs/php_codesniffer" : "^3.5",
- "phpunit/phpunit" : "9.4.4",
+ "phpunit/phpunit" : "12.0",
"phan/phan": "^5.0",
"phpstan/phpstan": "^1.4",
"slevomat/coding-standard": "^6.4",
diff --git a/composer.lock b/composer.lock
index 6619c420804..7d9c3a679f4 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": "1ef012a82c44beec9e9fe4941f1690d5",
+ "content-hash": "a588887142a42bc949d04d77c3540042",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -215,16 +215,16 @@
},
{
"name": "chillerlan/php-qrcode",
- "version": "5.0.3",
+ "version": "5.0.4",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git",
- "reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2"
+ "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/42e215640e9ebdd857570c9e4e52245d1ee51de2",
- "reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2",
+ "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/390393e97a6e42ccae0e0d6205b8d4200f7ddc43",
+ "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43",
"shasum": ""
},
"require": {
@@ -235,13 +235,13 @@
"require-dev": {
"chillerlan/php-authenticator": "^4.3.1 || ^5.2.1",
"ext-fileinfo": "*",
- "phan/phan": "^5.4.5",
+ "phan/phan": "^5.5.1",
"phpcompatibility/php-compatibility": "10.x-dev",
"phpmd/phpmd": "^2.15",
"phpunit/phpunit": "^9.6",
"setasign/fpdf": "^1.8.2",
- "slevomat/coding-standard": "^8.15",
- "squizlabs/php_codesniffer": "^3.11"
+ "slevomat/coding-standard": "^8.23.0",
+ "squizlabs/php_codesniffer": "^4.0.0"
},
"suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
@@ -304,7 +304,7 @@
"type": "Ko-Fi"
}
],
- "time": "2024-11-21T16:12:34+00:00"
+ "time": "2025-09-19T17:30:27+00:00"
},
{
"name": "chillerlan/php-settings-container",
@@ -3258,16 +3258,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.4.2",
+ "version": "12.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea"
+ "reference": "9912c83b5207ab3730fcadc42992e95bdb02dad8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a94ea4d26d865875803b23aaf78c3c2c670ea2ea",
- "reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9912c83b5207ab3730fcadc42992e95bdb02dad8",
+ "reference": "9912c83b5207ab3730fcadc42992e95bdb02dad8",
"shasum": ""
},
"require": {
@@ -3277,23 +3277,23 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.13.4",
+ "myclabs/deep-copy": "^1.12.1",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
- "phpunit/php-code-coverage": "^12.4.0",
+ "phpunit/php-code-coverage": "^12.0.0",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
"phpunit/php-timer": "^8.0.0",
- "sebastian/cli-parser": "^4.2.0",
- "sebastian/comparator": "^7.1.3",
+ "sebastian/cli-parser": "^4.0.0",
+ "sebastian/comparator": "^7.0.0",
"sebastian/diff": "^7.0.0",
- "sebastian/environment": "^8.0.3",
- "sebastian/exporter": "^7.0.2",
- "sebastian/global-state": "^8.0.2",
+ "sebastian/environment": "^8.0.0",
+ "sebastian/exporter": "^7.0.0",
+ "sebastian/global-state": "^8.0.0",
"sebastian/object-enumerator": "^7.0.0",
- "sebastian/type": "^6.0.3",
+ "sebastian/type": "^6.0.0",
"sebastian/version": "^6.0.0",
"staabm/side-effects-detector": "^1.0.5"
},
@@ -3303,7 +3303,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "12.4-dev"
+ "dev-main": "12.0-dev"
}
},
"autoload": {
@@ -3335,7 +3335,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.2"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.0.0"
},
"funding": [
{
@@ -3346,20 +3346,12 @@
"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": "2025-10-30T08:41:39+00:00"
+ "time": "2025-02-07T05:03:33+00:00"
},
{
"name": "psr/container",
diff --git a/test/phpunit.xml b/test/phpunit.xml
index a020c21bfba..782d7bd6967 100644
--- a/test/phpunit.xml
+++ b/test/phpunit.xml
@@ -57,77 +57,6 @@
From 937d9f8fc9e55d6a7525e184b98591d0f076f71f Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 09:22:37 -0400
Subject: [PATCH 20/28] Add translatons after rebase
---
Makefile | 7 +-
jsx/DataTable.d.ts | 1 +
jsx/I18nSetup.d.ts | 2 +
locale/ja/LC_MESSAGES/loris.po | 67 +++++++++++++++++++
modules/my_preferences/jsx/mfa.tsx | 11 +--
.../locale/hi/LC_MESSAGES/my_preferences.po | 5 +-
.../my_preferences/locale/my_preferences.pot | 9 ++-
.../templates/form_my_preferences.tpl | 2 +-
8 files changed, 91 insertions(+), 13 deletions(-)
create mode 100644 jsx/I18nSetup.d.ts
diff --git a/Makefile b/Makefile
index 00ad0f538ca..a3bd480fd37 100755
--- a/Makefile
+++ b/Makefile
@@ -135,6 +135,9 @@ locales:
msgfmt -o modules/module_manager/locale/ja/LC_MESSAGES/module_manager.mo modules/module_manager/locale/ja/LC_MESSAGES/module_manager.po
msgfmt -o modules/mri_violations/locale/ja/LC_MESSAGES/mri_violations.mo modules/mri_violations/locale/ja/LC_MESSAGES/mri_violations.po
msgfmt -o modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.mo modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.po
+ npx i18next-conv -l ja -s modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po -t modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.json --compatibilityJSON v4
+ npx i18next-conv -l hi -s modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.po -t modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.json --compatibilityJSON v4
+ msgfmt -o modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.mo modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
msgfmt -o modules/next_stage/locale/ja/LC_MESSAGES/next_stage.mo modules/next_stage/locale/ja/LC_MESSAGES/next_stage.po
msgfmt -o modules/next_stage/locale/es/LC_MESSAGES/next_stage.mo modules/next_stage/locale/es/LC_MESSAGES/next_stage.po
msgfmt -o modules/oidc/locale/ja/LC_MESSAGES/oidc.mo modules/oidc/locale/ja/LC_MESSAGES/oidc.po
@@ -199,5 +202,7 @@ server_processes_manager: modules/server_processes_manager/locale/ja/LC_MESSAGES
conflict_resolver:
target=conflict_resolver npm run compile
-my_preferences:
+my_preferences: modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.mo modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.mo
+ npx i18next-conv -l ja -s modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po -t modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.json --compatibilityJSON v4
+ npx i18next-conv -l hi -s modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.po -t modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.json --compatibilityJSON v4
target=my_preferences npm run compile
diff --git a/jsx/DataTable.d.ts b/jsx/DataTable.d.ts
index 82c89b7b4e4..53caa9c563b 100644
--- a/jsx/DataTable.d.ts
+++ b/jsx/DataTable.d.ts
@@ -1,3 +1,4 @@
+
import {ReactNode} from 'react';
type TableRow = (string|null)[]
diff --git a/jsx/I18nSetup.d.ts b/jsx/I18nSetup.d.ts
new file mode 100644
index 00000000000..0b845a98587
--- /dev/null
+++ b/jsx/I18nSetup.d.ts
@@ -0,0 +1,2 @@
+import i18n from 'i18next';
+export default i18n;
diff --git a/locale/ja/LC_MESSAGES/loris.po b/locale/ja/LC_MESSAGES/loris.po
index 7c299035cd5..6cc0c718427 100644
--- a/locale/ja/LC_MESSAGES/loris.po
+++ b/locale/ja/LC_MESSAGES/loris.po
@@ -241,6 +241,12 @@ msgstr "言語"
msgid "Ethnicity"
msgstr "民族"
+msgid "Save"
+msgstr "保存"
+
+msgid "Reset"
+msgstr "リセット"
+
# Data table strings
msgid "{{pageCount}} rows displayed of {{totalCount}}."
msgstr "{{totalCount}}行中{{pageCount}}行を表示"
@@ -255,6 +261,22 @@ msgstr "データをCSVとしてダウンロード"
msgid "Views"
msgstr "ビュー"
+#: php/libraries/Password.class.inc
+msgid "The password is too short"
+msgstr "パスワードが短すぎます"
+
+msgid "The password is not complex enough."
+msgstr "パスワードが十分に複雑ではありません。"
+
+msgid "This password is known to be exposed in online data breaches."
+msgstr "このパスワードは、オンラインデータ侵害で漏洩されることが知られています。"
+
+msgid "Data Supervisors to Email"
+msgstr "データ管理者に電子メールで連絡"
+
+msgid "Invalid email address"
+msgstr "無効なメールアドレス"
+
# Common strings on widgets
msgid "NEW"
msgstr "新しい"
@@ -355,3 +377,48 @@ msgstr "{{years}}歳"
msgid "Loading..."
msgstr "読み込み中..."
+# User Account related
+msgid "Password Rules"
+msgstr "パスワードルール"
+
+msgid "Username"
+msgstr "ユーザー名"
+
+msgid "First name"
+msgstr "ファーストネーム"
+
+msgid "First name is required and should not exceed 120 characters"
+msgstr "名は必須で、120文字以内で入力してください。"
+
+msgid "Last name"
+msgstr "苗字"
+
+msgid "Last name is required and should not exceed 120 characters"
+msgstr "姓は必須で、120文字以内で入力してください。"
+
+msgid "Email address"
+msgstr "電子メールアドレス"
+
+msgid "New Password"
+msgstr "新しいパスワード"
+
+msgid "Confirm Password"
+msgstr "パスワードを認証する"
+
+msgid "Email address is required"
+msgstr "メールアドレスは必須です"
+
+msgid "The password must be at least 8 characters long."
+msgstr "パスワードは8文字以上である必要があります"
+
+msgid "The password cannot be your username or email address."
+msgstr "パスワードにはユーザー名やメールアドレスは使用できません。"
+
+msgid "Please choose a unique password."
+msgstr "固有のパスワードを選択してください。"
+
+msgid "No special characters are required but your password must be sufficiently complex to be accepted."
+msgstr "特殊文字は必要ありませんが、パスワードは受け入れられるほど複雑である必要があります。"
+
+msgid "We suggest using a password manager to generate one for you."
+msgstr "パスワード マネージャーを使用してパスワードを生成することをお勧めします。"
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index 3217b62290d..d7ae63f5c9c 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -5,6 +5,8 @@ import QRCode from 'react-qr-code';
import * as base32 from 'hi-base32';
import Modal from 'Modal';
import MFAPrompt from 'jsx/MFAPrompt';
+import {withTranslation} from 'react-i18next';
+import i18n from 'I18nSetup';
declare const loris: any;
@@ -98,16 +100,17 @@ function MFAIndex(): React.ReactElement {
}
window.addEventListener('load', () => {
- /*
- const MFAIndex = withTranslation(
+ i18n.addResourceBundle('ja', 'my_preferences', require("../locale/ja/LC_MESSAGES/my_preferences.json"));
+ i18n.addResourceBundle('hi', 'my_preferences', require("../locale/ja/LC_MESSAGES/my_preferences.json"));
+ const TranslatedMFAIndex = withTranslation(
['my_preferences', 'loris']
)(MFAIndex);
- */
+
const element = document.getElementById('lorisworkspace');
if (!element) {
throw new Error('Missing lorisworkspace');
}
createRoot(element).render(
-
+
);
});
diff --git a/modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.po b/modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.po
index 8b54649b398..d686640a1d7 100644
--- a/modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.po
+++ b/modules/my_preferences/locale/hi/LC_MESSAGES/my_preferences.po
@@ -18,9 +18,6 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-msgid "My Preferences"
-msgstr "मेरी प्राथमिकताएँ"
-
msgid "Edit My Information"
msgstr "मेरी जानकारी संपादित करें"
@@ -52,4 +49,4 @@ msgid "The passwords do not match"
msgstr "पासवर्ड मेल नहीं खाते।"
msgid "New and old passwords are identical"
-msgstr "नया और पुराना पासवर्ड समान हैं।"
\ No newline at end of file
+msgstr "नया और पुराना पासवर्ड समान हैं।"
diff --git a/modules/my_preferences/locale/my_preferences.pot b/modules/my_preferences/locale/my_preferences.pot
index b83fd0bb954..df3675532a0 100644
--- a/modules/my_preferences/locale/my_preferences.pot
+++ b/modules/my_preferences/locale/my_preferences.pot
@@ -18,9 +18,6 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-msgid "My Preferences"
-msgstr ""
-
msgid "Edit My Information"
msgstr ""
@@ -53,3 +50,9 @@ msgstr ""
msgid "New and old passwords are identical"
msgstr ""
+
+msgid "Configure multi-factor authentication (MFA)"
+msgstr ""
+
+msgid "Configure MFA"
+msgstr ""
diff --git a/modules/my_preferences/templates/form_my_preferences.tpl b/modules/my_preferences/templates/form_my_preferences.tpl
index b93e8c8568d..a8146d1b35d 100644
--- a/modules/my_preferences/templates/form_my_preferences.tpl
+++ b/modules/my_preferences/templates/form_my_preferences.tpl
@@ -70,7 +70,7 @@
in a different form element with a smarty template and it's easier
to create a new "fresh" page with modern react/etc than rewrite
the whole page or do a hybrid here *}
- Configure multi-factor authentication (MFA)
+ {dgettext("my_preferences", "Configure multi-factor authentication (MFA)")}
From a9921c0d9b4667bec0778aa77aa28a7212715e85 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 10:14:34 -0400
Subject: [PATCH 21/28] Add missing file
---
modules/my_preferences/locale/my_preferences.pot | 3 +++
1 file changed, 3 insertions(+)
diff --git a/modules/my_preferences/locale/my_preferences.pot b/modules/my_preferences/locale/my_preferences.pot
index df3675532a0..21c846b80c1 100644
--- a/modules/my_preferences/locale/my_preferences.pot
+++ b/modules/my_preferences/locale/my_preferences.pot
@@ -56,3 +56,6 @@ msgstr ""
msgid "Configure MFA"
msgstr ""
+
+msgid "Use the following key in your authenticator app: {code} "
+msgstr ""
From 08728fcdf019505d82eb27371153e873faba92b3 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 10:19:18 -0400
Subject: [PATCH 22/28] WIP
---
modules/my_preferences/jsx/mfa.tsx | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index d7ae63f5c9c..945c961664d 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -5,7 +5,7 @@ import QRCode from 'react-qr-code';
import * as base32 from 'hi-base32';
import Modal from 'Modal';
import MFAPrompt from 'jsx/MFAPrompt';
-import {withTranslation} from 'react-i18next';
+import {useTranslation, Trans} from 'react-i18next';
import i18n from 'I18nSetup';
declare const loris: any;
@@ -71,6 +71,7 @@ function CodeValidator(props: {
function MFAIndex(): React.ReactElement {
const [showModal, setShowModal] = useState(false);
const [key] = useState(genPotentialSecret());
+ const {t} = useTranslation();
const studyTitle = loris.config('studyTitle');
const mfaUrl = 'otpauth://totp/'
+ encodeURI(studyTitle)
@@ -83,7 +84,10 @@ function MFAIndex(): React.ReactElement {
onClose={() => setShowModal(false)}
show={showModal}
throwWarning={false}>
- Use the following key in your authenticator app: {key}
+
+
Scan the following QR code below in your MFA authenticator and
enter the code to validate.
@@ -102,15 +106,12 @@ function MFAIndex(): React.ReactElement {
window.addEventListener('load', () => {
i18n.addResourceBundle('ja', 'my_preferences', require("../locale/ja/LC_MESSAGES/my_preferences.json"));
i18n.addResourceBundle('hi', 'my_preferences', require("../locale/ja/LC_MESSAGES/my_preferences.json"));
- const TranslatedMFAIndex = withTranslation(
- ['my_preferences', 'loris']
- )(MFAIndex);
const element = document.getElementById('lorisworkspace');
if (!element) {
throw new Error('Missing lorisworkspace');
}
createRoot(element).render(
-
+
);
});
From a39c060f3ef7045378d6b3104e1d680f04d90478 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 10:24:02 -0400
Subject: [PATCH 23/28] add mising file
---
.../locale/ja/LC_MESSAGES/my_preferences.po | 62 +++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
diff --git a/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po b/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
new file mode 100644
index 00000000000..0cb4fab45ba
--- /dev/null
+++ b/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
@@ -0,0 +1,62 @@
+# Default LORIS strings to be translated (English).
+# Copy this to a language specific file and add translations to the
+# new file.
+# Copyright (C) 2025
+# This file is distributed under the same license as the LORIS package.
+# Dave MacFarlane , 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: LORIS 27\n"
+"Report-Msgid-Bugs-To: https://github.com/aces/Loris/issues\n"
+"POT-Creation-Date: 2025-04-08 14:37-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid "Edit My Information"
+msgstr "私の情報を編集する"
+
+msgid "Notifications"
+msgstr "通知"
+
+msgid "Your email address must be less than 255 characters long"
+msgstr "メールアドレスは255文字未満でなければなりません"
+
+msgid "Language preference"
+msgstr "言語設定"
+
+msgid "Operation"
+msgstr "作用"
+
+msgid "Description"
+msgstr "説明"
+
+msgid "The email address already exists"
+msgstr "メールアドレスは既に存在します"
+
+msgid "Your password cannot be your email"
+msgstr "パスワードにメールアドレスは使用できません"
+
+msgid "Your password cannot be your username"
+msgstr "パスワードをユーザー名と同じにすることはできません。"
+
+msgid "The passwords do not match"
+msgstr "パスワードが一致しません"
+
+msgid "New and old passwords are identical"
+msgstr "新しいパスワードと古いパスワードは同一です"
+
+msgid "Configure multi-factor authentication (MFA)"
+msgstr "多要素認証を設定する"
+
+msgid "Configure MFA"
+msgstr "多要素認証を設定する"
+
+msgid "Use the following key in your authenticator app: {{code}} "
+msgstr "認証アプリで次のコードを使用してください: {{code}} "
From 559675c7f713c97a32445f819ab1293d9a9342e0 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 11:13:36 -0400
Subject: [PATCH 24/28] WIP
---
modules/my_preferences/jsx/mfa.tsx | 14 +++++++-------
.../locale/ja/LC_MESSAGES/my_preferences.po | 10 ++++++++--
2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index 945c961664d..52cc1b4e880 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -80,17 +80,17 @@ function MFAIndex(): React.ReactElement {
+ '&period=30&digits=6&issuer=' + encodeURI(studyTitle);
return
setShowModal(false)}
show={showModal}
throwWarning={false}>
-
-
+ CODE]}
+ values={{code: key}} />
-
Scan the following QR code below in your MFA authenticator and
- enter the code to validate.
+
{t('Scan the following QR code below in your MFA authenticator and enter the code to validate.', {ns: 'my_preferences'})}
Note that this will overwrite any previously
setup MFA in LORIS!
diff --git a/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po b/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
index 0cb4fab45ba..9512897c619 100644
--- a/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
+++ b/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
@@ -58,5 +58,11 @@ msgstr "多要素認証を設定する"
msgid "Configure MFA"
msgstr "多要素認証を設定する"
-msgid "Use the following key in your authenticator app: {{code}} "
-msgstr "認証アプリで次のコードを使用してください: {{code}} "
+msgid "Manual MFA Setup"
+msgstr "手動多要素認証の設定"
+
+msgid "Use the following key in your authenticator app: <0>{{code}}0>"
+msgstr "認証アプリで次のコードを使用してください: <0>{{code}}0>"
+
+msgid "Scan the following QR code below in your MFA authenticator and enter the code to validate."
+msgstr "多要素認証システムで以下の QR コードをスキャンし、コードを入力して検証します。"
From 78fb8da71f693ad08822e41171d97f168ea1e8d0 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 11:52:46 -0400
Subject: [PATCH 25/28] Fix Translations
---
modules/login/php/mfa.class.inc | 1 +
modules/my_preferences/jsx/mfa.tsx | 23 +++++++++++++------
.../locale/ja/LC_MESSAGES/my_preferences.po | 12 ++++++++++
modules/my_preferences/php/mfa.class.inc | 9 ++++++--
php/libraries/SinglePointLogin.class.inc | 1 -
php/libraries/User.class.inc | 1 -
test/unittests/security/TOTP_Test.php | 1 -
7 files changed, 36 insertions(+), 12 deletions(-)
diff --git a/modules/login/php/mfa.class.inc b/modules/login/php/mfa.class.inc
index 9ebff33848c..743e58ec08d 100644
--- a/modules/login/php/mfa.class.inc
+++ b/modules/login/php/mfa.class.inc
@@ -50,6 +50,7 @@ class MFA extends \NDB_Page
[$baseURL . '/login/css/login.css']
);
}
+
/**
* This function will return a json object for login module.
*
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index 52cc1b4e880..307c73c30de 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -29,6 +29,7 @@ function genPotentialSecret() {
function CodeValidator(props: {
secret: string
}): React.ReactElement {
+ const {t} = useTranslation();
const formSubmit = useCallback(
(code: string, onError: (msg: string) => void) => {
const formObject = new FormData();
@@ -46,7 +47,12 @@ function CodeValidator(props: {
return resp.json();
}).then( (json) => {
if (json.ok == 'success') {
- swal.fire('Success!', json.message, 'success').then( () => {
+ swal.fire({
+ title: t('Success!', {ns: 'loris'}),
+ text: json.message,
+ type: 'success',
+ confirmButtonText: t('OK', {ns: 'loris'}),
+ }).then( () => {
window.location.href = loris.BaseURL + '/my_preferences/';
});
} else if (json.error) {
@@ -60,7 +66,7 @@ function CodeValidator(props: {
}, [props.secret]);
return (
-
Validate Code
+ {t('Validate Code', {ns: 'my_preferences'})}
);
@@ -92,13 +98,16 @@ function MFAIndex(): React.ReactElement {
{t('Scan the following QR code below in your MFA authenticator and enter the code to validate.', {ns: 'my_preferences'})}
- Note that this will overwrite any previously
- setup MFA in LORIS!
+ overwrite ]}
+ defaults="Note that this will <0>overwrite0> any previously setup MFA in LORIS!" />
- Can't scan the QR code? setShowModal(true)}>
- Setup manually.
-
+ setShowModal(true)} />]} />
;
}
diff --git a/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po b/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
index 9512897c619..35212dfd974 100644
--- a/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
+++ b/modules/my_preferences/locale/ja/LC_MESSAGES/my_preferences.po
@@ -66,3 +66,15 @@ msgstr "認証アプリで次のコードを使用してください: <0>{{code}
msgid "Scan the following QR code below in your MFA authenticator and enter the code to validate."
msgstr "多要素認証システムで以下の QR コードをスキャンし、コードを入力して検証します。"
+
+msgid "Note that this will <0>overwrite0> any previously setup MFA in LORIS!"
+msgstr "これにより、ロリスで以前に設定された多要素認証が <0>上書き0> されることに注意してください。"
+
+msgid "Can't scan the QR code? <0>Setup manually.0>"
+msgstr "QR コードをスキャンできませんか? <0>手動で設定してください。0>"
+
+msgid "Validate Code"
+msgstr "コードの検証"
+
+msgid "Successfully registered multifactor authenticator"
+msgstr "多要素認証の登録に成功しました"
diff --git a/modules/my_preferences/php/mfa.class.inc b/modules/my_preferences/php/mfa.class.inc
index bb308f7cab6..3e6981e3264 100644
--- a/modules/my_preferences/php/mfa.class.inc
+++ b/modules/my_preferences/php/mfa.class.inc
@@ -55,6 +55,7 @@ class MFA extends \NDB_Page
}
}
+
/**
* {@inheritDoc}
*
@@ -111,8 +112,12 @@ class MFA extends \NDB_Page
$login = $_SESSION['State']->getProperty('login');
$login->setPassedMFA();
return new \LORIS\Http\Response\JSON\OK(
- ['ok' => 'success',
- 'message' => 'Successfully registered multifactor authenticator'
+ [
+ 'ok' => 'success',
+ 'message' => dgettext(
+ 'my_preferences',
+ 'Successfully registered multifactor authenticator'
+ )
]
);
}
diff --git a/php/libraries/SinglePointLogin.class.inc b/php/libraries/SinglePointLogin.class.inc
index d1ffa353642..6eb42f3c627 100644
--- a/php/libraries/SinglePointLogin.class.inc
+++ b/php/libraries/SinglePointLogin.class.inc
@@ -623,5 +623,4 @@ class SinglePointLogin
$_SESSION['PassedMFA'] = true;
session_write_close();
}
-
}
diff --git a/php/libraries/User.class.inc b/php/libraries/User.class.inc
index f3a243de72d..ca230682c45 100644
--- a/php/libraries/User.class.inc
+++ b/php/libraries/User.class.inc
@@ -689,7 +689,6 @@ class User extends UserPermissions implements
return $this->userInfo['Pending_approval'] == 'Y';
}
-
/**
* Returns a TOTP validator for this account, or null if 2FA
* has never been enabled by the user.
diff --git a/test/unittests/security/TOTP_Test.php b/test/unittests/security/TOTP_Test.php
index 78a4fb348eb..46daa34d590 100644
--- a/test/unittests/security/TOTP_Test.php
+++ b/test/unittests/security/TOTP_Test.php
@@ -15,7 +15,6 @@
*/
class TOTP_Test extends TestCase
{
-
/**
* Test that test vector counters from RFC6238 pass
*
From ab5e16596466eb5b89f2214d13409a6b03dcea21 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 12:29:40 -0400
Subject: [PATCH 26/28] phan
---
modules/user_accounts/php/edit_user.class.inc | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/user_accounts/php/edit_user.class.inc b/modules/user_accounts/php/edit_user.class.inc
index c5379a5ffa3..3c9450b7677 100644
--- a/modules/user_accounts/php/edit_user.class.inc
+++ b/modules/user_accounts/php/edit_user.class.inc
@@ -1139,9 +1139,9 @@ class Edit_User extends \NDB_Form
// already handled in email/password check
// case 3 - Edit user
if ((isset($values['UserID']) && !isset($values['NA_UserID'])
- && $values['UserID'] === $values['Password_hash'])
+ && $values['UserID'] === ($values['Password_hash'] ?? ''))
|| (!empty($this->identifier)
- && $this->identifier === $values['Password_hash'])
+ && $this->identifier === ($values['Password_hash'] ?? ''))
) {
$errors['Password'] = self::PASSWORD_ERROR_IS_USER;
}
From de802f3f996693b68d83b6c957a230491a5a228f Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 12:50:25 -0400
Subject: [PATCH 27/28] lint js
---
modules/my_preferences/jsx/mfa.tsx | 35 +++++++++++++++++-------------
tsconfig.json | 1 +
2 files changed, 21 insertions(+), 15 deletions(-)
diff --git a/modules/my_preferences/jsx/mfa.tsx b/modules/my_preferences/jsx/mfa.tsx
index 307c73c30de..c6cd6b7f6b6 100644
--- a/modules/my_preferences/jsx/mfa.tsx
+++ b/modules/my_preferences/jsx/mfa.tsx
@@ -7,6 +7,8 @@ import Modal from 'Modal';
import MFAPrompt from 'jsx/MFAPrompt';
import {useTranslation, Trans} from 'react-i18next';
import i18n from 'I18nSetup';
+import jaStrings from '../locale/ja/LC_MESSAGES/my_preferences.json';
+import hiStrings from '../locale/hi/LC_MESSAGES/my_preferences.json';
declare const loris: any;
@@ -50,9 +52,9 @@ function CodeValidator(props: {
swal.fire({
title: t('Success!', {ns: 'loris'}),
text: json.message,
- type: 'success',
- confirmButtonText: t('OK', {ns: 'loris'}),
- }).then( () => {
+ type: 'success',
+ confirmButtonText: t('OK', {ns: 'loris'}),
+ }).then( () => {
window.location.href = loris.BaseURL + '/my_preferences/';
});
} else if (json.error) {
@@ -91,30 +93,33 @@ function MFAIndex(): React.ReactElement {
show={showModal}
throwWarning={false}>
CODE]}
+ defaults={'Use the following key in your authenticator app: '
+ + '<0>{{code}}0>'}
+ ns="my_preferences"
+ components={[CODE ]}
values={{code: key}} />
- {t('Scan the following QR code below in your MFA authenticator and enter the code to validate.', {ns: 'my_preferences'})}
+ {t('Scan the following QR code below in your MFA authenticator and '
+ + 'enter the code to validate.', {ns: 'my_preferences'})}
overwrite ]}
- defaults="Note that this will <0>overwrite0> any previously setup MFA in LORIS!" />
+ ns="my_preferences"
+ components={[overwrite ]}
+ defaults={'Note that this will <0>overwrite0> any '
+ + 'previously setup MFA in LORIS!'} />
setShowModal(true)} />]} />
+ ns="my_preferences"
+ defaults="Can't scan the QR code? <0>Setup manually.0>"
+ components={[ setShowModal(true)} />]} />
;
}
window.addEventListener('load', () => {
- i18n.addResourceBundle('ja', 'my_preferences', require("../locale/ja/LC_MESSAGES/my_preferences.json"));
- i18n.addResourceBundle('hi', 'my_preferences', require("../locale/ja/LC_MESSAGES/my_preferences.json"));
+ i18n.addResourceBundle('ja', 'my_preferences', jaStrings);
+ i18n.addResourceBundle('hi', 'my_preferences', hiStrings);
const element = document.getElementById('lorisworkspace');
if (!element) {
diff --git a/tsconfig.json b/tsconfig.json
index 86af508d2d1..68d36de7035 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,6 +7,7 @@
"paths": {
"*": ["*", "jsx/*"]
},
+ "resolveJsonModule": true,
"sourceMap": true,
"jsx": "preserve",
"jsxFactory": "h",
From 4a505ac9f81b97364f51ee2842613b3c5b114b32 Mon Sep 17 00:00:00 2001
From: Dave MacFarlane
Date: Thu, 30 Oct 2025 13:45:12 -0400
Subject: [PATCH 28/28] Update template
---
.../my_preferences/locale/my_preferences.pot | 21 ++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/modules/my_preferences/locale/my_preferences.pot b/modules/my_preferences/locale/my_preferences.pot
index 21c846b80c1..a7474d1c51c 100644
--- a/modules/my_preferences/locale/my_preferences.pot
+++ b/modules/my_preferences/locale/my_preferences.pot
@@ -57,5 +57,24 @@ msgstr ""
msgid "Configure MFA"
msgstr ""
-msgid "Use the following key in your authenticator app: {code} "
+msgid "Manual MFA Setup"
msgstr ""
+
+msgid "Use the following key in your authenticator app: <0>{{code}}0>"
+msgstr ""
+
+msgid "Scan the following QR code below in your MFA authenticator and enter the code to validate."
+msgstr ""
+
+msgid "Note that this will <0>overwrite0> any previously setup MFA in LORIS!"
+msgstr ""
+
+msgid "Can't scan the QR code? <0>Setup manually.0>"
+msgstr ""
+
+msgid "Validate Code"
+msgstr ""
+
+msgid "Successfully registered multifactor authenticator"
+msgstr ""
+