From 931ed36570693cf79ed61f93caf687545589296d Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 4 Sep 2023 11:36:41 +0900 Subject: [PATCH 001/290] docs: add changelog and upgrading for v4.5.0 --- user_guide_src/source/changelogs/index.rst | 1 + user_guide_src/source/changelogs/v4.5.0.rst | 76 +++++++++++++++++++ .../source/installation/upgrade_450.rst | 50 ++++++++++++ .../source/installation/upgrading.rst | 1 + 4 files changed, 128 insertions(+) create mode 100644 user_guide_src/source/changelogs/v4.5.0.rst create mode 100644 user_guide_src/source/installation/upgrade_450.rst diff --git a/user_guide_src/source/changelogs/index.rst b/user_guide_src/source/changelogs/index.rst index 73eab706433f..c6870f241f37 100644 --- a/user_guide_src/source/changelogs/index.rst +++ b/user_guide_src/source/changelogs/index.rst @@ -12,6 +12,7 @@ See all the changes. .. toctree:: :titlesonly: + v4.5.0 v4.4.2 v4.4.1 v4.4.0 diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst new file mode 100644 index 000000000000..b4cca81c54f1 --- /dev/null +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -0,0 +1,76 @@ +Version 4.5.0 +############# + +Release Date: Unreleased + +**4.5.0 release of CodeIgniter4** + +.. contents:: + :local: + :depth: 3 + +Highlights +********** + +- TBD + +BREAKING +******** + +Behavior Changes +================ + +Interface Changes +================= + +Method Signature Changes +======================== + +Enhancements +************ + +Commands +======== + +Testing +======= + +Database +======== + +Query Builder +------------- + +Forge +----- + +Others +------ + +Model +===== + +Libraries +========= + +Helpers and Functions +===================== + +Others +====== + +Message Changes +*************** + +Changes +******* + +Deprecations +************ + +Bugs Fixed +********** + +See the repo's +`CHANGELOG.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst new file mode 100644 index 000000000000..5a52bca43aae --- /dev/null +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -0,0 +1,50 @@ +############################# +Upgrading from 4.4.x to 4.5.0 +############################# + +Please refer to the upgrade instructions corresponding to your installation method. + +- :ref:`Composer Installation App Starter Upgrading ` +- :ref:`Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading ` +- :ref:`Manual Installation Upgrading ` + +.. contents:: + :local: + :depth: 2 + +Mandatory File Changes +********************** + +Breaking Changes +**************** + +Breaking Enhancements +********************* + +Project Files +************* + +Some files in the **project space** (root, app, public, writable) received updates. Due to +these files being outside of the **system** scope they will not be changed without your intervention. + +There are some third-party CodeIgniter modules available to assist with merging changes to +the project space: `Explore on Packagist `_. + +Content Changes +=============== + +The following files received significant changes (including deprecations or visual adjustments) +and it is recommended that you merge the updated versions with your application: + +Config +------ + +- @TODO + +All Changes +=========== + +This is a list of all files in the **project space** that received changes; +many will be simple comments or formatting that have no effect on the runtime: + +- @TODO diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst index 9f9e17528052..f758b89047af 100644 --- a/user_guide_src/source/installation/upgrading.rst +++ b/user_guide_src/source/installation/upgrading.rst @@ -16,6 +16,7 @@ See also :doc:`./backward_compatibility_notes`. backward_compatibility_notes + upgrade_450 upgrade_442 upgrade_441 upgrade_440 From 8aedb6f69c20772e46ae4806642eb8ff441778ef Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 12:01:19 +0900 Subject: [PATCH 002/290] config: change default db charset to utf8mb4 --- app/Config/Database.php | 8 ++++---- system/Database/BaseConnection.php | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/Config/Database.php b/app/Config/Database.php index e2450ec16cf1..75d5838fa9ae 100644 --- a/app/Config/Database.php +++ b/app/Config/Database.php @@ -34,8 +34,8 @@ class Database extends Config 'DBPrefix' => '', 'pConnect' => false, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -59,8 +59,8 @@ class Database extends Config 'DBPrefix' => 'db_', // Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS 'pConnect' => false, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 1116d3966439..83303fea963d 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -136,16 +136,20 @@ abstract class BaseConnection implements ConnectionInterface /** * Character set * + * This value must be updated by Config\Database if the driver use it. + * * @var string */ - protected $charset = 'utf8'; + protected $charset = 'utf8mb4'; /** * Collation * + * This value must be updated by Config\Database if the driver use it. + * * @var string */ - protected $DBCollat = 'utf8_general_ci'; + protected $DBCollat = 'utf8mb4_general_ci'; /** * Swap Prefix From 995da81a3be565b47f92767ab86922b6111d28d4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 12:06:13 +0900 Subject: [PATCH 003/290] docs: update utf8 to utf8mb4 --- user_guide_src/source/database/configuration/001.php | 4 ++-- user_guide_src/source/database/configuration/004.php | 2 +- user_guide_src/source/database/configuration/005.php | 8 ++++---- user_guide_src/source/database/configuration/006.php | 4 ++-- user_guide_src/source/database/connecting/006.php | 4 ++-- user_guide_src/source/dbmgmt/forge/016.php | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/user_guide_src/source/database/configuration/001.php b/user_guide_src/source/database/configuration/001.php index b4336abbf1c0..874c673fe00a 100644 --- a/user_guide_src/source/database/configuration/001.php +++ b/user_guide_src/source/database/configuration/001.php @@ -18,8 +18,8 @@ class Database extends Config 'DBPrefix' => '', 'pConnect' => false, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, diff --git a/user_guide_src/source/database/configuration/004.php b/user_guide_src/source/database/configuration/004.php index 4b77961706da..58243f8a3727 100644 --- a/user_guide_src/source/database/configuration/004.php +++ b/user_guide_src/source/database/configuration/004.php @@ -10,7 +10,7 @@ class Database extends Config // MySQLi public array $default = [ - 'DSN' => 'MySQLi://username:password@hostname:3306/database?charset=utf8&DBCollat=utf8_general_ci', + 'DSN' => 'MySQLi://username:password@hostname:3306/database?charset=utf8mb4&DBCollat=utf8mb4_general_ci', // ... ]; diff --git a/user_guide_src/source/database/configuration/005.php b/user_guide_src/source/database/configuration/005.php index ca23aea169d9..34f64e381970 100644 --- a/user_guide_src/source/database/configuration/005.php +++ b/user_guide_src/source/database/configuration/005.php @@ -20,8 +20,8 @@ class Database extends Config 'DBPrefix' => '', 'pConnect' => true, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -36,8 +36,8 @@ class Database extends Config 'DBPrefix' => '', 'pConnect' => true, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, diff --git a/user_guide_src/source/database/configuration/006.php b/user_guide_src/source/database/configuration/006.php index 1cd5fafe421b..ff63399207ad 100644 --- a/user_guide_src/source/database/configuration/006.php +++ b/user_guide_src/source/database/configuration/006.php @@ -18,8 +18,8 @@ class Database extends Config 'DBPrefix' => '', 'pConnect' => true, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'compress' => false, 'encrypt' => false, diff --git a/user_guide_src/source/database/connecting/006.php b/user_guide_src/source/database/connecting/006.php index 55e8010730d2..6804ad8a9682 100644 --- a/user_guide_src/source/database/connecting/006.php +++ b/user_guide_src/source/database/connecting/006.php @@ -10,8 +10,8 @@ 'DBPrefix' => '', 'pConnect' => false, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, diff --git a/user_guide_src/source/dbmgmt/forge/016.php b/user_guide_src/source/dbmgmt/forge/016.php index e8db91a9ed63..4fb2abfce753 100644 --- a/user_guide_src/source/dbmgmt/forge/016.php +++ b/user_guide_src/source/dbmgmt/forge/016.php @@ -2,4 +2,4 @@ $attributes = ['ENGINE' => 'InnoDB']; $forge->createTable('table_name', false, $attributes); -// produces: CREATE TABLE `table_name` (...) ENGINE = InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci +// produces: CREATE TABLE `table_name` (...) ENGINE = InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci From 7f4d0a95fc5377ea56a164c5833f1ca5b0f46472 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 12:05:29 +0900 Subject: [PATCH 004/290] test: update charset utf8 to utf8mb4 --- tests/_support/Config/Registrar.php | 12 +++++----- tests/system/Database/BaseConnectionTest.php | 14 ++++++------ tests/system/Database/ConfigTest.php | 24 ++++++++++---------- tests/system/Models/GeneralModelTest.php | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php index a9c7c3097630..a8d1d40df579 100644 --- a/tests/_support/Config/Registrar.php +++ b/tests/_support/Config/Registrar.php @@ -34,8 +34,8 @@ class Registrar 'DBPrefix' => 'db_', 'pConnect' => false, 'DBDebug' => true, - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -54,7 +54,7 @@ class Registrar 'pConnect' => false, 'DBDebug' => true, 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'DBCollat' => '', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -73,7 +73,7 @@ class Registrar 'pConnect' => false, 'DBDebug' => true, 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'DBCollat' => '', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -93,7 +93,7 @@ class Registrar 'pConnect' => false, 'DBDebug' => true, 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'DBCollat' => '', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -112,7 +112,7 @@ class Registrar 'pConnect' => false, 'DBDebug' => true, 'charset' => 'AL32UTF8', - 'DBCollat' => 'utf8_general_ci', + 'DBCollat' => '', 'swapPre' => '', 'encrypt' => false, 'compress' => false, diff --git a/tests/system/Database/BaseConnectionTest.php b/tests/system/Database/BaseConnectionTest.php index 1d629832bf6c..ba612e4bed81 100644 --- a/tests/system/Database/BaseConnectionTest.php +++ b/tests/system/Database/BaseConnectionTest.php @@ -33,8 +33,8 @@ final class BaseConnectionTest extends CIUnitTestCase 'DBPrefix' => 'test_', 'pConnect' => true, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -51,8 +51,8 @@ final class BaseConnectionTest extends CIUnitTestCase 'DBPrefix' => 'test_', 'pConnect' => true, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -71,8 +71,8 @@ public function testSavesConfigOptions(): void $this->assertSame('MockDriver', $db->DBDriver); $this->assertTrue($db->pConnect); $this->assertTrue($db->DBDebug); - $this->assertSame('utf8', $db->charset); - $this->assertSame('utf8_general_ci', $db->DBCollat); + $this->assertSame('utf8mb4', $db->charset); + $this->assertSame('utf8mb4_general_ci', $db->DBCollat); $this->assertSame('', $db->swapPre); $this->assertFalse($db->encrypt); $this->assertFalse($db->compress); @@ -153,7 +153,7 @@ public function testMagicGet(): void { $db = new MockConnection($this->options); - $this->assertSame('utf8', $db->charset); + $this->assertSame('utf8mb4', $db->charset); } public function testMagicGetMissing(): void diff --git a/tests/system/Database/ConfigTest.php b/tests/system/Database/ConfigTest.php index 37ecfeb6f5e2..71c015c080a2 100644 --- a/tests/system/Database/ConfigTest.php +++ b/tests/system/Database/ConfigTest.php @@ -33,8 +33,8 @@ final class ConfigTest extends CIUnitTestCase 'DBPrefix' => 'test_', 'pConnect' => true, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -52,8 +52,8 @@ final class ConfigTest extends CIUnitTestCase 'DBPrefix' => 't_', 'pConnect' => false, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -71,8 +71,8 @@ final class ConfigTest extends CIUnitTestCase 'DBPrefix' => 't_', 'pConnect' => false, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -90,8 +90,8 @@ final class ConfigTest extends CIUnitTestCase 'DBPrefix' => 't_', 'pConnect' => false, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'charset' => 'utf8', - 'DBCollat' => 'utf8_general_ci', + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', 'swapPre' => '', 'encrypt' => false, 'compress' => false, @@ -157,8 +157,8 @@ public function testConnectionGroupWithDSNPostgre(): void $this->assertSame('Postgre', $this->getPrivateProperty($conn, 'DBDriver')); $this->assertSame('test_', $this->getPrivateProperty($conn, 'DBPrefix')); $this->assertFalse($this->getPrivateProperty($conn, 'pConnect')); - $this->assertSame('utf8', $this->getPrivateProperty($conn, 'charset')); - $this->assertSame('utf8_general_ci', $this->getPrivateProperty($conn, 'DBCollat')); + $this->assertSame('utf8mb4', $this->getPrivateProperty($conn, 'charset')); + $this->assertSame('utf8mb4_general_ci', $this->getPrivateProperty($conn, 'DBCollat')); $this->assertTrue($this->getPrivateProperty($conn, 'strictOn')); $this->assertSame([], $this->getPrivateProperty($conn, 'failover')); $this->assertSame('5', $this->getPrivateProperty($conn, 'connect_timeout')); @@ -185,8 +185,8 @@ public function testConnectionGroupWithDSNPostgreNative(): void $this->assertSame('Postgre', $this->getPrivateProperty($conn, 'DBDriver')); $this->assertSame('t_', $this->getPrivateProperty($conn, 'DBPrefix')); $this->assertFalse($this->getPrivateProperty($conn, 'pConnect')); - $this->assertSame('utf8', $this->getPrivateProperty($conn, 'charset')); - $this->assertSame('utf8_general_ci', $this->getPrivateProperty($conn, 'DBCollat')); + $this->assertSame('utf8mb4', $this->getPrivateProperty($conn, 'charset')); + $this->assertSame('utf8mb4_general_ci', $this->getPrivateProperty($conn, 'DBCollat')); $this->assertTrue($this->getPrivateProperty($conn, 'strictOn')); $this->assertSame([], $this->getPrivateProperty($conn, 'failover')); } diff --git a/tests/system/Models/GeneralModelTest.php b/tests/system/Models/GeneralModelTest.php index e3966ba5f8e5..65506a47b4a2 100644 --- a/tests/system/Models/GeneralModelTest.php +++ b/tests/system/Models/GeneralModelTest.php @@ -75,7 +75,7 @@ public function testMagicGetters(): void // from DB $this->assertTrue(isset($this->model->DBPrefix)); - $this->assertSame('utf8', $this->model->charset); + $this->assertSame('utf8mb4', $this->model->charset); // from Builder $this->assertTrue(isset($this->model->QBNoEscape)); From d2425b2e4fb461bd91fb9cdf8e69620d5ea13e5b Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 12 Sep 2023 06:33:00 +0900 Subject: [PATCH 005/290] config: change default charset/DBCollat for SQLite3 Both are not used at the moment, though. --- app/Config/Database.php | 4 ++-- tests/system/Models/GeneralModelTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Config/Database.php b/app/Config/Database.php index 75d5838fa9ae..4e4aac7c0449 100644 --- a/app/Config/Database.php +++ b/app/Config/Database.php @@ -59,8 +59,8 @@ class Database extends Config 'DBPrefix' => 'db_', // Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS 'pConnect' => false, 'DBDebug' => true, - 'charset' => 'utf8mb4', - 'DBCollat' => 'utf8mb4_general_ci', + 'charset' => 'utf8', + 'DBCollat' => '', 'swapPre' => '', 'encrypt' => false, 'compress' => false, diff --git a/tests/system/Models/GeneralModelTest.php b/tests/system/Models/GeneralModelTest.php index 65506a47b4a2..e3966ba5f8e5 100644 --- a/tests/system/Models/GeneralModelTest.php +++ b/tests/system/Models/GeneralModelTest.php @@ -75,7 +75,7 @@ public function testMagicGetters(): void // from DB $this->assertTrue(isset($this->model->DBPrefix)); - $this->assertSame('utf8mb4', $this->model->charset); + $this->assertSame('utf8', $this->model->charset); // from Builder $this->assertTrue(isset($this->model->QBNoEscape)); From 4e4f8bc9d0c2b4b3a404b965999b9898e05436ea Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 12 Sep 2023 06:47:57 +0900 Subject: [PATCH 006/290] docs: add upgrade_450.rst --- user_guide_src/source/installation/upgrade_450.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst index 5a52bca43aae..d3420edb64fe 100644 --- a/user_guide_src/source/installation/upgrade_450.rst +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -39,7 +39,10 @@ and it is recommended that you merge the updated versions with your application: Config ------ -- @TODO +- app/Config/Database.php + - The default value of ``charset`` in ``$default`` has been change to ``utf8mb4``. + - The default value of ``DBCollat`` in ``$default`` has been change to ``utf8mb4_general_ci``. + - The default value of ``DBCollat`` in ``$tests`` has been change to ``''``. All Changes =========== From 263f6cd4c0a85d6382afb66b90fc3c2f3d38c21a Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 11:39:03 +0900 Subject: [PATCH 007/290] test: update RouterTest Replaced deprecated methods. --- tests/system/Router/RouterTest.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index c5a05fb36ace..a2ca06c28a61 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -496,7 +496,7 @@ public function testRouteWorksWithFilters(): void $this->assertSame('\TestController', $router->controllerName()); $this->assertSame('foobar', $router->methodName()); - $this->assertSame('test', $router->getFilter()); + $this->assertSame(['test'], $router->getFilters()); } /** @@ -526,25 +526,25 @@ static function (RouteCollection $routes): void { $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('index', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); $router->handle('api/posts/new'); $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('new', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); $router->handle('api/posts/50'); $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('show', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); $router->handle('api/posts/50/edit'); $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('edit', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); // POST $this->collection->group(...$group); @@ -556,7 +556,7 @@ static function (RouteCollection $routes): void { $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('create', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); // PUT $this->collection->group(...$group); @@ -568,7 +568,7 @@ static function (RouteCollection $routes): void { $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('update', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); // PATCH $this->collection->group(...$group); @@ -580,7 +580,7 @@ static function (RouteCollection $routes): void { $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('update', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); // DELETE $this->collection->group(...$group); @@ -592,7 +592,7 @@ static function (RouteCollection $routes): void { $this->assertSame('\App\Controllers\Api\PostController', $router->controllerName()); $this->assertSame('delete', $router->methodName()); - $this->assertSame('api-auth', $router->getFilter()); + $this->assertSame(['api-auth'], $router->getFilters()); } public function testRouteWorksWithClassnameFilter(): void @@ -606,7 +606,7 @@ public function testRouteWorksWithClassnameFilter(): void $this->assertSame('\TestController', $router->controllerName()); $this->assertSame('foo', $router->methodName()); - $this->assertSame(Customfilter::class, $router->getFilter()); + $this->assertSame([Customfilter::class], $router->getFilters()); $this->resetServices(); } From 0a6b0e76afa2266b3c05424e3a00db0ec0a3a4ac Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 11:39:57 +0900 Subject: [PATCH 008/290] test: update FiltersTest Replaced deprecated methods. --- tests/system/Filters/FiltersTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/system/Filters/FiltersTest.php b/tests/system/Filters/FiltersTest.php index 8e80e4d5de1d..efe47300fd49 100644 --- a/tests/system/Filters/FiltersTest.php +++ b/tests/system/Filters/FiltersTest.php @@ -834,7 +834,7 @@ public function testEnableFilter(): void $filters = $this->createFilters($filtersConfig); $filters = $filters->initialize('admin/foo/bar'); - $filters->enableFilter('google', 'before'); + $filters->enableFilters(['google'], 'before'); $filters = $filters->getFilters(); $this->assertContains('google', $filters['before']); @@ -941,8 +941,8 @@ public function testEnableFilterWithArguments(): void $filters = $this->createFilters($filtersConfig); $filters = $filters->initialize('admin/foo/bar'); - $filters->enableFilter('role:admin , super', 'before'); - $filters->enableFilter('role:admin , super', 'after'); + $filters->enableFilters(['role:admin , super'], 'before'); + $filters->enableFilters(['role:admin , super'], 'after'); $found = $filters->getFilters(); $this->assertContains('role', $found['before']); @@ -973,8 +973,8 @@ public function testEnableFilterWithNoArguments(): void $filters = $this->createFilters($filtersConfig); $filters = $filters->initialize('admin/foo/bar'); - $filters->enableFilter('role', 'before'); - $filters->enableFilter('role', 'after'); + $filters->enableFilters(['role'], 'before'); + $filters->enableFilters(['role'], 'after'); $found = $filters->getFilters(); $this->assertContains('role', $found['before']); @@ -1005,7 +1005,7 @@ public function testEnableNonFilter(): void $filters = $this->createFilters($filtersConfig); $filters = $filters->initialize('admin/foo/bar'); - $filters->enableFilter('goggle', 'before'); + $filters->enableFilters(['goggle'], 'before'); } /** From 756d14315693c37648e7940d2e56247570a43ac0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 11:42:55 +0900 Subject: [PATCH 009/290] refactor: remove Config\Feature::$multipleFilters $multipleFilters is always enabled, and cannot be disabled. --- app/Config/Feature.php | 14 -------------- system/CodeIgniter.php | 18 ++---------------- .../Commands/Utilities/Routes/FilterFinder.php | 8 -------- system/Router/Router.php | 8 +------- .../Utilities/Routes/FilterFinderTest.php | 4 ---- tests/system/Router/RouterTest.php | 5 ----- 6 files changed, 3 insertions(+), 54 deletions(-) diff --git a/app/Config/Feature.php b/app/Config/Feature.php index 0bc45c6f46c0..8ed8e01a0158 100644 --- a/app/Config/Feature.php +++ b/app/Config/Feature.php @@ -9,20 +9,6 @@ */ class Feature extends BaseConfig { - /** - * Enable multiple filters for a route or not. - * - * If you enable this: - * - CodeIgniter\CodeIgniter::handleRequest() uses: - * - CodeIgniter\Filters\Filters::enableFilters(), instead of enableFilter() - * - CodeIgniter\CodeIgniter::tryToRouteIt() uses: - * - CodeIgniter\Router\Router::getFilters(), instead of getFilter() - * - CodeIgniter\Router\Router::handle() uses: - * - property $filtersInfo, instead of $filterInfo - * - CodeIgniter\Router\RouteCollection::getFiltersForRoute(), instead of getFilterForRoute() - */ - public bool $multipleFilters = false; - /** * Use improved new auto routing instead of the default legacy version. */ diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index e017d6b9ccc2..bd1f9ce9f8b0 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -31,7 +31,6 @@ use CodeIgniter\Router\Router; use Config\App; use Config\Cache; -use Config\Feature; use Config\Kint as KintConfig; use Config\Services; use Exception; @@ -452,15 +451,8 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // If any filters were specified within the routes file, // we need to ensure it's active for the current request if ($routeFilter !== null) { - $multipleFiltersEnabled = config(Feature::class)->multipleFilters ?? false; - if ($multipleFiltersEnabled) { - $filters->enableFilters($routeFilter, 'before'); - $filters->enableFilters($routeFilter, 'after'); - } else { - // for backward compatibility - $filters->enableFilter($routeFilter, 'before'); - $filters->enableFilter($routeFilter, 'after'); - } + $filters->enableFilters($routeFilter, 'before'); + $filters->enableFilters($routeFilter, 'after'); } // Run "before" filters @@ -810,12 +802,6 @@ protected function tryToRouteIt(?RouteCollectionInterface $routes = null) $this->benchmark->stop('routing'); - // for backward compatibility - $multipleFiltersEnabled = config(Feature::class)->multipleFilters ?? false; - if (! $multipleFiltersEnabled) { - return $this->router->getFilter(); - } - return $this->router->getFilters(); } diff --git a/system/Commands/Utilities/Routes/FilterFinder.php b/system/Commands/Utilities/Routes/FilterFinder.php index 2e5da617e795..40a834df52f1 100644 --- a/system/Commands/Utilities/Routes/FilterFinder.php +++ b/system/Commands/Utilities/Routes/FilterFinder.php @@ -15,7 +15,6 @@ use CodeIgniter\Filters\Filters; use CodeIgniter\HTTP\Exceptions\RedirectException; use CodeIgniter\Router\Router; -use Config\Feature; use Config\Services; /** @@ -38,13 +37,6 @@ private function getRouteFilters(string $uri): array { $this->router->handle($uri); - $multipleFiltersEnabled = config(Feature::class)->multipleFilters ?? false; - if (! $multipleFiltersEnabled) { - $filter = $this->router->getFilter(); - - return $filter === null ? [] : [$filter]; - } - return $this->router->getFilters(); } diff --git a/system/Router/Router.php b/system/Router/Router.php index 0013d7cdf920..bb551f1b7d3a 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -185,13 +185,7 @@ public function handle(?string $uri = null) // Checks defined routes if ($this->checkRoutes($uri)) { if ($this->collection->isFiltered($this->matchedRoute[0])) { - $multipleFiltersEnabled = config(Feature::class)->multipleFilters ?? false; - if ($multipleFiltersEnabled) { - $this->filtersInfo = $this->collection->getFiltersForRoute($this->matchedRoute[0]); - } else { - // for backward compatibility - $this->filterInfo = $this->collection->getFilterForRoute($this->matchedRoute[0]); - } + $this->filtersInfo = $this->collection->getFiltersForRoute($this->matchedRoute[0]); } return $this->controller; diff --git a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php index 67c9a1ca5815..652102976a19 100644 --- a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php +++ b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php @@ -171,8 +171,6 @@ public function testFindGlobalsAndRouteClassnameFilters(): void public function testFindGlobalsAndRouteMultipleFilters(): void { - config('Feature')->multipleFilters = true; - $collection = $this->createRouteCollection(); $collection->get('admin', ' AdminController::index', ['filter' => ['honeypot', InvalidChars::class]]); $router = $this->createRouter($collection); @@ -187,7 +185,5 @@ public function testFindGlobalsAndRouteMultipleFilters(): void 'after' => ['honeypot', InvalidChars::class, 'toolbar'], ]; $this->assertSame($expected, $filters); - - config('Feature')->multipleFilters = false; } } diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index a2ca06c28a61..d0c9b5a664ab 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -613,9 +613,6 @@ public function testRouteWorksWithClassnameFilter(): void public function testRouteWorksWithMultipleFilters(): void { - $feature = config('Feature'); - $feature->multipleFilters = true; - $collection = $this->collection; $collection->add('foo', 'TestController::foo', ['filter' => ['filter1', 'filter2:param']]); @@ -626,8 +623,6 @@ public function testRouteWorksWithMultipleFilters(): void $this->assertSame('\TestController', $router->controllerName()); $this->assertSame('foo', $router->methodName()); $this->assertSame(['filter1', 'filter2:param'], $router->getFilters()); - - $feature->multipleFilters = false; } /** From eca7c8271c2a32d3364abb3bc506c43f714fea55 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 11:45:44 +0900 Subject: [PATCH 010/290] refactor: remove Router::$filterInfo and getFilter() --- system/Router/Router.php | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/system/Router/Router.php b/system/Router/Router.php index bb551f1b7d3a..3e2f504b38bb 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -99,16 +99,6 @@ class Router implements RouterInterface */ protected $detectedLocale; - /** - * The filter info from Route Collection - * if the matched route should be filtered. - * - * @var string|null - * - * @deprecated Use $filtersInfo - */ - protected $filterInfo; - /** * The filter info from Route Collection * if the matched route should be filtered. @@ -179,7 +169,6 @@ public function handle(?string $uri = null) $uri = urldecode($uri); // Restart filterInfo - $this->filterInfo = null; $this->filtersInfo = []; // Checks defined routes @@ -206,18 +195,6 @@ public function handle(?string $uri = null) return $this->controllerName(); } - /** - * Returns the filter info for the matched route, if any. - * - * @return string|null - * - * @deprecated Use getFilters() - */ - public function getFilter() - { - return $this->filterInfo; - } - /** * Returns the filter info for the matched route, if any. * From 39c12701fdc66d3e5f6384178de9acca71636faa Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 11:49:34 +0900 Subject: [PATCH 011/290] refactor: remove Filters::enableFilter() --- system/Filters/Filters.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index d0c67845466f..33101aa512db 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -335,12 +335,8 @@ public function addFilter(string $class, ?string $alias = null, string $when = ' * are passed to the filter when executed. * * @param string $name filter_name or filter_name:arguments like 'role:admin,manager' - * - * @return $this - * - * @deprecated Use enableFilters(). This method will be private. */ - public function enableFilter(string $name, string $when = 'before') + private function enableFilter(string $name, string $when = 'before'): void { // Get arguments and clean name [$name, $arguments] = $this->getCleanName($name); @@ -362,8 +358,6 @@ public function enableFilter(string $name, string $when = 'before') $this->filters[$when][] = $name; $this->filtersClass[$when] = array_merge($this->filtersClass[$when], $classNames); } - - return $this; } /** From 4d29e231f9c46e3cca4f5445dc664e420728bae9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 12:39:50 +0900 Subject: [PATCH 012/290] chore: update phpstan-baseline --- phpstan-baseline.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 106de3723ef0..86e9be4ca931 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -2346,11 +2346,6 @@ 'count' => 2, 'path' => __DIR__ . '/system/Router/Router.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Call to an undefined method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getFilterForRoute\\(\\)\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Router/Router.php', -]; $ignoreErrors[] = [ 'message' => '#^Call to an undefined method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getFiltersForRoute\\(\\)\\.$#', 'count' => 1, From 8e548b0e4e6d7ee41fbe8f2723aef37718983ec1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 13:06:27 +0900 Subject: [PATCH 013/290] refactor: remove RouteCollection::getFilterForRoute() --- system/Router/RouteCollection.php | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 63b7397dbc4c..ec724016f301 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -1228,25 +1228,6 @@ public function isFiltered(string $search, ?string $verb = null): bool return isset($options[$search]['filter']); } - /** - * Returns the filter that should be applied for a single route, along - * with any parameters it might have. Parameters are found by splitting - * the parameter name on a colon to separate the filter name from the parameter list, - * and the splitting the result on commas. So: - * - * 'role:admin,manager' - * - * has a filter of "role", with parameters of ['admin', 'manager']. - * - * @deprecated Use getFiltersForRoute() - */ - public function getFilterForRoute(string $search, ?string $verb = null): string - { - $options = $this->loadRoutesOptions($verb); - - return $options[$search]['filter'] ?? ''; - } - /** * Returns the filters that should be applied for a single route, along * with any parameters it might have. Parameters are found by splitting From 581a5db2ee60b71bb232ccd51a018cc5e5f52ff6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 13 Sep 2023 15:07:19 +0900 Subject: [PATCH 014/290] docs: add docs --- user_guide_src/source/changelogs/v4.5.0.rst | 19 +++++++++++++++++++ user_guide_src/source/incoming/routing.rst | 8 +++++++- .../source/installation/upgrade_450.rst | 9 +++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index b4cca81c54f1..d18f0884e0ce 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -26,6 +26,21 @@ Interface Changes Method Signature Changes ======================== +.. _v450-removed-deprecated-items: + +Removed Deprecated Items +======================== + +Filters +------- + +- The following deprecated items have been removed, because now :ref:`multiple-filters` are always enabled. + + - ``Filters::enableFilter()`` + - ``RouteCollection::getFilterForRoute()`` + - ``Router::$filterInfo`` + - ``Router::getFilter()`` + Enhancements ************ @@ -65,6 +80,10 @@ Message Changes Changes ******* +- **Config:** + - ``Config\Feature::$multipleFilters`` has been removed, because now + :ref:`multiple-filters` are always enabled. + Deprecations ************ diff --git a/user_guide_src/source/incoming/routing.rst b/user_guide_src/source/incoming/routing.rst index 0fa702ff10d2..f956c68fb4df 100644 --- a/user_guide_src/source/incoming/routing.rst +++ b/user_guide_src/source/incoming/routing.rst @@ -384,12 +384,18 @@ You specify a filter classname for the filter value: .. literalinclude:: routing/036.php +.. _multiple-filters: + Multiple Filters ---------------- .. versionadded:: 4.1.5 -.. important:: *Multiple filters* is disabled by default. Because it breaks backward compatibility. If you want to use it, you need to configure. See :ref:`upgrade-415-multiple-filters-for-a-route` for the details. +.. important:: Since v4.5.0, *Multiple Filters* are always enabled. + Prior to v4.5.0, *Multiple Filters* were disabled by default. + If you want to use with prior to v4.5.0, See + :ref:`Upgrading from 4.1.4 to 4.1.5 ` + for the details. You specify an array for the filter value: diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst index d3420edb64fe..a9925320bf2d 100644 --- a/user_guide_src/source/installation/upgrade_450.rst +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -18,6 +18,12 @@ Mandatory File Changes Breaking Changes **************** +Removed Deprecated Items +======================== + +Some deprecated items have been removed. If you extend these classes and are +using them, upgrade your code. See :ref:`v450-removed-deprecated-items` for details. + Breaking Enhancements ********************* @@ -43,6 +49,9 @@ Config - The default value of ``charset`` in ``$default`` has been change to ``utf8mb4``. - The default value of ``DBCollat`` in ``$default`` has been change to ``utf8mb4_general_ci``. - The default value of ``DBCollat`` in ``$tests`` has been change to ``''``. +- app/Config/Feature.php + - ``Config\Feature::$multipleFilters`` has been removed, because now + :ref:`multiple-filters` are always enabled. All Changes =========== From 32c5307f36379c809c7829333ec5d4877b30e89e Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 4 Sep 2023 22:48:52 +0300 Subject: [PATCH 015/290] feat: Add lang:find command --- .../Translation/LocalizationFinder.php | 339 ++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 system/Commands/Translation/LocalizationFinder.php diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php new file mode 100644 index 000000000000..37f6d0be7d2c --- /dev/null +++ b/system/Commands/Translation/LocalizationFinder.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Commands\Translation; + +use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\CLI; +use Config\App; +use Locale; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; + +class LocalizationFinder extends BaseCommand +{ + protected $group = 'Translation'; + protected $name = 'lang:find'; + protected $description = 'Find and save available phrases to translate'; + protected $usage = 'lang:find [options]'; + protected $arguments = []; + protected $options = [ + '--locale' => 'Apply changes to the selected locale (en, ru, etc...).', + '--dir' => 'Directory for searching for translations (in relation to the APPPATH).', + '--show-new' => 'See only new translations as table.', + '--verbose' => 'Output detailed information.', + ]; + + /** + * Flag for output detailed information + */ + private bool $verbose = false; + + private string $languagePath; + + public function run(array $params) + { + $this->verbose = array_key_exists('verbose', $params); + $cliOptionShowNew = array_key_exists('show-new', $params); + $cliOptionLocale = ! empty($params['locale']) ? $params['locale'] : null; + $cliOptionDir = ! empty($params['dir']) ? $params['dir'] : null; + $currentLocale = Locale::getDefault(); + $currentDir = APPPATH; + $this->languagePath = $currentDir . 'Language'; + $tableRows = []; + $languageFoundKeys = []; + $countFiles = 0; + $countNewKeys = 0; + + if (ENVIRONMENT === 'testing') { + $currentDir = SUPPORTPATH; + $this->languagePath = $currentDir . 'Language/'; + } + + if (is_string($cliOptionLocale)) { + if (! in_array($cliOptionLocale, config(App::class)->supportedLocales, true)) { + CLI::error('Error: Supported locales ' . implode(', ', config(App::class)->supportedLocales)); + + return -1; + } + + $currentLocale = $cliOptionLocale; + } + + if (is_string($cliOptionDir)) { + $tempCurrentDir = realpath($currentDir . $cliOptionDir); + + if (false === $tempCurrentDir) { + CLI::error('Error: Dir must be located in ' . $currentDir); + + return -1; + } + + if (str_starts_with($tempCurrentDir, $this->languagePath)) { + CLI::error('Error: Dir ' . $this->languagePath . ' restricted to scan.'); + + return -1; + } + + $currentDir = $tempCurrentDir; + } + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($currentDir)); + $this->writeIsVerbose('Current locale: ' . $currentLocale); + $this->writeIsVerbose('Find phrases in ' . $currentDir . ' folder...'); + + /** + * @var SplFileInfo $file + */ + foreach ($iterator as $file) { + if ($this->isIgnoredFile($file)) { + continue; + } + + $this->writeIsVerbose('File found: ' . mb_substr($file->getRealPath(), mb_strlen(APPPATH))); + $countFiles++; + $languageFoundKeys = array_replace_recursive($this->findTranslationsInFile($file), $languageFoundKeys); + } + + ksort($languageFoundKeys); + $languageDiff = []; + $languageFoundGroups = array_unique(array_keys($languageFoundKeys)); + + foreach ($languageFoundGroups as $langFileName) { + $languageStoredKeys = []; + $languageFilePath = $this->languagePath . DIRECTORY_SEPARATOR . $currentLocale . DIRECTORY_SEPARATOR . $langFileName . '.php'; + + if (is_file($languageFilePath)) { + /** + * Load old localization + */ + $languageStoredKeys = require $languageFilePath; + } + + $languageDiff = $this->arrayDiffRecursive($languageFoundKeys[$langFileName], $languageStoredKeys); + $countNewKeys += $this->arrayCountRecursive($languageDiff); + + if ($cliOptionShowNew) { + $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); + } else { + $newLanguageKeys = array_replace_recursive($languageFoundKeys[$langFileName], $languageStoredKeys); + + /** + * New translates exists + */ + if ($languageDiff !== []) { + if (false === file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys))) { + $this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red'); + } else { + /** + * FIXME: Need help command (slow run) + */ + exec('composer cs-fix --quiet ' . $this->languagePath); + $this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green'); + } + } + } + } + + if ($cliOptionShowNew && ! empty($tableRows)) { + sort($tableRows); + CLI::table($tableRows, ['File', 'Key']); + } + + $this->writeIsVerbose('Files found: ' . $countFiles); + $this->writeIsVerbose('New translates found: ' . $countNewKeys); + CLI::write('All operations done!'); + } + + /** + * @param SplFileInfo|string $file + */ + private function findTranslationsInFile($file): array + { + $languageFoundKeys = []; + + if (is_string($file) && is_file($file)) { + $file = new SplFileInfo($file); + } + + $fileContent = file_get_contents($file->getRealPath()); + preg_match_all('/lang\(\'([._a-z0-9\-]+)\'\)/ui', $fileContent, $matches); + + if (empty($matches[1])) { + return []; + } + + foreach ($matches[1] as $phraseKey) { + $phraseKeys = explode('.', $phraseKey); + + /** + * Language key not have Filename or Lang key + */ + if (count($phraseKeys) < 2) { + continue; + } + + $languageFileName = array_shift($phraseKeys); + $isEmptyNestedArray = (! empty($languageFileName) && empty($phraseKeys[0])) + || (empty($languageFileName) && ! empty($phraseKeys[0])) + || (empty($languageFileName) && empty($phraseKeys[0])); + + if ($isEmptyNestedArray) { + continue; + } + + /** + * Language key as string + */ + if (count($phraseKeys) === 1) { + /** + * Add found language keys to temporary array + */ + $languageFoundKeys[$languageFileName][$phraseKeys[0]] = $phraseKey; + } else { + $childKeys = $this->buildMultiArray($phraseKeys, $phraseKey); + $languageFoundKeys[$languageFileName] = array_replace_recursive($languageFoundKeys[$languageFileName] ?? [], $childKeys); + } + } + + return $languageFoundKeys; + } + + private function isIgnoredFile(SplFileInfo $file): bool + { + if ($file->isDir() || str_contains($file->getRealPath(), $this->languagePath)) { + return true; + } + + return 'php' !== $file->getExtension(); + } + + private function templateFile(array $language = []): string + { + if (! empty($language)) { + $languageArrayString = $this->languageKeysDump($language); + + return << $originalValue) { + if (is_array($originalValue)) { + $diffArrays = null; + + if (isset($compareArray[$originalKey])) { + $diffArrays = $this->arrayDiffRecursive($originalValue, $compareArray[$originalKey]); + } else { + $difference[$originalKey] = $originalValue; + } + + if (! empty($diffArrays)) { + $difference[$originalKey] = $diffArrays; + } + } elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareArray)) { + $difference[$originalKey] = $originalValue; + } + } + + return $difference; + } + + /** + * Convert multi arrays to specific CLI table rows (flat array) + */ + private function arrayToTableRows(string $langFileName, array $array): array + { + $rows = []; + + foreach ($array as $value) { + if (is_array($value)) { + $rows = array_merge($rows, $this->arrayToTableRows($langFileName, $value)); + + continue; + } + + if (is_string($value)) { + $rows[] = [$langFileName, $value]; + } + } + + return $rows; + } + + private function arrayCountRecursive(array $array, int $counter = 0): int + { + foreach ($array as $value) { + if (! is_array($value)) { + $counter++; + } else { + $counter = $this->arrayCountRecursive($value, $counter); + } + } + + return $counter; + } + + private function languageKeysDump(array $inputArray): string + { + return var_export($inputArray, true); + } + + /** + * Show details in the console if the flag is set + */ + private function writeIsVerbose(string $text = '', ?string $foreground = null, ?string $background = null): void + { + if ($this->verbose) { + CLI::write($text, $foreground, $background); + } + } +} From 03a81b6926cce37bb7549d91a034e1460aa50de7 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 4 Sep 2023 22:49:22 +0300 Subject: [PATCH 016/290] test: Add test for lang:find command --- .../Services/Translation/TranslationFour.php | 30 +++ .../Services/Translation/TranslationOne.php | 36 +++ .../Services/Translation/TranslationThree.php | 37 +++ .../Services/Translation/TranslationTwo.php | 35 +++ .../Translation/LocalizationFinderTest.php | 221 ++++++++++++++++++ 5 files changed, 359 insertions(+) create mode 100644 tests/_support/Services/Translation/TranslationFour.php create mode 100644 tests/_support/Services/Translation/TranslationOne.php create mode 100644 tests/_support/Services/Translation/TranslationThree.php create mode 100644 tests/_support/Services/Translation/TranslationTwo.php create mode 100644 tests/system/Commands/Translation/LocalizationFinderTest.php diff --git a/tests/_support/Services/Translation/TranslationFour.php b/tests/_support/Services/Translation/TranslationFour.php new file mode 100644 index 000000000000..10f5a7714d51 --- /dev/null +++ b/tests/_support/Services/Translation/TranslationFour.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Services\Translation; + +class TranslationFour +{ + public function list() + { + $translationOne1 = lang('TranslationOne.title'); + $translationOne5 = lang('TranslationOne.last_operation_success'); + + $translationThree1 = lang('TranslationThree.alerts.created'); + $translationThree2 = lang('TranslationThree.alerts.failed_insert'); + + $translationThree5 = lang('TranslationThree.formFields.new.name'); + $translationThree7 = lang('TranslationThree.formFields.new.short_tag'); + + $translationFour1 = lang('Translation-Four.dashed.key-with-dash'); + $translationFour2 = lang('Translation-Four.dashed.key-with-dash-two'); + } +} diff --git a/tests/_support/Services/Translation/TranslationOne.php b/tests/_support/Services/Translation/TranslationOne.php new file mode 100644 index 000000000000..bf6b0e1e31c1 --- /dev/null +++ b/tests/_support/Services/Translation/TranslationOne.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Services\Translation; + +class TranslationOne +{ + public function list() + { + $translationOne1 = lang('TranslationOne.title'); + $translationOne2 = lang('TranslationOne.DESCRIPTION'); + $translationOne3 = lang('TranslationOne.metaTags'); + $translationOne4 = lang('TranslationOne.Copyright'); + $translationOne5 = lang('TranslationOne.last_operation_success'); + + $translationThree1 = lang('TranslationThree.alerts.created'); + $translationThree2 = lang('TranslationThree.alerts.failed_insert'); + $translationThree3 = lang('TranslationThree.alerts.Updated'); + $translationThree4 = lang('TranslationThree.alerts.DELETED'); + + $translationThree5 = lang('TranslationThree.formFields.new.name'); + $translationThree6 = lang('TranslationThree.formFields.new.TEXT'); + $translationThree7 = lang('TranslationThree.formFields.new.short_tag'); + $translationThree8 = lang('TranslationThree.formFields.edit.name'); + $translationThree9 = lang('TranslationThree.formFields.edit.TEXT'); + $translationThree10 = lang('TranslationThree.formFields.edit.short_tag'); + } +} diff --git a/tests/_support/Services/Translation/TranslationThree.php b/tests/_support/Services/Translation/TranslationThree.php new file mode 100644 index 000000000000..d9da9ba67c75 --- /dev/null +++ b/tests/_support/Services/Translation/TranslationThree.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Services\Translation; + +class TranslationThree +{ + public function list() + { + $translationOne1 = lang('TranslationOne.title'); + $translationOne2 = lang('TranslationOne.DESCRIPTION'); + $translationOne6 = lang('TranslationOne.subTitle'); + $translationOne7 = lang('TranslationOne.overflow_style'); + + $translationThree1 = lang('TranslationThree.alerts.created'); + $translationThree2 = lang('TranslationThree.alerts.failed_insert'); + + $translationThree5 = lang('TranslationThree.formFields.new.name'); + $translationThree6 = lang('TranslationThree.formFields.new.TEXT'); + $translationThree7 = lang('TranslationThree.formFields.new.short_tag'); + + $translationThree11 = lang('TranslationThree.alerts.CANCELED'); + $translationThree12 = lang('TranslationThree.alerts.missing_keys'); + + $translationThree13 = lang('TranslationThree.formErrors.edit.empty_name'); + $translationThree14 = lang('TranslationThree.formErrors.edit.INVALID_TEXT'); + $translationThree15 = lang('TranslationThree.formErrors.edit.missing_short_tag'); + } +} diff --git a/tests/_support/Services/Translation/TranslationTwo.php b/tests/_support/Services/Translation/TranslationTwo.php new file mode 100644 index 000000000000..be40786b5ee2 --- /dev/null +++ b/tests/_support/Services/Translation/TranslationTwo.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Services\Translation; + +class TranslationTwo +{ + public function list() + { + $langKey = 'TranslationTwo.error_key'; + + /** + * Error language keys + */ + $translationError1 = lang('TranslationTwo'); + $translationError2 = lang(' '); + $translationError3 = lang(''); + $translationError4 = lang('.invalid_key'); + $translationError5 = lang('TranslationTwo.'); + $translationError6 = lang('TranslationTwo...'); + $translationError7 = lang('..invalid_nested_key..'); + + /** + * Empty in comments lang('') lang(' ') + */ + } +} diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php new file mode 100644 index 000000000000..a3f15a4ba4cf --- /dev/null +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Commands\Translation; + +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\StreamFilterTrait; +use Config\App; +use Config\Services; +use Locale; + +/** + * @group Others + * + * @internal + */ +final class LocalizationFinderTest extends CIUnitTestCase +{ + use StreamFilterTrait; + + private static string $locale; + private static string $languageTestPath; + + protected function setUp(): void + { + parent::setUp(); + self::$locale = Locale::getDefault(); + self::$languageTestPath = SUPPORTPATH . 'Language/'; + $this->clearGeneratedFiles(); + } + + public function testUpdateDefaultLocale(): void + { + @mkdir(self::$languageTestPath . self::$locale, 0777, true); + Services::commands()->run('lang:find', [ + 'dir' => 'Services/Translation', + ]); + $this->realizeAssertion(); + $this->clearGeneratedFiles(); + } + + public function testUpdateWithLocaleOption(): void + { + self::$locale = config(App::class)->supportedLocales[0]; + @mkdir(self::$languageTestPath . self::$locale, 0777, true); + Services::commands()->run('lang:find', [ + 'dir' => 'Services/Translation', + 'locale' => self::$locale, + ]); + $this->realizeAssertion(); + $this->clearGeneratedFiles(); + } + + public function testUpdateWithIncorrectLocaleOption(): void + { + self::$locale = 'test_locale_incorrect'; + @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $status = Services::commands()->run('lang:find', [ + 'dir' => 'Services/Translation', + 'locale' => self::$locale, + ]); + $this->assertSame($status, -1); + $this->clearGeneratedFiles(); + } + + public function testUpdateWithEmptyDirOption(): void + { + @mkdir(self::$languageTestPath . self::$locale, 0777, true); + Services::commands()->run('lang:find', []); + $this->realizeAssertion(); + $this->clearGeneratedFiles(); + } + + public function testUpdateWithIncorrectDirOption(): void + { + @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $status = Services::commands()->run('lang:find', [ + 'dir' => 'Services/Translation/NotExistFolder', + ]); + $this->assertSame($status, -1); + $this->clearGeneratedFiles(); + } + + public function testShowNewTranslation(): void + { + @mkdir(self::$languageTestPath . self::$locale, 0777, true); + Services::commands()->run('lang:find', [ + 'dir' => 'Services/Translation', + 'show-new' => null, + ]); + $this->assertStringContainsString($this->getActualTableWithNewKeys(), $this->getStreamFilterBuffer()); + $this->clearGeneratedFiles(); + } + + private function getActualTranslationOneKeys(): array + { + return [ + 'title' => 'TranslationOne.title', + 'DESCRIPTION' => 'TranslationOne.DESCRIPTION', + 'subTitle' => 'TranslationOne.subTitle', + 'overflow_style' => 'TranslationOne.overflow_style', + 'metaTags' => 'TranslationOne.metaTags', + 'Copyright' => 'TranslationOne.Copyright', + 'last_operation_success' => 'TranslationOne.last_operation_success', + ]; + } + + private function getActualTranslationThreeKeys(): array + { + return [ + 'alerts' => [ + 'created' => 'TranslationThree.alerts.created', + 'failed_insert' => 'TranslationThree.alerts.failed_insert', + 'CANCELED' => 'TranslationThree.alerts.CANCELED', + 'missing_keys' => 'TranslationThree.alerts.missing_keys', + 'Updated' => 'TranslationThree.alerts.Updated', + 'DELETED' => 'TranslationThree.alerts.DELETED', + ], + 'formFields' => [ + 'new' => [ + 'name' => 'TranslationThree.formFields.new.name', + 'TEXT' => 'TranslationThree.formFields.new.TEXT', + 'short_tag' => 'TranslationThree.formFields.new.short_tag', + ], + 'edit' => [ + 'name' => 'TranslationThree.formFields.edit.name', + 'TEXT' => 'TranslationThree.formFields.edit.TEXT', + 'short_tag' => 'TranslationThree.formFields.edit.short_tag', + ], + ], + 'formErrors' => [ + 'edit' => [ + 'empty_name' => 'TranslationThree.formErrors.edit.empty_name', + 'INVALID_TEXT' => 'TranslationThree.formErrors.edit.INVALID_TEXT', + 'missing_short_tag' => 'TranslationThree.formErrors.edit.missing_short_tag', + ], + ], + ]; + } + + private function getActualTranslationFourKeys(): array + { + return [ + 'dashed' => [ + 'key-with-dash' => 'Translation-Four.dashed.key-with-dash', + 'key-with-dash-two' => 'Translation-Four.dashed.key-with-dash-two', + ], + ]; + } + + private function getActualTableWithNewKeys(): string + { + return <<<'TEXT_WRAP' + +------------------+----------------------------------------------------+ + | File | Key | + +------------------+----------------------------------------------------+ + | Translation-Four | Translation-Four.dashed.key-with-dash | + | Translation-Four | Translation-Four.dashed.key-with-dash-two | + | TranslationOne | TranslationOne.Copyright | + | TranslationOne | TranslationOne.DESCRIPTION | + | TranslationOne | TranslationOne.last_operation_success | + | TranslationOne | TranslationOne.metaTags | + | TranslationOne | TranslationOne.overflow_style | + | TranslationOne | TranslationOne.subTitle | + | TranslationOne | TranslationOne.title | + | TranslationThree | TranslationThree.alerts.CANCELED | + | TranslationThree | TranslationThree.alerts.DELETED | + | TranslationThree | TranslationThree.alerts.Updated | + | TranslationThree | TranslationThree.alerts.created | + | TranslationThree | TranslationThree.alerts.failed_insert | + | TranslationThree | TranslationThree.alerts.missing_keys | + | TranslationThree | TranslationThree.formErrors.edit.INVALID_TEXT | + | TranslationThree | TranslationThree.formErrors.edit.empty_name | + | TranslationThree | TranslationThree.formErrors.edit.missing_short_tag | + | TranslationThree | TranslationThree.formFields.edit.TEXT | + | TranslationThree | TranslationThree.formFields.edit.name | + | TranslationThree | TranslationThree.formFields.edit.short_tag | + | TranslationThree | TranslationThree.formFields.new.TEXT | + | TranslationThree | TranslationThree.formFields.new.name | + | TranslationThree | TranslationThree.formFields.new.short_tag | + +------------------+----------------------------------------------------+ + TEXT_WRAP; + } + + private function realizeAssertion(): void + { + $this->assertFileExists(self::$languageTestPath . self::$locale . '/TranslationOne.php'); + $this->assertFileExists(self::$languageTestPath . self::$locale . '/TranslationThree.php'); + + $translationOneKeys = require self::$languageTestPath . self::$locale . '/TranslationOne.php'; + $translationThreeKeys = require self::$languageTestPath . self::$locale . '/TranslationThree.php'; + $translationFourKeys = require self::$languageTestPath . self::$locale . '/Translation-Four.php'; + + $this->assertSame($translationOneKeys, $this->getActualTranslationOneKeys()); + $this->assertSame($translationThreeKeys, $this->getActualTranslationThreeKeys()); + $this->assertSame($translationFourKeys, $this->getActualTranslationFourKeys()); + } + + private function clearGeneratedFiles(): void + { + if (is_file(self::$languageTestPath . self::$locale . '/TranslationOne.php')) { + unlink(self::$languageTestPath . self::$locale . '/TranslationOne.php'); + } + + if (is_file(self::$languageTestPath . self::$locale . '/TranslationThree.php')) { + unlink(self::$languageTestPath . self::$locale . '/TranslationThree.php'); + } + + if (is_file(self::$languageTestPath . self::$locale . '/Translation-Four.php')) { + unlink(self::$languageTestPath . self::$locale . '/Translation-Four.php'); + } + } +} From 624ba0187a550e9aa87b84d821b68e66c9700be6 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 4 Sep 2023 22:55:01 +0300 Subject: [PATCH 017/290] docs: Description using command --- .../source/outgoing/localization.rst | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index f84e9b029004..3f5aa7bb77c3 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -254,3 +254,42 @@ project: The translated messages will be automatically picked up because the translations folders get mapped appropriately. + + +Automatic Translation Search via Command +======================================== + +.. versionadded:: 4.5.0 + +You can automatically find and update translation files in your **app/** folder. The command will search for the use of the ``lang()`` function, combine the current translation keys in **app/Language** by defining the locale ``defaultLocale`` from **Config/App**. +After the operation, you need to translate the language keys yourself. +The command is able to recognize nested keys normally ``File.array.nested.text``. +Previously saved keys do not change. + +.. code-block:: console + + php spark lang:find + +.. literalinclude:: localization/019.php + + +.. note:: Command ignoring **app/Language/** path. + + +Before updating, it is possible to preview the translations found by the command: + +.. code-block:: console + + php spark lang:find --verbose --show-new + +For a more accurate search, specify the desired locale or directory to scan. + +.. code-block:: console + + php spark lang:find --dir Controllers/Translation --locale en --show-new + +Detailed information can be found by running the command: + +.. code-block:: console + + php spark help lang:find From 034f9ddf6efc20ea9b290db50c5908283d687164 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 4 Sep 2023 22:55:24 +0300 Subject: [PATCH 018/290] docs: Example code with command --- user_guide_src/source/outgoing/localization/019.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 user_guide_src/source/outgoing/localization/019.php diff --git a/user_guide_src/source/outgoing/localization/019.php b/user_guide_src/source/outgoing/localization/019.php new file mode 100644 index 000000000000..ed16f1a056db --- /dev/null +++ b/user_guide_src/source/outgoing/localization/019.php @@ -0,0 +1,13 @@ + [ + 'success' => 'Text.info.success', + ], + 'paragraph' => 'Text.paragraph', +]; From 1fc285298f64493f472ab8169551c75a89a73271 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 5 Sep 2023 14:10:20 +0300 Subject: [PATCH 019/290] fix: Sorting of found files --- system/Commands/Translation/LocalizationFinder.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 37f6d0be7d2c..e8ac280fcd0c 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -88,6 +88,8 @@ public function run(array $params) } $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($currentDir)); + $files = iterator_to_array($iterator, true); + ksort($files); $this->writeIsVerbose('Current locale: ' . $currentLocale); $this->writeIsVerbose('Find phrases in ' . $currentDir . ' folder...'); From 119f386048be910813790bb7211b0a2c8d41e16e Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 5 Sep 2023 14:13:47 +0300 Subject: [PATCH 020/290] test: Reduce the search depth --- system/Commands/Translation/LocalizationFinder.php | 4 ++-- .../Commands/Translation/LocalizationFinderTest.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index e8ac280fcd0c..4e60b908fe18 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -55,8 +55,8 @@ public function run(array $params) $countNewKeys = 0; if (ENVIRONMENT === 'testing') { - $currentDir = SUPPORTPATH; - $this->languagePath = $currentDir . 'Language/'; + $currentDir = SUPPORTPATH . 'Services/'; + $this->languagePath = SUPPORTPATH . 'Language/'; } if (is_string($cliOptionLocale)) { diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index a3f15a4ba4cf..2d0aca08c90c 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -41,7 +41,7 @@ public function testUpdateDefaultLocale(): void { @mkdir(self::$languageTestPath . self::$locale, 0777, true); Services::commands()->run('lang:find', [ - 'dir' => 'Services/Translation', + 'dir' => 'Translation', ]); $this->realizeAssertion(); $this->clearGeneratedFiles(); @@ -52,7 +52,7 @@ public function testUpdateWithLocaleOption(): void self::$locale = config(App::class)->supportedLocales[0]; @mkdir(self::$languageTestPath . self::$locale, 0777, true); Services::commands()->run('lang:find', [ - 'dir' => 'Services/Translation', + 'dir' => 'Translation', 'locale' => self::$locale, ]); $this->realizeAssertion(); @@ -64,7 +64,7 @@ public function testUpdateWithIncorrectLocaleOption(): void self::$locale = 'test_locale_incorrect'; @mkdir(self::$languageTestPath . self::$locale, 0777, true); $status = Services::commands()->run('lang:find', [ - 'dir' => 'Services/Translation', + 'dir' => 'Translation', 'locale' => self::$locale, ]); $this->assertSame($status, -1); @@ -83,7 +83,7 @@ public function testUpdateWithIncorrectDirOption(): void { @mkdir(self::$languageTestPath . self::$locale, 0777, true); $status = Services::commands()->run('lang:find', [ - 'dir' => 'Services/Translation/NotExistFolder', + 'dir' => 'Translation/NotExistFolder', ]); $this->assertSame($status, -1); $this->clearGeneratedFiles(); @@ -93,7 +93,7 @@ public function testShowNewTranslation(): void { @mkdir(self::$languageTestPath . self::$locale, 0777, true); Services::commands()->run('lang:find', [ - 'dir' => 'Services/Translation', + 'dir' => 'Translation', 'show-new' => null, ]); $this->assertStringContainsString($this->getActualTableWithNewKeys(), $this->getStreamFilterBuffer()); From e6707619edde63ba23c5345730105c936ca5dc11 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 5 Sep 2023 14:16:24 +0300 Subject: [PATCH 021/290] test: Added missed actions --- tests/system/Commands/Translation/LocalizationFinderTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index 2d0aca08c90c..ceeb085ea432 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -194,6 +194,7 @@ private function realizeAssertion(): void { $this->assertFileExists(self::$languageTestPath . self::$locale . '/TranslationOne.php'); $this->assertFileExists(self::$languageTestPath . self::$locale . '/TranslationThree.php'); + $this->assertFileExists(self::$languageTestPath . self::$locale . '/Translation-Four.php'); $translationOneKeys = require self::$languageTestPath . self::$locale . '/TranslationOne.php'; $translationThreeKeys = require self::$languageTestPath . self::$locale . '/TranslationThree.php'; @@ -217,5 +218,9 @@ private function clearGeneratedFiles(): void if (is_file(self::$languageTestPath . self::$locale . '/Translation-Four.php')) { unlink(self::$languageTestPath . self::$locale . '/Translation-Four.php'); } + + if (is_dir(self::$languageTestPath . '/test_locale_incorrect')) { + rmdir(self::$languageTestPath . '/test_locale_incorrect'); + } } } From 9602e7909ce382e39b5c30580b1ee04dbb23c865 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 5 Sep 2023 14:18:53 +0300 Subject: [PATCH 022/290] test: Move test file to subfolder --- .../Translation/{ => TranslationNested}/TranslationFour.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/_support/Services/Translation/{ => TranslationNested}/TranslationFour.php (94%) diff --git a/tests/_support/Services/Translation/TranslationFour.php b/tests/_support/Services/Translation/TranslationNested/TranslationFour.php similarity index 94% rename from tests/_support/Services/Translation/TranslationFour.php rename to tests/_support/Services/Translation/TranslationNested/TranslationFour.php index 10f5a7714d51..545e821bb049 100644 --- a/tests/_support/Services/Translation/TranslationFour.php +++ b/tests/_support/Services/Translation/TranslationNested/TranslationFour.php @@ -9,7 +9,7 @@ * the LICENSE file that was distributed with this source code. */ -namespace Tests\Support\Services\Translation; +namespace Tests\Support\Services\Translation\Nested; class TranslationFour { From 49d2c5745bb58dccf87d9a5ac6b4e97790f1e5dc Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 06:26:40 +0300 Subject: [PATCH 023/290] fix: Missing sorted files in a loop --- system/Commands/Translation/LocalizationFinder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 4e60b908fe18..f3ed2d6c66b8 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -96,7 +96,7 @@ public function run(array $params) /** * @var SplFileInfo $file */ - foreach ($iterator as $file) { + foreach ($files as $file) { if ($this->isIgnoredFile($file)) { continue; } From 13d93ea2edccba598768e05b948cc4ada9266a7c Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 06:35:46 +0300 Subject: [PATCH 024/290] fix: Remove prefix variables --- .../Translation/LocalizationFinder.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index f3ed2d6c66b8..cf1a6f001752 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -43,9 +43,9 @@ class LocalizationFinder extends BaseCommand public function run(array $params) { $this->verbose = array_key_exists('verbose', $params); - $cliOptionShowNew = array_key_exists('show-new', $params); - $cliOptionLocale = ! empty($params['locale']) ? $params['locale'] : null; - $cliOptionDir = ! empty($params['dir']) ? $params['dir'] : null; + $optionShowNew = array_key_exists('show-new', $params); + $optionLocale = ! empty($params['locale']) ? $params['locale'] : null; + $optionDir = ! empty($params['dir']) ? $params['dir'] : null; $currentLocale = Locale::getDefault(); $currentDir = APPPATH; $this->languagePath = $currentDir . 'Language'; @@ -59,18 +59,18 @@ public function run(array $params) $this->languagePath = SUPPORTPATH . 'Language/'; } - if (is_string($cliOptionLocale)) { - if (! in_array($cliOptionLocale, config(App::class)->supportedLocales, true)) { + if (is_string($optionLocale)) { + if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) { CLI::error('Error: Supported locales ' . implode(', ', config(App::class)->supportedLocales)); return -1; } - $currentLocale = $cliOptionLocale; + $currentLocale = $optionLocale; } - if (is_string($cliOptionDir)) { - $tempCurrentDir = realpath($currentDir . $cliOptionDir); + if (is_string($optionDir)) { + $tempCurrentDir = realpath($currentDir . $optionDir); if (false === $tempCurrentDir) { CLI::error('Error: Dir must be located in ' . $currentDir); @@ -124,7 +124,7 @@ public function run(array $params) $languageDiff = $this->arrayDiffRecursive($languageFoundKeys[$langFileName], $languageStoredKeys); $countNewKeys += $this->arrayCountRecursive($languageDiff); - if ($cliOptionShowNew) { + if ($optionShowNew) { $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); } else { $newLanguageKeys = array_replace_recursive($languageFoundKeys[$langFileName], $languageStoredKeys); @@ -146,7 +146,7 @@ public function run(array $params) } } - if ($cliOptionShowNew && ! empty($tableRows)) { + if ($optionShowNew && ! empty($tableRows)) { sort($tableRows); CLI::table($tableRows, ['File', 'Key']); } From 31457727765af2aad2d062052c191245bbce7a63 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 06:36:33 +0300 Subject: [PATCH 025/290] fix: Typo in desciption --- system/Commands/Translation/LocalizationFinder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index cf1a6f001752..58f229edc368 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -23,7 +23,7 @@ class LocalizationFinder extends BaseCommand { protected $group = 'Translation'; protected $name = 'lang:find'; - protected $description = 'Find and save available phrases to translate'; + protected $description = 'Find and save available phrases to translate.'; protected $usage = 'lang:find [options]'; protected $arguments = []; protected $options = [ From 10c81bc9b780550d42e82e2a315134273c08ce1c Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 06:43:45 +0300 Subject: [PATCH 026/290] fix: Return with a constant exit code --- system/Commands/Translation/LocalizationFinder.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 58f229edc368..4d9872260e6a 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -63,7 +63,7 @@ public function run(array $params) if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) { CLI::error('Error: Supported locales ' . implode(', ', config(App::class)->supportedLocales)); - return -1; + return EXIT_USER_INPUT; } $currentLocale = $optionLocale; @@ -75,13 +75,13 @@ public function run(array $params) if (false === $tempCurrentDir) { CLI::error('Error: Dir must be located in ' . $currentDir); - return -1; + return EXIT_USER_INPUT; } if (str_starts_with($tempCurrentDir, $this->languagePath)) { CLI::error('Error: Dir ' . $this->languagePath . ' restricted to scan.'); - return -1; + return EXIT_USER_INPUT; } $currentDir = $tempCurrentDir; @@ -154,6 +154,8 @@ public function run(array $params) $this->writeIsVerbose('Files found: ' . $countFiles); $this->writeIsVerbose('New translates found: ' . $countNewKeys); CLI::write('All operations done!'); + + return EXIT_SUCCESS; } /** From 00284d1b235b9e70aad534dd6468d4725b98fb2b Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 06:49:26 +0300 Subject: [PATCH 027/290] fix: Error text is corrected --- system/Commands/Translation/LocalizationFinder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 4d9872260e6a..34923ac80648 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -79,7 +79,7 @@ public function run(array $params) } if (str_starts_with($tempCurrentDir, $this->languagePath)) { - CLI::error('Error: Dir ' . $this->languagePath . ' restricted to scan.'); + CLI::error('Error: Directory "' . $this->languagePath . '" restricted to scan.'); return EXIT_USER_INPUT; } From 3b86c1ae9870c044de072717d6200ff3a14fcfa3 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 06:51:31 +0300 Subject: [PATCH 028/290] fix: Format saved array code --- system/Commands/Translation/LocalizationFinder.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 34923ac80648..9cdd7b574f7f 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -230,6 +230,7 @@ private function templateFile(array $language = []): string Date: Wed, 6 Sep 2023 07:03:01 +0300 Subject: [PATCH 029/290] fix: Add method test is subfolder --- system/Commands/Translation/LocalizationFinder.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 9cdd7b574f7f..81056bdde9fc 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -78,7 +78,7 @@ public function run(array $params) return EXIT_USER_INPUT; } - if (str_starts_with($tempCurrentDir, $this->languagePath)) { + if ($this->isSubDirectory($tempCurrentDir, $this->languagePath)) { CLI::error('Error: Directory "' . $this->languagePath . '" restricted to scan.'); return EXIT_USER_INPUT; @@ -342,4 +342,9 @@ private function writeIsVerbose(string $text = '', ?string $foreground = null, ? CLI::write($text, $foreground, $background); } } + + public function isSubDirectory(string $directory, string $rootDirectory): bool + { + return 0 === strncmp($directory, $rootDirectory, strlen($directory)); + } } From 168c5af43e7f133e210bb398f27c74344ea8f877 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 07:17:35 +0300 Subject: [PATCH 030/290] fix: Move actions to tearDown() --- .../Commands/Translation/LocalizationFinderTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index ceeb085ea432..774bfd45f997 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -37,6 +37,12 @@ protected function setUp(): void $this->clearGeneratedFiles(); } + protected function tearDown(): void + { + parent::tearDown(); + $this->clearGeneratedFiles(); + } + public function testUpdateDefaultLocale(): void { @mkdir(self::$languageTestPath . self::$locale, 0777, true); @@ -44,7 +50,6 @@ public function testUpdateDefaultLocale(): void 'dir' => 'Translation', ]); $this->realizeAssertion(); - $this->clearGeneratedFiles(); } public function testUpdateWithLocaleOption(): void @@ -56,7 +61,6 @@ public function testUpdateWithLocaleOption(): void 'locale' => self::$locale, ]); $this->realizeAssertion(); - $this->clearGeneratedFiles(); } public function testUpdateWithIncorrectLocaleOption(): void @@ -68,7 +72,6 @@ public function testUpdateWithIncorrectLocaleOption(): void 'locale' => self::$locale, ]); $this->assertSame($status, -1); - $this->clearGeneratedFiles(); } public function testUpdateWithEmptyDirOption(): void @@ -76,7 +79,6 @@ public function testUpdateWithEmptyDirOption(): void @mkdir(self::$languageTestPath . self::$locale, 0777, true); Services::commands()->run('lang:find', []); $this->realizeAssertion(); - $this->clearGeneratedFiles(); } public function testUpdateWithIncorrectDirOption(): void @@ -86,7 +88,6 @@ public function testUpdateWithIncorrectDirOption(): void 'dir' => 'Translation/NotExistFolder', ]); $this->assertSame($status, -1); - $this->clearGeneratedFiles(); } public function testShowNewTranslation(): void @@ -97,7 +98,6 @@ public function testShowNewTranslation(): void 'show-new' => null, ]); $this->assertStringContainsString($this->getActualTableWithNewKeys(), $this->getStreamFilterBuffer()); - $this->clearGeneratedFiles(); } private function getActualTranslationOneKeys(): array From 37ece4ecd100a0377fe2c5b53b6f0cfa3be9a7e3 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 07:20:20 +0300 Subject: [PATCH 031/290] fix: Add makeLocaleDirectory() --- .../Translation/LocalizationFinderTest.php | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index 774bfd45f997..d5958efc07ee 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -45,58 +45,70 @@ protected function tearDown(): void public function testUpdateDefaultLocale(): void { - @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $this->makeLocaleDirectory(); + Services::commands()->run('lang:find', [ 'dir' => 'Translation', ]); + $this->realizeAssertion(); } public function testUpdateWithLocaleOption(): void { self::$locale = config(App::class)->supportedLocales[0]; - @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $this->makeLocaleDirectory(); + Services::commands()->run('lang:find', [ 'dir' => 'Translation', 'locale' => self::$locale, ]); + $this->realizeAssertion(); } public function testUpdateWithIncorrectLocaleOption(): void { self::$locale = 'test_locale_incorrect'; - @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $this->makeLocaleDirectory(); + $status = Services::commands()->run('lang:find', [ 'dir' => 'Translation', 'locale' => self::$locale, ]); + $this->assertSame($status, -1); } public function testUpdateWithEmptyDirOption(): void { - @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $this->makeLocaleDirectory(); + Services::commands()->run('lang:find', []); + $this->realizeAssertion(); } public function testUpdateWithIncorrectDirOption(): void { - @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $this->makeLocaleDirectory(); + $status = Services::commands()->run('lang:find', [ 'dir' => 'Translation/NotExistFolder', ]); + $this->assertSame($status, -1); } public function testShowNewTranslation(): void { - @mkdir(self::$languageTestPath . self::$locale, 0777, true); + $this->makeLocaleDirectory(); + Services::commands()->run('lang:find', [ 'dir' => 'Translation', 'show-new' => null, ]); + $this->assertStringContainsString($this->getActualTableWithNewKeys(), $this->getStreamFilterBuffer()); } @@ -205,6 +217,11 @@ private function realizeAssertion(): void $this->assertSame($translationFourKeys, $this->getActualTranslationFourKeys()); } + private function makeLocaleDirectory(): void + { + @mkdir(self::$languageTestPath . self::$locale, 0777, true); + } + private function clearGeneratedFiles(): void { if (is_file(self::$languageTestPath . self::$locale . '/TranslationOne.php')) { From 9982db0870e8b794d7ff68e4aae5a5a239364e2f Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 07:25:36 +0300 Subject: [PATCH 032/290] docs: Correct spelling --- user_guide_src/source/outgoing/localization.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index 3f5aa7bb77c3..3cffd3ed85e7 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -256,12 +256,12 @@ The translated messages will be automatically picked up because the translations folders get mapped appropriately. -Automatic Translation Search via Command +Generating Translation Files via Command ======================================== .. versionadded:: 4.5.0 -You can automatically find and update translation files in your **app/** folder. The command will search for the use of the ``lang()`` function, combine the current translation keys in **app/Language** by defining the locale ``defaultLocale`` from **Config/App**. +You can automatically generate and update translation files in your **app** folder. The command will search for the use of the ``lang()`` function, combine the current translation keys in **app/Language** by defining the locale ``defaultLocale`` from ``Config\App``. After the operation, you need to translate the language keys yourself. The command is able to recognize nested keys normally ``File.array.nested.text``. Previously saved keys do not change. @@ -273,7 +273,7 @@ Previously saved keys do not change. .. literalinclude:: localization/019.php -.. note:: Command ignoring **app/Language/** path. +.. note:: The command ignores **app/Language/** path. Before updating, it is possible to preview the translations found by the command: @@ -292,4 +292,4 @@ Detailed information can be found by running the command: .. code-block:: console - php spark help lang:find + php spark lang:find --help From d7a724b42c953ac2f28037a1b94ce89a5f75d522 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 07:29:57 +0300 Subject: [PATCH 033/290] fix: Assert returned code --- tests/system/Commands/Translation/LocalizationFinderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index d5958efc07ee..40d01efb820b 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -77,7 +77,7 @@ public function testUpdateWithIncorrectLocaleOption(): void 'locale' => self::$locale, ]); - $this->assertSame($status, -1); + $this->assertSame($status, EXIT_USER_INPUT); } public function testUpdateWithEmptyDirOption(): void @@ -97,7 +97,7 @@ public function testUpdateWithIncorrectDirOption(): void 'dir' => 'Translation/NotExistFolder', ]); - $this->assertSame($status, -1); + $this->assertSame($status, EXIT_USER_INPUT); } public function testShowNewTranslation(): void From 3b9766498aee6c2206b99852437d285bd8fe8c0d Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 07:37:03 +0300 Subject: [PATCH 034/290] fix: Method to private --- system/Commands/Translation/LocalizationFinder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 81056bdde9fc..162fc8184708 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -343,7 +343,7 @@ private function writeIsVerbose(string $text = '', ?string $foreground = null, ? } } - public function isSubDirectory(string $directory, string $rootDirectory): bool + private function isSubDirectory(string $directory, string $rootDirectory): bool { return 0 === strncmp($directory, $rootDirectory, strlen($directory)); } From ca3652b07502eb8758c66b3c84794464d2a1d87a Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Wed, 6 Sep 2023 08:37:46 +0300 Subject: [PATCH 035/290] fix: Move long code to process() --- .../Translation/LocalizationFinder.php | 160 ++++++++++-------- 1 file changed, 90 insertions(+), 70 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 162fc8184708..10afe46d6ab2 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -38,21 +38,22 @@ class LocalizationFinder extends BaseCommand */ private bool $verbose = false; + /** + * Flag for showing only translations, without saving + */ + private bool $showNew = false; + private string $languagePath; public function run(array $params) { $this->verbose = array_key_exists('verbose', $params); - $optionShowNew = array_key_exists('show-new', $params); + $this->showNew = array_key_exists('show-new', $params); $optionLocale = ! empty($params['locale']) ? $params['locale'] : null; $optionDir = ! empty($params['dir']) ? $params['dir'] : null; $currentLocale = Locale::getDefault(); $currentDir = APPPATH; $this->languagePath = $currentDir . 'Language'; - $tableRows = []; - $languageFoundKeys = []; - $countFiles = 0; - $countNewKeys = 0; if (ENVIRONMENT === 'testing') { $currentDir = SUPPORTPATH . 'Services/'; @@ -87,72 +88,8 @@ public function run(array $params) $currentDir = $tempCurrentDir; } - $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($currentDir)); - $files = iterator_to_array($iterator, true); - ksort($files); - $this->writeIsVerbose('Current locale: ' . $currentLocale); - $this->writeIsVerbose('Find phrases in ' . $currentDir . ' folder...'); - - /** - * @var SplFileInfo $file - */ - foreach ($files as $file) { - if ($this->isIgnoredFile($file)) { - continue; - } - - $this->writeIsVerbose('File found: ' . mb_substr($file->getRealPath(), mb_strlen(APPPATH))); - $countFiles++; - $languageFoundKeys = array_replace_recursive($this->findTranslationsInFile($file), $languageFoundKeys); - } - - ksort($languageFoundKeys); - $languageDiff = []; - $languageFoundGroups = array_unique(array_keys($languageFoundKeys)); - - foreach ($languageFoundGroups as $langFileName) { - $languageStoredKeys = []; - $languageFilePath = $this->languagePath . DIRECTORY_SEPARATOR . $currentLocale . DIRECTORY_SEPARATOR . $langFileName . '.php'; - - if (is_file($languageFilePath)) { - /** - * Load old localization - */ - $languageStoredKeys = require $languageFilePath; - } + $this->process($currentDir, $currentLocale); - $languageDiff = $this->arrayDiffRecursive($languageFoundKeys[$langFileName], $languageStoredKeys); - $countNewKeys += $this->arrayCountRecursive($languageDiff); - - if ($optionShowNew) { - $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); - } else { - $newLanguageKeys = array_replace_recursive($languageFoundKeys[$langFileName], $languageStoredKeys); - - /** - * New translates exists - */ - if ($languageDiff !== []) { - if (false === file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys))) { - $this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red'); - } else { - /** - * FIXME: Need help command (slow run) - */ - exec('composer cs-fix --quiet ' . $this->languagePath); - $this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green'); - } - } - } - } - - if ($optionShowNew && ! empty($tableRows)) { - sort($tableRows); - CLI::table($tableRows, ['File', 'Key']); - } - - $this->writeIsVerbose('Files found: ' . $countFiles); - $this->writeIsVerbose('New translates found: ' . $countNewKeys); CLI::write('All operations done!'); return EXIT_SUCCESS; @@ -347,4 +284,87 @@ private function isSubDirectory(string $directory, string $rootDirectory): bool { return 0 === strncmp($directory, $rootDirectory, strlen($directory)); } + + private function process(string $currentDir, string $currentLocale): void + { + $tableRows = []; + $countNewKeys = 0; + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($currentDir)); + $files = iterator_to_array($iterator, true); + ksort($files); + + [$languageFoundKeys, $countFiles] = $this->findLanguageKeysInFiles($files); + ksort($languageFoundKeys); + + /** + * New translates + */ + $languageDiff = []; + $languageFoundGroups = array_unique(array_keys($languageFoundKeys)); + + foreach ($languageFoundGroups as $langFileName) { + $languageStoredKeys = []; + $languageFilePath = $this->languagePath . DIRECTORY_SEPARATOR . $currentLocale . DIRECTORY_SEPARATOR . $langFileName . '.php'; + + if (is_file($languageFilePath)) { + /** + * Load old localization + */ + $languageStoredKeys = require $languageFilePath; + } + + $languageDiff = $this->arrayDiffRecursive($languageFoundKeys[$langFileName], $languageStoredKeys); + $countNewKeys += $this->arrayCountRecursive($languageDiff); + + if ($this->showNew) { + $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); + } else { + $newLanguageKeys = array_replace_recursive($languageFoundKeys[$langFileName], $languageStoredKeys); + + /** + * New translates exists + */ + if ($languageDiff !== []) { + if (false === file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys))) { + $this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red'); + } else { + exec('composer cs-fix --quiet ' . $this->languagePath); + $this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green'); + } + } + } + } + + if ($this->showNew && ! empty($tableRows)) { + sort($tableRows); + CLI::table($tableRows, ['File', 'Key']); + } + + $this->writeIsVerbose('Files found: ' . $countFiles); + $this->writeIsVerbose('New translates found: ' . $countNewKeys); + } + + /** + * @param SplFileInfo[] $files + * + * @return array + */ + private function findLanguageKeysInFiles(array $files): array + { + $languageFoundKeys = []; + $countFiles = 0; + + foreach ($files as $file) { + if ($this->isIgnoredFile($file)) { + continue; + } + + $this->writeIsVerbose('File found: ' . mb_substr($file->getRealPath(), mb_strlen(APPPATH))); + $countFiles++; + $languageFoundKeys = array_replace_recursive($this->findTranslationsInFile($file), $languageFoundKeys); + } + + return [$languageFoundKeys, $countFiles]; + } } From 672f8f8325bb567d5b82834a7963d7970cd8a3d8 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 10:47:50 +0300 Subject: [PATCH 036/290] docs: add changelog --- user_guide_src/source/changelogs/v4.5.0.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index d18f0884e0ce..29c31dcabf8e 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -47,6 +47,8 @@ Enhancements Commands ======== +- Added ``spark lang:find`` command to update translations keys. See :ref:`generating-translation-files-via-command` for the details. + Testing ======= From 84e5ae6f2cd2da4c3265ee1a16f7cac9e02dfdba Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 10:48:56 +0300 Subject: [PATCH 037/290] docs: add link --- user_guide_src/source/outgoing/localization.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index 3cffd3ed85e7..e0c1dd4dc4d1 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -255,6 +255,7 @@ project: The translated messages will be automatically picked up because the translations folders get mapped appropriately. +.. _generating-translation-files-via-command: Generating Translation Files via Command ======================================== From 156b9474ccad90f43883ae1e7cd58b97f6e11c93 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 11:34:48 +0300 Subject: [PATCH 038/290] docs: Language correction --- user_guide_src/source/outgoing/localization/019.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/outgoing/localization/019.php b/user_guide_src/source/outgoing/localization/019.php index ed16f1a056db..5de7d9a80731 100644 --- a/user_guide_src/source/outgoing/localization/019.php +++ b/user_guide_src/source/outgoing/localization/019.php @@ -4,7 +4,7 @@ $message = lang('Text.info.success'); $message2 = lang('Text.paragraph'); -// Save in Language/en/Text.php +// The following will be saved in Language/en/Text.php return [ 'info' => [ 'success' => 'Text.info.success', From c56437424a60f21c666e4f648053adb19a89f57c Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 11:40:04 +0300 Subject: [PATCH 039/290] fix: Language correction --- system/Commands/Translation/LocalizationFinder.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 10afe46d6ab2..413afef8803c 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -27,9 +27,9 @@ class LocalizationFinder extends BaseCommand protected $usage = 'lang:find [options]'; protected $arguments = []; protected $options = [ - '--locale' => 'Apply changes to the selected locale (en, ru, etc...).', + '--locale' => 'Specify locale (en, ru, etc...) to save files.', '--dir' => 'Directory for searching for translations (in relation to the APPPATH).', - '--show-new' => 'See only new translations as table.', + '--show-new' => 'See only new translations as table. Do not write to files.', '--verbose' => 'Output detailed information.', ]; @@ -74,7 +74,7 @@ public function run(array $params) $tempCurrentDir = realpath($currentDir . $optionDir); if (false === $tempCurrentDir) { - CLI::error('Error: Dir must be located in ' . $currentDir); + CLI::error('Error: Directory must be located in "' . $currentDir . '"'); return EXIT_USER_INPUT; } @@ -199,7 +199,7 @@ private function buildMultiArray(array $fromKeys, string $lastArrayValue = ''): } /** - * Compare recursive two arrays and return new array (diference) + * Compare recursive two arrays and return new array (difference) */ private function arrayDiffRecursive(array $originalArray, array $compareArray): array { From 62bff7eb018154536ec192e02ac1d0b145f5a79b Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 11:41:55 +0300 Subject: [PATCH 040/290] fix: Shorten the syntax --- system/Commands/Translation/LocalizationFinder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 413afef8803c..db0e78028a7c 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -49,8 +49,8 @@ public function run(array $params) { $this->verbose = array_key_exists('verbose', $params); $this->showNew = array_key_exists('show-new', $params); - $optionLocale = ! empty($params['locale']) ? $params['locale'] : null; - $optionDir = ! empty($params['dir']) ? $params['dir'] : null; + $optionLocale = $params['locale'] ?? null; + $optionDir = $params['dir'] ?? null; $currentLocale = Locale::getDefault(); $currentDir = APPPATH; $this->languagePath = $currentDir . 'Language'; From cde1e0233f62e77e5c3c4bb22f8012de243ecead Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 11:44:52 +0300 Subject: [PATCH 041/290] style: Format code --- system/Commands/Translation/LocalizationFinder.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index db0e78028a7c..4189ccdd6415 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -62,7 +62,10 @@ public function run(array $params) if (is_string($optionLocale)) { if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) { - CLI::error('Error: Supported locales ' . implode(', ', config(App::class)->supportedLocales)); + CLI::error( + 'Error: "' . $optionLocale . '" is not supported. Supported locales: ' + . implode(', ', config(App::class)->supportedLocales) + ); return EXIT_USER_INPUT; } From b3cb72622f5edcf6cf1b4ecf8cf5e88ee7f68b06 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 11:48:02 +0300 Subject: [PATCH 042/290] docs: add @phpstan-return --- system/Commands/Translation/LocalizationFinder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 4189ccdd6415..27bd3cf99b3c 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -352,6 +352,7 @@ private function process(string $currentDir, string $currentLocale): void * @param SplFileInfo[] $files * * @return array + * @phpstan-return list{0: array>, 1: int} */ private function findLanguageKeysInFiles(array $files): array { From 0d3ad09dabc757db675a2734125544124ab5fe7c Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 12:01:41 +0300 Subject: [PATCH 043/290] fix: Replace str_contains() --- system/Commands/Translation/LocalizationFinder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 27bd3cf99b3c..0681c2993bd5 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -154,7 +154,7 @@ private function findTranslationsInFile($file): array private function isIgnoredFile(SplFileInfo $file): bool { - if ($file->isDir() || str_contains($file->getRealPath(), $this->languagePath)) { + if ($file->isDir() || $this->isSubDirectory($file->getRealPath(), $this->languagePath)) { return true; } From b55964ea47a5ba19f238ed358af7d8987521a89a Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 12:03:00 +0300 Subject: [PATCH 044/290] fix: Shorten the syntax --- system/Commands/Translation/LocalizationFinder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 0681c2993bd5..4f8600c912d1 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -163,7 +163,7 @@ private function isIgnoredFile(SplFileInfo $file): bool private function templateFile(array $language = []): string { - if (! empty($language)) { + if ([] !== $language) { $languageArrayString = $this->languageKeysDump($language); return << Date: Fri, 8 Sep 2023 12:06:59 +0300 Subject: [PATCH 045/290] fix: Rename variable to foundLanguageKeys --- .../Translation/LocalizationFinder.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 4f8600c912d1..d601dc67fd36 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -103,7 +103,7 @@ public function run(array $params) */ private function findTranslationsInFile($file): array { - $languageFoundKeys = []; + $foundLanguageKeys = []; if (is_string($file) && is_file($file)) { $file = new SplFileInfo($file); @@ -142,14 +142,14 @@ private function findTranslationsInFile($file): array /** * Add found language keys to temporary array */ - $languageFoundKeys[$languageFileName][$phraseKeys[0]] = $phraseKey; + $foundLanguageKeys[$languageFileName][$phraseKeys[0]] = $phraseKey; } else { $childKeys = $this->buildMultiArray($phraseKeys, $phraseKey); - $languageFoundKeys[$languageFileName] = array_replace_recursive($languageFoundKeys[$languageFileName] ?? [], $childKeys); + $foundLanguageKeys[$languageFileName] = array_replace_recursive($foundLanguageKeys[$languageFileName] ?? [], $childKeys); } } - return $languageFoundKeys; + return $foundLanguageKeys; } private function isIgnoredFile(SplFileInfo $file): bool @@ -297,14 +297,14 @@ private function process(string $currentDir, string $currentLocale): void $files = iterator_to_array($iterator, true); ksort($files); - [$languageFoundKeys, $countFiles] = $this->findLanguageKeysInFiles($files); - ksort($languageFoundKeys); + [$foundLanguageKeys, $countFiles] = $this->findLanguageKeysInFiles($files); + ksort($foundLanguageKeys); /** * New translates */ $languageDiff = []; - $languageFoundGroups = array_unique(array_keys($languageFoundKeys)); + $languageFoundGroups = array_unique(array_keys($foundLanguageKeys)); foreach ($languageFoundGroups as $langFileName) { $languageStoredKeys = []; @@ -317,13 +317,13 @@ private function process(string $currentDir, string $currentLocale): void $languageStoredKeys = require $languageFilePath; } - $languageDiff = $this->arrayDiffRecursive($languageFoundKeys[$langFileName], $languageStoredKeys); + $languageDiff = $this->arrayDiffRecursive($foundLanguageKeys[$langFileName], $languageStoredKeys); $countNewKeys += $this->arrayCountRecursive($languageDiff); if ($this->showNew) { $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); } else { - $newLanguageKeys = array_replace_recursive($languageFoundKeys[$langFileName], $languageStoredKeys); + $newLanguageKeys = array_replace_recursive($foundLanguageKeys[$langFileName], $languageStoredKeys); /** * New translates exists @@ -356,7 +356,7 @@ private function process(string $currentDir, string $currentLocale): void */ private function findLanguageKeysInFiles(array $files): array { - $languageFoundKeys = []; + $foundLanguageKeys = []; $countFiles = 0; foreach ($files as $file) { @@ -366,9 +366,9 @@ private function findLanguageKeysInFiles(array $files): array $this->writeIsVerbose('File found: ' . mb_substr($file->getRealPath(), mb_strlen(APPPATH))); $countFiles++; - $languageFoundKeys = array_replace_recursive($this->findTranslationsInFile($file), $languageFoundKeys); + $foundLanguageKeys = array_replace_recursive($this->findTranslationsInFile($file), $foundLanguageKeys); } - return [$languageFoundKeys, $countFiles]; + return [$foundLanguageKeys, $countFiles]; } } From f6d9b2b63845b59f37a7a1927df626fbb2e15fba Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 12:11:21 +0300 Subject: [PATCH 046/290] tests: Delete unnecessary cleaning --- tests/system/Commands/Translation/LocalizationFinderTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index 40d01efb820b..5d61c4c19bbb 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -34,7 +34,6 @@ protected function setUp(): void parent::setUp(); self::$locale = Locale::getDefault(); self::$languageTestPath = SUPPORTPATH . 'Language/'; - $this->clearGeneratedFiles(); } protected function tearDown(): void From ecad35478d978bbc41d3ece20f127624cc4d0dd2 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 21:08:25 +0300 Subject: [PATCH 047/290] fix: Add array helper as separate class --- .../LocalizationFinder/ArrayHelper.php | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 system/Commands/Translation/LocalizationFinder/ArrayHelper.php diff --git a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php new file mode 100644 index 000000000000..b970c4ae65c4 --- /dev/null +++ b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Commands\Translation\LocalizationFinder; + +class ArrayHelper +{ + /** + * Compare recursive two associative arrays and return new array (difference) + * Returns keys that exist in $original but not in $compareWith + */ + public static function recursiveDiff(array $original, array $compareWith): array + { + $difference = []; + + if ([] === $original) { + return []; + } + + if ([] === $compareWith) { + return $original; + } + + foreach ($original as $originalKey => $originalValue) { + if ([] === $originalValue) { + continue; + } + + if (is_array($originalValue)) { + $diffArrays = []; + + if (isset($compareWith[$originalKey]) && is_array($compareWith[$originalKey])) { + $diffArrays = self::recursiveDiff($originalValue, $compareWith[$originalKey]); + } else { + $difference[$originalKey] = $originalValue; + } + + if ([] !== $diffArrays) { + $difference[$originalKey] = $diffArrays; + } + } elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareWith)) { + $difference[$originalKey] = $originalValue; + } + } + + return $difference; + } + + /** + * Count all keys (with nested keys) + */ + public static function recursiveCount(array $array, int $counter = 0): int + { + foreach ($array as $value) { + if (is_array($value)) { + $counter = self::recursiveCount($value, $counter); + } + + $counter++; + } + + return $counter; + } +} From ddf73f36d3649b631ed4c913b903848acb05da3d Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 21:08:56 +0300 Subject: [PATCH 048/290] test: Add array helper tests --- .../LocalizationFinder/ArrayHelperTest.php | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php diff --git a/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php b/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php new file mode 100644 index 000000000000..499d092521fe --- /dev/null +++ b/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Commands\TranslationLocalizationFinder; + +use CodeIgniter\Commands\Translation\LocalizationFinder\ArrayHelper; +use CodeIgniter\Test\CIUnitTestCase; + +/** + * @group Others + * + * @internal + */ +final class ArrayHelperTest extends CIUnitTestCase +{ + private array $compareWith; + + protected function setUp(): void + { + $this->compareWith = [ + 'a' => [ + 'b' => [ + 'c' => [ + 'd' => 'value1', + ], + ], + 'e' => 'value2', + 'f' => [ + 'g' => 'value3', + 'h' => 'value4', + 'i' => [ + 'j' => 'value5', + ], + ], + 'k' => null, + 'l' => [], + 'm' => '', + ], + ]; + } + + public function testRecursiveDiffCopy(): void + { + $this->assertSame([], ArrayHelper::recursiveDiff($this->compareWith, $this->compareWith)); + } + + public function testRecursiveDiffShuffleCopy(): void + { + $original = [ + 'a' => [ + 'l' => [], + 'k' => null, + 'e' => 'value2', + 'f' => [ + 'i' => [ + 'j' => 'value5', + ], + 'h' => 'value4', + 'g' => 'value3', + ], + 'm' => '', + 'b' => [ + 'c' => [ + 'd' => 'value1', + ], + ], + ], + ]; + + $this->assertSame([], ArrayHelper::recursiveDiff($original, $this->compareWith)); + } + + public function testRecursiveDiffCopyWithAnotherValues(): void + { + $original = [ + 'a' => [ + 'b' => [ + 'c' => [ + 'd' => 'value1_1', + ], + ], + 'e' => 'value2_2', + 'f' => [ + 'g' => 'value3_3', + 'h' => 'value4_4', + 'i' => [ + 'j' => 'value5_5', + ], + ], + 'k' => [], + 'l' => null, + 'm' => 'value6_6', + ], + ]; + + $this->assertSame([], ArrayHelper::recursiveDiff($original, $this->compareWith)); + } + + public function testRecursiveDiffEmptyCompare(): void + { + $this->assertSame($this->compareWith, ArrayHelper::recursiveDiff($this->compareWith, [])); + } + + public function testRecursiveDiffEmptyOriginal(): void + { + $this->assertSame([], ArrayHelper::recursiveDiff([], $this->compareWith)); + } + + public function testRecursiveDiffCompletelyDifferent(): void + { + $original = [ + 'new_a' => [ + 'new_b' => [ + 'new_c' => [ + 'new_d' => 'value1_1', + ], + ], + 'new_e' => 'value2_2', + 'new_f' => [ + 'new_g' => 'value3_3', + 'new_h' => 'value4_4', + 'new_i' => [ + 'new_j' => 'value5_5', + ], + ], + 'new_k' => [], + 'new_l' => null, + 'new_m' => '', + ], + ]; + + $this->assertSame($original, ArrayHelper::recursiveDiff($original, $this->compareWith)); + } + + public function testRecursiveDiffPartlyDifferent(): void + { + $original = [ + 'a' => [ + 'b' => [ + 'new_c' => [ + 'd' => 'value1', + ], + ], + 'e' => 'value2', + 'f' => [ + 'g' => 'value3', + 'new_h' => 'value4', + 'i' => [ + 'new_j' => 'value5', + ], + ], + 'k' => null, + 'new_l' => [], + 'm' => [ + 'new_n' => '', + ], + ], + ]; + + $diff = [ + 'a' => [ + 'b' => [ + 'new_c' => [ + 'd' => 'value1', + ], + ], + 'f' => [ + 'new_h' => 'value4', + 'i' => [ + 'new_j' => 'value5', + ], + ], + 'm' => [ + 'new_n' => '', + ], + ], + ]; + + $this->assertSame($diff, ArrayHelper::recursiveDiff($original, $this->compareWith)); + } + + public function testRecursiveCountSimple(): void + { + $array = [ + 'a' => 'value1', + 'b' => 'value2', + 'c' => 'value3', + ]; + + $this->assertSame(3, ArrayHelper::recursiveCount($array)); + } + + public function testRecursiveCountNested(): void + { + $array = [ + 'a' => 'value1', + 'b' => [ + 'c' => 'value2', + ], + 'd' => 'value3', + 'e' => [ + 'f' => [ + 'g' => 'value4', + 'h' => [ + 'i' => 'value5', + ], + ], + ], + 'j' => [], + 'k' => null, + ]; + + $this->assertSame(11, ArrayHelper::recursiveCount($array)); + } + + public function testRecursiveCountEqualEmpty(): void + { + $array = [ + 'root' => [ + 'a' => [], + 'b' => false, + 'c' => null, + 'd' => '', + 'e' => 0, + ], + ]; + + $this->assertSame(6, ArrayHelper::recursiveCount($array)); + } +} From 812212333a549d656bdacc070b1f2b766870c7eb Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 8 Sep 2023 21:14:06 +0300 Subject: [PATCH 049/290] fix: Move methods to array helper class --- .../Translation/LocalizationFinder.php | 56 ++----------------- 1 file changed, 6 insertions(+), 50 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index d601dc67fd36..9152a70da07a 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -13,6 +13,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use CodeIgniter\Commands\Translation\LocalizationFinder\ArrayHelper; use Config\App; use Locale; use RecursiveDirectoryIterator; @@ -57,7 +58,7 @@ public function run(array $params) if (ENVIRONMENT === 'testing') { $currentDir = SUPPORTPATH . 'Services/'; - $this->languagePath = SUPPORTPATH . 'Language/'; + $this->languagePath = SUPPORTPATH . 'Language'; } if (is_string($optionLocale)) { @@ -112,7 +113,7 @@ private function findTranslationsInFile($file): array $fileContent = file_get_contents($file->getRealPath()); preg_match_all('/lang\(\'([._a-z0-9\-]+)\'\)/ui', $fileContent, $matches); - if (empty($matches[1])) { + if ([] === $matches[1]) { return []; } @@ -201,38 +202,6 @@ private function buildMultiArray(array $fromKeys, string $lastArrayValue = ''): return $newArray; } - /** - * Compare recursive two arrays and return new array (difference) - */ - private function arrayDiffRecursive(array $originalArray, array $compareArray): array - { - $difference = []; - - if (count($compareArray) < 1) { - return $originalArray; - } - - foreach ($originalArray as $originalKey => $originalValue) { - if (is_array($originalValue)) { - $diffArrays = null; - - if (isset($compareArray[$originalKey])) { - $diffArrays = $this->arrayDiffRecursive($originalValue, $compareArray[$originalKey]); - } else { - $difference[$originalKey] = $originalValue; - } - - if (! empty($diffArrays)) { - $difference[$originalKey] = $diffArrays; - } - } elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareArray)) { - $difference[$originalKey] = $originalValue; - } - } - - return $difference; - } - /** * Convert multi arrays to specific CLI table rows (flat array) */ @@ -255,19 +224,6 @@ private function arrayToTableRows(string $langFileName, array $array): array return $rows; } - private function arrayCountRecursive(array $array, int $counter = 0): int - { - foreach ($array as $value) { - if (! is_array($value)) { - $counter++; - } else { - $counter = $this->arrayCountRecursive($value, $counter); - } - } - - return $counter; - } - private function languageKeysDump(array $inputArray): string { return var_export($inputArray, true); @@ -317,8 +273,8 @@ private function process(string $currentDir, string $currentLocale): void $languageStoredKeys = require $languageFilePath; } - $languageDiff = $this->arrayDiffRecursive($foundLanguageKeys[$langFileName], $languageStoredKeys); - $countNewKeys += $this->arrayCountRecursive($languageDiff); + $languageDiff = ArrayHelper::recursiveDiff($foundLanguageKeys[$langFileName], $languageStoredKeys); + $countNewKeys += ArrayHelper::recursiveCount($languageDiff); if ($this->showNew) { $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); @@ -339,7 +295,7 @@ private function process(string $currentDir, string $currentLocale): void } } - if ($this->showNew && ! empty($tableRows)) { + if ($this->showNew && [] !== $tableRows) { sort($tableRows); CLI::table($tableRows, ['File', 'Key']); } From b025df10c462914c00e7336f5316b7bd41a65348 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sat, 9 Sep 2023 22:17:39 +0300 Subject: [PATCH 050/290] fix: Language correction --- system/Commands/Translation/LocalizationFinder.php | 6 +++--- .../Commands/Translation/LocalizationFinder/ArrayHelper.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 9152a70da07a..0c97d2100a9b 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -28,9 +28,9 @@ class LocalizationFinder extends BaseCommand protected $usage = 'lang:find [options]'; protected $arguments = []; protected $options = [ - '--locale' => 'Specify locale (en, ru, etc...) to save files.', - '--dir' => 'Directory for searching for translations (in relation to the APPPATH).', - '--show-new' => 'See only new translations as table. Do not write to files.', + '--locale' => 'Specify locale (en, ru, etc.) to save files.', + '--dir' => 'Directory to search for translations relative to APPPATH.', + '--show-new' => 'Show only new translations in table. Does not write to files.', '--verbose' => 'Output detailed information.', ]; diff --git a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php index b970c4ae65c4..b6cd4d150852 100644 --- a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php +++ b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php @@ -14,8 +14,8 @@ class ArrayHelper { /** - * Compare recursive two associative arrays and return new array (difference) - * Returns keys that exist in $original but not in $compareWith + * Compare recursively two associative arrays and return difference as new array. + * Returns keys that exist in `$original` but not in `$compareWith`. */ public static function recursiveDiff(array $original, array $compareWith): array { @@ -55,7 +55,7 @@ public static function recursiveDiff(array $original, array $compareWith): array } /** - * Count all keys (with nested keys) + * Recursively count all keys. */ public static function recursiveCount(array $array, int $counter = 0): int { From d143a9b472902806bdcf484541cfc344180bd3c1 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sat, 9 Sep 2023 22:56:40 +0300 Subject: [PATCH 051/290] style: Format code --- .../Translation/LocalizationFinder.php | 140 ++++++++---------- 1 file changed, 60 insertions(+), 80 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 0c97d2100a9b..a8d075f0ac83 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -57,7 +57,7 @@ public function run(array $params) $this->languagePath = $currentDir . 'Language'; if (ENVIRONMENT === 'testing') { - $currentDir = SUPPORTPATH . 'Services/'; + $currentDir = SUPPORTPATH . 'Services' . DIRECTORY_SEPARATOR; $this->languagePath = SUPPORTPATH . 'Language'; } @@ -99,6 +99,58 @@ public function run(array $params) return EXIT_SUCCESS; } + private function process(string $currentDir, string $currentLocale): void + { + $tableRows = []; + $countNewKeys = 0; + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($currentDir)); + $files = iterator_to_array($iterator, true); + ksort($files); + + [$foundLanguageKeys, $countFiles] = $this->findLanguageKeysInFiles($files); + ksort($foundLanguageKeys); + + $languageDiff = []; + $languageFoundGroups = array_unique(array_keys($foundLanguageKeys)); + + foreach ($languageFoundGroups as $langFileName) { + $languageStoredKeys = []; + $languageFilePath = $this->languagePath . DIRECTORY_SEPARATOR . $currentLocale . DIRECTORY_SEPARATOR . $langFileName . '.php'; + + if (is_file($languageFilePath)) { + // Load old localization + $languageStoredKeys = require $languageFilePath; + } + + $languageDiff = ArrayHelper::recursiveDiff($foundLanguageKeys[$langFileName], $languageStoredKeys); + $countNewKeys += ArrayHelper::recursiveCount($languageDiff); + + if ($this->showNew) { + $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); + } else { + $newLanguageKeys = array_replace_recursive($foundLanguageKeys[$langFileName], $languageStoredKeys); + + if ($languageDiff !== []) { + if (false === file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys))) { + $this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red'); + } else { + exec('composer cs-fix --quiet ' . $this->languagePath); + $this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green'); + } + } + } + } + + if ($this->showNew && $tableRows !== []) { + sort($tableRows); + CLI::table($tableRows, ['File', 'Key']); + } + + $this->writeIsVerbose('Files found: ' . $countFiles); + $this->writeIsVerbose('New translates found: ' . $countNewKeys); + } + /** * @param SplFileInfo|string $file */ @@ -113,16 +165,14 @@ private function findTranslationsInFile($file): array $fileContent = file_get_contents($file->getRealPath()); preg_match_all('/lang\(\'([._a-z0-9\-]+)\'\)/ui', $fileContent, $matches); - if ([] === $matches[1]) { + if ($matches[1] === []) { return []; } foreach ($matches[1] as $phraseKey) { $phraseKeys = explode('.', $phraseKey); - /** - * Language key not have Filename or Lang key - */ + // Language key not have Filename or Lang key if (count($phraseKeys) < 2) { continue; } @@ -136,16 +186,11 @@ private function findTranslationsInFile($file): array continue; } - /** - * Language key as string - */ if (count($phraseKeys) === 1) { - /** - * Add found language keys to temporary array - */ $foundLanguageKeys[$languageFileName][$phraseKeys[0]] = $phraseKey; } else { - $childKeys = $this->buildMultiArray($phraseKeys, $phraseKey); + $childKeys = $this->buildMultiArray($phraseKeys, $phraseKey); + $foundLanguageKeys[$languageFileName] = array_replace_recursive($foundLanguageKeys[$languageFileName] ?? [], $childKeys); } } @@ -159,13 +204,13 @@ private function isIgnoredFile(SplFileInfo $file): bool return true; } - return 'php' !== $file->getExtension(); + return $file->getExtension() !== 'php'; } private function templateFile(array $language = []): string { - if ([] !== $language) { - $languageArrayString = $this->languageKeysDump($language); + if ($language !== []) { + $languageArrayString = var_export($language, true); return <<findLanguageKeysInFiles($files); - ksort($foundLanguageKeys); - - /** - * New translates - */ - $languageDiff = []; - $languageFoundGroups = array_unique(array_keys($foundLanguageKeys)); - - foreach ($languageFoundGroups as $langFileName) { - $languageStoredKeys = []; - $languageFilePath = $this->languagePath . DIRECTORY_SEPARATOR . $currentLocale . DIRECTORY_SEPARATOR . $langFileName . '.php'; - - if (is_file($languageFilePath)) { - /** - * Load old localization - */ - $languageStoredKeys = require $languageFilePath; - } - - $languageDiff = ArrayHelper::recursiveDiff($foundLanguageKeys[$langFileName], $languageStoredKeys); - $countNewKeys += ArrayHelper::recursiveCount($languageDiff); - - if ($this->showNew) { - $tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows); - } else { - $newLanguageKeys = array_replace_recursive($foundLanguageKeys[$langFileName], $languageStoredKeys); - - /** - * New translates exists - */ - if ($languageDiff !== []) { - if (false === file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys))) { - $this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red'); - } else { - exec('composer cs-fix --quiet ' . $this->languagePath); - $this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green'); - } - } - } - } - - if ($this->showNew && [] !== $tableRows) { - sort($tableRows); - CLI::table($tableRows, ['File', 'Key']); - } - - $this->writeIsVerbose('Files found: ' . $countFiles); - $this->writeIsVerbose('New translates found: ' . $countNewKeys); - } - /** * @param SplFileInfo[] $files * From 1026314a2f556b06e5d7c524bea5c1bf8ff37511 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sat, 9 Sep 2023 22:56:47 +0300 Subject: [PATCH 052/290] style: Format code --- .../Translation/LocalizationFinder/ArrayHelper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php index b6cd4d150852..d747127c3a42 100644 --- a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php +++ b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php @@ -21,16 +21,16 @@ public static function recursiveDiff(array $original, array $compareWith): array { $difference = []; - if ([] === $original) { + if ($original === []) { return []; } - if ([] === $compareWith) { + if ($compareWith === []) { return $original; } foreach ($original as $originalKey => $originalValue) { - if ([] === $originalValue) { + if ($originalValue === []) { continue; } @@ -43,7 +43,7 @@ public static function recursiveDiff(array $original, array $compareWith): array $difference[$originalKey] = $originalValue; } - if ([] !== $diffArrays) { + if ($diffArrays !== []) { $difference[$originalKey] = $diffArrays; } } elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareWith)) { From db1567edd87cb86571987c58b4b4fb4f75fd29c6 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sat, 9 Sep 2023 23:18:00 +0300 Subject: [PATCH 053/290] tests: Compliance with the project rules --- .../Translation/LocalizationFinderTest.php | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index 5d61c4c19bbb..f5cc38ec7b2f 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -33,7 +33,7 @@ protected function setUp(): void { parent::setUp(); self::$locale = Locale::getDefault(); - self::$languageTestPath = SUPPORTPATH . 'Language/'; + self::$languageTestPath = SUPPORTPATH . 'Language' . DIRECTORY_SEPARATOR; } protected function tearDown(): void @@ -46,11 +46,9 @@ public function testUpdateDefaultLocale(): void { $this->makeLocaleDirectory(); - Services::commands()->run('lang:find', [ - 'dir' => 'Translation', - ]); + command('lang:find --dir Translation'); - $this->realizeAssertion(); + $this->assertTranslationsExistAndHaveTranslatedKeys(); } public function testUpdateWithLocaleOption(): void @@ -58,12 +56,9 @@ public function testUpdateWithLocaleOption(): void self::$locale = config(App::class)->supportedLocales[0]; $this->makeLocaleDirectory(); - Services::commands()->run('lang:find', [ - 'dir' => 'Translation', - 'locale' => self::$locale, - ]); + command('lang:find --dir Translation --locale ' . self::$locale); - $this->realizeAssertion(); + $this->assertTranslationsExistAndHaveTranslatedKeys(); } public function testUpdateWithIncorrectLocaleOption(): void @@ -76,16 +71,16 @@ public function testUpdateWithIncorrectLocaleOption(): void 'locale' => self::$locale, ]); - $this->assertSame($status, EXIT_USER_INPUT); + $this->assertSame(EXIT_USER_INPUT, $status); } public function testUpdateWithEmptyDirOption(): void { $this->makeLocaleDirectory(); - Services::commands()->run('lang:find', []); + command('lang:find'); - $this->realizeAssertion(); + $this->assertTranslationsExistAndHaveTranslatedKeys(); } public function testUpdateWithIncorrectDirOption(): void @@ -96,17 +91,14 @@ public function testUpdateWithIncorrectDirOption(): void 'dir' => 'Translation/NotExistFolder', ]); - $this->assertSame($status, EXIT_USER_INPUT); + $this->assertSame(EXIT_USER_INPUT, $status); } public function testShowNewTranslation(): void { $this->makeLocaleDirectory(); - Services::commands()->run('lang:find', [ - 'dir' => 'Translation', - 'show-new' => null, - ]); + command('lang:find --dir Translation --show-new'); $this->assertStringContainsString($this->getActualTableWithNewKeys(), $this->getStreamFilterBuffer()); } @@ -201,7 +193,7 @@ private function getActualTableWithNewKeys(): string TEXT_WRAP; } - private function realizeAssertion(): void + private function assertTranslationsExistAndHaveTranslatedKeys(): void { $this->assertFileExists(self::$languageTestPath . self::$locale . '/TranslationOne.php'); $this->assertFileExists(self::$languageTestPath . self::$locale . '/TranslationThree.php'); From 1c16cb4a332f2fab248115f22f417155298a2dba Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sat, 9 Sep 2023 23:30:36 +0300 Subject: [PATCH 054/290] fix: Added a warning instead of cs-fix --- system/Commands/Translation/LocalizationFinder.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index a8d075f0ac83..a939b7d178be 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -135,7 +135,6 @@ private function process(string $currentDir, string $currentLocale): void if (false === file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys))) { $this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red'); } else { - exec('composer cs-fix --quiet ' . $this->languagePath); $this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green'); } } @@ -147,6 +146,10 @@ private function process(string $currentDir, string $currentLocale): void CLI::table($tableRows, ['File', 'Key']); } + if (! $this->showNew && $countNewKeys > 0) { + CLI::write('Note: You need to run your linting tool to fix coding standards issues.', 'white', 'red'); + } + $this->writeIsVerbose('Files found: ' . $countFiles); $this->writeIsVerbose('New translates found: ' . $countNewKeys); } From 48f8f6515707f98f19b6ae7c723ae81d4b840a5c Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sat, 9 Sep 2023 23:38:13 +0300 Subject: [PATCH 055/290] style: Format code --- tests/_support/Services/Translation/TranslationTwo.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/_support/Services/Translation/TranslationTwo.php b/tests/_support/Services/Translation/TranslationTwo.php index be40786b5ee2..71e5e663b27e 100644 --- a/tests/_support/Services/Translation/TranslationTwo.php +++ b/tests/_support/Services/Translation/TranslationTwo.php @@ -17,9 +17,7 @@ public function list() { $langKey = 'TranslationTwo.error_key'; - /** - * Error language keys - */ + // Error language keys $translationError1 = lang('TranslationTwo'); $translationError2 = lang(' '); $translationError3 = lang(''); @@ -28,8 +26,6 @@ public function list() $translationError6 = lang('TranslationTwo...'); $translationError7 = lang('..invalid_nested_key..'); - /** - * Empty in comments lang('') lang(' ') - */ + // Empty in comments lang('') lang(' ') } } From 7bb80d90074622f5db2c109ab7f8d8eaf8208fcf Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sun, 10 Sep 2023 09:19:58 +0300 Subject: [PATCH 056/290] fix: Replace array syntax --- .../Translation/LocalizationFinder.php | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index a939b7d178be..94aea443b424 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -215,12 +215,14 @@ private function templateFile(array $language = []): string if ($language !== []) { $languageArrayString = var_export($language, true); - return <<replaceArraySyntax($code); } return <<<'PHP' @@ -231,6 +233,47 @@ private function templateFile(array $language = []): string PHP; } + private function replaceArraySyntax(string $code): string + { + $tokens = token_get_all($code); + $newTokens = $tokens; + + foreach ($tokens as $i => $token) { + if (is_array($token)) { + [$tokenId, $tokenValue] = $token; + + // Replace "array (" + if ( + $tokenId === T_ARRAY + && $tokens[$i + 1][0] === T_WHITESPACE + && $tokens[$i + 2] === '(' + ) { + $newTokens[$i][1] = '['; + $newTokens[$i + 1][1] = ''; + $newTokens[$i + 2] = ''; + } + + // Replace indent + if ($tokenId === T_WHITESPACE) { + if (preg_match('/\n([ ]+)/u', $tokenValue, $matches)) { + $newTokens[$i][1] = "\n{$matches[1]}{$matches[1]}"; + } + } + } // Replace ")" + elseif ($token === ')') { + $newTokens[$i] = ']'; + } + } + + $output = ''; + + foreach ($newTokens as $token) { + $output .= $token[1] ?? $token; + } + + return $output; + } + /** * Create multidimensional array from another keys */ From e22599ae388813c6a5cc2d44f3e893863d7b0657 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 11 Sep 2023 07:58:40 +0300 Subject: [PATCH 057/290] docs: add formatting advice --- user_guide_src/source/outgoing/localization.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index e0c1dd4dc4d1..7fc10d81f752 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -273,6 +273,8 @@ Previously saved keys do not change. .. literalinclude:: localization/019.php +The language files generated will most likely not conform to your coding standards. +It is recommended to format them. For example, run ``vendor/bin/php-cs-fixer fix ./app/Language`` if ``php-cs-fixer`` is installed. .. note:: The command ignores **app/Language/** path. From d675308b49cd5c0609685c53ba43f5b651497296 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 11 Sep 2023 08:23:32 +0300 Subject: [PATCH 058/290] style: compliance with phpstan strict rules --- system/Commands/Translation/LocalizationFinder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 94aea443b424..636a193c7a2f 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -181,9 +181,9 @@ private function findTranslationsInFile($file): array } $languageFileName = array_shift($phraseKeys); - $isEmptyNestedArray = (! empty($languageFileName) && empty($phraseKeys[0])) - || (empty($languageFileName) && ! empty($phraseKeys[0])) - || (empty($languageFileName) && empty($phraseKeys[0])); + $isEmptyNestedArray = ($languageFileName !== '' && $phraseKeys[0] === '') + || ($languageFileName === '' && $phraseKeys[0] !== '') + || ($languageFileName === '' && $phraseKeys[0] === ''); if ($isEmptyNestedArray) { continue; From 935b3878b5f6e680b040547444dc849f531a4926 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 11 Sep 2023 08:33:37 +0300 Subject: [PATCH 059/290] style: compliance with rector rules --- system/Commands/Translation/LocalizationFinder.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index 636a193c7a2f..e5147093d2dc 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -20,6 +20,9 @@ use RecursiveIteratorIterator; use SplFileInfo; +/** + * @see \CodeIgniter\Commands\Translation\LocalizationFinderTest + */ class LocalizationFinder extends BaseCommand { protected $group = 'Translation'; @@ -254,10 +257,8 @@ private function replaceArraySyntax(string $code): string } // Replace indent - if ($tokenId === T_WHITESPACE) { - if (preg_match('/\n([ ]+)/u', $tokenValue, $matches)) { - $newTokens[$i][1] = "\n{$matches[1]}{$matches[1]}"; - } + if ($tokenId === T_WHITESPACE && preg_match('/\n([ ]+)/u', $tokenValue, $matches)) { + $newTokens[$i][1] = "\n{$matches[1]}{$matches[1]}"; } } // Replace ")" elseif ($token === ')') { From 070d7e59cc1d450927ec672549240d6ff70447a8 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Thu, 14 Sep 2023 07:59:16 +0300 Subject: [PATCH 060/290] docs: update note in localization.rst --- user_guide_src/source/outgoing/localization.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index 7fc10d81f752..837807989b0d 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -273,12 +273,11 @@ Previously saved keys do not change. .. literalinclude:: localization/019.php +.. note:: When the command scans folders, **app/Language** will be skipped. + The language files generated will most likely not conform to your coding standards. It is recommended to format them. For example, run ``vendor/bin/php-cs-fixer fix ./app/Language`` if ``php-cs-fixer`` is installed. -.. note:: The command ignores **app/Language/** path. - - Before updating, it is possible to preview the translations found by the command: .. code-block:: console From ae6c8f5f5bffc49eaaa9f6329a47b030659c55a7 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Sep 2023 10:04:52 +0200 Subject: [PATCH 061/290] feat: add seeXPath to DOMParser class --- system/Test/DOMParser.php | 18 ++++++++ tests/system/Test/DOMParserTest.php | 44 +++++++++++++++++++ user_guide_src/source/testing/response.rst | 15 +++++++ .../source/testing/response/033.php | 11 +++++ .../source/testing/response/034.php | 11 +++++ 5 files changed, 99 insertions(+) create mode 100644 user_guide_src/source/testing/response/033.php create mode 100644 user_guide_src/source/testing/response/034.php diff --git a/system/Test/DOMParser.php b/system/Test/DOMParser.php index 100b14b72dff..1a2cbb0d61aa 100644 --- a/system/Test/DOMParser.php +++ b/system/Test/DOMParser.php @@ -175,6 +175,24 @@ public function seeCheckboxIsChecked(string $element): bool return (bool) $result->length; } + /** + * Checks to see if the XPath can be found. + */ + public function seeXPath(string $path): bool + { + $xpath = new DOMXPath($this->dom); + + return (bool) $xpath->query($path)->length; + } + + /** + * Checks to see if the XPath can't be found. + */ + public function dontSeeXPath(string $path): bool + { + return ! $this->seeXPath($path); + } + /** * Search the DOM using an XPath expression. * diff --git a/tests/system/Test/DOMParserTest.php b/tests/system/Test/DOMParserTest.php index f6e33cc32250..63d04e9926e9 100644 --- a/tests/system/Test/DOMParserTest.php +++ b/tests/system/Test/DOMParserTest.php @@ -416,4 +416,48 @@ public function testSeeAttribute(): void $this->assertTrue($dom->see(null, '*[ name = user ]')); $this->assertFalse($dom->see(null, '*[ name = notthere ]')); } + + public function testSeeXPathSuccess(): void + { + $dom = new DOMParser(); + + $html = '

