diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b2f56755c3..9786833e1d2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## [v4.3.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.2) (2023-02-18) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.1...v4.3.2) + +### Breaking Changes + +* fix: base_url() removes trailing slash in baseURL by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7200 + +### Fixed Bugs + +* docs: fix incorrect sample code in view_parser by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7103 +* docs: add missing items in upgrade_430.rst/v4.3.0.rst by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7119 +* fix: remove `All` from `Options All -Indexes` in .htaccess by @sba in https://github.com/codeigniter4/CodeIgniter4/pull/7093 +* fix: bug on stuck content-type header in Feature Testing by @baycik in https://github.com/codeigniter4/CodeIgniter4/pull/7112 +* fix: ordering `Validation` show error by call `setRule()` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/7149 +* fix: remove parameter $relative in `uri_string()` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7135 +* fix: [QueryBuilder] where() generates incorrect SQL when using RawSql by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/7147 +* fix: [QueryBuilder] RawSql passed to set() disappears without error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7148 +* fix: [Parser] local_currency causes "Passing null to parameter" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7157 +* fix: [Parser] `!` does not work if delimiters are changed by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7142 +* fix: Throttler token time calculation by @rumpfc in https://github.com/codeigniter4/CodeIgniter4/pull/7160 +* fix: [QueryBuilder] getOperatorFromWhereKey() misses EXISTS, BETWEEN by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7155 +* docs: Correcting documentation mistakes in upgrading from one version to another by @objecttothis in https://github.com/codeigniter4/CodeIgniter4/pull/7191 +* fix: [Session] `Redis` connect to protocol `TLS` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/7187 +* fix: Autoloader may not add Composer package's namespaces by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7193 +* fix: add try/catch to real_path() in clean_path() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7195 +* fix: cannot create shared View instance when using debugbar by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7172 +* fix: RouteCollection::getRegisteredControllers() may not return all controllers by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7174 +* fix: `spark routes` shows incorrect hostname routes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7176 +* docs: add missing composer.json in Mandatory File Changes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7170 +* fix: stack trace displayed when Exception handler runs out of memory is useless by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7212 +* fix: support for display of error message using wildcard (*) by @sammyskills in https://github.com/codeigniter4/CodeIgniter4/pull/7226 +* fix: routing behavior when $uriProtocol is QUERY_STRING by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7199 +* fix: site_url() does not use alt Config by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7215 +* docs: add missing @method having() in Model by @paul45 in https://github.com/codeigniter4/CodeIgniter4/pull/7258 + +### Enhancements + +* add `application/vnd.microsoft.portable-executable` and `application/x-dosexec` by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/7144 + +### Refactoring + +* refactor: add PHPDoc types in RouteCollection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7129 +* refactor: URI::parseStr() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7154 +* refactor: error_exception.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7171 +* [Rector] Apply Rector to app/Views by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/7169 +* refactor: Update PHPDoc Common::config by @maniaba in https://github.com/codeigniter4/CodeIgniter4/pull/7224 + ## [v4.3.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.1) (2023-01-14) [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.0...v4.3.1) @@ -125,424 +172,4 @@ * refactor: remove Workaround for Faker deprecation errors in PHP 8.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6987 * refactor: to fix psalm error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6999 -## [v4.2.12](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.12) (2023-01-09) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.11...v4.2.12) - -### Fixed Bugs -* docs: fix request.rst by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7014 -* fix: `link_tag()` missing `type="application/rss+xml"` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7022 -* fix: Request::getIPaddress() causes error on CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7030 -* docs: fix upgrade_database.rst by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7036 -* fix: `spark migrate:status` shows incorrect filename when format is `Y_m_d_His_` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7038 -* fix: Model::save() object when useAutoIncrement is disabled by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7042 -* fix: define of STDOUT in CLI init() method by @jozefrebjak in https://github.com/codeigniter4/CodeIgniter4/pull/7052 -* fix: change `getFile()` function of \CodeIgniter\Events\Events to static. by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/7046 -* fix: [Email] add fallback to use gethostname() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7053 -* Fixing bug with legacy autoRoute when testing by @baycik in https://github.com/codeigniter4/CodeIgniter4/pull/7060 - -### Refactoring -* refactor: RequestTrait by rector by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7006 -* refactor: update sass output by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7026 - -## [v4.2.11](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.11) (2022-12-21) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.10...v4.2.11) - -### SECURITY -* *Attackers may spoof IP address when using proxy* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-ghw3-5qvm-3mqc) for more information. -* *Potential Session Handlers Vulnerability* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-6cq5-8cj7-g558) for more information. - -### Fixed Bugs -* fix: Request::getIPAddress() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6820 -* fix: Model cannot insert when $useAutoIncrement is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6827 -* fix: View Parser regexp does not support UTF-8 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6835 -* Handle key generation when key is not present in .env by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6839 -* Fix: Controller Test withBody() by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6843 -* fix: body assigned via options array in CURLRequest class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6854 -* Fix CreateDatabase leaving altered database config in connection by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6856 -* fix: cast to string all values except arrays in Header class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6862 -* add missing @method Query grouping in Model by @paul45 in https://github.com/codeigniter4/CodeIgniter4/pull/6874 -* fix: `composer update` might cause error "Failed to open directory" by @LeMyst in https://github.com/codeigniter4/CodeIgniter4/pull/6833 -* fix: required PHP extentions by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6897 -* fix: Use Services for the FeatureTestTrait request. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/6966 -* fix: FileLocator::locateFile() bug with a similar namespace name by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6964 -* fix: socket connection in RedisHandler class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6972 -* fix: `spark namespaces` cannot show a namespace with mutilple paths by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6977 -* fix: Undefined constant "CodeIgniter\Debug\VENDORPATH" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6985 -* fix: large HTTP input crashes framework by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6984 -* fix: empty paths for `rewrite.php` by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/6991 -* fix: `PHPStan` $cols not defined in `CLI` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6994 -* Fix MigrationRunnerTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6855 -* fix: turn off `Xdebug` note when running phpstan by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6851 -* Fix ShowTableInfoTest to pass on Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6853 -* Fix MigrateStatusTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6866 -* Fix ShowTableInfoTest when migration records are numerous by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6868 -* Fix CreateDatabaseTest to not leave database by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6867 -* Fix coverage merge warning by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6885 -* fix: replace tabs to spaces by @zl59503020 in https://github.com/codeigniter4/CodeIgniter4/pull/6898 -* fix: slack links by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6907 -* Fix typo in database/queries.rst by @philFernandez in https://github.com/codeigniter4/CodeIgniter4/pull/6920 -* Fix testInsertWithSetAndEscape to make not time dependent by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6974 -* fix: remove unnecessary global variables in rewrite.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6973 - -## [v4.2.10](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.10) (2022-11-05) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.9...v4.2.10) - -### Fixed Bugs -* docs: fix PHPDoc types in Session by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6796 -* fix: output "0" at the end of toolbar js when Kint::$enabled_mode is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6809 - -### Refactoring -* Refactor assertHeaderEmitted and assertHeaderNotEmitted by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6806 -* fix: variable types for PHPStan 1.9.0 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6810 - -## [v4.2.9](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.9) (2022-10-30) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.8...v4.2.9) - -**Hotfix release to fix PHPUnit errors (see https://github.com/codeigniter4/CodeIgniter4/pull/6794)** - -## [v4.2.8](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.8) (2022-10-30) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.7...v4.2.8) - -### Fixed Bugs -* Fix DotEnv class turning `export` to empty string by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6625 -* Remove unneeded `$logger` property in `Session` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6647 -* fix: Add missing CLIRequest::getCookie() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6646 -* fix: routes registration bug by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6644 -* Bug: showError in CLI/BaseCommand use hardcoded error view path by @fpoy in https://github.com/codeigniter4/CodeIgniter4/pull/6657 -* fix: getGetPost() and getPostGet() when index is null by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6675 -* fix: add missing methods to BaseConnection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6712 -* fix: bug that esc() accepts invalid context '0' by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6722 -* fix: [Postgres] reset binds when replace() method is called multiple times in the context by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6728 -* fix: [SQLSRV] _getResult() return object for preparedQuery class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6718 -* Fix error handler callback by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6724 -* bug: Supply mixin for TestResponse by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6756 -* fix: CodeIgniter::run() doesn't respect $returnResponse by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6737 -* Bug: ResponseTest::testSetLastModifiedWithDateTimeObject depends on time by @fpoy in https://github.com/codeigniter4/CodeIgniter4/pull/6683 -* fix: workaround for Faker deprecation errors in PHP 8.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6758 -* Add .gitattributes to framework by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/6774 -* Delete admin/module directory by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6775 - -## [v4.2.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.7) (2022-10-06) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.6...v4.2.7) - -### SECURITY -* *Secure or HttpOnly flag set in Config\Cookie is not reflected in Cookies issued* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-745p-r637-7vvp) for more information. - -### Breaking Changes -* fix: make Time::__toString() database-compatible on any locale by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6461 -* fix: set_cookie() does not use Config\Cookie values by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6544 -* fix: `required_without` rule logic in `Validation` class. by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6589 - -### Fixed Bugs -* fix: typos in messages in Language/en/Email.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6517 -* fix: table attribute cannot applied on td element by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6538 -* add: set up "script_name" to handle every request by index.php file. by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6522 -* fix: CSP autoNonce = false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6570 -* fix: inconsistent new line view in `date_helper` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6582 -* fix: safe_mailto() does not work with CSP by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6604 -* fix: script_tag() does not work with CSP by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6611 -* fix: `$cleanValidationRules` does not work in Model updates by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6588 -* Fixed a bug that URLs with trailing newlines do not become invalid in validation. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/6618 -* fix: missing `valid_json` in Validation Language by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6624 -* fix: default values for Session Redis Handler by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6614 - -### Enhancements -* Update coding-standards version by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6537 -* chore: update ThirdParty Kint to 4.2.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6583 - -### Refactoring -* Refactor: CodeIgniter::generateCacheName() by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6498 -* refactor: replace `global $app` with Services by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6524 -* refactor: small refactoring in view() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6546 -* refactor: replace utf8_encode() with mb_convert_encoding() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6564 -* refactor: make $precision int in View Filter round by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6566 - -## [v4.2.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.6) (2022-09-04) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.5...v4.2.6) - -### Fixed Bugs -* fix: AssertionError occurs when using Validation in CLI by @daycry in https://github.com/codeigniter4/CodeIgniter4/pull/6452 -* fix: [Validation] JSON data may cause "Array to string conversion" error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6467 -* Fix fatal error gets turned to `0` severity on shutdown handler by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6472 -* Fix redis cache increment/decrement methods by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6473 -* Fix broken caching system when array of allowed parameters used by @JavaDeveloperKiev in https://github.com/codeigniter4/CodeIgniter4/pull/6475 -* fix: Strict Validation Rules greater_than/less_than by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6492 - -### Refactoring -* refactor: fix PHPStan errors by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6470 -* Bump `friendsofphp/php-cs-fixer` to `~3.11.0` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6471 -* Fix overlooked coding style violations by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6491 - -## [v4.2.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.5) (2022-08-28) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.4...v4.2.5) - -### Breaking Changes -* Add $cached param to BaseConnection::tableExists() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6364 -* Fix validation custom error asterisk field by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6378 - -### Fixed Bugs -* fix: Email class may not log an error when it fails to send by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6362 -* fix: Response::download() causes TypeError by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6361 -* fix: command usages by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6402 -* Fix: The subquery adds a prefix for the table alias. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6390 -* Fix Sqlite Table::createTable() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6396 -* docs: add missing `@method` `groupBy()` in Model by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6433 -* fix: CLIRequest Erros in CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6421 -* fix: Call to undefined method CodeIgniter\HTTP\CLIRequest::getLocale() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6442 - -### Enhancements -* chore: update Kint to 4.2.0 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6436 - -### Refactoring -* refactor: add test for DownloadResponse by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6375 -* refactor: ValidationTest by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6382 -* refactor: remove unused `_parent_name` in BaseBuilder::objectToArray() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6427 -* Remove unneeded abstract `handle()` method by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6434 - -## [v4.2.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.4) (2022-08-13) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.3...v4.2.4) - -**Hotfix release to fix download errors (see https://github.com/codeigniter4/CodeIgniter4/pull/6361)** - -## [v4.2.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.3) (2022-08-06) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.2...v4.2.3) - -* SECURITY: Improve CSRF protection (for Shield CSRF security fix) - -## [v4.2.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.2) (2022-08-05) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.1...v4.2.2) - -### Breaking Changes -* fix: when running on CLI, two Request objects were used in the system by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6089 -* fix: Builder insert()/update() does not accept an object by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6216 -* fix: create table if not exists when indexes already exist by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6249 -* fix: page cache saves Response data before running after filters by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6282 -* fix: random_string('crypto') may return string less than $len or ErrorException by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6334 - -### Fixed Bugs -* Fixed: BaseBuilder increment/decrement do not reset state after a query by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6146 -* fix: SQLite3\Connection\getIndexData() error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6152 -* fix: `is_image` causes PHP 8.1 deprecated error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6157 -* fix: prepared query is executed when using QueryBuilder by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6164 -* fix: Time::getAge() calculation by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6159 -* fix: Session cookies are sent twice with Ajax by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6167 -* fix: QueryBuilder breaks select when escape is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5118 -* fix: PHPDoc return type in ControllerTestTrait methods by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6168 -* fix: `$routes->group('/', ...)` creates the route `foo///bar` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6186 -* fix: use lang('HTTP.pageNotFound') on production 404 page by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6202 -* fix: BaseConnection may create dynamic property by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6198 -* fix: Email SMTP may throw Uncaught ErrorException by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6184 -* fix: CSP reportOnly behavior by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6201 -* fix: lang() causes Error on CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6209 -* fix: multiple pagers with models do not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6211 -* fix: tweak empty line output of `spark db:table` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6215 -* fix: custom validation error is cleared when calling setRule() twice by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6241 -* Fix: Validation of fields with a leading asterisk. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6243 -* fix: Call to undefined method CodeIgniter\Pager\PagerRenderer::getDetails() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6251 -* fix: exceptionHandler may cause HTTPException: Unknown HTTP status code by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6254 -* fix: invalid INSERT/DELETE query when Query Builder uses table alias by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5376 -* fix: Add db port entry into env file. by @nalakapws in https://github.com/codeigniter4/CodeIgniter4/pull/6250 -* fix: update `.gitattributes` by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/6256 -* fix: format_number() can't be used on CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6263 -* fix: add parameter checking for max_size by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6261 -* fix: route name is not displayed in Exception message by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6269 -* fix: `spark routes` shows 404 error when using regex by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6279 -* fix: Entity::hasChanged() returns wrong result to mapped property by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6285 -* fix: unable to add more than one file to FileCollection constructor by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6291 -* fix: Security::derandomize() may cause hex2bin() error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6292 -* fix: use getenv() instead of $_SERVER in detectEnvironment() by @fcosrno in https://github.com/codeigniter4/CodeIgniter4/pull/6257 -* fix: OCI8 uses deprecated Entity by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6323 -* fix: Parse error occurs before PHP version check by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6327 -* fix: 404 page might display Exception message in production environment by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6333 - -### Refactoring -* refactor: replace $e->getMessage() with $e in log_message() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6182 -* refactor: add CompleteDynamicPropertiesRector by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6187 -* refactor: debug toolbar by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6272 -* refactor: Exception exit code by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6286 -* chore: Remove Vagrant by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6314 -* refactor: CSRF protection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6320 - -## [v4.2.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.1) (2022-06-16) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.0...v4.2.1) - -### Breaking Changes -* Fix MIME guessing of extension from type by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6059 -* fix: get_cookie() may not use the cookie prefix by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6082 - -### Fixed Bugs -* fix: get_cookie() does not take Config\Cookie::$prefix by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6080 -* fix: session cookie name bug by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6091 -* fix: Session Handlers do not take Config\Cookie by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6081 -* fix: reverse routing does not work with full classname starting with `\` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6104 -* fix: insert error message in QueryBuilder by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6108 -* fix: `spark routes` shows "ERROR: 404" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6098 -* fix: Time::setTestNow() does not work with fa Locale by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6116 -* fix: `migrate --all` causes `Class "SQLite3" not found` error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6117 -* fix: event DBQuery is not fired on failed query when DBDebug is true by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6127 -* fix: `Time::humanize()` causes error with ar locale by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6120 -* Fix decorators by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/6090 -* Fix lost error message by test when after testInsertResultFail. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/6113 -* test: fix forgetting to restore DBDebug value by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6115 - -### Refactoring -* Apply AutoRouterImproved::translateURIDashes() by @pjsde in https://github.com/codeigniter4/CodeIgniter4/pull/6084 -* Remove useless catch by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6095 -* Move preload.php example to starter app by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/6088 -* style: compile sass by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6099 - -## [v4.2.0](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.0) (2022-06-03) -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.9...v4.2.0) - -### Breaking Changes -* Validation: support placeholders for anything by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5545 -* Fix: Validation. Error key for field with asterisk by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5609 -* Improve exception logging by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5684 -* fix: spark can't use options on PHP 7.4 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5836 -* fix: [Autoloader] Composer classmap usage by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5850 -* fix: using multiple CLI::color() in CLI::write() outputs strings with wrong color by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5893 -* refactor: [Router] extract a class for auto-routing by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5877 -* feat: Debugbar request microtime by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5958 -* refactor: `system/bootstrap.php` only loads files and registers autoloader by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5972 -* fix: `dot_array_search()` unexpected behavior by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5940 -* feat: QueryBuilder join() raw SQL string support by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5875 -* fix: change BaseService::reset() $initAutoloader to true by default by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6020 - -### Fixed Bugs -* chore: update admin/framework/composer.json Kint by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5531 -* fix: BaseConnection::getConnectDuration() number_format(): Passing null to parameter by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5536 -* Fix: Debug toolbar selectors by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5544 -* Fix: Toolbar. ciDebugBar.showTab() context. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5554 -* Refactor Database Collector display by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5553 -* fix: add missing Migration lang item by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5557 -* feat: add Validation Strict Rules by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5445 -* fix: `Time::createFromTimestamp()` sets incorrect time when specifying timezone by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/5588 -* fix: Entity's isset() and unset() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5497 -* Fix: Deletion timestamp of the Model is updated when a record that has been soft-deleted is deleted again by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5578 -* Fix: Added alias escaping in subquery by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5601 -* fix: spark migrate:status does not show status with different namespaces by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5605 -* BaseService - Use lowercase key in resetSingle by @najdanovicivan in https://github.com/codeigniter4/CodeIgniter4/pull/5596 -* Fix `array_flatten_with_dots` ignores empty array values by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5606 -* fix: debug toolbar Routes Params output by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5619 -* fix: DownloadResponse memory leak by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5623 -* fix: spark does not show Exception by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5638 -* fix: Config CSRF $redirect does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5665 -* fix: do not call header() if headers have already been sent by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5680 -* fix: $routes->setDefaultMethod() does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5682 -* fix: debug toolbar vars response headers includes request headers by @zl59503020 in https://github.com/codeigniter4/CodeIgniter4/pull/5701 -* fix: 404 override controller does not output Response object body by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5703 -* fix: auto routes incorrectly display route filters with GET method by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5712 -* fix: Model::paginate() missing argument $group by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5699 -* Fix options are not passed to Command $params by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5206 -* fix: forceGlobalSecureRequests break URI schemes other than HTTP by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5730 -* fix: TypeError when `$tokenRandomize = true` and no token posted by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5742 -* fix: $builder->ignore()->insertBatch() only ignores on first iteration by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5672 -* fix: app/Config/Routes.php is loaded twice on Windows by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5780 -* fix: table name is double prefixed when LIKE clause by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5778 -* fix: Publisher $restrictions regex to FCPATH by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5793 -* fix: Timer::getElapsedTime() returns incorrect value by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5798 -* bug: Publisher $restrictions regex typo by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/5800 -* fix: [Validation] valid_date ErrorException when the field is not sent by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5804 -* fix: [Pager] can't get correct current page from segment by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5803 -* fix: bug that allows dynamic controllers to be used by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5814 -* config: remove App\ and Config\ in autoload.psr-4 in app starter composer.json by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5824 -* fix: failover's DBPrefix not working by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5816 -* fix: Validation returns incorrect errors after Redirect with Input by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5844 -* feat: [Parser] add configs to change conditional delimiters by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5842 -* fix: Commands::discoverCommands() loads incorrect classname by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5849 -* fix: Publisher::discover() loads incorrect classname by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5858 -* fix: validation errors in Model are not cleared when running validation again by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5861 -* fix: Parser fails with `({variable})` in loop by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5840 -* fix: [BaseConfig] string value is set from environment variable even if it should be int/float by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5779 -* fix: add Escaper Exception classes in $coreClassmap by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5891 -* fix: Composer PSR-4 overwrites Config\Autoload::$psr4 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5902 -* fix: Reverse Routing does not take into account the default namespace by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5936 -* fix: [Validation] Fields with an asterisk throws exception by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5938 -* fix: GDHandler::convert() does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5969 -* fix: Images\Handlers\GDHandler Implicit conversion from float to int loses precision by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5965 -* fix: GDHandler::save() removes transparency by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5971 -* fix: route limit to subdomains does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5961 -* fix: Model::_call() static analysis by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5970 -* fix: invalid css in error_404.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5978 -* Fix: Route placeholder (:any) with {locale} by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6003 -* Changing the subquery builder for the Oracle by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5999 -* fix: CURLRequest request body is not reset on the next request by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6014 -* Bug: The SQLSRV driver ignores the port value from the config. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6036 -* fix: `set_radio()` not working as expected by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6037 -* fix: add config for SQLite3 Foreign Keys by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6050 -* fix: Ignore non-HTML responses in storePreviousURL by @tearoom6 in https://github.com/codeigniter4/CodeIgniter4/pull/6012 -* fix: SQLite3\Table::copyData() does not escape column names by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6055 -* Fix `slash_item()` erroring when property fetched does not exist on `Config\App` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6058 - -### New Features -* Feature Add Oracle driver by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/2487 -* feat: new improved auto router by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5889 -* feat: new improved auto router `spark routes` command by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5953 -* feat: `db:table` command by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5979 - -### Enhancements -* feat: CSP enhancements by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5516 -* Feature: Subqueries in the FROM section by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5510 -* Added new View Decorators. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/5567 -* feat: auto routes listing by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5590 -* Feature: "spark routes" command shows routes with closure. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5651 -* feat: `spark routes` shows filters by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5628 -* Allow calling getQuery() multiple times, and other improvements by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5127 -* feat: add Controller::validateData() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5639 -* feat: can add route handler as callable by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5713 -* Checking if the subquery uses the same object as the main query by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5743 -* Feature: Subquery for SELECT by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5736 -* Extend Validation from BaseConfig so Registrars can add rules. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/5789 -* config: add mime type for webp by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5838 -* feat: add `$includeDir` option to `get_filenames()` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5862 -* feat: throws exception when controller name in routes contains `/` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5885 -* [PHPStan] Prepare for PHPStan 1.6.x-dev by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5876 -* [Rector] Add back SimplifyUselessVariableRector by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5911 -* Redirecting Routes. Placeholders. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5916 -* script_tag(): cosmetic for value-less attributes by @xlii-chl in https://github.com/codeigniter4/CodeIgniter4/pull/5884 -* feat: QueryBuilder raw SQL string support by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5817 -* improve Router Exception message by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5984 -* feat: DBForge::addField() `default` value raw SQL string support by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5957 -* Add sample file for preloading by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5974 -* Feature. QueryBuilder. Query union. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6015 -* feat: `getFieldData()` returns nullable data on PostgreSQL by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5981 - -### Refactoring -* refactor: add Factories::models() to suppress PHPStan error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5358 -* Fixed style for PHP7.4 by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5581 -* Fix Autoloader::initialize() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5592 -* refactor: CURLRequest and the slow tests by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5593 -* Refactor `if_exist` validation with dot notation by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5607 -* refactor: small changes in Filters and Router by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5627 -* refactor: replace deprecated `getFilterForRoute()` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5624 -* refactor: make BaseController abstract by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5647 -* refactor: move logic to prevent access to initController by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5648 -* refactor: remove migrations routes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5652 -* refactor: update Kint CSP nonce by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5657 -* Deprecate object implementations of `clean_path()` function by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5681 -* refactor: Session does not use cookies() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5656 -* refactor: replace deprecated Response::getReason() with getReasonPhrase() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5700 -* refactor: isCLI() in CLIRequest and IncomingRequest by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5653 -* refactor: CodeIgniter has context by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5650 -* Forge use statement by @mostafakhudair in https://github.com/codeigniter4/CodeIgniter4/pull/5729 -* refactor: remove `&` before $db by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5726 -* refactor: remove unneeded `&` references in ContentSecurityPolicy.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5734 -* Nonce replacement optimization. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5733 -* [Rector] Clean up skip config and re-run Rector by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5813 -* refactor: DB Session Handler by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5696 -* Rename `Abstact` to `Abstract` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5833 -* refactor: extract RedirectResponse::withErrors() method by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5860 -* Optimizing the RouteCollection::getRoutes() method by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5918 -* refactor: add strtolower() to Request::getMethod() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5963 -* refactor: remove `$_SERVER['HTTP_HOST']` in RouteCollection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5962 -* refactor: deprecate const `EVENT_PRIORITY_*` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6000 -* fix: replace EVENT_PRIORITY_NORMAL with Events::PRIORITY_NORMAL by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6005 -* Router class optimization. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6004 -* Prefer `is_file()` by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6025 -* refactor: use get_filenames() 4th param in FileLocator by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6026 -* refactor: use get_filenames() 4th param by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6031 -* refactor: CodeIgniter $context check by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6047 -* Small change to improve code reading by @valmorflores in https://github.com/codeigniter4/CodeIgniter4/pull/6051 -* refactor: remove `CodeIgniter\Services` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6053 - -See [CHANGELOG_4.1.md](./changelogs/CHANGELOG_4.1.md) +See [CHANGELOG_4.2.md](./changelogs/CHANGELOG_4.2.md) diff --git a/admin/framework/.github/workflows/close-pull-request.yml b/admin/framework/.github/workflows/close-pull-request.yml index 4e01f30f5138..96675f69e878 100644 --- a/admin/framework/.github/workflows/close-pull-request.yml +++ b/admin/framework/.github/workflows/close-pull-request.yml @@ -2,17 +2,23 @@ name: Close Pull Request on: pull_request_target: - types: [opened] + types: [opened, reopened] + +permissions: + pull-requests: write jobs: main: runs-on: ubuntu-latest steps: - name: Close PR with nice message - uses: superbrothers/close-pull-request@v3 - with: - comment: > + run: gh pr close ${{ env.ISSUE }} -c "${{ env.COMMENT }}" + working-directory: ${{ github.workspace }} + env: + COMMENT: > Thank you for your pull request. However, you have submitted your PR on a read-only - split of `codeigniter4/CodeIgniter4`. This repository, unfortunately, does + split of codeigniter4/CodeIgniter4. This repository, unfortunately, does not accept PRs. Please submit your PR at https://github.com/codeigniter4/CodeIgniter4 repository.

