From edd88b18483e309bab1411651d846aace255ab36 Mon Sep 17 00:00:00 2001 From: lonnieezell Date: Fri, 1 May 2020 05:01:20 +0000 Subject: [PATCH] Release v4.0.3 --- LICENSE | 21 -- README.md | 2 - app/Config/Kint.php | 2 +- app/Config/Logger.php | 17 +- app/Config/Mimes.php | 6 +- app/Config/Routes.php | 2 +- app/Config/Services.php | 1 - app/Views/errors/html/error_exception.php | 10 +- composer.json | 1 + contributing.md | 94 ------ public/index.php | 2 +- system/API/ResponseTrait.php | 48 ++- system/Autoloader/Autoloader.php | 2 +- system/Autoloader/FileLocator.php | 6 +- system/CLI/CLI.php | 21 +- system/CLI/CommandRunner.php | 4 +- system/Cache/Handlers/FileHandler.php | 2 +- system/CodeIgniter.php | 34 +- system/Commands/Database/CreateMigration.php | 12 +- system/Commands/Database/MigrateRefresh.php | 17 +- system/Commands/Database/MigrateRollback.php | 11 +- system/Commands/Database/MigrateStatus.php | 27 +- system/Commands/ListCommands.php | 3 +- system/Commands/Server/Serve.php | 11 +- system/Commands/Sessions/CreateMigration.php | 2 +- system/Common.php | 64 ++-- system/ComposerScripts.php | 4 + system/Config/BaseConfig.php | 4 - system/Config/DotEnv.php | 4 +- system/Config/Services.php | 22 +- system/Controller.php | 11 +- system/Database/BaseBuilder.php | 110 +++++-- system/Database/BaseConnection.php | 8 +- system/Database/BaseResult.php | 16 +- system/Database/BaseUtils.php | 45 +-- system/Database/Database.php | 8 +- system/Database/Forge.php | 8 +- system/Database/MigrationRunner.php | 11 +- system/Database/ModelFactory.php | 8 - system/Database/MySQLi/Connection.php | 17 +- system/Database/MySQLi/PreparedQuery.php | 6 +- system/Database/Postgre/Builder.php | 7 +- system/Database/Postgre/Connection.php | 16 +- system/Database/Postgre/PreparedQuery.php | 6 +- system/Database/Query.php | 6 +- system/Database/SQLite3/Connection.php | 22 +- system/Database/SQLite3/Forge.php | 2 - system/Database/SQLite3/PreparedQuery.php | 2 +- system/Database/Seeder.php | 2 + system/Debug/Exceptions.php | 28 +- system/Debug/Toolbar.php | 6 +- system/Debug/Toolbar/Collectors/Config.php | 2 +- system/Debug/Toolbar/Collectors/Events.php | 4 +- system/Debug/Toolbar/Collectors/Views.php | 4 +- .../Debug/Toolbar/Views/toolbarloader.js.php | 8 +- system/Encryption/Encryption.php | 4 +- system/Encryption/Handlers/OpenSSLHandler.php | 11 +- system/Entity.php | 28 +- system/Exceptions/CastException.php | 17 +- system/Exceptions/ConfigException.php | 2 +- system/Files/File.php | 2 +- system/Filters/Filters.php | 70 ++-- system/Filters/Honeypot.php | 2 +- system/Format/JSONFormatter.php | 4 +- system/HTTP/CURLRequest.php | 4 +- system/HTTP/ContentSecurityPolicy.php | 2 +- system/HTTP/IncomingRequest.php | 7 +- system/HTTP/Message.php | 4 +- system/HTTP/Negotiate.php | 54 +++- system/HTTP/Response.php | 12 +- system/HTTP/URI.php | 33 +- system/HTTP/UserAgent.php | 2 +- system/Helpers/array_helper.php | 4 +- system/Helpers/cookie_helper.php | 3 +- system/Helpers/date_helper.php | 3 +- system/Helpers/filesystem_helper.php | 51 +-- system/Helpers/form_helper.php | 24 +- system/Helpers/html_helper.php | 20 +- system/Helpers/inflector_helper.php | 10 +- system/Helpers/number_helper.php | 19 +- system/Helpers/text_helper.php | 4 +- system/Helpers/url_helper.php | 7 +- system/Honeypot/Honeypot.php | 2 +- system/I18n/Time.php | 8 +- system/Images/Handlers/BaseHandler.php | 11 +- system/Images/Handlers/GDHandler.php | 3 +- system/Language/Language.php | 14 +- system/Language/en/Migrations.php | 4 +- system/Language/en/Validation.php | 1 + system/Log/Handlers/ChromeLoggerHandler.php | 6 +- system/Log/Handlers/FileHandler.php | 2 +- system/Log/Logger.php | 62 ++-- system/Model.php | 81 +++-- system/Pager/Pager.php | 37 ++- system/Pager/PagerRenderer.php | 74 +++++ system/Pager/Views/default_full.php | 5 +- system/RESTful/ResourceController.php | 10 +- system/RESTful/ResourcePresenter.php | 5 +- system/Router/RouteCollection.php | 34 +- system/Router/Router.php | 41 ++- system/Session/Handlers/ArrayHandler.php | 3 - system/Session/Handlers/DatabaseHandler.php | 2 +- system/Session/Session.php | 10 +- system/Test/CIDatabaseTestCase.php | 43 +-- system/Test/CIUnitTestCase.php | 3 +- system/Test/ControllerTester.php | 5 +- system/Test/DOMParser.php | 4 +- system/Test/FeatureTestCase.php | 30 +- system/Test/Mock/MockChromeLogger.php | 25 -- system/Test/Mock/MockConnection.php | 1 - system/Test/Mock/MockEmail.php | 24 ++ system/Test/Mock/MockLogger.php | 15 +- system/Test/Mock/MockSecurity.php | 2 +- system/Test/ReflectionHelper.php | 2 +- system/Test/bootstrap.php | 20 +- system/ThirdParty/Kint/{kint.php => Kint.php} | 0 system/Validation/CreditCardRules.php | 3 +- system/Validation/FileRules.php | 18 +- system/Validation/FormatRules.php | 11 +- system/Validation/Rules.php | 35 +- system/Validation/Validation.php | 90 +++++- system/View/Cell.php | 6 +- system/View/Filters.php | 7 +- system/View/Parser.php | 75 +++-- system/View/Plugins.php | 8 +- system/View/Table.php | 15 +- system/View/View.php | 48 ++- system/bootstrap.php | 10 + .../_support/Autoloader/UnnamespacedClass.php | 5 - tests/_support/CIDatabaseTestCase.php | 8 - tests/_support/CIUnitTestCase.php | 8 - tests/_support/Cache/Handlers/MockHandler.php | 198 ------------ tests/_support/Commands/AbstractInfo.php | 13 - tests/_support/Commands/AppInfo.php | 36 --- tests/_support/Config/BadRegistrar.php | 18 -- tests/_support/Config/MockAppConfig.php | 34 -- tests/_support/Config/MockAutoload.php | 21 -- tests/_support/Config/MockCLIConfig.php | 32 -- tests/_support/Config/MockLogger.php | 103 ------ tests/_support/Config/MockServices.php | 28 -- tests/_support/Config/Registrar.php | 27 -- tests/_support/Config/Routes.php | 7 - tests/_support/Controllers/Popcorn.php | 77 ----- .../20160428212500_Create_test_tables.php | 148 --------- tests/_support/Database/MockBuilder.php | 15 - tests/_support/Database/MockConnection.php | 298 ------------------ tests/_support/Database/MockQuery.php | 8 - tests/_support/Database/MockResult.php | 93 ------ tests/_support/Database/MockTestClass.php | 7 - .../_support/Database/Seeds/AnotherSeeder.php | 15 - .../_support/Database/Seeds/CITestSeeder.php | 78 ----- .../SupportMigrations/001_Some_migration.php | 24 -- .../20160428212500_Create_test_tables.php | 148 --------- tests/_support/Events/MockEvents.php | 66 ---- tests/_support/Files/able/apple.php | 6 - tests/_support/Files/able/fig_3.php | 6 - tests/_support/Files/able/prune_ripe.php | 6 - tests/_support/Files/baker/banana.php | 6 - tests/_support/HTTP/Files/CookiesHolder.txt | 1 - tests/_support/HTTP/Files/tmp/fileA.txt | 1 - tests/_support/HTTP/Files/tmp/fileB.txt | 1 - tests/_support/HTTP/MockCURLRequest.php | 52 --- tests/_support/HTTP/MockIncomingRequest.php | 17 - tests/_support/HTTP/MockResponse.php | 31 -- .../Images/EXIFsamples/down-mirrored.jpg | Bin 15437 -> 0 bytes tests/_support/Images/EXIFsamples/down.jpg | Bin 7004 -> 0 bytes .../Images/EXIFsamples/left-mirrored.jpg | Bin 13042 -> 0 bytes tests/_support/Images/EXIFsamples/left.jpg | Bin 3874 -> 0 bytes .../Images/EXIFsamples/right-mirrored.jpg | Bin 15058 -> 0 bytes tests/_support/Images/EXIFsamples/right.jpg | Bin 5990 -> 0 bytes .../Images/EXIFsamples/up-mirrored.jpg | Bin 11987 -> 0 bytes tests/_support/Images/EXIFsamples/up.jpg | Bin 3266 -> 0 bytes tests/_support/Images/Steveston_dusk.JPG | Bin 121762 -> 0 bytes tests/_support/Images/ci-logo.gif | Bin 1844 -> 0 bytes tests/_support/Images/ci-logo.jpeg | Bin 7534 -> 0 bytes tests/_support/Images/ci-logo.png | Bin 4537 -> 0 bytes tests/_support/Language/MockLanguage.php | 61 ---- .../_support/Language/SecondMockLanguage.php | 27 -- tests/_support/Language/ab-CD/Allin.php | 8 - tests/_support/Language/ab/Allin.php | 8 - tests/_support/Language/en-ZZ/More.php | 6 - tests/_support/Language/en/Allin.php | 8 - tests/_support/Language/en/Core.php | 20 -- tests/_support/Language/en/More.php | 7 - tests/_support/Language/ru/Language.php | 5 - .../Log/Handlers/MockChromeHandler.php | 25 -- .../_support/Log/Handlers/MockFileHandler.php | 25 -- tests/_support/Log/Handlers/TestHandler.php | 64 ---- tests/_support/Log/TestLogger.php | 82 ----- .../2018-01-24-102300_Another_migration.py | 24 -- .../2018-01-24-102301_Some_migration.php | 24 -- .../2018-01-24-102302_Another_migration.php | 28 -- tests/_support/MockCodeIgniter.php | 11 - tests/_support/MockCommon.php | 37 --- tests/_support/Models/EntityModel.php | 22 -- tests/_support/Models/EventModel.php | 79 ----- tests/_support/Models/JobModel.php | 23 -- tests/_support/Models/SecondaryModel.php | 21 -- tests/_support/Models/SimpleEntity.php | 16 - tests/_support/Models/UserModel.php | 27 -- tests/_support/Models/ValidErrorsModel.php | 30 -- tests/_support/Models/ValidModel.php | 34 -- .../RESTful/MockResourceController.php | 24 -- .../RESTful/MockResourcePresenter.php | 24 -- tests/_support/RESTful/Worker.php | 11 - tests/_support/RESTful/Worker2.php | 12 - tests/_support/Security/MockSecurity.php | 17 - tests/_support/Services.php | 70 ---- tests/_support/Session/MockSession.php | 72 ----- tests/_support/SomeEntity.php | 14 - tests/_support/Validation/TestRules.php | 14 - tests/_support/Validation/uploads/phpUxc0ty | Bin 4614 -> 0 bytes tests/_support/View/MockTable.php | 17 - tests/_support/View/SampleClass.php | 52 --- tests/_support/View/Views/simple.php | 1 - tests/_support/View/Views/simpler.php | 1 - tests/_support/_bootstrap.php | 32 -- tests/_support/coverage.txt | 70 ---- 218 files changed, 1219 insertions(+), 3713 deletions(-) delete mode 100644 LICENSE delete mode 100644 contributing.md delete mode 100644 system/Test/Mock/MockChromeLogger.php create mode 100644 system/Test/Mock/MockEmail.php rename system/ThirdParty/Kint/{kint.php => Kint.php} (100%) delete mode 100644 tests/_support/Autoloader/UnnamespacedClass.php delete mode 100644 tests/_support/CIDatabaseTestCase.php delete mode 100644 tests/_support/CIUnitTestCase.php delete mode 100644 tests/_support/Cache/Handlers/MockHandler.php delete mode 100644 tests/_support/Commands/AbstractInfo.php delete mode 100644 tests/_support/Commands/AppInfo.php delete mode 100644 tests/_support/Config/BadRegistrar.php delete mode 100644 tests/_support/Config/MockAppConfig.php delete mode 100644 tests/_support/Config/MockAutoload.php delete mode 100644 tests/_support/Config/MockCLIConfig.php delete mode 100644 tests/_support/Config/MockLogger.php delete mode 100644 tests/_support/Config/MockServices.php delete mode 100644 tests/_support/Config/Registrar.php delete mode 100644 tests/_support/Config/Routes.php delete mode 100644 tests/_support/Controllers/Popcorn.php delete mode 100644 tests/_support/Database/Migrations/20160428212500_Create_test_tables.php delete mode 100644 tests/_support/Database/MockBuilder.php delete mode 100644 tests/_support/Database/MockConnection.php delete mode 100644 tests/_support/Database/MockQuery.php delete mode 100644 tests/_support/Database/MockResult.php delete mode 100644 tests/_support/Database/MockTestClass.php delete mode 100644 tests/_support/Database/Seeds/AnotherSeeder.php delete mode 100644 tests/_support/Database/Seeds/CITestSeeder.php delete mode 100644 tests/_support/Database/SupportMigrations/001_Some_migration.php delete mode 100644 tests/_support/DatabaseTestMigrations/Database/Migrations/20160428212500_Create_test_tables.php delete mode 100644 tests/_support/Events/MockEvents.php delete mode 100644 tests/_support/Files/able/apple.php delete mode 100644 tests/_support/Files/able/fig_3.php delete mode 100644 tests/_support/Files/able/prune_ripe.php delete mode 100644 tests/_support/Files/baker/banana.php delete mode 100644 tests/_support/HTTP/Files/CookiesHolder.txt delete mode 100644 tests/_support/HTTP/Files/tmp/fileA.txt delete mode 100644 tests/_support/HTTP/Files/tmp/fileB.txt delete mode 100644 tests/_support/HTTP/MockCURLRequest.php delete mode 100644 tests/_support/HTTP/MockIncomingRequest.php delete mode 100755 tests/_support/HTTP/MockResponse.php delete mode 100644 tests/_support/Images/EXIFsamples/down-mirrored.jpg delete mode 100644 tests/_support/Images/EXIFsamples/down.jpg delete mode 100644 tests/_support/Images/EXIFsamples/left-mirrored.jpg delete mode 100644 tests/_support/Images/EXIFsamples/left.jpg delete mode 100644 tests/_support/Images/EXIFsamples/right-mirrored.jpg delete mode 100644 tests/_support/Images/EXIFsamples/right.jpg delete mode 100644 tests/_support/Images/EXIFsamples/up-mirrored.jpg delete mode 100644 tests/_support/Images/EXIFsamples/up.jpg delete mode 100644 tests/_support/Images/Steveston_dusk.JPG delete mode 100644 tests/_support/Images/ci-logo.gif delete mode 100644 tests/_support/Images/ci-logo.jpeg delete mode 100644 tests/_support/Images/ci-logo.png delete mode 100644 tests/_support/Language/MockLanguage.php delete mode 100644 tests/_support/Language/SecondMockLanguage.php delete mode 100644 tests/_support/Language/ab-CD/Allin.php delete mode 100644 tests/_support/Language/ab/Allin.php delete mode 100644 tests/_support/Language/en-ZZ/More.php delete mode 100644 tests/_support/Language/en/Allin.php delete mode 100644 tests/_support/Language/en/Core.php delete mode 100644 tests/_support/Language/en/More.php delete mode 100644 tests/_support/Language/ru/Language.php delete mode 100644 tests/_support/Log/Handlers/MockChromeHandler.php delete mode 100644 tests/_support/Log/Handlers/MockFileHandler.php delete mode 100644 tests/_support/Log/Handlers/TestHandler.php delete mode 100644 tests/_support/Log/TestLogger.php delete mode 100644 tests/_support/MigrationTestMigrations/Database/Migrations/2018-01-24-102300_Another_migration.py delete mode 100644 tests/_support/MigrationTestMigrations/Database/Migrations/2018-01-24-102301_Some_migration.php delete mode 100644 tests/_support/MigrationTestMigrations/Database/Migrations/2018-01-24-102302_Another_migration.php delete mode 100644 tests/_support/MockCodeIgniter.php delete mode 100644 tests/_support/MockCommon.php delete mode 100644 tests/_support/Models/EntityModel.php delete mode 100644 tests/_support/Models/EventModel.php delete mode 100644 tests/_support/Models/JobModel.php delete mode 100644 tests/_support/Models/SecondaryModel.php delete mode 100644 tests/_support/Models/SimpleEntity.php delete mode 100644 tests/_support/Models/UserModel.php delete mode 100644 tests/_support/Models/ValidErrorsModel.php delete mode 100644 tests/_support/Models/ValidModel.php delete mode 100644 tests/_support/RESTful/MockResourceController.php delete mode 100644 tests/_support/RESTful/MockResourcePresenter.php delete mode 100644 tests/_support/RESTful/Worker.php delete mode 100644 tests/_support/RESTful/Worker2.php delete mode 100644 tests/_support/Security/MockSecurity.php delete mode 100644 tests/_support/Services.php delete mode 100644 tests/_support/Session/MockSession.php delete mode 100644 tests/_support/SomeEntity.php delete mode 100644 tests/_support/Validation/TestRules.php delete mode 100644 tests/_support/Validation/uploads/phpUxc0ty delete mode 100644 tests/_support/View/MockTable.php delete mode 100644 tests/_support/View/SampleClass.php delete mode 100644 tests/_support/View/Views/simple.php delete mode 100644 tests/_support/View/Views/simpler.php delete mode 100644 tests/_support/_bootstrap.php delete mode 100644 tests/_support/coverage.txt diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 81ddccc3..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 CodeIgniter 4 web framework - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 03626c76..275f0153 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,6 @@ This repository holds the distributable version of the framework, including the user guide. It has been built from the [development repository](https://github.com/codeigniter4/CodeIgniter4). -**This is pre-release code and should not be used in production sites.** - More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. The user guide corresponding to this version of the framework can be found diff --git a/app/Config/Kint.php b/app/Config/Kint.php index 9562447e..09db83dd 100644 --- a/app/Config/Kint.php +++ b/app/Config/Kint.php @@ -1,7 +1,7 @@ 0644, + + /* + * Logging Directory Path + * + * By default, logs are written to WRITEPATH . 'logs/' + * Specify a different destination here, if desired. + */ + 'path' => '', ], /** diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index a570ef54..41014d40 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -516,11 +516,7 @@ public static function guessExtensionFromType(string $type, ?string $proposed_ex foreach (static::$mimes as $ext => $types) { - if (is_string($types) && $types === $type) - { - return $ext; - } - else if (is_array($types) && in_array($type, $types)) + if ((is_string($types) && $types === $type) || (is_array($types) && in_array($type, $types))) { return $ext; } diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 48327365..a2a9654b 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -1,7 +1,7 @@ + } ?>

$

@@ -235,7 +235,7 @@ + } ?> @@ -287,15 +287,15 @@ - $value) : ?> + + } ?> + } ?> getName(), 'html') ?> diff --git a/composer.json b/composer.json index d8adb485..a8045d37 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "ext-curl": "*", "ext-intl": "*", "ext-json": "*", + "ext-mbstring": "*", "kint-php/kint": "^3.3", "psr/log": "^1.1", "laminas/laminas-escaper": "^2.6" diff --git a/contributing.md b/contributing.md deleted file mode 100644 index f0cf482f..00000000 --- a/contributing.md +++ /dev/null @@ -1,94 +0,0 @@ -# Contributing to CodeIgniter4 - - -## Contributions - -We expect all contributions to conform to our [style guide](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/styleguide.rst), be commented (inside the PHP source files), -be documented (in the [user guide](https://codeigniter4.github.io/userguide/)), and unit tested (in the [test folder](https://github.com/codeigniter4/CodeIgniter4/tree/develop/tests)). -There is a [Contributing to CodeIgniter](./contributing/README.rst) section in the repository which describes the contribution process; this page is an overview. - -Note, we expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite to prove the code works. If pull requests are not accompanied by relevant tests, they will likely be closed. Since we are a team of volunteers, we don't have any more time to work on the framework than you do. Please make it as painless for your contributions to be included as possible. If you need help with getting tests running on your local machines, ask for help on the forums. We would be happy to help out. - -The [Open Source Guide](https://opensource.guide/) is a good first read for those new to contributing to open source! -## Issues - -Issues are a quick way to point out a bug. If you find a bug or documentation error in CodeIgniter then please make sure that: - -1. There is not already an open [Issue](https://github.com/codeigniter4/CodeIgniter4/issues) -2. The Issue has not already been fixed (check the develop branch or look for [closed Issues](https://github.com/codeigniter4/CodeIgniter4/issues?q=is%3Aissue+is%3Aclosed)) -3. It's not something really obvious that you can fix yourself - -Reporting Issues is helpful, but an even [better approach](./contributing/workflow.rst) is to send a [Pull Request](https://help.github.com/en/articles/creating-a-pull-request), which is done by [Forking](https://help.github.com/en/articles/fork-a-repo) the main repository and making a [Commit](https://help.github.com/en/desktop/contributing-to-projects/committing-and-reviewing-changes-to-your-project) to your own copy of the project. This will require you to use the version control system called [Git](https://git-scm.com/). - -## Guidelines - -Before we look into how to contribute to CodeIgniter4, here are some guidelines. If your Pull Requests fail -to pass these guidelines, they will be declined, and you will need to re-submit -when you’ve made the changes. This might sound a bit tough, but it is required -for us to maintain the quality of the codebase. - -### PHP Style - -All code must meet the [Style Guide](./contributing/styleguide.rst). -This makes certain that all submitted code is of the same format as the existing code and ensures that the codebase will be as readable as possible. - -### Documentation - -If you change anything that requires a change to documentation, then you will need to add to the documentation. New classes, methods, parameters, changing default values, etc. are all changes that require a change to documentation. Also, the [changelog](https://codeigniter4.github.io/CodeIgniter4/changelogs/index.html) must be updated for every change, and [PHPDoc](https://github.com/codeigniter4/CodeIgniter4/blob/develop/phpdoc.dist.xml) blocks must be maintained. - -### Compatibility - -CodeIgniter4 requires [PHP 7.2](https://php.net/releases/7_2_0.php). - -### Branching - -CodeIgniter4 uses the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/) branching model which requires all -Pull Requests to be sent to the "develop" branch; this is where the next planned version will be developed. -The "master" branch will always contain the latest stable version and is kept clean so a "hotfix" (e.g. an -emergency security patch) can be applied to the "master" branch to create a new version, without worrying -about other features holding it up. For this reason, all commits need to be made to the "develop" branch, -and any sent to the "master" branch will be closed automatically. If you have multiple changes to submit, -please place all changes into their own branch on your fork. - -**One thing at a time:** A pull request should only contain one change. That does not mean only one commit, -but one change - however many commits it took. The reason for this is that if you change X and Y, -but send a pull request for both at the same time, we might really want X but disagree with Y, -meaning we cannot merge the request. Using the Git-Flow branching model you can create new -branches for both of these features and send two requests. - -A reminder: **please use separate branches for each of your PRs** - it will make it easier for you to keep changes separate from -each other and from whatever else you are doing with your repository! - -### Signing - -You must [GPG-sign](./contributing/signing.rst) your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open-source project. This is *not* just a "signed-off-by" commit, but instead, a digitally signed one. - -## How-to Guide - -The best way to contribute is to fork the CodeIgniter4 repository, and "clone" that to your development area. That sounds like some jargon, but "forking" on GitHub means "making a copy of that repo to your account" and "cloning" means "copying that code to your environment so you can work on it". - -1. Set up Git ([Windows](https://git-scm.com/download/win), [Mac](https://git-scm.com/download/mac), & [Linux](https://git-scm.com/download/linux)). -2. Go to the [CodeIgniter4 repository](https://github.com/codeigniter4/CodeIgniter4). -3. [Fork](https://help.github.com/en/articles/fork-a-repo) it (to your Github account). -4. [Clone](https://help.github.com/en/articles/cloning-a-repository) your CodeIgniter repository: `git@github.com:\/CodeIgniter4.git` -5. Create a new [branch](https://help.github.com/en/articles/about-branches) in your project for each set of changes you want to make. -6. Fix existing bugs on the [Issue tracker](https://github.com/codeigniter4/CodeIgniter4/issues) after confirming that no one else is working on them. -7. [Commit](https://help.github.com/en/desktop/contributing-to-projects/committing-and-reviewing-changes-to-your-project) the changed files in your contribution branch. -8. [Push](https://help.github.com/en/articles/pushing-to-a-remote) your contribution branch to your fork. -9. Send a [pull request](http://help.github.com/send-pull-requests/). - -The codebase maintainers will now be alerted to the submission and someone from the team will respond. If your change fails to meet the guidelines, it will be rejected or feedback will be provided to help you improve it. - -Once the maintainer handling your pull request is satisfied with it they will approve the pull request and merge it into the "develop" branch; your patch will now be part of the next release! - -### Keeping your fork up-to-date - -Unlike systems like Subversion, Git can have multiple remotes. A remote is the name for the URL of a Git repository. By default, your fork will have a remote named "origin", which points to your fork, but you can add another remote named "codeigniter", which points to `git://github.com/codeigniter4/CodeIgniter4.git`. This is a read-only remote, but you can pull from this develop branch to update your own. - -If you are using the command-line, you can do the following to update your fork to the latest changes: - -1. `git remote add codeigniter git://github.com/codeigniter4/CodeIgniter4.git` -2. `git pull codeigniter develop` -3. `git push origin develop` - -Your fork is now up to date. This should be done regularly and, at the least, before you submit a pull request. diff --git a/public/index.php b/public/index.php index 5b9e912f..3eaa592a 100644 --- a/public/index.php +++ b/public/index.php @@ -13,7 +13,7 @@ // Location of the Paths config file. // This is the line that might need to be changed, depending on your folder structure. -$pathsPath = FCPATH . '../app/Config/Paths.php'; +$pathsPath = realpath(FCPATH . '../app/Config/Paths.php'); // ^^^ Change this if you move your application folder /* diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php index f16b2846..49248eb1 100644 --- a/system/API/ResponseTrait.php +++ b/system/API/ResponseTrait.php @@ -39,8 +39,8 @@ namespace CodeIgniter\API; -use Config\Format; use CodeIgniter\HTTP\Response; +use Config\Format; /** * Response trait. @@ -56,7 +56,6 @@ */ trait ResponseTrait { - /** * Allows child classes to override the * status code that is used in their API. @@ -66,6 +65,7 @@ trait ResponseTrait protected $codes = [ 'created' => 201, 'deleted' => 200, + 'updated' => 200, 'no_content' => 204, 'invalid_request' => 400, 'unsupported_response_type' => 400, @@ -93,6 +93,15 @@ trait ResponseTrait 'not_implemented' => 501, ]; + /** + * How to format the response data. + * Either 'json' or 'xml'. If blank will be + * determine through content negotiation. + * + * @var string + */ + protected $format = 'json'; + //-------------------------------------------------------------------- /** @@ -190,6 +199,19 @@ public function respondDeleted($data = null, string $message = '') return $this->respond($data, $this->codes['deleted'], $message); } + /** + * Used after a resource has been successfully updated. + * + * @param mixed $data Data. + * @param string $message Message. + * + * @return mixed + */ + public function respondUpdated($data = null, string $message = '') + { + return $this->respond($data, $this->codes['updated'], $message); + } + //-------------------------------------------------------------------- /** @@ -364,9 +386,14 @@ protected function format($data = null) return $data; } - // Determine correct response type through content negotiation $config = new Format(); - $format = $this->request->negotiate('media', $config->supportedResponseFormats, false); + $format = "application/$this->format"; + + // Determine correct response type through content negotiation if not explicitly declared + if (empty($this->format) || ! in_array($this->format, ['json', 'xml'])) + { + $format = $this->request->negotiate('media', $config->supportedResponseFormats, false); + } $this->response->setContentType($format); @@ -387,4 +414,17 @@ protected function format($data = null) return $this->formatter->format($data); } + /** + * Sets the format the response should be in. + * + * @param string $format + * + * @return $this + */ + public function setResponseFormat(string $format = null) + { + $this->format = strtolower($format); + + return $this; + } } diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 46631074..fa5c9a2f 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -392,7 +392,7 @@ public function sanitizeFilename(string $filename): string // be a path. // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_278 // Modified to allow backslash and colons for on Windows machines. - $filename = preg_replace('/[^a-zA-Z0-9\s\/\-\_\.\:\\\\]/', '', $filename); + $filename = preg_replace('/[^0-9\p{L}\s\/\-\_\.\:\\\\]/u', '', $filename); // Clean up our filename edges. $filename = trim($filename, '.-_'); diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 0b78e9c0..5aa217e1 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -353,7 +353,11 @@ public function findQualifiedNameFromPath(string $path) // Remove the file extension (.php) $className = mb_substr($className, 0, -4); - return $className; + // Check if this exists + if (class_exists($className)) + { + return $className; + } } } diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index e60036c7..476c08d1 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -437,7 +437,7 @@ public static function newLine(int $num = 1) // Do it once or more, write with empty string gives us a new line for ($i = 0; $i < $num; $i ++) { - static::write(''); + static::write(); } } @@ -504,9 +504,7 @@ public static function color(string $text, string $foreground, string $backgroun $string .= "\033[4m"; } - $string .= $text . "\033[0m"; - - return $string; + return $string . ($text . "\033[0m"); } //-------------------------------------------------------------------- @@ -702,24 +700,17 @@ public static function wrap(string $string = null, int $max = 0, int $pad_left = */ protected static function parseCommandLine() { - $optionsFound = false; - // start picking segments off from #1, ignoring the invoking program for ($i = 1; $i < $_SERVER['argc']; $i ++) { // If there's no '-' at the beginning of the argument // then add it to our segments. - if (! $optionsFound && mb_strpos($_SERVER['argv'][$i], '-') === false) + if (mb_strpos($_SERVER['argv'][$i], '-') === false) { static::$segments[] = $_SERVER['argv'][$i]; continue; } - // We set $optionsFound here so that we know to - // skip the next argument since it's likely the - // value belonging to this option. - $optionsFound = true; - $arg = str_replace('-', '', $_SERVER['argv'][$i]); $value = null; @@ -731,10 +722,6 @@ protected static function parseCommandLine() } static::$options[$arg] = $value; - - // Reset $optionsFound so it can collect segments - // past any options. - $optionsFound = false; } } @@ -958,7 +945,7 @@ public static function table(array $tbody, array $thead = []) } } - fwrite(STDOUT, $table); + static::write($table); } //-------------------------------------------------------------------- diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php index b1e6103e..2e1a8e11 100644 --- a/system/CLI/CommandRunner.php +++ b/system/CLI/CommandRunner.php @@ -40,8 +40,8 @@ namespace CodeIgniter\CLI; -use Config\Services; use CodeIgniter\Controller; +use Config\Services; /** * Command runner @@ -104,7 +104,7 @@ public function index(array $params) if (is_null($command)) { - $command = 'help'; + $command = 'list'; } return $this->runCommand($command, $params); diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index 737db09e..86e70903 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -154,7 +154,7 @@ public function delete(string $key) { $key = $this->prefix . $key; - return is_file($this->path . $key) ? unlink($this->path . $key) : false; + return is_file($this->path . $key) && unlink($this->path . $key); } //-------------------------------------------------------------------- diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 26a68038..c708a279 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -39,20 +39,20 @@ namespace CodeIgniter; use Closure; +use CodeIgniter\Debug\Timer; +use CodeIgniter\Events\Events; +use CodeIgniter\Exceptions\PageNotFoundException; +use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\HTTP\DownloadResponse; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\Request; +use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; -use Config\Services; -use Config\Cache; use CodeIgniter\HTTP\URI; -use CodeIgniter\Debug\Timer; -use CodeIgniter\Events\Events; -use CodeIgniter\HTTP\Response; -use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\Router\Exceptions\RedirectException; use CodeIgniter\Router\RouteCollectionInterface; -use CodeIgniter\Exceptions\PageNotFoundException; +use Config\Cache; +use Config\Services; use Exception; /** @@ -66,7 +66,7 @@ class CodeIgniter /** * The current version of CodeIgniter Framework */ - const CI_VERSION = '4.0.2'; + const CI_VERSION = '4.0.3'; /** * App startup time. @@ -194,7 +194,9 @@ public function initialize() if (! CI_DEBUG) { + // @codeCoverageIgnoreStart \Kint::$enabled_mode = false; + // @codeCoverageIgnoreEnd } } @@ -496,9 +498,11 @@ protected function bootstrapEnvironment() } else { + // @codeCoverageIgnoreStart header('HTTP/1.1 503 Service Unavailable.', true, 503); echo 'The application environment is not set correctly.'; exit(1); // EXIT_ERROR + // @codeCoverageIgnoreEnd } } @@ -550,9 +554,11 @@ protected function getRequestObject() return; } - if (is_cli() && ! (ENVIRONMENT === 'testing')) + if (is_cli() && ENVIRONMENT !== 'testing') { + // @codeCoverageIgnoreStart $this->request = Services::clirequest($this->config); + // @codeCoverageIgnoreEnd } else { @@ -747,9 +753,7 @@ public function displayPerformanceMetrics(string $output): string { $this->totalTime = $this->benchmark->getElapsedTime('total_execution'); - $output = str_replace('{elapsed_time}', $this->totalTime, $output); - - return $output; + return str_replace('{elapsed_time}', $this->totalTime, $output); } //-------------------------------------------------------------------- @@ -958,10 +962,12 @@ protected function display404errors(PageNotFoundException $e) if (ENVIRONMENT !== 'testing') { + // @codeCoverageIgnoreStart if (ob_get_level() > 0) { ob_end_flush(); } + // @codeCoverageIgnoreEnd } else { @@ -972,7 +978,7 @@ protected function display404errors(PageNotFoundException $e) } } - throw PageNotFoundException::forPageNotFound($e->getMessage()); + throw PageNotFoundException::forPageNotFound(ENVIRONMENT !== 'production' || is_cli() ? $e->getMessage() : ''); } //-------------------------------------------------------------------- @@ -1110,7 +1116,9 @@ protected function sendResponse() */ protected function callExit($code) { + // @codeCoverageIgnoreStart exit($code); + // @codeCoverageIgnoreEnd } //-------------------------------------------------------------------- diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php index 19d87a44..3983d677 100644 --- a/system/Commands/Database/CreateMigration.php +++ b/system/Commands/Database/CreateMigration.php @@ -40,8 +40,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; -use Config\Autoload; -use Config\Migrations; +use Config\Services; /** * Creates a new migration file. @@ -124,15 +123,14 @@ public function run(array $params = []) if (! empty($ns)) { - // Get all namespaces from PSR4 paths. - $config = new Autoload(); - $namespaces = $config->psr4; + // Get all namespaces + $namespaces = Services::autoloader()->getNamespace(); foreach ($namespaces as $namespace => $path) { if ($namespace === $ns) { - $homepath = realpath($path); + $homepath = realpath(reset($path)); break; } } @@ -178,7 +176,7 @@ public function down() helper('filesystem'); if (! write_file($path, $template)) { - CLI::error(lang('Migrations.writeError')); + CLI::error(lang('Migrations.writeError', [$path])); return; } diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php index e0ec6696..bf6b4f8b 100644 --- a/system/Commands/Database/MigrateRefresh.php +++ b/system/Commands/Database/MigrateRefresh.php @@ -40,6 +40,7 @@ namespace CodeIgniter\Commands\Database; use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\CLI; /** * Does a rollback followed by a latest to refresh the current state @@ -95,6 +96,7 @@ class MigrateRefresh extends BaseCommand '-n' => 'Set migration namespace', '-g' => 'Set database group', '-all' => 'Set latest for all namespace, will ignore (-n) option', + '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', ]; /** @@ -105,7 +107,20 @@ class MigrateRefresh extends BaseCommand */ public function run(array $params = []) { - $this->call('migrate:rollback', ['-b' => 0]); + $params = ['-b' => 0]; + + if (ENVIRONMENT === 'production') + { + $force = $params['-f'] ?? CLI::getOption('f'); + if (is_null($force) && CLI::prompt(lang('Migrations.refreshConfirm'), ['y', 'n']) === 'n') + { + return; + } + + $params['-f'] = ''; + } + + $this->call('migrate:rollback', $params); $this->call('migrate'); } diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php index ba259897..b2dca1c8 100644 --- a/system/Commands/Database/MigrateRollback.php +++ b/system/Commands/Database/MigrateRollback.php @@ -42,7 +42,6 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use Config\Services; -use Config\Autoload; /** * Runs all of the migrations in reverse order, until they have @@ -97,6 +96,7 @@ class MigrateRollback extends BaseCommand protected $options = [ '-b' => 'Specify a batch to roll back to; e.g. "3" to return to batch #3 or "-2" to roll back twice', '-g' => 'Set database group', + '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', ]; /** @@ -107,6 +107,15 @@ class MigrateRollback extends BaseCommand */ public function run(array $params = []) { + if (ENVIRONMENT === 'production') + { + $force = $params['-f'] ?? CLI::getOption('f'); + if (is_null($force) && CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'n') + { + return; + } + } + $runner = Services::migrations(); $group = $params['-g'] ?? CLI::getOption('g'); diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php index 8e714369..94ad3ea9 100644 --- a/system/Commands/Database/MigrateStatus.php +++ b/system/Commands/Database/MigrateStatus.php @@ -42,7 +42,6 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use Config\Services; -use Config\Autoload; /** * Displays a list of all migrations and whether they've been run or not. @@ -106,6 +105,10 @@ class MigrateStatus extends BaseCommand 'CodeIgniter', 'Config', 'Tests\Support', + 'Kint', + 'Laminas\ZendFrameworkBridge', + 'Laminas\Escaper', + 'Psr\Log', ]; /** @@ -124,9 +127,11 @@ public function run(array $params = []) $runner->setGroup($group); } - // Get all namespaces from PSR4 paths. - $config = new Autoload(); - $namespaces = $config->psr4; + // Get all namespaces + $namespaces = Services::autoloader()->getNamespace(); + + // Determines whether any migrations were found + $found = false; // Loop for all $namespaces foreach ($namespaces as $namespace => $path) @@ -138,16 +143,17 @@ public function run(array $params = []) $runner->setNamespace($namespace); $migrations = $runner->findMigrations(); - $history = $runner->getHistory(); - - CLI::write($namespace); if (empty($migrations)) { - CLI::error(lang('Migrations.noneFound')); continue; } + $found = true; + $history = $runner->getHistory(); + + CLI::write($namespace); + ksort($migrations); $max = 0; @@ -176,6 +182,11 @@ public function run(array $params = []) CLI::write(str_pad(' ' . $migration->name, $max + 6) . ($date ? $date : '---')); } } + + if (! $found) + { + CLI::error(lang('Migrations.noneFound')); + } } } diff --git a/system/Commands/ListCommands.php b/system/Commands/ListCommands.php index bae8e73d..c20fe322 100644 --- a/system/Commands/ListCommands.php +++ b/system/Commands/ListCommands.php @@ -188,9 +188,8 @@ protected function padTitle(string $item, int $max, int $extra = 2, int $indent $max += $extra + $indent; $item = str_repeat(' ', $indent) . $item; - $item = str_pad($item, $max); - return $item; + return str_pad($item, $max); } //-------------------------------------------------------------------- diff --git a/system/Commands/Server/Serve.php b/system/Commands/Server/Serve.php index 9fb68fe8..9e658629 100644 --- a/system/Commands/Server/Serve.php +++ b/system/Commands/Server/Serve.php @@ -97,14 +97,14 @@ class Serve extends BaseCommand /** * The current port offset. * - * @var int + * @var integer */ protected $portOffset = 0; /** * The max number of ports to attempt to serve from * - * @var int + * @var integer */ protected $tries = 10; @@ -131,12 +131,14 @@ public function run(array $params) // Valid PHP Version? if (phpversion() < $this->minPHPVersion) { + // @codeCoverageIgnoreStart die('Your PHP version must be ' . $this->minPHPVersion . ' or higher to run CodeIgniter. Current version: ' . phpversion()); + // @codeCoverageIgnoreEnd } // Collect any user-supplied options and apply them. - $php = CLI::getOption('php') ?? PHP_BINARY; + $php = escapeshellarg(CLI::getOption('php') ?? PHP_BINARY); $host = CLI::getOption('host') ?? 'localhost'; $port = (int) (CLI::getOption('port') ?? '8080') + $this->portOffset; @@ -155,7 +157,8 @@ public function run(array $params) // to ensure our environment is set and it simulates basic mod_rewrite. passthru($php . ' -S ' . $host . ':' . $port . ' -t ' . $docroot . ' ' . $rewrite, $status); - if ($status && $this->portOffset < $this->tries) { + if ($status && $this->portOffset < $this->tries) + { $this->portOffset += 1; $this->run($params); diff --git a/system/Commands/Sessions/CreateMigration.php b/system/Commands/Sessions/CreateMigration.php index 15c7476a..ca26bfaa 100644 --- a/system/Commands/Sessions/CreateMigration.php +++ b/system/Commands/Sessions/CreateMigration.php @@ -126,7 +126,7 @@ public function run(array $params = []) helper('filesystem'); if (! write_file($path, $template)) { - CLI::error(lang('Migrations.migWriteError')); + CLI::error(lang('Migrations.writeError', [$path])); return; } diff --git a/system/Common.php b/system/Common.php index a14bc66e..2e3fcd7d 100644 --- a/system/Common.php +++ b/system/Common.php @@ -37,19 +37,20 @@ * @filesource */ -use Config\App; -use Config\Logger; -use Config\Database; -use Config\Services; -use CodeIgniter\HTTP\URI; -use Laminas\Escaper\Escaper; use CodeIgniter\Config\Config; -use CodeIgniter\Test\TestLogger; +use CodeIgniter\Database\ConnectionInterface; +use CodeIgniter\Files\Exceptions\FileNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Database\ConnectionInterface; -use CodeIgniter\Files\Exceptions\FileNotFoundException; +use CodeIgniter\HTTP\URI; +use CodeIgniter\Test\TestLogger; +use Config\App; +use Config\Database; +use Config\Logger; +use Config\Services; +use Config\View; +use Laminas\Escaper\Escaper; /** * Common Functions @@ -246,9 +247,11 @@ function db_connect($db = null, bool $getShared = true) */ function dd(...$vars) { + // @codeCoverageIgnoreStart Kint::$aliases[] = 'dd'; Kint::dump(...$vars); exit; + // @codeCoverageIgnoreEnd } } @@ -319,7 +322,7 @@ function esc($data, string $context = 'html', string $encoding = null) { if (is_array($data)) { - foreach ($data as $key => &$value) + foreach ($data as &$value) { $value = esc($value, $context); } @@ -384,10 +387,7 @@ function esc($data, string $context = 'html', string $encoding = null) * @param RequestInterface $request * @param ResponseInterface $response * - * Not testable, as it will exit! - * - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException - * @codeCoverageIgnore + * @throws \CodeIgniter\HTTP\Exceptions\HTTPException */ function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null) { @@ -400,25 +400,33 @@ function force_https(int $duration = 31536000, RequestInterface $request = null, $response = Services::response(null, true); } - if (is_cli() || $request->isSecure()) + if (ENVIRONMENT !== 'testing' && (is_cli() || $request->isSecure())) { + // @codeCoverageIgnoreStart return; + // @codeCoverageIgnoreEnd } - // If the session library is loaded, we should regenerate + // If the session status is active, we should regenerate // the session ID for safety sake. - if (class_exists('Session', false)) + if (ENVIRONMENT !== 'testing' && session_status() === PHP_SESSION_ACTIVE) { + // @codeCoverageIgnoreStart Services::session(null, true) ->regenerate(); + // @codeCoverageIgnoreEnd } - $uri = $request->uri; - $uri->setScheme('https'); + $baseURL = config(App::class)->baseURL; + + if (strpos($baseURL, 'http://') === 0) + { + $baseURL = (string) substr($baseURL, strlen('http://')); + } $uri = URI::createURIString( - $uri->getScheme(), $uri->getAuthority(true), $uri->getPath(), // Absolute URIs should use a "/" for an empty path - $uri->getQuery(), $uri->getFragment() + 'https', $baseURL, $request->uri->getPath(), // Absolute URIs should use a "/" for an empty path + $request->uri->getQuery(), $request->uri->getFragment() ); // Set an HSTS header @@ -426,7 +434,12 @@ function force_https(int $duration = 31536000, RequestInterface $request = null, $response->redirect($uri); $response->sendHeaders(); - exit(); + if (ENVIRONMENT !== 'testing') + { + // @codeCoverageIgnoreStart + exit(); + // @codeCoverageIgnoreEnd + } } } @@ -747,7 +760,9 @@ function old(string $key, $default = null, $escape = 'html') // Ensure the session is loaded if (session_status() === PHP_SESSION_NONE && ENVIRONMENT !== 'testing') { + // @codeCoverageIgnoreStart session(); + // @codeCoverageIgnoreEnd } $request = Services::request(); @@ -1060,8 +1075,9 @@ function view(string $name, array $data = [], array $options = []): string */ $renderer = Services::renderer(); - $saveData = true; - if (array_key_exists('saveData', $options) && $options['saveData'] === true) + $saveData = config(View::class)->saveData; + + if (array_key_exists('saveData', $options)) { $saveData = (bool) $options['saveData']; unset($options['saveData']); diff --git a/system/ComposerScripts.php b/system/ComposerScripts.php index d53108f4..6ebb1227 100644 --- a/system/ComposerScripts.php +++ b/system/ComposerScripts.php @@ -90,7 +90,9 @@ protected static function moveFile(string $source, string $destination): bool if (empty($source)) { + // @codeCoverageIgnoreStart die('Cannot move file. Source path invalid.'); + // @codeCoverageIgnoreEnd } if (! is_file($source)) @@ -203,7 +205,9 @@ public static function moveEscaper() { if (! static::moveFile($source, $dest)) { + // @codeCoverageIgnoreStart die('Error moving: ' . $source); + // @codeCoverageIgnoreEnd } } } diff --git a/system/Config/BaseConfig.php b/system/Config/BaseConfig.php index 9a0100f1..ceec8baa 100644 --- a/system/Config/BaseConfig.php +++ b/system/Config/BaseConfig.php @@ -164,16 +164,12 @@ protected function getEnvValue(string $property, string $prefix, string $shortPr { case array_key_exists("{$shortPrefix}.{$property}", $_ENV): return $_ENV["{$shortPrefix}.{$property}"]; - break; case array_key_exists("{$shortPrefix}.{$property}", $_SERVER): return $_SERVER["{$shortPrefix}.{$property}"]; - break; case array_key_exists("{$prefix}.{$property}", $_ENV): return $_ENV["{$prefix}.{$property}"]; - break; case array_key_exists("{$prefix}.{$property}", $_SERVER): return $_SERVER["{$prefix}.{$property}"]; - break; default: $value = getenv($property); return $value === false ? null : $value; diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index 541334a4..4220d77f 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -128,7 +128,7 @@ public function parse(): ?array if (strpos($line, '=') !== false) { list($name, $value) = $this->normaliseVariable($line); - $vars[$name] = $value; + $vars[$name] = $value; } } @@ -314,10 +314,8 @@ protected function getVariable(string $name) { case array_key_exists($name, $_ENV): return $_ENV[$name]; - break; case array_key_exists($name, $_SERVER): return $_SERVER[$name]; - break; default: $value = getenv($name); diff --git a/system/Config/Services.php b/system/Config/Services.php index 6f166a64..afb65123 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Config; use CodeIgniter\Cache\CacheFactory; +use CodeIgniter\Database\ConnectionInterface; +use CodeIgniter\Database\MigrationRunner; use CodeIgniter\Debug\Exceptions; use CodeIgniter\Debug\Iterator; use CodeIgniter\Debug\Timer; @@ -70,10 +72,8 @@ use CodeIgniter\Validation\Validation; use CodeIgniter\View\Cell; use CodeIgniter\View\Parser; -use Config\App; -use CodeIgniter\Database\ConnectionInterface; -use CodeIgniter\Database\MigrationRunner; use CodeIgniter\View\RendererInterface; +use Config\App; use Config\Cache; use Config\Images; use Config\Logger; @@ -207,8 +207,7 @@ public static function email($config = null, bool $getShared = true) { $config = new \Config\Email(); } - $email = new \CodeIgniter\Email\Email($config); - return $email; + return new \CodeIgniter\Email\Email($config); } /** @@ -232,8 +231,7 @@ public static function encrypter($config = null, $getShared = false) } $encryption = new Encryption($config); - $encrypter = $encryption->initialize($config); - return $encrypter; + return $encryption->initialize($config); } //-------------------------------------------------------------------- @@ -502,7 +500,7 @@ public static function pager($config = null, RendererInterface $view = null, boo if (empty($config)) { - $config = new \Config\Pager(); + $config = config('Pager'); } if (! $view instanceof RendererInterface) @@ -542,7 +540,7 @@ public static function parser(string $viewPath = null, $config = null, bool $get $viewPath = $paths->viewDirectory; } - return new Parser($config, $viewPath, static::locator(true), CI_DEBUG, static::logger(true)); + return new Parser($config, $viewPath, static::locator(), CI_DEBUG, static::logger()); } //-------------------------------------------------------------------- @@ -577,7 +575,7 @@ public static function renderer(string $viewPath = null, $config = null, bool $g $viewPath = $paths->viewDirectory; } - return new \CodeIgniter\View\View($config, $viewPath, static::locator(true), CI_DEBUG, static::logger(true)); + return new \CodeIgniter\View\View($config, $viewPath, static::locator(), CI_DEBUG, static::logger()); } //-------------------------------------------------------------------- @@ -705,7 +703,7 @@ public static function router(RouteCollectionInterface $routes = null, Request $ if (empty($routes)) { - $routes = static::routes(true); + $routes = static::routes(); } return new Router($routes, $request); @@ -759,7 +757,7 @@ public static function session(App $config = null, bool $getShared = true) $config = config(App::class); } - $logger = static::logger(true); + $logger = static::logger(); $driverName = $config->sessionDriver; $driver = new $driverName($config, static::request()->getIpAddress()); diff --git a/system/Controller.php b/system/Controller.php index 172104e0..985d828d 100644 --- a/system/Controller.php +++ b/system/Controller.php @@ -39,11 +39,11 @@ namespace CodeIgniter; -use Config\Services; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Validation\Validation; use CodeIgniter\Validation\Exceptions\ValidationException; +use CodeIgniter\Validation\Validation; +use Config\Services; use Psr\Log\LoggerInterface; /** @@ -117,7 +117,6 @@ public function initController(RequestInterface $request, ResponseInterface $res $this->request = $request; $this->response = $response; $this->logger = $logger; - $this->logger->info('Controller "' . get_class($this) . '" loaded.'); if ($this->forceHTTPS > 0) { @@ -195,7 +194,7 @@ protected function validate($rules, array $messages = []): bool // If you replace the $rules array with the name of the group if (is_string($rules)) { - $validation = new \Config\Validation(); + $validation = config('Validation'); // If the rule wasn't found in the \Config\Validation, we // should throw an exception so the developer can find it. @@ -214,12 +213,10 @@ protected function validate($rules, array $messages = []): bool $rules = $validation->$rules; } - $success = $this->validator + return $this->validator ->withRequest($this->request) ->setRules($rules, $messages) ->run(); - - return $success; } //-------------------------------------------------------------------- diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 800247f8..33e70045 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Database; +use Closure; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Exceptions\DataException; -use Closure; /** * Class BaseBuilder @@ -103,7 +103,7 @@ class BaseBuilder * * @var array */ - protected $QBGroupBy = []; + public $QBGroupBy = []; /** * QB HAVING data @@ -213,6 +213,14 @@ class BaseBuilder */ protected $binds = []; + /** + * Collects the key count for named parameters + * in the Query object. + * + * @var array + */ + protected $bindsKeyCount = []; + /** * Some databases, like SQLite, do not by default * allow limiting of delete clauses. @@ -388,7 +396,7 @@ public function select($select = '*', bool $escape = null) */ public function selectMax(string $select = '', string $alias = '') { - return $this->maxMinAvgSum($select, $alias, 'MAX'); + return $this->maxMinAvgSum($select, $alias); } //-------------------------------------------------------------------- @@ -973,20 +981,38 @@ public function orHavingNotIn(string $key = null, $values = null, bool $escape = * @used-by whereNotIn() * @used-by orWhereNotIn() * - * @param string $key The field to search - * @param array|Closure $values The values searched on, or anonymous function with subquery - * @param boolean $not If the statement would be IN or NOT IN - * @param string $type - * @param boolean $escape - * @param string $clause (Internal use only) + * @param string $key The field to search + * @param array|Closure $values The values searched on, or anonymous function with subquery + * @param boolean $not If the statement would be IN or NOT IN + * @param string $type + * @param boolean $escape + * @param string $clause (Internal use only) + * @throws InvalidArgumentException * * @return BaseBuilder */ protected function _whereIn(string $key = null, $values = null, bool $not = false, string $type = 'AND ', bool $escape = null, string $clause = 'QBWhere') { - if ($key === null || $values === null || (! is_array($values) && ! ($values instanceof Closure))) + if (empty($key) || ! is_string($key)) { + if (CI_DEBUG) + { + throw new \InvalidArgumentException(sprintf('%s() expects $key to be a non-empty string', debug_backtrace(0, 2)[1]['function'])); + } + // @codeCoverageIgnoreStart return $this; + // @codeCoverageIgnoreEnd + } + + if ($values === null || (! is_array($values) && ! ($values instanceof Closure))) + { + if (CI_DEBUG) + { + throw new \InvalidArgumentException(sprintf('%s() expects $values to be of type array or closure', debug_backtrace(0, 2)[1]['function'])); + } + // @codeCoverageIgnoreStart + return $this; + // @codeCoverageIgnoreEnd } is_bool($escape) || $escape = $this->db->protectIdentifiers; @@ -1301,7 +1327,7 @@ protected function _like_statement(string $prefix = null, string $column, string */ public function groupStart() { - return $this->groupStartPrepare('', 'AND ', 'QBWhere'); + return $this->groupStartPrepare(); } //-------------------------------------------------------------------- @@ -1313,7 +1339,7 @@ public function groupStart() */ public function orGroupStart() { - return $this->groupStartPrepare('', 'OR ', 'QBWhere'); + return $this->groupStartPrepare('', 'OR '); } //-------------------------------------------------------------------- @@ -1325,7 +1351,7 @@ public function orGroupStart() */ public function notGroupStart() { - return $this->groupStartPrepare('NOT ', 'AND ', 'QBWhere'); + return $this->groupStartPrepare('NOT '); } //-------------------------------------------------------------------- @@ -1337,7 +1363,7 @@ public function notGroupStart() */ public function orNotGroupStart() { - return $this->groupStartPrepare('NOT ', 'OR ', 'QBWhere'); + return $this->groupStartPrepare('NOT ', 'OR '); } //-------------------------------------------------------------------- @@ -1349,7 +1375,7 @@ public function orNotGroupStart() */ public function groupEnd() { - return $this->groupEndPrepare('QBWhere'); + return $this->groupEndPrepare(); } // -------------------------------------------------------------------- @@ -1682,9 +1708,9 @@ public function offset(int $offset) * * @return string */ - protected function _limit(string $sql): string + protected function _limit(string $sql, bool $offsetIgnore = false): string { - return $sql . ' LIMIT ' . ($this->QBOffset ? $this->QBOffset . ', ' : '') . $this->QBLimit; + return $sql . ' LIMIT ' . (false === $offsetIgnore && $this->QBOffset ? $this->QBOffset . ', ' : '') . $this->QBLimit; } //-------------------------------------------------------------------- @@ -1897,7 +1923,7 @@ public function countAllResults(bool $reset = true) $limit = $this->QBLimit; $this->QBLimit = false; - $sql = ($this->QBDistinct === true) + $sql = ($this->QBDistinct === true || ! empty($this->QBGroupBy)) ? $this->countString . $this->db->protectIdentifiers('numrows') . "\nFROM (\n" . $this->compileSelect() . "\n) CI_count_all_results" : @@ -2012,8 +2038,9 @@ public function insertBatch(array $set = null, bool $escape = null, int $batchSi { throw new DatabaseException('You must use the "set" method to update an entry.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } } else @@ -2024,8 +2051,9 @@ public function insertBatch(array $set = null, bool $escape = null, int $batchSi { throw new DatabaseException('insertBatch() called with no data'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $this->setInsertBatch($set, '', $escape); @@ -2234,8 +2262,9 @@ protected function validateInsert(): bool { throw new DatabaseException('You must use the "set" method to update an entry.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } return true; @@ -2284,7 +2313,9 @@ public function replace(array $set = null) { throw new DatabaseException('You must use the "set" method to update an entry.'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $table = $this->QBFrom[0]; @@ -2445,7 +2476,7 @@ protected function _update(string $table, array $values): string return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) . $this->compileWhereHaving('QBWhere') . $this->compileOrderBy() - . ($this->QBLimit ? $this->_limit(' ') : ''); + . ($this->QBLimit ? $this->_limit(' ', true) : ''); } //-------------------------------------------------------------------- @@ -2468,8 +2499,9 @@ protected function validateUpdate(): bool { throw new DatabaseException('You must use the "set" method to update an entry.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } return true; @@ -2497,8 +2529,9 @@ public function updateBatch(array $set = null, string $index = null, int $batchS { throw new DatabaseException('You must specify an index to match on for batch updates.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } if ($set === null) @@ -2509,7 +2542,9 @@ public function updateBatch(array $set = null, string $index = null, int $batchS { throw new DatabaseException('You must use the "set" method to update an entry.'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } } else @@ -2520,7 +2555,9 @@ public function updateBatch(array $set = null, string $index = null, int $batchS { throw new DatabaseException('updateBatch() called with no data'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $this->setUpdateBatch($set, $index); @@ -2573,7 +2610,7 @@ protected function _updateBatch(string $table, array $values, string $index): st $ids = []; $final = []; - foreach ($values as $key => $val) + foreach ($values as $val) { $ids[] = $val[$index]; @@ -2622,7 +2659,7 @@ public function setUpdateBatch($key, string $index = '', bool $escape = null) is_bool($escape) || $escape = $this->db->protectIdentifiers; - foreach ($key as $k => $v) + foreach ($key as $v) { $index_set = false; $clean = []; @@ -2769,8 +2806,9 @@ public function delete($where = '', int $limit = null, bool $reset_data = true) { throw new DatabaseException('Deletes are not allowed unless they contain a "where" or "like" clause.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $sql = $this->_delete($table); @@ -2787,7 +2825,7 @@ public function delete($where = '', int $limit = null, bool $reset_data = true) throw new DatabaseException('SQLite3 does not allow LIMITs on DELETE queries.'); } - $sql = $this->_limit($sql); + $sql = $this->_limit($sql, true); } if ($reset_data) @@ -2849,8 +2887,7 @@ public function decrement(string $column, int $value = 1) */ protected function _delete(string $table): string { - return 'DELETE ' . $this->compileIgnore('delete') . 'FROM ' . $table . $this->compileWhereHaving('QBWhere') - . ($this->QBLimit ? ' LIMIT ' . $this->QBLimit : ''); + return 'DELETE ' . $this->compileIgnore('delete') . 'FROM ' . $table . $this->compileWhereHaving('QBWhere'); } //-------------------------------------------------------------------- @@ -3304,6 +3341,12 @@ protected function resetSelect() { $this->db->setAliasedTables([]); } + + // Reset QBFrom part + if (! empty($this->QBFrom)) + { + $this->from(array_shift($this->QBFrom), true); + } } //-------------------------------------------------------------------- @@ -3402,12 +3445,11 @@ protected function setBind(string $key, $value = null, bool $escape = true): str return $key; } - $count = 0; - - while (array_key_exists($key . $count, $this->binds)) + if (! array_key_exists($key, $this->bindsKeyCount)) { - ++$count; + $this->bindsKeyCount[$key] = 0; } + $count = $this->bindsKeyCount[$key]++; $this->binds[$key . $count] = [ $value, diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index de9d510b..844cf7ba 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database; -use CodeIgniter\Events\Events; use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Events\Events; /** * Class BaseConnection @@ -1002,7 +1002,7 @@ public function prepare(\Closure $func, array $options = []) $this->initialize(); } - $this->pretend(true); + $this->pretend(); $sql = $func($this); @@ -1381,9 +1381,7 @@ public function escape($str) { if (is_array($str)) { - $str = array_map([&$this, 'escape'], $str); - - return $str; + return array_map([&$this, 'escape'], $str); } else if (is_string($str) || ( is_object($str) && method_exists($str, '__toString'))) { diff --git a/system/Database/BaseResult.php b/system/Database/BaseResult.php index b9d586a3..5e6abe35 100644 --- a/system/Database/BaseResult.php +++ b/system/Database/BaseResult.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Database; +use CodeIgniter\Entity; + /** * Class BaseResult */ @@ -187,12 +189,12 @@ public function getCustomResultObject(string $className) return $this->customResultObject[$className]; } - is_null($this->rowData) || $this->dataSeek(0); + is_null($this->rowData) || $this->dataSeek(); $this->customResultObject[$className] = []; while ($row = $this->fetchObject($className)) { - if (method_exists($row, 'syncOriginal')) + if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) { $row->syncOriginal(); } @@ -237,7 +239,7 @@ public function getResultArray(): array return $this->resultArray; } - is_null($this->rowData) || $this->dataSeek(0); + is_null($this->rowData) || $this->dataSeek(); while ($row = $this->fetchAssoc()) { $this->resultArray[] = $row; @@ -280,10 +282,10 @@ public function getResultObject(): array return $this->resultObject; } - is_null($this->rowData) || $this->dataSeek(0); + is_null($this->rowData) || $this->dataSeek(); while ($row = $this->fetchObject()) { - if (method_exists($row, 'syncOriginal')) + if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) { $row->syncOriginal(); } @@ -312,7 +314,7 @@ public function getRow($n = 0, string $type = 'object') if (! is_numeric($n)) { // We cache the row data for subsequent uses - is_array($this->rowData) || $this->rowData = $this->getRowArray(0); + is_array($this->rowData) || $this->rowData = $this->getRowArray(); // array_key_exists() instead of isset() to allow for NULL values if (empty($this->rowData) || ! array_key_exists($n, $this->rowData)) @@ -433,7 +435,7 @@ public function setRow($key, $value = null) // We cache the row data for subsequent uses if (! is_array($this->rowData)) { - $this->rowData = $this->getRowArray(0); + $this->rowData = $this->getRowArray(); } if (is_array($key)) diff --git a/system/Database/BaseUtils.php b/system/Database/BaseUtils.php index a2ba2b0d..e9a4dd63 100644 --- a/system/Database/BaseUtils.php +++ b/system/Database/BaseUtils.php @@ -356,7 +356,7 @@ public function backup($params = []) 'tables' => [], 'ignore' => [], 'filename' => '', - 'format' => 'gzip', // gzip, zip, txt + 'format' => 'gzip', // gzip, txt 'add_drop' => true, 'add_insert' => true, 'newline' => "\n", @@ -383,15 +383,14 @@ public function backup($params = []) } // Validate the format - if (! in_array($prefs['format'], ['gzip', 'zip', 'txt'], true)) + if (! in_array($prefs['format'], ['gzip', 'txt'], true)) { $prefs['format'] = 'txt'; } // Is the encoder supported? If not, we'll either issue an // error or use plain text depending on the debug settings - if (($prefs['format'] === 'gzip' && ! function_exists('gzencode')) - || ( $prefs['format'] === 'zip' && ! function_exists('gzcompress'))) + if ($prefs['format'] === 'gzip' && ! function_exists('gzencode')) { if ($this->db->DBDebug) { @@ -401,46 +400,12 @@ public function backup($params = []) $prefs['format'] = 'txt'; } - // Was a Zip file requested? - if ($prefs['format'] === 'zip') - { - // Set the filename if not provided (only needed with Zip files) - if ($prefs['filename'] === '') - { - $prefs['filename'] = (count($prefs['tables']) === 1 ? $prefs['tables'] : $this->db->database) - . date('Y-m-d_H-i', time()) . '.sql'; - } - else - { - // If they included the .zip file extension we'll remove it - if (preg_match('|.+?\.zip$|', $prefs['filename'])) - { - $prefs['filename'] = str_replace('.zip', '', $prefs['filename']); - } - - // Tack on the ".sql" file extension if needed - if (! preg_match('|.+?\.sql$|', $prefs['filename'])) - { - $prefs['filename'] .= '.sql'; - } - } - - // Load the Zip class and output it - // $CI =& get_instance(); - // $CI->load->library('zip'); - // $CI->zip->add_data($prefs['filename'], $this->_backup($prefs)); - // return $CI->zip->get_zip(); - } - elseif ($prefs['format'] === 'txt') // Was a text file requested? + if ($prefs['format'] === 'txt') // Was a text file requested? { return $this->_backup($prefs); } - elseif ($prefs['format'] === 'gzip') // Was a Gzip file requested? - { - return gzencode($this->_backup($prefs)); - } - return; + return gzencode($this->_backup($prefs)); } //-------------------------------------------------------------------- diff --git a/system/Database/Database.php b/system/Database/Database.php index a8d26b10..adb0063c 100644 --- a/system/Database/Database.php +++ b/system/Database/Database.php @@ -109,9 +109,7 @@ public function loadForge(ConnectionInterface $db) $db->initialize(); } - $class = new $className($db); - - return $class; + return new $className($db); } //-------------------------------------------------------------------- @@ -133,9 +131,7 @@ public function loadUtils(ConnectionInterface $db) $db->initialize(); } - $class = new $className($db); - - return $class; + return new $className($db); } //-------------------------------------------------------------------- diff --git a/system/Database/Forge.php b/system/Database/Forge.php index b40dc5eb..e38d505c 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -110,14 +110,14 @@ class Forge * * @var string */ - protected $createDatabaseIfStr = null; + protected $createDatabaseIfStr; /** * CHECK DATABASE EXIST statement * * @var string */ - protected $checkDatabaseExistStr = null; + protected $checkDatabaseExistStr; /** * DROP DATABASE statement @@ -717,9 +717,7 @@ protected function _dropTable(string $table, bool $if_exists, bool $cascade): st } } - $sql = $sql . ' ' . $this->db->escapeIdentifiers($table); - - return $sql; + return $sql . ' ' . $this->db->escapeIdentifiers($table); } //-------------------------------------------------------------------- diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 6d4f0e2b..55350c86 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -38,10 +38,10 @@ namespace CodeIgniter\Database; -use Config\Services; use CodeIgniter\CLI\CLI; use CodeIgniter\Config\BaseConfig; use CodeIgniter\Exceptions\ConfigException; +use Config\Services; /** * Class MigrationRunner @@ -1027,6 +1027,15 @@ protected function migrate($direction, $migration): bool // Determine DBGroup to use $group = $instance->getDBGroup() ?? config('Database')->defaultGroup; + // Skip tests db group when not running in testing environment + if (ENVIRONMENT !== 'testing' && $group === 'tests' && $this->groupFilter !== 'tests') + { + // @codeCoverageIgnoreStart + $this->groupSkip = true; + return true; + // @codeCoverageIgnoreEnd + } + // Skip migration if group filtering was set if ($direction === 'up' && ! is_null($this->groupFilter) && $this->groupFilter !== $group) { diff --git a/system/Database/ModelFactory.php b/system/Database/ModelFactory.php index 6b7d514e..b2b43620 100644 --- a/system/Database/ModelFactory.php +++ b/system/Database/ModelFactory.php @@ -12,14 +12,6 @@ class ModelFactory */ static private $instances = []; - /** - * The Database connection to use, - * if other than default. - * - * @var ConnectionInterface - */ - static private $connection = null; - /** * Create new configuration instances or return * a shared instance diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 26b213ce..9bda5b06 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -326,8 +326,19 @@ public function execute(string $sql) $res->free(); } } - - return $this->connID->query($this->prepQuery($sql)); + try + { + return $this->connID->query($this->prepQuery($sql)); + } + catch (\mysqli_sql_exception $e) + { + log_message('error', $e); + if ($this->DBDebug) + { + throw $e; + } + } + return false; } //-------------------------------------------------------------------- @@ -424,8 +435,6 @@ public function escapeLikeStringDirect($str) '\\' . '_', ], $str ); - - return $str; } //-------------------------------------------------------------------- diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 35d87b12..3ef41047 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database\MySQLi; -use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; +use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for MySQLi @@ -116,9 +116,7 @@ public function _execute(array $data): bool // Bind it $this->statement->bind_param($bindTypes, ...$data); - $success = $this->statement->execute(); - - return $success; + return $this->statement->execute(); } //-------------------------------------------------------------------- diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index bf20a3d1..fa45c45d 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -40,7 +40,6 @@ use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\Exceptions\DatabaseException; -use http\Encoding\Stream\Inflate; /** * Builder for Postgre @@ -196,7 +195,9 @@ public function replace(array $set = null) { throw new DatabaseException('You must use the "set" method to update an entry.'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $table = $this->QBFrom[0]; @@ -269,7 +270,7 @@ public function delete($where = '', int $limit = null, bool $reset_data = true) * * @return string */ - protected function _limit(string $sql): string + protected function _limit(string $sql, bool $offsetIgnore = false): string { return $sql . ' LIMIT ' . $this->QBLimit . ($this->QBOffset ? " OFFSET {$this->QBOffset}" : ''); } @@ -316,7 +317,7 @@ protected function _update(string $table, array $values): string protected function _updateBatch(string $table, array $values, string $index): string { $ids = []; - foreach ($values as $key => $val) + foreach ($values as $val) { $ids[] = $val[$index]; diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index b239232b..c3d6db6b 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -187,11 +187,23 @@ public function getVersion(): string * * @param string $sql * - * @return resource + * @return mixed */ public function execute(string $sql) { - return pg_query($this->connID, $sql); + try + { + return pg_query($this->connID, $sql); + } + catch (\ErrorException $e) + { + log_message('error', $e); + if ($this->DBDebug) + { + throw $e; + } + } + return false; } //-------------------------------------------------------------------- diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php index 7b3bff33..27e3da2c 100644 --- a/system/Database/Postgre/PreparedQuery.php +++ b/system/Database/Postgre/PreparedQuery.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database\Postgre; -use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; +use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for Postgre @@ -148,12 +148,10 @@ public function parameterize(string $sql): string // Track our current value $count = 0; - $sql = preg_replace_callback('/\?/', function ($matches) use (&$count) { + return preg_replace_callback('/\?/', function ($matches) use (&$count) { $count ++; return "\${$count}"; }, $sql); - - return $sql; } //-------------------------------------------------------------------- diff --git a/system/Database/Query.php b/system/Database/Query.php index 301185b4..dae2eeb1 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -368,7 +368,7 @@ protected function compileBinds() { $sql = $this->finalQueryString; - $hasNamedBinds = strpos($sql, ':') !== false; + $hasNamedBinds = strpos($sql, ':') !== false && strpos($sql, ':=') === false; if (empty($this->binds) || empty($this->bindMarker) || (strpos($sql, $this->bindMarker) === false && @@ -440,9 +440,7 @@ protected function matchNamedBinds(string $sql, array $binds): string $replacers[":{$placeholder}:"] = $escapedValue; } - $sql = strtr($sql, $replacers); - - return $sql; + return strtr($sql, $replacers); } //-------------------------------------------------------------------- diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index ae83cfc1..d3ac1cb6 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -165,9 +165,21 @@ public function getVersion(): string */ public function execute(string $sql) { - return $this->isWriteType($sql) - ? $this->connID->exec($sql) - : $this->connID->query($sql); + try + { + return $this->isWriteType($sql) + ? $this->connID->exec($sql) + : $this->connID->query($sql); + } + catch (\ErrorException $e) + { + log_message('error', $e); + if ($this->DBDebug) + { + throw $e; + } + } + return false; } //-------------------------------------------------------------------- @@ -323,8 +335,8 @@ public function _fieldData(string $table): array $retVal[$i]->type = $query[$i]->type; $retVal[$i]->max_length = null; $retVal[$i]->default = $query[$i]->dflt_value; - $retVal[$i]->primary_key = isset($query[$i]->pk) ? (bool)$query[$i]->pk : false; - $retVal[$i]->nullable = isset($query[$i]->notnull) ? ! (bool)$query[$i]->notnull : false; + $retVal[$i]->primary_key = isset($query[$i]->pk) && (bool)$query[$i]->pk; + $retVal[$i]->nullable = isset($query[$i]->notnull) && ! (bool)$query[$i]->notnull; } return $retVal; diff --git a/system/Database/SQLite3/Forge.php b/system/Database/SQLite3/Forge.php index 338c6e4e..bb6f4826 100644 --- a/system/Database/SQLite3/Forge.php +++ b/system/Database/SQLite3/Forge.php @@ -167,7 +167,6 @@ protected function _alterTable(string $alter_type, string $table, $field) ->run(); return ''; - break; case 'CHANGE': $sqlTable = new Table($this->db, $this); @@ -176,7 +175,6 @@ protected function _alterTable(string $alter_type, string $table, $field) ->run(); return null; - break; default: return parent::_alterTable($alter_type, $table, $field); } diff --git a/system/Database/SQLite3/PreparedQuery.php b/system/Database/SQLite3/PreparedQuery.php index fbd6feac..22202c3b 100644 --- a/system/Database/SQLite3/PreparedQuery.php +++ b/system/Database/SQLite3/PreparedQuery.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database\SQLite3; -use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; +use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for SQLite3 diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php index 682b7cec..97eb414f 100644 --- a/system/Database/Seeder.php +++ b/system/Database/Seeder.php @@ -122,6 +122,8 @@ public function __construct(BaseConfig $config, BaseConnection $db = null) } $this->db = & $db; + + $this->forge = \Config\Database::forge($this->DBGroup); } //-------------------------------------------------------------------- diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php index 7cc3043b..7f7ca2cb 100644 --- a/system/Debug/Exceptions.php +++ b/system/Debug/Exceptions.php @@ -142,6 +142,7 @@ public function initialize() */ public function exceptionHandler(Throwable $exception) { + // @codeCoverageIgnoreStart $codes = $this->determineCodes($exception); $statusCode = $codes[0]; $exitCode = $codes[1]; @@ -171,6 +172,7 @@ public function exceptionHandler(Throwable $exception) $this->render($exception, $statusCode); exit($exitCode); + // @codeCoverageIgnoreEnd } //-------------------------------------------------------------------- @@ -186,11 +188,10 @@ public function exceptionHandler(Throwable $exception) * @param string $message * @param string|null $file * @param integer|null $line - * @param null $context * * @throws \ErrorException */ - public function errorHandler(int $severity, string $message, string $file = null, int $line = null, $context = null) + public function errorHandler(int $severity, string $message, string $file = null, int $line = null) { if (! (error_reporting() & $severity)) { @@ -371,17 +372,20 @@ protected function determineCodes(Throwable $exception): array */ public static function cleanPath(string $file): string { - if (strpos($file, APPPATH) === 0) + switch (true) { - $file = 'APPPATH/' . substr($file, strlen(APPPATH)); - } - elseif (strpos($file, SYSTEMPATH) === 0) - { - $file = 'SYSTEMPATH/' . substr($file, strlen(SYSTEMPATH)); - } - elseif (strpos($file, FCPATH) === 0) - { - $file = 'FCPATH/' . substr($file, strlen(FCPATH)); + case strpos($file, APPPATH) === 0: + $file = 'APPPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(APPPATH)); + break; + case strpos($file, SYSTEMPATH) === 0: + $file = 'SYSTEMPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(SYSTEMPATH)); + break; + case strpos($file, FCPATH) === 0: + $file = 'FCPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(FCPATH)); + break; + case defined('VENDORPATH') && strpos($file, VENDORPATH) === 0; + $file = 'VENDORPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(VENDORPATH)); + break; } return $file; diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index 02e50fc8..ff2be35c 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -118,7 +118,7 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques $data['startTime'] = $startTime; $data['totalTime'] = $totalTime * 1000; $data['totalMemory'] = number_format((memory_get_peak_usage()) / 1024 / 1024, 3); - $data['segmentDuration'] = $this->roundTo($data['totalTime'] / 7, 5); + $data['segmentDuration'] = $this->roundTo($data['totalTime'] / 7); $data['segmentCount'] = (int) ceil($data['totalTime'] / $data['segmentDuration']); $data['CI_VERSION'] = \CodeIgniter\CodeIgniter::CI_VERSION; $data['collectors'] = []; @@ -167,7 +167,7 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques $data['vars']['post'][esc($name)] = is_array($value) ? '
' . esc(print_r($value, true)) . '
' : esc($value); } - foreach ($request->getHeaders() as $header => $value) + foreach ($request->getHeaders() as $value) { if (empty($value)) { @@ -415,6 +415,7 @@ public function respond() return; } + // @codeCoverageIgnoreStart $request = Services::request(); // If the request contains '?debugbar then we're @@ -459,6 +460,7 @@ public function respond() http_response_code(404); exit; // Exit here is needed to avoid load the index page } + // @codeCoverageIgnoreEnd } /** diff --git a/system/Debug/Toolbar/Collectors/Config.php b/system/Debug/Toolbar/Collectors/Config.php index 1ae809cd..82ef6347 100644 --- a/system/Debug/Toolbar/Collectors/Config.php +++ b/system/Debug/Toolbar/Collectors/Config.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; +use CodeIgniter\CodeIgniter; use Config\App; use Config\Services; -use CodeIgniter\CodeIgniter; /** * Debug toolbar configuration diff --git a/system/Debug/Toolbar/Collectors/Events.php b/system/Debug/Toolbar/Collectors/Events.php index 285d87d8..41c9f008 100644 --- a/system/Debug/Toolbar/Collectors/Events.php +++ b/system/Debug/Toolbar/Collectors/Events.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; -use Config\Services; use CodeIgniter\View\RendererInterface; +use Config\Services; /** * Views collector @@ -111,7 +111,7 @@ protected function formatTimelineData(): array $rows = $this->viewer->getPerformanceData(); - foreach ($rows as $name => $info) + foreach ($rows as $info) { $data[] = [ 'name' => 'View: ' . $info['view'], diff --git a/system/Debug/Toolbar/Collectors/Views.php b/system/Debug/Toolbar/Collectors/Views.php index 8bcec569..bf0c1108 100644 --- a/system/Debug/Toolbar/Collectors/Views.php +++ b/system/Debug/Toolbar/Collectors/Views.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; -use Config\Services; use CodeIgniter\View\RendererInterface; +use Config\Services; /** * Views collector @@ -126,7 +126,7 @@ protected function formatTimelineData(): array $rows = $this->viewer->getPerformanceData(); - foreach ($rows as $name => $info) + foreach ($rows as $info) { $data[] = [ 'name' => 'View: ' . $info['view'], diff --git a/system/Debug/Toolbar/Views/toolbarloader.js.php b/system/Debug/Toolbar/Views/toolbarloader.js.php index 4ecdeeb1..af693381 100644 --- a/system/Debug/Toolbar/Views/toolbarloader.js.php +++ b/system/Debug/Toolbar/Views/toolbarloader.js.php @@ -75,9 +75,11 @@ function newXHR() { var debugbarTime = realXHR.getResponseHeader('Debugbar-Time'); if (debugbarTime) { var h2 = document.querySelector('#ci-history > h2'); - h2.innerHTML = 'History You have new debug data. '; - var badge = document.querySelector('a[data-tab="ci-history"] > span > .badge'); - badge.className += ' active'; + if(h2) { + h2.innerHTML = 'History You have new debug data. '; + var badge = document.querySelector('a[data-tab="ci-history"] > span > .badge'); + badge.className += ' active'; + } } } }, false); diff --git a/system/Encryption/Encryption.php b/system/Encryption/Encryption.php index 1f047f6e..4c2d3337 100644 --- a/system/Encryption/Encryption.php +++ b/system/Encryption/Encryption.php @@ -38,10 +38,8 @@ namespace CodeIgniter\Encryption; -use Config\Encryption as EncryptionConfig; -use CodeIgniter\Encryption\Exceptions\EncryptionException; use CodeIgniter\Config\BaseConfig; -use Config\Services; +use CodeIgniter\Encryption\Exceptions\EncryptionException; /** * CodeIgniter Encryption Manager diff --git a/system/Encryption/Handlers/OpenSSLHandler.php b/system/Encryption/Handlers/OpenSSLHandler.php index 72b60236..50622d1f 100644 --- a/system/Encryption/Handlers/OpenSSLHandler.php +++ b/system/Encryption/Handlers/OpenSSLHandler.php @@ -64,7 +64,7 @@ class OpenSSLHandler extends BaseHandler * * @param BaseConfig $config * - * @throws \CodeIgniter\Encryption\EncryptionException + * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException */ public function __construct(BaseConfig $config = null) { @@ -77,7 +77,7 @@ public function __construct(BaseConfig $config = null) * @param string $data Input data * @param array $params Over-ridden parameters, specifically the key * @return string - * @throws \CodeIgniter\Encryption\EncryptionException + * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException */ public function encrypt($data, $params = null) { @@ -114,9 +114,8 @@ public function encrypt($data, $params = null) $result = $iv . $data; $hmacKey = \hash_hmac($this->digest, $result, $secret, true); - $result = $hmacKey . $result; - return $result; + return $hmacKey . $result; } // -------------------------------------------------------------------- @@ -127,7 +126,7 @@ public function encrypt($data, $params = null) * @param string $data Encrypted data * @param array $params Over-ridden parameters, specifically the key * @return string - * @throws \CodeIgniter\Encryption\EncryptionException + * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException */ public function decrypt($data, $params = null) { @@ -145,7 +144,7 @@ public function decrypt($data, $params = null) } if (empty($this->key)) { - throw EncryptionException::forStarterKeyNeeded(); + throw EncryptionException::forNeedsStarterKey(); } // derive a secret key diff --git a/system/Entity.php b/system/Entity.php index 94153d0b..1ccd6d29 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -39,14 +39,13 @@ namespace CodeIgniter; -use CodeIgniter\Exceptions\EntityException; -use CodeIgniter\I18n\Time; use CodeIgniter\Exceptions\CastException; +use CodeIgniter\I18n\Time; /** * Entity encapsulation, for use with CodeIgniter\Model */ -class Entity +class Entity implements \JsonSerializable { /** * Maps names used in sets and gets against unique @@ -165,7 +164,7 @@ public function toArray(bool $onlyChanged = false, bool $cast = true): array // allow our magic methods a chance to do their thing. foreach ($this->attributes as $key => $value) { - if (substr($key, 0, 1) === '_') + if (strpos($key, '_') === 0) { continue; } @@ -353,7 +352,7 @@ public function __set(string $key, $value = null) if (array_key_exists($key, $this->casts)) { - $isNullable = substr($this->casts[$key], 0, 1) === '?'; + $isNullable = strpos($this->casts[$key], '?') === 0; $castTo = $isNullable ? substr($this->casts[$key], 1) : $this->casts[$key]; } @@ -530,7 +529,7 @@ protected function mutateDate($value) protected function castAs($value, string $type) { - if (substr($type, 0, 1) === '?') + if (strpos($type, '?') === 0) { if ($value === null) { @@ -570,17 +569,15 @@ protected function castAs($value, string $type) $value = (array)$value; break; case 'json': - $value = $this->castAsJson($value, false); + $value = $this->castAsJson($value); break; case 'json-array': $value = $this->castAsJson($value, true); break; case 'datetime': - return new \DateTime($value); - break; + return $this->mutateDate($value); case 'timestamp': return strtotime($value); - break; } return $value; @@ -614,4 +611,15 @@ private function castAsJson($value, bool $asArray = false) } return $tmp; } + + /** + * Support for json_encode() + * + * @return array|mixed + * @throws \Exception + */ + public function jsonSerialize() + { + return $this->toArray(); + } } diff --git a/system/Exceptions/CastException.php b/system/Exceptions/CastException.php index a5bb0659..ddcb7178 100644 --- a/system/Exceptions/CastException.php +++ b/system/Exceptions/CastException.php @@ -19,22 +19,17 @@ public static function forInvalidJsonFormatException(int $error) switch($error) { case JSON_ERROR_DEPTH: - throw new static(lang('Cast.jsonErrorDepth')); - break; + return new static(lang('Cast.jsonErrorDepth')); case JSON_ERROR_STATE_MISMATCH: - throw new static(lang('Cast.jsonErrorStateMismatch')); - break; + return new static(lang('Cast.jsonErrorStateMismatch')); case JSON_ERROR_CTRL_CHAR: - throw new static(lang('Cast.jsonErrorCtrlChar')); - break; + return new static(lang('Cast.jsonErrorCtrlChar')); case JSON_ERROR_SYNTAX: - throw new static(lang('Cast.jsonErrorSyntax')); - break; + return new static(lang('Cast.jsonErrorSyntax')); case JSON_ERROR_UTF8: - throw new static(lang('Cast.jsonErrorUtf8')); - break; + return new static(lang('Cast.jsonErrorUtf8')); default: - throw new static(lang('Cast.jsonErrorUnknown')); + return new static(lang('Cast.jsonErrorUnknown')); } } diff --git a/system/Exceptions/ConfigException.php b/system/Exceptions/ConfigException.php index dc36377e..964c5212 100644 --- a/system/Exceptions/ConfigException.php +++ b/system/Exceptions/ConfigException.php @@ -16,6 +16,6 @@ class ConfigException extends CriticalError public static function forDisabledMigrations() { - throw new static(lang('Migrations.disabled')); + return new static(lang('Migrations.disabled')); } } diff --git a/system/Files/File.php b/system/Files/File.php index 8b5fc2ed..1f597958 100644 --- a/system/Files/File.php +++ b/system/Files/File.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Files; -use SplFileInfo; use CodeIgniter\Files\Exceptions\FileException; use CodeIgniter\Files\Exceptions\FileNotFoundException; +use SplFileInfo; /** * Wrapper for PHP's built-in SplFileInfo, with goodies. diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 853e135a..451a2dab 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Filters; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Filters\Exceptions\FilterException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Filters\Exceptions\FilterException; /** * Filters @@ -150,47 +150,59 @@ public function run(string $uri, string $position = 'before') throw FilterException::forNoAlias($alias); } - $class = new $this->config->aliases[$alias](); - - if (! $class instanceof FilterInterface) + if (is_array($this->config->aliases[$alias])) { - throw FilterException::forIncorrectInterface(get_class($class)); + $classNames = $this->config->aliases[$alias]; + } + else + { + $classNames = [$this->config->aliases[$alias]]; } - if ($position === 'before') + foreach ($classNames as $className) { - $result = $class->before($this->request, $this->arguments[$alias] ?? null); + $class = new $className(); - if ($result instanceof RequestInterface) + if (! $class instanceof FilterInterface) { - $this->request = $result; - continue; + throw FilterException::forIncorrectInterface(get_class($class)); } - // If the response object was sent back, - // then send it and quit. - if ($result instanceof ResponseInterface) + if ($position === 'before') { - // short circuit - bypass any other filters - return $result; - } + $result = $class->before($this->request, $this->arguments[$alias] ?? null); - // Ignore an empty result - if (empty($result)) - { - continue; - } + if ($result instanceof RequestInterface) + { + $this->request = $result; + continue; + } - return $result; - } - elseif ($position === 'after') - { - $result = $class->after($this->request, $this->response); + // If the response object was sent back, + // then send it and quit. + if ($result instanceof ResponseInterface) + { + // short circuit - bypass any other filters + return $result; + } + + // Ignore an empty result + if (empty($result)) + { + continue; + } - if ($result instanceof ResponseInterface) + return $result; + } + elseif ($position === 'after') { - $this->response = $result; - continue; + $result = $class->after($this->request, $this->response); + + if ($result instanceof ResponseInterface) + { + $this->response = $result; + continue; + } } } } diff --git a/system/Filters/Honeypot.php b/system/Filters/Honeypot.php index aea08c3c..a5807e42 100644 --- a/system/Filters/Honeypot.php +++ b/system/Filters/Honeypot.php @@ -38,10 +38,10 @@ namespace CodeIgniter\Filters; +use CodeIgniter\Honeypot\Exceptions\HoneypotException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Config\Services; -use CodeIgniter\Honeypot\Exceptions\HoneypotException; /** * Honeypot filter diff --git a/system/Format/JSONFormatter.php b/system/Format/JSONFormatter.php index 50caf393..823f3ca2 100644 --- a/system/Format/JSONFormatter.php +++ b/system/Format/JSONFormatter.php @@ -56,13 +56,13 @@ class JSONFormatter implements FormatterInterface */ public function format($data) { - $options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; + $options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR; $options = ENVIRONMENT === 'production' ? $options : $options | JSON_PRETTY_PRINT; $result = json_encode($data, $options, 512); - if (json_last_error() !== JSON_ERROR_NONE) + if ( ! in_array(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_RECURSION])) { throw FormatException::forInvalidJSON(json_last_error_msg()); } diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index c5506acc..d1a8e944 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -536,9 +536,7 @@ protected function applyMethod(string $method, array $curl_options): array // Have content? if ($size === null || $size > 0) { - $curl_options = $this->applyBody($curl_options); - - return $curl_options; + return $this->applyBody($curl_options); } if ($method === 'PUT' || $method === 'POST') diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 713312f9..910f7002 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -136,7 +136,7 @@ class ContentSecurityPolicy * * @var string */ - protected $reportURI = null; + protected $reportURI; /** * Used for security enforcement diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 0b363cdb..b7c04dfb 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -427,7 +427,7 @@ public function getPostGet($index = null, $filter = null, $flags = null) // Use $_POST directly here, since filter_has_var only // checks the initial POST data, not anything that might // have been added since. - return isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : $this->getGet($index, $filter, $flags); + return isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : (isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : $this->getPost()); } //-------------------------------------------------------------------- @@ -446,7 +446,7 @@ public function getGetPost($index = null, $filter = null, $flags = null) // Use $_GET directly here, since filter_has_var only // checks the initial GET data, not anything that might // have been added since. - return isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : $this->getPost($index, $filter, $flags); + return isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : (isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : $this->getGet()); } //-------------------------------------------------------------------- @@ -616,7 +616,6 @@ protected function detectURI(string $protocol, string $baseURL) $this->uri->setScheme(parse_url($baseURL, PHP_URL_SCHEME)); $this->uri->setHost(parse_url($baseURL, PHP_URL_HOST)); $this->uri->setPort(parse_url($baseURL, PHP_URL_PORT)); - $this->uri->resolveRelativeURI(parse_url($baseURL, PHP_URL_PATH)); // Ensure we have any query vars $this->uri->setQuery($_SERVER['QUERY_STRING'] ?? ''); @@ -722,7 +721,7 @@ protected function parseRequestURI(): string $query = $parts['query'] ?? ''; $uri = $parts['path'] ?? ''; - if (isset($_SERVER['SCRIPT_NAME'][0])) + if (isset($_SERVER['SCRIPT_NAME'][0]) && pathinfo($_SERVER['SCRIPT_NAME'], PATHINFO_EXTENSION) === 'php') { // strip the script name from the beginning of the URI if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) diff --git a/system/HTTP/Message.php b/system/HTTP/Message.php index 1afb1d52..6d7a5cce 100644 --- a/system/HTTP/Message.php +++ b/system/HTTP/Message.php @@ -316,7 +316,9 @@ public function appendHeader(string $name, string $value) { $orig_name = $this->getHeaderName($name); - $this->headers[$orig_name]->appendValue($value); + array_key_exists($orig_name, $this->headers) + ? $this->headers[$orig_name]->appendValue($value) + : $this->setHeader($name, $value); return $this; } diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index 6ac2d06b..74aa4706 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -179,7 +179,7 @@ public function encoding(array $supported = []): string */ public function language(array $supported): string { - return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language')); + return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language'), false, false, true); } //-------------------------------------------------------------------- @@ -198,10 +198,11 @@ public function language(array $supported): string * @param boolean $enforceTypes If TRUE, will compare media types and sub-types. * @param boolean $strictMatch If TRUE, will return empty string on no match. * If FALSE, will return the first supported element. + * @param boolean $matchLocales If TRUE, will match locale sub-types to a broad type (fr-FR = fr) * * @return string Best match */ - protected function getBestMatch(array $supported, string $header = null, bool $enforceTypes = false, bool $strictMatch = false): string + protected function getBestMatch(array $supported, string $header = null, bool $enforceTypes = false, bool $strictMatch = false, bool $matchLocales = false): string { if (empty($supported)) { @@ -232,7 +233,7 @@ protected function getBestMatch(array $supported, string $header = null, bool $e // If an acceptable value is supported, return it foreach ($supported as $available) { - if ($this->match($accept, $available, $enforceTypes)) + if ($this->match($accept, $available, $enforceTypes, $matchLocales)) { return $available; } @@ -337,12 +338,14 @@ public function parseHeader(string $header): array /** * Match-maker * - * @param array $acceptable - * @param string $supported - * @param boolean $enforceTypes + * @param array $acceptable + * @param string $supported + * @param boolean $enforceTypes + * @param boolean $matchLocales + * * @return boolean */ - protected function match(array $acceptable, string $supported, bool $enforceTypes = false): bool + protected function match(array $acceptable, string $supported, bool $enforceTypes = false, $matchLocales = false): bool { $supported = $this->parseHeader($supported); if (is_array($supported) && count($supported) === 1) @@ -363,6 +366,12 @@ protected function match(array $acceptable, string $supported, bool $enforceType return $this->matchTypes($acceptable, $supported); } + // Do we need to match locales against broader locales? + if ($matchLocales) + { + return $this->matchLocales($acceptable, $supported); + } + return false; } @@ -409,8 +418,14 @@ protected function matchParameters(array $acceptable, array $supported): bool */ public function matchTypes(array $acceptable, array $supported): bool { - list($aType, $aSubType) = explode('/', $acceptable['value']); - list($sType, $sSubType) = explode('/', $supported['value']); + [ + $aType, + $aSubType, + ] = explode('/', $acceptable['value']); + [ + $sType, + $sSubType, + ] = explode('/', $supported['value']); // If the types don't match, we're done. if ($aType !== $sType) @@ -429,4 +444,25 @@ public function matchTypes(array $acceptable, array $supported): bool } //-------------------------------------------------------------------- + + /** + * Will match locales against their broader pairs, so that fr-FR would + * match a supported localed of fr + * + * @param array $acceptable + * @param array $supported + * + * @return boolean + */ + public function matchLocales(array $acceptable, array $supported): bool + { + $aBroad = mb_strpos($acceptable['value'], '-') > 0 + ? mb_substr($acceptable['value'], 0, mb_strpos($acceptable['value'], '-')) + : $acceptable['value']; + $sBroad = mb_strpos($supported['value'], '-') > 0 + ? mb_substr($supported['value'], 0, mb_strpos($supported['value'], '-')) + : $supported['value']; + + return strtolower($aBroad) === strtolower($sBroad); + } } diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index a6d3c5ec..0d8320bb 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -40,10 +40,10 @@ namespace CodeIgniter\HTTP; -use Config\App; -use Config\Format; use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\Pager\PagerInterface; +use Config\App; +use Config\Format; /** * Representation of an outgoing, getServer-side response. @@ -986,6 +986,7 @@ public function deleteCookie(string $name = '', string $domain = '', string $pat $name = $prefix . $name; + $cookieHasFlag = false; foreach ($this->cookies as &$cookie) { if ($cookie['name'] === $name) @@ -1000,11 +1001,16 @@ public function deleteCookie(string $name = '', string $domain = '', string $pat } $cookie['value'] = ''; $cookie['expires'] = ''; - + $cookieHasFlag = true; break; } } + if (! $cookieHasFlag) + { + $this->setCookie($name, '', '', $domain, $path, $prefix); + } + return $this; } diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 001b0a96..09aa3cdc 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -566,7 +566,7 @@ public static function createURIString(string $scheme = null, string $authority $uri .= $authority; } - if ($path) + if ($path !== '') { $uri .= substr($uri, -1, 1) !== '/' ? '/' . ltrim($path, '/') : $path; } @@ -597,7 +597,12 @@ public function setAuthority(string $str) { $parts = parse_url($str); - if (empty($parts['host']) && ! empty($parts['path'])) + if (! isset($parts['path'])) + { + $parts['path'] = $this->getPath(); + } + + if (empty($parts['host']) && $parts['path'] !== '') { $parts['host'] = $parts['path']; unset($parts['path']); @@ -705,7 +710,9 @@ public function setPath(string $path) { $this->path = $this->filterPath($path); - $this->segments = explode('/', $this->path); + $tempPath = trim($this->path, '/'); + + $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); return $this; } @@ -721,7 +728,9 @@ public function refreshPath() { $this->path = $this->filterPath(implode('/', $this->segments)); - $this->segments = explode('/', $this->path); + $tempPath = trim($this->path, '/'); + + $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); return $this; } @@ -913,7 +922,7 @@ protected function applyParts(array $parts) { $this->user = $parts['user']; } - if (! empty($parts['path'])) + if (isset($parts['path']) && $parts['path'] !== '') { $this->path = $this->filterPath($parts['path']); } @@ -953,9 +962,11 @@ protected function applyParts(array $parts) } // Populate our segments array - if (! empty($parts['path'])) + if (isset($parts['path']) && $parts['path'] !== '') { - $this->segments = explode('/', trim($parts['path'], '/')); + $tempPath = trim($parts['path'], '/'); + + $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); } } @@ -1048,14 +1059,14 @@ public function resolveRelativeURI(string $uri) */ protected function mergePaths(URI $base, URI $reference): string { - if (! empty($base->getAuthority()) && empty($base->getPath())) + if (! empty($base->getAuthority()) && $base->getPath() === '') { return '/' . ltrim($reference->getPath(), '/ '); } $path = explode('/', $base->getPath()); - if (empty($path[0])) + if ($path[0] === '') { unset($path[0]); } @@ -1082,7 +1093,7 @@ protected function mergePaths(URI $base, URI $reference): string */ public function removeDotSegments(string $path): string { - if (empty($path) || $path === '/') + if ($path === '' || $path === '/') { return $path; } @@ -1091,7 +1102,7 @@ public function removeDotSegments(string $path): string $input = explode('/', $path); - if (empty($input[0])) + if ($input[0] === '') { unset($input[0]); $input = array_values($input); diff --git a/system/HTTP/UserAgent.php b/system/HTTP/UserAgent.php index 6e5b5965..c6873fd5 100644 --- a/system/HTTP/UserAgent.php +++ b/system/HTTP/UserAgent.php @@ -51,7 +51,7 @@ class UserAgent * * @var string */ - protected $agent = null; + protected $agent; /** * Flag for if the user-agent belongs to a browser diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php index 9c5d8f6e..c114df9f 100644 --- a/system/Helpers/array_helper.php +++ b/system/Helpers/array_helper.php @@ -79,7 +79,7 @@ function _array_search_dot(array $indexes, array $array) ? array_shift($indexes) : null; - if (empty($currentIndex) || (! isset($array[$currentIndex]) && $currentIndex !== '*')) + if ((empty($currentIndex) && intval($currentIndex) !== 0) || (! isset($array[$currentIndex]) && $currentIndex !== '*')) { return null; } @@ -90,7 +90,7 @@ function _array_search_dot(array $indexes, array $array) // If $array has more than 1 item, we have to loop over each. if (is_array($array)) { - foreach ($array as $key => $value) + foreach ($array as $value) { $answer = _array_search_dot($indexes, $value); diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index d85f644d..50ab3927 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -96,9 +96,8 @@ function get_cookie($index, bool $xssClean = false) $request = \Config\Services::request(); $filter = true === $xssClean ? FILTER_SANITIZE_STRING : null; - $cookie = $request->getCookie($prefix . $index, $filter); - return $cookie; + return $request->getCookie($prefix . $index, $filter); } } diff --git a/system/Helpers/date_helper.php b/system/Helpers/date_helper.php index 6cf362ad..1951d253 100644 --- a/system/Helpers/date_helper.php +++ b/system/Helpers/date_helper.php @@ -96,8 +96,7 @@ function timezone_select(string $class = '', string $default = '', int $what = \ $selected = ($timezone === $default) ? 'selected' : ''; $buffer .= "" . PHP_EOL; } - $buffer .= '' . PHP_EOL; - return $buffer; + return $buffer . ('' . PHP_EOL); } } diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php index 1ead233b..2556e86a 100644 --- a/system/Helpers/filesystem_helper.php +++ b/system/Helpers/filesystem_helper.php @@ -209,45 +209,54 @@ function delete_files(string $path, bool $del_dir = false, bool $htdocs = false, * Reads the specified directory and builds an array containing the filenames. * Any sub-folders contained within the specified path are read as well. * - * @param string $source_dir Path to source - * @param boolean $include_path Whether to include the path as part of the filename - * @param boolean $recursion Internal variable to determine recursion status - do not use in calls + * @param string $source_dir Path to source + * @param boolean|null $include_path Whether to include the path as part of the filename; false for no path, null for a relative path, true for full path + * @param boolean $hidden Whether to include hidden files (files beginning with a period) * * @return array */ - function get_filenames(string $source_dir, bool $include_path = false, bool $recursion = false): array + function get_filenames(string $source_dir, ?bool $include_path = false, bool $hidden = false): array { - static $fileData = []; + $files = []; + + $source_dir = realpath($source_dir) ?: $source_dir; + $source_dir = rtrim($source_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; try { - $fp = opendir($source_dir); - // reset the array and make sure $source_dir has a trailing slash on the initial call - if ($recursion === false) + foreach (new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($source_dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::SELF_FIRST + ) as $name => $object) { - $fileData = []; - $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - } + $basename = pathinfo($name, PATHINFO_BASENAME); - while (false !== ($file = readdir($fp))) - { - if (is_dir($source_dir . $file) && $file[0] !== '.') + if (! $hidden && $basename[0] === '.') { - get_filenames($source_dir . $file . DIRECTORY_SEPARATOR, $include_path, true); + continue; } - elseif ($file[0] !== '.') + elseif ($include_path === false) { - $fileData[] = ($include_path === true) ? $source_dir . $file : $file; + $files[] = $basename; + } + elseif (is_null($include_path)) + { + $files[] = str_replace($source_dir, '', $name); + } + else + { + $files[] = $name; } } - - closedir($fp); - return $fileData; } - catch (\Exception $fe) + catch (\Throwable $e) { return []; } + + sort($files); + + return $files; } } diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index 82c809ce..e505e65b 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -69,6 +69,12 @@ function form_open(string $action = '', $attributes = [], array $hidden = []): s } // If an action is not a full URL then turn it into one elseif (strpos($action, '://') === false) { + // If an action has {locale} + if (strpos($action, '{locale}') !== false) + { + $action = str_replace('{locale}', Services::request()->getLocale(), $action); + } + $action = site_url($action); } @@ -180,7 +186,7 @@ function form_hidden($name, $value = '', bool $recursing = false): string if (! is_array($value)) { - $form .= '\n"; + $form .= '\n"; } else { @@ -408,7 +414,7 @@ function form_dropdown($data = '', $options = [], $selected = [], $extra = ''): { $sel = in_array($optgroup_key, $selected) ? ' selected="selected"' : ''; $form .= '\n"; + . $optgroup_val . "\n"; } $form .= "\n"; } @@ -416,7 +422,7 @@ function form_dropdown($data = '', $options = [], $selected = [], $extra = ''): { $form .= '\n"; + . $val . "\n"; } } @@ -645,9 +651,7 @@ function form_datalist(string $name, string $value, array $options): string $out .= "