Hello World Wide Web

'; + $dom->withString($html); + + $this->assertTrue($dom->seeXPath('//h1[contains(@class, "heading")]')); + $this->assertTrue($dom->seeXPath('//h1[contains(@class, "heading")][contains(.,"Hello World")]')); + } + + public function testSeeXPathFail(): void + { + $dom = new DOMParser(); + + $html = '

Hello World Wide Web

'; + $dom->withString($html); + + $this->assertFalse($dom->seeXPath('//h1[contains(@class, "heading123")]')); + $this->assertFalse($dom->seeXPath('//h1[contains(@class, "heading")][contains(.,"Hello World 123")]')); + } + + public function testDontSeeXPathSuccess(): void + { + $dom = new DOMParser(); + + $html = '

Hello World Wide Web

'; + $dom->withString($html); + + $this->assertTrue($dom->dontSeeXPath('//h1[contains(@class, "heading123")]')); + $this->assertTrue($dom->dontSeeXPath('//h1[contains(@class, "heading")][contains(.,"Hello World 123")]')); + } + + public function testDontSeeXPathFail(): void + { + $dom = new DOMParser(); + + $html = '

Hello World Wide Web

'; + $dom->withString($html); + + $this->assertFalse($dom->dontSeeXPath('//h1[contains(@class, "heading")]')); + $this->assertFalse($dom->dontSeeXPath('//h1[contains(@class, "heading")][contains(.,"Hello World")]')); + } } diff --git a/user_guide_src/source/testing/response.rst b/user_guide_src/source/testing/response.rst index 2d5f4ddc9f13..435b797a2f3d 100644 --- a/user_guide_src/source/testing/response.rst +++ b/user_guide_src/source/testing/response.rst @@ -223,6 +223,21 @@ Finally, you can check if a checkbox exists and is checked with the ``seeCheckbo .. literalinclude:: response/023.php :lines: 2- +seeXPath() +--------- + +You can use ``seeXPath()`` to take advantage of the full power that xpath gives you. +This method is aimed at more advanced users who want to write a more complex query +using the DOMXPath object directly: + +.. literalinclude:: response/033.php + :lines: 2- + +The ``dontSeeXPath()`` method is the exact opposite: + +.. literalinclude:: response/034.php + :lines: 2- + DOM Assertions ============== diff --git a/user_guide_src/source/testing/response/033.php b/user_guide_src/source/testing/response/033.php new file mode 100644 index 000000000000..cf8cf3a7a3df --- /dev/null +++ b/user_guide_src/source/testing/response/033.php @@ -0,0 +1,11 @@ +seeXPath('//h1[contains(@class, "heading")]')) { + // ... +} + +// Check that h1 element which contains class "heading" have a text "Hello World" +if ($results->seeXPath('//h1[contains(@class, "heading")][contains(.,"Hello world")]')) { + // ... +} diff --git a/user_guide_src/source/testing/response/034.php b/user_guide_src/source/testing/response/034.php new file mode 100644 index 000000000000..e900dd1fff99 --- /dev/null +++ b/user_guide_src/source/testing/response/034.php @@ -0,0 +1,11 @@ +dontSeeXPath('//h1[contains(@class, "heading")]')) { + // ... +} + +// Check that h1 element which contains class "heading" and text "Hello World" does NOT exist on the page +if ($results->dontSeeXPath('//h1[contains(@class, "heading")][contains(.,"Hello world")]')) { + // ... +} From 9a5c1e4b877e8010eca1d9fee1e2bce4f1cca09e Mon Sep 17 00:00:00 2001 From: michalsn Date: Sat, 16 Sep 2023 12:50:01 +0200 Subject: [PATCH 062/290] update changelog --- user_guide_src/source/changelogs/v4.4.2.rst | 2 ++ user_guide_src/source/testing/response.rst | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/changelogs/v4.4.2.rst b/user_guide_src/source/changelogs/v4.4.2.rst index 3866a7664ee6..c23d67212676 100644 --- a/user_guide_src/source/changelogs/v4.4.2.rst +++ b/user_guide_src/source/changelogs/v4.4.2.rst @@ -22,6 +22,8 @@ Changes command was removed. It did not work from the beginning. Also, the rollback command returns the database(s) state to a specified batch number and cannot specify only a specific database group. +- **DomParser:** The new methods were added ``seeXPath()`` and ``dontSeeXPath()`` + which allows users to work directly with DOMXPath object, using complex expressions. Deprecations ************ diff --git a/user_guide_src/source/testing/response.rst b/user_guide_src/source/testing/response.rst index 435b797a2f3d..d5863b5892f3 100644 --- a/user_guide_src/source/testing/response.rst +++ b/user_guide_src/source/testing/response.rst @@ -227,7 +227,7 @@ seeXPath() --------- You can use ``seeXPath()`` to take advantage of the full power that xpath gives you. -This method is aimed at more advanced users who want to write a more complex query +This method is aimed at more advanced users who want to write a more complex expressions using the DOMXPath object directly: .. literalinclude:: response/033.php From 416b6eab0c9ada338d105995b72f3a35f8f2d46d Mon Sep 17 00:00:00 2001 From: michalsn Date: Sat, 16 Sep 2023 12:56:40 +0200 Subject: [PATCH 063/290] userguide: fix too short title underline --- user_guide_src/source/testing/response.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/testing/response.rst b/user_guide_src/source/testing/response.rst index d5863b5892f3..7d0e2c4ce8cf 100644 --- a/user_guide_src/source/testing/response.rst +++ b/user_guide_src/source/testing/response.rst @@ -224,7 +224,7 @@ Finally, you can check if a checkbox exists and is checked with the ``seeCheckbo :lines: 2- seeXPath() ---------- +---------- You can use ``seeXPath()`` to take advantage of the full power that xpath gives you. This method is aimed at more advanced users who want to write a more complex expressions From b00647b1a39f61984fdcbc5e3fdce633219e81a9 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Sep 2023 08:01:28 +0200 Subject: [PATCH 064/290] update changelog --- user_guide_src/source/changelogs/v4.4.2.rst | 2 -- user_guide_src/source/changelogs/v4.5.0.rst | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/changelogs/v4.4.2.rst b/user_guide_src/source/changelogs/v4.4.2.rst index c23d67212676..3866a7664ee6 100644 --- a/user_guide_src/source/changelogs/v4.4.2.rst +++ b/user_guide_src/source/changelogs/v4.4.2.rst @@ -22,8 +22,6 @@ Changes command was removed. It did not work from the beginning. Also, the rollback command returns the database(s) state to a specified batch number and cannot specify only a specific database group. -- **DomParser:** The new methods were added ``seeXPath()`` and ``dontSeeXPath()`` - which allows users to work directly with DOMXPath object, using complex expressions. Deprecations ************ diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index d18f0884e0ce..7235fa2a4b0d 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -50,6 +50,9 @@ Commands Testing ======= +- **DomParser:** The new methods were added ``seeXPath()`` and ``dontSeeXPath()`` + which allows users to work directly with DOMXPath object, using complex expressions. + Database ======== From 9ff890ca4cd481692c3a16a600de4a0ad93c2a42 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Sep 2023 08:01:51 +0200 Subject: [PATCH 065/290] add note in the user guide --- user_guide_src/source/testing/response.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user_guide_src/source/testing/response.rst b/user_guide_src/source/testing/response.rst index 7d0e2c4ce8cf..39d4d880e529 100644 --- a/user_guide_src/source/testing/response.rst +++ b/user_guide_src/source/testing/response.rst @@ -238,6 +238,8 @@ The ``dontSeeXPath()`` method is the exact opposite: .. literalinclude:: response/034.php :lines: 2- +.. note:: This method was introduced in v4.5.0. + DOM Assertions ============== From 6c788530eb380e4d339ff797b015ca55867ec2d1 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Sun, 17 Sep 2023 18:52:24 +0200 Subject: [PATCH 066/290] Update user_guide_src/source/testing/response.rst Co-authored-by: kenjis --- user_guide_src/source/testing/response.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user_guide_src/source/testing/response.rst b/user_guide_src/source/testing/response.rst index 39d4d880e529..d9661c7b01b5 100644 --- a/user_guide_src/source/testing/response.rst +++ b/user_guide_src/source/testing/response.rst @@ -226,6 +226,8 @@ Finally, you can check if a checkbox exists and is checked with the ``seeCheckbo seeXPath() ---------- +.. versionadded:: 4.5.0 + You can use ``seeXPath()`` to take advantage of the full power that xpath gives you. This method is aimed at more advanced users who want to write a more complex expressions using the DOMXPath object directly: From 60dc1554dff4850c1c11f68084d67d63d8e56cd7 Mon Sep 17 00:00:00 2001 From: michalsn Date: Mon, 18 Sep 2023 07:31:48 +0200 Subject: [PATCH 067/290] cleanup note --- user_guide_src/source/testing/response.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/user_guide_src/source/testing/response.rst b/user_guide_src/source/testing/response.rst index d9661c7b01b5..552133272a28 100644 --- a/user_guide_src/source/testing/response.rst +++ b/user_guide_src/source/testing/response.rst @@ -240,8 +240,6 @@ The ``dontSeeXPath()`` method is the exact opposite: .. literalinclude:: response/034.php :lines: 2- -.. note:: This method was introduced in v4.5.0. - DOM Assertions ============== From 5771057b81521c909ab9d99454c5291315d55ee6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 29 Nov 2022 09:21:56 +0900 Subject: [PATCH 068/290] Change min PHP version to 8.0 --- public/index.php | 2 +- spark | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/index.php b/public/index.php index 1cc4710549d5..826966179eec 100644 --- a/public/index.php +++ b/public/index.php @@ -1,7 +1,7 @@ Date: Tue, 29 Nov 2022 09:25:02 +0900 Subject: [PATCH 069/290] chore: require ^8.0 in composer.json --- admin/framework/composer.json | 2 +- admin/starter/composer.json | 2 +- composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/framework/composer.json b/admin/framework/composer.json index f93daa275b1d..70b51ba8f88c 100644 --- a/admin/framework/composer.json +++ b/admin/framework/composer.json @@ -10,7 +10,7 @@ "slack": "https://codeigniterchat.slack.com" }, "require": { - "php": "^7.4 || ^8.0", + "php": "^8.0", "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", diff --git a/admin/starter/composer.json b/admin/starter/composer.json index 082d7b648c25..37c3e5642f29 100644 --- a/admin/starter/composer.json +++ b/admin/starter/composer.json @@ -10,7 +10,7 @@ "slack": "https://codeigniterchat.slack.com" }, "require": { - "php": "^7.4 || ^8.0", + "php": "^8.0", "codeigniter4/framework": "^4.0" }, "require-dev": { diff --git a/composer.json b/composer.json index 2258eca9ac4d..646f53d5c784 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "slack": "https://codeigniterchat.slack.com" }, "require": { - "php": "^7.4 || ^8.0", + "php": "^8.0", "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", From 6bf8cd9b8942557ddbf9b394abddf5f0ab506729 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 29 Nov 2022 09:25:55 +0900 Subject: [PATCH 070/290] chore: drop PHP 7.4 in workflows --- .github/workflows/test-coding-standards.yml | 2 +- .github/workflows/test-phpunit.yml | 6 +----- .github/workflows/test-rector.yml | 2 +- admin/starter/.github/workflows/phpunit.yml | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-coding-standards.yml b/.github/workflows/test-coding-standards.yml index 9217f48bb127..233b563297e5 100644 --- a/.github/workflows/test-coding-standards.yml +++ b/.github/workflows/test-coding-standards.yml @@ -28,8 +28,8 @@ jobs: fail-fast: false matrix: php-version: - - '7.4' - '8.0' + - '8.1' steps: - name: Checkout diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index 9ca4ed8e15e4..9beee1b3aa09 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -55,7 +55,6 @@ jobs: strategy: matrix: php-version: - - '7.4' - '8.0' - '8.1' - '8.2' @@ -85,7 +84,6 @@ jobs: fail-fast: false matrix: php-version: - - '7.4' - '8.0' - '8.1' - '8.2' @@ -99,7 +97,7 @@ jobs: mysql-version: - '5.7' include: - - php-version: '7.4' + - php-version: '8.0' db-platform: MySQLi mysql-version: '8.0' - php-version: '8.3' @@ -127,7 +125,6 @@ jobs: strategy: matrix: php-version: - - '7.4' - '8.0' - '8.1' - '8.2' @@ -156,7 +153,6 @@ jobs: strategy: matrix: php-version: - - '7.4' - '8.0' - '8.1' - '8.2' diff --git a/.github/workflows/test-rector.yml b/.github/workflows/test-rector.yml index 7912a59f5d3f..027047dc44d7 100644 --- a/.github/workflows/test-rector.yml +++ b/.github/workflows/test-rector.yml @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.4', '8.0'] + php-versions: ['8.0', '8.1'] paths: - app - system diff --git a/admin/starter/.github/workflows/phpunit.yml b/admin/starter/.github/workflows/phpunit.yml index 73d413cad92f..9b54b83b8d22 100644 --- a/admin/starter/.github/workflows/phpunit.yml +++ b/admin/starter/.github/workflows/phpunit.yml @@ -2,7 +2,7 @@ name: PHPUnit on: pull_request: - branches: + branches: - develop jobs: @@ -11,7 +11,7 @@ jobs: strategy: matrix: - php-versions: ['7.4', '8.0'] + php-versions: ['8.0', '8.1'] runs-on: ubuntu-latest From b21a63c4ae44f02f2bb55056488994cfe1a050ca Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 29 Nov 2022 09:26:53 +0900 Subject: [PATCH 071/290] docs: require min PHP version 8.0 --- README.md | 2 +- admin/framework/README.md | 2 +- admin/starter/README.md | 2 +- contributing/pull_request.md | 2 +- user_guide_src/source/installation/upgrade_4xx.rst | 2 +- user_guide_src/source/intro/requirements.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1a47301b82cc..d5f8ec34dd51 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Made with [contrib.rocks](https://contrib.rocks). ## Server Requirements -PHP version 7.4 or higher is required, with the following extensions installed: +PHP version 8.0 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) - [mbstring](http://php.net/manual/en/mbstring.installation.php) diff --git a/admin/framework/README.md b/admin/framework/README.md index 00cdf704c2f4..f6f81cd65184 100644 --- a/admin/framework/README.md +++ b/admin/framework/README.md @@ -42,7 +42,7 @@ Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/ ## Server Requirements -PHP version 7.4 or higher is required, with the following extensions installed: +PHP version 8.0 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) - [mbstring](http://php.net/manual/en/mbstring.installation.php) diff --git a/admin/starter/README.md b/admin/starter/README.md index 11d1cf13eec4..fc8f69b5eb1d 100644 --- a/admin/starter/README.md +++ b/admin/starter/README.md @@ -50,7 +50,7 @@ Problems with it can be raised on our forum, or as issues in the main repository ## Server Requirements -PHP version 7.4 or higher is required, with the following extensions installed: +PHP version 8.0 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) - [mbstring](http://php.net/manual/en/mbstring.installation.php) diff --git a/contributing/pull_request.md b/contributing/pull_request.md index 0a863fd39746..8e5a489bb519 100644 --- a/contributing/pull_request.md +++ b/contributing/pull_request.md @@ -136,7 +136,7 @@ See [Contribution CSS](./css.md). ### Compatibility -CodeIgniter4 requires [PHP 7.4](https://php.net/releases/7_4_0.php). +CodeIgniter4 requires [PHP 8.0](https://php.net/releases/8_0_0.php). ### Backwards Compatibility diff --git a/user_guide_src/source/installation/upgrade_4xx.rst b/user_guide_src/source/installation/upgrade_4xx.rst index ae645d26c1a9..f452fc247f2a 100644 --- a/user_guide_src/source/installation/upgrade_4xx.rst +++ b/user_guide_src/source/installation/upgrade_4xx.rst @@ -41,7 +41,7 @@ Downloads Namespaces ========== -- CI4 is built for PHP 7.4+, and everything in the framework is namespaced, +- CI4 is built for PHP 8.0+, and everything in the framework is namespaced, except for the helper and lang files. Application Structure diff --git a/user_guide_src/source/intro/requirements.rst b/user_guide_src/source/intro/requirements.rst index e025198a8cc0..17154a952563 100644 --- a/user_guide_src/source/intro/requirements.rst +++ b/user_guide_src/source/intro/requirements.rst @@ -10,7 +10,7 @@ Server Requirements PHP and Required Extensions *************************** -`PHP `_ version 7.4 or newer is required, with the following PHP extensions are enabled: +`PHP `_ version 8.0 or newer is required, with the following PHP extensions are enabled: - `intl `_ - `mbstring `_ From da2e62c11ee7088bbf2ac993b0ab4935444a26eb Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 29 Nov 2022 09:28:14 +0900 Subject: [PATCH 072/290] refactor: remove version check `version_compare(PHP_VERSION, '8.0.0', '>=')` --- system/Autoloader/Autoloader.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index dcddec1d2976..a06ffc2c6252 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -343,11 +343,7 @@ public function sanitizeFilename(string $filename): string ); } if ($result === false) { - if (version_compare(PHP_VERSION, '8.0.0', '>=')) { - $message = preg_last_error_msg(); - } else { - $message = 'Regex error. error code: ' . preg_last_error(); - } + $message = preg_last_error_msg(); throw new RuntimeException($message . '. filename: "' . $filename . '"'); } From d734d9c72c290c9295d62a899a614ae10211273c Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 2 Dec 2022 08:37:36 +0900 Subject: [PATCH 073/290] docs: add changelog v4.5.0 --- user_guide_src/source/changelogs/v4.5.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index c999b562172f..50dae35cd5a2 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -12,6 +12,7 @@ Release Date: Unreleased Highlights ********** +- Update minimal PHP requirement to 8.0. - TBD BREAKING From 0c8727469d85aa43614518629b3e58fefe24e883 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 16:58:55 +0900 Subject: [PATCH 074/290] chore: update php version to 8.2 --- .github/workflows/test-coding-standards.yml | 2 +- .github/workflows/test-rector.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-coding-standards.yml b/.github/workflows/test-coding-standards.yml index 233b563297e5..cd79e13b243e 100644 --- a/.github/workflows/test-coding-standards.yml +++ b/.github/workflows/test-coding-standards.yml @@ -29,7 +29,7 @@ jobs: matrix: php-version: - '8.0' - - '8.1' + - '8.2' steps: - name: Checkout diff --git a/.github/workflows/test-rector.yml b/.github/workflows/test-rector.yml index 027047dc44d7..e29ee6ba64e7 100644 --- a/.github/workflows/test-rector.yml +++ b/.github/workflows/test-rector.yml @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.0', '8.1'] + php-versions: ['8.0', '8.2'] paths: - app - system From a408c39c7c98c740672509a91ced96cd447f0ad9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 17:03:40 +0900 Subject: [PATCH 075/290] docs: update next minor version sample --- user_guide_src/source/installation/installing_composer.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user_guide_src/source/installation/installing_composer.rst b/user_guide_src/source/installation/installing_composer.rst index 9af6a0f0fa71..f1883b61caf3 100644 --- a/user_guide_src/source/installation/installing_composer.rst +++ b/user_guide_src/source/installation/installing_composer.rst @@ -139,11 +139,11 @@ Next Minor Version If you want to use the next minor version branch, after using the ``builds`` command edit **composer.json** manually. -If you try the ``4.4`` branch, change the version to ``4.4.x-dev``:: +If you try the ``4.6`` branch, change the version to ``4.6.x-dev``:: "require": { - "php": "^7.4 || ^8.0", - "codeigniter4/codeigniter4": "4.4.x-dev" + "php": "^8.0", + "codeigniter4/codeigniter4": "4.6.x-dev" }, And run ``composer update`` to sync your vendor From 903583674baa30b3c9d0e7d378f64475189a568f Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 17:08:36 +0900 Subject: [PATCH 076/290] refactor: remove version check `PHP_VERSION_ID >= 80000` --- tests/system/Debug/TimerTest.php | 7 +------ tests/system/Helpers/ArrayHelperTest.php | 9 +-------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/system/Debug/TimerTest.php b/tests/system/Debug/TimerTest.php index 54c9fdfccb18..2be069062d9c 100644 --- a/tests/system/Debug/TimerTest.php +++ b/tests/system/Debug/TimerTest.php @@ -13,7 +13,6 @@ use ArgumentCountError; use CodeIgniter\Test\CIUnitTestCase; -use ErrorException; use RuntimeException; /** @@ -172,11 +171,7 @@ public function testRecordThrowsException(): void public function testRecordThrowsErrorOnCallableWithParams(): void { - if (PHP_VERSION_ID >= 80000) { - $this->expectException(ArgumentCountError::class); - } else { - $this->expectException(ErrorException::class); - } + $this->expectException(ArgumentCountError::class); $timer = new Timer(); $timer->record('error', 'strlen'); diff --git a/tests/system/Helpers/ArrayHelperTest.php b/tests/system/Helpers/ArrayHelperTest.php index 4844fe84188e..2c4ce103adc9 100644 --- a/tests/system/Helpers/ArrayHelperTest.php +++ b/tests/system/Helpers/ArrayHelperTest.php @@ -12,7 +12,6 @@ namespace CodeIgniter\Helpers; use CodeIgniter\Test\CIUnitTestCase; -use ErrorException; use ValueError; /** @@ -303,13 +302,7 @@ public function testArraySortByMultipleKeysFailsEmptyParameter(array $data, arra */ public function testArraySortByMultipleKeysFailsInconsistentArraySizes($data): void { - // PHP 8 changes this error type - if (PHP_VERSION_ID >= 80000) { - $this->expectException(ValueError::class); - } else { - $this->expectException(ErrorException::class); - } - + $this->expectException(ValueError::class); $this->expectExceptionMessage('Array sizes are inconsistent'); $sortColumns = [ From fd836d8dedd003afe99b8c163ec1043341bfb947 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 13 Sep 2023 12:18:01 +0900 Subject: [PATCH 077/290] chore: update php version Co-authored-by: MGatner --- admin/starter/.github/workflows/phpunit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/starter/.github/workflows/phpunit.yml b/admin/starter/.github/workflows/phpunit.yml index 9b54b83b8d22..0489a9376466 100644 --- a/admin/starter/.github/workflows/phpunit.yml +++ b/admin/starter/.github/workflows/phpunit.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - php-versions: ['8.0', '8.1'] + php-versions: ['8.0', '8.2'] runs-on: ubuntu-latest From 83865ae34b26eb4e5bc52ad11215c2dbcb2db108 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 09:10:54 +0900 Subject: [PATCH 078/290] chore: remove PHP version checks --- .php-cs-fixer.dist.php | 13 +++++-------- .php-cs-fixer.no-header.php | 13 +++++-------- .php-cs-fixer.user-guide.php | 13 +++++-------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 8c2a6b8b7a52..d4d3798ca1cf 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -80,13 +80,10 @@ 'admin@codeigniter.com' ); -// @TODO: remove this check when support for PHP 7.4 is dropped -if (PHP_VERSION_ID >= 80000) { - $config - ->registerCustomFixers(FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer')) - ->setRules(array_merge($config->getRules(), [ - NoCodeSeparatorCommentFixer::name() => true, - ])); -} +$config + ->registerCustomFixers(FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer')) + ->setRules(array_merge($config->getRules(), [ + NoCodeSeparatorCommentFixer::name() => true, + ])); return $config; diff --git a/.php-cs-fixer.no-header.php b/.php-cs-fixer.no-header.php index 5fb4ce95bfbe..0b75ff6bfd3f 100644 --- a/.php-cs-fixer.no-header.php +++ b/.php-cs-fixer.no-header.php @@ -62,13 +62,10 @@ $config = Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); -// @TODO: remove this check when support for PHP 7.4 is dropped -if (PHP_VERSION_ID >= 80000) { - $config - ->registerCustomFixers(FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer')) - ->setRules(array_merge($config->getRules(), [ - NoCodeSeparatorCommentFixer::name() => true, - ])); -} +$config + ->registerCustomFixers(FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer')) + ->setRules(array_merge($config->getRules(), [ + NoCodeSeparatorCommentFixer::name() => true, + ])); return $config; diff --git a/.php-cs-fixer.user-guide.php b/.php-cs-fixer.user-guide.php index cf344d903ad5..af7e896fcecd 100644 --- a/.php-cs-fixer.user-guide.php +++ b/.php-cs-fixer.user-guide.php @@ -67,13 +67,10 @@ $config = Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); -// @TODO: remove this check when support for PHP 7.4 is dropped -if (PHP_VERSION_ID >= 80000) { - $config - ->registerCustomFixers(FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer')) - ->setRules(array_merge($config->getRules(), [ - NoCodeSeparatorCommentFixer::name() => true, - ])); -} +$config + ->registerCustomFixers(FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer')) + ->setRules(array_merge($config->getRules(), [ + NoCodeSeparatorCommentFixer::name() => true, + ])); return $config; From 5ffc0b69a87f815a55cc3b3a304c2e756cb30469 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 13 Sep 2023 10:14:46 +0900 Subject: [PATCH 079/290] feat: array callable Validation rule If a rule is a callable, there is no way to set param. So the removed `($param === false)` is always true. $passed = $param === false ? $rule($value) : $rule($value, $param, $data); --- system/Validation/Validation.php | 14 +++- tests/system/Validation/ValidationTest.php | 95 ++++++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index b40bc91048bc..ef98496ac370 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -278,7 +278,9 @@ protected function processRules( } foreach ($rules as $i => $rule) { - $isCallable = is_callable($rule); + $isCallable = is_callable($rule); + $stringCallable = ($isCallable && is_string($rule)) ? true : false; + $arrayCallable = ($isCallable && is_array($rule)) ? true : false; $passed = false; $param = false; @@ -295,7 +297,13 @@ protected function processRules( if ($this->isClosure($rule)) { $passed = $rule($value, $data, $error, $field); } elseif ($isCallable) { - $passed = $param === false ? $rule($value) : $rule($value, $param, $data); + if ($stringCallable) { + // The rule is a function. + $passed = $rule($value); + } else { + // The rule is an array. + $passed = $rule($value, $data, $error, $field); + } } else { $found = false; @@ -335,7 +343,7 @@ protected function processRules( // @phpstan-ignore-next-line $error may be set by rule methods. $this->errors[$field] = $error ?? $this->getErrorMessage( - $this->isClosure($rule) ? $i : $rule, + ($this->isClosure($rule) || $arrayCallable) ? $i : $rule, $field, $label, $param, diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index e015c8b21de9..b3fce0d7c455 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -343,6 +343,101 @@ public function testClosureRuleWithLabel(): void ); } + /** + * Validation rule1 + * + * @param mixed $value + */ + public function rule1($value) + { + return $value === 'abc'; + } + + public function testCallableRule(): void + { + $this->validation->setRules( + [ + 'foo' => ['required', [$this, 'rule1']], + ], + [ + // Errors + 'foo' => [ + // Specify the array key for the callable rule. + 1 => 'The value is not "abc"', + ], + ], + ); + + $data = ['foo' => 'xyz']; + $result = $this->validation->run($data); + + $this->assertFalse($result); + $this->assertSame( + ['foo' => 'The value is not "abc"'], + $this->validation->getErrors() + ); + $this->assertSame([], $this->validation->getValidated()); + } + + /** + * Validation rule1 + * + * @param mixed $value + */ + public function rule2($value, array $data, ?string &$error, string $field) + { + if ($value !== 'abc') { + $error = 'The ' . $field . ' value is not "abc"'; + + return false; + } + + return true; + } + + public function testCallableRuleWithParamError(): void + { + $this->validation->setRules([ + 'foo' => [ + 'required', + [$this, 'rule2'], + ], + ]); + + $data = ['foo' => 'xyz']; + $result = $this->validation->run($data); + + $this->assertFalse($result); + $this->assertSame( + ['foo' => 'The foo value is not "abc"'], + $this->validation->getErrors() + ); + $this->assertSame([], $this->validation->getValidated()); + } + + public function testCallableRuleWithLabel(): void + { + $this->validation->setRules([ + 'secret' => [ + 'label' => 'シークレット', + 'rules' => ['required', [$this, 'rule1']], + 'errors' => [ + // Specify the array key for the callable rule. + 1 => 'The {field} is invalid', + ], + ], + ]); + + $data = ['secret' => 'xyz']; + $result = $this->validation->run($data); + + $this->assertFalse($result); + $this->assertSame( + ['secret' => 'The シークレット is invalid'], + $this->validation->getErrors() + ); + } + /** * @see https://github.com/codeigniter4/CodeIgniter4/issues/5368 * From 91f8b4b8bd9ba63a9eb683fc95bdfc851f09d9f0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 13 Sep 2023 11:11:40 +0900 Subject: [PATCH 080/290] docs: add and update docs --- .../installation/upgrade_validations.rst | 13 +++--- .../source/libraries/validation.rst | 23 ++++++++++ .../source/libraries/validation/046.php | 43 +++++++++++++++++++ .../source/libraries/validation/047.php | 22 ++++++++++ 4 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 user_guide_src/source/libraries/validation/046.php create mode 100644 user_guide_src/source/libraries/validation/047.php diff --git a/user_guide_src/source/installation/upgrade_validations.rst b/user_guide_src/source/installation/upgrade_validations.rst index 8343b989a699..b45823716869 100644 --- a/user_guide_src/source/installation/upgrade_validations.rst +++ b/user_guide_src/source/installation/upgrade_validations.rst @@ -14,12 +14,15 @@ Documentations of Library What has been changed ===================== - If you want to change validation error display, you have to set CI4 :ref:`validation View templates `. -- CI4 validation has no Callbacks nor Callable in CI3. - Use :ref:`Rule Classes ` or - :ref:`Closure Rule ` - instead. -- In CI3, Callbacks/Callable rules were prioritized, but in CI4, Closure Rules are +- CI4 validation has no `Callbacks `_ + in CI3. + Use :ref:`Callable Rules ` (since v4.5.0) or + :ref:`Closure Rules ` (since v4.3.0) or + :ref:`Rule Classes ` instead. +- In CI3, Callbacks/Callable rules were prioritized, but in CI4, Closure/Callable Rules are not prioritized, and are checked in the order in which they are listed. +- Since v4.5.0, :ref:`Callable Rules ` has been + introduced, but it is a bit different from CI3's `Callable `_. - CI4 validation format rules do not permit empty string. - CI4 validation never changes your data. - Since v4.3.0, :php:func:`validation_errors()` has been introduced, but the API is different from CI3's. diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst index 3224961e0821..2f3cdd662d70 100644 --- a/user_guide_src/source/libraries/validation.rst +++ b/user_guide_src/source/libraries/validation.rst @@ -794,6 +794,29 @@ Or you can use the following parameters: .. literalinclude:: validation/041.php :lines: 2- +.. _validation-using-callable-rule: + +Using Callable Rule +=================== + +.. versionadded:: 4.5.0 + +If you like to use an array callback as a rule, you may use it instead of a Closure Rule. + +You need to use an array for validation rules: + +.. literalinclude:: validation/046.php + :lines: 2- + +You must set the error message for the callable rule. +When you specify the error message, set the array key for the callable rule. +In the above code, the ``required`` rule has the key ``0``, and the callable has ``1``. + +Or you can use the following parameters: + +.. literalinclude:: validation/047.php + :lines: 2- + *************** Available Rules *************** diff --git a/user_guide_src/source/libraries/validation/046.php b/user_guide_src/source/libraries/validation/046.php new file mode 100644 index 000000000000..f37def009eba --- /dev/null +++ b/user_guide_src/source/libraries/validation/046.php @@ -0,0 +1,43 @@ +setRules( + [ + 'foo' => [ + 'required', + // Specify the method in this controller as a rule. + [$this, '_ruleEven'], + ], + ], + [ + // Errors + 'foo' => [ + // Specify the array key for the callable rule. + 1 => 'The value is not even.', + ], + ], + ); + + if (! $validation->run($data)) { + // handle validation errors + } + + // ... + } +} diff --git a/user_guide_src/source/libraries/validation/047.php b/user_guide_src/source/libraries/validation/047.php new file mode 100644 index 000000000000..6c2bb674f800 --- /dev/null +++ b/user_guide_src/source/libraries/validation/047.php @@ -0,0 +1,22 @@ + Date: Wed, 13 Sep 2023 11:40:16 +0900 Subject: [PATCH 081/290] refactor: by rector --- system/Validation/Validation.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index ef98496ac370..b42e093014fe 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -279,8 +279,8 @@ protected function processRules( foreach ($rules as $i => $rule) { $isCallable = is_callable($rule); - $stringCallable = ($isCallable && is_string($rule)) ? true : false; - $arrayCallable = ($isCallable && is_array($rule)) ? true : false; + $stringCallable = $isCallable && is_string($rule); + $arrayCallable = $isCallable && is_array($rule); $passed = false; $param = false; @@ -297,13 +297,7 @@ protected function processRules( if ($this->isClosure($rule)) { $passed = $rule($value, $data, $error, $field); } elseif ($isCallable) { - if ($stringCallable) { - // The rule is a function. - $passed = $rule($value); - } else { - // The rule is an array. - $passed = $rule($value, $data, $error, $field); - } + $passed = $stringCallable ? $rule($value) : $rule($value, $data, $error, $field); } else { $found = false; From 94a25b250a94b44485c132612f277390facf19ba Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 17:45:22 +0900 Subject: [PATCH 082/290] chore: update psr/log to v2.0 --- composer.json | 2 +- system/ComposerScripts.php | 3 +- system/ThirdParty/PSR/Log/AbstractLogger.php | 115 +----------------- .../ThirdParty/PSR/Log/LoggerAwareTrait.php | 2 +- system/ThirdParty/PSR/Log/LoggerInterface.php | 36 +++--- system/ThirdParty/PSR/Log/LoggerTrait.php | 36 +++--- system/ThirdParty/PSR/Log/NullLogger.php | 6 +- 7 files changed, 43 insertions(+), 157 deletions(-) diff --git a/composer.json b/composer.json index 646f53d5c784..4affeb06bece 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "ext-json": "*", "ext-mbstring": "*", "laminas/laminas-escaper": "^2.9", - "psr/log": "^1.1" + "psr/log": "^2.0" }, "require-dev": { "codeigniter/coding-standard": "^1.5", diff --git a/system/ComposerScripts.php b/system/ComposerScripts.php index b95bdf54c4ae..021079da468d 100644 --- a/system/ComposerScripts.php +++ b/system/ComposerScripts.php @@ -56,7 +56,7 @@ final class ComposerScripts ], 'psr-log' => [ 'license' => __DIR__ . '/../vendor/psr/log/LICENSE', - 'from' => __DIR__ . '/../vendor/psr/log/Psr/Log/', + 'from' => __DIR__ . '/../vendor/psr/log/src/', 'to' => __DIR__ . '/ThirdParty/PSR/Log/', ], ]; @@ -84,7 +84,6 @@ public static function postUpdate() } self::copyKintInitFiles(); - self::recursiveDelete(self::$dependencies['psr-log']['to'] . 'Test/'); } /** diff --git a/system/ThirdParty/PSR/Log/AbstractLogger.php b/system/ThirdParty/PSR/Log/AbstractLogger.php index e02f9daf3d51..d60a091affae 100644 --- a/system/ThirdParty/PSR/Log/AbstractLogger.php +++ b/system/ThirdParty/PSR/Log/AbstractLogger.php @@ -11,118 +11,5 @@ */ abstract class AbstractLogger implements LoggerInterface { - /** - * System is unusable. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function emergency($message, array $context = array()) - { - $this->log(LogLevel::EMERGENCY, $message, $context); - } - - /** - * Action must be taken immediately. - * - * Example: Entire website down, database unavailable, etc. This should - * trigger the SMS alerts and wake you up. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function alert($message, array $context = array()) - { - $this->log(LogLevel::ALERT, $message, $context); - } - - /** - * Critical conditions. - * - * Example: Application component unavailable, unexpected exception. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function critical($message, array $context = array()) - { - $this->log(LogLevel::CRITICAL, $message, $context); - } - - /** - * Runtime errors that do not require immediate action but should typically - * be logged and monitored. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function error($message, array $context = array()) - { - $this->log(LogLevel::ERROR, $message, $context); - } - - /** - * Exceptional occurrences that are not errors. - * - * Example: Use of deprecated APIs, poor use of an API, undesirable things - * that are not necessarily wrong. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function warning($message, array $context = array()) - { - $this->log(LogLevel::WARNING, $message, $context); - } - - /** - * Normal but significant events. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function notice($message, array $context = array()) - { - $this->log(LogLevel::NOTICE, $message, $context); - } - - /** - * Interesting events. - * - * Example: User logs in, SQL logs. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function info($message, array $context = array()) - { - $this->log(LogLevel::INFO, $message, $context); - } - - /** - * Detailed debug information. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function debug($message, array $context = array()) - { - $this->log(LogLevel::DEBUG, $message, $context); - } + use LoggerTrait; } diff --git a/system/ThirdParty/PSR/Log/LoggerAwareTrait.php b/system/ThirdParty/PSR/Log/LoggerAwareTrait.php index 82bf45c89bab..5f1553a4c810 100644 --- a/system/ThirdParty/PSR/Log/LoggerAwareTrait.php +++ b/system/ThirdParty/PSR/Log/LoggerAwareTrait.php @@ -12,7 +12,7 @@ trait LoggerAwareTrait * * @var LoggerInterface|null */ - protected $logger; + protected ?LoggerInterface $logger = null; /** * Sets a logger. diff --git a/system/ThirdParty/PSR/Log/LoggerInterface.php b/system/ThirdParty/PSR/Log/LoggerInterface.php index 2206cfde41ae..b4d062b9b640 100644 --- a/system/ThirdParty/PSR/Log/LoggerInterface.php +++ b/system/ThirdParty/PSR/Log/LoggerInterface.php @@ -22,12 +22,12 @@ interface LoggerInterface /** * System is unusable. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function emergency($message, array $context = array()); + public function emergency(string|\Stringable $message, array $context = []); /** * Action must be taken immediately. @@ -35,35 +35,35 @@ public function emergency($message, array $context = array()); * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function alert($message, array $context = array()); + public function alert(string|\Stringable $message, array $context = []); /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function critical($message, array $context = array()); + public function critical(string|\Stringable $message, array $context = []); /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function error($message, array $context = array()); + public function error(string|\Stringable $message, array $context = []); /** * Exceptional occurrences that are not errors. @@ -71,55 +71,55 @@ public function error($message, array $context = array()); * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function warning($message, array $context = array()); + public function warning(string|\Stringable $message, array $context = []); /** * Normal but significant events. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function notice($message, array $context = array()); + public function notice(string|\Stringable $message, array $context = []); /** * Interesting events. * * Example: User logs in, SQL logs. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function info($message, array $context = array()); + public function info(string|\Stringable $message, array $context = []); /** * Detailed debug information. * - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void */ - public function debug($message, array $context = array()); + public function debug(string|\Stringable $message, array $context = []); /** * Logs with an arbitrary level. * * @param mixed $level - * @param string $message + * @param string|\Stringable $message * @param mixed[] $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ - public function log($level, $message, array $context = array()); + public function log($level, string|\Stringable $message, array $context = []); } diff --git a/system/ThirdParty/PSR/Log/LoggerTrait.php b/system/ThirdParty/PSR/Log/LoggerTrait.php index e392fef0a0cb..920bda77f835 100644 --- a/system/ThirdParty/PSR/Log/LoggerTrait.php +++ b/system/ThirdParty/PSR/Log/LoggerTrait.php @@ -15,12 +15,12 @@ trait LoggerTrait /** * System is unusable. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function emergency($message, array $context = array()) + public function emergency(string|\Stringable $message, array $context = []) { $this->log(LogLevel::EMERGENCY, $message, $context); } @@ -31,12 +31,12 @@ public function emergency($message, array $context = array()) * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function alert($message, array $context = array()) + public function alert(string|\Stringable $message, array $context = []) { $this->log(LogLevel::ALERT, $message, $context); } @@ -46,12 +46,12 @@ public function alert($message, array $context = array()) * * Example: Application component unavailable, unexpected exception. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function critical($message, array $context = array()) + public function critical(string|\Stringable $message, array $context = []) { $this->log(LogLevel::CRITICAL, $message, $context); } @@ -60,12 +60,12 @@ public function critical($message, array $context = array()) * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function error($message, array $context = array()) + public function error(string|\Stringable $message, array $context = []) { $this->log(LogLevel::ERROR, $message, $context); } @@ -76,12 +76,12 @@ public function error($message, array $context = array()) * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function warning($message, array $context = array()) + public function warning(string|\Stringable $message, array $context = []) { $this->log(LogLevel::WARNING, $message, $context); } @@ -89,12 +89,12 @@ public function warning($message, array $context = array()) /** * Normal but significant events. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function notice($message, array $context = array()) + public function notice(string|\Stringable $message, array $context = []) { $this->log(LogLevel::NOTICE, $message, $context); } @@ -104,12 +104,12 @@ public function notice($message, array $context = array()) * * Example: User logs in, SQL logs. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function info($message, array $context = array()) + public function info(string|\Stringable $message, array $context = []) { $this->log(LogLevel::INFO, $message, $context); } @@ -117,12 +117,12 @@ public function info($message, array $context = array()) /** * Detailed debug information. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function debug($message, array $context = array()) + public function debug(string|\Stringable $message, array $context = []) { $this->log(LogLevel::DEBUG, $message, $context); } @@ -131,12 +131,12 @@ public function debug($message, array $context = array()) * Logs with an arbitrary level. * * @param mixed $level - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ - abstract public function log($level, $message, array $context = array()); + abstract public function log($level, string|\Stringable $message, array $context = []); } diff --git a/system/ThirdParty/PSR/Log/NullLogger.php b/system/ThirdParty/PSR/Log/NullLogger.php index c8f7293b1c66..56077057159d 100644 --- a/system/ThirdParty/PSR/Log/NullLogger.php +++ b/system/ThirdParty/PSR/Log/NullLogger.php @@ -16,14 +16,14 @@ class NullLogger extends AbstractLogger * Logs with an arbitrary level. * * @param mixed $level - * @param string $message - * @param array $context + * @param string|\Stringable $message + * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ - public function log($level, $message, array $context = array()) + public function log($level, string|\Stringable $message, array $context = []) { // noop } From ec2af9411bf816a1d40d0ad50b0307b37da528f1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 17:46:25 +0900 Subject: [PATCH 083/290] fix: incorrect Logger interface --- system/Common.php | 8 +++++--- system/Log/Logger.php | 39 +++++++++++++++++++------------------- system/Test/TestLogger.php | 5 +++-- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/system/Common.php b/system/Common.php index ebfd8e2565e8..4ec6cbbbadbf 100644 --- a/system/Common.php +++ b/system/Common.php @@ -794,7 +794,7 @@ function lang(string $line, array $args = [], ?string $locale = null) * - info * - debug * - * @return bool + * @return void */ function log_message(string $level, string $message, array $context = []) { @@ -804,10 +804,12 @@ function log_message(string $level, string $message, array $context = []) if (ENVIRONMENT === 'testing') { $logger = new TestLogger(new Logger()); - return $logger->log($level, $message, $context); + $logger->log($level, $message, $context); + + return; } - return Services::logger(true)->log($level, $message, $context); // @codeCoverageIgnore + Services::logger(true)->log($level, $message, $context); // @codeCoverageIgnore } } diff --git a/system/Log/Logger.php b/system/Log/Logger.php index b1583f5d789c..4d4e36a8afad 100644 --- a/system/Log/Logger.php +++ b/system/Log/Logger.php @@ -15,6 +15,7 @@ use CodeIgniter\Log\Handlers\HandlerInterface; use Psr\Log\LoggerInterface; use RuntimeException; +use Stringable; use Throwable; /** @@ -157,9 +158,9 @@ public function __construct($config, bool $debug = CI_DEBUG) * * @param string $message */ - public function emergency($message, array $context = []): bool + public function emergency(string|Stringable $message, array $context = []): void { - return $this->log('emergency', $message, $context); + $this->log('emergency', $message, $context); } /** @@ -170,9 +171,9 @@ public function emergency($message, array $context = []): bool * * @param string $message */ - public function alert($message, array $context = []): bool + public function alert(string|Stringable $message, array $context = []): void { - return $this->log('alert', $message, $context); + $this->log('alert', $message, $context); } /** @@ -182,9 +183,9 @@ public function alert($message, array $context = []): bool * * @param string $message */ - public function critical($message, array $context = []): bool + public function critical(string|Stringable $message, array $context = []): void { - return $this->log('critical', $message, $context); + $this->log('critical', $message, $context); } /** @@ -193,9 +194,9 @@ public function critical($message, array $context = []): bool * * @param string $message */ - public function error($message, array $context = []): bool + public function error(string|Stringable $message, array $context = []): void { - return $this->log('error', $message, $context); + $this->log('error', $message, $context); } /** @@ -206,9 +207,9 @@ public function error($message, array $context = []): bool * * @param string $message */ - public function warning($message, array $context = []): bool + public function warning(string|Stringable $message, array $context = []): void { - return $this->log('warning', $message, $context); + $this->log('warning', $message, $context); } /** @@ -216,9 +217,9 @@ public function warning($message, array $context = []): bool * * @param string $message */ - public function notice($message, array $context = []): bool + public function notice(string|Stringable $message, array $context = []): void { - return $this->log('notice', $message, $context); + $this->log('notice', $message, $context); } /** @@ -228,9 +229,9 @@ public function notice($message, array $context = []): bool * * @param string $message */ - public function info($message, array $context = []): bool + public function info(string|Stringable $message, array $context = []): void { - return $this->log('info', $message, $context); + $this->log('info', $message, $context); } /** @@ -238,9 +239,9 @@ public function info($message, array $context = []): bool * * @param string $message */ - public function debug($message, array $context = []): bool + public function debug(string|Stringable $message, array $context = []): void { - return $this->log('debug', $message, $context); + $this->log('debug', $message, $context); } /** @@ -249,7 +250,7 @@ public function debug($message, array $context = []): bool * @param string $level * @param string $message */ - public function log($level, $message, array $context = []): bool + public function log($level, string|Stringable $message, array $context = []): void { if (is_numeric($level)) { $level = array_search((int) $level, $this->logLevels, true); @@ -262,7 +263,7 @@ public function log($level, $message, array $context = []): bool // Does the app want to log this right now? if (! in_array($level, $this->loggableLevels, true)) { - return false; + return; } // Parse our placeholders @@ -295,8 +296,6 @@ public function log($level, $message, array $context = []): bool break; } } - - return true; } /** diff --git a/system/Test/TestLogger.php b/system/Test/TestLogger.php index 240f5c6d95b5..dce1277d98d2 100644 --- a/system/Test/TestLogger.php +++ b/system/Test/TestLogger.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Test; use CodeIgniter\Log\Logger; +use Stringable; /** * @see \CodeIgniter\Test\TestLoggerTest @@ -27,7 +28,7 @@ class TestLogger extends Logger * @param string $level * @param string $message */ - public function log($level, $message, array $context = []): bool + public function log($level, string|Stringable $message, array $context = []): void { // While this requires duplicate work, we want to ensure // we have the final message to test against. @@ -52,7 +53,7 @@ public function log($level, $message, array $context = []): bool ]; // Let the parent do it's thing. - return parent::log($level, $message, $context); + parent::log($level, $message, $context); } /** From 079d80103dfee8e751a2cee05d0c4be188ad335b Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 17:50:23 +0900 Subject: [PATCH 084/290] chore: update psr/log to ^2.0 --- admin/framework/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/framework/composer.json b/admin/framework/composer.json index 70b51ba8f88c..132932acc25d 100644 --- a/admin/framework/composer.json +++ b/admin/framework/composer.json @@ -15,7 +15,7 @@ "ext-json": "*", "ext-mbstring": "*", "laminas/laminas-escaper": "^2.9", - "psr/log": "^1.1" + "psr/log": "^2.0" }, "require-dev": { "codeigniter/coding-standard": "^1.5", From 88cc5722a8e010e3943389d2b02c4c52d6ef25c8 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 18:01:37 +0900 Subject: [PATCH 085/290] test: update test code --- tests/system/Log/LoggerTest.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/system/Log/LoggerTest.php b/tests/system/Log/LoggerTest.php index 317ad9c14bf4..8087ff645845 100644 --- a/tests/system/Log/LoggerTest.php +++ b/tests/system/Log/LoggerTest.php @@ -17,6 +17,7 @@ use CodeIgniter\Test\Mock\MockLogger as LoggerConfig; use Exception; use Tests\Support\Log\Handlers\TestHandler; +use TypeError; /** * @internal @@ -48,14 +49,14 @@ public function testLogThrowsExceptionOnInvalidLevel(): void $logger->log('foo', ''); } - public function testLogReturnsFalseWhenLogNotHandled(): void + public function testLogAlwaysReturnsVoid(): void { $config = new LoggerConfig(); $config->threshold = 3; $logger = new Logger($config); - $this->assertFalse($logger->log('debug', '')); + $this->assertNull($logger->log('debug', '')); } public function testLogActuallyLogs(): void @@ -361,16 +362,12 @@ public function testLogLevels(): void public function testNonStringMessage(): void { + $this->expectException(TypeError::class); + $config = new LoggerConfig(); $logger = new Logger($config); - $expected = '[Tests\Support\Log\Handlers\TestHandler]'; $logger->log(5, $config); - - $logs = TestHandler::getLogs(); - - $this->assertCount(1, $logs); - $this->assertStringContainsString($expected, $logs[0]); } public function testDetermineFileNoStackTrace(): void From d64615bba80629519503a6cb18dbdbace225ab2a Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 18:10:29 +0900 Subject: [PATCH 086/290] docs: update return type of log_message() --- user_guide_src/source/general/common_functions.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst index a7b701241007..af6ab261cb59 100755 --- a/user_guide_src/source/general/common_functions.rst +++ b/user_guide_src/source/general/common_functions.rst @@ -324,9 +324,13 @@ Miscellaneous Functions :param string $level: The level of severity :param string $message: The message that is to be logged. :param array $context: An associative array of tags and their values that should be replaced in $message - :returns: true if was logged successfully or false if there was a problem logging it + :returns: void :rtype: bool + .. note:: Since v4.5.0, the return value is fixed to be compatible with PSR + Log. In previous versions, it returned ``true`` if was logged successfully + or ``false`` if there was a problem logging it. + Logs a message using the Log Handlers defined in **app/Config/Logger.php**. Level can be one of the following values: **emergency**, **alert**, **critical**, **error**, **warning**, From 7d31b07ec546cdafea0b2c28de3795e1f5e1ccac Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 18:13:31 +0900 Subject: [PATCH 087/290] docs: change text decoration --- user_guide_src/source/general/common_functions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst index af6ab261cb59..846273551ea5 100755 --- a/user_guide_src/source/general/common_functions.rst +++ b/user_guide_src/source/general/common_functions.rst @@ -333,8 +333,8 @@ Miscellaneous Functions Logs a message using the Log Handlers defined in **app/Config/Logger.php**. - Level can be one of the following values: **emergency**, **alert**, **critical**, **error**, **warning**, - **notice**, **info**, or **debug**. + Level can be one of the following values: ``emergency``, ``alert``, ``critical``, ``error``, ``warning``, + ``notice``, ``info``, or ``debug``. Context can be used to substitute values in the message string. For full details, see the :doc:`Logging Information ` page. From 7dd0ffd5d9335bbe3fece1fd46384faef9a9e5ce Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 25 Sep 2023 09:49:42 +0900 Subject: [PATCH 088/290] docs: add changelog --- user_guide_src/source/changelogs/v4.5.0.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index 50dae35cd5a2..8bff44c26ab0 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -21,12 +21,28 @@ BREAKING Behavior Changes ================ +- **Logger:** The :php:func:`log_message()` function and the logger methods in + ``CodeIgniter\Log\Logger`` now do not return ``bool`` values. The return types + have been fixed to ``void`` to follow the PSR-3 interface. + Interface Changes ================= +.. note:: As long as you have not extended the relevant CodeIgniter core classes + or implemented these interfaces, all these changes are backward compatible + and require no intervention. + +- **Logger:** The `psr/log `_ package has + been upgraded to v2.0.0. + Method Signature Changes ======================== +- **Logger:** The method signatures of the methods in ``CodeIgniter\Log\Logger`` + that implements the PSR-3 interface have been fixed. The ``bool`` return + types are changed to ``void``. The ``$message`` parameters now have + ``string|Stringable`` types. + .. _v450-removed-deprecated-items: Removed Deprecated Items From d9d3214b9539a5b8dfb8a3db9f731454184cb8e1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 25 Sep 2023 11:51:02 +0900 Subject: [PATCH 089/290] feat: db:table shows db config --- system/Commands/Database/ShowTableInfo.php | 18 ++++++++++++++++++ .../Commands/Database/ShowTableInfoTest.php | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/system/Commands/Database/ShowTableInfo.php b/system/Commands/Database/ShowTableInfo.php index 07aa60c961ca..92fde73aed28 100644 --- a/system/Commands/Database/ShowTableInfo.php +++ b/system/Commands/Database/ShowTableInfo.php @@ -102,6 +102,8 @@ public function run(array $params) $this->db = Database::connect(); $this->DBPrefix = $this->db->getPrefix(); + $this->showDBConfig(); + $tables = $this->db->listTables(); if (array_key_exists('desc', $params)) { @@ -145,6 +147,22 @@ public function run(array $params) $this->showDataOfTable($tableName, $limitRows, $limitFieldValue); } + private function showDBConfig(): void + { + $data = [[ + 'hostname' => $this->db->hostname, + 'database' => $this->db->getDatabase(), + 'username' => $this->db->username, + 'DBDriver' => $this->db->getPlatform(), + 'DBPrefix' => $this->DBPrefix, + 'port' => $this->db->port, + ]]; + CLI::table( + $data, + ['hostname', 'database', 'username', 'DBDriver', 'DBPrefix', 'port'] + ); + } + private function removeDBPrefix(): void { $this->db->setPrefix(''); diff --git a/tests/system/Commands/Database/ShowTableInfoTest.php b/tests/system/Commands/Database/ShowTableInfoTest.php index be52a71b2991..718cb1e9d827 100644 --- a/tests/system/Commands/Database/ShowTableInfoTest.php +++ b/tests/system/Commands/Database/ShowTableInfoTest.php @@ -64,6 +64,20 @@ public function testDbTable(): void $this->assertMatchesRegularExpression($expectedPattern, $result); } + public function testDbTableShowsDBConfig(): void + { + command('db:table'); + + $result = $this->getNormalizedResult(); + + $expected = <<<'EOL' + +-----------+----------+----------+----------+----------+------+ + | hostname | database | username | DBDriver | DBPrefix | port | + +-----------+----------+----------+----------+----------+------+ + EOL; + $this->assertStringContainsString($expected, $result); + } + public function testDbTableShow(): void { command('db:table --show'); From 7eadd2422a3cf77df84b273675f06ec343ba052d Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 25 Sep 2023 12:03:20 +0900 Subject: [PATCH 090/290] docs: add missing @property --- system/Database/BaseConnection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 83303fea963d..6d92965f0b94 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -45,6 +45,7 @@ * @property int $transDepth * @property bool $transFailure * @property bool $transStatus + * @property string $username * * @template TConnection * @template TResult From 368036017470c53460db6794b212fa28665cd172 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 25 Sep 2023 12:59:07 +0900 Subject: [PATCH 091/290] test: fix assertion --- tests/system/Commands/Database/ShowTableInfoTest.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/system/Commands/Database/ShowTableInfoTest.php b/tests/system/Commands/Database/ShowTableInfoTest.php index 718cb1e9d827..9b54ff2acf9e 100644 --- a/tests/system/Commands/Database/ShowTableInfoTest.php +++ b/tests/system/Commands/Database/ShowTableInfoTest.php @@ -70,12 +70,8 @@ public function testDbTableShowsDBConfig(): void $result = $this->getNormalizedResult(); - $expected = <<<'EOL' - +-----------+----------+----------+----------+----------+------+ - | hostname | database | username | DBDriver | DBPrefix | port | - +-----------+----------+----------+----------+----------+------+ - EOL; - $this->assertStringContainsString($expected, $result); + $expectedPattern = '/\| hostname[[:blank:]]+\| database[[:blank:]]+\| username[[:blank:]]+\| DBDriver[[:blank:]]+\| DBPrefix[[:blank:]]+\| port[[:blank:]]+\|/'; + $this->assertMatchesRegularExpression($expectedPattern, $result); } public function testDbTableShow(): void From 92ef367a0a54d7173dd6c90b8b2ec0e561caa0ca Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 10:22:03 +0900 Subject: [PATCH 092/290] fix: incorrect return types --- phpstan-baseline.php | 5 ----- system/BaseModel.php | 7 ++++--- system/Model.php | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index a08185d5f2b0..542ca2dbf8c2 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -71,11 +71,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/BaseModel.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Only booleans are allowed in an if condition, array\\|null given\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/BaseModel.php', -]; $ignoreErrors[] = [ 'message' => '#^Only booleans are allowed in an if condition, int given\\.$#', 'count' => 1, diff --git a/system/BaseModel.php b/system/BaseModel.php index 0cf63ac9f3ec..63a87e587828 100644 --- a/system/BaseModel.php +++ b/system/BaseModel.php @@ -1657,7 +1657,7 @@ protected function objectToArray($data, bool $onlyChanged = true, bool $recursiv $properties = $this->objectToRawArray($data, $onlyChanged, $recursive); // Convert any Time instances to appropriate $dateFormat - if ($properties) { + if ($properties !== []) { $properties = array_map(function ($value) { if ($value instanceof Time) { return $this->timeToDate($value); @@ -1678,12 +1678,13 @@ protected function objectToArray($data, bool $onlyChanged = true, bool $recursiv * @param bool $onlyChanged Only Changed Property * @param bool $recursive If true, inner entities will be casted as array as well * - * @return array|null Array + * @return array Array with raw values. * * @throws ReflectionException */ - protected function objectToRawArray($data, bool $onlyChanged = true, bool $recursive = false): ?array + protected function objectToRawArray($data, bool $onlyChanged = true, bool $recursive = false): array { + // @TODO Should define Interface or Class. Entity has toRawArray() now. if (method_exists($data, 'toRawArray')) { $properties = $data->toRawArray($onlyChanged, $recursive); } else { diff --git a/system/Model.php b/system/Model.php index e53c0aae4bd1..b8c4de4470a2 100644 --- a/system/Model.php +++ b/system/Model.php @@ -783,11 +783,11 @@ public function update($id = null, $data = null): bool * @param object|string $data * @param bool $recursive If true, inner entities will be cast as array as well * - * @return array|null Array + * @return array Array with raw values. * * @throws ReflectionException */ - protected function objectToRawArray($data, bool $onlyChanged = true, bool $recursive = false): ?array + protected function objectToRawArray($data, bool $onlyChanged = true, bool $recursive = false): array { $properties = parent::objectToRawArray($data, $onlyChanged); From d764dafb32cfc6f2703d711018c849c1ebf2c41f Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 10:38:12 +0900 Subject: [PATCH 093/290] docs: add changelog and upgrade --- user_guide_src/source/changelogs/v4.5.0.rst | 11 +++++++++++ user_guide_src/source/installation/upgrade_450.rst | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index 8bff44c26ab0..7c83b9d46333 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -35,9 +35,20 @@ Interface Changes - **Logger:** The `psr/log `_ package has been upgraded to v2.0.0. +.. _v450-method-signature-changes: + Method Signature Changes ======================== +Return Type Changes +------------------- + +- **Model:** The return type of the ``objectToRawArray()`` method in the ``Model`` + and ``BaseModel`` classes has been changed to ``array``. + +Others +------ + - **Logger:** The method signatures of the methods in ``CodeIgniter\Log\Logger`` that implements the PSR-3 interface have been fixed. The ``bool`` return types are changed to ``void``. The ``$message`` parameters now have diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst index a9925320bf2d..74fcf24a9e0e 100644 --- a/user_guide_src/source/installation/upgrade_450.rst +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -18,11 +18,18 @@ Mandatory File Changes Breaking Changes **************** +Method Signature Changes +======================== + +Some method signature changes have been made. Classes that extend them should +update their APIs to reflect the changes. See :ref:`ChangeLog ` +for details. + Removed Deprecated Items ======================== Some deprecated items have been removed. If you extend these classes and are -using them, upgrade your code. See :ref:`v450-removed-deprecated-items` for details. +using them, upgrade your code. See :ref:`ChangeLog ` for details. Breaking Enhancements ********************* From 952aabd4feca97fd6a5f9cc7d24741c64cc46675 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Sep 2023 15:31:36 +0900 Subject: [PATCH 094/290] docs: add previous type Co-authored-by: MGatner --- user_guide_src/source/changelogs/v4.5.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index 7c83b9d46333..49b2fd6f4ac5 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -44,7 +44,7 @@ Return Type Changes ------------------- - **Model:** The return type of the ``objectToRawArray()`` method in the ``Model`` - and ``BaseModel`` classes has been changed to ``array``. + and ``BaseModel`` classes has been changed from ``?array`` to ``array``. Others ------ From 9d26d216dd4f4d36f31b7c25f0d44996f1e49f1a Mon Sep 17 00:00:00 2001 From: c4user Date: Tue, 4 Apr 2023 01:08:20 +0300 Subject: [PATCH 095/290] Filters's is execute order fixed. --- system/Filters/Filters.php | 44 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 33101aa512db..5b45e4fbe408 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -245,9 +245,9 @@ public function initialize(?string $uri = null) return $this; } - $this->processGlobals($uri); - $this->processMethods(); $this->processFilters($uri); + $this->processMethods(); + $this->processGlobals($uri); // Set the toolbar filter to the last position to be executed if (in_array('toolbar', $this->filters['after'], true) @@ -436,6 +436,8 @@ protected function processGlobals(?string $uri = null) // Add any global filters, unless they are excluded for this URI $sets = ['before', 'after']; + $filters = []; + foreach ($sets as $set) { if (isset($this->config->globals[$set])) { // look at each alias in the group @@ -455,11 +457,19 @@ protected function processGlobals(?string $uri = null) } if ($keep) { - $this->filters[$set][] = $alias; + $filters[$set][] = $alias; } } } } + + if (isset($filters['before'])) { + $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + } + + if (isset($filters['after'])) { + $this->filters['after'] = array_merge($this->filters['after'], $filters['after']); + } } /** @@ -477,7 +487,7 @@ protected function processMethods() $method = strtolower($this->request->getMethod()) ?? 'cli'; if (array_key_exists($method, $this->config->methods)) { - $this->filters['before'] = array_merge($this->filters['before'], $this->config->methods[$method]); + $this->filters['before'] = array_merge($this->config->methods[$method], $this->filters['before']); } } @@ -497,6 +507,8 @@ protected function processFilters(?string $uri = null) $uri = strtolower(trim($uri, '/ ')); // Add any filters that apply to this URI + $filters = []; + foreach ($this->config->filters as $alias => $settings) { // Look for inclusion rules if (isset($settings['before'])) { @@ -506,7 +518,7 @@ protected function processFilters(?string $uri = null) // Get arguments and clean name [$name, $arguments] = $this->getCleanName($alias); - $this->filters['before'][] = $name; + $filters['before'][] = $name; $this->registerArguments($name, $arguments); } @@ -519,7 +531,7 @@ protected function processFilters(?string $uri = null) // Get arguments and clean name [$name, $arguments] = $this->getCleanName($alias); - $this->filters['after'][] = $name; + $filters['after'][] = $name; // The arguments may have already been registered in the before filter. // So disable check. @@ -527,6 +539,14 @@ protected function processFilters(?string $uri = null) } } } + + if (isset($filters['before'])) { + $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + } + + if (isset($filters['after'])) { + $this->filters['after'] = array_merge($this->filters['after'], $filters['after']); + } } /** @@ -563,6 +583,8 @@ private function registerArguments(string $name, array $arguments, bool $check = */ protected function processAliasesToClass(string $position) { + $filtersClass = []; + foreach ($this->filters[$position] as $alias => $rules) { if (is_numeric($alias) && is_string($rules)) { $alias = $rules; @@ -573,14 +595,20 @@ protected function processAliasesToClass(string $position) } if (is_array($this->config->aliases[$alias])) { - $this->filtersClass[$position] = array_merge($this->filtersClass[$position], $this->config->aliases[$alias]); + $filtersClass = array_merge($filtersClass, $this->config->aliases[$alias]); } else { - $this->filtersClass[$position][] = $this->config->aliases[$alias]; + $filtersClass[] = $this->config->aliases[$alias]; } } // when using enableFilter() we already write the class name in $filtersClass as well as the // alias in $filters. This leads to duplicates when using route filters. + if ($position === 'before') { + $this->filtersClass[$position] = array_merge($filtersClass, $this->filtersClass[$position]); + } else { + $this->filtersClass[$position] = array_merge($this->filtersClass[$position], $filtersClass); + } + // Since some filters like rate limiters rely on being executed once a request we filter em here. $this->filtersClass[$position] = array_values(array_unique($this->filtersClass[$position])); } From 3b0ec991bc21c76142f6254aacfbb9a01779a7e6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 17:09:38 +0900 Subject: [PATCH 096/290] test: update test expectations --- tests/system/Commands/Utilities/Routes/FilterFinderTest.php | 6 +++--- tests/system/Filters/FiltersTest.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php index 652102976a19..c97212cd5fed 100644 --- a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php +++ b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php @@ -145,7 +145,7 @@ public function testFindGlobalsAndRouteFilters(): void $filters = $finder->find('admin'); $expected = [ - 'before' => ['honeypot', 'csrf'], + 'before' => ['csrf', 'honeypot'], 'after' => ['honeypot', 'toolbar'], ]; $this->assertSame($expected, $filters); @@ -163,7 +163,7 @@ public function testFindGlobalsAndRouteClassnameFilters(): void $filters = $finder->find('admin'); $expected = [ - 'before' => [InvalidChars::class, 'csrf'], + 'before' => ['csrf', InvalidChars::class], 'after' => [InvalidChars::class, 'toolbar'], ]; $this->assertSame($expected, $filters); @@ -181,7 +181,7 @@ public function testFindGlobalsAndRouteMultipleFilters(): void $filters = $finder->find('admin'); $expected = [ - 'before' => ['honeypot', InvalidChars::class, 'csrf'], + 'before' => ['csrf', 'honeypot', InvalidChars::class], 'after' => ['honeypot', InvalidChars::class, 'toolbar'], ]; $this->assertSame($expected, $filters); diff --git a/tests/system/Filters/FiltersTest.php b/tests/system/Filters/FiltersTest.php index efe47300fd49..7faf127f4647 100644 --- a/tests/system/Filters/FiltersTest.php +++ b/tests/system/Filters/FiltersTest.php @@ -406,8 +406,8 @@ public function testProcessMethodProcessesCombinedAfterForToolbar(): void $expected = [ 'before' => ['bar'], 'after' => [ - 'bazg', 'foof', + 'bazg', 'toolbar', ], ]; @@ -1049,8 +1049,8 @@ public function testMatchesURICaseInsensitively(): void 'frak', ], 'after' => [ - 'baz', 'frak', + 'baz', ], ]; $this->assertSame($expected, $filters->initialize($uri)->getFilters()); From b2998ba0df640150356a7505738d070847b0e42d Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 21 Sep 2023 10:00:35 +0900 Subject: [PATCH 097/290] feat: add feature flag for filter execution order --- app/Config/Feature.php | 5 +++++ system/Filters/Filters.php | 31 +++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/Config/Feature.php b/app/Config/Feature.php index 8ed8e01a0158..39a37676183c 100644 --- a/app/Config/Feature.php +++ b/app/Config/Feature.php @@ -13,4 +13,9 @@ class Feature extends BaseConfig * Use improved new auto routing instead of the default legacy version. */ public bool $autoRoutesImproved = false; + + /** + * Use filter execution order in 4.4 or before. + */ + public bool $oldFilterOrder = false; } diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 5b45e4fbe408..003c43769fc5 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -15,6 +15,7 @@ use CodeIgniter\Filters\Exceptions\FilterException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use Config\Feature; use Config\Filters as FiltersConfig; use Config\Modules; use Config\Services; @@ -245,9 +246,15 @@ public function initialize(?string $uri = null) return $this; } - $this->processFilters($uri); - $this->processMethods(); - $this->processGlobals($uri); + if (config(Feature::class)->oldFilterOrder) { + $this->processGlobals($uri); + $this->processMethods(); + $this->processFilters($uri); + } else { + $this->processFilters($uri); + $this->processMethods(); + $this->processGlobals($uri); + } // Set the toolbar filter to the last position to be executed if (in_array('toolbar', $this->filters['after'], true) @@ -464,7 +471,11 @@ protected function processGlobals(?string $uri = null) } if (isset($filters['before'])) { - $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + if (config(Feature::class)->oldFilterOrder) { + $this->filters['before'] = array_merge($this->filters['before'], $filters['before']); + } else { + $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + } } if (isset($filters['after'])) { @@ -487,7 +498,11 @@ protected function processMethods() $method = strtolower($this->request->getMethod()) ?? 'cli'; if (array_key_exists($method, $this->config->methods)) { - $this->filters['before'] = array_merge($this->config->methods[$method], $this->filters['before']); + if (config(Feature::class)->oldFilterOrder) { + $this->filters['before'] = array_merge($this->filters['before'], $this->config->methods[$method]); + } else { + $this->filters['before'] = array_merge($this->config->methods[$method], $this->filters['before']); + } } } @@ -541,7 +556,11 @@ protected function processFilters(?string $uri = null) } if (isset($filters['before'])) { - $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + if (config(Feature::class)->oldFilterOrder) { + $this->filters['before'] = array_merge($this->filters['before'], $filters['before']); + } else { + $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + } } if (isset($filters['after'])) { From d101e3594763ce5117c7da306dabce9d9c19db69 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 21 Sep 2023 10:26:52 +0900 Subject: [PATCH 098/290] docs: add about Filter Execution Order --- user_guide_src/source/incoming/filters.rst | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/user_guide_src/source/incoming/filters.rst b/user_guide_src/source/incoming/filters.rst index 3965dce51b0e..bbeebdd87ff4 100644 --- a/user_guide_src/source/incoming/filters.rst +++ b/user_guide_src/source/incoming/filters.rst @@ -85,11 +85,11 @@ There are two ways to configure filters when they get run. One is done in If you want to specify filter to a specific route, use **app/Config/Routes.php** and see :ref:`URI Routing `. -The filters that are specified to a route (in **app/Config/Routes.php**) are -executed before the filters specified in **app/Config/Filters.php**. - .. Note:: The safest way to apply filters is to :ref:`disable auto-routing `, and :ref:`set filters to routes `. +app/Config/Filters.php +====================== + The **app/Config/Filters.php** file contains four properties that allow you to configure exactly when the filters run. @@ -99,7 +99,7 @@ configure exactly when the filters run. it can be accessible with ``blog``, ``blog/index``, and ``blog/index/1``, etc. $aliases -======== +-------- The ``$aliases`` array is used to associate a simple name with one or more fully-qualified class names that are the filters to run: @@ -117,7 +117,7 @@ You can combine multiple filters into one alias, making complex sets of filters You should define as many aliases as you need. $globals -======== +-------- The second section allows you to define any filters that should be applied to every request made by the framework. You should take care with how many you use here, since it could have performance implications to have too many @@ -126,7 +126,7 @@ run on every request. Filters can be specified by adding their alias to either t .. literalinclude:: filters/005.php Except for a Few URIs ---------------------- +^^^^^^^^^^^^^^^^^^^^^ There are times where you want to apply a filter to almost every request, but have a few that should be left alone. One common example is if you need to exclude a few URI's from the CSRF protection filter to allow requests from @@ -143,7 +143,7 @@ URI paths, you can use an array of URI path patterns: .. literalinclude:: filters/007.php $methods -======== +-------- .. Warning:: If you use ``$methods`` filters, you should :ref:`disable Auto Routing (Legacy) ` because :ref:`auto-routing-legacy` permits any HTTP method to access a controller. @@ -161,7 +161,7 @@ In addition to the standard HTTP methods, this also supports one special case: ` all requests that were run from the command line. $filters -======== +-------- This property is an array of filter aliases. For each alias, you can specify ``before`` and ``after`` arrays that contain a list of URI path (relative to BaseURL) patterns that filter should apply to: @@ -171,7 +171,7 @@ a list of URI path (relative to BaseURL) patterns that filter should apply to: .. _filters-filters-filter-arguments: Filter Arguments ----------------- +^^^^^^^^^^^^^^^^ .. versionadded:: 4.4.0 @@ -184,6 +184,22 @@ will be passed in ``$arguments`` to the ``group`` filter's ``before()`` methods. When the URI matches ``admin/users/*'``, the array ``['users.manage']`` will be passed in ``$arguments`` to the ``permission`` filter's ``before()`` methods. +Filter Execution Order +====================== + +.. important:: Starting with v4.5.0, the order in which filters are executed has + changed. If you wish to maintain the same execution order as in previous versions, + you must set ``true`` to ``Config\Feature::$oldFilterOrder``. + +Filters are executed in the following order: + +- **Before Filters**: globals → methods → filters → route +- **After Filters**: route → filters → globals + +.. note:: Prior to v4.5.0, the filters that are specified to a route + (in **app/Config/Routes.php**) are executed before the filters specified in + **app/Config/Filters.php**. + ****************** Confirming Filters ****************** From 42c02ea0683799e795d1a1891f300de6bc96d5cc Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 21 Sep 2023 10:50:09 +0900 Subject: [PATCH 099/290] docs: add changelog and ugrading --- user_guide_src/source/changelogs/v4.5.0.rst | 9 +++++++++ user_guide_src/source/incoming/filters.rst | 5 ++++- .../source/installation/upgrade_450.rst | 20 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index 49b2fd6f4ac5..3f509d73431f 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -21,6 +21,15 @@ BREAKING Behavior Changes ================ +Filter Execution Order +---------------------- + +The order in which Controller Filters are executed has changed. See +:ref:`Upgrading Guide ` for details. + +Others +------ + - **Logger:** The :php:func:`log_message()` function and the logger methods in ``CodeIgniter\Log\Logger`` now do not return ``bool`` values. The return types have been fixed to ``void`` to follow the PSR-3 interface. diff --git a/user_guide_src/source/incoming/filters.rst b/user_guide_src/source/incoming/filters.rst index bbeebdd87ff4..9dda87c9bb23 100644 --- a/user_guide_src/source/incoming/filters.rst +++ b/user_guide_src/source/incoming/filters.rst @@ -184,6 +184,8 @@ will be passed in ``$arguments`` to the ``group`` filter's ``before()`` methods. When the URI matches ``admin/users/*'``, the array ``['users.manage']`` will be passed in ``$arguments`` to the ``permission`` filter's ``before()`` methods. +.. _filter-execution-order: + Filter Execution Order ====================== @@ -198,7 +200,8 @@ Filters are executed in the following order: .. note:: Prior to v4.5.0, the filters that are specified to a route (in **app/Config/Routes.php**) are executed before the filters specified in - **app/Config/Filters.php**. + **app/Config/Filters.php**. See + :ref:`Upgrading Guide ` for details. ****************** Confirming Filters diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst index 74fcf24a9e0e..c9a80e5e3efe 100644 --- a/user_guide_src/source/installation/upgrade_450.rst +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -25,6 +25,26 @@ Some method signature changes have been made. Classes that extend them should update their APIs to reflect the changes. See :ref:`ChangeLog ` for details. +.. _upgrade-450-filter-execution-order: + +Filter Execution Order +====================== + +The order in which Controller Filters are executed has changed. + +Before Filters:: + + Previous: route → globals → methods → filters + Now: globals → methods → filters → route + +After Filters:: + + Previous: route → globals → filters + Now: route → filters → globals + +If you wish to maintain the same execution order as in previous versions, set +``true`` to ``Config\Feature::$oldFilterOrder``. See also :ref:`filter-execution-order`. + Removed Deprecated Items ======================== From 72968b79242dcadaa7a1bb4abb507359a529e87d Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 21 Sep 2023 11:09:35 +0900 Subject: [PATCH 100/290] test: add tests for filter order --- .../Utilities/Routes/FilterFinderTest.php | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php index c97212cd5fed..0a4d1e958e2f 100644 --- a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php +++ b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php @@ -23,6 +23,7 @@ use CodeIgniter\Router\Router; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\ConfigFromArrayTrait; +use Config\Feature; use Config\Filters as FiltersConfig; use Config\Modules; use Config\Routing; @@ -186,4 +187,131 @@ public function testFindGlobalsAndRouteMultipleFilters(): void ]; $this->assertSame($expected, $filters); } + + public function testFilterOrder() + { + $collection = $this->createRouteCollection([]); + $collection->get('/', ' Home::index', ['filter' => ['route1', 'route2']]); + $router = $this->createRouter($collection); + $filters = $this->createFilters([ + 'aliases' => [ + 'global1' => 'Dummy', + 'global2' => 'Dummy', + 'method1' => 'Dummy', + 'method2' => 'Dummy', + 'filter1' => 'Dummy', + 'filter2' => 'Dummy', + 'route1' => 'Dummy', + 'route2' => 'Dummy', + ], + 'globals' => [ + 'before' => [ + 'global1', + 'global2', + ], + 'after' => [ + 'global1', + 'global2', + ], + ], + 'methods' => [ + 'get' => ['method1', 'method2'], + ], + 'filters' => [ + 'filter1' => ['before' => '*', 'after' => '*'], + 'filter2' => ['before' => '*', 'after' => '*'], + ], + ]); + + $finder = new FilterFinder($router, $filters); + + $filters = $finder->find('/'); + + $expected = [ + 'before' => [ + 'global1', + 'global2', + 'method1', + 'method2', + 'filter1', + 'filter2', + 'route1', + 'route2', + ], + 'after' => [ + 'route1', + 'route2', + 'filter1', + 'filter2', + 'global1', + 'global2', + ], + ]; + $this->assertSame($expected, $filters); + } + + public function testFilterOrderWithOldFilterOrder() + { + $feature = config(Feature::class); + $feature->oldFilterOrder = true; + + $collection = $this->createRouteCollection([]); + $collection->get('/', ' Home::index', ['filter' => ['route1', 'route2']]); + $router = $this->createRouter($collection); + $filters = $this->createFilters([ + 'aliases' => [ + 'global1' => 'Dummy', + 'global2' => 'Dummy', + 'method1' => 'Dummy', + 'method2' => 'Dummy', + 'filter1' => 'Dummy', + 'filter2' => 'Dummy', + 'route1' => 'Dummy', + 'route2' => 'Dummy', + ], + 'globals' => [ + 'before' => [ + 'global1', + 'global2', + ], + 'after' => [ + 'global1', + 'global2', + ], + ], + 'methods' => [ + 'get' => ['method1', 'method2'], + ], + 'filters' => [ + 'filter1' => ['before' => '*', 'after' => '*'], + 'filter2' => ['before' => '*', 'after' => '*'], + ], + ]); + + $finder = new FilterFinder($router, $filters); + + $filters = $finder->find('/'); + + $expected = [ + 'before' => [ + 'route1', + 'route2', + 'global1', + 'global2', + 'method1', + 'method2', + 'filter1', + 'filter2', + ], + 'after' => [ + 'route1', + 'route2', + 'global1', + 'global2', + 'filter1', + 'filter2', + ], + ]; + $this->assertSame($expected, $filters); + } } From ac743b29af3750993aa3f646984f76d08e12e9f2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 22 Sep 2023 16:13:09 +0900 Subject: [PATCH 101/290] fix: change filter order in route filter and filters filter --- system/CodeIgniter.php | 6 ++++++ system/Commands/Utilities/Routes/FilterFinder.php | 7 +++++++ system/Filters/Filters.php | 4 ++++ .../system/Commands/Utilities/Routes/FilterFinderTest.php | 8 ++++---- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index da24156b654f..678e6d4cd1e2 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -31,6 +31,7 @@ use CodeIgniter\Router\Router; use Config\App; use Config\Cache; +use Config\Feature; use Config\Kint as KintConfig; use Config\Services; use Exception; @@ -458,6 +459,11 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // we need to ensure it's active for the current request if ($routeFilter !== null) { $filters->enableFilters($routeFilter, 'before'); + + if (! config(Feature::class)->oldFilterOrder) { + $routeFilter = array_reverse($routeFilter); + } + $filters->enableFilters($routeFilter, 'after'); } diff --git a/system/Commands/Utilities/Routes/FilterFinder.php b/system/Commands/Utilities/Routes/FilterFinder.php index 40a834df52f1..cc1f6d2b05f4 100644 --- a/system/Commands/Utilities/Routes/FilterFinder.php +++ b/system/Commands/Utilities/Routes/FilterFinder.php @@ -15,6 +15,7 @@ use CodeIgniter\Filters\Filters; use CodeIgniter\HTTP\Exceptions\RedirectException; use CodeIgniter\Router\Router; +use Config\Feature; use Config\Services; /** @@ -52,7 +53,13 @@ public function find(string $uri): array // Add route filters try { $routeFilters = $this->getRouteFilters($uri); + $this->filters->enableFilters($routeFilters, 'before'); + + if (! config(Feature::class)->oldFilterOrder) { + $routeFilters = array_reverse($routeFilters); + } + $this->filters->enableFilters($routeFilters, 'after'); $this->filters->initialize($uri); diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 003c43769fc5..ad74b016acaf 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -564,6 +564,10 @@ protected function processFilters(?string $uri = null) } if (isset($filters['after'])) { + if (! config(Feature::class)->oldFilterOrder) { + $filters['after'] = array_reverse($filters['after']); + } + $this->filters['after'] = array_merge($this->filters['after'], $filters['after']); } } diff --git a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php index 0a4d1e958e2f..09877c279c73 100644 --- a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php +++ b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php @@ -210,8 +210,8 @@ public function testFilterOrder() 'global2', ], 'after' => [ - 'global1', 'global2', + 'global1', ], ], 'methods' => [ @@ -239,12 +239,12 @@ public function testFilterOrder() 'route2', ], 'after' => [ - 'route1', 'route2', - 'filter1', + 'route1', 'filter2', - 'global1', + 'filter1', 'global2', + 'global1', ], ]; $this->assertSame($expected, $filters); From 47b8493b02214521bbeaef747a2b50289e59769c Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 22 Sep 2023 16:14:39 +0900 Subject: [PATCH 102/290] refactor: rename variable name --- system/CodeIgniter.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 678e6d4cd1e2..22f86b5d4c7e 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -447,7 +447,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache return $response; } - $routeFilter = $this->tryToRouteIt($routes); + $routeFilters = $this->tryToRouteIt($routes); $uri = $this->determinePath(); @@ -457,14 +457,14 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // If any filters were specified within the routes file, // we need to ensure it's active for the current request - if ($routeFilter !== null) { - $filters->enableFilters($routeFilter, 'before'); + if ($routeFilters !== null) { + $filters->enableFilters($routeFilters, 'before'); if (! config(Feature::class)->oldFilterOrder) { - $routeFilter = array_reverse($routeFilter); + $routeFilters = array_reverse($routeFilters); } - $filters->enableFilters($routeFilter, 'after'); + $filters->enableFilters($routeFilters, 'after'); } // Run "before" filters From 5deb11f00f6156d8cac859d88204fd179724af0d Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 22 Sep 2023 16:41:07 +0900 Subject: [PATCH 103/290] docs: update docs --- user_guide_src/source/incoming/filters.rst | 5 ++- .../source/installation/upgrade_450.rst | 43 +++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/user_guide_src/source/incoming/filters.rst b/user_guide_src/source/incoming/filters.rst index 9dda87c9bb23..5292841d895f 100644 --- a/user_guide_src/source/incoming/filters.rst +++ b/user_guide_src/source/incoming/filters.rst @@ -200,8 +200,9 @@ Filters are executed in the following order: .. note:: Prior to v4.5.0, the filters that are specified to a route (in **app/Config/Routes.php**) are executed before the filters specified in - **app/Config/Filters.php**. See - :ref:`Upgrading Guide ` for details. + **app/Config/Filters.php**. And the After Filters in Route filters and Filters + filters execution order were not reversed. + See :ref:`Upgrading Guide ` for details. ****************** Confirming Filters diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst index c9a80e5e3efe..7a4fbc0b29da 100644 --- a/user_guide_src/source/installation/upgrade_450.rst +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -31,19 +31,46 @@ Filter Execution Order ====================== The order in which Controller Filters are executed has changed. +If you wish to maintain the same execution order as in previous versions, set +``true`` to ``Config\Feature::$oldFilterOrder``. See also :ref:`filter-execution-order`. -Before Filters:: +1. The order of execution of filter groups has been changed. - Previous: route → globals → methods → filters - Now: globals → methods → filters → route + Before Filters:: -After Filters:: + Previous: route → globals → methods → filters + Now: globals → methods → filters → route - Previous: route → globals → filters - Now: route → filters → globals + After Filters:: -If you wish to maintain the same execution order as in previous versions, set -``true`` to ``Config\Feature::$oldFilterOrder``. See also :ref:`filter-execution-order`. + Previous: route → globals → filters + Now: route → filters → globals + +2. The After Filters in *Route* filters and *Filters* filters execution order is now +reversed. + + When you have the following configuration: + + .. code-block:: php + + // In app/Config/Routes.php + $routes->get('/', 'Home::index', ['filter' => ['route1', 'route2']]); + + // In app/Config/Filters.php + public array $filters = [ + 'filter1' => ['before' => '*', 'after' => '*'], + 'filter2' => ['before' => '*', 'after' => '*'], + ]; + + Before Filters:: + + Previous: route1 → route2 → filter1 → filter2 + Now: filter1 → filter2 → route1 → route2 + + After Filters:: + + Previous: route1 → route2 → filter1 → filter2 + Now: route2 → route1 → filter2 → filter1 Removed Deprecated Items ======================== From 7687855678c4bdecb539929eb76626f154c07334 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 22 Sep 2023 16:48:29 +0900 Subject: [PATCH 104/290] test: update assertion --- tests/system/Commands/Utilities/Routes/FilterFinderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php index 09877c279c73..c59c9b9aee8e 100644 --- a/tests/system/Commands/Utilities/Routes/FilterFinderTest.php +++ b/tests/system/Commands/Utilities/Routes/FilterFinderTest.php @@ -183,7 +183,7 @@ public function testFindGlobalsAndRouteMultipleFilters(): void $expected = [ 'before' => ['csrf', 'honeypot', InvalidChars::class], - 'after' => ['honeypot', InvalidChars::class, 'toolbar'], + 'after' => [InvalidChars::class, 'honeypot', 'toolbar'], ]; $this->assertSame($expected, $filters); } From e06edd276d59633e247668d968063e399aa22197 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 17:21:12 +0900 Subject: [PATCH 105/290] test: fix namespace --- .../Translation/LocalizationFinder/ArrayHelperTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php b/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php index 499d092521fe..cc64f3cd3cbf 100644 --- a/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php +++ b/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php @@ -9,9 +9,8 @@ * the LICENSE file that was distributed with this source code. */ -namespace CodeIgniter\Commands\TranslationLocalizationFinder; +namespace CodeIgniter\Commands\Translation\LocalizationFinder; -use CodeIgniter\Commands\Translation\LocalizationFinder\ArrayHelper; use CodeIgniter\Test\CIUnitTestCase; /** From c18a66188096cb9c450ff46cbca37da48d47cbf1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 17:24:29 +0900 Subject: [PATCH 106/290] fix: Config\Kint does not extend BaseConfig We do not need to share the Config file, and if we enable Config Caching, it cannot be loaded from FactoriesCache when we run `composer install --no-dev`. Because the autoloader for Kint is initialized in CodeIgniter::initialize(). --- app/Config/Kint.php | 3 +-- system/CodeIgniter.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Config/Kint.php b/app/Config/Kint.php index cc8b54592b6b..36232446889b 100644 --- a/app/Config/Kint.php +++ b/app/Config/Kint.php @@ -2,7 +2,6 @@ namespace Config; -use CodeIgniter\Config\BaseConfig; use Kint\Parser\ConstructablePluginInterface; use Kint\Renderer\AbstractRenderer; use Kint\Renderer\Rich\TabPluginInterface; @@ -18,7 +17,7 @@ * * @see https://kint-php.github.io/kint/ for details on these settings. */ -class Kint extends BaseConfig +class Kint { /* |-------------------------------------------------------------------------- diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 22f86b5d4c7e..15ca7f81ad18 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -296,7 +296,7 @@ private function autoloadKint(): void private function configureKint(): void { - $config = config(KintConfig::class); + $config = new KintConfig(); Kint::$depth_limit = $config->maxDepth; Kint::$display_called_from = $config->displayCalledFrom; From 80057d729d1de2ae494156b75766259a86148077 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 17:43:28 +0900 Subject: [PATCH 107/290] docs: add @see ArrayHelperTest --- system/Commands/Translation/LocalizationFinder/ArrayHelper.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php index d747127c3a42..c3cf4b9c53d9 100644 --- a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php +++ b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php @@ -11,6 +11,9 @@ namespace CodeIgniter\Commands\Translation\LocalizationFinder; +/** + * @see \CodeIgniter\Commands\Translation\LocalizationFinder\ArrayHelperTest + */ class ArrayHelper { /** From ff2c6b65f23b871c6a77e9507ab2f903a8e7a5d8 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 17:53:55 +0900 Subject: [PATCH 108/290] docs: add upgrade_450 --- user_guide_src/source/installation/upgrade_450.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst index 7a4fbc0b29da..240203f4b888 100644 --- a/user_guide_src/source/installation/upgrade_450.rst +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -106,6 +106,9 @@ Config - app/Config/Feature.php - ``Config\Feature::$multipleFilters`` has been removed, because now :ref:`multiple-filters` are always enabled. +- app/Config/Kint.php + - It no longer extends ``BaseConfig`` because enabling + :ref:`factories-config-caching` could cause errors. All Changes =========== From ed25c835e1031cb8bf68657c8c1bdfb6c558c922 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 20:03:10 +0900 Subject: [PATCH 109/290] config: move Config namespace to AutoloadConfig We can't customize `Config` namespace name. --- app/Config/Autoload.php | 1 - system/Config/AutoloadConfig.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 22f05ecdab26..34d234631949 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -46,7 +46,6 @@ class Autoload extends AutoloadConfig */ public $psr4 = [ APP_NAMESPACE => APPPATH, // For custom app namespace - 'Config' => APPPATH . 'Config', ]; /** diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index da08dd6f6640..1417b6096a10 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -93,6 +93,7 @@ class AutoloadConfig protected $corePsr4 = [ 'CodeIgniter' => SYSTEMPATH, 'App' => APPPATH, // To ensure filters, etc still found, + 'Config' => APPPATH . 'Config', ]; /** From 2431b5b2d090058dbaf0e22170fe93f5c9be3b51 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 20:04:18 +0900 Subject: [PATCH 110/290] refactor: remove `App` namespace in AutoloadConfig It is set in Config\Autoload, and it may be renamed. --- system/Config/AutoloadConfig.php | 1 - 1 file changed, 1 deletion(-) diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index 1417b6096a10..3f0dc494f0a9 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -92,7 +92,6 @@ class AutoloadConfig */ protected $corePsr4 = [ 'CodeIgniter' => SYSTEMPATH, - 'App' => APPPATH, // To ensure filters, etc still found, 'Config' => APPPATH . 'Config', ]; From 092989f68be3d8978cc3579b88517f090fc3da9d Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 20:06:00 +0900 Subject: [PATCH 111/290] perf: use Composer autoloader first - When we use Composer, we use Composer autoloader first. So no need to load Composer classmap to CI4 autoloader. - Add `App` and `Config` namespaces to composer.json. So we can use composer dump-autoload for app classes. --- composer.json | 2 ++ system/Autoloader/Autoloader.php | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index aeac6801da1b..a3e8e3028c8c 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,8 @@ }, "autoload": { "psr-4": { + "App\\": "app/", + "Config\\": "app/Config/", "CodeIgniter\\": "system/" }, "exclude-from-classmap": [ diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index a06ffc2c6252..a7832a190e31 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -137,8 +137,6 @@ private function loadComposerInfo(Modules $modules): void */ $composer = include COMPOSER_PATH; - $this->loadComposerClassmap($composer); - // Should we load through Composer's namespaces, also? if ($modules->discoverInComposer) { // @phpstan-ignore-next-line @@ -155,11 +153,11 @@ private function loadComposerInfo(Modules $modules): void */ public function register() { - // Prepend the PSR4 autoloader for maximum performance. - spl_autoload_register([$this, 'loadClass'], true, true); + // Register classmap loader for the files in our class map. + spl_autoload_register([$this, 'loadClassmap'], true); - // Now prepend another loader for the files in our class map. - spl_autoload_register([$this, 'loadClassmap'], true, true); + // Register the PSR-4 autoloader. + spl_autoload_register([$this, 'loadClass'], true); // Load our non-class files foreach ($this->files as $file) { @@ -362,9 +360,13 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa { $namespacePaths = $composer->getPrefixesPsr4(); - // Get rid of CodeIgniter so we don't have duplicates - if (isset($namespacePaths['CodeIgniter\\'])) { - unset($namespacePaths['CodeIgniter\\']); + // Get rid of duplicated namespaces. + $duplicatedNamespaces = ['CodeIgniter', APP_NAMESPACE, 'Config']; + + foreach ($duplicatedNamespaces as $ns) { + if (isset($namespacePaths[$ns . '\\'])) { + unset($namespacePaths[$ns . '\\']); + } } if (! method_exists(InstalledVersions::class, 'getAllRawData')) { From 7ad5d96ca87536823c06e2636bf223970bd17546 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 20:21:13 +0900 Subject: [PATCH 112/290] refactor: remove unused method --- system/Autoloader/Autoloader.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index a7832a190e31..967543c3e9b3 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -430,13 +430,6 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa $this->addNamespace($newPaths); } - private function loadComposerClassmap(ClassLoader $composer): void - { - $classes = $composer->getClassMap(); - - $this->classmap = array_merge($this->classmap, $classes); - } - /** * Locates autoload information from Composer, if available. * From 90481ba500d2b855d755c6daca93c67dc36569d2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 20:21:54 +0900 Subject: [PATCH 113/290] chore: composer normalize --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a3e8e3028c8c..925f1678068e 100644 --- a/composer.json +++ b/composer.json @@ -59,8 +59,8 @@ "autoload": { "psr-4": { "App\\": "app/", - "Config\\": "app/Config/", - "CodeIgniter\\": "system/" + "CodeIgniter\\": "system/", + "Config\\": "app/Config/" }, "exclude-from-classmap": [ "**/Database/Migrations/**" From 6d6512f65c53926b9583d11e3c234421d2043c65 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 20:29:19 +0900 Subject: [PATCH 114/290] test: update assertion --- tests/system/Commands/Utilities/NamespacesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Commands/Utilities/NamespacesTest.php b/tests/system/Commands/Utilities/NamespacesTest.php index 755dd70f7cc6..d700dd666ec6 100644 --- a/tests/system/Commands/Utilities/NamespacesTest.php +++ b/tests/system/Commands/Utilities/NamespacesTest.php @@ -56,8 +56,8 @@ public function testNamespacesCommandCodeIgniterOnly(): void | Namespace | Path | Found? | +---------------+-------------------------+--------+ | CodeIgniter | ROOTPATH/system | Yes | - | App | ROOTPATH/app | Yes | | Config | APPPATH/Config | Yes | + | App | ROOTPATH/app | Yes | | Tests\Support | ROOTPATH/tests/_support | Yes | +---------------+-------------------------+--------+ EOL; From dbca19cace2dea4d2551053189cd9efbf9f199c6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 21:22:46 +0900 Subject: [PATCH 115/290] test: remove failed test for deprecated Config class 1) CodeIgniter\Config\ConfigTest::testCreateNonConfig ErrorException: Constant EVENT_PRIORITY_LOW already defined /home/runner/work/CodeIgniter4/CodeIgniter4/app/Config/Constants.php:84 /home/runner/work/CodeIgniter4/CodeIgniter4/system/Config/Factories.php:271 /home/runner/work/CodeIgniter4/CodeIgniter4/system/Config/Factories.php:148 /home/runner/work/CodeIgniter4/CodeIgniter4/system/Config/Config.php:31 /home/runner/work/CodeIgniter4/CodeIgniter4/tests/system/Config/ConfigTest.php:51 --- tests/system/Config/ConfigTest.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/system/Config/ConfigTest.php b/tests/system/Config/ConfigTest.php index 6bd4103a7aa2..b00bc2b9f699 100644 --- a/tests/system/Config/ConfigTest.php +++ b/tests/system/Config/ConfigTest.php @@ -46,13 +46,6 @@ public function testCreateSharedInstance(): void $this->assertSame($Config2, $Config); } - public function testCreateNonConfig(): void - { - $Config = Config::get('Constants', false); - - $this->assertNull($Config); - } - /** * @runInSeparateProcess * @preserveGlobalState disabled From 6bba15ade30615909dc26a319e53e7a16b5c9744 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 5 Oct 2023 08:17:18 +0900 Subject: [PATCH 116/290] chore: remove `App` from composer.json It may be customized. --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 925f1678068e..f72ecc7f548c 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,6 @@ }, "autoload": { "psr-4": { - "App\\": "app/", "CodeIgniter\\": "system/", "Config\\": "app/Config/" }, From 5aecfd1a80a81427a45dfe3c20a1b40614df3853 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 5 Oct 2023 08:33:34 +0900 Subject: [PATCH 117/290] docs: update comments in Config/Autoload.php --- app/Config/Autoload.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 34d234631949..85e0d9919d2b 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -30,22 +30,18 @@ class Autoload extends AutoloadConfig * their location on the file system. These are used by the autoloader * to locate files the first time they have been instantiated. * - * The '/app' and '/system' directories are already mapped for you. - * you may change the name of the 'App' namespace if you wish, + * The 'Config' (APPPATH . 'Config') and 'CodeIgniter' (SYSTEMPATH) are + * already mapped for you. + * + * You may change the name of the 'App' namespace if you wish, * but this should be done prior to creating any namespaced classes, * else you will need to modify all of those classes for this to work. * - * Prototype: - * $psr4 = [ - * 'CodeIgniter' => SYSTEMPATH, - * 'App' => APPPATH - * ]; - * * @var array|string> * @phpstan-var array> */ public $psr4 = [ - APP_NAMESPACE => APPPATH, // For custom app namespace + APP_NAMESPACE => APPPATH, ]; /** From 13282524b207568c7af51753b4f1ed704780cf7e Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 5 Oct 2023 08:42:33 +0900 Subject: [PATCH 118/290] docs: update description --- user_guide_src/source/concepts/autoloader.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/concepts/autoloader.rst b/user_guide_src/source/concepts/autoloader.rst index 22722716661b..5fdf4e89bfee 100644 --- a/user_guide_src/source/concepts/autoloader.rst +++ b/user_guide_src/source/concepts/autoloader.rst @@ -90,9 +90,14 @@ The key of each row is the name of the class that you want to locate. The value Composer Support **************** -Composer support is automatically initialized by default. By default, it looks for Composer's autoload file at +Composer support is automatically initialized by default. + +By default, it looks for Composer's autoload file at ``ROOTPATH . 'vendor/autoload.php'``. If you need to change the location of that file for any reason, you can modify the value defined in **app/Config/Constants.php**. -.. note:: If the same namespace is defined in both CodeIgniter and Composer, CodeIgniter's autoloader will be +If the same namespace is defined in both CodeIgniter and Composer, Composer's +autoloader will be the first one to get a chance to locate the file. + +.. note:: Prior to v4.5.0, if the same namespace was defined in both CodeIgniter and Composer, CodeIgniter's autoloader was the first one to get a chance to locate the file. From be9204e34d30c292578a74cbb295135b3784462c Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 5 Oct 2023 08:43:36 +0900 Subject: [PATCH 119/290] docs: add *** for headings --- user_guide_src/source/concepts/autoloader.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/user_guide_src/source/concepts/autoloader.rst b/user_guide_src/source/concepts/autoloader.rst index 5fdf4e89bfee..ff522732669c 100644 --- a/user_guide_src/source/concepts/autoloader.rst +++ b/user_guide_src/source/concepts/autoloader.rst @@ -13,6 +13,7 @@ classes that your project is using. Keeping track of where every single file is, hard-coding that location into your files in a series of ``requires()`` is a massive headache and very error-prone. That's where autoloaders come in. +*********************** CodeIgniter4 Autoloader *********************** @@ -36,12 +37,14 @@ beginning of the framework's execution. file name case is incorrect, the autoloader cannot find the file on the server. +************* Configuration ************* Initial configuration is done in **app/Config/Autoload.php**. This file contains two primary arrays: one for the classmap, and one for PSR-4 compatible namespaces. +********** Namespaces ********** @@ -76,6 +79,7 @@ You will need to modify any existing files that are referencing the current name expect. This allows the core system files to always be able to locate them, even when the application namespace has changed. +******** Classmap ******** @@ -87,6 +91,7 @@ third-party libraries that are not namespaced: The key of each row is the name of the class that you want to locate. The value is the path to locate it at. +**************** Composer Support **************** From e5d7d3f38ed2a9dc79fe34ecc30d0344e03fad3b Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 5 Oct 2023 08:45:30 +0900 Subject: [PATCH 120/290] docs: update sample code --- user_guide_src/source/concepts/autoloader/001.php | 3 +-- user_guide_src/source/dbmgmt/migration.rst | 1 + user_guide_src/source/dbmgmt/migration/004.php | 4 ++-- user_guide_src/source/general/modules/001.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/user_guide_src/source/concepts/autoloader/001.php b/user_guide_src/source/concepts/autoloader/001.php index 68eeaddd625f..d6da1bce5389 100644 --- a/user_guide_src/source/concepts/autoloader/001.php +++ b/user_guide_src/source/concepts/autoloader/001.php @@ -8,8 +8,7 @@ class Autoload extends AutoloadConfig { // ... public $psr4 = [ - APP_NAMESPACE => APPPATH, // For custom app namespace - 'Config' => APPPATH . 'Config', + APP_NAMESPACE => APPPATH, ]; // ... diff --git a/user_guide_src/source/dbmgmt/migration.rst b/user_guide_src/source/dbmgmt/migration.rst index 4af207164b73..3f7a17e71024 100644 --- a/user_guide_src/source/dbmgmt/migration.rst +++ b/user_guide_src/source/dbmgmt/migration.rst @@ -93,6 +93,7 @@ For example, assume that we have the following namespaces defined in our Autoloa configuration file: .. literalinclude:: migration/004.php + :lines: 2- This will look for any migrations located at both **APPPATH/Database/Migrations** and **ROOTPATH/MyCompany/Database/Migrations**. This makes it simple to include migrations in your diff --git a/user_guide_src/source/dbmgmt/migration/004.php b/user_guide_src/source/dbmgmt/migration/004.php index 1e4c831c8219..24c684088a4b 100644 --- a/user_guide_src/source/dbmgmt/migration/004.php +++ b/user_guide_src/source/dbmgmt/migration/004.php @@ -1,6 +1,6 @@ APPPATH, - 'MyCompany' => ROOTPATH . 'MyCompany', + APP_NAMESPACE => APPPATH, + 'MyCompany' => ROOTPATH . 'MyCompany', ]; diff --git a/user_guide_src/source/general/modules/001.php b/user_guide_src/source/general/modules/001.php index 1f93e213359f..dcc1fbe3a9e4 100644 --- a/user_guide_src/source/general/modules/001.php +++ b/user_guide_src/source/general/modules/001.php @@ -6,9 +6,9 @@ class Autoload extends AutoloadConfig { + // ... public $psr4 = [ - APP_NAMESPACE => APPPATH, // For custom namespace - 'Config' => APPPATH . 'Config', + APP_NAMESPACE => APPPATH, 'Acme\Blog' => ROOTPATH . 'acme/Blog', ]; From 1ac4ff67fbb7eb384cbd44782c0d2c67d63630c5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 5 Oct 2023 09:07:07 +0900 Subject: [PATCH 121/290] docs: add "Improve Performance" --- user_guide_src/source/concepts/autoloader.rst | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/user_guide_src/source/concepts/autoloader.rst b/user_guide_src/source/concepts/autoloader.rst index ff522732669c..eaec9eaf8693 100644 --- a/user_guide_src/source/concepts/autoloader.rst +++ b/user_guide_src/source/concepts/autoloader.rst @@ -101,8 +101,36 @@ By default, it looks for Composer's autoload file at ``ROOTPATH . 'vendor/autoload.php'``. If you need to change the location of that file for any reason, you can modify the value defined in **app/Config/Constants.php**. +Priority of Autoloaders +======================= + If the same namespace is defined in both CodeIgniter and Composer, Composer's autoloader will be the first one to get a chance to locate the file. .. note:: Prior to v4.5.0, if the same namespace was defined in both CodeIgniter and Composer, CodeIgniter's autoloader was the first one to get a chance to locate the file. + +.. _autoloader-composer-support-improve-performance: + +Improve Performance +=================== + +.. versionadded:: 4.5.0 + +When you use Composer, you could improve the performance of autoloading with +Composer's classmap dump. + +Add your ``App`` namespace in your **composer.json**, and run ``composer dump-autoload``. + +.. code-block:: text + + { + ... + "autoload": { + "psr-4": { + "App\\": "app/", + }, + ... + }, + ... + } From 1199b4c4dbb3c8a83dad1ccb59dace31af7c815b Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 5 Oct 2023 09:26:45 +0900 Subject: [PATCH 122/290] docs: add changelog --- user_guide_src/source/changelogs/v4.5.0.rst | 4 ++++ user_guide_src/source/concepts/autoloader.rst | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index 3f509d73431f..282ec793767d 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -116,6 +116,10 @@ Helpers and Functions Others ====== +- **Autoloader:** Autoloading performance when using Composer has been improved; + changing the ``autoload.psr4`` setting in **composer.json** may also improve + the performance of your app. See :ref:`autoloader-composer-support-improve-performance`. + Message Changes *************** diff --git a/user_guide_src/source/concepts/autoloader.rst b/user_guide_src/source/concepts/autoloader.rst index eaec9eaf8693..993f72ab3ddc 100644 --- a/user_guide_src/source/concepts/autoloader.rst +++ b/user_guide_src/source/concepts/autoloader.rst @@ -117,7 +117,7 @@ Improve Performance .. versionadded:: 4.5.0 -When you use Composer, you could improve the performance of autoloading with +When you use Composer, you may improve the performance of autoloading with Composer's classmap dump. Add your ``App`` namespace in your **composer.json**, and run ``composer dump-autoload``. From b52f770e70990f3883d6b67fcbae7f4e9013eccc Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 06:11:33 +0900 Subject: [PATCH 123/290] chore: add App namespace in autoload.psr-4 in appstarter composer.json --- admin/starter/composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/admin/starter/composer.json b/admin/starter/composer.json index 37c3e5642f29..35b791c3f050 100644 --- a/admin/starter/composer.json +++ b/admin/starter/composer.json @@ -19,6 +19,9 @@ "phpunit/phpunit": "^9.1" }, "autoload": { + "psr-4": { + "App\\": "app/" + }, "exclude-from-classmap": [ "**/Database/Migrations/**" ] From 9a9a5edaf558b04e5387c09f54cfcebef6b482ff Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 06:12:20 +0900 Subject: [PATCH 124/290] docs: update description for app namespace --- user_guide_src/source/changelogs/v4.5.0.rst | 6 +- user_guide_src/source/concepts/autoloader.rst | 64 ++++++++++--------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index 282ec793767d..f109180c8009 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -116,9 +116,9 @@ Helpers and Functions Others ====== -- **Autoloader:** Autoloading performance when using Composer has been improved; - changing the ``autoload.psr4`` setting in **composer.json** may also improve - the performance of your app. See :ref:`autoloader-composer-support-improve-performance`. +- **Autoloader:** Autoloading performance when using Composer has been improved. + Adding the ``App`` namespace in the ``autoload.psr4`` setting in **composer.json** + may also improve the performance of your app. See :ref:`autoloader-application-namespace`. Message Changes *************** diff --git a/user_guide_src/source/concepts/autoloader.rst b/user_guide_src/source/concepts/autoloader.rst index 993f72ab3ddc..cc47162e5c0c 100644 --- a/user_guide_src/source/concepts/autoloader.rst +++ b/user_guide_src/source/concepts/autoloader.rst @@ -64,20 +64,51 @@ The value is the location to the directory the classes can be found in. php spark namespaces +.. _autoloader-application-namespace: + +Application Namespace +===================== + By default, the application directory is namespace to the ``App`` namespace. You must namespace the controllers, libraries, or models in the application directory, and they will be found under the ``App`` namespace. +Config Namespace +---------------- + +Config files are namespaced in the ``Config`` namespace, not in ``App\Config`` as you might +expect. This allows the core system files to always be able to locate them, even when the application +namespace has changed. + +Changing App Namespace +---------------------- + You may change this namespace by editing the **app/Config/Constants.php** file and setting the new namespace value under the ``APP_NAMESPACE`` setting: .. literalinclude:: autoloader/002.php :lines: 2- -You will need to modify any existing files that are referencing the current namespace. +And if you use Composer autoloader, you also need to change the ``App`` namespace +in your **composer.json**, and run ``composer dump-autoload``. -.. important:: Config files are namespaced in the ``Config`` namespace, not in ``App\Config`` as you might - expect. This allows the core system files to always be able to locate them, even when the application - namespace has changed. +.. code-block:: text + + { + ... + "autoload": { + "psr-4": { + "App\\": "app/" <-- Change + }, + ... + }, + ... + } + +.. note:: Since v4.5.0 appstarter, the ``App\\`` namespace has been added to + **composer.json**'s ``autoload.psr-4``. If your **composer.json** does not + have it, adding it may improve your app's autoloading performance. + +You will need to modify any existing files that are referencing the current namespace. ******** Classmap @@ -109,28 +140,3 @@ autoloader will be the first one to get a chance to locate the file. .. note:: Prior to v4.5.0, if the same namespace was defined in both CodeIgniter and Composer, CodeIgniter's autoloader was the first one to get a chance to locate the file. - -.. _autoloader-composer-support-improve-performance: - -Improve Performance -=================== - -.. versionadded:: 4.5.0 - -When you use Composer, you may improve the performance of autoloading with -Composer's classmap dump. - -Add your ``App`` namespace in your **composer.json**, and run ``composer dump-autoload``. - -.. code-block:: text - - { - ... - "autoload": { - "psr-4": { - "App\\": "app/", - }, - ... - }, - ... - } From 43e035864bbf1d4e6c76b6ed9086e2517711b2f1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 10:30:08 +0900 Subject: [PATCH 125/290] =?UTF-8?q?fix:=20findQualifiedNameFromPath()=20re?= =?UTF-8?q?turns=20FQCN=20with=20leading=20=E2=80=9C\"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FQCN should not start with “\". ::class returns FQCN without leading "\". --- system/Autoloader/FileLocator.php | 2 +- tests/system/Autoloader/FileLocatorTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index bc18bd31b980..71be43b5aa8c 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -273,7 +273,7 @@ public function findQualifiedNameFromPath(string $path) } if (mb_strpos($path, $namespace['path']) === 0) { - $className = '\\' . $namespace['prefix'] . '\\' . + $className = $namespace['prefix'] . '\\' . ltrim(str_replace( '/', '\\', diff --git a/tests/system/Autoloader/FileLocatorTest.php b/tests/system/Autoloader/FileLocatorTest.php index 1cf32cfa5ad1..0d084280f520 100644 --- a/tests/system/Autoloader/FileLocatorTest.php +++ b/tests/system/Autoloader/FileLocatorTest.php @@ -278,7 +278,7 @@ public function testListFilesWithoutPath(): void public function testFindQNameFromPathSimple(): void { $ClassName = $this->locator->findQualifiedNameFromPath(SYSTEMPATH . 'HTTP/Header.php'); - $expected = '\\' . Header::class; + $expected = Header::class; $this->assertSame($expected, $ClassName); } From f51c542f7f7d77ab175c5d0d9ed11487cf39c8da Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 10:33:36 +0900 Subject: [PATCH 126/290] style: break long line --- system/Autoloader/FileLocator.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 71be43b5aa8c..6f241e847be3 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -274,11 +274,14 @@ public function findQualifiedNameFromPath(string $path) if (mb_strpos($path, $namespace['path']) === 0) { $className = $namespace['prefix'] . '\\' . - ltrim(str_replace( + ltrim( + str_replace( '/', '\\', mb_substr($path, mb_strlen($namespace['path'])) - ), '\\'); + ), + '\\' + ); // Remove the file extension (.php) $className = mb_substr($className, 0, -4); From 4e6b1bbfe1335a7aab506ed7e8756d24e5c9a0fa Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 10:50:23 +0900 Subject: [PATCH 127/290] docs: add changelog and upgrade_450 --- user_guide_src/source/changelogs/v4.5.0.rst | 2 ++ user_guide_src/source/installation/upgrade_450.rst | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/user_guide_src/source/changelogs/v4.5.0.rst b/user_guide_src/source/changelogs/v4.5.0.rst index 3f509d73431f..320c961bb8e1 100644 --- a/user_guide_src/source/changelogs/v4.5.0.rst +++ b/user_guide_src/source/changelogs/v4.5.0.rst @@ -33,6 +33,8 @@ Others - **Logger:** The :php:func:`log_message()` function and the logger methods in ``CodeIgniter\Log\Logger`` now do not return ``bool`` values. The return types have been fixed to ``void`` to follow the PSR-3 interface. +- **Autoloader:** The prefix ``\`` in the fully qualified classname returned by + ``FileLocator::findQualifiedNameFromPath()`` has been removed. Interface Changes ================= diff --git a/user_guide_src/source/installation/upgrade_450.rst b/user_guide_src/source/installation/upgrade_450.rst index 7a4fbc0b29da..8b91b67ae77e 100644 --- a/user_guide_src/source/installation/upgrade_450.rst +++ b/user_guide_src/source/installation/upgrade_450.rst @@ -72,6 +72,14 @@ reversed. Previous: route1 → route2 → filter1 → filter2 Now: route2 → route1 → filter2 → filter1 +FileLocator::findQualifiedNameFromPath() +======================================== + +In previous versions, ``FileLocator::findQualifiedNameFromPath()`` returns Fully +Qualified Classnames with a leading ``\``. Now the leading ``\`` has been removed. + +If you have code that expects a leading ``\``, fix it. + Removed Deprecated Items ======================== From 0529467402781af1b15797facd952dc45413ba8d Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 09:32:21 +0900 Subject: [PATCH 128/290] refactor: move logic for "Update the performance metrics" --- system/CodeIgniter.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index d854dac5ec11..48cb8595aad7 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -535,13 +535,6 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // Must be run after filters to preserve the Response headers. $this->pageCache->make($this->request, $this->response); - // Update the performance metrics - $body = $this->response->getBody(); - if ($body !== null) { - $output = $this->displayPerformanceMetrics($body); - $this->response->setBody($output); - } - // Save our current URI as the previous URI in the session // for safer, more accurate use with `previous_url()` helper function. $this->storePreviousURL(current_url(true)); @@ -1098,6 +1091,13 @@ public function spoofRequestMethod() */ protected function sendResponse() { + // Update the performance metrics + $body = $this->response->getBody(); + if ($body !== null) { + $output = $this->displayPerformanceMetrics($body); + $this->response->setBody($output); + } + $this->response->send(); } From 12e724518692cc08f270a9b636a4610c8727b3ea Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 09:39:49 +0900 Subject: [PATCH 129/290] feat: implement {memory_usage} replacement It was in CI3. https://codeigniter.com/userguide3/libraries/benchmark.html#displaying-memory-consumption --- system/CodeIgniter.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 48cb8595aad7..daa8f26c47ba 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -773,11 +773,15 @@ protected function generateCacheName(Cache $config): string } /** - * Replaces the elapsed_time tag. + * Replaces the elapsed_time and memory_usage tag. */ public function displayPerformanceMetrics(string $output): string { - return str_replace('{elapsed_time}', (string) $this->totalTime, $output); + return str_replace( + ['{elapsed_time}', '{memory_usage}'], + [(string) $this->totalTime, number_format(memory_get_peak_usage() / 1024 / 1024, 3)], + $output + ); } /** From b4a1e524cd0ca44226d75a3e27685ae6bd92756e Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 6 Oct 2023 09:41:17 +0900 Subject: [PATCH 130/290] feat: show memory usage in Welcome page --- app/Views/welcome_message.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Views/welcome_message.php b/app/Views/welcome_message.php index 6d5da23f1224..396f7f44d3fe 100644 --- a/app/Views/welcome_message.php +++ b/app/Views/welcome_message.php @@ -292,7 +292,7 @@