Thank you. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE: ${{ github.event.pull_request.html_url }} diff --git a/admin/framework/composer.json b/admin/framework/composer.json index f4f974c6d635..2f3022a92b2b 100644 --- a/admin/framework/composer.json +++ b/admin/framework/composer.json @@ -13,7 +13,7 @@ "psr/log": "^1.1" }, "require-dev": { - "kint-php/kint": "^5.0.1", + "kint-php/kint": "^5.0.3", "codeigniter/coding-standard": "^1.5", "fakerphp/faker": "^1.9", "friendsofphp/php-cs-fixer": "3.13.0", @@ -42,6 +42,11 @@ "ext-fileinfo": "Improves mime type detection for files", "ext-readline": "Improves CLI::input() usability" }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, "autoload": { "psr-4": { "CodeIgniter\\": "system/" @@ -51,9 +56,6 @@ ] }, "scripts": { - "post-update-cmd": [ - "CodeIgniter\\ComposerScripts::postUpdate" - ], "test": "phpunit" }, "support": { diff --git a/admin/starter/.github/workflows/close-pull-request.yml b/admin/starter/.github/workflows/close-pull-request.yml index 4e01f30f5138..96675f69e878 100644 --- a/admin/starter/.github/workflows/close-pull-request.yml +++ b/admin/starter/.github/workflows/close-pull-request.yml @@ -2,17 +2,23 @@ name: Close Pull Request on: pull_request_target: - types: [opened] + types: [opened, reopened] + +permissions: + pull-requests: write jobs: main: runs-on: ubuntu-latest steps: - name: Close PR with nice message - uses: superbrothers/close-pull-request@v3 - with: - comment: > + run: gh pr close ${{ env.ISSUE }} -c "${{ env.COMMENT }}" + working-directory: ${{ github.workspace }} + env: + COMMENT: > Thank you for your pull request. However, you have submitted your PR on a read-only - split of `codeigniter4/CodeIgniter4`. This repository, unfortunately, does + split of codeigniter4/CodeIgniter4. This repository, unfortunately, does not accept PRs. Please submit your PR at https://github.com/codeigniter4/CodeIgniter4 repository.

Thank you. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE: ${{ github.event.pull_request.html_url }} diff --git a/admin/starter/composer.json b/admin/starter/composer.json index e52218478b04..cec23ba1987e 100644 --- a/admin/starter/composer.json +++ b/admin/starter/composer.json @@ -13,8 +13,10 @@ "mikey179/vfsstream": "^1.6", "phpunit/phpunit": "^9.1" }, - "suggest": { - "ext-fileinfo": "Improves mime type detection for files" + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true }, "autoload": { "exclude-from-classmap": [ diff --git a/admin/userguide/.github/workflows/close-pull-request.yml b/admin/userguide/.github/workflows/close-pull-request.yml index 4e01f30f5138..96675f69e878 100644 --- a/admin/userguide/.github/workflows/close-pull-request.yml +++ b/admin/userguide/.github/workflows/close-pull-request.yml @@ -2,17 +2,23 @@ name: Close Pull Request on: pull_request_target: - types: [opened] + types: [opened, reopened] + +permissions: + pull-requests: write jobs: main: runs-on: ubuntu-latest steps: - name: Close PR with nice message - uses: superbrothers/close-pull-request@v3 - with: - comment: > + run: gh pr close ${{ env.ISSUE }} -c "${{ env.COMMENT }}" + working-directory: ${{ github.workspace }} + env: + COMMENT: > Thank you for your pull request. However, you have submitted your PR on a read-only - split of `codeigniter4/CodeIgniter4`. This repository, unfortunately, does + split of codeigniter4/CodeIgniter4. This repository, unfortunately, does not accept PRs. Please submit your PR at https://github.com/codeigniter4/CodeIgniter4 repository.

Thank you. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE: ${{ github.event.pull_request.html_url }} diff --git a/app/Config/App.php b/app/Config/App.php index 0a23462b612e..f598e324152a 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -12,15 +12,10 @@ class App extends BaseConfig * Base Site URL * -------------------------------------------------------------------------- * - * URL to your CodeIgniter root. Typically this will be your base URL, + * URL to your CodeIgniter root. Typically, this will be your base URL, * WITH a trailing slash: * * http://example.com/ - * - * If this is not set then CodeIgniter will try guess the protocol, domain - * and path to your installation. However, you should always configure this - * explicitly and never rely on auto-guessing, especially in production - * environments. */ public string $baseURL = 'http://localhost:8080/'; diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index 6b06c4e3a3f5..99d28e5f8f86 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -53,6 +53,8 @@ class Mimes 'lzh' => 'application/octet-stream', 'exe' => [ 'application/octet-stream', + 'application/vnd.microsoft.portable-executable', + 'application/x-dosexec', 'application/x-msdownload', ], 'class' => 'application/octet-stream', diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php index ae46d3056e89..f311d910c0fa 100644 --- a/app/Views/errors/html/error_exception.php +++ b/app/Views/errors/html/error_exception.php @@ -1,4 +1,9 @@ - + @@ -77,16 +82,16 @@   —   - - ( arguments ) -
+ + ( arguments ) +
getParameters(); } @@ -189,7 +194,7 @@
- +
@@ -283,21 +288,11 @@ - - - - - - - - + + + + +
getName(), 'html') ?>getValueLine(), 'html') ?>
getName(), 'html') ?>getValueLine(), 'html') ?>
@@ -307,7 +302,7 @@ setStatusCode(http_response_code()); ?>
@@ -332,7 +327,7 @@ - $value) : ?> + getHeaderLine($name), 'html') ?> @@ -387,7 +382,7 @@

Displayed at — PHP: — - CodeIgniter: + CodeIgniter:

diff --git a/changelogs/CHANGELOG_4.0.md b/changelogs/CHANGELOG_4.0.md index 228eacaed594..ba2a9a85826d 100644 --- a/changelogs/CHANGELOG_4.0.md +++ b/changelogs/CHANGELOG_4.0.md @@ -1,4 +1,4 @@ -# Changelog +# Changelog 4.0 ## [v4.0.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.5) (2021-01-31) diff --git a/changelogs/CHANGELOG_4.1.md b/changelogs/CHANGELOG_4.1.md index 9cf4efa4cb80..0da26d272274 100644 --- a/changelogs/CHANGELOG_4.1.md +++ b/changelogs/CHANGELOG_4.1.md @@ -1,4 +1,4 @@ -# Changelog +# Changelog 4.1 ## [v4.1.9](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.9) (2022-02-25) diff --git a/changelogs/CHANGELOG_4.2.md b/changelogs/CHANGELOG_4.2.md new file mode 100644 index 000000000000..a6eae8a48637 --- /dev/null +++ b/changelogs/CHANGELOG_4.2.md @@ -0,0 +1,423 @@ +# Changelog 4.2 + +## [v4.2.12](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.12) (2023-01-09) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.11...v4.2.12) + +### Fixed Bugs +* docs: fix request.rst by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7014 +* fix: `link_tag()` missing `type="application/rss+xml"` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7022 +* fix: Request::getIPaddress() causes error on CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7030 +* docs: fix upgrade_database.rst by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7036 +* fix: `spark migrate:status` shows incorrect filename when format is `Y_m_d_His_` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7038 +* fix: Model::save() object when useAutoIncrement is disabled by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7042 +* fix: define of STDOUT in CLI init() method by @jozefrebjak in https://github.com/codeigniter4/CodeIgniter4/pull/7052 +* fix: change `getFile()` function of \CodeIgniter\Events\Events to static. by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/7046 +* fix: [Email] add fallback to use gethostname() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7053 +* Fixing bug with legacy autoRoute when testing by @baycik in https://github.com/codeigniter4/CodeIgniter4/pull/7060 + +### Refactoring +* refactor: RequestTrait by rector by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7006 +* refactor: update sass output by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7026 + +## [v4.2.11](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.11) (2022-12-21) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.10...v4.2.11) + +### SECURITY +* *Attackers may spoof IP address when using proxy* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-ghw3-5qvm-3mqc) for more information. +* *Potential Session Handlers Vulnerability* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-6cq5-8cj7-g558) for more information. + +### Fixed Bugs +* fix: Request::getIPAddress() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6820 +* fix: Model cannot insert when $useAutoIncrement is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6827 +* fix: View Parser regexp does not support UTF-8 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6835 +* Handle key generation when key is not present in .env by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6839 +* Fix: Controller Test withBody() by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6843 +* fix: body assigned via options array in CURLRequest class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6854 +* Fix CreateDatabase leaving altered database config in connection by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6856 +* fix: cast to string all values except arrays in Header class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6862 +* add missing @method Query grouping in Model by @paul45 in https://github.com/codeigniter4/CodeIgniter4/pull/6874 +* fix: `composer update` might cause error "Failed to open directory" by @LeMyst in https://github.com/codeigniter4/CodeIgniter4/pull/6833 +* fix: required PHP extentions by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6897 +* fix: Use Services for the FeatureTestTrait request. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/6966 +* fix: FileLocator::locateFile() bug with a similar namespace name by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6964 +* fix: socket connection in RedisHandler class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6972 +* fix: `spark namespaces` cannot show a namespace with mutilple paths by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6977 +* fix: Undefined constant "CodeIgniter\Debug\VENDORPATH" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6985 +* fix: large HTTP input crashes framework by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6984 +* fix: empty paths for `rewrite.php` by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/6991 +* fix: `PHPStan` $cols not defined in `CLI` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6994 +* Fix MigrationRunnerTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6855 +* fix: turn off `Xdebug` note when running phpstan by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6851 +* Fix ShowTableInfoTest to pass on Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6853 +* Fix MigrateStatusTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6866 +* Fix ShowTableInfoTest when migration records are numerous by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6868 +* Fix CreateDatabaseTest to not leave database by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6867 +* Fix coverage merge warning by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6885 +* fix: replace tabs to spaces by @zl59503020 in https://github.com/codeigniter4/CodeIgniter4/pull/6898 +* fix: slack links by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6907 +* Fix typo in database/queries.rst by @philFernandez in https://github.com/codeigniter4/CodeIgniter4/pull/6920 +* Fix testInsertWithSetAndEscape to make not time dependent by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6974 +* fix: remove unnecessary global variables in rewrite.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6973 + +## [v4.2.10](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.10) (2022-11-05) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.9...v4.2.10) + +### Fixed Bugs +* docs: fix PHPDoc types in Session by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6796 +* fix: output "0" at the end of toolbar js when Kint::$enabled_mode is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6809 + +### Refactoring +* Refactor assertHeaderEmitted and assertHeaderNotEmitted by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6806 +* fix: variable types for PHPStan 1.9.0 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6810 + +## [v4.2.9](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.9) (2022-10-30) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.8...v4.2.9) + +**Hotfix release to fix PHPUnit errors (see https://github.com/codeigniter4/CodeIgniter4/pull/6794)** + +## [v4.2.8](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.8) (2022-10-30) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.7...v4.2.8) + +### Fixed Bugs +* Fix DotEnv class turning `export` to empty string by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6625 +* Remove unneeded `$logger` property in `Session` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6647 +* fix: Add missing CLIRequest::getCookie() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6646 +* fix: routes registration bug by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6644 +* Bug: showError in CLI/BaseCommand use hardcoded error view path by @fpoy in https://github.com/codeigniter4/CodeIgniter4/pull/6657 +* fix: getGetPost() and getPostGet() when index is null by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6675 +* fix: add missing methods to BaseConnection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6712 +* fix: bug that esc() accepts invalid context '0' by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6722 +* fix: [Postgres] reset binds when replace() method is called multiple times in the context by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6728 +* fix: [SQLSRV] _getResult() return object for preparedQuery class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6718 +* Fix error handler callback by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6724 +* bug: Supply mixin for TestResponse by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6756 +* fix: CodeIgniter::run() doesn't respect $returnResponse by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6737 +* Bug: ResponseTest::testSetLastModifiedWithDateTimeObject depends on time by @fpoy in https://github.com/codeigniter4/CodeIgniter4/pull/6683 +* fix: workaround for Faker deprecation errors in PHP 8.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6758 +* Add .gitattributes to framework by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/6774 +* Delete admin/module directory by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6775 + +## [v4.2.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.7) (2022-10-06) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.6...v4.2.7) + +### SECURITY +* *Secure or HttpOnly flag set in Config\Cookie is not reflected in Cookies issued* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-745p-r637-7vvp) for more information. + +### Breaking Changes +* fix: make Time::__toString() database-compatible on any locale by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6461 +* fix: set_cookie() does not use Config\Cookie values by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6544 +* fix: `required_without` rule logic in `Validation` class. by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6589 + +### Fixed Bugs +* fix: typos in messages in Language/en/Email.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6517 +* fix: table attribute cannot applied on td element by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6538 +* add: set up "script_name" to handle every request by index.php file. by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6522 +* fix: CSP autoNonce = false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6570 +* fix: inconsistent new line view in `date_helper` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6582 +* fix: safe_mailto() does not work with CSP by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6604 +* fix: script_tag() does not work with CSP by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6611 +* fix: `$cleanValidationRules` does not work in Model updates by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6588 +* Fixed a bug that URLs with trailing newlines do not become invalid in validation. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/6618 +* fix: missing `valid_json` in Validation Language by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6624 +* fix: default values for Session Redis Handler by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6614 + +### Enhancements +* Update coding-standards version by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6537 +* chore: update ThirdParty Kint to 4.2.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6583 + +### Refactoring +* Refactor: CodeIgniter::generateCacheName() by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6498 +* refactor: replace `global $app` with Services by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6524 +* refactor: small refactoring in view() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6546 +* refactor: replace utf8_encode() with mb_convert_encoding() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6564 +* refactor: make $precision int in View Filter round by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6566 + +## [v4.2.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.6) (2022-09-04) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.5...v4.2.6) + +### Fixed Bugs +* fix: AssertionError occurs when using Validation in CLI by @daycry in https://github.com/codeigniter4/CodeIgniter4/pull/6452 +* fix: [Validation] JSON data may cause "Array to string conversion" error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6467 +* Fix fatal error gets turned to `0` severity on shutdown handler by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6472 +* Fix redis cache increment/decrement methods by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6473 +* Fix broken caching system when array of allowed parameters used by @JavaDeveloperKiev in https://github.com/codeigniter4/CodeIgniter4/pull/6475 +* fix: Strict Validation Rules greater_than/less_than by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6492 + +### Refactoring +* refactor: fix PHPStan errors by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6470 +* Bump `friendsofphp/php-cs-fixer` to `~3.11.0` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6471 +* Fix overlooked coding style violations by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6491 + +## [v4.2.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.5) (2022-08-28) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.4...v4.2.5) + +### Breaking Changes +* Add $cached param to BaseConnection::tableExists() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6364 +* Fix validation custom error asterisk field by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/6378 + +### Fixed Bugs +* fix: Email class may not log an error when it fails to send by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6362 +* fix: Response::download() causes TypeError by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6361 +* fix: command usages by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6402 +* Fix: The subquery adds a prefix for the table alias. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6390 +* Fix Sqlite Table::createTable() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6396 +* docs: add missing `@method` `groupBy()` in Model by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6433 +* fix: CLIRequest Erros in CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6421 +* fix: Call to undefined method CodeIgniter\HTTP\CLIRequest::getLocale() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6442 + +### Enhancements +* chore: update Kint to 4.2.0 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6436 + +### Refactoring +* refactor: add test for DownloadResponse by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6375 +* refactor: ValidationTest by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6382 +* refactor: remove unused `_parent_name` in BaseBuilder::objectToArray() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6427 +* Remove unneeded abstract `handle()` method by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6434 + +## [v4.2.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.4) (2022-08-13) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.3...v4.2.4) + +**Hotfix release to fix download errors (see https://github.com/codeigniter4/CodeIgniter4/pull/6361)** + +## [v4.2.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.3) (2022-08-06) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.2...v4.2.3) + +* SECURITY: Improve CSRF protection (for Shield CSRF security fix) + +## [v4.2.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.2) (2022-08-05) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.1...v4.2.2) + +### Breaking Changes +* fix: when running on CLI, two Request objects were used in the system by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6089 +* fix: Builder insert()/update() does not accept an object by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6216 +* fix: create table if not exists when indexes already exist by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6249 +* fix: page cache saves Response data before running after filters by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6282 +* fix: random_string('crypto') may return string less than $len or ErrorException by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6334 + +### Fixed Bugs +* Fixed: BaseBuilder increment/decrement do not reset state after a query by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6146 +* fix: SQLite3\Connection\getIndexData() error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6152 +* fix: `is_image` causes PHP 8.1 deprecated error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6157 +* fix: prepared query is executed when using QueryBuilder by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6164 +* fix: Time::getAge() calculation by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6159 +* fix: Session cookies are sent twice with Ajax by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6167 +* fix: QueryBuilder breaks select when escape is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5118 +* fix: PHPDoc return type in ControllerTestTrait methods by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6168 +* fix: `$routes->group('/', ...)` creates the route `foo///bar` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6186 +* fix: use lang('HTTP.pageNotFound') on production 404 page by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6202 +* fix: BaseConnection may create dynamic property by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6198 +* fix: Email SMTP may throw Uncaught ErrorException by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6184 +* fix: CSP reportOnly behavior by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6201 +* fix: lang() causes Error on CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6209 +* fix: multiple pagers with models do not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6211 +* fix: tweak empty line output of `spark db:table` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6215 +* fix: custom validation error is cleared when calling setRule() twice by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6241 +* Fix: Validation of fields with a leading asterisk. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6243 +* fix: Call to undefined method CodeIgniter\Pager\PagerRenderer::getDetails() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6251 +* fix: exceptionHandler may cause HTTPException: Unknown HTTP status code by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6254 +* fix: invalid INSERT/DELETE query when Query Builder uses table alias by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5376 +* fix: Add db port entry into env file. by @nalakapws in https://github.com/codeigniter4/CodeIgniter4/pull/6250 +* fix: update `.gitattributes` by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/6256 +* fix: format_number() can't be used on CLI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6263 +* fix: add parameter checking for max_size by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6261 +* fix: route name is not displayed in Exception message by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6269 +* fix: `spark routes` shows 404 error when using regex by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6279 +* fix: Entity::hasChanged() returns wrong result to mapped property by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6285 +* fix: unable to add more than one file to FileCollection constructor by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6291 +* fix: Security::derandomize() may cause hex2bin() error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6292 +* fix: use getenv() instead of $_SERVER in detectEnvironment() by @fcosrno in https://github.com/codeigniter4/CodeIgniter4/pull/6257 +* fix: OCI8 uses deprecated Entity by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6323 +* fix: Parse error occurs before PHP version check by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6327 +* fix: 404 page might display Exception message in production environment by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6333 + +### Refactoring +* refactor: replace $e->getMessage() with $e in log_message() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6182 +* refactor: add CompleteDynamicPropertiesRector by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6187 +* refactor: debug toolbar by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6272 +* refactor: Exception exit code by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6286 +* chore: Remove Vagrant by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6314 +* refactor: CSRF protection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6320 + +## [v4.2.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.1) (2022-06-16) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.0...v4.2.1) + +### Breaking Changes +* Fix MIME guessing of extension from type by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6059 +* fix: get_cookie() may not use the cookie prefix by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6082 + +### Fixed Bugs +* fix: get_cookie() does not take Config\Cookie::$prefix by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6080 +* fix: session cookie name bug by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6091 +* fix: Session Handlers do not take Config\Cookie by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6081 +* fix: reverse routing does not work with full classname starting with `\` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6104 +* fix: insert error message in QueryBuilder by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6108 +* fix: `spark routes` shows "ERROR: 404" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6098 +* fix: Time::setTestNow() does not work with fa Locale by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6116 +* fix: `migrate --all` causes `Class "SQLite3" not found` error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6117 +* fix: event DBQuery is not fired on failed query when DBDebug is true by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6127 +* fix: `Time::humanize()` causes error with ar locale by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6120 +* Fix decorators by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/6090 +* Fix lost error message by test when after testInsertResultFail. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/6113 +* test: fix forgetting to restore DBDebug value by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6115 + +### Refactoring +* Apply AutoRouterImproved::translateURIDashes() by @pjsde in https://github.com/codeigniter4/CodeIgniter4/pull/6084 +* Remove useless catch by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6095 +* Move preload.php example to starter app by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/6088 +* style: compile sass by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6099 + +## [v4.2.0](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.0) (2022-06-03) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.9...v4.2.0) + +### Breaking Changes +* Validation: support placeholders for anything by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5545 +* Fix: Validation. Error key for field with asterisk by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5609 +* Improve exception logging by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5684 +* fix: spark can't use options on PHP 7.4 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5836 +* fix: [Autoloader] Composer classmap usage by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5850 +* fix: using multiple CLI::color() in CLI::write() outputs strings with wrong color by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5893 +* refactor: [Router] extract a class for auto-routing by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5877 +* feat: Debugbar request microtime by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5958 +* refactor: `system/bootstrap.php` only loads files and registers autoloader by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5972 +* fix: `dot_array_search()` unexpected behavior by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5940 +* feat: QueryBuilder join() raw SQL string support by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5875 +* fix: change BaseService::reset() $initAutoloader to true by default by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6020 + +### Fixed Bugs +* chore: update admin/framework/composer.json Kint by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5531 +* fix: BaseConnection::getConnectDuration() number_format(): Passing null to parameter by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5536 +* Fix: Debug toolbar selectors by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5544 +* Fix: Toolbar. ciDebugBar.showTab() context. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5554 +* Refactor Database Collector display by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5553 +* fix: add missing Migration lang item by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5557 +* feat: add Validation Strict Rules by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5445 +* fix: `Time::createFromTimestamp()` sets incorrect time when specifying timezone by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/5588 +* fix: Entity's isset() and unset() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5497 +* Fix: Deletion timestamp of the Model is updated when a record that has been soft-deleted is deleted again by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5578 +* Fix: Added alias escaping in subquery by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5601 +* fix: spark migrate:status does not show status with different namespaces by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5605 +* BaseService - Use lowercase key in resetSingle by @najdanovicivan in https://github.com/codeigniter4/CodeIgniter4/pull/5596 +* Fix `array_flatten_with_dots` ignores empty array values by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5606 +* fix: debug toolbar Routes Params output by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5619 +* fix: DownloadResponse memory leak by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5623 +* fix: spark does not show Exception by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5638 +* fix: Config CSRF $redirect does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5665 +* fix: do not call header() if headers have already been sent by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5680 +* fix: $routes->setDefaultMethod() does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5682 +* fix: debug toolbar vars response headers includes request headers by @zl59503020 in https://github.com/codeigniter4/CodeIgniter4/pull/5701 +* fix: 404 override controller does not output Response object body by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5703 +* fix: auto routes incorrectly display route filters with GET method by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5712 +* fix: Model::paginate() missing argument $group by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5699 +* Fix options are not passed to Command $params by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5206 +* fix: forceGlobalSecureRequests break URI schemes other than HTTP by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5730 +* fix: TypeError when `$tokenRandomize = true` and no token posted by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5742 +* fix: $builder->ignore()->insertBatch() only ignores on first iteration by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5672 +* fix: app/Config/Routes.php is loaded twice on Windows by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5780 +* fix: table name is double prefixed when LIKE clause by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5778 +* fix: Publisher $restrictions regex to FCPATH by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5793 +* fix: Timer::getElapsedTime() returns incorrect value by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5798 +* bug: Publisher $restrictions regex typo by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/5800 +* fix: [Validation] valid_date ErrorException when the field is not sent by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5804 +* fix: [Pager] can't get correct current page from segment by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5803 +* fix: bug that allows dynamic controllers to be used by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5814 +* config: remove App\ and Config\ in autoload.psr-4 in app starter composer.json by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5824 +* fix: failover's DBPrefix not working by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5816 +* fix: Validation returns incorrect errors after Redirect with Input by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5844 +* feat: [Parser] add configs to change conditional delimiters by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5842 +* fix: Commands::discoverCommands() loads incorrect classname by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5849 +* fix: Publisher::discover() loads incorrect classname by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5858 +* fix: validation errors in Model are not cleared when running validation again by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5861 +* fix: Parser fails with `({variable})` in loop by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5840 +* fix: [BaseConfig] string value is set from environment variable even if it should be int/float by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5779 +* fix: add Escaper Exception classes in $coreClassmap by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5891 +* fix: Composer PSR-4 overwrites Config\Autoload::$psr4 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5902 +* fix: Reverse Routing does not take into account the default namespace by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5936 +* fix: [Validation] Fields with an asterisk throws exception by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5938 +* fix: GDHandler::convert() does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5969 +* fix: Images\Handlers\GDHandler Implicit conversion from float to int loses precision by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5965 +* fix: GDHandler::save() removes transparency by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5971 +* fix: route limit to subdomains does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5961 +* fix: Model::_call() static analysis by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5970 +* fix: invalid css in error_404.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5978 +* Fix: Route placeholder (:any) with {locale} by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6003 +* Changing the subquery builder for the Oracle by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5999 +* fix: CURLRequest request body is not reset on the next request by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6014 +* Bug: The SQLSRV driver ignores the port value from the config. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6036 +* fix: `set_radio()` not working as expected by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6037 +* fix: add config for SQLite3 Foreign Keys by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6050 +* fix: Ignore non-HTML responses in storePreviousURL by @tearoom6 in https://github.com/codeigniter4/CodeIgniter4/pull/6012 +* fix: SQLite3\Table::copyData() does not escape column names by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6055 +* Fix `slash_item()` erroring when property fetched does not exist on `Config\App` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6058 + +### New Features +* Feature Add Oracle driver by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/2487 +* feat: new improved auto router by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5889 +* feat: new improved auto router `spark routes` command by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5953 +* feat: `db:table` command by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5979 + +### Enhancements +* feat: CSP enhancements by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5516 +* Feature: Subqueries in the FROM section by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5510 +* Added new View Decorators. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/5567 +* feat: auto routes listing by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5590 +* Feature: "spark routes" command shows routes with closure. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5651 +* feat: `spark routes` shows filters by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5628 +* Allow calling getQuery() multiple times, and other improvements by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5127 +* feat: add Controller::validateData() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5639 +* feat: can add route handler as callable by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5713 +* Checking if the subquery uses the same object as the main query by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5743 +* Feature: Subquery for SELECT by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5736 +* Extend Validation from BaseConfig so Registrars can add rules. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/5789 +* config: add mime type for webp by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5838 +* feat: add `$includeDir` option to `get_filenames()` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5862 +* feat: throws exception when controller name in routes contains `/` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5885 +* [PHPStan] Prepare for PHPStan 1.6.x-dev by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5876 +* [Rector] Add back SimplifyUselessVariableRector by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5911 +* Redirecting Routes. Placeholders. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5916 +* script_tag(): cosmetic for value-less attributes by @xlii-chl in https://github.com/codeigniter4/CodeIgniter4/pull/5884 +* feat: QueryBuilder raw SQL string support by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5817 +* improve Router Exception message by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5984 +* feat: DBForge::addField() `default` value raw SQL string support by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5957 +* Add sample file for preloading by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5974 +* Feature. QueryBuilder. Query union. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6015 +* feat: `getFieldData()` returns nullable data on PostgreSQL by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5981 + +### Refactoring +* refactor: add Factories::models() to suppress PHPStan error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5358 +* Fixed style for PHP7.4 by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5581 +* Fix Autoloader::initialize() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5592 +* refactor: CURLRequest and the slow tests by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5593 +* Refactor `if_exist` validation with dot notation by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5607 +* refactor: small changes in Filters and Router by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5627 +* refactor: replace deprecated `getFilterForRoute()` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5624 +* refactor: make BaseController abstract by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5647 +* refactor: move logic to prevent access to initController by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5648 +* refactor: remove migrations routes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5652 +* refactor: update Kint CSP nonce by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5657 +* Deprecate object implementations of `clean_path()` function by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5681 +* refactor: Session does not use cookies() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5656 +* refactor: replace deprecated Response::getReason() with getReasonPhrase() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5700 +* refactor: isCLI() in CLIRequest and IncomingRequest by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5653 +* refactor: CodeIgniter has context by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5650 +* Forge use statement by @mostafakhudair in https://github.com/codeigniter4/CodeIgniter4/pull/5729 +* refactor: remove `&` before $db by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5726 +* refactor: remove unneeded `&` references in ContentSecurityPolicy.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5734 +* Nonce replacement optimization. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5733 +* [Rector] Clean up skip config and re-run Rector by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5813 +* refactor: DB Session Handler by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5696 +* Rename `Abstact` to `Abstract` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5833 +* refactor: extract RedirectResponse::withErrors() method by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5860 +* Optimizing the RouteCollection::getRoutes() method by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5918 +* refactor: add strtolower() to Request::getMethod() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5963 +* refactor: remove `$_SERVER['HTTP_HOST']` in RouteCollection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5962 +* refactor: deprecate const `EVENT_PRIORITY_*` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6000 +* fix: replace EVENT_PRIORITY_NORMAL with Events::PRIORITY_NORMAL by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6005 +* Router class optimization. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/6004 +* Prefer `is_file()` by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6025 +* refactor: use get_filenames() 4th param in FileLocator by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6026 +* refactor: use get_filenames() 4th param by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6031 +* refactor: CodeIgniter $context check by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6047 +* Small change to improve code reading by @valmorflores in https://github.com/codeigniter4/CodeIgniter4/pull/6051 +* refactor: remove `CodeIgniter\Services` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6053 + +See [CHANGELOG_4.1.md](./CHANGELOG_4.1.md) diff --git a/composer.json b/composer.json index 682e3f9c81c4..d0c2bd1e4dda 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "psr/log": "^1.1" }, "require-dev": { - "kint-php/kint": "^5.0.1", + "kint-php/kint": "^5.0.3", "codeigniter/coding-standard": "^1.5", "fakerphp/faker": "^1.9", "friendsofphp/php-cs-fixer": "3.13.0", @@ -25,7 +25,7 @@ "phpunit/phpcov": "^8.2", "phpunit/phpunit": "^9.1", "predis/predis": "^1.1 || ^2.0", - "rector/rector": "0.15.5", + "rector/rector": "0.15.16", "vimeo/psalm": "^5.0" }, "suggest": { diff --git a/contributing/signing.md b/contributing/signing.md index e925327e9b71..6626911591d2 100644 --- a/contributing/signing.md +++ b/contributing/signing.md @@ -53,3 +53,7 @@ bash shell to use the **-S** option to force the secure signing. Regardless of how you sign a commit, commit messages are important too. See [Contribution Workflow](./workflow.md#commit-messages) for details. + +## GPG-Signing Old Commits + +See [Contribution Workflow](./workflow.md#gpg-signing-old-commits). diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml index 157b02055990..b64ce2899051 100644 --- a/phpdoc.dist.xml +++ b/phpdoc.dist.xml @@ -5,12 +5,12 @@ xmlns="https://www.phpdoc.org" xsi:noNamespaceSchemaLocation="https://docs.phpdoc.org/latest/phpdoc.xsd" > - CodeIgniter v4.0.0 API + CodeIgniter v4.3 API api/build/ api/cache/ - + system diff --git a/phpstan-baseline.neon.dist b/phpstan-baseline.neon.dist index af189e08b8b1..2d1f9ddd3c41 100644 --- a/phpstan-baseline.neon.dist +++ b/phpstan-baseline.neon.dist @@ -100,11 +100,6 @@ parameters: count: 13 path: system/Database/SQLSRV/Forge.php - - - message: "#^Call to an undefined method CodeIgniter\\\\View\\\\RendererInterface\\:\\:getPerformanceData\\(\\)\\.$#" - count: 1 - path: system/Debug/Toolbar/Collectors/Events.php - - message: "#^Property CodeIgniter\\\\Log\\\\Logger\\:\\:\\$logCache \\(array\\) on left side of \\?\\? is not nullable\\.$#" count: 1 @@ -222,7 +217,7 @@ parameters: - message: "#^Call to an undefined method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getDefaultNamespace\\(\\)\\.$#" - count: 3 + count: 2 path: system/Router/Router.php - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 6fe1103175b9..9816237b9c82 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -172,6 +172,11 @@ UnexsistenceClass + + + SimpleConfig + + 'SomeWidget' diff --git a/public/.htaccess b/public/.htaccess index a5d6c2a54128..dbed322fdc1e 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,5 +1,5 @@ # Disable directory browsing -Options All -Indexes +Options -Indexes # ---------------------------------------------------------------------- # Rewrite engine diff --git a/rector.php b/rector.php index 095cb2444c62..797387ba4aba 100644 --- a/rector.php +++ b/rector.php @@ -40,6 +40,7 @@ use Rector\Php71\Rector\FuncCall\CountOnNullRector; use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Php73\Rector\FuncCall\StringifyStrNeedlesRector; +use Rector\PHPUnit\Rector\MethodCall\AssertPropertyExistsRector; use Rector\PHPUnit\Rector\MethodCall\GetMockBuilderGetMockToCreateMockRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector; @@ -74,13 +75,14 @@ // is there a file you need to skip? $rectorConfig->skip([ - __DIR__ . '/app/Views', __DIR__ . '/system/Debug/Toolbar/Views/toolbar.tpl.php', __DIR__ . '/system/ThirdParty', __DIR__ . '/tests/system/Config/fixtures', __DIR__ . '/tests/_support', JsonThrowOnErrorRector::class, StringifyStrNeedlesRector::class, + // assertObjectHasAttribute() is deprecated + AssertPropertyExistsRector::class, RemoveUnusedPrivateMethodRector::class => [ // private method called via getPrivateMethodInvoker diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 0f1d91eb7809..f838451ce6fb 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -381,7 +381,12 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa ); } // This method requires Composer 2.0.14 or later. - $packageList = InstalledVersions::getAllRawData()[0]['versions']; + $allData = InstalledVersions::getAllRawData(); + $packageList = []; + + foreach ($allData as $list) { + $packageList = array_merge($packageList, $list['versions']); + } // Check config for $composerPackages. $only = $composerPackages['only'] ?? []; diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index 98a4ccbf78c6..b95d7f9ee28c 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -229,7 +229,7 @@ public function isSupported(): bool * Does the heavy lifting of actually retrieving the file and * verifying it's age. * - * @return mixed + * @return array|bool|float|int|object|string|null */ protected function getItem(string $filename) { @@ -366,8 +366,8 @@ protected function getDirFileInfo(string $sourceDir, bool $topLevelOnly = true, * Options are: name, server_path, size, date, readable, writable, executable, fileperms * Returns FALSE if the file cannot be found. * - * @param string $file Path to file - * @param mixed $returnedValues Array or comma separated string of information returned + * @param string $file Path to file + * @param array|string $returnedValues Array or comma separated string of information returned * * @return array|false */ diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 5cb98c438863..d370e34ca22e 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -47,7 +47,7 @@ class CodeIgniter /** * The current version of CodeIgniter Framework */ - public const CI_VERSION = '4.3.1'; + public const CI_VERSION = '4.3.2'; /** * App startup time. diff --git a/system/Common.php b/system/Common.php index ab148e5d66d1..2a98253cb6b4 100644 --- a/system/Common.php +++ b/system/Common.php @@ -10,6 +10,7 @@ */ use CodeIgniter\Cache\CacheInterface; +use CodeIgniter\Config\BaseConfig; use CodeIgniter\Config\Factories; use CodeIgniter\Cookie\Cookie; use CodeIgniter\Cookie\CookieStore; @@ -31,6 +32,7 @@ use CodeIgniter\Test\TestLogger; use Config\App; use Config\Database; +use Config\DocTypes; use Config\Logger; use Config\Services; use Config\View; @@ -47,8 +49,7 @@ */ function app_timezone(): string { - /** @var App $config */ - $config = config('App'); + $config = config(App::class); return $config->appTimezone; } @@ -90,7 +91,11 @@ function cache(?string $key = null) function clean_path(string $path): string { // Resolve relative paths - $path = realpath($path) ?: $path; + try { + $path = realpath($path) ?: $path; + } catch (ErrorException|ValueError $e) { + $path = 'error file path: ' . urlencode($path); + } switch (true) { case strpos($path, APPPATH) === 0: @@ -199,7 +204,12 @@ function command(string $command) /** * More simple way of getting config instances from Factories * - * @return object|null + * @template ConfigTemplate of BaseConfig + * + * @param class-string|string $name + * + * @return ConfigTemplate|null + * @phpstan-return ($name is class-string ? ConfigTemplate : object|null) */ function config(string $name, bool $getShared = true) { @@ -491,7 +501,7 @@ function force_https(int $duration = 31_536_000, ?RequestInterface $request = nu Services::session(null, true)->regenerate(); // @codeCoverageIgnore } - $baseURL = config('App')->baseURL; + $baseURL = config(App::class)->baseURL; if (strpos($baseURL, 'https://') === 0) { $authority = substr($baseURL, strlen('https://')); @@ -807,11 +817,12 @@ function log_message(string $level, string $message, array $context = []) /** * More simple way of getting model instances from Factories * - * @template T of Model + * @template ModelTemplate of Model * - * @param class-string $name + * @param class-string|string $name * - * @return T + * @return ModelTemplate|null + * @phpstan-return ($name is class-string ? ModelTemplate : object|null) */ function model(string $name, bool $getShared = true, ?ConnectionInterface &$conn = null) { @@ -859,7 +870,7 @@ function old(string $key, $default = null, $escape = 'html') * * If more control is needed, you must use $response->redirect explicitly. * - * @param string $route + * @param string|null $route Route name or Controller::method */ function redirect(?string $route = null): RedirectResponse { @@ -881,7 +892,7 @@ function redirect(?string $route = null): RedirectResponse */ function _solidus(): string { - if (config('DocTypes')->html5 ?? false) { + if (config(DocTypes::class)->html5 ?? false) { return ''; } @@ -941,18 +952,18 @@ function response(): ResponseInterface if (! function_exists('route_to')) { /** - * Given a controller/method string and any params, + * Given a route name or controller/method string and any params, * will attempt to build the relative URL to the * matching route. * * NOTE: This requires the controller/method to * have a route defined in the routes Config file. * - * @param string $method Named route or Controller::method + * @param string $method Route name or Controller::method * @param int|string ...$params One or more parameters to be passed to the route. * The last parameter allows you to set the locale. * - * @return false|string + * @return false|string The route (URI path relative to baseURL) or false if not found. */ function route_to(string $method, ...$params) { @@ -969,8 +980,6 @@ function route_to(string $method, ...$params) * session()->set('foo', 'bar'); * $foo = session('bar'); * - * @param string $val - * * @return array|bool|float|int|object|Session|string|null * @phpstan-return ($val is null ? Session : array|bool|float|int|object|string|null) */ @@ -1060,7 +1069,7 @@ function single_service(string $name, ...$params) */ function slash_item(string $item): ?string { - $config = config('App'); + $config = config(App::class); if (! property_exists($config, $item)) { return null; @@ -1162,10 +1171,8 @@ function timer(?string $name = null, ?callable $callable = null) */ function view(string $name, array $data = [], array $options = []): string { - /** @var CodeIgniter\View\View $renderer */ $renderer = Services::renderer(); - /** @var \CodeIgniter\Config\View $config */ $config = config(View::class); $saveData = $config->saveData; diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 9e2c634cb7f2..653f43013f15 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -728,9 +728,15 @@ public function orWhere($key, $value = null, ?bool $escape = null) */ protected function whereHaving(string $qbKey, $key, $value = null, string $type = 'AND ', ?bool $escape = null) { + $rawSqlOnly = false; + if ($key instanceof RawSql) { - $keyValue = [(string) $key => $key]; - $escape = false; + if ($value === null) { + $keyValue = [(string) $key => $key]; + $rawSqlOnly = true; + } else { + $keyValue = [(string) $key => $value]; + } } elseif (! is_array($key)) { $keyValue = [$key => $value]; } else { @@ -745,7 +751,7 @@ protected function whereHaving(string $qbKey, $key, $value = null, string $type foreach ($keyValue as $k => $v) { $prefix = empty($this->{$qbKey}) ? $this->groupGetType('') : $this->groupGetType($type); - if ($v instanceof RawSql) { + if ($rawSqlOnly === true) { $k = ''; $op = ''; } elseif ($v !== null) { @@ -755,9 +761,9 @@ protected function whereHaving(string $qbKey, $key, $value = null, string $type $k = trim($k); end($op); - $op = trim(current($op)); + // Does the key end with operator? if (substr($k, -strlen($op)) === $op) { $k = rtrim(substr($k, 0, -strlen($op))); $op = " {$op}"; @@ -777,7 +783,15 @@ protected function whereHaving(string $qbKey, $key, $value = null, string $type } elseif (! $this->hasOperator($k) && $qbKey !== 'QBHaving') { // value appears not to have been set, assign the test to IS NULL $op = ' IS NULL'; - } elseif (preg_match('/\s*(!?=|<>|IS(?:\s+NOT)?)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE)) { + } elseif ( + // The key ends with !=, =, <>, IS, IS NOT + preg_match( + '/\s*(!?=|<>|IS(?:\s+NOT)?)\s*$/i', + $k, + $match, + PREG_OFFSET_CAPTURE + ) + ) { $k = substr($k, 0, $match[0][1]); $op = $match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL'; } else { @@ -3209,6 +3223,10 @@ protected function objectToArray($object) return $object; } + if ($object instanceof RawSql) { + throw new InvalidArgumentException('RawSql "' . $object . '" cannot be used here.'); + } + $array = []; foreach (get_object_vars($object) as $key => $val) { @@ -3367,16 +3385,16 @@ protected function getOperator(string $str, bool $list = false) : ''; $this->pregOperators = [ '\s*(?:<|>|!)?=\s*', // =, <=, >=, != - '\s*<>?\s*', // <, <> - '\s*>\s*', // > - '\s+IS NULL', // IS NULL - '\s+IS NOT NULL', // IS NOT NULL - '\s+EXISTS\s*\(.*\)', // EXISTS(sql) + '\s*<>?\s*', // <, <> + '\s*>\s*', // > + '\s+IS NULL', // IS NULL + '\s+IS NOT NULL', // IS NOT NULL + '\s+EXISTS\s*\(.*\)', // EXISTS (sql) '\s+NOT EXISTS\s*\(.*\)', // NOT EXISTS(sql) - '\s+BETWEEN\s+', // BETWEEN value AND value - '\s+IN\s*\(.*\)', // IN(list) - '\s+NOT IN\s*\(.*\)', // NOT IN (list) - '\s+LIKE\s+\S.*(' . $_les . ')?', // LIKE 'expr'[ ESCAPE '%s'] + '\s+BETWEEN\s+', // BETWEEN value AND value + '\s+IN\s*\(.*\)', // IN (list) + '\s+NOT IN\s*\(.*\)', // NOT IN (list) + '\s+LIKE\s+\S.*(' . $_les . ')?', // LIKE 'expr'[ ESCAPE '%s'] '\s+NOT LIKE\s+\S.*(' . $_les . ')?', // NOT LIKE 'expr'[ ESCAPE '%s'] ]; } @@ -3399,13 +3417,18 @@ private function getOperatorFromWhereKey(string $whereKey) $whereKey = trim($whereKey); $pregOperators = [ - '\s*(?:<|>|!)?=', // =, <=, >=, != - '\s*<>?', // <, <> - '\s*>', // > - '\s+IS NULL', // IS NULL - '\s+IS NOT NULL', // IS NOT NULL - '\s+LIKE', // LIKE - '\s+NOT LIKE', // NOT LIKE + '\s*(?:<|>|!)?=', // =, <=, >=, != + '\s*<>?', // <, <> + '\s*>', // > + '\s+IS NULL', // IS NULL + '\s+IS NOT NULL', // IS NOT NULL + '\s+EXISTS\s*\(.*\)', // EXISTS (sql) + '\s+NOT EXISTS\s*\(.*\)', // NOT EXISTS (sql) + '\s+BETWEEN\s+', // BETWEEN value AND value + '\s+IN\s*\(.*\)', // IN (list) + '\s+NOT IN\s*\(.*\)', // NOT IN (list) + '\s+LIKE', // LIKE + '\s+NOT LIKE', // NOT LIKE ]; return preg_match_all( diff --git a/system/Database/Database.php b/system/Database/Database.php index 58d2a22bad9a..df3ad9ae1550 100644 --- a/system/Database/Database.php +++ b/system/Database/Database.php @@ -123,6 +123,7 @@ protected function parseDSN(array $params): array /** * Initialize database driver. * + * @param string $driver Driver name. FQCN can be used. * @param array|object $argument * * @return BaseConnection|BaseUtils|Forge diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php index bcce589db894..6dc098d4af10 100644 --- a/system/Debug/Exceptions.php +++ b/system/Debug/Exceptions.php @@ -68,6 +68,8 @@ class Exceptions */ protected $response; + private ?Throwable $exceptionCaughtByExceptionHandler = null; + /** * @param CLIRequest|IncomingRequest $request */ @@ -113,6 +115,8 @@ public function initialize() */ public function exceptionHandler(Throwable $exception) { + $this->exceptionCaughtByExceptionHandler = $exception; + [$statusCode, $exitCode] = $this->determineCodes($exception); if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes, true)) { @@ -191,6 +195,13 @@ public function shutdownHandler() ['type' => $type, 'message' => $message, 'file' => $file, 'line' => $line] = $error; + if ($this->exceptionCaughtByExceptionHandler) { + $message .= "\n【Previous Exception】\n" + . get_class($this->exceptionCaughtByExceptionHandler) . "\n" + . $this->exceptionCaughtByExceptionHandler->getMessage() . "\n" + . $this->exceptionCaughtByExceptionHandler->getTraceAsString(); + } + if (in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) { $this->exceptionHandler(new ErrorException($message, 0, $type, $file, $line)); } diff --git a/system/Debug/Toolbar/Collectors/Events.php b/system/Debug/Toolbar/Collectors/Events.php index a8a7e7aa4c35..178a886644a5 100644 --- a/system/Debug/Toolbar/Collectors/Events.php +++ b/system/Debug/Toolbar/Collectors/Events.php @@ -11,11 +11,8 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; -use CodeIgniter\View\RendererInterface; -use Config\Services; - /** - * Views collector + * Events collector */ class Events extends BaseCollector { @@ -25,7 +22,7 @@ class Events extends BaseCollector * * @var bool */ - protected $hasTimeline = false; + protected $hasTimeline = true; /** * Whether this collector needs to display @@ -51,21 +48,6 @@ class Events extends BaseCollector */ protected $title = 'Events'; - /** - * Instance of the Renderer service - * - * @var RendererInterface - */ - protected $viewer; - - /** - * Constructor. - */ - public function __construct() - { - $this->viewer = Services::renderer(); - } - /** * Child classes should implement this to return the timeline data * formatted for correct usage. @@ -74,12 +56,12 @@ protected function formatTimelineData(): array { $data = []; - $rows = $this->viewer->getPerformanceData(); + $rows = \CodeIgniter\Events\Events::getPerformanceLogs(); foreach ($rows as $info) { $data[] = [ - 'name' => 'View: ' . $info['view'], - 'component' => 'Views', + 'name' => 'Event: ' . $info['event'], + 'component' => 'Events', 'start' => $info['start'], 'duration' => $info['end'] - $info['start'], ]; diff --git a/system/Debug/Toolbar/Collectors/Views.php b/system/Debug/Toolbar/Collectors/Views.php index fae3385fffbc..ea44dae44bd5 100644 --- a/system/Debug/Toolbar/Collectors/Views.php +++ b/system/Debug/Toolbar/Collectors/Views.php @@ -60,9 +60,9 @@ class Views extends BaseCollector protected $title = 'Views'; /** - * Instance of the Renderer service + * Instance of the shared Renderer service * - * @var RendererInterface + * @var RendererInterface|null */ protected $viewer; @@ -73,12 +73,9 @@ class Views extends BaseCollector */ protected $views = []; - /** - * Constructor. - */ - public function __construct() + private function initViewer(): void { - $this->viewer = Services::renderer(); + $this->viewer ??= Services::renderer(); } /** @@ -87,6 +84,8 @@ public function __construct() */ protected function formatTimelineData(): array { + $this->initViewer(); + $data = []; $rows = $this->viewer->getPerformanceData(); @@ -121,8 +120,9 @@ protected function formatTimelineData(): array */ public function getVarData(): array { - return [ + $this->initViewer(); + return [ 'View Data' => $this->viewer->getData(), ]; } @@ -132,6 +132,8 @@ public function getVarData(): array */ public function getBadgeValue(): int { + $this->initViewer(); + return count($this->viewer->getPerformanceData()); } diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index ef69a08996f6..008a865bcdcd 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -62,8 +62,8 @@ class IncomingRequest extends Request * * Note: This WILL NOT match the actual URL in the browser since for * everything this cares about (and the router, etc) is the portion - * AFTER the script name. So, if hosted in a sub-folder this will - * appear different than actual URL. If you need that use getPath(). + * AFTER the baseURL. So, if hosted in a sub-folder this will + * appear different than actual URI path. If you need that use getPath(). * * @deprecated Will be protected. Use getUri() instead. * @@ -72,7 +72,7 @@ class IncomingRequest extends Request public $uri; /** - * The detected path (relative to SCRIPT_NAME). + * The detected URI path (relative to the baseURL). * * Note: current_url() uses this to build its URI, * so this becomes the source for the "current URL" @@ -329,7 +329,7 @@ protected function parseQueryString(): string $uri = $_SERVER['QUERY_STRING'] ?? @getenv('QUERY_STRING'); if (trim($uri, '/') === '') { - return ''; + return '/'; } if (strncmp($uri, '/', 1) === 0) { @@ -443,7 +443,7 @@ public function isSecure(): bool * instance, this can be used to change the "current URL" * for testing. * - * @param string $path URI path relative to SCRIPT_NAME + * @param string $path URI path relative to baseURL * @param App|null $config Optional alternate config to use * * @return $this @@ -451,6 +451,9 @@ public function isSecure(): bool public function setPath(string $path, ?App $config = null) { $this->path = $path; + + // @TODO remove this. The path of the URI object should be a full URI path, + // not a URI path relative to baseURL. $this->uri->setPath($path); $config ??= $this->config; @@ -481,8 +484,11 @@ public function setPath(string $path, ?App $config = null) $this->uri->setScheme('https'); } } elseif (! is_cli()) { + // Do not change exit() to exception; Request is initialized before + // setting the exception handler, so if an exception is raised, an + // error will be displayed even if in the production environment. // @codeCoverageIgnoreStart - exit('You have an empty or invalid base URL. The baseURL value must be set in Config\App.php, or through the .env file.'); + exit('You have an empty or invalid baseURL. The baseURL value must be set in app/Config/App.php, or through the .env file.'); // @codeCoverageIgnoreEnd } @@ -511,7 +517,7 @@ private function determineHost(App $config, string $baseURL): string } /** - * Returns the path relative to SCRIPT_NAME, + * Returns the URI path relative to baseURL, * running detection as necessary. */ public function getPath(): string diff --git a/system/HTTP/RedirectResponse.php b/system/HTTP/RedirectResponse.php index 34f950de0a60..ed620c40f2ae 100644 --- a/system/HTTP/RedirectResponse.php +++ b/system/HTTP/RedirectResponse.php @@ -24,7 +24,7 @@ class RedirectResponse extends Response * Sets the URI to redirect to and, optionally, the HTTP status code to use. * If no code is provided it will be automatically determined. * - * @param string $uri The URI to redirect to + * @param string $uri The URI path (relative to baseURL) to redirect to * @param int|null $code HTTP status code * * @return $this @@ -44,7 +44,7 @@ public function to(string $uri, ?int $code = null, string $method = 'auto') * Sets the URI to redirect to but as a reverse-routed or named route * instead of a raw URI. * - * @param string $route Named route or Controller::method + * @param string $route Route name or Controller::method * * @return $this * diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 23227e98d0f2..587e441ffc17 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -90,7 +90,7 @@ class URI * * Note: The constructor of the IncomingRequest class changes the path of * the URI object held by the IncomingRequest class to a path relative - * to the SCRIPT_NAME. If the baseURL contains subfolders, this value + * to the baseURL. If the baseURL contains subfolders, this value * will be different from the current URI path. * * @var string @@ -148,11 +148,9 @@ class URI /** * Builds a representation of the string from the component parts. * - * @param string|null $scheme URI scheme. E.g., http, ftp - * @param string $authority - * @param string $path - * @param string $query - * @param string $fragment + * @param string|null $scheme URI scheme. E.g., http, ftp + * + * @return string URI string with only passed parts. Maybe incomplete as a URI. */ public static function createURIString( ?string $scheme = null, @@ -1089,14 +1087,12 @@ protected function parseStr(string $query): array ), $query); $params = implode('&', $params); - parse_str($params, $params); + parse_str($params, $result); - foreach ($params as $key => $value) { + foreach ($result as $key => $value) { $return[hex2bin($key)] = $value; } - $query = $params = null; - return $return; } } diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index 57d3f38b63a4..dfd4a64f0669 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -741,9 +741,12 @@ function validation_show_error(string $field, string $template = 'single'): stri $config = config('Validation'); $view = Services::renderer(); - $errors = validation_errors(); + $errors = array_filter(validation_errors(), static fn ($key) => preg_match( + '/^' . str_replace(['\.\*', '\*\.'], ['\..+', '.+\.'], preg_quote($field, '/')) . '$/', + $key + ), ARRAY_FILTER_USE_KEY); - if (! array_key_exists($field, $errors)) { + if ($errors === []) { return ''; } @@ -751,7 +754,7 @@ function validation_show_error(string $field, string $template = 'single'): stri throw ValidationException::forInvalidTemplate($template); } - return $view->setVar('error', $errors[$field]) + return $view->setVar('error', implode("\n", $errors)) ->render($config->templates[$template]); } } diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php index 682830ba1e5a..1a064a76175f 100644 --- a/system/Helpers/url_helper.php +++ b/system/Helpers/url_helper.php @@ -26,17 +26,34 @@ * * @internal Outside the framework this should not be used directly. * - * @param string $relativePath May include queries or fragments + * @param array|string $relativePath URI string or array of URI segments. + * May include queries or fragments. + * @param App|null $config Alternative Config to use * * @throws HTTPException For invalid paths. * @throws InvalidArgumentException For invalid config. */ - function _get_uri(string $relativePath = '', ?App $config = null): URI + function _get_uri($relativePath = '', ?App $config = null): URI { - $config ??= config('App'); + $appConfig = null; + if ($config === null) { + /** @var App $appConfig */ + $appConfig = config('App'); + + if ($appConfig->baseURL === '') { + throw new InvalidArgumentException( + '_get_uri() requires a valid baseURL.' + ); + } + } elseif ($config->baseURL === '') { + throw new InvalidArgumentException( + '_get_uri() requires a valid baseURL.' + ); + } - if ($config->baseURL === '') { - throw new InvalidArgumentException('_get_uri() requires a valid baseURL.'); + // Convert array of segments to a string + if (is_array($relativePath)) { + $relativePath = implode('/', $relativePath); } // If a full URI was passed then convert it @@ -53,27 +70,31 @@ function _get_uri(string $relativePath = '', ?App $config = null): URI $relativePath = URI::removeDotSegments($relativePath); - // Build the full URL based on $config and $relativePath $request = Services::request(); - /** @var App $config */ - $url = $request instanceof CLIRequest - ? rtrim($config->baseURL, '/ ') . '/' - : $request->getUri()->getBaseURL(); + if ($config === null) { + $baseURL = $request instanceof CLIRequest + ? rtrim($appConfig->baseURL, '/ ') . '/' + // Use the current baseURL for multiple domain support + : $request->getUri()->getBaseURL(); + + $config = $appConfig; + } else { + $baseURL = rtrim($config->baseURL, '/ ') . '/'; + } // Check for an index page + $indexPage = ''; if ($config->indexPage !== '') { - $url .= $config->indexPage; + $indexPage = $config->indexPage; // Check if we need a separator if ($relativePath !== '' && $relativePath[0] !== '/' && $relativePath[0] !== '?') { - $url .= '/'; + $indexPage .= '/'; } } - $url .= $relativePath; - - $uri = new URI($url); + $uri = new URI($baseURL . $indexPage . $relativePath); // Check if the baseURL scheme needs to be coerced into its secure version if ($config->forceGlobalSecureRequests && $uri->getScheme() === 'http') { @@ -94,11 +115,6 @@ function _get_uri(string $relativePath = '', ?App $config = null): URI */ function site_url($relativePath = '', ?string $scheme = null, ?App $config = null): string { - // Convert array of segments to a string - if (is_array($relativePath)) { - $relativePath = implode('/', $relativePath); - } - $uri = _get_uri($relativePath, $config); return URI::createURIString( @@ -121,37 +137,47 @@ function site_url($relativePath = '', ?string $scheme = null, ?App $config = nul */ function base_url($relativePath = '', ?string $scheme = null): string { - $config = clone config('App'); + /** @var App $config */ + $config = clone config('App'); + + // Use the current baseURL for multiple domain support + $request = Services::request(); + $config->baseURL = $request instanceof CLIRequest + ? rtrim($config->baseURL, '/ ') . '/' + : $request->getUri()->getBaseURL(); + $config->indexPage = ''; - return rtrim(site_url($relativePath, $scheme, $config), '/'); + return site_url($relativePath, $scheme, $config); } } if (! function_exists('current_url')) { /** * Returns the current full URL based on the Config\App settings and IncomingRequest. - * String returns ignore query and fragment parts. * * @param bool $returnObject True to return an object instead of a string * @param IncomingRequest|null $request A request to use when retrieving the path * - * @return string|URI + * @return string|URI When returning string, the query and fragment parts are removed. + * When returning URI, the query and fragment parts are preserved. */ function current_url(bool $returnObject = false, ?IncomingRequest $request = null) { $request ??= Services::request(); - $path = $request->getPath(); + /** @var CLIRequest|IncomingRequest $request */ + $routePath = $request->getPath(); + $currentURI = $request->getUri(); // Append queries and fragments - if ($query = $request->getUri()->getQuery()) { - $path .= '?' . $query; + if ($query = $currentURI->getQuery()) { + $query = '?' . $query; } - if ($fragment = $request->getUri()->getFragment()) { - $path .= '#' . $fragment; + if ($fragment = $currentURI->getFragment()) { + $fragment = '#' . $fragment; } - $uri = _get_uri($path); + $uri = _get_uri($routePath . $query . $fragment); return $returnObject ? $uri : URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath()); } @@ -183,15 +209,14 @@ function previous_url(bool $returnObject = false) /** * URL String * - * Returns the path part of the current URL - * - * @param bool $relative Whether the resulting path should be relative to baseURL + * Returns the path part (relative to baseURL) of the current URL */ - function uri_string(bool $relative = false): string + function uri_string(): string { - return $relative - ? ltrim(Services::request()->getPath(), '/') - : Services::request()->getUri()->getPath(); + // The value of Services::request()->getUri()->getPath() is overridden + // by IncomingRequest constructor. If we use it here, the current tests + // in CurrentUrlTest will fail. + return ltrim(Services::request()->getPath(), '/'); } } @@ -543,14 +568,15 @@ function mb_url_title(string $str, string $separator = '-', bool $lowercase = fa if (! function_exists('url_to')) { /** - * Get the full, absolute URL to a controller method + * Get the full, absolute URL to a route name or controller method * (with additional arguments) * * NOTE: This requires the controller/method to * have a route defined in the routes Config file. * - * @param string $controller Named route or Controller::method - * @param int|string ...$args One or more parameters to be passed to the route + * @param string $controller Route name or Controller::method + * @param int|string ...$args One or more parameters to be passed to the route. + * The last parameter allows you to set the locale. * * @throws RouterException */ @@ -583,7 +609,7 @@ function url_is(string $path): bool { // Setup our regex to allow wildcards $path = '/' . trim(str_replace('*', '(\S)*', $path), '/ '); - $currentPath = '/' . trim(uri_string(true), '/ '); + $currentPath = '/' . trim(uri_string(), '/ '); return (bool) preg_match("|^{$path}$|", $currentPath, $matches); } diff --git a/system/Model.php b/system/Model.php index de646c6f8c7c..2df1bb2f5132 100644 --- a/system/Model.php +++ b/system/Model.php @@ -43,6 +43,7 @@ * @method $this groupBy($by, ?bool $escape = null) * @method $this groupEnd() * @method $this groupStart() + * @method $this having($key, $value = null, ?bool $escape = null) * @method $this havingGroupEnd() * @method $this havingGroupStart() * @method $this havingIn(?string $key = null, $values = null, ?bool $escape = null) diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 30be78a2bce5..50b73616ea0e 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -112,8 +112,9 @@ class RouteCollection implements RouteCollectionInterface * verb => [ * routeName => [ * 'route' => [ - * routeKey(or from) => handler, - * ] + * routeKey(regex) => handler, + * ], + * 'redirect' => statusCode, * ] * ], * ] @@ -138,7 +139,7 @@ class RouteCollection implements RouteCollectionInterface * * [ * verb => [ - * routeKey(or from) => [ + * routeKey(regex) => [ * key => value, * ] * ], @@ -527,6 +528,8 @@ public function getRoutes(?string $verb = null): array /** * Returns one or all routes options + * + * @return array [key => value] */ public function getRoutesOptions(?string $from = null, ?string $verb = null): array { @@ -719,7 +722,7 @@ public function group(string $name, ...$params) * POST /photos/{id} update * * @param string $name The name of the resource/controller to route to. - * @param array|null $options An list of possible ways to customize the routing. + * @param array|null $options A list of possible ways to customize the routing. */ public function resource(string $name, ?array $options = null): RouteCollectionInterface { @@ -813,7 +816,7 @@ public function resource(string $name, ?array $options = null): RouteCollectionI * POST /photos/delete/{id} delete deleting the specified photo object * * @param string $name The name of the controller to route to. - * @param array|null $options An list of possible ways to customize the routing. + * @param array|null $options A list of possible ways to customize the routing. */ public function presenter(string $name, ?array $options = null): RouteCollectionInterface { @@ -1040,11 +1043,11 @@ public function environment(string $env, Closure $callback): RouteCollectionInte * // Equals 'path/$param1/$param2' * reverseRoute('Controller::method', $param1, $param2); * - * @param string $search Named route or Controller::method + * @param string $search Route name or Controller::method * @param int|string ...$params One or more parameters to be passed to the route. * The last parameter allows you to set the locale. * - * @return false|string + * @return false|string The route (URI path relative to baseURL) or false if not found. */ public function reverseRoute(string $search, ...$params) { @@ -1149,12 +1152,17 @@ public function getFilterForRoute(string $search, ?string $verb = null): string * 'role:admin,manager' * * has a filter of "role", with parameters of ['admin', 'manager']. + * + * @param string $search routeKey + * + * @return array filter_name or filter_name:arguments like 'role:admin,manager' + * @phpstan-return list */ public function getFiltersForRoute(string $search, ?string $verb = null): array { $options = $this->loadRoutesOptions($verb); - if (! array_key_exists($search, $options)) { + if (! array_key_exists($search, $options) || ! array_key_exists('filter', $options[$search])) { return []; } @@ -1162,7 +1170,7 @@ public function getFiltersForRoute(string $search, ?string $verb = null): array return [$options[$search]['filter']]; } - return $options[$search]['filter'] ?? []; + return $options[$search]['filter']; } /** @@ -1308,13 +1316,12 @@ protected function create(string $verb, string $from, $to, ?array $options = nul // Hostname limiting? if (! empty($options['hostname'])) { // @todo determine if there's a way to whitelist hosts? - if (isset($this->httpHost) && strtolower($this->httpHost) !== strtolower($options['hostname'])) { + if (! $this->checkHostname($options['hostname'])) { return; } $overwrite = true; } - // Limiting to subdomains? elseif (! empty($options['subdomain'])) { // If we don't match the current subdomain, then @@ -1387,6 +1394,22 @@ protected function create(string $verb, string $from, $to, ?array $options = nul } } + /** + * Compares the hostname passed in against the current hostname + * on this page request. + * + * @param string $hostname Hostname in route options + */ + private function checkHostname($hostname): bool + { + // CLI calls can't be on hostname. + if (! isset($this->httpHost)) { + return false; + } + + return strtolower($this->httpHost) === strtolower($hostname); + } + private function processArrayCallableSyntax(string $from, array $to): string { // [classname, method] @@ -1459,7 +1482,7 @@ private function checkSubdomains($subdomains): bool } /** - * Examines the HTTP_HOST to get a best match for the subdomain. It + * Examines the HTTP_HOST to get the best match for the subdomain. It * won't be perfect, but should work for our needs. * * It's especially not perfect since it's possible to register a domain @@ -1520,6 +1543,16 @@ public function resetRoutes() /** * Load routes options based on verb + * + * @return array> [routeKey(or from) => [key => value]] + * @phpstan-return array< + * string, + * array{ + * filter?: string|list, namespace?: string, hostname?: string, + * subdomain?: string, offset?: int, priority?: int, as?: string, + * redirect?: string + * } + * > */ protected function loadRoutesOptions(?string $verb = null): array { @@ -1559,41 +1592,52 @@ public function setPrioritize(bool $enabled = true) * Get all controllers in Route Handlers * * @param string|null $verb HTTP verb. `'*'` returns all controllers in any verb. + * + * @return array controller name list + * @phpstan-return list */ public function getRegisteredControllers(?string $verb = '*'): array { - $routes = []; + $controllers = []; if ($verb === '*') { - $rawRoutes = []; - foreach ($this->defaultHTTPMethods as $tmpVerb) { - $rawRoutes = array_merge($rawRoutes, $this->routes[$tmpVerb]); - } - - foreach ($rawRoutes as $route) { - $key = key($route['route']); - $handler = $route['route'][$key]; - - $routes[$key] = $handler; + foreach ($this->routes[$tmpVerb] as $route) { + $routeKey = key($route['route']); + $controller = $this->getControllerName($route['route'][$routeKey]); + if ($controller !== null) { + $controllers[] = $controller; + } + } } } else { $routes = $this->getRoutes($verb); - } - - $controllers = []; - foreach ($routes as $handler) { - if (! is_string($handler)) { - continue; + foreach ($routes as $handler) { + $controller = $this->getControllerName($handler); + if ($controller !== null) { + $controllers[] = $controller; + } } + } - [$controller] = explode('::', $handler, 2); + return array_unique($controllers); + } - $controllers[] = $controller; + /** + * @param Closure|string $handler Handler + * + * @return string|null Controller classname + */ + private function getControllerName($handler) + { + if (! is_string($handler)) { + return null; } - return array_unique($controllers); + [$controller] = explode('::', $handler, 2); + + return $controller; } /** diff --git a/system/Router/RouteCollectionInterface.php b/system/Router/RouteCollectionInterface.php index 7044459899c2..a8f264a8b8fd 100644 --- a/system/Router/RouteCollectionInterface.php +++ b/system/Router/RouteCollectionInterface.php @@ -21,7 +21,7 @@ * add a number of additional methods to customize how the routes are defined. * * The RouteCollection provides the Router with the routes so that it can determine - * which controller should be ran. + * which controller should be run. */ interface RouteCollectionInterface { @@ -157,7 +157,7 @@ public function getRoutes(); public function getHTTPVerb(); /** - * Attempts to look up a route based on it's destination. + * Attempts to look up a route based on its destination. * * If a route exists: * @@ -172,7 +172,7 @@ public function getHTTPVerb(); * @param string $search Named route or Controller::method * @param int|string ...$params * - * @return false|string + * @return false|string The route (URI path relative to baseURL) or false if not found. */ public function reverseRoute(string $search, ...$params); diff --git a/system/Router/Router.php b/system/Router/Router.php index 83cb6b8244f9..8dd35c656e39 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -155,6 +155,10 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request } /** + * Finds the controller method corresponding to the URI. + * + * @param string|null $uri URI path relative to baseURL + * * @return Closure|string Controller classname or Closure * * @throws PageNotFoundException @@ -162,12 +166,9 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request */ public function handle(?string $uri = null) { - // If we cannot find a URI to match against, then - // everything runs off of its default settings. + // If we cannot find a URI to match against, then set it to root (`/`). if ($uri === null || $uri === '') { - return strpos($this->controller, '\\') === false - ? $this->collection->getDefaultNamespace() . $this->controller - : $this->controller; + $uri = '/'; } // Decode URL-encoded string @@ -324,7 +325,7 @@ public function getMatchedRouteOptions() /** * Sets the value that should be used to match the index.php file. Defaults - * to index.php but this allows you to modify it in case your are using + * to index.php but this allows you to modify it in case you are using * something like mod_rewrite to remove the page. This allows you to set * it a blank. * @@ -376,7 +377,7 @@ public function getLocale() } /** - * Checks Defined Routs. + * Checks Defined Routes. * * Compares the uri string against the routes that the * RouteCollection class defined for us, attempting to find a match. @@ -495,7 +496,7 @@ protected function checkRoutes(string $uri): bool } /** - * Checks Auto Routs. + * Checks Auto Routes. * * Attempts to match a URI path against Controllers and directories * found in APPPATH/Controllers, to find a matching route. diff --git a/system/Router/RouterInterface.php b/system/Router/RouterInterface.php index 1efeb2e2c6e2..ffed59ca8aac 100644 --- a/system/Router/RouterInterface.php +++ b/system/Router/RouterInterface.php @@ -27,7 +27,7 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request /** * Finds the controller method corresponding to the URI. * - * @param string $uri + * @param string|null $uri URI path relative to baseURL * * @return Closure|string Controller classname or Closure */ diff --git a/system/Session/Handlers/RedisHandler.php b/system/Session/Handlers/RedisHandler.php index 4492c4dbed32..9910637663f5 100644 --- a/system/Session/Handlers/RedisHandler.php +++ b/system/Session/Handlers/RedisHandler.php @@ -24,7 +24,8 @@ */ class RedisHandler extends BaseHandler { - private const DEFAULT_PORT = 6379; + private const DEFAULT_PORT = 6379; + private const DEFAULT_PROTOCOL = 'tcp'; /** * phpRedis instance @@ -102,20 +103,21 @@ protected function setSavePath(): void throw SessionException::forEmptySavepath(); } - if (preg_match('#(?:tcp://)?([^:?]+)(?:\:(\d+))?(\?.+)?#', $this->savePath, $matches)) { - if (! isset($matches[3])) { - $matches[3] = ''; // Just to avoid undefined index notices below + if (preg_match('#(?:(tcp|tls)://)?([^:?]+)(?:\:(\d+))?(\?.+)?#', $this->savePath, $matches)) { + if (! isset($matches[4])) { + $matches[4] = ''; // Just to avoid undefined index notices below } $this->savePath = [ - 'host' => $matches[1], - 'port' => empty($matches[2]) ? self::DEFAULT_PORT : $matches[2], - 'password' => preg_match('#auth=([^\s&]+)#', $matches[3], $match) ? $match[1] : null, - 'database' => preg_match('#database=(\d+)#', $matches[3], $match) ? (int) $match[1] : 0, - 'timeout' => preg_match('#timeout=(\d+\.\d+|\d+)#', $matches[3], $match) ? (float) $match[1] : 0.0, + 'protocol' => ! empty($matches[1]) ? $matches[1] : self::DEFAULT_PROTOCOL, + 'host' => $matches[2], + 'port' => empty($matches[3]) ? self::DEFAULT_PORT : $matches[3], + 'password' => preg_match('#auth=([^\s&]+)#', $matches[4], $match) ? $match[1] : null, + 'database' => preg_match('#database=(\d+)#', $matches[4], $match) ? (int) $match[1] : 0, + 'timeout' => preg_match('#timeout=(\d+\.\d+|\d+)#', $matches[4], $match) ? (float) $match[1] : 0.0, ]; - preg_match('#prefix=([^\s&]+)#', $matches[3], $match) && $this->keyPrefix = $match[1]; + preg_match('#prefix=([^\s&]+)#', $matches[4], $match) && $this->keyPrefix = $match[1]; } else { throw SessionException::forInvalidSavePathFormat($this->savePath); } @@ -135,7 +137,7 @@ public function open($path, $name): bool $redis = new Redis(); - if (! $redis->connect($this->savePath['host'], ($this->savePath['host'][0] === '/' ? 0 : $this->savePath['port']), $this->savePath['timeout'])) { + if (! $redis->connect($this->savePath['protocol'] . '://' . $this->savePath['host'], ($this->savePath['host'][0] === '/' ? 0 : $this->savePath['port']), $this->savePath['timeout'])) { $this->logger->error('Session: Unable to connect to Redis with the configured settings.'); } elseif (isset($this->savePath['password']) && ! $redis->auth($this->savePath['password'])) { $this->logger->error('Session: Unable to authenticate to Redis instance.'); diff --git a/system/Test/FeatureTestTrait.php b/system/Test/FeatureTestTrait.php index 3b36129f71e1..e6e8492ea466 100644 --- a/system/Test/FeatureTestTrait.php +++ b/system/Test/FeatureTestTrait.php @@ -286,7 +286,7 @@ protected function setupRequest(string $method, ?string $path = null): IncomingR { $path = URI::removeDotSegments($path); $config = config(App::class); - $request = Services::request($config, true); + $request = Services::request($config, false); // $path may have a query in it $parts = explode('?', $path); diff --git a/system/ThirdParty/Kint/Utils.php b/system/ThirdParty/Kint/Utils.php index 1394b0819095..b51b33558501 100644 --- a/system/ThirdParty/Kint/Utils.php +++ b/system/ThirdParty/Kint/Utils.php @@ -118,7 +118,9 @@ public static function composerGetExtras(string $key = 'kint'): array continue; } - foreach ($packages as $package) { + // Composer 2.0 Compatibility: packages are now wrapped into a "packages" top level key instead of the whole file being the package array + // @see https://getcomposer.org/upgrade/UPGRADE-2.0.md + foreach ($packages['packages'] ?? $packages as $package) { if (isset($package['extra'][$key]) && \is_array($package['extra'][$key])) { $extras = \array_replace($extras, $package['extra'][$key]); } diff --git a/system/Throttle/Throttler.php b/system/Throttle/Throttler.php index bdca42ed8e9f..dc0844fa4cb8 100644 --- a/system/Throttle/Throttler.php +++ b/system/Throttle/Throttler.php @@ -139,7 +139,7 @@ public function check(string $key, int $capacity, int $seconds, int $cost = 1): // How many seconds till a new token is available. // We must have a minimum wait of 1 second for a new token. // Primarily stored to allow devs to report back to users. - $newTokenAvailable = (int) ($refresh - $elapsed - $refresh * $tokens); + $newTokenAvailable = (int) round((1 - $tokens) * $refresh); $this->tokenTime = max(1, $newTokenAvailable); return false; diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index 41aaa4a098fb..c22f38ae2413 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -443,7 +443,7 @@ public function setRule(string $field, ?string $label, $rules, array $errors = [ $ruleSet[$field]['errors'] = $errors; } - $this->setRules($ruleSet + $this->getRules(), $this->customErrors); + $this->setRules(array_merge($this->getRules(), $ruleSet), $this->customErrors); return $this; } diff --git a/system/View/Filters.php b/system/View/Filters.php index 336a0d08a67c..65dfe6dff943 100644 --- a/system/View/Filters.php +++ b/system/View/Filters.php @@ -171,6 +171,8 @@ public static function local_currency($value, string $currency, ?string $locale { helper('number'); + $fraction ??= 0; + $options = [ 'type' => NumberFormatter::CURRENCY, 'currency' => $currency, diff --git a/system/View/Parser.php b/system/View/Parser.php index 8d0331a9e1cf..b9abf91edb5f 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -257,7 +257,9 @@ protected function parse(string $template, array $data = [], ?array $options = n */ protected function parseSingle(string $key, string $val): array { - $pattern = '#' . $this->leftDelimiter . '!?\s*' . preg_quote($key, '#') . '(?(?=\s*\|\s*)(\s*\|*\s*([|\w<>=\(\),:.\-\s\+\\\\/]+)*\s*))(\s*)!?' . $this->rightDelimiter . '#ums'; + $pattern = '#' . $this->leftDelimiter . '!?\s*' . preg_quote($key, '#') + . '(?(?=\s*\|\s*)(\s*\|*\s*([|\w<>=\(\),:.\-\s\+\\\\/]+)*\s*))(\s*)!?' + . $this->rightDelimiter . '#ums'; return [$pattern => $val]; } @@ -506,7 +508,10 @@ protected function replaceSingle($pattern, $content, $template, bool $escape = f // Replace the content in the template return preg_replace_callback($pattern, function ($matches) use ($content, $escape) { // Check for {! !} syntax to not escape this one. - if (strpos($matches[0], '{!') === 0 && substr($matches[0], -2) === '!}') { + if ( + strpos($matches[0], $this->leftDelimiter . '!') === 0 + && substr($matches[0], -1 - strlen($this->rightDelimiter)) === '!' . $this->rightDelimiter + ) { $escape = false; } diff --git a/tests/system/AutoReview/ComposerJsonTest.php b/tests/system/AutoReview/ComposerJsonTest.php index 80ebcaeeb172..dc9ad26b9ef8 100644 --- a/tests/system/AutoReview/ComposerJsonTest.php +++ b/tests/system/AutoReview/ComposerJsonTest.php @@ -27,6 +27,7 @@ final class ComposerJsonTest extends TestCase { private array $devComposer; private array $frameworkComposer; + private array $starterComposer; protected function setUp(): void { @@ -34,15 +35,12 @@ protected function setUp(): void $this->devComposer = $this->getComposerJson(dirname(__DIR__, 3) . '/composer.json'); $this->frameworkComposer = $this->getComposerJson(dirname(__DIR__, 3) . '/admin/framework/composer.json'); + $this->starterComposer = $this->getComposerJson(dirname(__DIR__, 3) . '/admin/starter/composer.json'); } public function testFrameworkRequireIsTheSameWithDevRequire(): void { - $this->assertSame( - $this->devComposer['require'], - $this->frameworkComposer['require'], - 'The framework\'s "require" section is not updated with the main composer.json.' - ); + $this->checkFramework('require'); } public function testFrameworkRequireDevIsTheSameWithDevRequireDev(): void @@ -69,11 +67,35 @@ public function testFrameworkRequireDevIsTheSameWithDevRequireDev(): void } public function testFrameworkSuggestIsTheSameWithDevSuggest(): void + { + $this->checkFramework('suggest'); + } + + public function testFrameworkConfigIsTheSameWithDevSuggest(): void + { + $this->checkFramework('config'); + } + + public function testStarterConfigIsTheSameWithDevSuggest(): void + { + $this->checkStarter('config'); + } + + private function checkFramework(string $section): void + { + $this->assertSame( + $this->devComposer[$section], + $this->frameworkComposer[$section], + 'The framework\'s "' . $section . '" section is not updated with the main composer.json.' + ); + } + + private function checkStarter(string $section): void { $this->assertSame( - $this->devComposer['suggest'], - $this->frameworkComposer['suggest'], - 'The framework\'s "suggest" section is not updated with the main composer.json.' + $this->devComposer[$section], + $this->starterComposer[$section], + 'The starter\'s "' . $section . '" section is not updated with the main composer.json.' ); } diff --git a/tests/system/Commands/BaseCommandTest.php b/tests/system/Commands/BaseCommandTest.php index e1b857e40060..068a8d832924 100644 --- a/tests/system/Commands/BaseCommandTest.php +++ b/tests/system/Commands/BaseCommandTest.php @@ -35,14 +35,14 @@ public function testMagicIssetTrue() { $command = new AppInfo($this->logger, service('commands')); - $this->assertObjectHasAttribute('group', $command); + $this->assertTrue(isset($command->group)); } public function testMagicIssetFalse() { $command = new AppInfo($this->logger, service('commands')); - $this->assertObjectNotHasAttribute('foobar', $command); + $this->assertFalse(isset($command->foobar)); } public function testMagicGet() diff --git a/tests/system/Config/BaseConfigTest.php b/tests/system/Config/BaseConfigTest.php index 014deca6c344..3d1fe88c53a8 100644 --- a/tests/system/Config/BaseConfigTest.php +++ b/tests/system/Config/BaseConfigTest.php @@ -110,7 +110,7 @@ public function testEnvironmentOverrides() // override config with shortPrefix ENV var $this->assertSame('hubbahubba', $config->delta); // incorrect env name should not inject property - $this->assertObjectNotHasAttribute('notthere', $config); + $this->assertFalse(property_exists($config, 'notthere')); // empty ENV var should not affect config setting $this->assertSame('pineapple', $config->fruit); // non-empty ENV var should overrideconfig setting diff --git a/tests/system/Database/Builder/InsertTest.php b/tests/system/Database/Builder/InsertTest.php index 6601c6847366..b0fc3890cc3c 100644 --- a/tests/system/Database/Builder/InsertTest.php +++ b/tests/system/Database/Builder/InsertTest.php @@ -16,6 +16,7 @@ use CodeIgniter\Database\RawSql; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Mock\MockConnection; +use InvalidArgumentException; /** * @internal @@ -279,4 +280,22 @@ public function testInsertBatchThrowsExceptionOnEmptyData() $this->expectExceptionMessage('insertBatch() has no data.'); $builder->insertBatch([]); } + + public function testSetIncorrectRawSqlUsage() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'RawSql "expires = DATE_ADD(NOW(), INTERVAL 2 HOUR)" cannot be used here.' + ); + + $builder = $this->db->table('auth_bearer'); + + $builder->testMode() + ->set([ + 'jti' => 'jti', + 'proctorID' => '12', + ]) + ->set(new RawSql('expires = DATE_ADD(NOW(), INTERVAL 2 HOUR)')) + ->insert(); + } } diff --git a/tests/system/Database/Builder/WhereTest.php b/tests/system/Database/Builder/WhereTest.php index 4d5884dc05a0..b4a1b90a59f4 100644 --- a/tests/system/Database/Builder/WhereTest.php +++ b/tests/system/Database/Builder/WhereTest.php @@ -160,6 +160,53 @@ public function testWhereCustomString() $this->assertSame($expectedBinds, $builder->getBinds()); } + public function testWhereCustomStringWithOperatorEscapeFalse() + { + $builder = $this->db->table('jobs'); + + $where = 'CURRENT_TIMESTAMP() = DATE_ADD(column, INTERVAL 2 HOUR)'; + $builder->where($where, null, false); + + $expectedSQL = 'SELECT * FROM "jobs" WHERE CURRENT_TIMESTAMP() = DATE_ADD(column, INTERVAL 2 HOUR)'; + $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + + $expectedBinds = []; + $this->assertSame($expectedBinds, $builder->getBinds()); + } + + public function testWhereCustomStringWithoutOperatorEscapeFalse() + { + $builder = $this->db->table('jobs'); + + $where = "REPLACE(column, 'somestring', '')"; + $builder->where($where, "''", false); + + $expectedSQL = "SELECT * FROM \"jobs\" WHERE REPLACE(column, 'somestring', '') = ''"; + $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + + $expectedBinds = [ + "REPLACE(column, 'somestring', '')" => [ + 0 => "''", + 1 => false, + ], + ]; + $this->assertSame($expectedBinds, $builder->getBinds()); + } + + public function testWhereCustomStringWithBetweenEscapeFalse() + { + $builder = $this->db->table('jobs'); + + $where = "created_on BETWEEN '2022-07-01 00:00:00' AND '2022-12-31 23:59:59'"; + $builder->where($where, null, false); + + $expectedSQL = "SELECT * FROM \"jobs\" WHERE created_on BETWEEN '2022-07-01 00:00:00' AND '2022-12-31 23:59:59'"; + $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + + $expectedBinds = []; + $this->assertSame($expectedBinds, $builder->getBinds()); + } + public function testWhereRawSql() { $builder = $this->db->table('jobs'); @@ -174,6 +221,66 @@ public function testWhereRawSql() $this->assertSame($expectedBinds, $builder->getBinds()); } + public function testWhereValueRawSql() + { + $sql = $this->db->table('auth_bearer') + ->select('*') + ->where('expires', new RawSql('DATE_ADD(NOW(), INTERVAL 2 HOUR)')) + ->getCompiledSelect(true); + + $expected = <<<'SQL' + SELECT * + FROM "auth_bearer" + WHERE "expires" = DATE_ADD(NOW(), INTERVAL 2 HOUR) + SQL; + $this->assertSame($expected, $sql); + } + + public function testWhereKeyOnlyRawSql() + { + $sql = $this->db->table('auth_bearer') + ->select('*') + ->where(new RawSql('DATE_ADD(NOW(), INTERVAL 2 HOUR)'), '2023-01-01') + ->getCompiledSelect(true); + + $expected = <<<'SQL' + SELECT * + FROM "auth_bearer" + WHERE DATE_ADD(NOW(), INTERVAL 2 HOUR) = '2023-01-01' + SQL; + $this->assertSame($expected, $sql); + } + + public function testWhereKeyAndValueRawSql() + { + $sql = $this->db->table('auth_bearer') + ->select('*') + ->where(new RawSql('CURRENT_TIMESTAMP()'), new RawSql('DATE_ADD(column, INTERVAL 2 HOUR)')) + ->getCompiledSelect(true); + + $expected = <<<'SQL' + SELECT * + FROM "auth_bearer" + WHERE CURRENT_TIMESTAMP() = DATE_ADD(column, INTERVAL 2 HOUR) + SQL; + $this->assertSame($expected, $sql); + } + + public function testWhereKeyAndValueRawSqlWithOperator() + { + $sql = $this->db->table('auth_bearer') + ->select('*') + ->where(new RawSql('CURRENT_TIMESTAMP() >='), new RawSql('DATE_ADD(column, INTERVAL 2 HOUR)')) + ->getCompiledSelect(true); + + $expected = <<<'SQL' + SELECT * + FROM "auth_bearer" + WHERE CURRENT_TIMESTAMP() >= DATE_ADD(column, INTERVAL 2 HOUR) + SQL; + $this->assertSame($expected, $sql); + } + public function testWhereValueSubQuery() { $expectedSQL = 'SELECT * FROM "neworder" WHERE "advance_amount" < (SELECT MAX(advance_amount) FROM "orders" WHERE "id" > 2)'; diff --git a/tests/system/Entity/EntityTest.php b/tests/system/Entity/EntityTest.php index 85addb58e56e..1e03157ab188 100644 --- a/tests/system/Entity/EntityTest.php +++ b/tests/system/Entity/EntityTest.php @@ -96,7 +96,7 @@ public function testFill() $this->assertSame(123, $entity->foo); $this->assertSame('bar:234:bar', $entity->bar); - $this->assertObjectNotHasAttribute('baz', $entity); + $this->assertSame(4556, $entity->baz); } /** @@ -429,7 +429,7 @@ public function testCastTimestamp() public function testCastTimestampException() { $this->expectException(CastException::class); - $this->expectErrorMessage('Type casting "timestamp" expects a correct timestamp.'); + $this->expectExceptionMessage('Type casting "timestamp" expects a correct timestamp.'); $entity = $this->getCastEntity(); $entity->ninth = 'some string'; @@ -725,7 +725,7 @@ public function testCustomCast() public function testCustomCastException() { $this->expectException(CastException::class); - $this->expectErrorMessage('The "Tests\Support\Entity\Cast\NotExtendsBaseCast" class must inherit the "CodeIgniter\Entity\Cast\BaseCast" class'); + $this->expectExceptionMessage('The "Tests\Support\Entity\Cast\NotExtendsBaseCast" class must inherit the "CodeIgniter\Entity\Cast\BaseCast" class'); $entity = $this->getCustomCastEntity(); diff --git a/tests/system/HTTP/IncomingRequestDetectingTest.php b/tests/system/HTTP/IncomingRequestDetectingTest.php index ab0bfb5c6092..cd220cbe0987 100644 --- a/tests/system/HTTP/IncomingRequestDetectingTest.php +++ b/tests/system/HTTP/IncomingRequestDetectingTest.php @@ -31,127 +31,158 @@ protected function setUp(): void $_POST = $_GET = $_SERVER = $_REQUEST = $_ENV = $_COOKIE = $_SESSION = []; - $origin = 'http://www.example.com/index.php/woot?code=good#pos'; - + // The URI object is not used in detectPath(). + $origin = 'http://www.example.com/index.php/woot?code=good#pos'; $this->request = new IncomingRequest(new App(), new URI($origin), null, new UserAgent()); } public function testPathDefault() { - $this->request->uri = '/index.php/woot?code=good#pos'; + // /index.php/woot?code=good#pos $_SERVER['REQUEST_URI'] = '/index.php/woot'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = 'woot'; + + $expected = 'woot'; $this->assertSame($expected, $this->request->detectPath()); } - public function testPathEmpty() + public function testPathDefaultEmpty() { - $this->request->uri = '/'; + // / $_SERVER['REQUEST_URI'] = '/'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = '/'; + + $expected = '/'; $this->assertSame($expected, $this->request->detectPath()); } public function testPathRequestURI() { - $this->request->uri = '/index.php/woot?code=good#pos'; + // /index.php/woot?code=good#pos $_SERVER['REQUEST_URI'] = '/index.php/woot'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = 'woot'; + + $expected = 'woot'; $this->assertSame($expected, $this->request->detectPath('REQUEST_URI')); } public function testPathRequestURINested() { - $this->request->uri = '/ci/index.php/woot?code=good#pos'; + // I'm not sure but this is a case of Apache config making such SERVER + // values? + // The current implementation doesn't use the value of the URI object. + // So I removed the code to set URI. Therefore, it's exactly the same as + // the method above as a test. + // But it may be changed in the future to use the value of the URI object. + // So I don't remove this test case. + + // /ci/index.php/woot?code=good#pos $_SERVER['REQUEST_URI'] = '/index.php/woot'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = 'woot'; + + $expected = 'woot'; $this->assertSame($expected, $this->request->detectPath('REQUEST_URI')); } public function testPathRequestURISubfolder() { - $this->request->uri = '/ci/index.php/popcorn/woot?code=good#pos'; + // /ci/index.php/popcorn/woot?code=good#pos $_SERVER['REQUEST_URI'] = '/ci/index.php/popcorn/woot'; $_SERVER['SCRIPT_NAME'] = '/ci/index.php'; - $expected = 'popcorn/woot'; + + $expected = 'popcorn/woot'; $this->assertSame($expected, $this->request->detectPath('REQUEST_URI')); } public function testPathRequestURINoIndex() { - $this->request->uri = '/sub/example'; + // /sub/example $_SERVER['REQUEST_URI'] = '/sub/example'; $_SERVER['SCRIPT_NAME'] = '/sub/index.php'; - $expected = 'example'; + + $expected = 'example'; $this->assertSame($expected, $this->request->detectPath('REQUEST_URI')); } public function testPathRequestURINginx() { - $this->request->uri = '/ci/index.php/woot?code=good#pos'; + // /ci/index.php/woot?code=good#pos $_SERVER['REQUEST_URI'] = '/index.php/woot?code=good'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = 'woot'; + + $expected = 'woot'; $this->assertSame($expected, $this->request->detectPath('REQUEST_URI')); } public function testPathRequestURINginxRedirecting() { - $this->request->uri = '/?/ci/index.php/woot'; + // /?/ci/index.php/woot $_SERVER['REQUEST_URI'] = '/?/ci/woot'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = 'ci/woot'; + + $expected = 'ci/woot'; $this->assertSame($expected, $this->request->detectPath('REQUEST_URI')); } public function testPathRequestURISuppressed() { - $this->request->uri = '/woot?code=good#pos'; + // /woot?code=good#pos $_SERVER['REQUEST_URI'] = '/woot'; $_SERVER['SCRIPT_NAME'] = '/'; - $expected = 'woot'; + + $expected = 'woot'; $this->assertSame($expected, $this->request->detectPath('REQUEST_URI')); } public function testPathQueryString() { - $this->request->uri = '/?/ci/index.php/woot'; - $_SERVER['REQUEST_URI'] = '/?/ci/woot'; + // /index.php?/ci/woot + $_SERVER['REQUEST_URI'] = '/index.php?/ci/woot'; $_SERVER['QUERY_STRING'] = '/ci/woot'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = 'ci/woot'; + + $expected = 'ci/woot'; + $this->assertSame($expected, $this->request->detectPath('QUERY_STRING')); + } + + public function testPathQueryStringWithQueryString() + { + // /index.php?/ci/woot?code=good#pos + $_SERVER['REQUEST_URI'] = '/index.php?/ci/woot?code=good'; + $_SERVER['QUERY_STRING'] = '/ci/woot?code=good'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; + + $expected = 'ci/woot'; $this->assertSame($expected, $this->request->detectPath('QUERY_STRING')); } public function testPathQueryStringEmpty() { - $this->request->uri = '/?/ci/index.php/woot'; - $_SERVER['REQUEST_URI'] = '/?/ci/woot'; + // /index.php? + $_SERVER['REQUEST_URI'] = '/index.php?'; $_SERVER['QUERY_STRING'] = ''; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = ''; + + $expected = '/'; $this->assertSame($expected, $this->request->detectPath('QUERY_STRING')); } public function testPathPathInfo() { - $this->request->uri = '/index.php/woot?code=good#pos'; + // /index.php/woot?code=good#pos $this->request->setGlobal('server', [ 'PATH_INFO' => null, ]); $_SERVER['REQUEST_URI'] = '/index.php/woot'; $_SERVER['SCRIPT_NAME'] = '/index.php'; - $expected = 'woot'; + + $expected = 'woot'; $this->assertSame($expected, $this->request->detectPath('PATH_INFO')); } public function testPathPathInfoGlobal() { - $this->request->uri = '/index.php/woot?code=good#pos'; + // /index.php/woot?code=good#pos $this->request->setGlobal('server', [ 'PATH_INFO' => 'silliness', ]); diff --git a/tests/system/HTTP/URITest.php b/tests/system/HTTP/URITest.php index c88a4ca6799a..14de6e3d587b 100644 --- a/tests/system/HTTP/URITest.php +++ b/tests/system/HTTP/URITest.php @@ -978,6 +978,22 @@ public function testSetURISilent() $this->assertTrue(true); } + public function testCreateURIStringNoArguments() + { + $uri = URI::createURIString(); + + $expected = ''; + $this->assertSame($expected, $uri); + } + + public function testCreateURIStringOnlyAuthority() + { + $uri = URI::createURIString(null, 'example.com'); + + $expected = 'example.com'; + $this->assertSame($expected, $uri); + } + public function testCreateURIString() { $uri = URI::createURIString('https', 'example.com', '/'); diff --git a/tests/system/Helpers/FormHelperTest.php b/tests/system/Helpers/FormHelperTest.php index 4e1ea582dd2d..9a525adf7ad2 100644 --- a/tests/system/Helpers/FormHelperTest.php +++ b/tests/system/Helpers/FormHelperTest.php @@ -996,6 +996,25 @@ public function testValidationShowError() $this->assertSame('The ID field is required.' . "\n", $html); } + public function testValidationShowErrorForWildcards() + { + $validation = Services::validation(); + $validation->setRule('user.*.name', 'Name', 'required') + ->run([ + 'user' => [ + 'friends' => [ + ['name' => 'Name1'], + ['name' => ''], + ['name' => 'Name2'], + ], + ], + ]); + + $html = validation_show_error('user.*.name'); + + $this->assertSame('The Name field is required.' . "\n", $html); + } + public function testFormParseFormAttributesTrue() { $expected = 'readonly '; diff --git a/tests/system/Helpers/URLHelper/CurrentUrlTest.php b/tests/system/Helpers/URLHelper/CurrentUrlTest.php index 1caafc192f46..faee0608b54f 100644 --- a/tests/system/Helpers/URLHelper/CurrentUrlTest.php +++ b/tests/system/Helpers/URLHelper/CurrentUrlTest.php @@ -63,6 +63,10 @@ public function testCurrentURLReturnsBasicURL() $this->config->baseURL = 'http://example.com/public'; + // URI object are updated in IncomingRequest constructor. + $request = Services::incomingrequest($this->config); + Services::injectMock('request', $request); + $this->assertSame('http://example.com/public/index.php/', current_url()); } @@ -163,7 +167,7 @@ public function testCurrentURLWithPortInSubfolder() $this->assertSame(8080, $uri->getPort()); } - public function testUriStringAbsolute() + public function testUriString() { $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/assets/image.jpg'; @@ -183,18 +187,7 @@ private function setService(string $uri): void Services::injectMock('request', $request); } - public function testUriStringRelative() - { - $_SERVER['HTTP_HOST'] = 'example.com'; - $_SERVER['REQUEST_URI'] = '/assets/image.jpg'; - - $uri = 'http://example.com/assets/image.jpg'; - $this->setService($uri); - - $this->assertSame('assets/image.jpg', uri_string(true)); - } - - public function testUriStringNoTrailingSlashAbsolute() + public function testUriStringNoTrailingSlash() { $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/assets/image.jpg'; @@ -207,33 +200,12 @@ public function testUriStringNoTrailingSlashAbsolute() $this->assertSame('assets/image.jpg', uri_string()); } - public function testUriStringNoTrailingSlashRelative() - { - $_SERVER['HTTP_HOST'] = 'example.com'; - $_SERVER['REQUEST_URI'] = '/assets/image.jpg'; - - $this->config->baseURL = 'http://example.com'; - - $uri = 'http://example.com/assets/image.jpg'; - $this->setService($uri); - - $this->assertSame('assets/image.jpg', uri_string(true)); - } - - public function testUriStringEmptyAbsolute() + public function testUriStringEmpty() { $uri = 'http://example.com/'; $this->setService($uri); - $this->assertSame('/', uri_string()); - } - - public function testUriStringEmptyRelative() - { - $uri = 'http://example.com/'; - $this->setService($uri); - - $this->assertSame('', uri_string(true)); + $this->assertSame('', uri_string()); } public function testUriStringSubfolderAbsolute() @@ -260,7 +232,7 @@ public function testUriStringSubfolderRelative() $uri = 'http://example.com/subfolder/assets/image.jpg'; $this->setService($uri); - $this->assertSame('assets/image.jpg', uri_string(true)); + $this->assertSame('assets/image.jpg', uri_string()); } public function urlIsProvider() diff --git a/tests/system/Helpers/URLHelper/MiscUrlTest.php b/tests/system/Helpers/URLHelper/MiscUrlTest.php index fb949f2e9ecc..7f5d9667501a 100644 --- a/tests/system/Helpers/URLHelper/MiscUrlTest.php +++ b/tests/system/Helpers/URLHelper/MiscUrlTest.php @@ -884,4 +884,20 @@ public function urlToMissingRoutesProvider() ], ]; } + + public function testUrlToWithSupportedLocaleInRoute() + { + Services::createRequest(new App()); + $routes = service('routes'); + $routes->add( + '{locale}/path/(:segment)/to/(:num)', + 'myController::goto/$1/$2', + ['as' => 'path-to'] + ); + + $this->assertSame( + 'http://example.com/index.php/en/path/string/to/13', + url_to('path-to', 'string', 13, 'en') + ); + } } diff --git a/tests/system/Helpers/URLHelper/SiteUrlTest.php b/tests/system/Helpers/URLHelper/SiteUrlTest.php index 1f6f081b4dc2..e05a7093e3da 100644 --- a/tests/system/Helpers/URLHelper/SiteUrlTest.php +++ b/tests/system/Helpers/URLHelper/SiteUrlTest.php @@ -59,27 +59,31 @@ protected function tearDown(): void * @param bool $secure * @param string $path * @param string $expectedSiteUrl + * @param string $expectedBaseUrl * * @dataProvider configProvider */ - public function testUrls($baseURL, $indexPage, $scheme, $secure, $path, $expectedSiteUrl) - { + public function testUrls( + $baseURL, + $indexPage, + $scheme, + $secure, + $path, + $expectedSiteUrl, + $expectedBaseUrl + ) { // Set the config $this->config->baseURL = $baseURL; $this->config->indexPage = $indexPage; $this->config->forceGlobalSecureRequests = $secure; $this->assertSame($expectedSiteUrl, site_url($path, $scheme, $this->config)); - - // base_url is always the trimmed site_url without index page - $expectedBaseUrl = $indexPage === '' ? $expectedSiteUrl : str_replace('/' . $indexPage, '', $expectedSiteUrl); - $expectedBaseUrl = rtrim($expectedBaseUrl, '/'); $this->assertSame($expectedBaseUrl, base_url($path, $scheme)); } public function configProvider() { - // baseURL, indexPage, scheme, secure, path, expectedSiteUrl + // baseURL, indexPage, scheme, secure, path, expectedSiteUrl, expectedBaseUrl return [ 'forceGlobalSecure' => [ 'http://example.com/', @@ -88,6 +92,7 @@ public function configProvider() true, '', 'https://example.com/index.php', + 'https://example.com/', ], [ 'http://example.com/', @@ -96,14 +101,16 @@ public function configProvider() false, '', 'http://example.com/index.php', + 'http://example.com/', ], - [ + 'baseURL missing /' => [ 'http://example.com', 'index.php', null, false, '', 'http://example.com/index.php', + 'http://example.com/', ], [ 'http://example.com/', @@ -112,6 +119,7 @@ public function configProvider() false, '', 'http://example.com/', + 'http://example.com/', ], [ 'http://example.com/', @@ -120,6 +128,7 @@ public function configProvider() false, '', 'http://example.com/banana.php', + 'http://example.com/', ], [ 'http://example.com/', @@ -128,6 +137,34 @@ public function configProvider() false, 'abc', 'http://example.com/abc', + 'http://example.com/abc', + ], + [ + 'http://example.com/', + '', + null, + false, + '/abc', + 'http://example.com/abc', + 'http://example.com/abc', + ], + [ + 'http://example.com/', + '', + null, + false, + '/abc/', + 'http://example.com/abc/', + 'http://example.com/abc/', + ], + [ + 'http://example.com/', + '', + null, + false, + '/abc/def', + 'http://example.com/abc/def', + 'http://example.com/abc/def', ], 'URL decode' => [ 'http://example.com/', @@ -136,6 +173,7 @@ public function configProvider() false, 'template/meet-%26-greet', 'http://example.com/template/meet-&-greet', + 'http://example.com/template/meet-&-greet', ], 'URL encode' => [ 'http://example.com/', @@ -144,6 +182,7 @@ public function configProvider() false, 'alert', 'http://example.com/%3Cs%3Ealert%3C/s%3E', + 'http://example.com/%3Cs%3Ealert%3C/s%3E', ], [ 'http://example.com/public/', @@ -152,6 +191,7 @@ public function configProvider() false, '', 'http://example.com/public/index.php', + 'http://example.com/public/', ], [ 'http://example.com/public/', @@ -160,6 +200,7 @@ public function configProvider() false, '', 'http://example.com/public/', + 'http://example.com/public/', ], [ 'http://example.com/public', @@ -168,6 +209,7 @@ public function configProvider() false, '', 'http://example.com/public/', + 'http://example.com/public/', ], [ 'http://example.com/public', @@ -176,6 +218,7 @@ public function configProvider() false, '/', 'http://example.com/public/index.php/', + 'http://example.com/public/', ], [ 'http://example.com/public/', @@ -184,6 +227,7 @@ public function configProvider() false, '/', 'http://example.com/public/index.php/', + 'http://example.com/public/', ], [ 'http://example.com/', @@ -192,6 +236,7 @@ public function configProvider() false, 'foo', 'http://example.com/index.php/foo', + 'http://example.com/foo', ], [ 'http://example.com/', @@ -200,6 +245,7 @@ public function configProvider() false, '0', 'http://example.com/index.php/0', + 'http://example.com/0', ], [ 'http://example.com/public', @@ -208,6 +254,7 @@ public function configProvider() false, 'foo', 'http://example.com/public/index.php/foo', + 'http://example.com/public/foo', ], [ 'http://example.com/', @@ -216,6 +263,7 @@ public function configProvider() false, 'foo?bar=bam', 'http://example.com/index.php/foo?bar=bam', + 'http://example.com/foo?bar=bam', ], [ 'http://example.com/', @@ -224,6 +272,7 @@ public function configProvider() false, 'test#banana', 'http://example.com/index.php/test#banana', + 'http://example.com/test#banana', ], [ 'http://example.com/', @@ -232,6 +281,7 @@ public function configProvider() false, 'foo', 'ftp://example.com/index.php/foo', + 'ftp://example.com/foo', ], [ 'http://example.com/', @@ -240,6 +290,7 @@ public function configProvider() false, 'news/local/123', 'http://example.com/index.php/news/local/123', + 'http://example.com/news/local/123', ], [ 'http://example.com/', @@ -248,6 +299,7 @@ public function configProvider() false, ['news', 'local', '123'], 'http://example.com/index.php/news/local/123', + 'http://example.com/news/local/123', ], ]; } @@ -267,12 +319,12 @@ public function testBaseURLDiscovery() $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/test'; - $this->assertSame('http://example.com', base_url()); + $this->assertSame('http://example.com/', base_url()); $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/test/page'; - $this->assertSame('http://example.com', base_url()); + $this->assertSame('http://example.com/', base_url()); $this->assertSame('http://example.com/profile', base_url('profile')); } @@ -289,6 +341,24 @@ public function testBaseURLService() $this->assertSame('http://example.com/ci/v4/controller/method', base_url('controller/method', null)); } + public function testBaseURLWithCLIRequest() + { + unset($_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI']); + + $this->config->baseURL = 'http://example.com/'; + $request = Services::clirequest($this->config); + Services::injectMock('request', $request); + + $this->assertSame( + 'http://example.com/index.php/controller/method', + site_url('controller/method', null, $this->config) + ); + $this->assertSame( + 'http://example.com/controller/method', + base_url('controller/method', null) + ); + } + public function testSiteURLWithAllowedHostname() { $_SERVER['HTTP_HOST'] = 'www.example.jp'; @@ -297,6 +367,7 @@ public function testSiteURLWithAllowedHostname() $this->config->baseURL = 'http://example.com/public/'; $this->config->allowedHostnames = ['www.example.jp']; + Services::injectMock('config', $this->config); // URI object are updated in IncomingRequest constructor. $request = Services::incomingrequest($this->config); @@ -304,7 +375,29 @@ public function testSiteURLWithAllowedHostname() $this->assertSame( 'http://www.example.jp/public/index.php/controller/method', - site_url('controller/method', null, $this->config) + site_url('controller/method') + ); + } + + public function testSiteURLWithAltConfig() + { + $_SERVER['HTTP_HOST'] = 'www.example.jp'; + $_SERVER['REQUEST_URI'] = '/public'; + $_SERVER['SCRIPT_NAME'] = '/public/index.php'; + + $this->config->baseURL = 'http://example.com/public/'; + $this->config->allowedHostnames = ['www.example.jp']; + + // URI object are updated in IncomingRequest constructor. + $request = Services::incomingrequest($this->config); + Services::injectMock('request', $request); + + $altConfig = clone $this->config; + $altConfig->baseURL = 'http://alt.example.com/public/'; + + $this->assertSame( + 'http://alt.example.com/public/index.php/controller/method', + site_url('controller/method', null, $altConfig) ); } diff --git a/tests/system/Log/Handlers/ChromeLoggerHandlerTest.php b/tests/system/Log/Handlers/ChromeLoggerHandlerTest.php index 0587763bec09..88a27b45b351 100644 --- a/tests/system/Log/Handlers/ChromeLoggerHandlerTest.php +++ b/tests/system/Log/Handlers/ChromeLoggerHandlerTest.php @@ -66,10 +66,11 @@ public function testSetDateFormat() $config->handlers['CodeIgniter\Log\Handlers\TestHandler']['handles'] = ['critical']; $logger = new ChromeLoggerHandler($config->handlers['CodeIgniter\Log\Handlers\TestHandler']); + $result = $logger->setDateFormat('F j, Y'); - $this->assertObjectHasAttribute('dateFormat', $result); - $this->assertObjectHasAttribute('dateFormat', $logger); + $this->assertSame('F j, Y', $this->getPrivateProperty($result, 'dateFormat')); + $this->assertSame('F j, Y', $this->getPrivateProperty($logger, 'dateFormat')); } public function testChromeLoggerHeaderSent() diff --git a/tests/system/Router/RouteCollectionTest.php b/tests/system/Router/RouteCollectionTest.php index 70d378467414..6458449a02c9 100644 --- a/tests/system/Router/RouteCollectionTest.php +++ b/tests/system/Router/RouteCollectionTest.php @@ -1782,15 +1782,16 @@ public function testGetRegisteredControllersReturnsOneControllerWhenTwoRoutsWith public function testGetRegisteredControllersReturnsAllControllers() { $collection = $this->getCollector(); - $collection->get('test', '\App\Controllers\Hello::get'); - $collection->post('test', '\App\Controllers\Hello::post'); - $collection->post('hello', '\App\Controllers\Test::hello'); + $collection->get('test', '\App\Controllers\HelloGet::get'); + $collection->post('test', '\App\Controllers\HelloPost::post'); + $collection->post('hello', '\App\Controllers\TestPost::hello'); $routes = $collection->getRegisteredControllers('*'); $expects = [ - '\App\Controllers\Hello', - '\App\Controllers\Test', + '\App\Controllers\HelloGet', + '\App\Controllers\HelloPost', + '\App\Controllers\TestPost', ]; $this->assertSame($expects, $routes); } diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index 5e7733ee4ff4..5be01a47bfc6 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -39,6 +39,7 @@ protected function setUp(): void $this->collection = new RouteCollection(Services::locator(), $moduleConfig); $routes = [ + '/' => 'Home::index', 'users' => 'Users::index', 'user-setting/show-list' => 'User_setting::show_list', 'user-setting/(:segment)' => 'User_setting::detail/$1', @@ -56,20 +57,20 @@ protected function setUp(): void 'objects/(:segment)/sort/(:segment)/([A-Z]{3,7})' => 'AdminList::objectsSortCreate/$1/$2/$3', '(:segment)/(:segment)/(:segment)' => '$2::$3/$1', ]; - $this->collection->map($routes); + $this->request = Services::request(); $this->request->setMethod('get'); } - public function testEmptyURIMatchesDefaults() + public function testEmptyURIMatchesRoot() { $router = new Router($this->collection, $this->request); $router->handle(''); - $this->assertSame($this->collection->getDefaultController(), $router->controllerName()); - $this->assertSame($this->collection->getDefaultMethod(), $router->methodName()); + $this->assertSame('\Home', $router->controllerName()); + $this->assertSame('index', $router->methodName()); } public function testZeroAsURIPath() diff --git a/tests/system/Session/Handlers/Database/RedisHandlerTest.php b/tests/system/Session/Handlers/Database/RedisHandlerTest.php index 4a4cdcc45f19..b5d67a768c47 100644 --- a/tests/system/Session/Handlers/Database/RedisHandlerTest.php +++ b/tests/system/Session/Handlers/Database/RedisHandlerTest.php @@ -54,6 +54,41 @@ protected function getInstance($options = []) return new RedisHandler(new AppConfig(), $this->userIpAddress); } + public function testSavePathWithoutProtocol() + { + $handler = $this->getInstance( + ['savePath' => '127.0.0.1:6379'] + ); + + $savePath = $this->getPrivateProperty($handler, 'savePath'); + + $this->assertSame('tcp', $savePath['protocol']); + } + + public function testSavePathTLSAuth() + { + $handler = $this->getInstance( + ['savePath' => 'tls://127.0.0.1:6379?auth=password'] + ); + + $savePath = $this->getPrivateProperty($handler, 'savePath'); + + $this->assertSame('tls', $savePath['protocol']); + $this->assertSame('password', $savePath['password']); + } + + public function testSavePathTCPAuth() + { + $handler = $this->getInstance( + ['savePath' => 'tcp://127.0.0.1:6379?auth=password'] + ); + + $savePath = $this->getPrivateProperty($handler, 'savePath'); + + $this->assertSame('tcp', $savePath['protocol']); + $this->assertSame('password', $savePath['password']); + } + public function testSavePathTimeoutFloat() { $handler = $this->getInstance( @@ -82,6 +117,19 @@ public function testOpen() $this->assertTrue($handler->open($this->sessionSavePath, $this->sessionName)); } + public function testOpenWithDefaultProtocol() + { + $default = $this->sessionSavePath; + + $this->sessionSavePath = '127.0.0.1:6379'; + + $handler = $this->getInstance(); + $this->assertTrue($handler->open($this->sessionSavePath, $this->sessionName)); + + // Rollback to default + $this->sessionSavePath = $default; + } + public function testWrite() { $handler = $this->getInstance(); diff --git a/tests/system/Test/FabricatorTest.php b/tests/system/Test/FabricatorTest.php index 7cadb350e047..6f92be2aaaad 100644 --- a/tests/system/Test/FabricatorTest.php +++ b/tests/system/Test/FabricatorTest.php @@ -406,7 +406,7 @@ public function testCreateMockSetsDatabaseFields() $this->assertIsInt($result->created_at); $this->assertIsInt($result->updated_at); - $this->assertObjectHasAttribute('deleted_at', $result); + $this->assertTrue(property_exists($result, 'deleted_at')); $this->assertNull($result->deleted_at); } diff --git a/tests/system/Throttle/ThrottleTest.php b/tests/system/Throttle/ThrottleTest.php index 4bff58c01f51..2121636baefa 100644 --- a/tests/system/Throttle/ThrottleTest.php +++ b/tests/system/Throttle/ThrottleTest.php @@ -187,4 +187,138 @@ public function testFlooding() $this->assertTrue($throttler->check('127.0.0.1', $rate, MINUTE, 0)); $this->assertSame(10.0, round($this->cache->get('throttler_127.0.0.1'))); } + + /** + * @dataProvider tokenTimeUsecases + */ + public function testTokenTimeCalculationUCs(int $capacity, int $seconds, array $checkInputs): void + { + $key = 'testkey'; + $throttler = new Throttler($this->cache); + + // clear $key before test start + $throttler->remove($key); + + foreach ($checkInputs as $index => $checkInput) { + $throttler->setTestTime($checkInput['testTime']); + $checkResult = $throttler->check($key, $capacity, $seconds, $checkInput['cost']); + + $this->assertSame($checkInput['expectedCheckResult'], $checkResult, "Input#{$index}: Wrong check() result"); + $this->assertSame($checkInput['expectedTokenTime'], $throttler->getTokenTime(), "Input#{$index}: Wrong tokenTime"); + } + } + + public function tokenTimeUsecases(): array + { + return [ + '2 capacity / 200 seconds (100s refresh, 0.01 tokens/s) -> 5 checks, 1 cost each' => [ + 'capacity' => 2, + 'seconds' => 200, + 'checkInputs' => [ + [ // 2 -> 1 + 'testTime' => 0, + 'cost' => 1, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +3s / 1.03 -> 0.03 + 'testTime' => 3, + 'cost' => 1, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +0s / 0.03 -> 0.03 / (1 - 0.03) * 100 = 97 + 'testTime' => 3, + 'cost' => 1, + 'expectedCheckResult' => false, + 'expectedTokenTime' => 97, + ], + [ // +1m 32s / 0.95 -> 0.95 / (1 - 0.95) * 100 = 5 + 'testTime' => 95, + 'cost' => 1, + 'expectedCheckResult' => false, + 'expectedTokenTime' => 5, + ], + [ // +7s / 1.02 -> 0.02 + 'testTime' => 102, + 'cost' => 1, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +13s / 0.15 / (1 - 0.15) * 100 = 85 + 'testTime' => 115, + 'cost' => 1, + 'expectedCheckResult' => false, + 'expectedTokenTime' => 85, + ], + ], + ], + '1 capacity / 3600 seconds (3600s refresh, 2.77e-4 tokens/s) -> 2 checks with 1 cost each' => [ + 'capacity' => 1, + 'seconds' => 3600, + 'checkInputs' => [ + [ // 1 -> 0 + 'testTime' => 0, + 'cost' => 1, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +6m / 0.1 -> 0.1 / (1 - 0.1) * 3600 = 3240 + 'testTime' => 360, + 'cost' => 1, + 'expectedCheckResult' => false, + 'expectedTokenTime' => 3240, + ], + ], + ], + '10 capacity / 200 seconds (20s refresh, 0.05 tokens/s) -> 7 checks with different costs (RNG)' => [ + 'capacity' => 10, + 'seconds' => 200, + 'checkInputs' => [ + [ // -2t / 10 -> 8 + 'testTime' => 0, + 'cost' => 2, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +19s -2t / 8.95 -> 6.95 + 'testTime' => 19, + 'cost' => 2, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +16s -3t / 7.75 -> 4.75 + 'testTime' => 35, + 'cost' => 3, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +4s -2t / 4.95 -> 2.95 + 'testTime' => 39, + 'cost' => 2, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +13s -5t / 3.6 -> -1.4 (blow allowed) + 'testTime' => 52, + 'cost' => 5, + 'expectedCheckResult' => true, + 'expectedTokenTime' => 0, + ], + [ // +2s -2t / -1.3 -> -1.3 / (1 - (-1.3)) * 20 = 46 + 'testTime' => 54, + 'cost' => 2, + 'expectedCheckResult' => false, + 'expectedTokenTime' => 46, + ], + [ // +7s -2t / -0.95 - -0.95 / (1 - (-0.95)) * 20 = 39 + 'testTime' => 61, + 'cost' => 2, + 'expectedCheckResult' => false, + 'expectedTokenTime' => 39, + ], + ], + ], + ]; + } } diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 6597866b3f88..2c6d52337fe9 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -102,6 +102,27 @@ public function testSetRuleStoresRule() ], $this->validation->getRules()); } + public function testSetRuleMultipleWithIndividual() + { + $this->validation->setRule('username', 'Username', 'required|min_length[3]'); + $this->validation->setRule('password', 'Password', ['required', 'min_length[8]', 'alpha_numeric_punct']); + + $this->assertSame([ + 'username' => [ + 'label' => 'Username', + 'rules' => 'required|min_length[3]', + ], + 'password' => [ + 'label' => 'Password', + 'rules' => [ + 'required', + 'min_length[8]', + 'alpha_numeric_punct', + ], + ], + ], $this->validation->getRules()); + } + public function testSetRuleAddsRule() { $this->validation->setRules([ @@ -113,14 +134,14 @@ public function testSetRuleAddsRule() $this->validation->setRule('foo', null, 'foo|foz'); $this->assertSame([ - 'foo' => [ - 'label' => null, - 'rules' => 'foo|foz', - ], 'bar' => [ 'label' => null, 'rules' => 'bar|baz', ], + 'foo' => [ + 'label' => null, + 'rules' => 'foo|foz', + ], ], $this->validation->getRules()); } @@ -142,6 +163,24 @@ public function testSetRuleOverwritesRule() ], $this->validation->getRules()); } + public function testSetRuleOverwritesRuleReverse() + { + $this->validation->setRule('foo', null, 'foo|foz'); + $this->validation->setRules([ + 'foo' => [ + 'label' => null, + 'rules' => 'bar|baz', + ], + ]); + + $this->assertSame([ + 'foo' => [ + 'label' => null, + 'rules' => 'bar|baz', + ], + ], $this->validation->getRules()); + } + /** * @dataProvider setRuleRulesFormatCaseProvider * diff --git a/tests/system/View/ParserFilterTest.php b/tests/system/View/ParserFilterTest.php index 696a16d2a19a..063c97acd972 100644 --- a/tests/system/View/ParserFilterTest.php +++ b/tests/system/View/ParserFilterTest.php @@ -396,6 +396,20 @@ public function testLocalCurrency() $this->assertSame("1.234.567,89\u{a0}€", $parser->renderString($template)); } + public function testLocalCurrencyWithoutFraction() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $data = [ + 'mynum' => 1_234_567.8912346, + ]; + + $template = '{ mynum|local_currency(EUR,de_DE) }'; + + $parser->setData($data); + $this->assertSame("1.234.568\u{a0}€", $parser->renderString($template)); + } + public function testParsePairWithAbs() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 9b969f08972d..cfa2ab1e81e8 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -600,11 +600,24 @@ public function testParserNoEscape() 'title' => '', ]; - $template = '{! title!}'; + $template = '{! title !}'; $this->parser->setData($data); $this->assertSame('', $this->parser->renderString($template)); } + public function testParserNoEscapeAndDelimiterChange() + { + $this->parser->setDelimiters('{{', '}}'); + + $data = [ + 'title' => '', + ]; + $this->parser->setData($data); + + $template = '{{! title !}}'; + $this->assertSame('', $this->parser->renderString($template)); + } + public function testIgnoresComments() { $data = [ diff --git a/user_guide_src/source/changelogs/index.rst b/user_guide_src/source/changelogs/index.rst index 401d178f285f..9932e8725891 100644 --- a/user_guide_src/source/changelogs/index.rst +++ b/user_guide_src/source/changelogs/index.rst @@ -12,6 +12,7 @@ See all the changes. .. toctree:: :titlesonly: + v4.3.2 v4.3.1 v4.3.0 v4.2.12 diff --git a/user_guide_src/source/changelogs/v4.0.3.rst b/user_guide_src/source/changelogs/v4.0.3.rst index 80a93c3100cf..126f01cc06e0 100644 --- a/user_guide_src/source/changelogs/v4.0.3.rst +++ b/user_guide_src/source/changelogs/v4.0.3.rst @@ -31,3 +31,7 @@ Bugs Fixed - Default pagination templates fixed to use the correct locale. - Lots of tweaks and corrections in the user guide. - Fixed locating files in custom namespaces that were occassionally not found. Primarily affected console commands. + +See the repo's +`CHANGELOG_4.0.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.0.4.rst b/user_guide_src/source/changelogs/v4.0.4.rst index 740a150f42cb..1e8a85c50277 100644 --- a/user_guide_src/source/changelogs/v4.0.4.rst +++ b/user_guide_src/source/changelogs/v4.0.4.rst @@ -9,6 +9,11 @@ Release Date: July 15, 2020 :local: :depth: 2 +BREAKING +-------- + +- Added ``$arguments`` parameter to ``after()`` and ``before()`` in ``FilterInterface``. This is a breaking change, so all code implementing the ``FilterInterface`` must be updated. + Enhancements ------------ @@ -24,11 +29,11 @@ Enhancements - New :doc:`command() helper function ` to programatically run your CLI commands. Useful for testing and cron jobs. - New command, ``make:seeder`` to generate a :doc:`Database Seed class ` skeleton file. - Colors now available on the CLI within Windows, as well as other Windows-related CLI improvements. -- New helper :doc:`mb_url_title ` that functions like ``url_title`` but automatically escapes and extended URL characters. +- New helper :doc:`mb_url_title() ` that functions like ``url_title()`` but automatically escapes and extended URL characters. - :doc:`Image library ` now supports ``webp`` images. - Added Unicode support for regular expressions in the Router. -- Added support for removing hidden folders in the :doc:`delete_files ` helper -- ``fetchGlobal`` in the Request class now supports applying filters to arrays of data, not just the first item. +- Added support for removing hidden folders in the :doc:`delete_files() ` helper. +- ``fetchGlobal()`` in the Request class now supports applying filters to arrays of data, not just the first item. - ``file`` validation now works with arrays of files. - URI class now supports a ``setSilent()`` method that will disable the throwing of Exceptions. - New argument to ``URI::getSegment()`` that allows us to change the default value returned if nothing exists. @@ -40,24 +45,27 @@ Bugs Fixed ---------- - Fixed location for the SQLite3 database which by default will be now located in a ``writable`` folder instead of the ``public`` folder. -- Fixed bug where ``force_https`` could add ``https://`` a second time. +- Fixed bug where ``force_https()`` could add ``https://`` a second time. - Fixed a bug with CurlRequest that could result in incorrect "100 Continue" headers. -- Image::save() bug fixed when ``$target`` parameter was ``null`` -- fixes for ``set_checkbox()`` and ``set_radio()`` when the $default parameter is set to ``true`` -- fix for result object handling in Model class . -- fixed escape character SQLite database -- fix for inserts on Postgres and Entities when the primary key was null +- Image::save() bug fixed when ``$target`` parameter was ``null``. +- fixes for ``set_checkbox()`` and ``set_radio()`` when the $default parameter is set to ``true``. +- fix for result object handling in Model class. +- fixed escape character SQLite database. +- fix for inserts on Postgres and Entities when the primary key was null. - CLI scripts can now correctly recognize dashes within arguments. -- CURLRequest now properly sets content length with multipart data -- Misc. stability improvements for the ImageMagick handler -- setting validation errors within a config file should now work +- CURLRequest now properly sets content length with multipart data. +- Misc. stability improvements for the ImageMagick handler. +- setting validation errors within a config file should now work. - Unicode characters are not escaped when saving JSON from Entities. -- redirecting with a custom HTTP code should work correctly now -- Time::setTimezone now working correctly -- added full outer join support for Postgres +- redirecting with a custom HTTP code should work correctly now. +- ``Time::setTimezone()`` now working correctly. +- added full outer join support for Postgres. - some cast items in the Entity (like array, json) were not being set correctly during a ``fill()`` process. -- Fixed bug in Image GD handler that would try to compress images twice in certain cases -- Ensure get translation output logic work on selected locale, dashed locale, and fallback "en" -- Fix is_unique/is_not_unique validation called on POST/PUT via API in Postgresql -- Added ``$arguments`` parameter to after() and before() in FilterInterface. This is a breaking change, so all code implementing the FilterInterface must be updated -- Fixed a bug where filter arguments were not passed to after() +- Fixed bug in Image GD handler that would try to compress images twice in certain cases. +- Ensure get translation output logic work on selected locale, dashed locale, and fallback "en". +- Fix ``is_unique``/``is_not_unique`` validation called on POST/PUT via API in PostgreSQL. +- Fixed a bug where filter arguments were not passed to ``after()``. + +See the repo's +`CHANGELOG_4.0.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.0.5.rst b/user_guide_src/source/changelogs/v4.0.5.rst index 33e14c380da6..777e2fd1193b 100644 --- a/user_guide_src/source/changelogs/v4.0.5.rst +++ b/user_guide_src/source/changelogs/v4.0.5.rst @@ -12,36 +12,29 @@ Release Date: January 31, 2021 Enhancements ------------ -- New URL helper function ``url_to()`` which creates absolute URLs based on controllers. +- New URL helper function :php:func:`url_to()` which creates absolute URLs based on controllers. - New Model option: ``$useAutoIncrement`` which when set to ``false`` allows you to provide your own primary key for each record in the table. Handy when we want to implement 1:1 relation or use UUIDs for our model. -- New URL helper function ``url_is()`` which allows you to check the current URL to see if matches the given string. +- New URL helper function :php:func:`url_is()` which allows you to check the current URL to see if matches the given string. - Services now have their config parameters strictly typehinted. This will ensure no one will pass a different config instance. If you need to pass a new config with additional properties, you need to extend that particular config. - Support for setting SameSite attribute on Session and CSRF cookies has been added. For security and compatibility with latest browser versions, the default setting is ``Lax``. -- Guessing file extensions from mime type in ``Config\Mimes::guessExtensionFromType`` now only reverse searches the ``$mimes`` array if no extension is proposed (i.e., usually not for uploaded files). -- The getter functions for file extensions of uploaded files now have different fallback values (``$this->getClientExtension()`` for ``UploadedFile->getExtension()`` and ``''`` for ``UploadedFile->guessExtension()``). This is a security fix and makes the process less dependent on the client. -- The Cache ``FileHandler`` now allows setting the file permissions mode via ``Config\Cache`` +- Guessing file extensions from mime type in ``Config\Mimes::guessExtensionFromType()`` now only reverse searches the ``$mimes`` array if no extension is proposed (i.e., usually not for uploaded files). +- The getter functions for file extensions of uploaded files now have different fallback values (``UploadedFile::getClientExtension()`` for ``UploadedFile::getExtension()`` and ``''`` for ``UploadedFile::guessExtension()``). This is a security fix and makes the process less dependent on the client. +- The Cache ``FileHandler`` now allows setting the file permissions mode via ``Config\Cache``. Changes ------- -- System messages defined in ``system/Language/en/`` are now strictly for internal framework use and are not covered by backwards compatibility (BC) promise. Users may use these messages in their applications but at their own risks. - -Bugs Fixed ----------- - -- Fixed a bug in ``Entity`` class where declaring class parameters was preventing data propagation to the ``attributes`` array. -- Handling for the environment variable ``encryption.key`` has changed. Previously, explicit function calls, like ``getenv('encryption.key')`` or ``env('encryption.key')`` where the value has the special prefix ``hex2bin:`` returns an automatically converted binary string. This is now changed to just return the character string with the prefix. This change was due to incompatibility with handling binary strings in environment variables on Windows platforms. However, accessing ``$key`` using ``Encryption`` class config remains unchanged and still returns a binary string. -- ``Config\Services`` (in **app/Config/Services.php**) now extends ``CodeIgniter\Config\BaseService`` to allow proper discovery of third-party services. +- System messages defined in **system/Language/en/** are now strictly for internal framework use and are not covered by backwards compatibility (BC) promise. Users may use these messages in their applications but at their own risks. Deprecations ------------ -- Deprecated ``BaseCommand::getPad`` in favor of ``BaseCommand::setPad``. -- Deprecated ``CodeIgniter\Controller::loadHelpers`` in favor of ``helper`` function. -- Deprecated ``Config\Format::getFormatter`` in favor of ``CodeIgniter\Format\Format::getFormatter`` -- Deprecated ``CodeIgniter\Security\Security::CSRFVerify`` in favor of ``CodeIgniter\Security\Security::verify`` -- Deprecated ``CodeIgniter\Security\Security::getCSRFHash`` in favor of ``CodeIgniter\Security\Security::getHash`` -- Deprecated ``CodeIgniter\Security\Security::getCSRTokenName`` in favor of ``CodeIgniter\Security\Security::getCSRTokenName`` +- Deprecated ``BaseCommand::getPad()`` in favor of ``BaseCommand::setPad()``. +- Deprecated ``CodeIgniter\Controller::loadHelpers()`` in favor of ``helper()`` function. +- Deprecated ``Config\Format::getFormatter()`` in favor of ``CodeIgniter\Format\Format::getFormatter()`` +- Deprecated ``CodeIgniter\Security\Security::CSRFVerify()`` in favor of ``CodeIgniter\Security\Security::verify()`` +- Deprecated ``CodeIgniter\Security\Security::getCSRFHash()`` in favor of ``CodeIgniter\Security\Security::getHash()`` +- Deprecated ``CodeIgniter\Security\Security::getCSRTokenName()`` in favor of ``CodeIgniter\Security\Security::getTokenName()`` - Deprecated ``Config\App::$CSRFTokenName`` in favor of ``Config\Security::$tokenName`` - Deprecated ``Config\App::$CSRFHeaderName`` in favor of ``Config\Security::$headerName`` - Deprecated ``Config\App::$CSRFCookieName`` in favor of ``Config\Security::$cookieName`` @@ -52,6 +45,17 @@ Deprecations - Deprecated ``migrate:create`` command in favor of ``make:migration`` command. - Deprecated ``CodeIgniter\Database\ModelFactory`` in favor of ``CodeIgniter\Config\Factories::models()`` - Deprecated ``CodeIgniter\Config\Config`` in favor of ``CodeIgniter\Config\Factories::config()`` -- Deprecated ``CodeIgniter\HTTP\Message::getHeader`` in favor of ``header()`` to prepare for PSR-7 -- Deprecated ``CodeIgniter\HTTP\Message::getHeaders`` in favor of ``headers()`` to prepare for PSR-7 +- Deprecated ``CodeIgniter\HTTP\Message::getHeader()`` in favor of ``header()`` to prepare for PSR-7 +- Deprecated ``CodeIgniter\HTTP\Message::getHeaders()`` in favor of ``headers()`` to prepare for PSR-7 - Deprecated ``Config\Cache::$storePath`` in favor of ``Config\Cache::$file['storePath']`` + +Bugs Fixed +---------- + +- Fixed a bug in ``Entity`` class where declaring class parameters was preventing data propagation to the ``attributes`` array. +- Handling for the environment variable ``encryption.key`` has changed. Previously, explicit function calls, like ``getenv('encryption.key')`` or ``env('encryption.key')`` where the value has the special prefix ``hex2bin:`` returns an automatically converted binary string. This is now changed to just return the character string with the prefix. This change was due to incompatibility with handling binary strings in environment variables on Windows platforms. However, accessing ``$key`` using ``Encryption`` class config remains unchanged and still returns a binary string. +- ``Config\Services`` (in **app/Config/Services.php**) now extends ``CodeIgniter\Config\BaseService`` to allow proper discovery of third-party services. + +See the repo's +`CHANGELOG_4.0.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.1.0.rst b/user_guide_src/source/changelogs/v4.1.0.rst index 25fa9a5f6726..eee8abfddc28 100644 --- a/user_guide_src/source/changelogs/v4.1.0.rst +++ b/user_guide_src/source/changelogs/v4.1.0.rst @@ -23,3 +23,10 @@ Deprecations ************ - Deprecated ``Model::fillPlaceholders()`` method, use ``Validation::fillPlaceholders()`` instead. + +Bugs Fixed +********** + +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.1.1.rst b/user_guide_src/source/changelogs/v4.1.1.rst index a99fb24d3770..aeccef6e3c41 100644 --- a/user_guide_src/source/changelogs/v4.1.1.rst +++ b/user_guide_src/source/changelogs/v4.1.1.rst @@ -15,3 +15,7 @@ Bugs Fixed - Fixed an issue where ``.gitattributes`` was preventing framework downloads. Note that this fix was also applied retroactively to ``4.0.5`` on the **framework** repo. + +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.1.2.rst b/user_guide_src/source/changelogs/v4.1.2.rst index 572188e6ff94..c25cb11f32b7 100644 --- a/user_guide_src/source/changelogs/v4.1.2.rst +++ b/user_guide_src/source/changelogs/v4.1.2.rst @@ -64,6 +64,8 @@ Deprecations Bugs Fixed ---------- -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. - - ``BaseConnection::query()`` now returns ``false`` for failed queries (unless ``DBDebug==true``, in which case an exception will be thrown) and returns boolean values for write-type queries as specified in the docs. + +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.1.3.rst b/user_guide_src/source/changelogs/v4.1.3.rst index 36eb4230d51b..73ae4a4a8380 100644 --- a/user_guide_src/source/changelogs/v4.1.3.rst +++ b/user_guide_src/source/changelogs/v4.1.3.rst @@ -30,3 +30,7 @@ Bugs Fixed - Fixed a bug where CLI mode was not detected under ``cgi-fcgi`` - Fixed a logic bug in Cookie construction - Fixed numerous issues in SQLite3's ``Forge`` class related to an incorrect attribute name + +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.1.4.rst b/user_guide_src/source/changelogs/v4.1.4.rst index 60a4a9c21432..b3fad8dd8b53 100644 --- a/user_guide_src/source/changelogs/v4.1.4.rst +++ b/user_guide_src/source/changelogs/v4.1.4.rst @@ -12,8 +12,8 @@ Release Date: September 6, 2021 This release focuses on code style. All changes (except those noted below) are cosmetic to bring the code in line with the new `CodeIgniter Coding Standard `_ (based on PSR-12). -Breaking Changes ----------------- +BREAKING +-------- - The following methods were changed from "public" to "protected" to match their parent class methods and better align with their uses: @@ -57,3 +57,7 @@ Breaking Changes * ``CodeIgniter\Session\Handlers\FileHandler`` * ``CodeIgniter\Session\Handlers\MemcachedHandler`` * ``CodeIgniter\Session\Handlers\RedisHandler`` + +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of changes. diff --git a/user_guide_src/source/changelogs/v4.1.5.rst b/user_guide_src/source/changelogs/v4.1.5.rst index 960d29b8a4f9..02393424dacb 100644 --- a/user_guide_src/source/changelogs/v4.1.5.rst +++ b/user_guide_src/source/changelogs/v4.1.5.rst @@ -45,4 +45,6 @@ Deprecations Bugs Fixed ========== -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.1.6.rst b/user_guide_src/source/changelogs/v4.1.6.rst index 84e4f9f7d39e..14e43d0adc84 100644 --- a/user_guide_src/source/changelogs/v4.1.6.rst +++ b/user_guide_src/source/changelogs/v4.1.6.rst @@ -68,4 +68,6 @@ And the following methods are deprecated: Bugs Fixed ********** -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.1.7.rst b/user_guide_src/source/changelogs/v4.1.7.rst index 5c9d07357858..592358514b09 100644 --- a/user_guide_src/source/changelogs/v4.1.7.rst +++ b/user_guide_src/source/changelogs/v4.1.7.rst @@ -32,4 +32,6 @@ none. Bugs Fixed ********** -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.1.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst index 232b7a98fede..861666077934 100644 --- a/user_guide_src/source/changelogs/v4.2.0.rst +++ b/user_guide_src/source/changelogs/v4.2.0.rst @@ -158,6 +158,9 @@ Deprecations Bugs Fixed ********** + - The SQLSRV driver ignores the port value from the config. -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.1.rst b/user_guide_src/source/changelogs/v4.2.1.rst index 486197688894..44a16d464c8a 100644 --- a/user_guide_src/source/changelogs/v4.2.1.rst +++ b/user_guide_src/source/changelogs/v4.2.1.rst @@ -17,3 +17,10 @@ Behavior Changes - Guessing the file extension from the MIME type has been changed if the proposed extension is not valid. Previously, the guessing will early terminate and return ``null``. Now, if a proposed extension is given and is invalid, the MIME guessing will continue checking using the mapping of extension to MIME types. - If there is a cookie with a prefixed name and a cookie with the same name without a prefix, the previous ``get_cookie()`` had the tricky behavior of returning the cookie without the prefix. Now the behavior has been fixed as a bug, and has been changed. See :ref:`Upgrading ` for details. + +Bugs Fixed +********** + +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.10.rst b/user_guide_src/source/changelogs/v4.2.10.rst index de0d0c231a75..cb753723e158 100644 --- a/user_guide_src/source/changelogs/v4.2.10.rst +++ b/user_guide_src/source/changelogs/v4.2.10.rst @@ -14,4 +14,6 @@ Bugs Fixed - Fixed incorrect PHPDoc types in Session. -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.11.rst b/user_guide_src/source/changelogs/v4.2.11.rst index 98e66a8ad7a0..1f796d1361b4 100644 --- a/user_guide_src/source/changelogs/v4.2.11.rst +++ b/user_guide_src/source/changelogs/v4.2.11.rst @@ -23,10 +23,17 @@ BREAKING :ref:`sessions-memcachedhandler-driver` and :ref:`sessions-redishandler-driver` has changed. See :ref:`Upgrading Guide `. +Enhancements +************ + +- Full support for PHP 8.2. + Bugs Fixed ********** - Fixed a ``FileLocator::locateFile()`` bug where a similar namespace name could be replaced by another, causing a failure to find a file that exists. - Fixed a ``RedisHandler`` session class to use the correct config when used with a socket connection. -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.12.rst b/user_guide_src/source/changelogs/v4.2.12.rst index d3239378f431..441d19675b74 100644 --- a/user_guide_src/source/changelogs/v4.2.12.rst +++ b/user_guide_src/source/changelogs/v4.2.12.rst @@ -17,4 +17,6 @@ Bugs Fixed - Fixed ``spark migrate:status`` shows incorrect filenames when format is ``Y_m_d_His_``. - Fixed an error when ``Model::save()`` saves an object if ``$useAutoIncrement`` is false. -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.2.rst b/user_guide_src/source/changelogs/v4.2.2.rst index 754216ce4102..6b57ba63c9f2 100644 --- a/user_guide_src/source/changelogs/v4.2.2.rst +++ b/user_guide_src/source/changelogs/v4.2.2.rst @@ -42,4 +42,6 @@ Deprecations Bugs Fixed ********** -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.3.rst b/user_guide_src/source/changelogs/v4.2.3.rst index 69a2889a810e..e3b2eb0cf140 100644 --- a/user_guide_src/source/changelogs/v4.2.3.rst +++ b/user_guide_src/source/changelogs/v4.2.3.rst @@ -17,4 +17,6 @@ Enhancements Bugs Fixed ********** -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.5.rst b/user_guide_src/source/changelogs/v4.2.5.rst index 96908b750843..1f03ac1e041e 100644 --- a/user_guide_src/source/changelogs/v4.2.5.rst +++ b/user_guide_src/source/changelogs/v4.2.5.rst @@ -25,4 +25,6 @@ Bugs Fixed ********** - When using subqueries in the main query, prefixes are added to the table alias. -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.6.rst b/user_guide_src/source/changelogs/v4.2.6.rst index 154927470ba7..4e955706b612 100644 --- a/user_guide_src/source/changelogs/v4.2.6.rst +++ b/user_guide_src/source/changelogs/v4.2.6.rst @@ -21,4 +21,6 @@ Many bugs fixed, but notably: - AssertionError occurs when using Validation in CLI `#6452 `_ -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.7.rst b/user_guide_src/source/changelogs/v4.2.7.rst index 920ba72761ce..9b30c8385942 100644 --- a/user_guide_src/source/changelogs/v4.2.7.rst +++ b/user_guide_src/source/changelogs/v4.2.7.rst @@ -31,4 +31,6 @@ Message Changes Bugs Fixed ********** -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.8.rst b/user_guide_src/source/changelogs/v4.2.8.rst index 4b6d83c98a6a..19f1696ea29d 100644 --- a/user_guide_src/source/changelogs/v4.2.8.rst +++ b/user_guide_src/source/changelogs/v4.2.8.rst @@ -22,4 +22,6 @@ Bugs Fixed - Fixed a bug when the ``CodeIgniter\Database\SQLSRV\PreparedQuery::_getResult()`` was returning the bool value instead of the resource. - Fixed a bug in the error handler where in cases the callback cannot process the error level it does not pass the error to PHP's standard error handler. -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.2.9.rst b/user_guide_src/source/changelogs/v4.2.9.rst index b5d6ecaea143..2e7a0fbacbeb 100644 --- a/user_guide_src/source/changelogs/v4.2.9.rst +++ b/user_guide_src/source/changelogs/v4.2.9.rst @@ -16,4 +16,6 @@ Bugs Fixed - Fixed a bug that causes `PHP Fatal error: Trait "Nexus\PHPUnit\Extension\Expeditable" not found` when running PHPUnit. -See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. +See the repo's +`CHANGELOG_4.2.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/changelogs/v4.3.0.rst b/user_guide_src/source/changelogs/v4.3.0.rst index ed6047cd320e..d70b9dc66963 100644 --- a/user_guide_src/source/changelogs/v4.3.0.rst +++ b/user_guide_src/source/changelogs/v4.3.0.rst @@ -240,7 +240,7 @@ Query Builder Forge ----- -- Added ``Forge::processIndexes()`` allowing the creation of indexes on an existing table. See :ref:`adding-keys` for the details. +- Added ``Forge::processIndexes()`` allowing the creation of indexes on an existing table. See :ref:`db-forge-adding-keys-to-a-table` for the details. - Added the ability to manually set index names. These methods include: ``Forge::addKey()``, ``Forge::addPrimaryKey()``, and ``Forge::addUniqueKey()`` - The new method ``Forge::dropPrimaryKey()`` allows dropping the primary key on a table. See :ref:`dropping-a-primary-key`. - Fixed ``Forge::dropKey()`` to allow dropping unique indexes. This required the ``DROP CONSTRAINT`` SQL command. @@ -335,6 +335,7 @@ Message Changes - Updated English language strings to be more consistent. - Added ``CLI.generator.className.cell`` and ``CLI.generator.viewName.cell``. +- Added **en/Errors.php** file. Changes ******* diff --git a/user_guide_src/source/changelogs/v4.3.2.rst b/user_guide_src/source/changelogs/v4.3.2.rst new file mode 100644 index 000000000000..939ee780bb29 --- /dev/null +++ b/user_guide_src/source/changelogs/v4.3.2.rst @@ -0,0 +1,44 @@ +Version 4.3.2 +############# + +Release Date: February 18, 2023 + +**4.3.2 release of CodeIgniter4** + +.. contents:: + :local: + :depth: 3 + +BREAKING +******** + +Behavior Changes +================ + +base_url() +---------- + +Due to a bug, in previous versions :php:func:`base_url()` without argument returned baseURL +without a trailing slash (``/``) like ``http://localhost:8080``. Now it returns +baseURL with a trailing slash. This is the same behavior as ``base_url()`` in +CodeIgniter 3. + +Changes +******* + +- The parameter ``$relative`` in :php:func:`uri_string()` was removed. Due to a bug, + the function always returned a path relative to baseURL. No behavior changes. + +Bugs Fixed +********** + +- **QueryBuilder:** ``where()`` generates incorrect SQL when using ``RawSql`` +- **QueryBuilder:** ``RawSql`` passed to ``set()`` disappears without error +- **Session:** Can't connect to Redis with ``RedisHandler`` over TLS +- **Autoloader:** May not add Composer package's namespaces +- **Parser:** ``!`` does not work if delimiters are changed +- **UserGuide:** Added missing items in ChangeLog and Upgrading Guide v4.3.0 + +See the repo's +`CHANGELOG.md `_ +for a complete list of bugs fixed. diff --git a/user_guide_src/source/cli/spark_commands.rst b/user_guide_src/source/cli/spark_commands.rst index e336623ec571..8ac2ecba1d6a 100644 --- a/user_guide_src/source/cli/spark_commands.rst +++ b/user_guide_src/source/cli/spark_commands.rst @@ -12,13 +12,18 @@ CodeIgniter ships with the official command **spark** and built-in commands. Running Commands **************** -The commands are run from the command line, in the root directory. -A custom script, **spark** has been provided that is used to run any of the CLI commands:: +Running via CLI +=============== + +The commands are run from the command line, in the project root directory. +The command file **spark** has been provided that is used to run any of the CLI commands:: > php spark When called without specifying a command, a simple help page is displayed that also provides a list of -available commands. You should pass the name of the command as the first argument to run that command:: +available commands. + +You should pass the name of the command as the first argument to run that command:: > php spark migrate @@ -33,8 +38,9 @@ You may always pass ``--no-header`` to suppress the header output, helpful for p For all of the commands CodeIgniter provides, if you do not provide the required arguments, you will be prompted for the information it needs to run correctly:: - > php spark migrate:version - > Version? + > php spark make::controller + + Controller class name : Calling Commands ================ @@ -54,9 +60,24 @@ so that you can choose to display it or not. Using Help Command ****************** -You can get help about any CLI command using the help command as follows:: +spark help +========== + +You can get help about any CLI command using the ``help`` command as follows:: > php spark help db:seed -Use the **list** command to get a list of available commands and their descriptions, sorted by categories. -You may also use ``spark list --simple`` to get a raw list of all available commands, sorted alphabetically. +Since v4.3.0, you can also use the ``--help`` option instead of the ``help`` command:: + + > php spark db:seed --help + +spark list +========== + +Use the ``list`` command to get a list of available commands and their descriptions, sorted by categories:: + + > php spark list + +You may also use the ``--simple`` option to get a raw list of all available commands, sorted alphabetically:: + + > php spark list --simple diff --git a/user_guide_src/source/concepts/factories.rst b/user_guide_src/source/concepts/factories.rst index dff3a92e017b..c09134a47b54 100644 --- a/user_guide_src/source/concepts/factories.rst +++ b/user_guide_src/source/concepts/factories.rst @@ -15,6 +15,11 @@ What are Factories? Like :doc:`./services`, **Factories** are an extension of autoloading that helps keep your code concise yet optimal, without having to pass around object instances between classes. +Factories are similar to CodeIgniter 3's ``$this->load`` in the following points: + +- Load a class +- Share the loaded class instance + At its simplest, Factories provide a common way to create a class instance and access it from anywhere. This is a great way to reuse object states and reduce memory load from keeping @@ -36,16 +41,24 @@ On the other hand, Services have code to create instances, so it can create a co that needs other services or class instances. When you get a service, Services require a service name, not a class name, so the returned instance can be changed without changing the client code. +Loading Classes +*************** + +Loading a Class +=============== + .. _factories-example: -Example -======= +Model Example +------------- Take a look at **Models** as an example. You can access the Factory specific to Models by using the magic static method of the Factories class, ``Factories::models()``. +The static method name is called *component*. + By default, Factories first searches in the ``App`` namespace for the path corresponding to the magic static method name. -``Factories::models()`` searches the path **Models/**. +``Factories::models()`` searches the **app/Models** directory. In the following code, if you have ``App\Models\UserModel``, the instance will be returned: @@ -54,6 +67,7 @@ In the following code, if you have ``App\Models\UserModel``, the instance will b Or you could also request a specific class: .. literalinclude:: factories/002.php + :lines: 2- If you have only ``Blog\Models\UserModel``, the instance will be returned. But if you have both ``App\Models\UserModel`` and ``Blog\Models\UserModel``, @@ -62,6 +76,7 @@ the instance of ``App\Models\UserModel`` will be returned. If you want to get ``Blog\Models\UserModel``, you need to disable the option ``preferApp``: .. literalinclude:: factories/010.php + :lines: 2- See :ref:`factories-options` for the details. @@ -70,6 +85,15 @@ you get back the instance as before: .. literalinclude:: factories/003.php +Loading a Class in Sub-directories +================================== + +If you want to load a class in sub directories, you use the ``/`` as a separator. +The following code loads **app/Libraries/Sub/SubLib.php**: + +.. literalinclude:: factories/013.php + :lines: 2- + Convenience Functions ********************* @@ -119,8 +143,8 @@ Key Type Description ========== ============== ============================================================ =================================================== component string or null The name of the component (if different than the static ``null`` (defaults to the component name) method). This can be used to alias one component to another. -path string or null The relative path within the namespace/folder to look for ``null`` (defaults to the component name) - classes. +path string or null The relative path within the namespace/folder to look for ``null`` (defaults to the component name, + classes. but makes the first character uppercase) instanceOf string or null A required class name to match on the returned instance. ``null`` (no filtering) getShared boolean Whether to return a shared instance of the class or load a ``true`` fresh one. @@ -143,6 +167,9 @@ Configurations To set default component options, create a new Config files at **app/Config/Factory.php** that supplies options as an array property that matches the name of the component. +Example: Filters Factories +-------------------------- + For example, if you want to create **Filters** by Factories, the component name wll be ``filters``. And if you want to ensure that each filter is an instance of a class which implements CodeIgniter's ``FilterInterface``, your **app/Config/Factory.php** file might look like this: @@ -155,6 +182,22 @@ and the returned instance will surely be a CodeIgniter's filter. This would prevent conflict of an third-party module which happened to have an unrelated ``Filters`` path in its namespace. +Example: Library Factories +-------------------------- + +If you want to load your library classes in the **app/Libraries** directory with +``Factories::library('SomeLib')``, the path `Libraries` is different from the +default path `Library`. + +In this case, your **app/Config/Factory.php** file will look like this: + +.. literalinclude:: factories/011.php + +Now you can load your libraries with the ``Factories::library()`` method: + +.. literalinclude:: factories/012.php + :lines: 2- + setOptions Method ================= diff --git a/user_guide_src/source/concepts/factories/003.php b/user_guide_src/source/concepts/factories/003.php index 44d3b06c55f6..f062aeb33f20 100644 --- a/user_guide_src/source/concepts/factories/003.php +++ b/user_guide_src/source/concepts/factories/003.php @@ -1,5 +1,7 @@ true]); // Default; will always be the same instance +$users = Factories::models('UserModel', ['getShared' => true]); // Default; will always be the same instance $other = Factories::models('UserModel', ['getShared' => false]); // Will always create a new instance diff --git a/user_guide_src/source/concepts/factories/010.php b/user_guide_src/source/concepts/factories/010.php index 20df61e3b397..90f5f26ef524 100644 --- a/user_guide_src/source/concepts/factories/010.php +++ b/user_guide_src/source/concepts/factories/010.php @@ -1,3 +1,3 @@ false]); +$users = Factories::models('Blog\Models\UserModel', ['preferApp' => false]); diff --git a/user_guide_src/source/concepts/factories/011.php b/user_guide_src/source/concepts/factories/011.php new file mode 100644 index 000000000000..6e6ce0997884 --- /dev/null +++ b/user_guide_src/source/concepts/factories/011.php @@ -0,0 +1,12 @@ + 'Libraries', + ]; +} diff --git a/user_guide_src/source/concepts/factories/012.php b/user_guide_src/source/concepts/factories/012.php new file mode 100644 index 000000000000..c096ce3b619c --- /dev/null +++ b/user_guide_src/source/concepts/factories/012.php @@ -0,0 +1,5 @@ +getUri()->getPath(); // Retrieve $_GET and $_POST variables @@ -22,7 +22,9 @@ $request->getServer('Host'); // Retrieve an HTTP Request header, with case-insensitive names -$request->getHeader('host'); -$request->getHeader('Content-Type'); +$request->header('host'); +$request->header('Content-Type'); -$request->getMethod(); // get, post, put, etc +// Checks the HTTP method +$request->is('get'); +$request->is('post'); diff --git a/user_guide_src/source/concepts/http/002.php b/user_guide_src/source/concepts/http/002.php index e5fc38efeec2..9a15a6d42d5e 100644 --- a/user_guide_src/source/concepts/http/002.php +++ b/user_guide_src/source/concepts/http/002.php @@ -2,7 +2,7 @@ use CodeIgniter\HTTP\Response; -$response = service('response'); +$response = response(); $response->setStatusCode(Response::HTTP_OK); $response->setBody($output); diff --git a/user_guide_src/source/conf.py b/user_guide_src/source/conf.py index a0484b3d0c3c..7f7aa87e7c59 100644 --- a/user_guide_src/source/conf.py +++ b/user_guide_src/source/conf.py @@ -26,7 +26,7 @@ version = '4.3' # The full version, including alpha/beta/rc tags. -release = '4.3.1' +release = '4.3.2' # -- General configuration --------------------------------------------------- diff --git a/user_guide_src/source/database/configuration.rst b/user_guide_src/source/database/configuration.rst index 836cd53ec92b..b5a768439ccc 100644 --- a/user_guide_src/source/database/configuration.rst +++ b/user_guide_src/source/database/configuration.rst @@ -118,14 +118,15 @@ Explanation of Values: **database** The name of the database you want to connect to. .. note:: CodeIgniter doesn't support dots (``.``) in the database, table, and column names. -**DBDriver** The database driver name. e.g.,: ``MySQLi``, ``Postgres``, etc. The case must match the driver name +**DBDriver** The database driver name. e.g.,: ``MySQLi``, ``Postgres``, etc. The case must match the driver name. + You can set a fully qualified classname to use your custom driver. **DBPrefix** An optional table prefix which will added to the table name when running :doc:`Query Builder ` queries. This permits multiple CodeIgniter installations to share one database. **pConnect** true/false (boolean) - Whether to use a persistent connection. **DBDebug** true/false (boolean) - Whether to throw exceptions or not when database errors occur. **charset** The character set used in communicating with the database. -**DBCollat** The character collation used in communicating with the database (``MySQLi`` only) +**DBCollat** The character collation used in communicating with the database (``MySQLi`` only). **swapPre** A default table prefix that should be swapped with ``DBPrefix``. This is useful for distributed applications where you might run manually written queries, and need the prefix to still be customizable by the end user. diff --git a/user_guide_src/source/database/queries.rst b/user_guide_src/source/database/queries.rst index cefc49c705f5..35d8c5ad08df 100644 --- a/user_guide_src/source/database/queries.rst +++ b/user_guide_src/source/database/queries.rst @@ -56,25 +56,38 @@ fetchable results. Working with Database Prefixes Manually *************************************** +$db->prefixTable() +================== + If you have configured a database prefix and would like to prepend it to a table name for use in a native SQL query for example, then you can use the following: .. literalinclude:: queries/004.php +$db->setPrefix() +================ + If for any reason you would like to change the prefix programmatically without needing to create a new connection you can use this method: .. literalinclude:: queries/005.php +$db->getPrefix() +================ + You can get the current prefix any time with this method: .. literalinclude:: queries/006.php + ********************** Protecting Identifiers ********************** +$db->protectIdentifiers() +========================= + In many databases, it is advisable to protect table and field names - for example with backticks in MySQL. **Query Builder queries are automatically protected**, but if you need to manually protect an @@ -93,6 +106,7 @@ prefixing set ``true`` (boolean) via the second parameter: .. literalinclude:: queries/008.php + *************** Escaping Values *************** @@ -184,6 +198,7 @@ example: .. literalinclude:: queries/015.php + **************** Prepared Queries **************** @@ -257,8 +272,10 @@ hasError() Returns boolean true/false if the last ``execute()`` call created any errors. -getErrorCode() getErrorMessage() --------------------------------- +getErrorCode() +-------------- +getErrorMessage() +----------------- If any errors were encountered these methods can be used to retrieve the error code and string. @@ -271,8 +288,8 @@ Internally, all queries are processed and stored as instances of the parameters, otherwise preparing the query, and storing performance data about its query. -getLastQuery() -============== +$db->getLastQuery() +=================== When you just need to retrieve the last Query object, use the ``getLastQuery()`` method: diff --git a/user_guide_src/source/database/queries/011.php b/user_guide_src/source/database/queries/011.php index e3b1f9033bc9..484bf11e0f76 100644 --- a/user_guide_src/source/database/queries/011.php +++ b/user_guide_src/source/database/queries/011.php @@ -1,5 +1,4 @@ escapeLikeString($search) . "%' ESCAPE '!'"; +$sql = "SELECT id FROM table WHERE column LIKE '%" . $db->escapeLikeString($search) . "%' ESCAPE '!'"; diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index a53ecc65415c..56035153db80 100755 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -148,6 +148,8 @@ escaping of fields may break them. RawSql ^^^^^^ +.. versionadded:: 4.2.0 + Since v4.2.0, ``$builder->select()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings. .. literalinclude:: query_builder/099.php @@ -265,6 +267,8 @@ outer``, and ``right outer``. RawSql ^^^^^^ +.. versionadded:: 4.2.0 + Since v4.2.0, ``$builder->join()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings. .. literalinclude:: query_builder/102.php @@ -335,6 +339,8 @@ methods: 5. RawSql ^^^^^^^^^ + .. versionadded:: 4.2.0 + Since v4.2.0, ``$builder->where()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings. .. literalinclude:: query_builder/100.php @@ -450,6 +456,8 @@ searches. 3. RawSql ^^^^^^^^^ + .. versionadded:: 4.2.0 + Since v4.2.0, ``$builder->like()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings. .. literalinclude:: query_builder/101.php @@ -900,6 +908,8 @@ Upsert $builder->upsert() ------------------ +.. versionadded:: 4.3.0 + Generates an upsert string based on the data you supply, and runs the query. You can either pass an **array** or an **object** to the method. By default a constraint will be defined in order. A primary @@ -921,6 +931,8 @@ The first parameter is an object. $builder->getCompiledUpsert() ----------------------------- +.. versionadded:: 4.3.0 + Compiles the upsert query just like ``$builder->upsert()`` but does not *run* the query. This method simply returns the SQL query as a string. @@ -936,10 +948,12 @@ upsertBatch $builder->upsertBatch() ----------------------- +.. versionadded:: 4.3.0 + Generates an upsert string based on the data you supply, and runs the query. You can either pass an **array** or an **object** to the method. By default a constraint will be defined in order. A primary -key will be selected first and then unique keys. Mysql will use any +key will be selected first and then unique keys. MySQL will use any constraint by default. Here is an example using an array: .. literalinclude:: query_builder/108.php @@ -952,13 +966,16 @@ You can also upsert from a query: .. literalinclude:: query_builder/115.php -.. note:: ``setQueryAsData()`` can be used since v4.3.0. +.. note:: The ``setQueryAsData()``, ``onConstraint()``, and ``updateFields()`` + methods can be used since v4.3.0. .. note:: It is required to alias the columns of the select query to match those of the target table. $builder->onConstraint() ------------------------ +.. versionadded:: 4.3.0 + Allows manually setting constraint to be used for upsert. This does not work with MySQL because MySQL checks all constraints by default. @@ -968,6 +985,9 @@ This method accepts a string or an array of columns. $builder->updateFields() ------------------------ + +.. versionadded:: 4.3.0 + Allows manually setting the fields to be updated when performing upserts. .. literalinclude:: query_builder/110.php @@ -1076,14 +1096,24 @@ UpdateBatch $builder->updateBatch() ----------------------- +.. note:: Since v4.3.0, the second parameter ``$index`` of ``updateBatch()`` has + changed to ``$constraints``. It now accepts types array, string, or ``RawSql``. + Generates an update string based on the data you supply, and runs the query. You can either pass an **array** or an **object** to the method. Here is an example using an array: .. literalinclude:: query_builder/092.php +.. note:: Since v4.3.0, the generated SQL structure has been Improved. + The first parameter is an associative array of values, the second parameter is the where key. +Since v4.3.0, you can also use the ``setQueryAsData()``, ``onConstraint()``, and +``updateFields()`` methods: + +.. literalinclude:: query_builder/120.php + .. note:: All values except ``RawSql`` are escaped automatically producing safer queries. .. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections. @@ -1096,7 +1126,8 @@ You can also update from a query: .. literalinclude:: query_builder/116.php -.. note:: ``setQueryAsData()`` can be used since v4.3.0. +.. note:: The ``setQueryAsData()``, ``onConstraint()``, and ``updateFields()`` + methods can be used since v4.3.0. .. note:: It is required to alias the columns of the select query to match those of the target table. @@ -1138,6 +1169,8 @@ method, or ``emptyTable()``. $builder->deleteBatch() ----------------------- +.. versionadded:: 4.3.0 + Generates a batch **DELETE** statement based on a set of data. .. literalinclude:: query_builder/118.php @@ -1190,6 +1223,8 @@ When $builder->when() ---------------- +.. versionadded:: 4.3.0 + This allows modifying the query based on a condition without breaking out of the query builder chain. The first parameter is the condition, and it should evaluate to a boolean. The second parameter is a callable that will be ran @@ -1215,6 +1250,8 @@ WhenNot $builder->whenNot() ------------------- +.. versionadded:: 4.3.0 + This works exactly the same way as ``$builder->when()`` except that it will only run the callable when the condition evaluates to ``false``, instead of ``true`` like ``when()``. @@ -1407,6 +1444,8 @@ Class Reference .. php:method:: setQueryAsData($query[, $alias[, $columns = null]]) + .. versionadded:: 4.3.0 + :param BaseBuilder|RawSql $query: Instance of the BaseBuilder or RawSql :param string|null $alias: Alias for query :param array|string|null $columns: Array or comma delimited string of columns in the query diff --git a/user_guide_src/source/database/query_builder/092.php b/user_guide_src/source/database/query_builder/092.php index 911771fc1850..407eadbfef29 100644 --- a/user_guide_src/source/database/query_builder/092.php +++ b/user_guide_src/source/database/query_builder/092.php @@ -14,28 +14,7 @@ 'date' => 'Date 2', ], ]; - $builder->updateBatch($data, ['title', 'author']); - -// OR -$builder->setData($data)->onConstraint('title, author')->updateBatch(); - -// OR -$builder->setData($data, null, 'u') - ->onConstraint(['`mytable`.`title`' => '`u`.`title`', 'author' => new RawSql('`u`.`author`')]) - ->updateBatch(); - -// OR -foreach ($data as $row) { - $builder->setData($row); -} -$builder->onConstraint('title, author')->updateBatch(); - -// OR -$builder->setData($data, true, 'u') - ->onConstraint(new RawSql('`mytable`.`title` = `u`.`title` AND `mytable`.`author` = `u`.`author`')) - ->updateFields(['last_update' => new RawSql('CURRENT_TIMESTAMP()')], true) - ->updateBatch(); /* * Produces: * UPDATE `mytable` @@ -47,6 +26,5 @@ * SET * `mytable`.`title` = `u`.`title`, * `mytable`.`name` = `u`.`name`, - * `mytable`.`date` = `u`.`date`, - * `mytable`.`last_update` = CURRENT_TIMESTAMP() // this only applies to the last scenario + * `mytable`.`date` = `u`.`date` */ diff --git a/user_guide_src/source/database/query_builder/115.php b/user_guide_src/source/database/query_builder/115.php index cc5607d3982c..7526474da334 100644 --- a/user_guide_src/source/database/query_builder/115.php +++ b/user_guide_src/source/database/query_builder/115.php @@ -7,7 +7,10 @@ $additionalUpdateField = ['updated_at' => new RawSql('CURRENT_TIMESTAMP')]; -$sql = $builder->setQueryAsData($query)->onConstraint('email')->updateFields($additionalUpdateField, true)->upsertBatch(); +$sql = $builder->setQueryAsData($query) + ->onConstraint('email') + ->updateFields($additionalUpdateField, true) + ->upsertBatch(); /* MySQLi produces: INSERT INTO `db_user` (`country`, `email`, `name`) SELECT user2.name, user2.email, user2.country diff --git a/user_guide_src/source/database/query_builder/120.php b/user_guide_src/source/database/query_builder/120.php new file mode 100644 index 000000000000..d0bea8a1cfba --- /dev/null +++ b/user_guide_src/source/database/query_builder/120.php @@ -0,0 +1,36 @@ +setData($data)->onConstraint('title, author')->updateBatch(); + +// OR +$builder->setData($data, null, 'u') + ->onConstraint(['`mytable`.`title`' => '`u`.`title`', 'author' => new RawSql('`u`.`author`')]) + ->updateBatch(); + +// OR +foreach ($data as $row) { + $builder->setData($row); +} +$builder->onConstraint('title, author')->updateBatch(); + +// OR +$builder->setData($data, true, 'u') + ->onConstraint(new RawSql('`mytable`.`title` = `u`.`title` AND `mytable`.`author` = `u`.`author`')) + ->updateFields(['last_update' => new RawSql('CURRENT_TIMESTAMP()')], true) + ->updateBatch(); +/* + * Produces: + * UPDATE `mytable` + * INNER JOIN ( + * SELECT 'Title 1' `title`, 'Author 1' `author`, 'Name 1' `name`, 'Date 1' `date` UNION ALL + * SELECT 'Title 2' `title`, 'Author 2' `author`, 'Name 2' `name`, 'Date 2' `date` + * ) `u` + * ON `mytable`.`title` = `u`.`title` AND `mytable`.`author` = `u`.`author` + * SET + * `mytable`.`title` = `u`.`title`, + * `mytable`.`name` = `u`.`name`, + * `mytable`.`date` = `u`.`date`, + * `mytable`.`last_update` = CURRENT_TIMESTAMP() // this only applies to the last scenario + */ diff --git a/user_guide_src/source/dbmgmt/forge.rst b/user_guide_src/source/dbmgmt/forge.rst index ee5731d85064..21da852e9d4f 100644 --- a/user_guide_src/source/dbmgmt/forge.rst +++ b/user_guide_src/source/dbmgmt/forge.rst @@ -1,3 +1,4 @@ +#################### Database Forge Class #################### @@ -77,14 +78,16 @@ for the file where the database will be created using the ``--ext`` option. Vali produce a success message but no database file is created. This is because SQLite3 will just use an in-memory database. -**************************** -Creating and Dropping Tables -**************************** +*************** +Creating Tables +*************** There are several things you may wish to do when creating tables. Add fields, add keys to the table, alter columns. CodeIgniter provides a mechanism for this. +.. _adding-fields: + Adding Fields ============= @@ -115,15 +118,16 @@ After the fields have been defined, they can be added using $forge->addField() ------------------ -The add fields method will accept the above array. +The ``addField()`` method will accept the above array. .. _forge-addfield-default-value-rawsql: Raw Sql Strings as Default Values --------------------------------- -Since v4.2.0, ``$forge->addField()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings. +.. versionadded:: 4.2.0 +Since v4.2.0, ``$forge->addField()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings. .. literalinclude:: forge/027.php @@ -141,7 +145,7 @@ string into the field definitions with ``addField()``: .. note:: Multiple calls to ``addField()`` are cumulative. -Creating an id field +Creating an id Field -------------------- There is a special exception for creating id fields. A field with type @@ -155,6 +159,9 @@ Primary Key. Adding Keys =========== +$forge->addKey() +---------------- + Generally speaking, you'll want your table to have Keys. This is accomplished with ``$forge->addKey('field')``. The optional second parameter set to true will make it a primary key and the third @@ -168,6 +175,12 @@ below is for MySQL. .. literalinclude:: forge/010.php +$forge->addPrimaryKey() +----------------------- + +$forge->addUniqueKey() +---------------------- + To make code reading more objective it is also possible to add primary and unique keys with specific methods: @@ -175,10 +188,6 @@ and unique keys with specific methods: .. note:: When you add a primary key, MySQL and SQLite will assume the name ``PRIMARY`` even if a name is provided. -You may add keys to an existing table by using ``processIndexes()``: - -.. literalinclude:: forge/029.php - .. _adding-foreign-keys: Adding Foreign Keys @@ -215,6 +224,10 @@ You could also pass optional table attributes, such as MySQL's ``ENGINE``: ``createTable()`` will always add them with your configured *charset* and *DBCollat* values, as long as they are not empty (MySQL only). +*************** +Dropping Tables +*************** + Dropping a Table ================ @@ -227,51 +240,19 @@ drivers to handle removal of tables with foreign keys. .. literalinclude:: forge/018.php -Dropping a Foreign Key -====================== - -Execute a DROP FOREIGN KEY. - -.. literalinclude:: forge/019.php - -Dropping a Key -====================== - -Execute a DROP KEY. - -.. literalinclude:: forge/020.php - -.. _dropping-a-primary-key: - -Dropping a Primary Key -====================== - -.. versionadded:: 4.3.0 - -Execute a DROP PRIMARY KEY. - -.. literalinclude:: forge/028.php - -Renaming a Table -================ - -Executes a TABLE rename - -.. literalinclude:: forge/021.php - **************** Modifying Tables **************** -Adding a Column to a Table -========================== +Adding a Field to a Table +========================= $forge->addColumn() ------------------- The ``addColumn()`` method is used to modify an existing table. It -accepts the same field array as above, and can be used for an unlimited -number of additional fields. +accepts the same field array as :ref:`Creating Tables `, and can +be used to add additional fields. .. literalinclude:: forge/022.php @@ -282,8 +263,8 @@ Examples: .. literalinclude:: forge/023.php -Dropping Columns From a Table -============================== +Dropping Fields From a Table +============================ .. _db-forge-dropColumn: @@ -298,8 +279,8 @@ Used to remove multiple columns from a table. .. literalinclude:: forge/025.php -Modifying a Column in a Table -============================= +Modifying a Field in a Table +============================ $forge->modifyColumn() ---------------------- @@ -310,6 +291,50 @@ change the name, you can add a "name" key into the field defining array. .. literalinclude:: forge/026.php +.. _db-forge-adding-keys-to-a-table: + +Adding Keys to a Table +====================== + +.. versionadded:: 4.3.0 + +You may add keys to an existing table by using ``addKey()``, ``addPrimaryKey()``, +``addUniqueKey()`` or ``addForeignKey()`` and ``processIndexes()``: + +.. literalinclude:: forge/029.php + +.. _dropping-a-primary-key: + +Dropping a Primary Key +====================== + +.. versionadded:: 4.3.0 + +Execute a DROP PRIMARY KEY. + +.. literalinclude:: forge/028.php + +Dropping a Key +=============== + +Execute a DROP KEY. + +.. literalinclude:: forge/020.php + +Dropping a Foreign Key +====================== + +Execute a DROP FOREIGN KEY. + +.. literalinclude:: forge/019.php + +Renaming a Table +================ + +Executes a TABLE rename + +.. literalinclude:: forge/021.php + *************** Class Reference *************** @@ -325,15 +350,15 @@ Class Reference :returns: true on success, false on failure :rtype: bool - Adds a column to a table. Usage: See `Adding a Column to a Table`_. + Adds a column to an existing table. Usage: See `Adding a Field to a Table`_. .. php:method:: addField($field) :param array $field: Field definition to add - :returns: \CodeIgniter\Database\Forge instance (method chaining) - :rtype: \CodeIgniter\Database\Forge + :returns: ``\CodeIgniter\Database\Forge`` instance (method chaining) + :rtype: ``\CodeIgniter\Database\Forge`` - Adds a field to the set that will be used to create a table. Usage: See `Adding Fields`_. + Adds a field to the set that will be used to create a table. Usage: See `Adding Fields`_. .. php:method:: addForeignKey($fieldName, $tableName, $tableField[, $onUpdate = '', $onDelete = '', $fkName = '']) @@ -343,12 +368,10 @@ Class Reference :param string $onUpdate: Desired action for the "on update" :param string $onDelete: Desired action for the "on delete" :param string $fkName: Name of foreign key. This does not work with SQLite3 - :returns: \CodeIgniter\Database\Forge instance (method chaining) - :rtype: \CodeIgniter\Database\Forge + :returns: ``\CodeIgniter\Database\Forge`` instance (method chaining) + :rtype: ``\CodeIgniter\Database\Forge`` - .. note:: ``$fkName`` can be used since v4.3.0. - - Adds a foreign key to the set that will be used to create a table. Usage: See `Adding Foreign Keys`_. + Adds a foreign key to the set that will be used to create a table. Usage: See `Adding Foreign Keys`_. .. note:: ``$fkName`` can be used since v4.3.0. @@ -358,10 +381,10 @@ Class Reference :param bool $primary: Set to true if it should be a primary key or a regular one :param bool $unique: Set to true if it should be a unique key or a regular one :param string $keyName: Name of key to be added - :returns: \CodeIgniter\Database\Forge instance (method chaining) - :rtype: \CodeIgniter\Database\Forge + :returns: ``\CodeIgniter\Database\Forge`` instance (method chaining) + :rtype: ``\CodeIgniter\Database\Forge`` - Adds a key to the set that will be used to create a table. Usage: See `Adding Keys`_. + Adds a key to the set that will be used to create a table. Usage: See `Adding Keys`_. .. note:: ``$keyName`` can be used since v4.3.0. @@ -369,10 +392,10 @@ Class Reference :param mixed $key: Name of a key field or an array of fields :param string $keyName: Name of key to be added - :returns: \CodeIgniter\Database\Forge instance (method chaining) - :rtype: \CodeIgniter\Database\Forge + :returns: ``\CodeIgniter\Database\Forge`` instance (method chaining) + :rtype: ``\CodeIgniter\Database\Forge`` - Adds a primary key to the set that will be used to create a table. Usage: See `Adding Keys`_. + Adds a primary key to the set that will be used to create a table. Usage: See `Adding Keys`_. .. note:: ``$keyName`` can be used since v4.3.0. @@ -380,10 +403,10 @@ Class Reference :param mixed $key: Name of a key field or an array of fields :param string $keyName: Name of key to be added - :returns: \CodeIgniter\Database\Forge instance (method chaining) - :rtype: \CodeIgniter\Database\Forge + :returns: ``\CodeIgniter\Database\Forge`` instance (method chaining) + :rtype: ``\CodeIgniter\Database\Forge`` - Adds a unique key to the set that will be used to create a table. Usage: See `Adding Keys`_. + Adds a unique key to the set that will be used to create a table. Usage: See `Adding Keys`_. .. note:: ``$keyName`` can be used since v4.3.0. @@ -394,7 +417,7 @@ Class Reference :returns: true on success, false on failure :rtype: bool - Creates a new database. Usage: See `Creating and Dropping Databases`_. + Creates a new database. Usage: See `Creating and Dropping Databases`_. .. php:method:: createTable($table[, $if_not_exists = false[, array $attributes = []]]) @@ -404,7 +427,7 @@ Class Reference :returns: Query object on success, false on failure :rtype: mixed - Creates a new table. Usage: See `Creating a Table`_. + Creates a new table. Usage: See `Creating a Table`_. .. php:method:: dropColumn($table, $column_name) @@ -413,7 +436,7 @@ Class Reference :returns: true on success, false on failure :rtype: bool - Drops single or multiple columns from a table. Usage: See `Dropping Columns From a Table`_. + Drops single or multiple columns from a table. Usage: See `Dropping Fields From a Table`_. .. php:method:: dropDatabase($dbName) @@ -421,7 +444,7 @@ Class Reference :returns: true on success, false on failure :rtype: bool - Drops a database. Usage: See `Creating and Dropping Databases`_. + Drops a database. Usage: See `Creating and Dropping Databases`_. .. php:method:: dropKey($table, $keyName[, $prefixKeyName = true]) @@ -453,16 +476,19 @@ Class Reference :returns: true on success, false on failure :rtype: bool - Drops a table. Usage: See `Dropping a Table`_. + Drops a table. Usage: See `Dropping a Table`_. .. php:method:: processIndexes($table) + .. versionadded:: 4.3.0 + :param string $table: Name of the table to add indexes to :returns: true on success, false on failure :rtype: bool Used following ``addKey()``, ``addPrimaryKey()``, ``addUniqueKey()``, and ``addForeignKey()`` to add indexes to an existing table. + See `Adding Keys to a Table`_. .. php:method:: modifyColumn($table, $field) @@ -471,7 +497,7 @@ Class Reference :returns: true on success, false on failure :rtype: bool - Modifies a table column. Usage: See `Modifying a Column in a Table`_. + Modifies a table column. Usage: See `Modifying a Field in a Table`_. .. php:method:: renameTable($table_name, $new_table_name) @@ -480,4 +506,4 @@ Class Reference :returns: Query object on success, false on failure :rtype: mixed - Renames a table. Usage: See `Renaming a Table`_. + Renames a table. Usage: See `Renaming a Table`_. diff --git a/user_guide_src/source/dbmgmt/migration.rst b/user_guide_src/source/dbmgmt/migration.rst index c918d92d6ffc..bf43be4b6199 100644 --- a/user_guide_src/source/dbmgmt/migration.rst +++ b/user_guide_src/source/dbmgmt/migration.rst @@ -128,7 +128,7 @@ You can use (migrate) with the following options: This example will migrate ``Acme\Blog`` namespace with any new migrations on the test database group:: - > php spark migrate -g test -n 'Acme\Blog' + > php spark migrate -g test -n Acme\\Blog When using the ``--all`` option, it will scan through all namespaces attempting to find any migrations that have not been run. These will all be collected and then sorted as a group by date created. This should help diff --git a/user_guide_src/source/dbmgmt/seeds.rst b/user_guide_src/source/dbmgmt/seeds.rst index 3ed29914159f..9eb9ee4396f9 100644 --- a/user_guide_src/source/dbmgmt/seeds.rst +++ b/user_guide_src/source/dbmgmt/seeds.rst @@ -54,7 +54,7 @@ Using the command line, you can easily generate seed files. You can supply the **root** namespace where the seed file will be stored by supplying the ``--namespace`` option:: - > php spark make:seeder MySeeder --namespace Acme\Blog + > php spark make:seeder MySeeder --namespace Acme\\Blog If ``Acme\Blog`` is mapped to ``app/Blog`` directory, then this command will generate ``MySeeder.php`` at ``app/Blog/Database/Seeds`` directory. diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst index 02cc8c5924a1..c96e171680f3 100755 --- a/user_guide_src/source/general/common_functions.rst +++ b/user_guide_src/source/general/common_functions.rst @@ -104,9 +104,14 @@ Service Accessors :param string $name: The model classname. :param boolean $getShared: Whether to return a shared instance. :param ConnectionInterface|null $conn: The database connection. - :returns: More simple way of getting model instances + :returns: The model instances :rtype: object + More simple way of getting model instances. + + The ``model()`` uses ``Factories::models()`` internally. + See :ref:`factories-example` for details on the first parameter ``$name``. + See also the :ref:`Using CodeIgniter's Model `. .. php:function:: old($key[, $default = null,[, $escape = 'html']]) @@ -312,7 +317,7 @@ Miscellaneous Functions .. php:function:: redirect(string $route) - :param string $route: The reverse-routed or named route to redirect the user to. + :param string $route: The route name or Controller::method to redirect the user to. :rtype: RedirectResponse .. important:: When you use this function, an instance of ``RedirectResponse`` must be returned @@ -320,18 +325,40 @@ Miscellaneous Functions the :doc:`Controller Filter <../incoming/filters>`. If you forget to return it, no redirection will occur. - Returns a RedirectResponse instance allowing you to easily create redirects: + Returns a RedirectResponse instance allowing you to easily create redirects. + + **Redirect to a URI path** + + When you want to pass a URI path (relative to baseURL), use ``redirect()->to()``: .. literalinclude:: common_functions/005.php + :lines: 2- - .. note:: ``redirect()->back()`` is not the same as browser "back" button. - It takes a visitor to "the last page viewed during the Session" when the Session is available. - If the Session hasn’t been loaded, or is otherwise unavailable, then a sanitized version of HTTP_REFERER will be used. + **Redirect to a Defined Route** + + When you want to pass a :ref:`route name ` or Controller::method + for :ref:`reverse routing `, use ``redirect()->route()``: + + .. literalinclude:: common_functions/013.php + :lines: 2- - When passing an argument into the function, it is treated as a named/reverse-routed route, not a relative/full URI, + When passing an argument into the function, it is treated as a route name or + Controller::method for reverse routing, not a relative/full URI, treating it the same as using ``redirect()->route()``: .. literalinclude:: common_functions/006.php + :lines: 2- + + **Redirect Back** + + When you want to redirect back, use ``redirect()->back()``: + + .. literalinclude:: common_functions/014.php + :lines: 2- + + .. note:: ``redirect()->back()`` is not the same as browser "back" button. + It takes a visitor to "the last page viewed during the Session" when the Session is available. + If the Session hasn’t been loaded, or is otherwise unavailable, then a sanitized version of HTTP_REFERER will be used. .. php:function:: remove_invisible_characters($str[, $urlEncoded = true]) @@ -367,16 +394,23 @@ Miscellaneous Functions .. php:function:: route_to($method[, ...$params]) - :param string $method: The named route alias, or name of the controller/method to match. - :param int|string $params: One or more parameters to be passed to be matched in the route. The last parameter allows you to set the locale. + :param string $method: Route name or Controller::method + :param int|string ...$params: One or more parameters to be passed to the route. The last parameter allows you to set the locale. + :returns: a route (URI path) + :rtype: string .. note:: This function requires the controller/method to have a route defined in **app/Config/routes.php**. + .. important:: ``route_to()`` returns a *route*, not a full URI path for your site. + If your **baseURL** contains sub folders, the return value is not the same + as the URI to link. In that case, just use :php:func:`url_to()` instead. + See also :ref:`urls-url-structure`. + Generates a route for you based on a controller::method combination. Will take parameters into effect, if provided. .. literalinclude:: common_functions/009.php - Generates a route for you based on a named route alias. + Generates a route for you based on a route name. .. literalinclude:: common_functions/010.php @@ -384,10 +418,6 @@ Miscellaneous Functions .. literalinclude:: common_functions/011.php - .. note:: ``route_to()`` returns a route, not a full URI path for your site. - If your **baseURL** contains sub folders, the return value is not the same - as the URI to link. In that case, just use :php:func:`url_to()` instead. - .. php:function:: service($name[, ...$params]) :param string $name: The name of the service to load diff --git a/user_guide_src/source/general/common_functions/005.php b/user_guide_src/source/general/common_functions/005.php index 0b2197de88ed..6f6f6286dbad 100644 --- a/user_guide_src/source/general/common_functions/005.php +++ b/user_guide_src/source/general/common_functions/005.php @@ -1,22 +1,4 @@ back(); - -// Go to specific URI -return redirect()->to('/admin'); - -// Go to a named route -return redirect()->route('named_route'); - -// Keep the old input values upon redirect so they can be used by the `old()` function -return redirect()->back()->withInput(); - -// Set a flash message -return redirect()->back()->with('foo', 'message'); - -// Copies all cookies from global response instance -return redirect()->back()->withCookies(); - -// Copies all headers from the global response instance -return redirect()->back()->withHeaders(); +// Go to specific URI path. "admin/home" is the URI path relative to baseURL. +return redirect()->to('admin/home'); diff --git a/user_guide_src/source/general/common_functions/006.php b/user_guide_src/source/general/common_functions/006.php index 7df72c5dca79..f605edb4855a 100644 --- a/user_guide_src/source/general/common_functions/006.php +++ b/user_guide_src/source/general/common_functions/006.php @@ -1,4 +1,4 @@ get('users/(:num)/gallery(:any)', 'Galleries::showUserGallery/$1/$2'); +$routes->get('users/(:num)/gallery/(:num)', 'Galleries::showUserGallery/$1/$2'); ?> diff --git a/user_guide_src/source/general/common_functions/010.php b/user_guide_src/source/general/common_functions/010.php index 82df6a7dd714..16280ab7b9a9 100644 --- a/user_guide_src/source/general/common_functions/010.php +++ b/user_guide_src/source/general/common_functions/010.php @@ -1,7 +1,7 @@ get('users/(:num)/gallery(:any)', 'Galleries::showUserGallery/$1/$2', ['as' => 'user_gallery']); +$routes->get('users/(:num)/gallery/(:num)', 'Galleries::showUserGallery/$1/$2', ['as' => 'user_gallery']); ?> diff --git a/user_guide_src/source/general/common_functions/011.php b/user_guide_src/source/general/common_functions/011.php index 3d354cec2533..0bea6d6d3293 100644 --- a/user_guide_src/source/general/common_functions/011.php +++ b/user_guide_src/source/general/common_functions/011.php @@ -2,7 +2,7 @@ // The route is defined as: $routes->add( - '{locale}/users/(:num)/gallery(:any)', + '{locale}/users/(:num)/gallery/(:num)', 'Galleries::showUserGallery/$1/$2', ['as' => 'user_gallery'] ); diff --git a/user_guide_src/source/general/common_functions/013.php b/user_guide_src/source/general/common_functions/013.php new file mode 100644 index 000000000000..6fe1122b6407 --- /dev/null +++ b/user_guide_src/source/general/common_functions/013.php @@ -0,0 +1,4 @@ +route('user_gallery'); diff --git a/user_guide_src/source/general/common_functions/014.php b/user_guide_src/source/general/common_functions/014.php new file mode 100644 index 000000000000..28b73d6c267c --- /dev/null +++ b/user_guide_src/source/general/common_functions/014.php @@ -0,0 +1,16 @@ +back(); + +// Keep the old input values upon redirect so they can be used by the `old()` function. +return redirect()->back()->withInput(); + +// Set a flash message. +return redirect()->back()->with('foo', 'message'); + +// Copies all cookies from global response instance. +return redirect()->back()->withCookies(); + +// Copies all headers from the global response instance. +return redirect()->back()->withHeaders(); diff --git a/user_guide_src/source/general/environments.rst b/user_guide_src/source/general/environments.rst index bac1f4c394cb..c95041865f54 100644 --- a/user_guide_src/source/general/environments.rst +++ b/user_guide_src/source/general/environments.rst @@ -48,8 +48,8 @@ The simplest method to set the variable is in your :doc:`.env file `_. +This server variable can be set in your **.htaccess** file or Apache +config using `SetEnv `_. .. code-block:: apache @@ -58,11 +58,11 @@ config using `SetEnv .. _environment-nginx: -nginx +Nginx ----- -Under nginx, you must pass the environment variable through the ``fastcgi_params`` -in order for it to show up under the `$_SERVER` variable. This allows it to work on the +Under Nginx, you must pass the environment variable through the ``fastcgi_params`` +in order for it to show up under the ``$_SERVER`` variable. This allows it to work on the virtual-host level, instead of using `env` to set it for the entire server, though that would work fine on a dedicated server. You would then modify your server config to something like: @@ -80,7 +80,7 @@ like: } } -Alternative methods are available for nginx and other servers, or you can +Alternative methods are available for Nginx and other servers, or you can remove this logic entirely and set the constant based on the server's IP address (for instance). @@ -102,17 +102,17 @@ a fresh install: * production.php * testing.php -Effects On Default Framework Behavior +Effects on Default Framework Behavior ===================================== -There are some places in the CodeIgniter system where the ENVIRONMENT +There are some places in the CodeIgniter system where the ``ENVIRONMENT`` constant is used. This section describes how default framework behavior is affected. Error Reporting --------------- -Setting the ENVIRONMENT constant to a value of ``development`` will cause +Setting the ``ENVIRONMENT`` constant to a value of ``development`` will cause all PHP errors to be rendered to the browser when they occur. Conversely, setting the constant to ``production`` will disable all error output. Disabling error reporting in production is a diff --git a/user_guide_src/source/general/errors.rst b/user_guide_src/source/general/errors.rst index ee3c09e8c64a..c46aed6572ce 100644 --- a/user_guide_src/source/general/errors.rst +++ b/user_guide_src/source/general/errors.rst @@ -119,6 +119,8 @@ redirect code to use instead of the default (``302``, "temporary redirect"): Specify HTTP Status Code in Your Exception ========================================== +.. versionadded:: 4.3.0 + Since v4.3.0, you can specify the HTTP status code for your Exception class to implement ``HTTPExceptionInterface``. @@ -129,6 +131,8 @@ When an exception implementing ``HTTPExceptionInterface`` is caught by CodeIgnit Specify Exit Code in Your Exception =================================== +.. versionadded:: 4.3.0 + Since v4.3.0, you can specify the exit code for your Exception class to implement ``HasExitCodeInterface``. diff --git a/user_guide_src/source/general/modules.rst b/user_guide_src/source/general/modules.rst index 5785d99ad00d..6c3eb08f792c 100644 --- a/user_guide_src/source/general/modules.rst +++ b/user_guide_src/source/general/modules.rst @@ -208,7 +208,7 @@ Seeds Seed files can be used from both the CLI and called from within other seed files as long as the full namespace is provided. If calling on the CLI, you will need to provide double backslashes:: - > php public/index.php migrations seed Acme\\Blog\\Database\\Seeds\\TestPostSeeder + > php spark db:seed Acme\\Blog\\Database\\Seeds\\TestPostSeeder Helpers ======= diff --git a/user_guide_src/source/general/urls.rst b/user_guide_src/source/general/urls.rst index 25d7b577d977..965328ce817c 100644 --- a/user_guide_src/source/general/urls.rst +++ b/user_guide_src/source/general/urls.rst @@ -15,6 +15,8 @@ Your URLs can be defined using the :doc:`URI Routing ` featur The :doc:`URI Library <../libraries/uri>` and the :doc:`URL Helper <../helpers/url_helper>` contain functions that make it easy to work with your URI data. +.. _urls-url-structure: + URL Structure ============= diff --git a/user_guide_src/source/helpers/form_helper.rst b/user_guide_src/source/helpers/form_helper.rst index 0b05d49f2ae6..cb60013843cc 100644 --- a/user_guide_src/source/helpers/form_helper.rst +++ b/user_guide_src/source/helpers/form_helper.rst @@ -9,25 +9,28 @@ forms. :local: :depth: 2 +************* Configuration -============= +************* -Since ``v4.3.0``, void HTML elements (e.g. ````) in ``form_helper`` functions have been changed to be HTML5-compatible by default and if you need to be compatible with XHTML, you must set the ``$html5`` property in **app/Config/DocTypes.php** to ``false``. +Since v4.3.0, void HTML elements (e.g. ````) in ``form_helper`` functions have been changed to be HTML5-compatible by default and if you need to be compatible with XHTML, you must set the ``$html5`` property in **app/Config/DocTypes.php** to ``false``. +******************* Loading this Helper -=================== +******************* This helper is loaded using the following code: .. literalinclude:: form_helper/001.php -Escaping field values -===================== +********************* +Escaping Field Values +********************* You may need to use HTML and characters such as quotes within your form elements. In order to do that safely, you'll need to use :doc:`common function <../general/common_functions>` -:func:`esc()`. +:php:func:`esc()`. Consider the following example: @@ -37,7 +40,7 @@ Since the above string contains a set of quotes, it will cause the form to break. The :php:func:`esc()` function converts HTML special characters so that it can be used safely:: - + .. note:: If you use any of the form helper functions listed on this page, and you pass values as an associative array, @@ -45,8 +48,9 @@ characters so that it can be used safely:: to call this function. Use it only if you are creating your own form elements, which you would pass as strings. +******************* Available Functions -=================== +******************* The following functions are available: @@ -99,14 +103,14 @@ The following functions are available: