diff --git a/.editorconfig b/.editorconfig index 53faf5c3beac..7382bb8eb572 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,15 @@ -; top-most EditorConfig file +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + root = true -; Unix-style newlines [*] -end_of_line = lf - -[*.php] -indent_style = space -indent_size = 4 charset = utf-8 -trim_trailing_whitespace = true +indent_size = 4 +indent_style = space +end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes index bf0e1f766d96..d87cc8aa8cf2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,28 +5,36 @@ # git files .gitattributes export-ignore -.gitignore export-ignore +.gitignore export-ignore -# Don't give admin files -.github/ export-ignore -admin/ export-ignore -contributing/ export-ignore -.editorconfig export-ignore -.nojekyll export-ignore export-ignore +# admin files +.github/ export-ignore +admin/ export-ignore +contributing/ export-ignore +.editorconfig export-ignore CODE_OF_CONDUCT.md export-ignore -DCO.txt export-ignore -PULL_REQUEST_TEMPLATE.md export-ignore -stale.yml export-ignore -Vagrantfile.dist export-ignore +CONTRIBUTING.md export-ignore -# They don't want our test files -tests/system/ export-ignore -utils/ export-ignore -rector.php export-ignore -phpunit.xml.dist export-ignore -phpstan.neon.dist export-ignore -.php-cs-fixer.dist.php export-ignore +# contributor/development files +tests/ export-ignore +utils/ export-ignore +.php-cs-fixer.dist.php export-ignore +.php-cs-fixer.no-header.php export-ignore +.php-cs-fixer.tests.php export-ignore +.php-cs-fixer.user-guide.php export-ignore +structarmed.php export-ignore +phpmetrics.json export-ignore +phpstan-baseline.php export-ignore +phpstan-bootstrap.php export-ignore +phpstan.dist.neon export-ignore +phpunit.dist.xml export-ignore +psalm-baseline.xml export-ignore +psalm.xml export-ignore +psalm_autoload.php export-ignore +rector.php export-ignore +AGENTS.md export-ignore -# The source user guide, either +# source user guide user_guide_src/ export-ignore +.nojekyll export-ignore phpdoc.dist.xml export-ignore diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 02224130008f..000000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: 'Bug: ' -labels: bug -assignees: '' - ---- - ---- -name: Bug report -about: Help us improve the framework by reporting bugs! - ---- - -**Direction** -We use github issues to track bugs, not for support. -If you have a support question, or a feature request, raise these as threads on our -[forum](https://forum.codeigniter.com/index.php). - -**Describe the bug** -A clear and concise description of what the bug is. - -**CodeIgniter 4 version** -Which version (and branch, if applicable) the bug is in. - -**Affected module(s)** -Which package or class is the bug in, if known. - -**Expected behavior, and steps to reproduce if appropriate** -A clear and concise description of what you expected to happen, -and how you got there. -Feel free to include a text/log extract, but use a pastebin facility for any -screenshots you deem necessary. - -**Context** - - OS: [e.g. Windows 99] - - Web server [e.g. Apache 1.2.3] - - PHP version [e.g. 6.5.4] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000000..3f99053424ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,138 @@ +name: Bug report +description: Create a report to help us improve CodeIgniter +title: "Bug: " +labels: ['bug'] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + Before you begin, **please ensure that there are no existing issues, + whether still open or closed, related to your report**. + If there is, your report will be closed promptly. + + For example, if you get the error "*Undefined property: Config\\Exceptions::$sensitiveDataInTrace*", + you can search the GitHub repository with the keyword "[$sensitiveDataInTrace](https://github.com/codeigniter4/CodeIgniter4/search?q=%24sensitiveDataInTrace&type=issues)". + + --- + + - type: dropdown + id: php-version + attributes: + label: PHP Version + description: Which PHP versions did you run your code? + multiple: true + options: + - '8.2' + - '8.3' + - '8.4' + - '8.5' + validations: + required: true + + - type: input + id: codeigniter-version + attributes: + label: CodeIgniter4 Version + description: | + e.g. 4.1.5 + If you are not using the [latest version](https://github.com/codeigniter4/CodeIgniter4/releases), please + check to see if the problem occurs with the latest version. + validations: + required: true + + - type: dropdown + id: codeigniter-installation + attributes: + label: CodeIgniter4 Installation Method + multiple: false + options: + - Composer (using `codeigniter4/appstarter`) + - Composer (as dependency to an existing project) + - Manual (zip or tar.gz) + - Git + validations: + required: true + + - type: dropdown + id: operating-systems + attributes: + label: Which operating systems have you tested for this bug? + description: You may select more than one. + multiple: true + options: + - macOS + - Windows + - Linux + validations: + required: true + + - type: dropdown + id: server + attributes: + label: Which server did you use? + options: + - apache + - cli + - cli-server (PHP built-in webserver) + - cgi-fcgi + - fpm-fcgi + - phpdbg + validations: + required: true + + - type: dropdown + id: environment + attributes: + label: Environment + description: Which CI_ENVIRONMENT setting are you using? + multiple: true + options: + - production + - development + - testing + - other (custom) + validations: + required: true + + - type: input + id: database + attributes: + label: Database + description: e.g. MySQL 5.6, MariaDB 10.2, PostgreSQL 9.6 + validations: + required: false + + - type: textarea + id: description + attributes: + label: What happened? + placeholder: Tell us what you see! + validations: + required: true + + - type: textarea + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior. + validations: + required: true + + - type: textarea + attributes: + label: Expected Output + description: What do you expect to happen instead of this filed bug? + validations: + required: true + + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..d399e41dca3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +blank_issues_enabled: false +contact_links: + - name: CodeIgniter Forum + url: https://forum.codeigniter.com/forum-30.html + about: Please ask your support questions and/or feature requests in the forums. Thanks! + + - name: CodeIgniter Slack channel + url: https://codeigniterchat.slack.com + about: Engage with other members of the community in our Slack channel. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 3bd2d0a6c44f..000000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Feature request -about: How to submit a feature request -title: '' -labels: '' -assignees: '' - ---- - -Please submit feature requests to our [forum](https://forum.codeigniter.com/forum-29.html). -We use github issues to track bugs and planned work. diff --git a/.github/ISSUE_TEMPLATE/planned-work.md b/.github/ISSUE_TEMPLATE/planned-work.md index 4faa45ed874e..3da11a7dbc58 100644 --- a/.github/ISSUE_TEMPLATE/planned-work.md +++ b/.github/ISSUE_TEMPLATE/planned-work.md @@ -1,10 +1,10 @@ --- name: Planned work -about: Approved work planning +about: Maintainers' space. DO NOT use this for your bug reports! title: 'Dev: ' labels: dev assignees: '' --- -Repo maintainers will create "issues" for planned work, so it can be tracked. +Maintainers will create "issues" for planned work, so it can be tracked. diff --git a/.github/ISSUE_TEMPLATE/support-question.md b/.github/ISSUE_TEMPLATE/support-question.md deleted file mode 100644 index 390991ac99d0..000000000000 --- a/.github/ISSUE_TEMPLATE/support-question.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Support question -about: How to ask a support question -title: '' -labels: '' -assignees: '' - ---- - -Please ask support questions on our [forum](https://forum.codeigniter.com/forum-30.html). -We use github issues to track bugs and planned work. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000000..38f7db70cc60 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ + +**Description** +Explain what you have changed, and why. + +**Checklist:** +- [ ] Securely signed commits +- [ ] Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication) +- [ ] Unit testing, with >80% coverage +- [ ] User guide updated +- [ ] Conforms to style guide diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000000..4ad38ee3eadf --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,30 @@ +# Copilot Code Review Instructions + +Repository-wide conventions, the target-branch compatibility policy, coding +standards, and FrankenPHP Worker Mode requirements are defined in the root +`AGENTS.md`. Apply that policy in every review; this file covers review +behavior only. + +## Review priorities + +Prioritize actionable findings involving: + +- incorrect behavior or an unhandled edge case; +- backward compatibility; +- security, input validation, and context-appropriate output escaping; +- request-specific state leaking through globals, static properties, or + shared services, including under Worker Mode where one process serves + many requests; +- differences between supported database drivers (MySQLi, PostgreSQL, + SQLite3, SQLSRV, OCI8); +- missing regression tests, user-guide changes, changelog entries, or + upgrade instructions. + +## Review behavior + +- Determine the PR base branch and apply the compatibility policy from + `AGENTS.md`. If the base branch cannot be determined, assume `develop` + and state that assumption. +- Do not report formatting-only issues already enforced by PHP-CS-Fixer. +- Every finding should identify a concrete failure scenario and the + affected code. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d3afefe5c9fd..c6188a21b9e6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,13 +6,21 @@ updates: schedule: interval: 'daily' open-pull-requests-limit: 10 + groups: + composer-dependencies: + patterns: + - '*' - package-ecosystem: 'github-actions' - directory: '/' + directories: + - '/' + - '/admin/framework' + - '/admin/starter' + - '/admin/userguide' schedule: interval: 'daily' - ignore: - - dependency-name: '*' - update-types: - - 'version-update:semver-minor' - - 'version-update:semver-patch' + groups: + github-actions: + patterns: + - '*' + group-by: dependency-name diff --git a/.github/instructions/database.instructions.md b/.github/instructions/database.instructions.md new file mode 100644 index 000000000000..b550a4a0573e --- /dev/null +++ b/.github/instructions/database.instructions.md @@ -0,0 +1,19 @@ +--- +applyTo: "system/Database/**/*.php,tests/system/Database/**/*.php" +--- + +# Database layer + +- Consider all supported drivers: MySQLi, PostgreSQL, SQLite3, SQLSRV, and + OCI8. +- Check identifier quoting, value binding, `NULL` behavior, transactions, + affected rows, return types, and platform-specific SQL. +- Do not implement driver-independent behavior in only one driver. +- When changing a base database class, inspect driver overrides and their + method signatures. +- Keep driver-independent tests separate from tests that require a live + database. +- Use the `DatabaseLive` PHPUnit group only when a real database connection is + required. +- Prefer focused tests for the affected base class and drivers. Leave the full + multi-driver matrix to GitHub Actions. diff --git a/.github/instructions/framework-core.instructions.md b/.github/instructions/framework-core.instructions.md new file mode 100644 index 000000000000..0ae53495c513 --- /dev/null +++ b/.github/instructions/framework-core.instructions.md @@ -0,0 +1,23 @@ +--- +applyTo: "system/**/*.php" +--- + +# Framework core + +- Assume public and protected symbols are extension points used by third-party + applications unless they are clearly marked internal. +- Apply the target-branch compatibility policy from the root `AGENTS.md` + before changing any extension point. +- Before changing a signature or behavior, inspect parent classes, implemented + interfaces, traits, service factories, configuration, and corresponding + tests. +- Keep file paths, class names, and namespaces aligned under `CodeIgniter\`. +- Preserve existing extension and dependency-injection points. +- Consider long-running worker environments. Do not retain request-specific + state in static properties, globals, or shared services after a request + finishes. +- Avoid introducing a framework-wide abstraction to solve a single local + problem. +- On `develop`, keep exceptions and observable side effects compatible. On a + `4.*` target, change them only as an explicit, narrow, documented breaking + enhancement with a migration path. diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 000000000000..af2d9a96ba96 --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,21 @@ +--- +applyTo: "tests/**/*.php" +--- + +# PHPUnit tests + +- Mirror the corresponding `system/` component and local test conventions + where practical. +- Add the appropriate PHPUnit `Group` attribute described in + `tests/README.md`. +- Prefer `assertSame()` and dedicated assertions over loose or generic + assertions. +- Cover the regression or behavior through a public API where possible. + Avoid testing private implementation details. +- Restore services, factories, environment variables, superglobals, handlers, + locale, time-related state, and other global state modified by a test. +- Tests must not depend on execution order or state left by another test. +- Do not replace a meaningful assertion with a weaker assertion to make a test + pass. +- After changing behavior, run the smallest relevant test file or component. + Do not run the complete test suite by default. diff --git a/.github/instructions/user-guide.instructions.md b/.github/instructions/user-guide.instructions.md new file mode 100644 index 000000000000..9b55a50ba3f1 --- /dev/null +++ b/.github/instructions/user-guide.instructions.md @@ -0,0 +1,27 @@ +--- +applyTo: "user_guide_src/**/*.rst" +--- + +# User guide + +- Preserve the existing reStructuredText structure, terminology, directives, + anchors, and surrounding writing style. +- Keep PHP examples compatible with PHP 8.2 and verify signatures and behavior + against the current framework code. +- Distinguish new behavior from existing behavior and document defaults + precisely. +- Determine the PR target branch before describing compatibility: + `develop` follows the patch-release policy, while `4.*` follows the + minor-release policy in the root `AGENTS.md`. +- Behavior changes, enhancements, deprecations, and important bug fixes may + require a changelog entry. +- Changes requiring users to modify code or configuration may require an + upgrading-guide entry. +- For an intentional compatibility break targeting `4.*`, document the old + behavior, new behavior, affected users, reason for the change, and the + smallest migration in both the minor changelog and upgrading guide. +- Keep code examples minimal but complete enough to run in a normal + CodeIgniter application. +- Leave the complete Sphinx build and documentation validation to GitHub + Actions unless the task specifically requires local documentation + validation. diff --git a/.github/instructions/worker-mode.instructions.md b/.github/instructions/worker-mode.instructions.md new file mode 100644 index 000000000000..ec6e1742ec5a --- /dev/null +++ b/.github/instructions/worker-mode.instructions.md @@ -0,0 +1,38 @@ +--- +applyTo: "system/Boot.php,system/CodeIgniter.php,system/Commands/Worker/**,system/Config/BaseService.php,system/Config/Factories.php,system/Config/Factory.php,system/Config/Services.php,system/Database/Config.php,system/Database/BaseConnection.php,system/Database/ConnectionInterface.php,system/Database/*/Connection.php,system/Events/Events.php,system/Session/**,system/Cache/**,system/HTTP/**,system/Filters/**,system/Router/**,system/Security/**,system/Debug/Toolbar.php,system/Debug/Toolbar/**,app/Config/WorkerMode.php,tests/system/Commands/Worker/**,user_guide_src/source/installation/worker_mode.rst" +--- + +# FrankenPHP Worker Mode + +Use `system/Commands/Worker/Views/frankenphp-worker.php.tpl` as the canonical +Worker Mode entry point. `worker:install` publishes this template to +`public/frankenphp-worker.php`. + +When changing bootstrap or request-lifecycle code, trace all three Worker Mode +phases: + +1. One-time process bootstrap through `Boot::bootWorker()`. +2. Per-request preparation: reconnect database and cache connections, reset + the `CodeIgniter` instance, and replace all request superglobals before + calling `$app->run()`. +3. Post-request cleanup: close the session, clean up unfinished database + transactions, reset factories and non-persistent services, clean up event + listeners and performance logs, and reset the debug toolbar. + +- Preserve the phase ordering unless the change explicitly proves a different + order is safe. +- Check every new mutable static property, singleton, shared service, event + listener, connection, handler, or global for cross-request state leakage. +- Do not preserve a service across requests merely for performance. Persistent + services must be safe to reuse and must not retain request, response, user, + route, security token, locale, or error state. +- Ensure exceptions and early exits cannot skip cleanup needed before the next + request. +- For state-isolation fixes, prefer a regression test that exercises two + sequential requests or reset cycles in the same process and proves the + second request cannot observe the first request's state. +- If the template contract changes, update focused assertions in + `tests/system/Commands/Worker/WorkerCommandsTest.php`. +- If existing installations need the new generated entry point, update the + changelog and upgrading guide and instruct users to run + `php spark worker:install --force`. diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000000..69cb0c51e123 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,23 @@ +# https://github.com/actions/labeler?tab=readme-ov-file#usage + +# Add the `4.8` label to PRs that target the `4.8` branch. +'4.8': # @todo change value whenever the next minor version is changed +- base-branch: '4.8' + +# Add the `github_actions` label to PRs that change any file in the `.github/workflows/` directory. +'github_actions': +- changed-files: + - any-glob-to-any-file: + - '.github/workflows/*' + +# Add the `documentation` label to PRs for documentation only. +'documentation': +- changed-files: + - any-glob-to-all-files: + - 'user_guide_src/source/**' + +# Add the `testing` label to PRs that changes tests only. +'testing': +- changed-files: + - any-glob-to-all-files: + - 'tests/**' diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 000000000000..2e58526188aa --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,69 @@ +# https://mergeable.readthedocs.io/en/latest/configuration.html + +version: 2 +mergeable: + - when: issues.opened, issues.reopened + validate: + - do: or + validate: + - do: and + validate: + - do: description + must_include: + regex: '### PHP Version' + - do: description + must_include: + regex: '### CodeIgniter4 Version' + - do: author + must_include: + regex: ^kenjis|lonnieezell|MGatner|michalsn|paulbalandan|samsonasik|ddevsr$ + fail: + - do: comment + payload: + body: | + Hi there, @@author! :wave: + + It looks like you opened an issue without following the bug report template: + + * Bug report ([open an issue](https://github.com/codeigniter4/CodeIgniter4/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=Bug%3A+)) + * For feature request or support question, please use the [forums](https://forum.codeigniter.com/forum-30.html). + + The current issue will be closed. This is a precaution to save maintainers' time, I hope you'll understand. + + Sincerely, the mergeable bot 🤖 + - do: close + + - when: pull_request.opened, pull_request.ready_for_review + filter: + - do: payload + pull_request: + author_association: + must_include: + regex: ^NONE|FIRST_TIME_CONTRIBUTOR|FIRST_TIMER$ + validate: [] + pass: + - do: comment + payload: + body: | + Hi there, @@author! :wave: + + Thank you for sending this PR! + + We expect the following in all Pull Requests (PRs). + - PRs must be sent to the [appropriate branch](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#branching) + - All git commits must be [GPG-signed](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#signing) + - Must follow our [style guide](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#php-style) + - Be [commented](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#comments) in the PHP source file + - Be documented in the [user guide](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#user-guide) + - Be [unit tested](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#unit-testing) + - Pass all checks in GitHub Actions + + > [!IMPORTANT] + > We expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite to prove the code works. + + If pull requests do not comply with the above, they will likely be closed. Since we are a team of volunteers, we don't have any more time to work + on the framework than you do. Please make it as painless for your contributions to be included as possible. + + See https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md + + Sincerely, the mergeable bot 🤖 diff --git a/.github/prlint.json b/.github/prlint.json new file mode 100644 index 000000000000..bb352e554b71 --- /dev/null +++ b/.github/prlint.json @@ -0,0 +1,8 @@ +{ + "title": [ + { + "pattern": "^(\\[\\d+\\.\\d+\\]\\s{1})?(feat|fix|chore|docs|perf|refactor|style|test|config|revert)(\\([\\-.@:`a-zA-Z0-9]+\\))?!?:\\s{1}\\S.+\\S|Prep for \\d\\.\\d\\.\\d release|\\d\\.\\d\\.\\d (Ready|Merge) code$", + "message": "PR title must include the type (feat, fix, chore, docs, perf, refactor, style, test, config, revert) of the commit per Conventional Commits specification. See https://www.conventionalcommits.org/en/v1.0.0/ for the discussion." + } + ] +} diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000000..09ead7ca89d2 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,24 @@ +changelog: + exclude: + authors: + - dependabot + - dependabot[bot] + categories: + - title: Breaking Changes + labels: + - 'breaking change' + - title: Fixed Bugs + labels: + - bug + - title: New Features + labels: + - 'new feature' + - title: Enhancements + labels: + - enhancement + - title: Refactoring + labels: + - refactor + - title: Others (Only for checking. Remove this category) + labels: + - "*" diff --git a/.github/scripts/deploy-appstarter b/.github/scripts/deploy-appstarter index 870f24760d6d..86d7bad2241a 100644 --- a/.github/scripts/deploy-appstarter +++ b/.github/scripts/deploy-appstarter @@ -20,7 +20,7 @@ git checkout master rm -rf * # Copy common files -releasable='app public writable env LICENSE spark' +releasable='app public writable env LICENSE spark preload.php' for fff in $releasable; do cp -Rf ${SOURCE}/$fff ./ @@ -32,4 +32,4 @@ cp -Rf ${SOURCE}/admin/starter/. ./ # Commit the changes git add . git commit -m "Release ${RELEASE}" -git push +bash ${SOURCE}/.github/scripts/secure-git-push https://github.com/codeigniter4/appstarter.git HEAD:master diff --git a/.github/scripts/deploy-framework b/.github/scripts/deploy-framework index c3f7e39fa1b7..ea18cadf1607 100644 --- a/.github/scripts/deploy-framework +++ b/.github/scripts/deploy-framework @@ -20,7 +20,7 @@ git checkout master rm -rf * # Copy common files -releasable='app public writable env LICENSE spark system' +releasable='app public writable env LICENSE spark system preload.php' for fff in $releasable; do cp -Rf ${SOURCE}/$fff ./ @@ -28,8 +28,10 @@ done # Copy repo-specific files cp -Rf ${SOURCE}/admin/framework/. ./ +# Copy tests files +cp -Rf ${SOURCE}/admin/starter/tests/. ./tests/ # Commit the changes git add . git commit -m "Release ${RELEASE}" -git push +bash ${SOURCE}/.github/scripts/secure-git-push https://github.com/codeigniter4/framework.git HEAD:master diff --git a/.github/scripts/deploy-userguide b/.github/scripts/deploy-userguide new file mode 100755 index 000000000000..e664ac1b7ff5 --- /dev/null +++ b/.github/scripts/deploy-userguide @@ -0,0 +1,61 @@ +#!/bin/bash + +## Deploy codeigniter4/userguide + +# Setup variables +SOURCE=$1 +TARGET=$2 +RELEASE=$3 + +# Check if RELEASE is empty +if [ -z "$RELEASE" ]; then + echo "Error: \$RELEASE parameter is empty." + exit 1 +fi + +VERSION=$(echo "$RELEASE" | cut -c 2-) + +# Check if VERSION is empty +if [ -z "$VERSION" ]; then + echo "Error: Failed to extract \$VERSION from \$RELEASE parameter '$RELEASE'." + exit 1 +fi + +# Check if VERSION matches the format X.Y.Z +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: VERSION '$VERSION' does not match the expected format X.Y.Z." + exit 1 +fi + +echo "Preparing for version $3" +echo "Merging files from $1 to $2" + +# Prepare the source +cd $SOURCE +git checkout master +cd user_guide_src +make html +make epub + +# Prepare the target +cd $TARGET +git checkout master +rm -rf docs + +# Copy common files +cp -Rf ${SOURCE}/LICENSE ./ + +# Copy repo-specific files +cp -Rf ${SOURCE}/admin/userguide/. ./ + +# Copy files +cp -Rf ${SOURCE}/user_guide_src/build/html ./docs +cp -Rf ${SOURCE}/user_guide_src/build/epub/CodeIgniter.epub ./CodeIgniter${VERSION}.epub + +# Ensure underscore prefixed files are published +touch ${TARGET}/docs/.nojekyll + +# Commit the changes +git add . +git commit -m "Release ${RELEASE}" +bash ${SOURCE}/.github/scripts/secure-git-push https://github.com/codeigniter4/userguide.git HEAD:master diff --git a/.github/scripts/random-tests-config.txt b/.github/scripts/random-tests-config.txt new file mode 100644 index 000000000000..5bf0fce66733 --- /dev/null +++ b/.github/scripts/random-tests-config.txt @@ -0,0 +1,49 @@ +# CodeIgniter4 Components Verified for Random Test Execution +# This file lists components that have been verified to pass all tests +# when run in random order (--order-by=random) +# +# Format: One component directory name per line (matching tests/system//) +# Comments start with # +# Uncomment components as they are verified to work with random execution +# +# Reference: https://github.com/codeigniter4/CodeIgniter4/issues/9968 + +API +AutoReview +Autoloader +Cache +CLI +Commands +Config +Cookie +# DataCaster +# DataConverter +# Database +# Debug +Email +# Encryption +# Entity +Events +Files +Filters +Format +HTTP +# Helpers +Honeypot +HotReloader +# I18n +# Images +Language +Log +# Models +Pager +Publisher +RESTful +# Router +Security +# Session +Test +Throttle +Typography +# Validation +View diff --git a/.github/scripts/run-random-tests.sh b/.github/scripts/run-random-tests.sh new file mode 100755 index 000000000000..fe321bef0492 --- /dev/null +++ b/.github/scripts/run-random-tests.sh @@ -0,0 +1,917 @@ +#!/usr/bin/env bash + +################################################################################ +# CodeIgniter4 - Random Test Execution Verification +# +# Verifies that tests for each component pass when run in random order. +# Reads a list of components from a config file and tests each with parallel +# execution while respecting a configurable concurrency limit. +# +# Usage: ./run-random-tests.sh [options] +# Options: +# -q, --quiet Suppress debug output +# -c, --component COMPONENT Test single COMPONENT (overrides config file) +# -n, --max-jobs MAX_JOBS Limit concurrent test jobs (auto-detect if omitted) +# -r, --repeat REPEAT Repeat full component run REPEAT times +# -t, --timeout TIMEOUT Per-component TIMEOUT in seconds (0 disables, default: 300) +# -h, --help Show this help message +# +# Examples: +# ./run-random-tests.sh --repeat 10 +# ./run-random-tests.sh --component Database --repeat 5 +# ./run-random-tests.sh --repeat 10 --max-jobs 4 --quiet +################################################################################ + +set -u +export LC_NUMERIC=C +trap 'kill "${bg_pids[@]:-}" 2>/dev/null; wait 2>/dev/null' EXIT INT TERM + +################################################################################ +# CONFIGURATION & INITIALIZATION +################################################################################ + +# Color codes for terminal output +readonly RED='\033[0;31m' +readonly BOLD_RED='\033[1;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BOLD_YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly BOLD='\033[1m' +readonly RESET='\033[0m' + +# Script paths +readonly script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +readonly project_root="$( cd "$script_dir/../.." && pwd )" +readonly config_file="$script_dir/random-tests-config.txt" +readonly results_dir="$project_root/build/random-tests" + +# Runtime variables +quiet="" +component="" +max_jobs="" +repeat_count=1 +timeout_seconds=300 +first_result=true +declare -a bg_pids=() + +# Counters +completed=0 +passed=0 +failed=0 +skipped=0 +total=0 + +# Component state tracking +declare -a displayed_components=() +declare -a failed_components=() +declare -a skipped_components=() + +################################################################################ +# UTILITY FUNCTIONS +################################################################################ + +is_quiet() { + [[ "$quiet" == "--quiet" || "$quiet" == "-q" ]] +} + +should_show_spinner() { + if ! is_quiet; then + return 1 + fi + + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + return 1 + fi + + return 0 +} + +show_spinner() { + local spinner_marker="$results_dir/run_random_tests_$$.spinner" + touch "$spinner_marker" + + local spinner_chars=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') + local spinner_index=0 + + echo -ne "\033[?25l" >&2 + + ( + while [[ -f "$spinner_marker" ]]; do + echo -ne "\033[2K\r${BLUE}${spinner_chars[$((spinner_index % 10))]} Running tests in parallel...${RESET}" >&2 + ((spinner_index++)) + sleep 0.1 + done + echo -ne "\033[2K\r" >&2 + ) & + + echo "$!" > "${spinner_marker}.pid" +} + +stop_spinner() { + local spinner_marker="$results_dir/run_random_tests_$$.spinner" + + if [[ ! -f "$spinner_marker" ]]; then + echo -ne "\033[?25h" >&2 + return + fi + + rm -f "$spinner_marker" + echo -ne "\033[2K\r" >&2 + + if [[ -f "${spinner_marker}.pid" ]]; then + kill "$(cat "${spinner_marker}.pid")" 2>/dev/null || true + rm -f "${spinner_marker}.pid" + fi + + wait 2>/dev/null + echo -ne "\033[?25h" >&2 +} + +print_header() { + echo -e "${BLUE}==============================================================================${RESET}" + echo -e "${BLUE}$1${RESET}" + echo -e "${BLUE}==============================================================================${RESET}" +} + +print_success() { + echo -e "${GREEN}✓ $1${RESET}" +} + +print_error() { + echo -e "${RED}✗ $1${RESET}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${RESET}" +} + +print_debug() { + if ! is_quiet; then + echo -e "${BLUE}🔧 $1${RESET}" + fi +} + +inflect() { + local count=$1 + local singular=$2 + local plural=${3:-${singular}s} + + if [[ "$count" -eq 1 ]]; then + echo "$singular" + return + fi + + echo "$plural" +} + +generate_phpunit_random_seed() { + local seed=$(date +%s) + + if [[ ! "$seed" =~ ^[0-9]+$ ]]; then + echo 1 + return + fi + + echo $((seed + (RANDOM % 1000))) +} + +extract_test_order() { + local events_file="$1" + + if [[ ! -f "$events_file" ]]; then + return + fi + + while IFS= read -r line; do + if [[ "$line" =~ ^Test\ Prepared\ \((.*)\)$ ]]; then + echo "${BASH_REMATCH[1]}" + fi + done < "$events_file" +} + +get_failed_test_predecessor() { + local events_file="$1" + + if [[ ! -f "$events_file" ]]; then + return + fi + + local failed_test="" + while IFS= read -r line; do + if [[ "$line" =~ ^Test\ Failed\ \((.*)\)$ ]] \ + || [[ "$line" =~ ^Test\ Errored\ \((.*)\)$ ]] \ + || [[ "$line" =~ ^Test\ Considered\ Risky\ \((.*)\)$ ]] \ + || [[ "$line" =~ ^Test\ Triggered\ Warning\ \((.*)\)$ ]] \ + || [[ "$line" =~ ^Test\ Triggered\ PHP\ Warning\ \((.*)\)$ ]] \ + || [[ "$line" =~ ^Test\ Triggered\ PHP\ Notice\ \((.*)\)$ ]] \ + || [[ "$line" =~ ^Test\ Triggered\ PHP\ Deprecation\ \((.*)\)$ ]] \ + || [[ "$line" =~ ^Test\ Triggered\ PHP\ Unit\ Deprecation\ \((.*)\)$ ]]; then + failed_test="${BASH_REMATCH[1]}" + break + fi + done < "$events_file" + + if [[ -z "$failed_test" ]]; then + return + fi + + local previous_test="" + while IFS= read -r line; do + if [[ "$line" =~ ^Test\ Prepared\ \((.*)\)$ ]]; then + local current_test="${BASH_REMATCH[1]}" + if [[ "$current_test" == "$failed_test" ]]; then + echo "$failed_test|$previous_test" + return + fi + previous_test="$current_test" + fi + done < "$events_file" + + echo "$failed_test|" +} + +print_result() { + local type=$1 completed=$2 total=$3 component=$4 elapsed_str=$5 + local padded=$(printf "%${#total}d" "$completed") + local color symbol + + case "$type" in + success) color=$GREEN; symbol="✓" ;; + failure) color=$RED; symbol="✗" ;; + warning) color=$YELLOW; symbol="⚠" ;; + esac + + echo -e "${BOLD_YELLOW}[${padded}/${total}]${RESET} ${color}${symbol}${RESET} Component: ${BLUE}$component${RESET} ${YELLOW}($elapsed_str)${RESET}" +} + +format_elapsed_time() { + local elapsed=$1 + + if ! [[ "$elapsed" =~ ^[0-9]+$ ]]; then + echo "N/A" + return + fi + + if [[ $elapsed -ge 60000 ]]; then + printf "%dm %.2fs" "$((elapsed / 60000))" "$(echo "scale=2; ($elapsed % 60000) / 1000" | bc)" + elif [[ $elapsed -ge 1000 ]]; then + printf "%.2fs" "$(echo "scale=2; $elapsed / 1000" | bc)" + else + echo "${elapsed}ms" + fi +} + +################################################################################ +# CONFIGURATION & VALIDATION +################################################################################ + +parse_arguments() { + while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + -q|--quiet) + quiet="--quiet" + shift + ;; + -c|--component) + if [[ $# -lt 2 ]]; then + print_error "Missing value for --component" + exit 1 + fi + + component="$2" + shift 2 + ;; + -n|--max-jobs) + if [[ $# -lt 2 ]]; then + print_error "Missing value for --max-jobs" + exit 1 + fi + + if [[ ! "$2" =~ ^[1-9][0-9]*$ ]]; then + print_error "--max-jobs must be a positive integer" + exit 1 + fi + + max_jobs="$2" + shift 2 + ;; + -r|--repeat) + if [[ $# -lt 2 ]]; then + print_error "Missing value for --repeat" + exit 1 + fi + + if [[ ! "$2" =~ ^[1-9][0-9]*$ ]]; then + print_error "--repeat must be a positive integer" + exit 1 + fi + + repeat_count="$2" + shift 2 + ;; + -t|--timeout) + if [[ $# -lt 2 ]]; then + print_error "Missing value for --timeout" + exit 1 + fi + + if [[ ! "$2" =~ ^[0-9]+$ ]]; then + print_error "--timeout must be a non-negative integer" + exit 1 + fi + + timeout_seconds="$2" + shift 2 + ;; + *) + print_error "Unknown option '$1'" + exit 1 + ;; + esac + done +} + +show_help() { + echo "CodeIgniter4 - Random Test Execution Verification" + echo "" + echo -e "${YELLOW}Usage:${RESET}" + echo -e " ${GREEN}$0${RESET} [options]" + echo "" + echo -e "${YELLOW}Options:${RESET}" + echo -e " ${GREEN}-q, --quiet${RESET} Suppress debug output" + echo -e " ${GREEN}-c, --component COMPONENT${RESET} Test single ${GREEN}COMPONENT${RESET} (overrides config file)" + echo -e " ${GREEN}-n, --max-jobs MAX_JOBS${RESET} Limit concurrent test jobs (auto-detect if omitted)" + echo -e " ${GREEN}-r, --repeat REPEAT${RESET} Repeat full component run ${GREEN}REPEAT${RESET} times" + echo -e " ${GREEN}-t, --timeout TIMEOUT${RESET} Per-component ${GREEN}TIMEOUT${RESET} in seconds (0 disables, default: 300)" + echo -e " ${GREEN}-h, --help${RESET} Show this help message" +} + +auto_detect_max_jobs() { + if command -v nproc &>/dev/null; then + nproc + return + fi + + if command -v sysctl &>/dev/null; then + sysctl -n hw.ncpu 2>/dev/null + return + fi + + echo 4 +} + +check_required_tools() { + local -a missing_tools=() + local -a required_tools=(date find sort sed grep bc) + local tool + + for tool in "${required_tools[@]}"; do + if ! command -v "$tool" >/dev/null 2>&1; then + missing_tools+=("$tool") + fi + done + + if [[ $timeout_seconds -gt 0 ]]; then + if ! command -v timeout >/dev/null 2>&1; then + if ! command -v gtimeout >/dev/null 2>&1; then + if ! command -v pgrep >/dev/null 2>&1; then + missing_tools+=("pgrep") + fi + + if ! command -v pkill >/dev/null 2>&1; then + missing_tools+=("pkill") + fi + fi + fi + fi + + if [[ ! -x "$project_root/vendor/bin/phpunit" ]]; then + print_error "PHPUnit executable not found or not executable: $project_root/vendor/bin/phpunit" + echo "Run composer install before running this script." + exit 1 + fi + + if [[ ${#missing_tools[@]} -gt 0 ]]; then + print_error "Missing required command(s): ${missing_tools[*]}" + echo "Install the missing tools and re-run this script." + exit 1 + fi +} + +verify_config() { + print_debug "Verifying configuration file: $config_file" + + if [[ ! -f "$config_file" ]]; then + print_error "Configuration file not found: $config_file" + echo "Please create $config_file with a list of components, one per line." + exit 1 + fi +} + +validate_component_name() { + local component="$1" + + # Only allow alphanumeric characters, hyphens, and underscores + # Reject path traversal attempts and command injection characters + if [[ ! "$component" =~ ^[a-zA-Z0-9_-]+$ ]]; then + return 1 + fi + + return 0 +} + +read_components() { + declare -a components=() + + while IFS= read -r line; do + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + + if [[ -z "$line" || "$line" =~ ^# ]]; then + continue + fi + + # Validate component name for security + if ! validate_component_name "$line"; then + print_warning "Skipping invalid/unsafe component name: $line" + continue + fi + + components+=("$line") + done < "$config_file" + + echo "${components[@]}" +} + +################################################################################ +# TEST EXECUTION +################################################################################ + +run_component_tests() { + local component=$1 + + # Security: Validate component name before use + if ! validate_component_name "$component"; then + print_error "Security: Invalid component name rejected: $component" + return 1 + fi + + local test_dir="tests/system/$component" + local start_time=$(date +%s%N) + + print_debug "Running tests for: $component" + + if [[ ! -d "$test_dir" ]]; then + local elapsed=$((($(date +%s%N) - $start_time) / 1000000)) + { + echo "Exit code: 2" + echo "Elapsed time: $elapsed" + } > "$results_dir/random_test_result_${elapsed}_${component}.txt" + return + fi + + local output_file="$results_dir/random_test_output_${component}_$$.log" + local events_file="$results_dir/random_test_events_${component}_$$.log" + local exit_code=0 + local attempt=1 + local -r max_attempts=2 + local random_seed + local -a phpunit_args + + # Retry loop: the Composer classmap autoloader occasionally fails to load + # CodeIgniter\CodeIgniter under parallel CI load — a transient infra race, + # not a real test failure. Retry once on that signature with a fresh random + # seed; a second miss is reported as genuine failure. + while true; do + random_seed=$(generate_phpunit_random_seed) + + # Security: Use array to avoid eval and prevent command injection + phpunit_args=( + "vendor/bin/phpunit" + "$test_dir" + "--colors=never" + "--no-coverage" + "--do-not-cache-result" + "--order-by=random" + "--random-order-seed=${random_seed}" + "--log-events-text" + "$events_file" + ) + + if [[ $timeout_seconds -gt 0 ]] && command -v timeout >/dev/null 2>&1; then + (cd "$project_root" && timeout --kill-after=2s "${timeout_seconds}s" "${phpunit_args[@]}") > "$output_file" 2>&1 + exit_code=$? + elif [[ $timeout_seconds -gt 0 ]] && command -v gtimeout >/dev/null 2>&1; then + (cd "$project_root" && gtimeout --kill-after=2s "${timeout_seconds}s" "${phpunit_args[@]}") > "$output_file" 2>&1 + exit_code=$? + else + local timeout_marker="$output_file.timeout" + (cd "$project_root" && "${phpunit_args[@]}") > "$output_file" 2>&1 & + local test_pid=$! + + if [[ $timeout_seconds -gt 0 ]]; then + # Watchdog: monitors test process and kills it after timeout + # Uses 1-second sleep intervals to respond quickly when test finishes early + ( + local elapsed=0 + while [[ $elapsed -lt $timeout_seconds ]]; do + sleep 1 + elapsed=$((elapsed + 1)) + kill -0 "$test_pid" 2>/dev/null || exit 0 + done + + if kill -0 "$test_pid" 2>/dev/null; then + touch "$timeout_marker" + local pids_to_kill=$(pgrep -P "$test_pid" 2>/dev/null) + + kill -TERM "$test_pid" 2>/dev/null || true + if [[ -n "$pids_to_kill" ]]; then + echo "$pids_to_kill" | xargs kill -TERM 2>/dev/null || true + fi + + sleep 2 + + if kill -0 "$test_pid" 2>/dev/null; then + kill -KILL "$test_pid" 2>/dev/null || true + if [[ -n "$pids_to_kill" ]]; then + echo "$pids_to_kill" | xargs kill -KILL 2>/dev/null || true + fi + # Security: Quote and escape test_dir for safe pattern matching + pkill -KILL -f "phpunit.*${test_dir//\//\\/}" 2>/dev/null || true + fi + fi + ) & + disown $! 2>/dev/null || true + fi + + wait "$test_pid" 2>/dev/null + exit_code=$? + + if [[ -f "$timeout_marker" ]]; then + exit_code=124 + rm -f "$timeout_marker" + elif [[ $exit_code -eq 143 || $exit_code -eq 137 ]]; then + exit_code=124 + fi + fi + + # Success, exhausted attempts, or a non-infra failure: stop retrying. + if [[ $exit_code -eq 0 ]] || [[ $attempt -ge $max_attempts ]]; then + break + fi + + # Only retry on the known transient autoload race signatures. + # Matching on error messages (not line numbers) so the pattern survives + # unrelated edits to MockCodeIgniter/CIUnitTestCase. + if ! grep -qE 'Failed to open stream: No such file or directory|Class "CodeIgniter.CodeIgniter" not found' "$output_file" 2>/dev/null; then + break + fi + + print_debug "Transient autoload failure detected in $component; retrying (attempt $((attempt + 1))/${max_attempts})" + ((attempt++)) + rm -f "$events_file" + done + + local elapsed=$((($(date +%s%N) - $start_time) / 1000000)) + local result_file="$results_dir/random_test_result_${elapsed}_${component}.txt" + local order_file="$results_dir/random_test_order_${elapsed}_${component}.txt" + + if [[ -f "$events_file" ]]; then + extract_test_order "$events_file" > "$order_file" + else + echo "Execution order unavailable (events file not created)." > "$order_file" + fi + + if [[ $exit_code -eq 0 ]]; then + { + echo "Exit code: 0" + echo "Elapsed time: $elapsed" + } > "$result_file" + rm -f "$output_file" "$events_file" "$order_file" + else + local output="" + if [[ -f "$output_file" ]]; then + output=$(cat "$output_file") + fi + + if [[ $exit_code -eq 124 ]]; then + output+=$'\n\nTest timed out after '"${timeout_seconds}s" + fi + + local predecessor_info=$'\nExecution order file: '"${order_file}" + if [[ $exit_code -eq 124 ]]; then + predecessor_info+=$'\nFailed test: (timeout before PHPUnit emitted failure event)' + if [[ -f "$events_file" ]]; then + local last_prepared_test=$(extract_test_order "$events_file" | tail -n 1) + if [[ -n "$last_prepared_test" ]]; then + predecessor_info+=$'\nLast prepared test before timeout: '"${last_prepared_test}" + else + predecessor_info+=$'\nLast prepared test before timeout: (unavailable)' + fi + else + predecessor_info+=$'\nLast prepared test before timeout: (events file unavailable)' + fi + predecessor_info+=$'\nPrevious test: (unavailable due to timeout)' + else + if [[ -f "$events_file" ]]; then + local predecessor_result=$(get_failed_test_predecessor "$events_file") + if [[ -n "$predecessor_result" ]]; then + local previous_test=${predecessor_result#*|} + predecessor_info+=$'\nFailed test: '"${predecessor_result%%|*}" + if [[ -n "$previous_test" ]]; then + predecessor_info+=$'\nPrevious test: '"${previous_test}" + else + predecessor_info+=$'\nPrevious test: (none - failed test ran first)' + fi + else + predecessor_info+=$'\nFailed test: (not detected from PHPUnit events log)' + predecessor_info+=$'\nPrevious test: (unavailable)' + fi + else + predecessor_info+=$'\nFailed test: (events file unavailable)' + predecessor_info+=$'\nPrevious test: (unavailable)' + fi + fi + + { + echo "> ${phpunit_args[@]:0:7}" + echo "" + echo "$output" + echo "$predecessor_info" + echo "" + echo "Exit code: 1" + echo "Elapsed time: $elapsed" + } > "$result_file" + rm -f "$output_file" "$events_file" + fi +} + +cleanup_finished_pids() { + local -a active=() + for pid in "${bg_pids[@]:-}"; do + if kill -0 "$pid" 2>/dev/null; then + active+=("$pid") + fi + done + bg_pids=("${active[@]:-}") +} + +spawn_limited_job() { + local component=$1 + + cleanup_finished_pids + + while [[ ${#bg_pids[@]} -ge $max_jobs ]]; do + sleep 0.05 + cleanup_finished_pids + done + + run_component_tests "$component" & + bg_pids+=($!) +} + +process_result() { + local component=$1 + local elapsed=$2 + local result_file="$results_dir/random_test_result_${elapsed}_${component}.txt" + + if [[ ! -f "$result_file" ]]; then + return 1 + fi + + ((completed++)) + + if [[ "$first_result" == true ]] && ! is_quiet; then + echo "" + first_result=false + fi + + local status=$(grep "^Exit code:" "$result_file" | sed 's/Exit code: //') + local elapsed_str=$(format_elapsed_time "$elapsed") + + case "$status" in + 0) + ((passed++)) + print_result "success" "$completed" "$total" "$component" "$elapsed_str" + rm -f "$result_file" + ;; + 2) + ((skipped++)) + skipped_components+=("$component") + print_result "warning" "$completed" "$total" "$component" "$elapsed_str" + rm -f "$result_file" + ;; + *) + ((failed++)) + failed_components+=("$component") + print_result "failure" "$completed" "$total" "$component" "$elapsed_str" + ;; + esac + + displayed_components+=("$component") + + return 0 +} + +get_completed_components() { + # Returns unprocessed test results sorted by elapsed time (fastest first) + # Filename format: random_test_result_${elapsed}_${component}.txt + # Output format: "component|elapsed" (one per line) + + # Extract and sort files by elapsed time + local -a entries=() + + while IFS= read -r file_path; do + # Remove prefix: random_test_result_ + local temp=$(basename "$file_path") + temp=${temp#random_test_result_} + + # Extract elapsed time (everything before first underscore after number) + local elapsed=${temp%%_*} + + # Validate elapsed is numeric + if [[ ! "$elapsed" =~ ^[0-9]+$ ]]; then + continue + fi + + # Extract component (everything after elapsed and underscore, before .txt) + local listed_component=${temp#${elapsed}_} + listed_component=${listed_component%.txt} + + entries+=("$elapsed|$listed_component") + done < <(find "$results_dir" -maxdepth 1 -type f -name "random_test_result_*.txt" 2>/dev/null) + + # Sort entries by elapsed time numerically + printf '%s\n' "${entries[@]}" | sort -t'|' -k1,1n | + while IFS='|' read -r elapsed listed_component; do + if [[ ! " ${displayed_components[*]:-} " =~ " ${listed_component} " ]]; then + echo "$listed_component|$elapsed" + fi + done +} + + +print_summary() { + local run_number=$1 + local pass_percent=0.00 + local fail_percent=0.00 + local skip_percent=0.00 + + if [[ $total -gt 0 ]]; then + pass_percent=$(printf "%.2f" "$(echo "scale=2; $passed * 100 / $total" | bc)") + fail_percent=$(printf "%.2f" "$(echo "scale=2; $failed * 100 / $total" | bc)") + skip_percent=$(printf "%.2f" "$(echo "scale=2; $skipped * 100 / $total" | bc)") + fi + + echo "" + print_header "Test Execution Summary" + printf "%-20s %b\n" "Total $(inflect "$total" "Component" "Components"):" "${BLUE}$total${RESET}" + printf "%-20s %b\n" "Passed:" "${GREEN}$passed${RESET} (${GREEN}${pass_percent}%${RESET})" + printf "%-20s %b\n" "Failed:" "${RED}$failed${RESET} (${RED}${fail_percent}%${RESET})" + printf "%-20s %b\n" "Skipped:" "${YELLOW}$skipped${RESET} (${YELLOW}${skip_percent}%${RESET})" + printf "%-20s %b\n" "Completed Runs:" "${BLUE}$run_number${RESET} / ${BLUE}$repeat_count${RESET}" + + if [[ $failed -gt 0 ]]; then + echo -e "\n${BOLD_RED}Failed $(inflect "$failed" "Component" "Components"):${RESET}" + while IFS= read -r failed_component; do + local result_file=$(find "$results_dir" -name "random_test_result_*_${failed_component}.txt" 2>/dev/null | head -n 1) + + if [[ -z "$result_file" ]]; then + result_file="$results_dir/random_test_result_*_${failed_component}.txt" + fi + + echo -e " ${RED}✗${RESET} ${BOLD}$failed_component${RESET} ($result_file)" + done < <(printf '%s\n' "${failed_components[@]}" | sort) + fi + + if [[ $skipped -gt 0 ]]; then + echo -e "\n${BOLD_YELLOW}Skipped $(inflect "$skipped" "Component" "Components"):${RESET}" + while IFS= read -r skipped_component; do + echo -e " ${YELLOW}⚠${RESET} ${BOLD}$skipped_component${RESET} (no such directory: ${BOLD}tests/system/$skipped_component${RESET})" + done < <(printf '%s\n' "${skipped_components[@]}" | sort) + fi +} + +################################################################################ +# MAIN SCRIPT +################################################################################ + +main() { + cd "$project_root" || exit 1 + + parse_arguments "$@" + check_required_tools + + if [[ -z "$max_jobs" ]]; then + max_jobs=$(auto_detect_max_jobs) + fi + + print_header "CodeIgniter4 - Random Test Execution Verification" + echo "" + + declare -a components_array + + if [[ -n "$component" ]]; then + # Single component specified via command line + if ! validate_component_name "$component"; then + print_error "Invalid component name: $component" + echo " Component name must contain only: alphanumeric, hyphens, underscores, forward slashes" + echo " Cannot contain: spaces, dots, consecutive slashes, or start with slash" + exit 1 + fi + print_debug "Testing single component specified via command line: $component\n" + components_array=("$component") + else + # Read components from config file + verify_config + print_success "Configuration file: $config_file\n" + components_array=($(read_components)) + fi + + total=${#components_array[@]} + + if [[ $total -eq 0 ]]; then + if [[ -n "$component" ]]; then + print_error "Component not found or inaccessible: $component" + else + print_warning "No components configured in $config_file" + echo "Please add component names to the configuration file, one per line." + fi + exit 0 + fi + + print_debug "Found $total $(inflect "$total" "component") to test" + print_debug "Max concurrent jobs: $max_jobs" + print_debug "Per-component timeout: ${timeout_seconds}s" + print_debug "Total runs: $repeat_count\n" + + local run=1 + while [[ $run -le $repeat_count ]]; do + completed=0 + passed=0 + failed=0 + skipped=0 + displayed_components=() + failed_components=() + skipped_components=() + bg_pids=() + first_result=true + + if [[ $run -gt 1 ]]; then + echo "" + fi + + if [[ $repeat_count -gt 1 ]]; then + print_header "Run $run/$repeat_count" + echo "" + fi + + print_debug "Setting up results directory: $results_dir\n" + mkdir -p "$results_dir" + rm -f "$results_dir"/* + + if ! should_show_spinner; then + echo -e "${BLUE}Running tests in parallel...${RESET}\n" + else + show_spinner + fi + + for next_component in "${components_array[@]}"; do + spawn_limited_job "$next_component" + done + + for finished_pid in "${bg_pids[@]:-}"; do + wait "$finished_pid" 2>/dev/null || true + done + + if should_show_spinner; then + stop_spinner + fi + + while IFS='|' read -r next_component next_elapsed; do + process_result "$next_component" "$next_elapsed" || true + done < <(get_completed_components) + + print_summary "$run" + + if [[ $failed -gt 0 || $skipped -gt 0 ]]; then + exit 1 + fi + + ((run++)) + done + + echo "" + if [[ -n "$component" ]]; then + print_success "Component '$component' passed random execution tests!" + else + print_success "All components passed random execution tests!" + fi +} + +main "$@" diff --git a/.github/scripts/secure-git-push b/.github/scripts/secure-git-push new file mode 100755 index 000000000000..217581f0a806 --- /dev/null +++ b/.github/scripts/secure-git-push @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 2 ]]; then + echo "Usage: secure-git-push " >&2 + exit 1 +fi + +if [[ -z "${PUSH_TOKEN:-}" ]]; then + echo "PUSH_TOKEN is required" >&2 + exit 1 +fi + +REMOTE_URL="$1" +REFSPEC="$2" +AUTH_HEADER="$(printf 'x-access-token:%s' "${PUSH_TOKEN}" | base64 | tr -d '\n')" + +echo "::add-mask::${AUTH_HEADER}" +git -c http.https://github.com/.extraheader="AUTHORIZATION: basic ${AUTH_HEADER}" push "${REMOTE_URL}" "${REFSPEC}" + +unset AUTH_HEADER PUSH_TOKEN diff --git a/.github/scripts/validate-version b/.github/scripts/validate-version new file mode 100644 index 000000000000..411900dedb75 --- /dev/null +++ b/.github/scripts/validate-version @@ -0,0 +1,21 @@ +#!/bin/bash +set -o pipefail + +if [[ -z $1 ]]; then + echo "validate-version requires a version identifier" + exit 1 +fi + +FILES=("system/CodeIgniter.php" "user_guide_src/source/conf.py") +LENGTH="${#FILES[@]}" + +for FILE in "${FILES[@]}"; do + COUNT="$((COUNT + $(grep -c "$FILE" -e "'$1'")))" +done + +if [[ $COUNT -ne $LENGTH ]]; then + echo "CodeIgniter version is not updated to v"$1"" + exit 1 +fi + +echo "CodeIgniter version is updated to v"$1"" diff --git a/.github/skills/README.md b/.github/skills/README.md new file mode 100644 index 000000000000..1c641b9e335a --- /dev/null +++ b/.github/skills/README.md @@ -0,0 +1,25 @@ +# Skills + +This directory contains workspace agent skills for maintainers. + +## Available Skills +- `forum-announcement`: Create CodeIgniter4 release forum announcements in myBB format. + +## Structure +- `.github/skills//SKILL.md`: Skill metadata and workflow. +- `.github/skills//references/`: Optional supporting docs loaded on demand. +- `.github/skills//assets/`: Optional templates and reusable files. +- `.github/skills//scripts/`: Optional executables for automation. + +## Usage +1. Open Copilot Chat in this workspace. +2. Invoke `/`. +3. Provide inputs, for example: +``` +/forum-announcement 4.7.0 +``` + +## Maintainer Notes +- Keep `name` in `SKILL.md` identical to its folder name. +- Keep the `description` keyword-rich so the skill is discoverable. +- Use references only when needed; avoid duplicating guidance between `SKILL.md` and `references/`. diff --git a/.github/skills/forum-announcement/SKILL.md b/.github/skills/forum-announcement/SKILL.md new file mode 100644 index 000000000000..e9e7c731a1a3 --- /dev/null +++ b/.github/skills/forum-announcement/SKILL.md @@ -0,0 +1,112 @@ +--- +name: forum-announcement +description: 'Create CodeIgniter4 release forum announcements in myBB format. Use when preparing release posts, dual-version announcements (maintenance + major), changelog summaries, contributor acknowledgements, upgrade guidance, and final forum-ready text files.' +argument-hint: 'Target release version(s), e.g. "4.6.5 and 4.7.0" or "4.7.1"' +user-invocable: true +--- + +# Forum Announcement + +Create a forum-ready CodeIgniter4 release announcement using myBB markup. + +## When to Use +- Publish a new CodeIgniter4 release announcement on the forum. +- Convert changelog and RST details into a structured, readable post. +- Produce a dual-release announcement with maintenance-first ordering. + +## Inputs +- One version (`4.x.y`) or two versions (`maintenance`, then `major`). + +## Output Naming +- `v{VERSION}` is a placeholder and must be replaced by an actual version string. +- Single release example: `v4.7.1-announcement.txt`. +- Dual release example (`4.6.5` + `4.7.0`): use the major version filename `v4.7.0-announcement.txt`. +- Save the file at repository root. + +## Procedure +1. Read `CHANGELOG.md` for release summary and PR references. +2. Read `user_guide_src/source/changelogs/v{VERSION}.rst` for each target version. +3. Determine release mode: + - Single release: one `4.x.y` version. + - Dual release: maintenance + major; maintenance is presented first in the post. +4. Draft the post with this structure (remove sections that do not apply): + - Title. + - Intro paragraphs. + - Release links. + - Which version to use (dual release only). + - Maintenance release section (dual release only). + - Highlights and new features. + - Notable enhancements. + - Security and quality. + - Breaking changes. + - Other notable changes. + - Contributor thanks. + - Upgrade links, issue reporting link, closing. + - AI-assistance disclosure. +5. Apply myBB formatting and escaping rules in this file. +6. Save final content as `v{VERSION}-announcement.txt` at repository root, replacing `{VERSION}` with the actual version. + +## Required Announcement Template +```text +[size=x-large][b]CodeIgniter {VERSION} Released![/b][/size] + +Introduction paragraph(s) + +[url=RELEASE_URL]GitHub Release[/url] +[url=CHANGELOG_URL]Changelog[/url] + +[hr] + +[size=large][b]Highlights & New Features[/b][/size] + +[hr] + +[size=large][b]Notable Enhancements[/b][/size] + +[hr] + +[size=large][b]Breaking Changes[/b][/size] + +[hr] + +[size=large][b]Thanks to Our Contributors[/b][/size] + +[hr] + +[url=UPGRADE_GUIDE_URL]Upgrade Guide[/url] +[url=ISSUES_URL]Report Issues[/url] + +[hr] + +[size=small][i]Note: This announcement was created with the assistance of GitHub Copilot (GPT-5.3-Codex).[/i][/size] +``` + +For dual releases, adapt the title and body to include both versions, with maintenance version first, plus a dedicated "Which Version Should I Use?" section. + +## myBB Rules +- Use `[b]`, `[i]`, `[size=x-large]`, `[size=large]`, `[size=small]`, `[url=...]`, `[list]`, `[list=1]`, `[*]`, and `[hr]`. +- Use double backticks for inline code-like text when needed. +- Escape colon patterns that may trigger myBB emoticons by replacing `:` with `:` in code-like snippets. + +## Content Quality Rules +- Tone is professional, friendly, and community-facing. +- Do not use emojis. +- Verify details against RST changelog entries before finalizing. +- Explain breaking changes with old behavior versus new behavior. +- Include a "Removed Deprecated Items" subsection when applicable. +- For maintenance bug-fix summaries, prefer ordered lists with concise before/after statements. +- Thank contributors by GitHub username. +- Include upgrade links and issue reporting links. + +## Version Guidance Rules +- Dual releases must list maintenance before major. +- The "Which Version Should I Use?" section is required for dual releases. +- In version guidance, present the lower-PHP-support option first. +- For single releases, omit the "Which Version Should I Use?" section. + +## Self-Check +- Maintenance version is presented before major version. +- PHP-version guidance and upgrade links are included. +- Breaking changes are clearly explained. +- Final AI-assistance disclosure is present and should be adjusted to the actual AI model used if different from the template. +- Output filename is concrete and does not include braces. diff --git a/.github/workflows/deploy-apidocs.yml b/.github/workflows/deploy-apidocs.yml index 4a7d20dd502b..8eb59179a946 100644 --- a/.github/workflows/deploy-apidocs.yml +++ b/.github/workflows/deploy-apidocs.yml @@ -8,14 +8,20 @@ on: branches: - 'develop' paths: - - 'system/**' + - '.github/scripts/secure-git-push' - '.github/workflows/deploy-apidocs.yml' + - 'system/**' + +permissions: + contents: read jobs: build: name: Deploy to api + permissions: + contents: write if: github.repository == 'codeigniter4/CodeIgniter4' - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Setup credentials @@ -24,27 +30,29 @@ jobs: git config --global user.name "${GITHUB_ACTOR}" - name: Checkout source - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: path: source + persist-credentials: false - name: Checkout target - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: codeigniter4/api token: ${{ secrets.ACCESS_TOKEN }} path: api + persist-credentials: false - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: - php-version: '8.0' + php-version: '8.2' tools: phive coverage: none - name: Download latest phpDocumentor working-directory: source - run: sudo phive --no-progress install --global --trust-gpg-keys 67F861C3D889C656 phpDocumentor + run: phive --no-progress install --trust-gpg-keys 6DA3ACC4991FFAE5 phpDocumentor - name: Prepare API repo working-directory: api @@ -56,14 +64,20 @@ jobs: - name: Build API in source repo working-directory: source run: | - phpDocumentor run --ansi --verbose + php tools/phpDocumentor --ansi --verbose cp -R ${GITHUB_WORKSPACE}/source/api/build/* ${GITHUB_WORKSPACE}/api/docs - name: Deploy to API repo working-directory: api + env: + PUSH_TOKEN: ${{ secrets.ACCESS_TOKEN }} run: | git add . + if ! git diff-index --quiet HEAD; then git commit -m "Updated API for commit ${GITHUB_SHA}" - git push origin master + bash "${GITHUB_WORKSPACE}/source/.github/scripts/secure-git-push" https://github.com/codeigniter4/api.git HEAD:master + echo "API documentation deployed successfully." + else + echo "No changes to deploy." fi diff --git a/.github/workflows/deploy-distributables.yml b/.github/workflows/deploy-distributables.yml new file mode 100644 index 000000000000..1eeb86f772cb --- /dev/null +++ b/.github/workflows/deploy-distributables.yml @@ -0,0 +1,207 @@ +# When a new release is created, deploy relevant +# files to each of the generated repos. +name: Deploy Distributable Repos + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + check-version: + name: Check for updated version + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 # fetch all tags + persist-credentials: false + + - name: Get latest version + id: version + run: | + echo 'LATEST_VERSION<> $GITHUB_OUTPUT + echo $(git describe --tags --abbrev=0) | sed "s/v//" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + - name: Search for updated version + if: ${{ steps.version.outputs.LATEST_VERSION }} + run: | + chmod +x ${GITHUB_WORKSPACE}/.github/scripts/validate-version + ${GITHUB_WORKSPACE}/.github/scripts/validate-version ${{ steps.version.outputs.LATEST_VERSION }} + + framework: + name: Deploy to framework + permissions: + # Allow actions/github-script to create release + contents: write + if: github.repository == 'codeigniter4/CodeIgniter4' + runs-on: ubuntu-24.04 + needs: check-version + + steps: + - name: Identify + run: | + git config --global user.email "action@github.com" + git config --global user.name "${GITHUB_ACTOR}" + + - name: Checkout source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: source + persist-credentials: false + + - name: Checkout target + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: codeigniter4/framework + token: ${{ secrets.ACCESS_TOKEN }} + path: framework + persist-credentials: false + + - name: Chmod + run: chmod +x ./source/.github/scripts/deploy-framework + + - name: Deploy + env: + PUSH_TOKEN: ${{ secrets.ACCESS_TOKEN }} + run: ./source/.github/scripts/deploy-framework ${GITHUB_WORKSPACE}/source ${GITHUB_WORKSPACE}/framework ${GITHUB_REF##*/} + + - name: Release + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{secrets.ACCESS_TOKEN}} + script: | + const release = await github.rest.repos.getLatestRelease({ + owner: context.repo.owner, + repo: context.repo.repo + }) + github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: 'framework', + tag_name: release.data.tag_name, + name: release.data.name, + body: release.data.body + }) + + appstarter: + name: Deploy to appstarter + permissions: + # Allow actions/github-script to create release + contents: write + if: github.repository == 'codeigniter4/CodeIgniter4' + runs-on: ubuntu-24.04 + needs: check-version + + steps: + - name: Identify + run: | + git config --global user.email "action@github.com" + git config --global user.name "${GITHUB_ACTOR}" + + - name: Checkout source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: source + persist-credentials: false + + - name: Checkout target + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: codeigniter4/appstarter + token: ${{ secrets.ACCESS_TOKEN }} + path: appstarter + persist-credentials: false + + - name: Chmod + run: chmod +x ./source/.github/scripts/deploy-appstarter + + - name: Deploy + env: + PUSH_TOKEN: ${{ secrets.ACCESS_TOKEN }} + run: ./source/.github/scripts/deploy-appstarter ${GITHUB_WORKSPACE}/source ${GITHUB_WORKSPACE}/appstarter ${GITHUB_REF##*/} + + - name: Release + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{secrets.ACCESS_TOKEN}} + script: | + const release = await github.rest.repos.getLatestRelease({ + owner: context.repo.owner, + repo: context.repo.repo + }) + github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: 'appstarter', + tag_name: release.data.tag_name, + name: release.data.name, + body: release.data.body + }) + + userguide: + name: Deploy to userguide + permissions: + # Allow actions/github-script to create release + contents: write + if: github.repository == 'codeigniter4/CodeIgniter4' + runs-on: ubuntu-24.04 + needs: check-version + + steps: + - name: Identify + run: | + git config --global user.email "action@github.com" + git config --global user.name "${GITHUB_ACTOR}" + + - name: Checkout source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: source + persist-credentials: false + + - name: Checkout target + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: codeigniter4/userguide + token: ${{ secrets.ACCESS_TOKEN }} + path: userguide + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + + - name: Install Sphinx + run: | + python -m pip install --upgrade pip + pip install -r ./source/user_guide_src/requirements.txt + + - name: Chmod + run: chmod +x ./source/.github/scripts/deploy-userguide + + - name: Deploy + env: + PUSH_TOKEN: ${{ secrets.ACCESS_TOKEN }} + run: ./source/.github/scripts/deploy-userguide ${GITHUB_WORKSPACE}/source ${GITHUB_WORKSPACE}/userguide ${GITHUB_REF##*/} + + - name: Release + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{secrets.ACCESS_TOKEN}} + script: | + const release = await github.rest.repos.getLatestRelease({ + owner: context.repo.owner, + repo: context.repo.repo + }) + github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: 'userguide', + tag_name: release.data.tag_name, + name: release.data.name, + body: release.data.body + }) diff --git a/.github/workflows/deploy-framework.yml b/.github/workflows/deploy-framework.yml deleted file mode 100644 index 5a65f3bd1cfc..000000000000 --- a/.github/workflows/deploy-framework.yml +++ /dev/null @@ -1,98 +0,0 @@ -# When a new Release is created, deploy relevant -# files to each of the generated repos. -name: Deploy Framework - -on: - release: - types: [published] - -jobs: - framework: - name: Deploy to framework - if: github.repository == 'codeigniter4/CodeIgniter4' - runs-on: ubuntu-latest - steps: - - name: Identify - run: | - git config --global user.email "action@github.com" - git config --global user.name "${GITHUB_ACTOR}" - - - name: Checkout source - uses: actions/checkout@v2 - with: - path: source - - - name: Checkout target - uses: actions/checkout@v2 - with: - repository: codeigniter4/framework - token: ${{ secrets.ACCESS_TOKEN }} - path: framework - - - name: Chmod - run: chmod +x ./source/.github/scripts/deploy-framework - - - name: Deploy - run: ./source/.github/scripts/deploy-framework ${GITHUB_WORKSPACE}/source ${GITHUB_WORKSPACE}/framework ${GITHUB_REF##*/} - - - name: Release - uses: actions/github-script@v5 - with: - github-token: ${{secrets.ACCESS_TOKEN}} - script: | - const release = await github.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }) - github.repos.createRelease({ - owner: context.repo.owner, - repo: 'framework', - tag_name: release.data.tag_name, - name: release.data.name, - body: release.data.body - }) - - appstarter: - name: Deploy to appstarter - if: github.repository == 'codeigniter4/CodeIgniter4' - runs-on: ubuntu-latest - steps: - - name: Identify - run: | - git config --global user.email "action@github.com" - git config --global user.name "${GITHUB_ACTOR}" - - - name: Checkout source - uses: actions/checkout@v2 - with: - path: source - - - name: Checkout target - uses: actions/checkout@v2 - with: - repository: codeigniter4/appstarter - token: ${{ secrets.ACCESS_TOKEN }} - path: appstarter - - - name: Chmod - run: chmod +x ./source/.github/scripts/deploy-appstarter - - - name: Deploy - run: ./source/.github/scripts/deploy-appstarter ${GITHUB_WORKSPACE}/source ${GITHUB_WORKSPACE}/appstarter ${GITHUB_REF##*/} - - - name: Release - uses: actions/github-script@v5 - with: - github-token: ${{secrets.ACCESS_TOKEN}} - script: | - const release = await github.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }) - github.repos.createRelease({ - owner: context.repo.owner, - repo: 'appstarter', - tag_name: release.data.tag_name, - name: release.data.name, - body: release.data.body - }) diff --git a/.github/workflows/deploy-userguide-latest.yml b/.github/workflows/deploy-userguide-latest.yml index 22e6eed4db4f..c943254325d6 100644 --- a/.github/workflows/deploy-userguide-latest.yml +++ b/.github/workflows/deploy-userguide-latest.yml @@ -12,24 +12,54 @@ on: paths: - 'user_guide_src/**' +permissions: + contents: read + jobs: build: name: Deploy to gh-pages + permissions: + # Allow push to branch gh-pages + contents: write if: (github.repository == 'codeigniter4/CodeIgniter4') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - # Build the latest User Guide - - name: Build with Sphinx - uses: ammaraskar/sphinx-action@0.4 + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: - docs-folder: user_guide_src/ + php-version: '8.2' + coverage: none + + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + + - name: Install Sphinx + run: | + python -m pip install --upgrade pip + pip install -r user_guide_src/requirements.txt + + # Build the latest User Guide + - name: Build Docs with Sphinx + run: make html + working-directory: user_guide_src + + - name: Add "Edit this page" links + run: | + cd user_guide_src + # Fix permissions + sudo chown -R runner:docker build/html/ + php add-edit-this-page build/html/ # Create an artifact of the html output - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: HTML Documentation path: user_guide_src/build/html/ @@ -47,8 +77,7 @@ jobs: git commit -m "Update User Guide" -a || true - name: Push changes - uses: ad-m/github-push-action@v0.6.0 - with: - branch: gh-pages - directory: gh-pages - github_token: ${{ secrets.ACCESS_TOKEN }} + working-directory: gh-pages + env: + PUSH_TOKEN: ${{ secrets.ACCESS_TOKEN }} + run: bash ${GITHUB_WORKSPACE}/.github/scripts/secure-git-push https://github.com/codeigniter4/CodeIgniter4.git HEAD:gh-pages diff --git a/.github/workflows/label-add-conflict-all-pr.yml b/.github/workflows/label-add-conflict-all-pr.yml new file mode 100644 index 000000000000..f9adde8207de --- /dev/null +++ b/.github/workflows/label-add-conflict-all-pr.yml @@ -0,0 +1,51 @@ +name: Auto Label "stale" for All PRs + +on: + push: + branches: + - develop + - '4.*' + +jobs: + build: + name: Check Conflicts + + permissions: + contents: read + pull-requests: write + + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Get PR List + id: PR-list + run: echo "pr_list=$(gh pr list -L 100 --json mergeable,url,labels,author)" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: 'Add label "stale" and comment' + env: + PR_LIST: ${{ steps.PR-list.outputs.pr_list }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + + IFS=$'\n' # Set Internal Field Separator to newline to handle array elements + + # Iterate through the PRs in PR_LIST + for pr in $(echo "$PR_LIST" | jq -c '.[]'); do + mergeable=$(echo "$pr" | jq -r '.mergeable') + author=$(echo "$pr" | jq -r '.author.login') + labels=$(echo "$pr" | jq -c '.labels[].name' | tr -d '[]"') + url=$(echo "$pr" | jq -r '.url') + + # CONFLICTING and no 'stale' label + if [ "$mergeable" == "CONFLICTING" ] && [[ ! "$labels" == *"stale"* ]]; then + # Add "stale" label + gh pr edit $url --add-label "stale" + + # Add a comment + gh pr comment $url --body ":wave: Hi, @$author!

We detected conflicts in your PR against the base branch :speak_no_evil:
You may want to sync :arrows_counterclockwise: your branch with upstream!

Ref: [Syncing Your Branch](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/workflow.md#updating-your-branch)" + fi + done diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml new file mode 100644 index 000000000000..bb84e2ad3504 --- /dev/null +++ b/.github/workflows/label-pr.yml @@ -0,0 +1,47 @@ +name: Add Labels to PRs + +# NOTE: When updating this workflow, you should first change the event to `pull_request` to test the changes +# in a PR, and then change it back to `pull_request_target` before merging. +# @see https://github.com/actions/labeler?tab=readme-ov-file#updating-major-version-of-the-labeler +on: + - pull_request_target + +jobs: + add-labels: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Verify PR source for workflow file changes + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const prFiles = await github.paginate(github.rest.pulls.listFiles.endpoint.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + })); + const workflowFileChanged = prFiles.some(file => file.filename === '.github/workflows/label-pr.yml'); + + if (workflowFileChanged) { + if (context.payload.pull_request.head.repo.full_name !== 'codeigniter4/CodeIgniter4') { + throw new Error('Changes to label-pr.yml are not allowed from forks.'); + } + + console.log('Workflow file changed, but PR is from the main repository. Proceeding with label addition.'); + return; + } + + console.log('No changes to workflow file detected, proceeding with label addition.'); + + - name: Add labels + uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0 + with: + sync-labels: true # Remove labels when matching files are reverted diff --git a/.github/workflows/label-signing.yml b/.github/workflows/label-signing.yml new file mode 100644 index 000000000000..609e84a0a23e --- /dev/null +++ b/.github/workflows/label-signing.yml @@ -0,0 +1,32 @@ +name: Check Signed PR +on: + pull_request: + branches: + - 'develop' + - '4.*' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + build: + name: Check Signed Commit + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Check signed commits in PR + uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1.2.0 + with: + comment: | + You must GPG-sign your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open-source project. See Developer's Certificate of Origin. See [signing][1]. + + **Note that all your commits must be signed.** If you have an unsigned commit, you can sign the previous commits by referring to [gpg-signing-old-commits][2]. + [1]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#signing + [2]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/workflow.md#gpg-signing-old-commits diff --git a/.github/workflows/reusable-coveralls.yml b/.github/workflows/reusable-coveralls.yml new file mode 100644 index 000000000000..cf9c02b22e11 --- /dev/null +++ b/.github/workflows/reusable-coveralls.yml @@ -0,0 +1,76 @@ +name: Reusable Coveralls Upload + +on: + workflow_call: + inputs: + php-version: + description: The PHP version the workflow should run + type: string + required: true + +permissions: + contents: read + +jobs: + coveralls: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: ${{ inputs.php-version }} + tools: composer + coverage: xdebug + + - name: Download coverage files + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: build/cov + + - name: Display structure of downloaded files + run: ls -R + working-directory: build/cov + + - name: Get composer cache directory + id: composer-cache + run: | + echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_FILES_DIR }} + key: ${{ github.job }}-php-${{ inputs.php-version }}-${{ hashFiles('**/composer.*') }} + restore-keys: | + ${{ github.job }}-php-${{ inputs.php-version }}- + ${{ github.job }}- + + - name: Cache PHPUnit's static analysis cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: build/.phpunit.cache/code-coverage + key: phpunit-code-coverage-${{ hashFiles('**/phpunit.*') }} + restore-keys: | + phpunit-code-coverage- + + - name: Install dependencies + run: composer update --ansi + + - name: Merge coverage files + run: | + jq '.autoload."psr-4" += {"Config\\": "app/Config/"}' composer.json > temp.json && mv temp.json composer.json + composer dump-autoload + vendor/bin/phpcov merge --clover build/logs/clover.xml build/cov + + - name: Upload coverage to Coveralls + run: | + composer global require php-coveralls/php-coveralls + php-coveralls --verbose --exclude-no-stmt --ansi + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reusable-phpunit-test.yml b/.github/workflows/reusable-phpunit-test.yml new file mode 100644 index 000000000000..b216a71493bb --- /dev/null +++ b/.github/workflows/reusable-phpunit-test.yml @@ -0,0 +1,241 @@ +name: Reusable PHPUnit Test + +on: + workflow_call: + inputs: + job-name: + description: Name of the job to appear in GitHub UI + type: string + required: true + php-version: + description: The PHP version the workflow should run + type: string + required: true + job-id: + description: Job ID to be used as part of cache key and artifact name + type: string + required: false + db-platform: + description: The database platform to be tested + type: string + required: false + mysql-version: + description: Version of the mysql Docker image + type: string + required: false + group-name: + description: The @group to test + type: string + required: false + enable-artifact-upload: + description: Whether artifact uploading of coverage results should be enabled + type: boolean + required: false + enable-coverage: + description: Whether coverage should be enabled + type: boolean + required: false + enable-profiling: + description: Whether slow tests should be profiled + type: boolean + required: false + extra-extensions: + description: Additional PHP extensions that are needed to be enabled + type: string + required: false + extra-ini-options: + description: Additional PHP configuration directives that should be appended to the php.ini + type: string + required: false + extra-composer-options: + description: Additional Composer options that should be appended to the `composer update` call + type: string + required: false + extra-phpunit-options: + description: Additional PHPUnit options that should be appended to the `vendor/bin/phpunit` call + type: string + required: false + +permissions: + contents: read + +env: + NLS_LANG: 'AMERICAN_AMERICA.UTF8' + NLS_DATE_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_TZ_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + +jobs: + tests: + name: ${{ inputs.job-name }} + runs-on: ubuntu-24.04 + + # Service containers cannot be extracted to caller workflows yet + services: + mysql: + image: ${{ inputs.db-platform == 'MySQLi' && format('mysql:{0}', inputs.mysql-version || '8.0') || '' }} + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + postgres: + image: ${{ inputs.db-platform == 'Postgre' && 'postgres' || '' }} + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + ports: + - 5432:5432 + options: >- + --health-cmd=pg_isready + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + mssql: + image: ${{ inputs.db-platform == 'SQLSRV' && 'mcr.microsoft.com/mssql/server:2025-CU3-ubuntu-24.04' || '' }} + env: + MSSQL_SA_PASSWORD: 1Secure*Password1 + ACCEPT_EULA: Y + MSSQL_PID: Developer + ports: + - 1433:1433 + options: >- + --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + oracle: + image: ${{ inputs.db-platform == 'OCI8' && 'gvenzl/oracle-free:latest' || '' }} + env: + ORACLE_RANDOM_PASSWORD: true + APP_USER: ORACLE + APP_USER_PASSWORD: ORACLE + ports: + - 1521:1521 + options: >- + --health-cmd healthcheck.sh + --health-interval 20s + --health-timeout 10s + --health-retries 10 + + redis: + image: redis + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + memcached: + image: memcached:1.6-alpine + ports: + - 11211:11211 + + steps: + - name: Install mssql-tools on runner + if: ${{ inputs.db-platform == 'SQLSRV' }} + run: | + source /etc/os-release + curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor --batch --yes -o /usr/share/keyrings/microsoft-prod.gpg + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${UBUNTU_CODENAME} main" | sudo tee /etc/apt/sources.list.d/mssql-release.list + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 mssql-tools18 unixodbc-dev + + # Make sqlcmd available to subsequent steps + echo "/opt/mssql-tools18/bin" >> $GITHUB_PATH + + - name: Create database for MSSQL Server + if: ${{ inputs.db-platform == 'SQLSRV' }} + run: | + sqlcmd -S 127.0.0.1 \ + -U sa -P 1Secure*Password1 \ + -N -C \ + -Q "CREATE DATABASE test COLLATE Latin1_General_100_CS_AS_SC_UTF8" + + - name: Install latest ImageMagick + if: ${{ contains(inputs.extra-extensions, 'imagick') }} + run: | + sudo apt-get update + sudo apt-get install -y imagemagick libmagickwand-dev ghostscript poppler-data libjbig2dec0:amd64 libopenjp2-7:amd64 + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: ${{ inputs.php-version }} + tools: composer + extensions: gd, ${{ inputs.extra-extensions }} + ini-values: ${{ inputs.extra-ini-options }} + coverage: ${{ env.COVERAGE_DRIVER }} + env: + COVERAGE_DRIVER: ${{ inputs.enable-coverage && 'xdebug' || 'none' }} + + - name: Setup global environment variables + id: setup-env + run: | + echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + echo "ARTIFACT_NAME=${{ inputs.job-id || github.job }}-php-${{ inputs.php-version }}-db-${{ inputs.db-platform || 'none' }}${{ inputs.mysql-version || '' }}" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.setup-env.outputs.COMPOSER_CACHE_FILES_DIR }} + key: ${{ inputs.job-id || github.job }}-php-${{ inputs.php-version }}-db-${{ inputs.db-platform || 'none' }}-${{ hashFiles('**/composer.*') }} + restore-keys: | + ${{ inputs.job-id || github.job }}-php-${{ inputs.php-version }}-db-${{ inputs.db-platform || 'none' }}- + ${{ inputs.job-id || github.job }}-php-${{ inputs.php-version }}- + ${{ inputs.job-id || github.job }}- + + - name: Cache PHPUnit's static analysis cache + if: ${{ inputs.enable-artifact-upload }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: build/.phpunit.cache/code-coverage + key: phpunit-code-coverage-${{ hashFiles('**/phpunit.*') }} + restore-keys: | + phpunit-code-coverage- + + - name: Install dependencies + run: | + composer config --global github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + composer update --ansi ${{ inputs.extra-composer-options }} + + - name: Compute additional PHPUnit options + id: phpunit-options + run: | + echo "EXTRA_PHPUNIT_OPTIONS=${{ format('{0} {1} {2}', env.GROUP_OPTION, env.COVERAGE_OPTION, inputs.extra-phpunit-options) }}" >> $GITHUB_OUTPUT + env: + COVERAGE_OPTION: ${{ inputs.enable-coverage && format('--coverage-php build/cov/coverage-{0}.cov', steps.setup-env.outputs.ARTIFACT_NAME) || '--no-coverage' }} + GROUP_OPTION: ${{ inputs.group-name && format('--group {0}', inputs.group-name) || '' }} + + - name: Run tests + run: script -e -c "vendor/bin/phpunit --color=always ${{ steps.phpunit-options.outputs.EXTRA_PHPUNIT_OPTIONS }}" + env: + DB: ${{ inputs.db-platform }} + TACHYCARDIA_MONITOR_GA: ${{ inputs.enable-profiling && 'enabled' || '' }} + TERM: xterm-256color + continue-on-error: ${{ inputs.php-version == '8.5' }} + + - name: Upload coverage results as artifact + if: ${{ inputs.enable-artifact-upload }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ steps.setup-env.outputs.ARTIFACT_NAME }} + path: build/cov/coverage-${{ steps.setup-env.outputs.ARTIFACT_NAME }}.cov + if-no-files-found: error + retention-days: 1 diff --git a/.github/workflows/reusable-serviceless-phpunit-test.yml b/.github/workflows/reusable-serviceless-phpunit-test.yml new file mode 100644 index 000000000000..8420d2b8c978 --- /dev/null +++ b/.github/workflows/reusable-serviceless-phpunit-test.yml @@ -0,0 +1,135 @@ +# Reusable workflow for PHPUnit testing +# without Docker services and databases +name: Reusable Serviceless PHPUnit Test + +on: + workflow_call: + inputs: + job-name: + description: Name of the job to appear in GitHub UI + type: string + required: true + php-version: + description: The PHP version the workflow should run + type: string + required: true + job-id: + description: Job ID to be used as part of cache key and artifact name + type: string + required: false + group-name: + description: The @group to test + type: string + required: false + enable-artifact-upload: + description: Whether artifact uploading of coverage results should be enabled + type: boolean + required: false + enable-coverage: + description: Whether coverage should be enabled + type: boolean + required: false + enable-profiling: + description: Whether slow tests should be profiled + type: boolean + required: false + extra-extensions: + description: Additional PHP extensions that are needed to be enabled + type: string + required: false + extra-ini-options: + description: Additional PHP configuration directives that should be appended to the php.ini + type: string + required: false + extra-composer-options: + description: Additional Composer options that should be appended to the `composer update` call + type: string + required: false + extra-phpunit-options: + description: Additional PHPUnit options that should be appended to the `vendor/bin/phpunit` call + type: string + required: false + +permissions: + contents: read + +jobs: + tests: + name: ${{ inputs.job-name }} + runs-on: ubuntu-24.04 + + steps: + - name: Install latest ImageMagick + if: ${{ contains(inputs.extra-extensions, 'imagick') }} + run: | + sudo apt-get update + sudo apt-get install -y imagemagick libmagickwand-dev ghostscript poppler-data libjbig2dec0:amd64 libopenjp2-7:amd64 + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: ${{ inputs.php-version }} + tools: composer + extensions: gd, ${{ inputs.extra-extensions }} + ini-values: ${{ inputs.extra-ini-options }} + coverage: ${{ env.COVERAGE_DRIVER }} + env: + COVERAGE_DRIVER: ${{ inputs.enable-coverage && 'xdebug' || 'none' }} + + - name: Setup global environment variables + id: setup-env + run: | + echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + echo "ARTIFACT_NAME=${{ inputs.job-id || github.job }}-php-${{ inputs.php-version }}" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.setup-env.outputs.COMPOSER_CACHE_FILES_DIR }} + key: ${{ inputs.job-id || github.job }}-php-${{ inputs.php-version }}-${{ hashFiles('**/composer.*') }} + restore-keys: | + ${{ inputs.job-id || github.job }}-php-${{ inputs.php-version }}- + ${{ inputs.job-id || github.job }}- + + - name: Cache PHPUnit's static analysis cache + if: ${{ inputs.enable-artifact-upload }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: build/.phpunit.cache/code-coverage + key: phpunit-code-coverage-${{ hashFiles('**/phpunit.*') }} + restore-keys: | + phpunit-code-coverage- + + - name: Install dependencies + run: | + composer config --global github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + composer update --ansi ${{ inputs.extra-composer-options }} + + - name: Compute additional PHPUnit options + id: phpunit-options + run: | + echo "EXTRA_PHPUNIT_OPTIONS=${{ format('{0} {1} {2}', env.GROUP_OPTION, env.COVERAGE_OPTION, inputs.extra-phpunit-options) }}" >> $GITHUB_OUTPUT + env: + COVERAGE_OPTION: ${{ inputs.enable-coverage && format('--coverage-php build/cov/coverage-{0}.cov', steps.setup-env.outputs.ARTIFACT_NAME) || '--no-coverage' }} + GROUP_OPTION: ${{ inputs.group-name && format('--group {0}', inputs.group-name) || '' }} + + - name: Run tests + run: script -e -c "vendor/bin/phpunit --color=always ${{ steps.phpunit-options.outputs.EXTRA_PHPUNIT_OPTIONS }}" + env: + TACHYCARDIA_MONITOR_GA: ${{ inputs.enable-profiling && 'enabled' || '' }} + TERM: xterm-256color + + - name: Upload coverage results as artifact + if: ${{ inputs.enable-artifact-upload }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ steps.setup-env.outputs.ARTIFACT_NAME }} + path: build/cov/coverage-${{ steps.setup-env.outputs.ARTIFACT_NAME }}.cov + if-no-files-found: error + retention-days: 1 diff --git a/.github/workflows/test-autoreview.yml b/.github/workflows/test-autoreview.yml new file mode 100644 index 000000000000..f06e02619966 --- /dev/null +++ b/.github/workflows/test-autoreview.yml @@ -0,0 +1,108 @@ +name: AutoReview + +on: + pull_request: + paths: + - composer.json + - spark + - '**.php' + - .github/workflows/test-autoreview.yml + push: + paths: + - composer.json + - spark + - '**.php' + - .github/workflows/test-autoreview.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + auto-review-tests: + name: Automatic Code Review + uses: ./.github/workflows/reusable-serviceless-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo + with: + job-name: PHP 8.2 + php-version: '8.2' + job-id: auto-review-tests + group-name: AutoReview + + composer-normalize-tests: + name: Check normalized composer.json + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: '8.2' + + - name: Install dependencies + run: composer update + + - name: Run on root composer.json + id: normalize-root-composer + if: ${{ always() }} + run: | + echo "ROOT_COMPOSER_RESULT<> $GITHUB_OUTPUT + echo "$(composer normalize)" >> $GITHUB_OUTPUT + echo "RCR" >> $GITHUB_OUTPUT + + - name: Run on framework composer.json + id: normalize-framework-composer + if: ${{ always() }} + working-directory: admin/framework + run: | + echo "FRAMEWORK_COMPOSER_RESULT<> $GITHUB_OUTPUT + echo "$(composer normalize)" >> $GITHUB_OUTPUT + echo "FCR" >> $GITHUB_OUTPUT + + - name: Run on starter composer.json + id: normalize-starter-composer + if: ${{ always() }} + working-directory: admin/starter + run: | + echo "STARTER_COMPOSER_RESULT<> $GITHUB_OUTPUT + echo "$(composer normalize)" >> $GITHUB_OUTPUT + echo "SCR" >> $GITHUB_OUTPUT + + - name: Analyse normalization results + run: | + if [[ '${{ steps.normalize-root-composer.conclusion }}' == 'failure' ]]; then + echo 'Normalization of root composer.json encountered a problem.'; + echo 'Please run it locally: `composer normalize`'; + exit 1; + fi + + if [[ ${{ contains(steps.normalize-root-composer.outputs.ROOT_COMPOSER_RESULT, 'Successfully normalized') }} == true ]]; then + echo 'Root composer.json is not yet normalized.'; + exit 1; + fi + + if [[ '${{ steps.normalize-framework-composer.conclusion }}' == 'failure' ]]; then + echo 'Normalization of framework composer.json encountered a problem.'; + echo 'Please run it locally: `composer normalize -d admin/framework`'; + exit 1; + fi + + if [[ ${{ contains(steps.normalize-framework-composer.outputs.FRAMEWORK_COMPOSER_RESULT, 'Successfully normalized') }} == true ]]; then + echo 'Framework composer.json is not yet normalized.'; + exit 1; + fi + + if [[ '${{ steps.normalize-starter-composer.conclusion }}' == 'failure' ]]; then + echo 'Normalization of starter composer.json encountered a problem.'; + echo 'Please run it locally: `composer normalize -d admin/starter`'; + exit 1; + fi + + if [[ ${{ contains(steps.normalize-starter-composer.outputs.STARTER_COMPOSER_RESULT, 'Successfully normalized') }} == true ]]; then + echo 'Starter composer.json is not yet normalized.'; + exit 1; + fi diff --git a/.github/workflows/test-coding-standards.yml b/.github/workflows/test-coding-standards.yml index cb7d9671e2cd..b02059733b13 100644 --- a/.github/workflows/test-coding-standards.yml +++ b/.github/workflows/test-coding-standards.yml @@ -12,25 +12,33 @@ on: - 'spark' - '.github/workflows/test-coding-standards.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + jobs: lint: name: PHP ${{ matrix.php-version }} Lint with PHP CS Fixer - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: php-version: - - '7.3' - - '7.4' - - '8.0' + - '8.2' + include: + - php-version: '8.5' + composer-option: '--ignore-platform-req=php' steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php-version }} extensions: tokenizer @@ -38,22 +46,19 @@ jobs: - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-${{ matrix.php-version }}- ${{ runner.os }}- - name: Install dependencies - run: composer update --ansi --no-interaction - - - name: Run lint on `app/`, `admin/`, `public/` - run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --config=.no-header.php-cs-fixer.dist.php --using-cache=no --diff + run: composer update --ansi --no-interaction ${{ matrix.composer-option }} - - name: Run lint on `system/`, `tests`, `utils/`, and root PHP files - run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --using-cache=no --diff + - name: Run lint + run: composer cs diff --git a/.github/workflows/test-deptrac.yml b/.github/workflows/test-deptrac.yml deleted file mode 100644 index 0698809319fe..000000000000 --- a/.github/workflows/test-deptrac.yml +++ /dev/null @@ -1,75 +0,0 @@ -# When a PR is opened or a push is made, perform an -# architectural inspection on the code using Deptrac. -name: Deptrac - -on: - pull_request: - branches: - - 'develop' - - '4.*' - paths: - - 'app/**' - - 'system/**' - - 'composer.json' - - 'depfile.yaml' - - '.github/workflows/test-deptrac.yml' - push: - branches: - - 'develop' - - '4.*' - paths: - - 'app/**' - - 'system/**' - - 'composer.json' - - 'depfile.yaml' - - '.github/workflows/test-deptrac.yml' - -jobs: - build: - name: Architectural Inspection - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: composer, phive - extensions: intl, json, mbstring, gd, mysqlnd, xdebug, xml, sqlite3 - - - name: Validate composer.json - run: composer validate --strict - - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} - - - name: Cache composer dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create Deptrac cache directory - run: mkdir -p build/ - - - name: Cache Deptrac results - uses: actions/cache@v2 - with: - path: build - key: ${{ runner.os }}-deptrac-${{ github.sha }} - restore-keys: ${{ runner.os }}-deptrac- - - - name: Install dependencies - run: composer update --ansi --no-interaction - - - name: Run architectural inspection - run: | - sudo phive --no-progress install --global qossmic/deptrac --trust-gpg-keys B8F640134AB1782E - deptrac analyze --cache-file=build/deptrac.cache diff --git a/.github/workflows/test-file-permissions.yml b/.github/workflows/test-file-permissions.yml new file mode 100644 index 000000000000..441d555137e8 --- /dev/null +++ b/.github/workflows/test-file-permissions.yml @@ -0,0 +1,24 @@ +name: Check File Permissions + +on: + pull_request: + push: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + permission-check: + name: Check File Permission + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Detect unnecessary execution permissions + run: php utils/check_permission_x.php diff --git a/.github/workflows/test-phpcpd.yml b/.github/workflows/test-phpcpd.yml index d78edbea1f0e..636252763ddf 100644 --- a/.github/workflows/test-phpcpd.yml +++ b/.github/workflows/test-phpcpd.yml @@ -8,36 +8,40 @@ on: - 'develop' - '4.*' paths: - - 'app/**' - - 'public/**' - - 'system/**' + - 'app/**.php' + - 'public/**.php' + - 'system/**.php' - '.github/workflows/test-phpcpd.yml' push: branches: - 'develop' - '4.*' paths: - - 'app/**' - - 'public/**' - - 'system/**' + - 'app/**.php' + - 'public/**.php' + - 'system/**.php' - '.github/workflows/test-phpcpd.yml' -jobs: - build: - name: Duplicate Code Detection - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: phive - extensions: intl, json, mbstring, xml +permissions: + contents: read - - name: Detect code duplication - run: | - sudo phive --no-progress install --global --trust-gpg-keys 4AA394086372C20A phpcpd - phpcpd --exclude system/Test --exclude system/ThirdParty --exclude system/Database/SQLSRV/Builder.php --exclude system/Database/SQLSRV/Forge.php app/ public/ system/ +jobs: + phpcpd: + # Note: Reusable workflow SHA must be manually updated. Check for updates with: + # git ls-remote https://github.com/codeigniter4/.github main | head -1 + uses: codeigniter4/.github/.github/workflows/phpcpd.yml@0ad5e1bc5620281e766d3267205dc4c22f4ac0ee # main + with: + dirs: "app/ public/ system/" + options: >- + --exclude system/Test + --exclude system/ThirdParty + --exclude system/Database/SQLSRV/Builder.php + --exclude system/Database/SQLSRV/Forge.php + --exclude system/Database/MySQLi/Builder.php + --exclude system/Database/OCI8/Builder.php + --exclude system/Database/Postgre/Builder.php + --exclude system/Debug/Exceptions.php + --exclude system/HTTP/SiteURI.php + --exclude system/Validation/Rules.php + --exclude system/Autoloader/Autoloader.php + --exclude system/Config/Filters.php diff --git a/.github/workflows/test-phpstan.yml b/.github/workflows/test-phpstan.yml index 0e59e3bf4e3a..cc246f871808 100644 --- a/.github/workflows/test-phpstan.yml +++ b/.github/workflows/test-phpstan.yml @@ -8,37 +8,52 @@ on: - 'develop' - '4.*' paths: - - 'app/**' - - 'system/**' + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'utils/**.php' - composer.json - - phpstan.neon.dist + - phpstan.dist.neon + - phpstan-baseline.php + - '.github/workflows/test-phpstan.yml' + push: branches: - 'develop' - '4.*' paths: - - 'app/**' - - 'system/**' + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'utils/**.php' - composer.json - - phpstan.neon.dist + - phpstan.dist.neon + - phpstan-baseline.php + - '.github/workflows/test-phpstan.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read jobs: build: name: PHP ${{ matrix.php-versions }} Static Analysis - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false - matrix: - php-versions: ['7.4', '8.0'] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: - php-version: ${{ matrix.php-versions }} + php-version: '8.2' extensions: intl + coverage: none - name: Use latest Composer run: composer self-update @@ -48,15 +63,12 @@ jobs: - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - name: Cache composer dependencies - uses: actions/cache@v2 + - name: Cache dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- @@ -64,7 +76,7 @@ jobs: run: mkdir -p build/phpstan - name: Cache PHPStan result cache directory - uses: actions/cache@v2 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: build/phpstan key: ${{ runner.os }}-phpstan-${{ github.sha }} @@ -74,4 +86,4 @@ jobs: run: composer update --ansi --no-interaction - name: Run static analysis - run: vendor/bin/phpstan analyse + run: composer phpstan:check diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index aa7d584991a6..8c17cae53de6 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -6,154 +6,173 @@ on: - develop - '4.*' paths: - - 'app/**' - - 'public/**' - - 'system/**' - - 'tests/**' + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'spark' - composer.json - - spark - - phpunit.xml.dist - - '**.php' + - phpunit.dist.xml - .github/workflows/test-phpunit.yml + - .github/workflows/reusable-phpunit-test.yml + pull_request: branches: - develop - '4.*' paths: - - 'app/**' - - 'public/**' - - 'system/**' - - 'tests/**' + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'spark' - composer.json - - spark - - phpunit.xml.dist - - '**.php' + - phpunit.dist.xml - .github/workflows/test-phpunit.yml + - .github/workflows/reusable-phpunit-test.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read jobs: + # Any environment variables set in an env context defined at the workflow level + # in the caller workflow are not propagated to the called workflow. + coverage-php-version: + name: Setup PHP Version for Code Coverage + runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.coverage-php-version.outputs.version }} + steps: + - id: coverage-php-version + run: | + echo "version=8.2" >> $GITHUB_OUTPUT - tests: - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, '[ci skip]')" - name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} + sanity-tests: + name: Others + needs: coverage-php-version + + strategy: + matrix: + php-version: + - '8.2' + - '8.3' + - '8.4' + - '8.5' + + uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo + with: + job-name: Sanity Tests + php-version: ${{ matrix.php-version }} + job-id: sanity-tests + group-name: Others + enable-artifact-upload: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + enable-coverage: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + enable-profiling: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + extra-extensions: imagick, redis, memcached + + database-live-tests: + name: DatabaseLive + needs: + - coverage-php-version + - sanity-tests strategy: fail-fast: false matrix: - php-versions: ['7.3', '7.4', '8.0'] - db-platforms: ['MySQLi', 'Postgre', 'SQLite3', 'SQLSRV'] - mysql-versions: ['5.7'] + php-version: + - '8.2' + - '8.3' + - '8.4' + - '8.5' + db-platform: + - MySQLi + - OCI8 + - Postgre + - SQLSRV + - SQLite3 + mysql-version: + - '8.0' include: - - php-versions: '7.4' - db-platforms: MySQLi - mysql-versions: '8.0' - - services: - mysql: - image: mysql:${{ matrix.mysql-versions }} - env: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: test - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - postgres: - image: postgres - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: test - ports: - - 5432:5432 - options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 - mssql: - image: mcr.microsoft.com/mssql/server:2019-CU10-ubuntu-20.04 - env: - SA_PASSWORD: 1Secure*Password1 - ACCEPT_EULA: Y - MSSQL_PID: Developer - ports: - - 1433:1433 - options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" --health-interval=10s --health-timeout=5s --health-retries=3 - redis: - image: redis - ports: - - 6379:6379 - options: --health-cmd "redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - memcached: - image: memcached:1.6-alpine - ports: - - 11211:11211 + - php-version: '8.2' + db-platform: MySQLi + mysql-version: '5.7' + + uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo + with: + job-name: '' + php-version: ${{ matrix.php-version }} + job-id: database-live-tests + db-platform: ${{ matrix.db-platform }} + mysql-version: ${{ matrix.mysql-version }} + group-name: DatabaseLive + enable-artifact-upload: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + enable-coverage: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + enable-profiling: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + extra-extensions: mysqli, oci8, pgsql, sqlsrv, sqlite3 + + separate-process-tests: + name: SeparateProcess + needs: + - coverage-php-version + - sanity-tests - steps: - - name: Create database for MSSQL Server - if: matrix.db-platforms == 'SQLSRV' - run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" - - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: composer, pecl - extensions: imagick, sqlsrv, gd, sqlite3, redis, memcached, pgsql - coverage: xdebug - env: - update: true - - - name: Install latest ImageMagick - run: | - sudo apt-get install --reinstall libgs9-common fonts-noto-mono libgs9:amd64 libijs-0.35:amd64 fonts-urw-base35 ghostscript poppler-data libjbig2dec0:amd64 gsfonts libopenjp2-7:amd64 fonts-droid-fallback ttf-dejavu-core - sudo apt-get install -y imagemagick - sudo apt-get install --fix-broken - - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - composer update --ansi --no-interaction - composer remove --ansi --dev --unused -W rector/rector phpstan/phpstan friendsofphp/php-cs-fixer nexusphp/cs-config codeigniter/coding-standard - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} - - - name: Profile slow tests in PHP 7.4 MySQLi for now - if: matrix.php-versions == '7.4' && matrix.db-platforms == 'MySQLi' - run: echo "TACHYCARDIA_MONITOR_GA=enabled" >> $GITHUB_ENV - - - name: Test with PHPUnit - run: script -e -c "vendor/bin/phpunit --color=always" - env: - DB: ${{ matrix.db-platforms }} - TERM: xterm-256color - - - if: github.repository_owner == 'codeigniter4' && matrix.php-versions == '7.4' - name: Run Coveralls - run: | - composer global require --ansi php-coveralls/php-coveralls:^2.4 - php-coveralls --coverage_clover=build/logs/clover.xml -v - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} - - coveralls-finish: + strategy: + matrix: + php-version: + - '8.2' + - '8.3' + - '8.4' + - '8.5' + + uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo + with: + job-name: '' + php-version: ${{ matrix.php-version }} + job-id: separate-process-tests + group-name: SeparateProcess + enable-artifact-upload: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + enable-coverage: true # needs xdebug for assertHeaderEmitted() tests + enable-profiling: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + extra-extensions: mysqli, oci8, pgsql, sqlsrv-5.10.1, sqlite3 + + cache-live-tests: + name: CacheLive + needs: + - coverage-php-version + - sanity-tests + + strategy: + matrix: + php-version: + - '8.2' + - '8.3' + - '8.4' + - '8.5' + + uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo + with: + job-name: Cache Live Tests + php-version: ${{ matrix.php-version }} + job-id: cache-live-tests + group-name: CacheLive + enable-artifact-upload: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + enable-coverage: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + enable-profiling: ${{ matrix.php-version == needs.coverage-php-version.outputs.version }} + extra-extensions: redis, memcached, apcu + extra-ini-options: apc.enable_cli=1 + + coveralls: + name: Upload coverage results to Coveralls if: github.repository_owner == 'codeigniter4' - needs: [tests] - runs-on: ubuntu-20.04 - steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true + needs: + - coverage-php-version + - sanity-tests + - cache-live-tests + - database-live-tests + - separate-process-tests + + uses: ./.github/workflows/reusable-coveralls.yml # @TODO Extract to codeigniter4/.github repo + with: + php-version: ${{ needs.coverage-php-version.outputs.version }} diff --git a/.github/workflows/test-psalm.yml b/.github/workflows/test-psalm.yml new file mode 100644 index 000000000000..3f385ce858fb --- /dev/null +++ b/.github/workflows/test-psalm.yml @@ -0,0 +1,79 @@ +name: Psalm + +on: + pull_request: + branches: + - develop + - '4.*' + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/test-psalm.yml' + push: + branches: + - develop + - '4.*' + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/test-psalm.yml' + +permissions: + contents: read + +jobs: + build: + name: Psalm Analysis + runs-on: ubuntu-24.04 + + strategy: + fail-fast: false + matrix: + php-version: + - '8.2' + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: ${{ matrix.php-version }} + extensions: intl, json, mbstring, xml, mysqli, oci8, pgsql, sqlsrv, sqlite3 + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache composer dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_FILES_DIR }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create Psalm cache directory + run: mkdir -p build/psalm + + - name: Cache Psalm results + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: build/psalm + key: ${{ runner.os }}-psalm-${{ github.sha }} + restore-keys: ${{ runner.os }}-psalm- + + - name: Install dependencies + run: | + composer require sebastian/diff:^5.0 --ansi --working-dir utils + composer update --ansi --no-interaction + + - name: Run Psalm analysis + run: utils/vendor/bin/psalm diff --git a/.github/workflows/test-random-execution.yml b/.github/workflows/test-random-execution.yml new file mode 100644 index 000000000000..e90de6bb031d --- /dev/null +++ b/.github/workflows/test-random-execution.yml @@ -0,0 +1,238 @@ +name: Random Test Execution Verification + +on: + push: + branches: + - develop + - '4.*' + paths: + - '.github/scripts/run-random-tests.sh' + - '.github/scripts/random-tests-config.txt' + - '.github/workflows/test-random-execution.yml' + - 'phpunit.dist.xml' + - 'system/**.php' + - 'tests/**.php' + + pull_request: + branches: + - develop + - '4.*' + paths: + - '.github/scripts/run-random-tests.sh' + - '.github/scripts/random-tests-config.txt' + - '.github/workflows/test-random-execution.yml' + - 'phpunit.dist.xml' + - 'system/**.php' + - 'tests/**.php' + + workflow_dispatch: + inputs: + quiet: + description: Suppress debug output + type: boolean + required: false + default: false + component: + description: Test single component (overrides config file) + type: string + required: false + default: '' + max-jobs: + description: Limit concurrent test jobs (auto-detect if omitted) + type: string + required: false + default: '' + repeat: + description: Repeat full component run REPEAT times + type: string + required: false + default: '10' + timeout: + description: Per-component timeout in seconds (0 disables) + type: string + required: false + default: '300' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + random-tests: + name: PHP ${{ matrix.php-version }} - ${{ matrix.db-platform }} + runs-on: ubuntu-24.04 + + strategy: + fail-fast: false + matrix: + php-version: + - '8.5' + db-platform: + - MySQLi + - Postgre + - SQLSRV + - SQLite3 + - Oracle + + services: + mysql: + image: ${{ matrix.db-platform == 'MySQLi' && 'mysql:8.0' || '' }} + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + postgres: + image: ${{ matrix.db-platform == 'Postgre' && 'postgres' || '' }} + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + ports: + - 5432:5432 + options: >- + --health-cmd=pg_isready + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + mssql: + image: ${{ matrix.db-platform == 'SQLSRV' && 'mcr.microsoft.com/mssql/server:2025-CU3-ubuntu-24.04' || '' }} + env: + MSSQL_SA_PASSWORD: 1Secure*Password1 + ACCEPT_EULA: Y + MSSQL_PID: Developer + ports: + - 1433:1433 + options: >- + --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + oracle: + image: ${{ matrix.db-platform == 'Oracle' && 'gvenzl/oracle-free:latest' || '' }} + env: + ORACLE_RANDOM_PASSWORD: true + APP_USER: ORACLE + APP_USER_PASSWORD: ORACLE + ports: + - 1521:1521 + options: >- + --health-cmd healthcheck.sh + --health-interval 20s + --health-timeout 10s + --health-retries 10 + + redis: + image: redis + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + memcached: + image: memcached:1.6-alpine + ports: + - 11211:11211 + + steps: + - name: Install mssql-tools on runner + if: ${{ matrix.db-platform == 'SQLSRV' }} + run: | + source /etc/os-release + curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor --batch --yes -o /usr/share/keyrings/microsoft-prod.gpg + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${UBUNTU_CODENAME} main" | sudo tee /etc/apt/sources.list.d/mssql-release.list + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 mssql-tools18 unixodbc-dev + echo "/opt/mssql-tools18/bin" >> $GITHUB_PATH + + - name: Create database for MSSQL Server + if: ${{ matrix.db-platform == 'SQLSRV' }} + run: | + sqlcmd -S 127.0.0.1 \ + -U sa -P 1Secure*Password1 \ + -N -C \ + -Q "CREATE DATABASE test COLLATE Latin1_General_100_CS_AS_SC_UTF8" + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Setup PHP ${{ matrix.php-version }} + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: ${{ matrix.php-version }} + extensions: gd, curl, iconv, json, mbstring, openssl, sodium + ini-values: opcache.enable_cli=0 + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache composer dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_FILES_DIR }} + key: PHP_${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }} + restore-keys: | + PHP_${{ matrix.php-version }}- + + - name: Install project dependencies + run: | + composer config --global github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + composer update --ansi + + - name: Run random test execution verification + run: | + args=() + + # Add --quiet flag if input is true + if [[ "${{ inputs.quiet }}" == "true" ]]; then + args+=("--quiet") + fi + + # Add --component flag if component is specified + if [[ -n "${{ inputs.component }}" ]]; then + args+=("--component" "${{ inputs.component }}") + fi + + # Add --max-jobs flag if specified (empty means auto-detect) + if [[ -n "${{ inputs.max-jobs }}" ]]; then + args+=("--max-jobs" "${{ inputs.max-jobs }}") + fi + + # Add --repeat flag (always, default is 10) + args+=("--repeat" "${{ inputs.repeat || '10' }}") + + # Add --timeout flag (always, default is 300) + args+=("--timeout" "${{ inputs.timeout || '300' }}") + + .github/scripts/run-random-tests.sh "${args[@]}" + env: + DB: ${{ matrix.db-platform }} + TERM: xterm-256color + + - name: Upload random-test artifacts on failure + if: ${{ always() && failure() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: random-tests-${{ matrix.db-platform }}-php${{ matrix.php-version }} + path: build/random-tests/ + retention-days: 1 + overwrite: true diff --git a/.github/workflows/test-rector.yml b/.github/workflows/test-rector.yml index 0d7262ceb4ea..2fa9b74c0e02 100644 --- a/.github/workflows/test-rector.yml +++ b/.github/workflows/test-rector.yml @@ -8,40 +8,57 @@ on: - 'develop' - '4.*' paths: - - 'app/**' - - 'system/**' + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'utils/**.php' - '.github/workflows/test-rector.yml' - composer.json - - 'utils/Rector/**' - - 'rector.php' + - rector.php + - '**.neon.dist' + push: branches: - 'develop' - '4.*' paths: - - 'app/**' - - 'system/**' + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'utils/**.php' - '.github/workflows/test-rector.yml' - composer.json - - 'utils/Rector/**' - - 'rector.php' + - rector.php + - '**.neon.dist' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read jobs: build: - name: PHP ${{ matrix.php-versions }} Analyze code (Rector) - runs-on: ubuntu-20.04 + name: PHP ${{ matrix.php-version }} Analyze code (Rector) + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - php-versions: ['7.4', '8.0'] + php-version: + - '8.2' + include: + - php-version: '8.5' + composer-option: '--ignore-platform-req=php' + steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: - php-version: ${{ matrix.php-versions }} + php-version: ${{ matrix.php-version }} extensions: intl - name: Use latest Composer @@ -52,20 +69,26 @@ jobs: - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} - - - name: Cache composer dependencies - uses: actions/cache@v2 + - name: Cache dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies - run: composer update --ansi --no-interaction + run: composer update --ansi --no-interaction ${{ matrix.composer-option }} + + - name: Rector Cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: /tmp/rector + key: ${{ runner.os }}-rector-${{ github.run_id }} + restore-keys: ${{ runner.os }}-rector- + + - run: mkdir -p /tmp/rector - name: Run static analysis run: vendor/bin/rector process --dry-run --no-progress-bar diff --git a/.github/workflows/test-scss.yml b/.github/workflows/test-scss.yml new file mode 100644 index 000000000000..0000e700a422 --- /dev/null +++ b/.github/workflows/test-scss.yml @@ -0,0 +1,57 @@ +name: SCSS Compilation + +on: + pull_request: + branches: + - 'develop' + - '4.*' + paths: + - '**.scss' + - '**.css' + - '.github/workflows/test-scss.yml' + + push: + branches: + - 'develop' + - '4.*' + paths: + - '**.scss' + - '**.css' + - '.github/workflows/test-scss.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + name: Compilation of SCSS (Dart Sass) + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + + - name: Install Dart Sass + run: | + npm install --global sass + sass --version + + - name: Run Dart Sass + run: sass --no-source-map admin/css/debug-toolbar/toolbar.scss system/Debug/Toolbar/Views/toolbar.css + + - name: Check for changed CSS files + run: | + if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then + echo "Your changes to the SCSS files did not match the expected CSS output." + git diff-files --patch + exit 1 + fi diff --git a/.github/workflows/test-structarmed.yml b/.github/workflows/test-structarmed.yml new file mode 100644 index 000000000000..1c6d731c9e4b --- /dev/null +++ b/.github/workflows/test-structarmed.yml @@ -0,0 +1,90 @@ +# When a PR is opened or a push is made, perform +# a static analysis check on the code using Structarmed. +name: Structarmed + +on: + pull_request: + branches: + - 'develop' + - '4.*' + paths: + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'utils/**.php' + - '.github/workflows/test-structarmed.yml' + - composer.json + - structarmed.php + - '**.neon.dist' + + push: + branches: + - 'develop' + - '4.*' + paths: + - 'app/**.php' + - 'system/**.php' + - 'tests/**.php' + - 'utils/**.php' + - '.github/workflows/test-structarmed.yml' + - composer.json + - structarmed.php + - '**.neon.dist' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + name: PHP ${{ matrix.php-version }} Analyze code (Structarmed) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-version: + - '8.2' + - '8.5' + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: ${{ matrix.php-version }} + extensions: intl + tools: composer + + - name: Validate composer.json + run: composer validate --strict + + - name: Get composer cache directory + id: composer-cache + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_FILES_DIR }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer update --ansi --no-interaction + + - name: Structarmed Cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: /tmp/structarmed + key: ${{ runner.os }}-structarmed-${{ github.run_id }} + restore-keys: ${{ runner.os }}-structarmed- + + - run: mkdir -p /tmp/structarmed + + - name: Run static analysis + run: vendor/bin/structarmed analyze diff --git a/.github/workflows/test-userguide.yml b/.github/workflows/test-userguide.yml index 44cfa52c8b17..88f316888f02 100644 --- a/.github/workflows/test-userguide.yml +++ b/.github/workflows/test-userguide.yml @@ -10,18 +10,35 @@ on: - 'user_guide_src/**' - '.github/workflows/test-userguide.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + jobs: syntax_check: name: Check User Guide syntax - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + + - name: Install Sphinx + run: | + python -m pip install --upgrade pip + pip install -r user_guide_src/requirements.txt - name: Detect usage of tabs in RST files run: php utils/check_tabs_in_rst.php - - uses: ammaraskar/sphinx-action@0.4 - with: - docs-folder: user_guide_src + - name: Build Docs with Sphinx + run: make html SPHINXOPTS="-W --keep-going -w /tmp/sphinx-log" + working-directory: user_guide_src diff --git a/.github/workflows/verify-release.yml b/.github/workflows/verify-release.yml new file mode 100644 index 000000000000..0488b76a1a70 --- /dev/null +++ b/.github/workflows/verify-release.yml @@ -0,0 +1,185 @@ +# When a release is published, verify that the release pipeline delivered +# everything: the distributable repos, the new framework version on +# Packagist, and the user guide with its ePub. +# +# Use "Run workflow" with the release tag to verify an existing release. +name: Verify Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: The release tag to verify (e.g., v4.7.5) + type: string + required: true + +permissions: + contents: read + +env: + TAG: ${{ github.event.release.tag_name || inputs.version }} + PUBLISHED_AT: ${{ github.event.release.published_at }} + +jobs: + wait-for-deploy: + name: Wait for distributables deploy + if: github.repository == 'codeigniter4/CodeIgniter4' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + actions: read + contents: read + + steps: + - name: Wait for "Deploy Distributable Repos" to succeed + env: + GH_TOKEN: ${{ github.token }} + run: | + # On workflow_dispatch the event carries no publish time. + if [ -z "${PUBLISHED_AT}" ]; then + PUBLISHED_AT=$(gh api "repos/${{ github.repository }}/releases/tags/${TAG}" --jq '.published_at') + echo "Resolved published_at=${PUBLISHED_AT} for ${TAG}." + fi + + query="repos/${{ github.repository }}/actions/workflows/deploy-distributables.yml/runs?event=release&per_page=1&created=>=${PUBLISHED_AT}" + + while true; do + run=$(gh api "${query}" --jq '.workflow_runs[0] | "\(.status) \(.conclusion)"' || true) + status=${run% *} + conclusion=${run#* } + echo "Deploy Distributable Repos: status=${status:-not found} conclusion=${conclusion:-n/a}" + + if [ "${status}" = "completed" ]; then + if [ "${conclusion}" = "success" ]; then + exit 0 + fi + + echo "Deploy Distributable Repos did not succeed." + exit 1 + fi + + sleep 30 + done + + appstarter: + name: Install and test appstarter + needs: wait-for-deploy + runs-on: ubuntu-24.04 + timeout-minutes: 45 + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: '8.2' + tools: composer + extensions: intl, gd + coverage: none + + - name: Wait for Packagist to serve the new framework version + run: | + version="${TAG#v}" + + for i in $(seq 1 30); do + if curl -fsSL https://repo.packagist.org/p2/codeigniter4/framework.json \ + | jq -e --arg version "v${version}" \ + '.packages."codeigniter4/framework" | map(select(.version == $version)) | length > 0' > /dev/null; then + echo "Packagist serves codeigniter4/framework v${version}." + exit 0 + fi + + echo "Attempt ${i}: v${version} is not yet on Packagist." + sleep 60 + done + + echo "Timed out waiting for Packagist to serve v${version}." + exit 1 + + - name: Install appstarter + run: composer create-project codeigniter4/appstarter release-test --no-interaction + + - name: Verify the framework version + working-directory: release-test + run: | + version="${TAG#v}" + installed=$(composer show codeigniter4/framework --format=json | jq -r '.versions[0]') + echo "Installed codeigniter4/framework: ${installed}" + + if [ "${installed}" != "v${version}" ]; then + echo "Expected v${version}." + exit 1 + fi + + - name: Test appstarter + working-directory: release-test + run: composer test + + userguide: + name: Verify user guide deployment + needs: wait-for-deploy + runs-on: ubuntu-24.04 + timeout-minutes: 30 + + steps: + - name: Check that the ePub was added + env: + GH_TOKEN: ${{ github.token }} + run: | + version="${TAG#v}" + + for i in $(seq 1 30); do + if gh api "repos/codeigniter4/userguide/contents/CodeIgniter${version}.epub" --jq '.name' > /dev/null 2>&1; then + echo "CodeIgniter${version}.epub is in the userguide repo." + exit 0 + fi + + echo "Attempt ${i}: CodeIgniter${version}.epub is not yet in the userguide repo." + sleep 30 + done + + echo "Timed out waiting for CodeIgniter${version}.epub." + exit 1 + + - name: Check that the user guide workflows succeeded + env: + GH_TOKEN: ${{ github.token }} + run: | + # On workflow_dispatch the event carries no publish time. + if [ -z "${PUBLISHED_AT}" ]; then + PUBLISHED_AT=$(gh api "repos/${{ github.repository }}/releases/tags/${TAG}" --jq '.published_at') + echo "Resolved published_at=${PUBLISHED_AT} for ${TAG}." + fi + + for workflow in "Deploy Production" "pages build and deployment"; do + query="repos/codeigniter4/userguide/actions/runs?per_page=30&created=>=${PUBLISHED_AT}" + + success="" + + for _ in $(seq 1 30); do + run=$(gh api "${query}" \ + | jq -r --arg name "${workflow}" \ + '[.workflow_runs[] | select(.name == $name)][0] | "\(.status) \(.conclusion)"') + status=${run% *} + conclusion=${run#* } + echo "${workflow}: status=${status:-not found} conclusion=${conclusion:-n/a}" + + if [ "${status}" = "completed" ]; then + if [ "${conclusion}" = "success" ]; then + success=1 + break + fi + + echo "\"${workflow}\" did not succeed." + exit 1 + fi + + sleep 30 + done + + if [ -z "${success}" ]; then + echo "Timed out waiting for \"${workflow}\"." + exit 1 + fi + done diff --git a/.gitignore b/.gitignore index 74c6d0ce6c4f..3e4a7c1d18f7 100644 --- a/.gitignore +++ b/.gitignore @@ -44,42 +44,45 @@ $RECYCLE.BIN/ .env .vagrant Vagrantfile +/user_guide_src/venv/ +.python-version +/user_guide_src/.python-version #------------------------- # Temporary Files #------------------------- -writable/cache/* -!writable/cache/index.html +/writable/cache/* +!/writable/cache/index.html -writable/logs/* -!writable/logs/index.html +/writable/logs/* +!/writable/logs/index.html -writable/session/* -!writable/session/index.html +/writable/session/* +!/writable/session/index.html -writable/uploads/* -!writable/uploads/index.html +/writable/uploads/* +!/writable/uploads/index.html -writable/debugbar/* -!writable/debugbar/.gitkeep +/writable/debugbar/* +!/writable/debugbar/index.html -writable/**/*.db -writable/**/*.sqlite +/writable/**/*.db +/writable/**/*.sqlite php_errors.log #------------------------- # User Guide Temp Files #------------------------- -user_guide_src/build/* +/user_guide_src/build/* #------------------------- # Test Files #------------------------- -tests/coverage* +/tests/coverage* # Don't save phpunit under version control. -phpunit +/phpunit #------------------------- # Composer @@ -102,14 +105,14 @@ _modules/* *.iml # Netbeans -nbproject/ -build/ -nbbuild/ -dist/ -nbdist/ -nbactions.xml -nb-configuration.xml -.nb-gradle/ +/nbproject/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/nbactions.xml +/nb-configuration.xml +/.nb-gradle/ # Sublime Text *.tmlanguage.cache @@ -124,7 +127,6 @@ nb-configuration.xml .vscode/ /results/ -/phpunit*.xml -/.phpunit.*.cache +/phpunit.xml /.php-cs-fixer.php diff --git a/.no-header.php-cs-fixer.dist.php b/.no-header.php-cs-fixer.dist.php deleted file mode 100644 index 6e0a5c09602d..000000000000 --- a/.no-header.php-cs-fixer.dist.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -use CodeIgniter\CodingStandard\CodeIgniter4; -use Nexus\CsConfig\Factory; -use Nexus\CsConfig\Fixer\Comment\NoCodeSeparatorCommentFixer; -use Nexus\CsConfig\Fixer\Comment\SpaceAfterCommentStartFixer; -use Nexus\CsConfig\FixerGenerator; -use PhpCsFixer\Finder; - -$finder = Finder::create() - ->files() - ->in([ - __DIR__ . '/admin', - __DIR__ . '/app', - __DIR__ . '/public', - ]) - ->notName('#Logger\.php$#'); - -$overrides = [ - // @TODO Remove once these are live in coding-standard - 'assign_null_coalescing_to_coalesce_equal' => false, // requires 7.4+ - 'control_structure_continuation_position' => ['position' => 'same_line'], - 'empty_loop_condition' => ['style' => 'while'], - 'integer_literal_case' => true, - 'modernize_strpos' => false, // requires 8.0+ - 'no_alternative_syntax' => ['fix_non_monolithic_code' => false], - 'no_space_around_double_colon' => true, - 'octal_notation' => false, // requires 8.1+ - 'string_length_to_empty' => true, -]; - -$options = [ - 'cacheFile' => 'build/.no-header.php-cs-fixer.cache', - 'finder' => $finder, - 'customFixers' => FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'), - 'customRules' => [ - NoCodeSeparatorCommentFixer::name() => true, - SpaceAfterCommentStartFixer::name() => true, - ], -]; - -return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 2196640dae93..ad6ffacb8f0c 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -13,52 +13,38 @@ use CodeIgniter\CodingStandard\CodeIgniter4; use Nexus\CsConfig\Factory; -use Nexus\CsConfig\Fixer\Comment\NoCodeSeparatorCommentFixer; -use Nexus\CsConfig\Fixer\Comment\SpaceAfterCommentStartFixer; -use Nexus\CsConfig\FixerGenerator; use PhpCsFixer\Finder; $finder = Finder::create() ->files() ->in([ __DIR__ . '/system', - __DIR__ . '/tests', __DIR__ . '/utils', ]) - ->exclude(['ThirdParty']) - ->notName('#Foobar.php$#') + ->exclude([ + 'Pager/Views', + 'ThirdParty', + 'Validation/Views', + ]) ->append([ __FILE__, - __DIR__ . '/.no-header.php-cs-fixer.dist.php', + __DIR__ . '/.php-cs-fixer.no-header.php', + __DIR__ . '/.php-cs-fixer.tests.php', + __DIR__ . '/.php-cs-fixer.user-guide.php', + __DIR__ . '/preload.php', __DIR__ . '/rector.php', __DIR__ . '/spark', ]); -$overrides = [ - // @TODO Remove once these are live in coding-standard - 'assign_null_coalescing_to_coalesce_equal' => false, // requires 7.4+ - 'control_structure_continuation_position' => ['position' => 'same_line'], - 'empty_loop_condition' => ['style' => 'while'], - 'integer_literal_case' => true, - 'modernize_strpos' => false, // requires 8.0+ - 'no_alternative_syntax' => ['fix_non_monolithic_code' => false], - 'no_space_around_double_colon' => true, - 'octal_notation' => false, // requires 8.1+ - 'string_length_to_empty' => true, -]; +$overrides = []; $options = [ - 'cacheFile' => 'build/.php-cs-fixer.cache', - 'finder' => $finder, - 'customFixers' => FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'), - 'customRules' => [ - NoCodeSeparatorCommentFixer::name() => true, - SpaceAfterCommentStartFixer::name() => true, - ], + 'cacheFile' => 'build/.php-cs-fixer.cache', + 'finder' => $finder, ]; return Factory::create(new CodeIgniter4(), $overrides, $options)->forLibrary( 'CodeIgniter 4 framework', 'CodeIgniter Foundation', - 'admin@codeigniter.com' + 'admin@codeigniter.com', ); diff --git a/.php-cs-fixer.no-header.php b/.php-cs-fixer.no-header.php new file mode 100644 index 000000000000..f3bc97dc78ac --- /dev/null +++ b/.php-cs-fixer.no-header.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +use PhpCsFixer\ConfigInterface; +use PhpCsFixer\Finder; + +/** @var ConfigInterface $config */ +$config = require __DIR__ . '/.php-cs-fixer.dist.php'; + +$finder = Finder::create() + ->files() + ->in([ + __DIR__ . '/admin', + __DIR__ . '/app', + __DIR__ . '/public', + ]) + ->exclude(['Views/errors/html']) + ->append([ + __DIR__ . '/admin/starter/builds', + ]); + +$overrides = [ + 'header_comment' => false, +]; + +return $config + ->setFinder($finder) + ->setCacheFile('build/.php-cs-fixer.no-header.cache') + ->setRules(array_merge($config->getRules(), $overrides)); diff --git a/.php-cs-fixer.tests.php b/.php-cs-fixer.tests.php new file mode 100644 index 000000000000..ca74f7f5e404 --- /dev/null +++ b/.php-cs-fixer.tests.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +use PhpCsFixer\Config; +use PhpCsFixer\Finder; +use Utils\PhpCsFixer\CodeIgniterRuleCustomisationPolicy; + +/** @var Config $config */ +$config = require __DIR__ . '/.php-cs-fixer.dist.php'; + +$finder = Finder::create() + ->files() + ->in([ + __DIR__ . '/tests', + ]) + ->notPath([ + '_support/View/Cells/multiplier.php', + '_support/View/Cells/colors.php', + '_support/View/Cells/addition.php', + ]); + +$overrides = [ + 'declare_strict_types' => true, + 'phpdoc_to_return_type' => true, + 'void_return' => true, +]; + +return $config + ->setRuleCustomisationPolicy(new CodeIgniterRuleCustomisationPolicy()) + ->setFinder($finder) + ->setCacheFile('build/.php-cs-fixer.tests.cache') + ->setRules(array_merge($config->getRules(), $overrides)); diff --git a/.php-cs-fixer.user-guide.php b/.php-cs-fixer.user-guide.php new file mode 100644 index 000000000000..6b925ee8b0b7 --- /dev/null +++ b/.php-cs-fixer.user-guide.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +use PhpCsFixer\ConfigInterface; +use PhpCsFixer\Finder; + +/** @var ConfigInterface $config */ +$config = require __DIR__ . '/.php-cs-fixer.dist.php'; + +$finder = Finder::create() + ->files() + ->in([ + __DIR__ . '/user_guide_src/source', + ]) + ->notPath([ + 'ci3sample/', + 'database/query_builder/075.php', + 'libraries/sessions/016.php', + 'outgoing/response/031.php', + 'outgoing/response/032.php', + ]); + +$overrides = [ + 'echo_tag_syntax' => false, + 'header_comment' => false, + 'php_unit_internal_class' => false, + 'no_unused_imports' => false, + 'class_attributes_separation' => false, + 'fully_qualified_strict_types' => [ + 'import_symbols' => false, + 'leading_backslash_in_global_namespace' => true, + ], +]; + +return $config + ->setFinder($finder) + ->setCacheFile('build/.php-cs-fixer.user-guide.cache') + ->setRules(array_merge($config->getRules(), $overrides)); diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..208568025619 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,148 @@ +# CodeIgniter 4 — Repository Guidelines + +CodeIgniter 4 is a mature open-source PHP framework. Framework production +code lives in `system/`; its tests mirror that structure under +`tests/system/`. + +## Before making changes + +Consult the relevant sections of: + +- `contributing/pull_request.md` +- `contributing/internals.md` +- `contributing/styleguide.md` +- `tests/README.md` + +Follow existing code and tests in the affected component. Search for an +existing implementation before introducing a new abstraction or convention. + +## Target branch and compatibility policy + +Compatibility is judged against the pull request's base branch, not the +contributor's source or feature branch. When the base branch is unknown, +apply the stricter `develop` policy. + +### Base branch `develop` + +`develop` is the next patch-release line. Intentional backward-incompatible +changes are not allowed. + +- Preserve public and protected APIs, documented behavior, configuration + defaults, generated project files, exceptions, and observable side effects. +- Do not remove deprecated items or change public/protected signatures, + visibility, or interface/abstract requirements. +- A bug or security fix may correct faulty behavior but must preserve the + documented contract and ordinary valid usage. +- If a fix necessarily breaks compatibility, target the appropriate minor + branch instead. + +### Base branch `4.*` + +Minor-release lines (for example `4.9`). Small, deliberate compatibility +breaks may be accepted when they materially improve the framework or +complete the documented deprecation lifecycle. + +- Keep each break narrow, explicit, and justified; never an incidental + side effect of refactoring. +- Deprecated APIs may be removed no earlier than the second subsequent + minor release (deprecated in 4.6.x means removable in 4.8.0), with a + supported replacement. +- Every accepted break needs tests for the new behavior, a minor-version + changelog entry, and migration instructions in the upgrading guide. + +### All base branches + +- Production code must run on PHP 8.2. Do not require a newer PHP version, + even when a newer version is installed locally. +- Signatures, properties, constants, default values, exceptions, side + effects, configuration, and generated project files are all + compatibility-sensitive. +- Major-scale redesigns and ecosystem-wide migration requirements belong in + a major release, not a patch or minor release. +- Prefer the minimum useful abstraction and keep framework components as + independent as practical. +- Dependencies should be injectable. When a framework service is used as a + default, preserve the ability for applications to replace it. +- Do not add or update a Composer dependency unless the task explicitly + requires it and the change is justified. +- Do not modify code in `system/ThirdParty/`. + +## Coding standards + +- Do not add `declare(strict_types=1)` mechanically. Follow nearby code and + check the `DeclareStrictTypesRector` exclusions in `rector.php`; PHP files + use strict types unless excluded there. Treat the existing exclusions as + intentional compatibility constraints. Do not remove an exclusion without + dedicated justification and tests. +- Every new class property must have a native type declaration. Use the + most precise type supported by PHP 8.2; use `mixed` only when the + property intentionally accepts unrelated types. +- Do not add or change a type on an existing public or protected property + mechanically. Property types affect inheritance and must follow the + target-branch compatibility policy. +- Add PHPDoc only when it contributes information that native types cannot + express. Do not duplicate a parent or interface docblock. +- Never suppress a static-analysis error or update a baseline to make a + check pass. + +## FrankenPHP Worker Mode + +In Worker Mode one process serves many requests. The entry point's source +template is `system/Commands/Worker/Views/frankenphp-worker.php.tpl`; the +`worker:install` command publishes it as `public/frankenphp-worker.php`. + +- For every change affecting application bootstrap, request handling, + response sending, shutdown, superglobals, sessions, database or cache + connections, services, factories, events, toolbar state, static state, or + other request-lifecycle behavior, inspect the worker entry-point template + even when it is not part of the diff. +- Compare traditional per-process execution with worker execution, where + the framework boots once and handles multiple requests in the same + process. +- Classify mutable state as process-lifetime, intentionally persistent, or + request-specific. Request-specific state must be refreshed or reset for + every request and must not leak into the next request. +- Preserve the ordering requirements between per-request reconnection, + framework reset, superglobal refresh, application execution, and + post-request cleanup. +- Treat the template as the source of truth. Do not edit a generated + `public/frankenphp-worker.php` in place. +- When a release changes the template in a way existing installations must + receive, add an upgrading instruction telling Worker Mode users to + republish it with `php spark worker:install --force`. + +## Tests + +- Every bug fix should include a regression test that fails without the + fix. +- Test failure paths, exceptions, boundary conditions, and state cleanup, + not only the happy path. +- Prefer strict and dedicated PHPUnit assertions as described in the + project style guide. +- Do not weaken or remove an existing test unless the documented behavior + or specification changed. + +## Documentation + +- Public API, behavior, message, or default-value changes may require an + update to the user guide and changelog. +- Changes requiring user action, including configuration changes, may also + require an upgrading-guide entry. +- For a `4.*` target, every intentional compatibility break must be + documented in both the minor-version changelog and its upgrading guide. + +## Focused validation + +GitHub Actions is the authoritative source for full validation. Do not +reproduce the complete CI matrix locally. + +- Run only the narrowest PHPUnit test file or component that covers a code + change, for example + `vendor/bin/phpunit tests/system//Test.php`. +- Run a file-scoped analysis or formatting check only when it is quick and + directly relevant. +- Do not run the complete PHPUnit suite, `composer phpstan:check`, + `composer cs`, Psalm, Structarmed, or a repository-wide Rector analysis + by default. +- Report which focused checks were executed and which validation was left + to CI. diff --git a/CHANGELOG.md b/CHANGELOG.md index 74513d243589..82d3974606b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2976 +1,266 @@ # Changelog -## [v4.1.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.4) (2021-09-06) - -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](https://github.com/CodeIgniter/coding-standard) (based on PSR-12). - -*Note: Full changelog forthcoming.* - -## [v4.1.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.3) (2021-06-06) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.2...v4.1.3) - -**Fixed bugs:** - -- Bug: Error using SQLITE3 strftime in CodeIgniter 4.1.2 [\#4760](https://github.com/codeigniter4/CodeIgniter4/issues/4760) -- Bug: Caching something through cron, is not accessible in the web application [\#4751](https://github.com/codeigniter4/CodeIgniter4/issues/4751) -- Bug: SQLite Drop Column [\#4746](https://github.com/codeigniter4/CodeIgniter4/issues/4746) -- Bug: CURL Class - BaseURI options notworking [\#4713](https://github.com/codeigniter4/CodeIgniter4/issues/4713) -- Bug: autorouting [\#4711](https://github.com/codeigniter4/CodeIgniter4/issues/4711) -- Bug: curlrequest not using baseURI on localhost [\#4707](https://github.com/codeigniter4/CodeIgniter4/issues/4707) -- Bug: cli not working with cron [\#4699](https://github.com/codeigniter4/CodeIgniter4/issues/4699) - -**Closed issues:** - -- Bug: Class 'Locale' not found [\#4775](https://github.com/codeigniter4/CodeIgniter4/issues/4775) -- Bug: deprecated notice on CodeIgniter\HTTP\RequestInterface::getMethod\(\) [\#4717](https://github.com/codeigniter4/CodeIgniter4/issues/4717) -- Allow to join models between primary keys and foreign keys [\#4714](https://github.com/codeigniter4/CodeIgniter4/issues/4714) -- DateTime::\_\_construct\(\): Failed to parse time string \(\) at position 0 \(�\): Unexpected character [\#4708](https://github.com/codeigniter4/CodeIgniter4/issues/4708) -- Bug: Query Builder breaks with SQL function LENGTH\(\) and column name "row" [\#4687](https://github.com/codeigniter4/CodeIgniter4/issues/4687) - -**Merged pull requests:** - -- Expand Query named binds recognition [\#4769](https://github.com/codeigniter4/CodeIgniter4/pull/4769) ([paulbalandan](https://github.com/paulbalandan)) -- \[Rector\] Remove @var from class constant [\#4766](https://github.com/codeigniter4/CodeIgniter4/pull/4766) ([samsonasik](https://github.com/samsonasik)) -- Set WarningsReturnAsErrors = 0 before connection [\#4762](https://github.com/codeigniter4/CodeIgniter4/pull/4762) ([obelisk-services](https://github.com/obelisk-services)) -- \[Rector\] Apply Rector: VarConstantCommentRector [\#4759](https://github.com/codeigniter4/CodeIgniter4/pull/4759) ([samsonasik](https://github.com/samsonasik)) -- \[Autoloader\] include\_once is not needed on Autoloader::loadClass\(\) with no namespace [\#4756](https://github.com/codeigniter4/CodeIgniter4/pull/4756) ([samsonasik](https://github.com/samsonasik)) -- Fix imagemagick build [\#4755](https://github.com/codeigniter4/CodeIgniter4/pull/4755) ([michalsn](https://github.com/michalsn)) -- \[Rector\] Apply Rector: MoveVariableDeclarationNearReferenceRector [\#4752](https://github.com/codeigniter4/CodeIgniter4/pull/4752) ([samsonasik](https://github.com/samsonasik)) -- SQLite3 "nullable" [\#4749](https://github.com/codeigniter4/CodeIgniter4/pull/4749) ([MGatner](https://github.com/MGatner)) -- Remove $response variable at ControllerResponse::\_\_construct\(\) as never defined [\#4747](https://github.com/codeigniter4/CodeIgniter4/pull/4747) ([samsonasik](https://github.com/samsonasik)) -- Use variable for Config/Paths config to reduce repetitive definition [\#4745](https://github.com/codeigniter4/CodeIgniter4/pull/4745) ([samsonasik](https://github.com/samsonasik)) -- \[Rector\] Apply Rector : ListToArrayDestructRector [\#4743](https://github.com/codeigniter4/CodeIgniter4/pull/4743) ([samsonasik](https://github.com/samsonasik)) -- Add default TTL [\#4742](https://github.com/codeigniter4/CodeIgniter4/pull/4742) ([MGatner](https://github.com/MGatner)) -- update return sample of `dot array\_search\(\)` [\#4740](https://github.com/codeigniter4/CodeIgniter4/pull/4740) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Additional check for `$argv` variable when detecting CLI [\#4739](https://github.com/codeigniter4/CodeIgniter4/pull/4739) ([paulbalandan](https://github.com/paulbalandan)) -- Ensure variable declarations [\#4737](https://github.com/codeigniter4/CodeIgniter4/pull/4737) ([jeromegamez](https://github.com/jeromegamez)) -- Fix setting of value in Cookie's flag attributes [\#4736](https://github.com/codeigniter4/CodeIgniter4/pull/4736) ([paulbalandan](https://github.com/paulbalandan)) -- Add missing imports [\#4735](https://github.com/codeigniter4/CodeIgniter4/pull/4735) ([jeromegamez](https://github.com/jeromegamez)) -- Add environment spark command [\#4734](https://github.com/codeigniter4/CodeIgniter4/pull/4734) ([paulbalandan](https://github.com/paulbalandan)) -- Remove explicit condition that is always true [\#4731](https://github.com/codeigniter4/CodeIgniter4/pull/4731) ([jeromegamez](https://github.com/jeromegamez)) -- Deduplicate code [\#4730](https://github.com/codeigniter4/CodeIgniter4/pull/4730) ([jeromegamez](https://github.com/jeromegamez)) -- Replace `isset\(\)` with the `??` null coalesce operator [\#4729](https://github.com/codeigniter4/CodeIgniter4/pull/4729) ([jeromegamez](https://github.com/jeromegamez)) -- Remove unused imports [\#4728](https://github.com/codeigniter4/CodeIgniter4/pull/4728) ([jeromegamez](https://github.com/jeromegamez)) -- Fix truncated SCRIPT\_NAME [\#4726](https://github.com/codeigniter4/CodeIgniter4/pull/4726) ([MGatner](https://github.com/MGatner)) -- Expand CLI detection [\#4725](https://github.com/codeigniter4/CodeIgniter4/pull/4725) ([paulbalandan](https://github.com/paulbalandan)) -- \[Rector\] Add custom Rector Rule: RemoveErrorSuppressInTryCatchStmtsRector rector rule [\#4724](https://github.com/codeigniter4/CodeIgniter4/pull/4724) ([samsonasik](https://github.com/samsonasik)) -- Test with MySQL 8 [\#4721](https://github.com/codeigniter4/CodeIgniter4/pull/4721) ([jeromegamez](https://github.com/jeromegamez)) -- Replace URI string casts [\#4716](https://github.com/codeigniter4/CodeIgniter4/pull/4716) ([MGatner](https://github.com/MGatner)) -- Format URI directly [\#4715](https://github.com/codeigniter4/CodeIgniter4/pull/4715) ([MGatner](https://github.com/MGatner)) -- Additional File functions [\#4712](https://github.com/codeigniter4/CodeIgniter4/pull/4712) ([MGatner](https://github.com/MGatner)) -- Remove unused private rowOffset property in Database/SQLSRV/Result.php [\#4709](https://github.com/codeigniter4/CodeIgniter4/pull/4709) ([samsonasik](https://github.com/samsonasik)) -- Check for configured instead of hard-coded database in DbUtilsTest [\#4705](https://github.com/codeigniter4/CodeIgniter4/pull/4705) ([jeromegamez](https://github.com/jeromegamez)) -- Revert UG margins [\#4704](https://github.com/codeigniter4/CodeIgniter4/pull/4704) ([MGatner](https://github.com/MGatner)) -- Create .git/hooks directory if not already present [\#4703](https://github.com/codeigniter4/CodeIgniter4/pull/4703) ([jeromegamez](https://github.com/jeromegamez)) -- Annotate specifically designed slow tests with custom limits [\#4698](https://github.com/codeigniter4/CodeIgniter4/pull/4698) ([paulbalandan](https://github.com/paulbalandan)) -- Cache robustness [\#4697](https://github.com/codeigniter4/CodeIgniter4/pull/4697) ([MGatner](https://github.com/MGatner)) - -## [v4.1.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.2) (2021-05-18) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.1...v4.1.2) - -**Implemented enhancements:** - -- New HTTP classes, ``Cookie`` and ``CookieStore``, for abstracting web cookies. -- New ``assertRedirectTo()`` assertion available for HTTP tests. -- New logger handler, ``ErrorlogHandler``, that writes to ``error_log()``. -- Entity. Added custom type casting functionality. -- New option in routing. The ``priority`` option lower the priority of specific route processing. -- The ``Autoloader`` class can now load files which do not contain PHP classes. The list of `non-class` files will be listed in the ``$files`` property of ``Config\Autoload`` class. - -**Deprecations:** - -- Deprecated ``Codeigniter\View\View::$currentSection`` property. -- Language strings and exceptions on invalid cookie samesite are deprecated for the ``CookieException``'s own exception message. -- Deprecated `CodeIgniter\Entity` in favor of `CodeIgniter\Entity\Entity` -- Deprecated cookie-related properties of ``Response`` in order to use the ``Cookie`` class. -- Deprecated cookie-related properties of ``Security`` in order to use the ``Cookie`` class. -- Deprecated cookie-related properties of ``Session`` in order to use the ``Cookie`` class. -- Deprecated ``Security::isExpired()`` to use the ``Cookie``'s internal expires status. -- Deprecated ``CIDatabaseTestCase`` to use the ``DatabaseTestTrait`` instead. -- Deprecated ``FeatureTestCase`` to use the ``FeatureTestTrait`` instead. -- Deprecated ``ControllerTester`` to use the ``ControllerTestTrait`` instead. -- Consolidated and deprecated ``ControllerResponse`` and ``FeatureResponse`` in favor of ``TestResponse``. -- Deprecated ``Time::instance()``, use ``Time::createFromInstance()`` instead (now accepts ``DateTimeInterface``). -- Deprecated ``IncomingRequest::removeRelativeDirectory()``, use ``URI::removeDotSegments()`` instead -- Deprecated ``\API\ResponseTrait::failValidationError`` to use ``\API\ResponseTrait::failValidationErrors`` instead - -**Fixed bugs:** - -- Bug: NULL Fields definition not working for MSQLi Forge in Migration due property $\_null and $null names difference [\#4693](https://github.com/codeigniter4/CodeIgniter4/issues/4693) -- Bug: Missing Cookie Config [\#4619](https://github.com/codeigniter4/CodeIgniter4/issues/4619) -- Bug: [\#4610](https://github.com/codeigniter4/CodeIgniter4/issues/4610) -- Bug: Customized Validation language does not take effect [\#4597](https://github.com/codeigniter4/CodeIgniter4/issues/4597) -- Bug: colon issue in query binding [\#4595](https://github.com/codeigniter4/CodeIgniter4/issues/4595) -- Bug: set\_checkbox\(\) default value not working [\#4582](https://github.com/codeigniter4/CodeIgniter4/issues/4582) -- Bug: Request & Response objects stored multiple times [\#4580](https://github.com/codeigniter4/CodeIgniter4/issues/4580) -- Bug: Class information on output is missing during migrate:rollback command [\#4579](https://github.com/codeigniter4/CodeIgniter4/issues/4579) -- Bug: Cookie path replaced with system's PATH env variable [\#4559](https://github.com/codeigniter4/CodeIgniter4/issues/4559) -- Bug: Validation::withRequest\(\) method does not receive data. [\#4552](https://github.com/codeigniter4/CodeIgniter4/issues/4552) -- `esc` and `nl2br` combo gives nasty HTML error output [\#4533](https://github.com/codeigniter4/CodeIgniter4/issues/4533) -- Bug: typo error when creating a model using php spark make:model [\#4525](https://github.com/codeigniter4/CodeIgniter4/issues/4525) -- Bug: if\_exist not working with ".\*" notation [\#4521](https://github.com/codeigniter4/CodeIgniter4/issues/4521) -- Bug: Query::matchSimpleBinds index problem only toolbar. [\#4518](https://github.com/codeigniter4/CodeIgniter4/issues/4518) -- Bug: Unable to use debugger toolbar on a live server [\#4516](https://github.com/codeigniter4/CodeIgniter4/issues/4516) -- Missing config options and config options repetition [\#4504](https://github.com/codeigniter4/CodeIgniter4/issues/4504) -- Bug: db:create command should create database even database not exists yet, and defined in .env [\#4498](https://github.com/codeigniter4/CodeIgniter4/issues/4498) -- Bug: Differences in file names created with CLI command [\#4495](https://github.com/codeigniter4/CodeIgniter4/issues/4495) -- Bug: Session removeTempdata\(\) method not accepting arrays. [\#4490](https://github.com/codeigniter4/CodeIgniter4/issues/4490) -- Bug: Session remove\(\) method not removing tempdata sessions. [\#4489](https://github.com/codeigniter4/CodeIgniter4/issues/4489) -- Bug: Session getFlashdata\(\) not support for dot notation. [\#4488](https://github.com/codeigniter4/CodeIgniter4/issues/4488) -- Bug: New Service replacement fails at service provider precedence on core factory implementations [\#4483](https://github.com/codeigniter4/CodeIgniter4/issues/4483) -- Bug: Filter is not work ! [\#4482](https://github.com/codeigniter4/CodeIgniter4/issues/4482) -- Bug: PHPStorm anlysis fault [\#4474](https://github.com/codeigniter4/CodeIgniter4/issues/4474) -- Bug: apache mod\_userdir causes weird URL segment duplication [\#4471](https://github.com/codeigniter4/CodeIgniter4/issues/4471) -- Postgre Forge doesn't use schema in creating tables [\#4469](https://github.com/codeigniter4/CodeIgniter4/issues/4469) -- Bug: UG QueryBuilder::from\(\) wrong SQL example. [\#4464](https://github.com/codeigniter4/CodeIgniter4/issues/4464) -- Bug: results on getX\(\) not equal [\#4452](https://github.com/codeigniter4/CodeIgniter4/issues/4452) -- Bug: Queries with LOWER\( throwing errors [\#4443](https://github.com/codeigniter4/CodeIgniter4/issues/4443) -- Bug: RouteCollection::getHTTPVerb\(\) can return null [\#4435](https://github.com/codeigniter4/CodeIgniter4/issues/4435) -- Bug: can't run `spark migrate` on CI server [\#4428](https://github.com/codeigniter4/CodeIgniter4/issues/4428) -- Bug: URI Routing Placeholders [\#4421](https://github.com/codeigniter4/CodeIgniter4/issues/4421) -- Bug: Third Flags needs default [\#4411](https://github.com/codeigniter4/CodeIgniter4/issues/4411) -- Bug: another Flags needs default [\#4410](https://github.com/codeigniter4/CodeIgniter4/issues/4410) -- Bug: Flags needs default value [\#4409](https://github.com/codeigniter4/CodeIgniter4/issues/4409) -- Bug: log\_message passed object [\#4407](https://github.com/codeigniter4/CodeIgniter4/issues/4407) -- Bug: Model creation error [\#4393](https://github.com/codeigniter4/CodeIgniter4/issues/4393) -- Bug: If the file name contains "app", "php spark make: migration" will not create it successfully. [\#4383](https://github.com/codeigniter4/CodeIgniter4/issues/4383) -- Bug: IncomingRequest.php getVar\(\) [\#4381](https://github.com/codeigniter4/CodeIgniter4/issues/4381) -- Bug: Minimum PHP Version Discrepancy [\#4361](https://github.com/codeigniter4/CodeIgniter4/issues/4361) -- Bug: insertBatch generates an incorrect SQL query if the fields differ only in number at the end [\#4345](https://github.com/codeigniter4/CodeIgniter4/issues/4345) -- Bug: Database/Live tests fail [\#4336](https://github.com/codeigniter4/CodeIgniter4/issues/4336) -- Bug: red line on model by setPrefix & prefixTable [\#4329](https://github.com/codeigniter4/CodeIgniter4/issues/4329) -- Bug: $model-\>errors\(\) produce output when no error [\#4323](https://github.com/codeigniter4/CodeIgniter4/issues/4323) -- Bug: Can't Rewrite System Validation Messages [\#4318](https://github.com/codeigniter4/CodeIgniter4/issues/4318) -- Bug: "useSoftDelete" for model files generated by `phpspark` [\#4316](https://github.com/codeigniter4/CodeIgniter4/issues/4316) -- Bug: require the unused namespace [\#4309](https://github.com/codeigniter4/CodeIgniter4/issues/4309) -- Bug: FeatureTest cannot assert Status\(404\) [\#4306](https://github.com/codeigniter4/CodeIgniter4/issues/4306) -- Bug: BaseBuilder-\>\_insert [\#4302](https://github.com/codeigniter4/CodeIgniter4/issues/4302) -- Bug: previous\_url\(\) contains current URL after reloading a page. [\#4299](https://github.com/codeigniter4/CodeIgniter4/issues/4299) -- Bug: Cannot add route to controller in filename with dash/hyphen [\#4294](https://github.com/codeigniter4/CodeIgniter4/issues/4294) -- Bug: FeatureTest dies when throws RedirectException/cached page [\#4288](https://github.com/codeigniter4/CodeIgniter4/issues/4288) -- Bug: /test.php show home page [\#4263](https://github.com/codeigniter4/CodeIgniter4/issues/4263) -- Bug: Fabricator::fake\(\) function is breaking when it returns an array [\#4261](https://github.com/codeigniter4/CodeIgniter4/issues/4261) -- Bug: Session issue with CI Environment set to Testing \(CI4\) [\#4248](https://github.com/codeigniter4/CodeIgniter4/issues/4248) -- Bug: Wrong HTML code in output of "form\_input" helper function [\#4235](https://github.com/codeigniter4/CodeIgniter4/issues/4235) -- make:scaffold input information is missing [\#4230](https://github.com/codeigniter4/CodeIgniter4/issues/4230) -- Bug: CodeIgniter 4.1.1 - csrf token is always regenerated [\#4224](https://github.com/codeigniter4/CodeIgniter4/issues/4224) -- Bug: getFileMultiple expects an "0" index but string is given [\#4221](https://github.com/codeigniter4/CodeIgniter4/issues/4221) -- Bug: cannot resolve Services::xxx\(\) [\#4220](https://github.com/codeigniter4/CodeIgniter4/issues/4220) -- Bug: tfoot\_open / tfoot\_close have no default when using custom table template [\#4219](https://github.com/codeigniter4/CodeIgniter4/issues/4219) -- Bug: Spark PHP version [\#4213](https://github.com/codeigniter4/CodeIgniter4/issues/4213) -- Bug: Soft deletes and model validation when unique [\#4162](https://github.com/codeigniter4/CodeIgniter4/issues/4162) -- Bug: Debug Toolbar - Memory Leak - Allocation Exception [\#4137](https://github.com/codeigniter4/CodeIgniter4/issues/4137) -- current\_url\(\) global method returning URLs without the index.php part. [\#4116](https://github.com/codeigniter4/CodeIgniter4/issues/4116) -- Bug: appstarter HealthTest::testBaseUrlHasBeenSet fails [\#3977](https://github.com/codeigniter4/CodeIgniter4/issues/3977) -- Bug: Time::createFromTimestamp\(\) uses default timezone, not UTC for timestamp [\#3951](https://github.com/codeigniter4/CodeIgniter4/issues/3951) -- Bug: Unexpected filter behavior [\#3874](https://github.com/codeigniter4/CodeIgniter4/issues/3874) -- Bug: Double initializing of class [\#3855](https://github.com/codeigniter4/CodeIgniter4/issues/3855) -- Bug: Registrars take priority over .env [\#3845](https://github.com/codeigniter4/CodeIgniter4/issues/3845) -- Bug: SQLite3 NOT NULL prevents inserts [\#3599](https://github.com/codeigniter4/CodeIgniter4/issues/3599) -- Bug: Model doesn't reset errors in FeatureTestCase [\#3578](https://github.com/codeigniter4/CodeIgniter4/issues/3578) -- Bug: Problem in "/system/Database/Query.php" function "compileBinds\(\)" [\#3566](https://github.com/codeigniter4/CodeIgniter4/issues/3566) -- Bug: Exceptions cause risky Feature Tests [\#3114](https://github.com/codeigniter4/CodeIgniter4/issues/3114) -- Bug: current\_url\(\) loses subdomain [\#3004](https://github.com/codeigniter4/CodeIgniter4/issues/3004) - -**Closed issues:** - -- mysqli\_sql\_exception \#2002 [\#4640](https://github.com/codeigniter4/CodeIgniter4/issues/4640) -- intl - Name missing exception [\#4636](https://github.com/codeigniter4/CodeIgniter4/issues/4636) -- HUGE BUG: update\(\) function updates all records if id is empty [\#4617](https://github.com/codeigniter4/CodeIgniter4/issues/4617) -- Bug: Validation rule "matches" doesn't work [\#4615](https://github.com/codeigniter4/CodeIgniter4/issues/4615) -- Bug: chmod 777 writable/cache fixed codeignitor install for me [\#4598](https://github.com/codeigniter4/CodeIgniter4/issues/4598) -- Model-\>where method does not exist [\#4583](https://github.com/codeigniter4/CodeIgniter4/issues/4583) -- Transactions between two databases [\#4578](https://github.com/codeigniter4/CodeIgniter4/issues/4578) -- Bug: Mysql connection issue with MYSQLI\_CLIENT\_SSL\_DONT\_VERIFY\_SERVER\_CERT [\#4558](https://github.com/codeigniter4/CodeIgniter4/issues/4558) -- Release cycle [\#4526](https://github.com/codeigniter4/CodeIgniter4/issues/4526) -- Call to a member function setContentType\(\) on null - Responsetrait [\#4524](https://github.com/codeigniter4/CodeIgniter4/issues/4524) -- Bug: mock single\_service [\#4515](https://github.com/codeigniter4/CodeIgniter4/issues/4515) -- Bug: failed to open stream [\#4514](https://github.com/codeigniter4/CodeIgniter4/issues/4514) -- Array Validation Fails [\#4510](https://github.com/codeigniter4/CodeIgniter4/issues/4510) -- Bug: return $this-\>failValidationError\($validation-\>getErrors\(\)\) Has Invalid Signature [\#4506](https://github.com/codeigniter4/CodeIgniter4/issues/4506) -- Bug: Ok The Model ERRORS came right back with the newest build again! [\#4491](https://github.com/codeigniter4/CodeIgniter4/issues/4491) -- Bug: Composer install loads require-dev when I require another package [\#4477](https://github.com/codeigniter4/CodeIgniter4/issues/4477) -- Logger [\#4460](https://github.com/codeigniter4/CodeIgniter4/issues/4460) -- Bug: Improve creation of scaffolds with the CLI [\#4441](https://github.com/codeigniter4/CodeIgniter4/issues/4441) -- Request: Feature Test Optimization [\#4438](https://github.com/codeigniter4/CodeIgniter4/issues/4438) -- request.getVar not populated with GET parameters | unexpected behavior [\#4418](https://github.com/codeigniter4/CodeIgniter4/issues/4418) -- Running via CLI - Only Default Controller works [\#4415](https://github.com/codeigniter4/CodeIgniter4/issues/4415) -- Parser content typehint \[strict\_types=1\] [\#4412](https://github.com/codeigniter4/CodeIgniter4/issues/4412) -- Toolbar::setFiles\(\) requires int \[strict\_types=1\] [\#4408](https://github.com/codeigniter4/CodeIgniter4/issues/4408) -- FeatureTest currently supports file testing? [\#4405](https://github.com/codeigniter4/CodeIgniter4/issues/4405) -- Bug: set404Override now working in group rotes [\#4400](https://github.com/codeigniter4/CodeIgniter4/issues/4400) -- Dynamic URL [\#4394](https://github.com/codeigniter4/CodeIgniter4/issues/4394) -- ErrorException preg\_replace\_callback\(\): Unknown modifier '{' SYSTEMPATH/View/Parser.php at line 584 [\#4367](https://github.com/codeigniter4/CodeIgniter4/issues/4367) -- Feature: In HTTP Feature Testing, delivering in body in application/json format [\#4362](https://github.com/codeigniter4/CodeIgniter4/issues/4362) -- \[Dev\] Database Live Tests should depart from using deprecated CIDatabaseTestCase [\#4351](https://github.com/codeigniter4/CodeIgniter4/issues/4351) -- Bug: Migration in module \(different namespace\) do not find migrations [\#4348](https://github.com/codeigniter4/CodeIgniter4/issues/4348) -- Bug: getVar does not look at $\_SESSION as documentation suggests [\#4284](https://github.com/codeigniter4/CodeIgniter4/issues/4284) -- QBSelect, QBFrom, other properties cannot be accessed, modified from the model. [\#4255](https://github.com/codeigniter4/CodeIgniter4/issues/4255) -- Dev: Restrictions on trait "ResponseTrait" [\#4238](https://github.com/codeigniter4/CodeIgniter4/issues/4238) -- ResponseTrait trait \> Can the description support array? [\#4237](https://github.com/codeigniter4/CodeIgniter4/issues/4237) -- Feature: add old data in afterUpdate model event [\#4234](https://github.com/codeigniter4/CodeIgniter4/issues/4234) -- Dev: [\#4233](https://github.com/codeigniter4/CodeIgniter4/issues/4233) -- Cache unable to write to /var/www/html/ci4test/writable/cache/ [\#4227](https://github.com/codeigniter4/CodeIgniter4/issues/4227) -- Documentation: multiple databases setup in the ENV also need to be setup in the database config file [\#4218](https://github.com/codeigniter4/CodeIgniter4/issues/4218) -- Documentation: $this-\>request-\>setLocale\(\) is missing in documentation [\#4091](https://github.com/codeigniter4/CodeIgniter4/issues/4091) -- vars in .env sometimes returns null [\#3992](https://github.com/codeigniter4/CodeIgniter4/issues/3992) -- parseRequestURI dose not override globals\['server'\] ? [\#3976](https://github.com/codeigniter4/CodeIgniter4/issues/3976) -- Feature: Spark header Suppression [\#3918](https://github.com/codeigniter4/CodeIgniter4/issues/3918) -- Feature: AJAX filters don't work [\#2314](https://github.com/codeigniter4/CodeIgniter4/issues/2314) -- Request: Bulk route filters with parameters [\#2078](https://github.com/codeigniter4/CodeIgniter4/issues/2078) -- Need a global way to set config values dynamically [\#1661](https://github.com/codeigniter4/CodeIgniter4/issues/1661) -- Feature Request : support the db config instead of .env while the core is initialzed [\#1618](https://github.com/codeigniter4/CodeIgniter4/issues/1618) -- TODO Database BaseConnection needs better connections [\#1253](https://github.com/codeigniter4/CodeIgniter4/issues/1253) - -**Merged pull requests:** - -- Fix nullable type not showing in SQL string [\#4696](https://github.com/codeigniter4/CodeIgniter4/pull/4696) ([paulbalandan](https://github.com/paulbalandan)) -- Add reference to cache repo [\#4694](https://github.com/codeigniter4/CodeIgniter4/pull/4694) ([MGatner](https://github.com/MGatner)) -- Allow CI Environments [\#4692](https://github.com/codeigniter4/CodeIgniter4/pull/4692) ([MGatner](https://github.com/MGatner)) -- Add URI cast [\#4691](https://github.com/codeigniter4/CodeIgniter4/pull/4691) ([MGatner](https://github.com/MGatner)) -- MockCache::getCacheInfo\(\) [\#4689](https://github.com/codeigniter4/CodeIgniter4/pull/4689) ([MGatner](https://github.com/MGatner)) -- Remove Psr\Cache [\#4688](https://github.com/codeigniter4/CodeIgniter4/pull/4688) ([MGatner](https://github.com/MGatner)) -- Spacing issues [\#4686](https://github.com/codeigniter4/CodeIgniter4/pull/4686) ([MGatner](https://github.com/MGatner)) -- \[Rector\] Update rector 0.11.2 and phpstan 0.12.86 [\#4685](https://github.com/codeigniter4/CodeIgniter4/pull/4685) ([samsonasik](https://github.com/samsonasik)) -- Optimize CommandRunner and Commands [\#4683](https://github.com/codeigniter4/CodeIgniter4/pull/4683) ([paulbalandan](https://github.com/paulbalandan)) -- Revert Actions minor version [\#4682](https://github.com/codeigniter4/CodeIgniter4/pull/4682) ([MGatner](https://github.com/MGatner)) -- Revert Actions minor versioning [\#4681](https://github.com/codeigniter4/CodeIgniter4/pull/4681) ([MGatner](https://github.com/MGatner)) -- Bump shivammathur/setup-php from 2 to 2.11.0 [\#4679](https://github.com/codeigniter4/CodeIgniter4/pull/4679) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump actions/checkout from 2 to 2.3.4 [\#4678](https://github.com/codeigniter4/CodeIgniter4/pull/4678) ([dependabot[bot]](https://github.com/apps/dependabot)) -- \[Rector\] Update rector to 0.10.22, remove symplify/composer-json-manipulator [\#4677](https://github.com/codeigniter4/CodeIgniter4/pull/4677) ([samsonasik](https://github.com/samsonasik)) -- URL Functions [\#4675](https://github.com/codeigniter4/CodeIgniter4/pull/4675) ([MGatner](https://github.com/MGatner)) -- Remove unused imports [\#4674](https://github.com/codeigniter4/CodeIgniter4/pull/4674) ([paulbalandan](https://github.com/paulbalandan)) -- Split URL Helper tests [\#4672](https://github.com/codeigniter4/CodeIgniter4/pull/4672) ([MGatner](https://github.com/MGatner)) -- \[Rector\] Apply Rector: RemoveUnusedPrivatePropertyRector [\#4671](https://github.com/codeigniter4/CodeIgniter4/pull/4671) ([samsonasik](https://github.com/samsonasik)) -- \[UG\] update line number for "managing apps" [\#4670](https://github.com/codeigniter4/CodeIgniter4/pull/4670) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Add setLocale to UG [\#4669](https://github.com/codeigniter4/CodeIgniter4/pull/4669) ([MGatner](https://github.com/MGatner)) -- UTC Time from timestamp [\#4668](https://github.com/codeigniter4/CodeIgniter4/pull/4668) ([MGatner](https://github.com/MGatner)) -- PSR: Cache [\#4667](https://github.com/codeigniter4/CodeIgniter4/pull/4667) ([MGatner](https://github.com/MGatner)) -- Limit cache filenames [\#4666](https://github.com/codeigniter4/CodeIgniter4/pull/4666) ([MGatner](https://github.com/MGatner)) -- Use descriptive failure message for `assertLogged` [\#4665](https://github.com/codeigniter4/CodeIgniter4/pull/4665) ([paulbalandan](https://github.com/paulbalandan)) -- \[Rector\] Use $containerConfigurator-\>import\(\) instead of "sets" Option [\#4664](https://github.com/codeigniter4/CodeIgniter4/pull/4664) ([samsonasik](https://github.com/samsonasik)) -- Update rector/rector requirement from 0.10.19 to 0.10.21 [\#4663](https://github.com/codeigniter4/CodeIgniter4/pull/4663) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Spark header suppression [\#4661](https://github.com/codeigniter4/CodeIgniter4/pull/4661) ([MGatner](https://github.com/MGatner)) -- Registrar and .env priority [\#4659](https://github.com/codeigniter4/CodeIgniter4/pull/4659) ([MGatner](https://github.com/MGatner)) -- Reset Single Service [\#4657](https://github.com/codeigniter4/CodeIgniter4/pull/4657) ([MGatner](https://github.com/MGatner)) -- Unify migration message format for `migrate` and `migrate:rollback` [\#4656](https://github.com/codeigniter4/CodeIgniter4/pull/4656) ([paulbalandan](https://github.com/paulbalandan)) -- \[Scripts\] Make sure bash script still works in Windows [\#4655](https://github.com/codeigniter4/CodeIgniter4/pull/4655) ([paulbalandan](https://github.com/paulbalandan)) -- change instance\(\) to createFromInstance\(\) [\#4654](https://github.com/codeigniter4/CodeIgniter4/pull/4654) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Add branch alias for develop branch [\#4652](https://github.com/codeigniter4/CodeIgniter4/pull/4652) ([paulbalandan](https://github.com/paulbalandan)) -- Refactor URI detection [\#4651](https://github.com/codeigniter4/CodeIgniter4/pull/4651) ([MGatner](https://github.com/MGatner)) -- \[Scipts\] Ensure admin/setup.sh exists before run bash admin/setup.sh [\#4650](https://github.com/codeigniter4/CodeIgniter4/pull/4650) ([samsonasik](https://github.com/samsonasik)) -- Update rector/rector requirement from 0.10.17 to 0.10.19 [\#4649](https://github.com/codeigniter4/CodeIgniter4/pull/4649) ([dependabot[bot]](https://github.com/apps/dependabot)) -- \[ci skip\] Remove ajax filter reference from docs. Fixes \#2314 [\#4648](https://github.com/codeigniter4/CodeIgniter4/pull/4648) ([lonnieezell](https://github.com/lonnieezell)) -- Internal URI handling [\#4646](https://github.com/codeigniter4/CodeIgniter4/pull/4646) ([MGatner](https://github.com/MGatner)) -- URI::removeDotSegments\(\) [\#4644](https://github.com/codeigniter4/CodeIgniter4/pull/4644) ([MGatner](https://github.com/MGatner)) -- \[Rector\] Pin "nikic/php-parser": "4.10.4" [\#4642](https://github.com/codeigniter4/CodeIgniter4/pull/4642) ([samsonasik](https://github.com/samsonasik)) -- Update to psr/log v1.1.4 [\#4641](https://github.com/codeigniter4/CodeIgniter4/pull/4641) ([paulbalandan](https://github.com/paulbalandan)) -- Update rector/rector requirement from 0.10.15 to 0.10.17 [\#4639](https://github.com/codeigniter4/CodeIgniter4/pull/4639) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Update MockCache [\#4638](https://github.com/codeigniter4/CodeIgniter4/pull/4638) ([MGatner](https://github.com/MGatner)) -- Cache Key Validation [\#4637](https://github.com/codeigniter4/CodeIgniter4/pull/4637) ([MGatner](https://github.com/MGatner)) -- \[Cache\] Allow covariant returns and optimize code [\#4635](https://github.com/codeigniter4/CodeIgniter4/pull/4635) ([paulbalandan](https://github.com/paulbalandan)) -- Refactor ComposerScripts [\#4634](https://github.com/codeigniter4/CodeIgniter4/pull/4634) ([paulbalandan](https://github.com/paulbalandan)) -- Expand Time for interface [\#4633](https://github.com/codeigniter4/CodeIgniter4/pull/4633) ([MGatner](https://github.com/MGatner)) -- Patch Log code [\#4631](https://github.com/codeigniter4/CodeIgniter4/pull/4631) ([MGatner](https://github.com/MGatner)) -- Cache Returns Types [\#4630](https://github.com/codeigniter4/CodeIgniter4/pull/4630) ([MGatner](https://github.com/MGatner)) -- Cache getMetadata\(\) Format [\#4629](https://github.com/codeigniter4/CodeIgniter4/pull/4629) ([MGatner](https://github.com/MGatner)) -- fix\(cache\): add check for redis empty results in deleteMatching [\#4628](https://github.com/codeigniter4/CodeIgniter4/pull/4628) ([yassinedoghri](https://github.com/yassinedoghri)) -- Update rector/rector requirement from 0.10.12 to 0.10.15 [\#4627](https://github.com/codeigniter4/CodeIgniter4/pull/4627) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Add fallback for Config\Cookie [\#4625](https://github.com/codeigniter4/CodeIgniter4/pull/4625) ([paulbalandan](https://github.com/paulbalandan)) -- \[Test\] Use @codeCoverageIgnore for deprecated class/method [\#4623](https://github.com/codeigniter4/CodeIgniter4/pull/4623) ([samsonasik](https://github.com/samsonasik)) -- Fix: Nested sections rendering [\#4622](https://github.com/codeigniter4/CodeIgniter4/pull/4622) ([iRedds](https://github.com/iRedds)) -- Update rector/rector requirement from 0.10.11 to 0.10.12 [\#4621](https://github.com/codeigniter4/CodeIgniter4/pull/4621) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Update phpstan/phpstan requirement from 0.12.84 to 0.12.85 [\#4620](https://github.com/codeigniter4/CodeIgniter4/pull/4620) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump actions/github-script from v4.0.1 to v4.0.2 [\#4614](https://github.com/codeigniter4/CodeIgniter4/pull/4614) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Update rector/rector requirement from 0.10.9 to 0.10.11 [\#4613](https://github.com/codeigniter4/CodeIgniter4/pull/4613) ([dependabot[bot]](https://github.com/apps/dependabot)) -- \[Rector\] Refactor UnderscoreToCamelCaseVariableNameRector with latest Rector compatible code [\#4612](https://github.com/codeigniter4/CodeIgniter4/pull/4612) ([samsonasik](https://github.com/samsonasik)) -- \[Feat\]\[Autoloader\] Allow autoloading non-class files [\#4611](https://github.com/codeigniter4/CodeIgniter4/pull/4611) ([paulbalandan](https://github.com/paulbalandan)) -- failValidationError can take an array of errors [\#4609](https://github.com/codeigniter4/CodeIgniter4/pull/4609) ([caswell-wc](https://github.com/caswell-wc)) -- assertJsonFragment fails gracefully with invalid json [\#4608](https://github.com/codeigniter4/CodeIgniter4/pull/4608) ([caswell-wc](https://github.com/caswell-wc)) -- Non-persistent fake [\#4607](https://github.com/codeigniter4/CodeIgniter4/pull/4607) ([caswell-wc](https://github.com/caswell-wc)) -- Fix validation of array data [\#4606](https://github.com/codeigniter4/CodeIgniter4/pull/4606) ([paulbalandan](https://github.com/paulbalandan)) -- Use realpath\(\) to fix app prioritization of validation messages [\#4605](https://github.com/codeigniter4/CodeIgniter4/pull/4605) ([paulbalandan](https://github.com/paulbalandan)) -- Optimizations for Autoloader [\#4604](https://github.com/codeigniter4/CodeIgniter4/pull/4604) ([paulbalandan](https://github.com/paulbalandan)) -- format style [\#4603](https://github.com/codeigniter4/CodeIgniter4/pull/4603) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- whitespaces [\#4602](https://github.com/codeigniter4/CodeIgniter4/pull/4602) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- \[Rector\] Apply Full PHP 7.3 Rector Set List \(Skip JsonThrowOnErrorRector & StringifyStrNeedlesRector\) [\#4601](https://github.com/codeigniter4/CodeIgniter4/pull/4601) ([samsonasik](https://github.com/samsonasik)) -- Bump actions/github-script from v3 to v4.0.1 [\#4599](https://github.com/codeigniter4/CodeIgniter4/pull/4599) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Simplify Cookie Class [\#4596](https://github.com/codeigniter4/CodeIgniter4/pull/4596) ([mostafakhudair](https://github.com/mostafakhudair)) -- Fix service methods …$params type [\#4594](https://github.com/codeigniter4/CodeIgniter4/pull/4594) ([najdanovicivan](https://github.com/najdanovicivan)) -- Fix new service replacement service provider precedence on core factory implementations [\#4593](https://github.com/codeigniter4/CodeIgniter4/pull/4593) ([element-code](https://github.com/element-code)) -- Update rector/rector requirement from 0.10.6 to 0.10.9 [\#4592](https://github.com/codeigniter4/CodeIgniter4/pull/4592) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Debug/Toolbar - Memory issue fix [\#4590](https://github.com/codeigniter4/CodeIgniter4/pull/4590) ([najdanovicivan](https://github.com/najdanovicivan)) -- BaseModel - Add public getIdValue\(\) method [\#4589](https://github.com/codeigniter4/CodeIgniter4/pull/4589) ([najdanovicivan](https://github.com/najdanovicivan)) -- Feature: Escaping array dot notation [\#4588](https://github.com/codeigniter4/CodeIgniter4/pull/4588) ([iRedds](https://github.com/iRedds)) -- Update phpstan/phpstan requirement from 0.12.83 to 0.12.84 [\#4587](https://github.com/codeigniter4/CodeIgniter4/pull/4587) ([dependabot[bot]](https://github.com/apps/dependabot)) -- extra \ in tabs area [\#4586](https://github.com/codeigniter4/CodeIgniter4/pull/4586) ([jbrahy](https://github.com/jbrahy)) -- user\_guide update: remove whitespaces, convert tabs to spaces & adjust the over-extended lines [\#4585](https://github.com/codeigniter4/CodeIgniter4/pull/4585) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Fix: BaseModel. Removed duplicate code. [\#4581](https://github.com/codeigniter4/CodeIgniter4/pull/4581) ([iRedds](https://github.com/iRedds)) -- Change Entity Namespace [\#4577](https://github.com/codeigniter4/CodeIgniter4/pull/4577) ([mostafakhudair](https://github.com/mostafakhudair)) -- \[Rector\] Remove RemoveDefaultArgumentValueRector [\#4576](https://github.com/codeigniter4/CodeIgniter4/pull/4576) ([samsonasik](https://github.com/samsonasik)) -- \[Rector\] Add Comment for reason RemoveDefaultArgumentValueRector copied to utils/Rector [\#4575](https://github.com/codeigniter4/CodeIgniter4/pull/4575) ([samsonasik](https://github.com/samsonasik)) -- \[Rector\] Copy RemoveDefaultArgumentValueRector to utils/Rector [\#4574](https://github.com/codeigniter4/CodeIgniter4/pull/4574) ([samsonasik](https://github.com/samsonasik)) -- Fix: Validation::withRequest\(\) with Content-Type: multipart/form-data [\#4571](https://github.com/codeigniter4/CodeIgniter4/pull/4571) ([iRedds](https://github.com/iRedds)) -- \[Rector\] Update Rector 0.10.6, re-enable auto imports [\#4569](https://github.com/codeigniter4/CodeIgniter4/pull/4569) ([samsonasik](https://github.com/samsonasik)) -- feat\(cache\): add deleteMatching method to remove multiple cache items [\#4567](https://github.com/codeigniter4/CodeIgniter4/pull/4567) ([yassinedoghri](https://github.com/yassinedoghri)) -- Mysql connection issue with MYSQLI\_CLIENT\_SSL\_DONT\_VERIFY\_SERVER\_CERT [\#4566](https://github.com/codeigniter4/CodeIgniter4/pull/4566) ([fedeburo](https://github.com/fedeburo)) -- Bump actions/cache from v2.1.4 to v2.1.5 [\#4564](https://github.com/codeigniter4/CodeIgniter4/pull/4564) ([dependabot[bot]](https://github.com/apps/dependabot)) -- \[Rector\] Apply Rector: UnnecessaryTernaryExpressionRector [\#4563](https://github.com/codeigniter4/CodeIgniter4/pull/4563) ([samsonasik](https://github.com/samsonasik)) -- Check intl extension loaded on check min PHP version [\#4562](https://github.com/codeigniter4/CodeIgniter4/pull/4562) ([samsonasik](https://github.com/samsonasik)) -- Prefix calls to getenv\(\) during config resolution [\#4561](https://github.com/codeigniter4/CodeIgniter4/pull/4561) ([paulbalandan](https://github.com/paulbalandan)) -- \[Rector\] Apply Rector: ChangeArrayPushToArrayAssignRector [\#4560](https://github.com/codeigniter4/CodeIgniter4/pull/4560) ([samsonasik](https://github.com/samsonasik)) -- \[Rector\] Enable check tests/system/Models [\#4557](https://github.com/codeigniter4/CodeIgniter4/pull/4557) ([samsonasik](https://github.com/samsonasik)) -- Debugging SQL Server in Actions [\#4554](https://github.com/codeigniter4/CodeIgniter4/pull/4554) ([paulbalandan](https://github.com/paulbalandan)) -- Support for masking sensitive debug data [\#4550](https://github.com/codeigniter4/CodeIgniter4/pull/4550) ([pixobit](https://github.com/pixobit)) -- Use message directly if intl is not available [\#4549](https://github.com/codeigniter4/CodeIgniter4/pull/4549) ([paulbalandan](https://github.com/paulbalandan)) -- Add compatibility for strict types [\#4548](https://github.com/codeigniter4/CodeIgniter4/pull/4548) ([paulbalandan](https://github.com/paulbalandan)) -- Removes deprecated settings in env file [\#4547](https://github.com/codeigniter4/CodeIgniter4/pull/4547) ([paulbalandan](https://github.com/paulbalandan)) -- Fix wrong argument passed in doc [\#4546](https://github.com/codeigniter4/CodeIgniter4/pull/4546) ([paulbalandan](https://github.com/paulbalandan)) -- Fix dot notation for if\_exist [\#4545](https://github.com/codeigniter4/CodeIgniter4/pull/4545) ([paulbalandan](https://github.com/paulbalandan)) -- Relocate cookie exception [\#4544](https://github.com/codeigniter4/CodeIgniter4/pull/4544) ([mostafakhudair](https://github.com/mostafakhudair)) -- \[Rector\] Apply RemoveDefaultArgumentValueRector [\#4543](https://github.com/codeigniter4/CodeIgniter4/pull/4543) ([samsonasik](https://github.com/samsonasik)) -- Fix html formatting for exceptions and errors [\#4542](https://github.com/codeigniter4/CodeIgniter4/pull/4542) ([musmanikram](https://github.com/musmanikram)) -- Create Config::Cookie Class [\#4508](https://github.com/codeigniter4/CodeIgniter4/pull/4508) ([mostafakhudair](https://github.com/mostafakhudair)) - -## [v4.1.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.0) (2021-02-01) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.0...v4.1.1) - -**Fixed bugs:** - -- Fixed an issue where **.gitattributes** was preventing framework downloads - -## [v4.1.0](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.0) (2021-01-31) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.5...v4.1.0) - -**Implemented enhancements:** - -- Rector 0.9 fixes [\#4196](https://github.com/codeigniter4/CodeIgniter4/issues/#4196) -- Cannot declare class Config\App error on running PHPUnit [\#4114](https://github.com/codeigniter4/CodeIgniter4/issues/4114) -- Backfill non-optional parameters (https://github.com/codeigniter4/CodeIgniter4/pull/3938) -- Change deprecated assertFileNotExists (https://github.com/codeigniter4/CodeIgniter4/pull/3862) - -## [v4.0.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.5) (2021-01-31) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.4...v4.0.5) - -**Deprecations:** - -- `CodeIgniter\Database\ModelFactory` is now deprecated in favor of `CodeIgniter\Config\Factories::models()` -- `CodeIgniter\Config\Config` is now deprecated in favor of `CodeIgniter\Config\Factories::config()` -- HTTP Layer Refactor: Numerous deprecations have been made towards a transition to a PSR-compliant HTTP layer. [See the User Guide](user_guide_src/source/installation/upgrade_405.rst) - -**Mime Type Detection** - -- `Config\Mimes::guessExtensionFromType` now only reverse searches the `$mimes` array if no extension is proposed (i.e., usually not for uploaded files). -- The fallback values of `UploadedFile->getExtension()` and `UploadedFile->guessExtension()` have been changed. `UploadedFile->getExtension()` now returns `$this->getClientExtension()` instead of `''`; `UploadedFile->guessExtension()` now returns `''` instead of `$this->getClientExtension()`. -These changes increase security when handling uploaded files as the client can no longer force a wrong mime type on the application. However, these might affect how file extensions are detected in your application. - -**Implemented enhancements:** - -- Bug: controller routing on modules not working [\#3927](https://github.com/codeigniter4/CodeIgniter4/issues/3927) -- CLI: method prompt should accept array validation rules [\#3766](https://github.com/codeigniter4/CodeIgniter4/issues/3766) -- Validation: permit\_empty, optional arguments [\#3670](https://github.com/codeigniter4/CodeIgniter4/issues/3670) -- php 8 support [\#3498](https://github.com/codeigniter4/CodeIgniter4/issues/3498) -- getRoutesOptions should return the controller and method if available [\#3445](https://github.com/codeigniter4/CodeIgniter4/issues/3445) -- before function in FilterInterface is missing response param [\#2085](https://github.com/codeigniter4/CodeIgniter4/issues/2085) -- Feature Request: Centralized loggedInUser before RC [\#2055](https://github.com/codeigniter4/CodeIgniter4/issues/2055) - -**Fixed bugs:** - -- Bug: UploadedFile::store\(\) can't return null [\#4183](https://github.com/codeigniter4/CodeIgniter4/issues/4183) -- Bug: BaseBuilder::getCompiledDelete\(\) runs real query [\#4180](https://github.com/codeigniter4/CodeIgniter4/issues/4180) -- Bug: Deprecated: Required parameter $userAgent follows optional parameter $body in /opt/lampp/htdocs/framework-4.0.4/system/HTTP/IncomingRequest.php on line 161 [\#4172](https://github.com/codeigniter4/CodeIgniter4/issues/4172) -- Bug: table template closes tbody after tfoot [\#4155](https://github.com/codeigniter4/CodeIgniter4/issues/4155) -- Bug: delete\_cookie\(\) helper not working [\#4149](https://github.com/codeigniter4/CodeIgniter4/issues/4149) -- Bug: Required parameter $userAgent follows optional parameter $body in [\#4148](https://github.com/codeigniter4/CodeIgniter4/issues/4148) -- Bug: spark issue [\#4144](https://github.com/codeigniter4/CodeIgniter4/issues/4144) -- Bug: PostgreSQL driver issues [\#4142](https://github.com/codeigniter4/CodeIgniter4/issues/4142) -- Bug: phpunit coverage report causes `Cannot declare class Config\App, because the name is already in use` [\#4114](https://github.com/codeigniter4/CodeIgniter4/issues/4114) -- Bug: Wrong file/line in exceptions created by "factory" methods [\#4110](https://github.com/codeigniter4/CodeIgniter4/issues/4110) -- Bug: Request::withMethod\(\) uses deprecated code [\#4109](https://github.com/codeigniter4/CodeIgniter4/issues/4109) -- Bug: View rendering, extracted variables overwrites existed variables\(arguments and etc.\) [\#4108](https://github.com/codeigniter4/CodeIgniter4/issues/4108) -- Bug: Loss of escape value and data in the model [\#4087](https://github.com/codeigniter4/CodeIgniter4/issues/4087) -- Bug: classes overwrites parameter defaults, phpDoc's defined in interfaces [\#4086](https://github.com/codeigniter4/CodeIgniter4/issues/4086) -- Bug: getRandomName Return file extension .csv when upload docx or xlsx becouse mimetype application/octet-stream are set [\#4084](https://github.com/codeigniter4/CodeIgniter4/issues/4084) -- Bug: Required parameter $from follows optional parameter $verbs [\#4076](https://github.com/codeigniter4/CodeIgniter4/issues/4076) -- Bug: username input form shows root as a default value [\#4062](https://github.com/codeigniter4/CodeIgniter4/issues/4062) -- Bug: Issue with pagination [\#4045](https://github.com/codeigniter4/CodeIgniter4/issues/4045) -- Bug: Model calls builder before initializing [\#4036](https://github.com/codeigniter4/CodeIgniter4/issues/4036) -- Bug: can't run migrations when CI\_ENVIRONMENT = testing [\#4033](https://github.com/codeigniter4/CodeIgniter4/issues/4033) -- Bug: can't get environment variable with dot [\#4026](https://github.com/codeigniter4/CodeIgniter4/issues/4026) -- Bug: CodeIgniter model not recognizing custom deletedField in find queries [\#3999](https://github.com/codeigniter4/CodeIgniter4/issues/3999) -- Bug: assertSee\(\) can not assert title tag. [\#3984](https://github.com/codeigniter4/CodeIgniter4/issues/3984) -- Bug: RAR file detected as CSV [\#3979](https://github.com/codeigniter4/CodeIgniter4/issues/3979) -- Bug: Session Initialization via DatabaseHandler [\#3978](https://github.com/codeigniter4/CodeIgniter4/issues/3978) -- Bug: required\_with validation rule does not work with arrays [\#3965](https://github.com/codeigniter4/CodeIgniter4/issues/3965) -- Bug: helper cookie not working [\#3939](https://github.com/codeigniter4/CodeIgniter4/issues/3939) -- Bug: Uploaded SRT files are saved as CSV [\#3921](https://github.com/codeigniter4/CodeIgniter4/issues/3921) -- Bug: Pre-commit hook for Phpstan and sniffer stop working after e111f04d74569e413c5aede3ed9bd9fa1ce7dca2 [\#3920](https://github.com/codeigniter4/CodeIgniter4/issues/3920) -- Bug: Documentation Example Leads to Bug [\#3914](https://github.com/codeigniter4/CodeIgniter4/issues/3914) -- Bug: Route filter runs twice [\#3902](https://github.com/codeigniter4/CodeIgniter4/issues/3902) -- Bug: Premature empty check in Model-\>update function. [\#3896](https://github.com/codeigniter4/CodeIgniter4/issues/3896) -- Bug: Sqldrv problems to insert when change DBPrefix [\#3881](https://github.com/codeigniter4/CodeIgniter4/issues/3881) -- Bug: CI4 won't recognise current namespace for language files [\#3867](https://github.com/codeigniter4/CodeIgniter4/issues/3867) -- Unexpected validation bug [\#3859](https://github.com/codeigniter4/CodeIgniter4/issues/3859) -- Bug: single\_service does not set service's arguments correctly [\#3854](https://github.com/codeigniter4/CodeIgniter4/issues/3854) -- Bug: debugbar should not insert code in code tag [\#3847](https://github.com/codeigniter4/CodeIgniter4/issues/3847) -- Parser won't properly parse tags that have a similar name [\#3841](https://github.com/codeigniter4/CodeIgniter4/issues/3841) -- Bug: insertBatch not generating createdField [\#3838](https://github.com/codeigniter4/CodeIgniter4/issues/3838) -- Bug: Parser - Replacements happening even if key does not fully matches { value } [\#3825](https://github.com/codeigniter4/CodeIgniter4/issues/3825) -- Bug: Parser preg\_replace\_callback exception if template has \# [\#3824](https://github.com/codeigniter4/CodeIgniter4/issues/3824) -- Translation function 'lang' does not work as described [\#3822](https://github.com/codeigniter4/CodeIgniter4/issues/3822) -- Bug: File rewrite.php is always lost in large-request unhandled error [\#3818](https://github.com/codeigniter4/CodeIgniter4/issues/3818) -- Bug: Model::builder\(\) ignores parameter [\#3793](https://github.com/codeigniter4/CodeIgniter4/issues/3793) -- Bug: Warning no tests found in CodeIgiter\Tests\... [\#3788](https://github.com/codeigniter4/CodeIgniter4/issues/3788) -- Bug: Class '\CodeIgniter\Database\pdo\Connection' not found [\#3785](https://github.com/codeigniter4/CodeIgniter4/issues/3785) -- Bug: Composer php spark migrate error [\#3771](https://github.com/codeigniter4/CodeIgniter4/issues/3771) -- Bug: ORDER BY RANDOM ON SQLite3 [\#3768](https://github.com/codeigniter4/CodeIgniter4/issues/3768) -- Bug: CLI: generateDimensions fails with a uncaught exception when exec is disabled [\#3762](https://github.com/codeigniter4/CodeIgniter4/issues/3762) -- Bug: createTable if not exists not working [\#3757](https://github.com/codeigniter4/CodeIgniter4/issues/3757) -- Bug: SQLite drop column corrupts table cache [\#3752](https://github.com/codeigniter4/CodeIgniter4/issues/3752) -- Bug: route filters don't apply for different methods [\#3733](https://github.com/codeigniter4/CodeIgniter4/issues/3733) -- /system/Images/Image.php image\(\)-\>copy\(\) mkdir [\#3732](https://github.com/codeigniter4/CodeIgniter4/issues/3732) -- Bug: route bug [\#3731](https://github.com/codeigniter4/CodeIgniter4/issues/3731) -- page not redirecting. when i redirecting by calling function [\#3729](https://github.com/codeigniter4/CodeIgniter4/issues/3729) -- Bug: \Config\Services::image\(\)-\>flatten\(\) [\#3728](https://github.com/codeigniter4/CodeIgniter4/issues/3728) -- Bug: Parser will overwrite the value if the initial variable is same [\#3726](https://github.com/codeigniter4/CodeIgniter4/issues/3726) -- Bug: Validating Json Requests [\#3719](https://github.com/codeigniter4/CodeIgniter4/issues/3719) -- Bug: view caching not work on windows [\#3711](https://github.com/codeigniter4/CodeIgniter4/issues/3711) -- Bug: getRoutesOptions return wrong when I have \>2 routes has same name, but different method \(HTTPVerb\) [\#3700](https://github.com/codeigniter4/CodeIgniter4/issues/3700) -- Bug: Validation with request raw body. [\#3694](https://github.com/codeigniter4/CodeIgniter4/issues/3694) -- Image format webp is not preserve transparent [\#3690](https://github.com/codeigniter4/CodeIgniter4/issues/3690) -- problem: [\#3686](https://github.com/codeigniter4/CodeIgniter4/issues/3686) -- Documentation Error [\#3668](https://github.com/codeigniter4/CodeIgniter4/issues/3668) -- Bug: BaseBuilder's query is wrong [\#3659](https://github.com/codeigniter4/CodeIgniter4/issues/3659) -- Bug: The lifetime of the CSRF check cookie cannot be set to 0 \(Session\). [\#3655](https://github.com/codeigniter4/CodeIgniter4/issues/3655) -- Bug: isRedirect does not respond depending on how redirects are set up. [\#3654](https://github.com/codeigniter4/CodeIgniter4/issues/3654) -- Bug: SQL Error when countAllResults, groupBy and DBPrefix used together [\#3651](https://github.com/codeigniter4/CodeIgniter4/issues/3651) -- Bug: helper current\_url\(\) return wrong protocol [\#3648](https://github.com/codeigniter4/CodeIgniter4/issues/3648) -- Bug: IncomingRequest::setLocale\(\) [\#3640](https://github.com/codeigniter4/CodeIgniter4/issues/3640) -- CodeIgniter\Database\Exceptions\DatabaseException \#8 Unable to connect to the database. [\#3639](https://github.com/codeigniter4/CodeIgniter4/issues/3639) -- Bug: Cannot use view filter on array values [\#3630](https://github.com/codeigniter4/CodeIgniter4/issues/3630) -- Bug: Changelog navigation is not working properly [\#3625](https://github.com/codeigniter4/CodeIgniter4/issues/3625) -- Bug: Routing 404 Override confusion under route group [\#3623](https://github.com/codeigniter4/CodeIgniter4/issues/3623) -- Bug: Target batch not found when running `php spark migrate:rollback` [\#3620](https://github.com/codeigniter4/CodeIgniter4/issues/3620) -- Bug: getHeaders returns Array with no values [\#3616](https://github.com/codeigniter4/CodeIgniter4/issues/3616) -- Ignore this, was a false report [\#3611](https://github.com/codeigniter4/CodeIgniter4/issues/3611) -- Bug: Incorrect type that fails strict\_types=1 [\#3610](https://github.com/codeigniter4/CodeIgniter4/issues/3610) -- The isAJAX method does not exist in filters [\#3604](https://github.com/codeigniter4/CodeIgniter4/issues/3604) -- Bug: `current\_url\(\)` helper returns wrong url with slash between host and port [\#3603](https://github.com/codeigniter4/CodeIgniter4/issues/3603) -- Bug: Connection ID unknown immediately after connection [\#3601](https://github.com/codeigniter4/CodeIgniter4/issues/3601) -- Bug: Join Query is not working in Library [\#3600](https://github.com/codeigniter4/CodeIgniter4/issues/3600) -- Bug: Database cache not working [\#3597](https://github.com/codeigniter4/CodeIgniter4/issues/3597) -- Bug: Issue in the route, redirect to parent domain, if you put / at the end of url [\#3595](https://github.com/codeigniter4/CodeIgniter4/issues/3595) -- Bug: Segments Also Include The Segments In BASE\_URL [\#3594](https://github.com/codeigniter4/CodeIgniter4/issues/3594) -- Bug: Route Filters doesn't restart in FeatureTestCase. [\#3591](https://github.com/codeigniter4/CodeIgniter4/issues/3591) -- Bug: CURL call returns always code 200 in case de server uses HTTP/2 [\#3586](https://github.com/codeigniter4/CodeIgniter4/issues/3586) -- Bug: Language folders inside locale folders NOT Working ! [\#3582](https://github.com/codeigniter4/CodeIgniter4/issues/3582) -- Bug: Uninitialized string offset: 1 CI 4 [\#3573](https://github.com/codeigniter4/CodeIgniter4/issues/3573) -- Deprecated assertArraySubset in PHPUnit8 used in FeatureResponse::assertJSONFragment [\#3562](https://github.com/codeigniter4/CodeIgniter4/issues/3562) -- Wrong HTTP status code [\#3558](https://github.com/codeigniter4/CodeIgniter4/issues/3558) -- Bug: Invalid serialization data for DateTime object [\#3553](https://github.com/codeigniter4/CodeIgniter4/issues/3553) -- Bug: Setting session expires parameter via $this-\>sessionExpiration [\#3543](https://github.com/codeigniter4/CodeIgniter4/issues/3543) -- Bug: Upload file validation, $model-\>validate return true if failed [\#3532](https://github.com/codeigniter4/CodeIgniter4/issues/3532) -- Bug: Inconsistent behavior of view renderer on Windows and Linux [\#3529](https://github.com/codeigniter4/CodeIgniter4/issues/3529) -- Bug: Add dash to parser plugin regex [\#3523](https://github.com/codeigniter4/CodeIgniter4/issues/3523) -- Bug: When Cronjob run the ip address of the request is 0.0.0.0 [\#3512](https://github.com/codeigniter4/CodeIgniter4/issues/3512) -- Bug: Inconsistency in replace\(\) method [\#3510](https://github.com/codeigniter4/CodeIgniter4/issues/3510) -- Bug: setPath function in UploadedFile.php is writing the index.html wrong [\#3506](https://github.com/codeigniter4/CodeIgniter4/issues/3506) -- Bug: Cannot get session data after server redirecting [\#3503](https://github.com/codeigniter4/CodeIgniter4/issues/3503) -- Bug: Database group defined in .env doesn't work [\#3497](https://github.com/codeigniter4/CodeIgniter4/issues/3497) -- Bug: I cant upload mp4 files [\#3494](https://github.com/codeigniter4/CodeIgniter4/issues/3494) -- Bug: Error message for matches rule doesn't support nested params [\#3492](https://github.com/codeigniter4/CodeIgniter4/issues/3492) -- CI\_VERSION = '4.0.0-beta.4'; session 文件file缓存问题失效问题;session\_start\(\): Failed to decode session object. Session has been destroyed [\#3485](https://github.com/codeigniter4/CodeIgniter4/issues/3485) -- Bug: onlyDeleted\(\) conflicts to paginate\(\) [\#3482](https://github.com/codeigniter4/CodeIgniter4/issues/3482) -- Bug: Unable to connect to the database [\#3477](https://github.com/codeigniter4/CodeIgniter4/issues/3477) -- Bug: Argument 1 passed to CodeIgniter\Config\Services::request\(\) must be an instance of Config\App or null, instance of BackEnd\Config\App [\#3475](https://github.com/codeigniter4/CodeIgniter4/issues/3475) -- Bug: Fatal error running [\#3473](https://github.com/codeigniter4/CodeIgniter4/issues/3473) -- Bug: set\(\) doesn't work on DateTime database field types [\#3471](https://github.com/codeigniter4/CodeIgniter4/issues/3471) -- before\(\) Filters that are executed are missing from codeigniter debug toolbar. [\#3470](https://github.com/codeigniter4/CodeIgniter4/issues/3470) -- Bug: Model insert method always insert with current datetime on updatedField [\#3469](https://github.com/codeigniter4/CodeIgniter4/issues/3469) -- Bug: The search function in the documentation is not working [\#3458](https://github.com/codeigniter4/CodeIgniter4/issues/3458) -- Bug: env variable database.default.dsn not working for mysqli [\#3456](https://github.com/codeigniter4/CodeIgniter4/issues/3456) -- Bug: [\#3453](https://github.com/codeigniter4/CodeIgniter4/issues/3453) -- Bug: form\_textarea in form\_helper does not create specified 'rows' [\#3452](https://github.com/codeigniter4/CodeIgniter4/issues/3452) -- Bug: afterUpdate event return array instead of key value [\#3450](https://github.com/codeigniter4/CodeIgniter4/issues/3450) -- Using Cronjob with php line argument while using Crontab redirects the script and never executes [\#3444](https://github.com/codeigniter4/CodeIgniter4/issues/3444) -- Bug: Support for SameSite cookie setting missing [\#3442](https://github.com/codeigniter4/CodeIgniter4/issues/3442) -- Bug: Validation mime\_in SVG files not working correctly [\#3439](https://github.com/codeigniter4/CodeIgniter4/issues/3439) -- Bug: Cannot declare class Config\Paths, because the name is already in use [\#3434](https://github.com/codeigniter4/CodeIgniter4/issues/3434) -- Bug: delete\_cookie\(\) helper function not working [\#3433](https://github.com/codeigniter4/CodeIgniter4/issues/3433) -- Bug: insertBatch not working correctly [\#3432](https://github.com/codeigniter4/CodeIgniter4/issues/3432) -- Feature request : CodeIgniter\File with SplFileInfo does not have a method to rewrite parts of the file only to append an CSV style row [\#3431](https://github.com/codeigniter4/CodeIgniter4/issues/3431) -- Bug: SMTP to port 465 should use TLS from the start [\#3429](https://github.com/codeigniter4/CodeIgniter4/issues/3429) -- Bug: Form data and file is not receiving well formed with PUT Method [\#3417](https://github.com/codeigniter4/CodeIgniter4/issues/3417) -- Bug: form\_textarea helper row and col defaults not overwriting when defined as $extra [\#3412](https://github.com/codeigniter4/CodeIgniter4/issues/3412) -- Encryption Class does not allow to change digest parameter from SHA512 to SHA256 or others [\#3404](https://github.com/codeigniter4/CodeIgniter4/issues/3404) -- Bug: table.Array in first\(\) ... - and complex primary keys! ;-\) [\#3394](https://github.com/codeigniter4/CodeIgniter4/issues/3394) -- Error: Email SMTP configured wrong gives error [\#3390](https://github.com/codeigniter4/CodeIgniter4/issues/3390) -- Bug: CodeIgniter\Router\Router hasLocale returns true even if {locale} is absent [\#3386](https://github.com/codeigniter4/CodeIgniter4/issues/3386) -- Bug: Logs Collector isn't collecting logs for Debug Toolbar [\#3376](https://github.com/codeigniter4/CodeIgniter4/issues/3376) -- Bug: Entity's original is not set [\#3370](https://github.com/codeigniter4/CodeIgniter4/issues/3370) -- Bug: warning in Routes.php [\#3369](https://github.com/codeigniter4/CodeIgniter4/issues/3369) -- Bug: Model can not insert Entity [\#3368](https://github.com/codeigniter4/CodeIgniter4/issues/3368) -- Bug: stringify\_attributes\(\) looks unfinished. [\#3363](https://github.com/codeigniter4/CodeIgniter4/issues/3363) -- Bug: php spark migrate throws wrongful CodeIgniter\Database\Exceptions\DatabaseException Unable to connect to the database [\#3359](https://github.com/codeigniter4/CodeIgniter4/issues/3359) -- Bug: Image watermark after save.. text not align in center middle when resolution lower and higher. [\#3356](https://github.com/codeigniter4/CodeIgniter4/issues/3356) -- Bug: SQL Lite driver with Builder -\> InsertBatch has intermittend lastError reporting issues [\#3350](https://github.com/codeigniter4/CodeIgniter4/issues/3350) -- Bug: isCLI error when calling a controller from a Command Line [\#3342](https://github.com/codeigniter4/CodeIgniter4/issues/3342) -- Bug: missing clear upgrade instructions [\#3332](https://github.com/codeigniter4/CodeIgniter4/issues/3332) -- Bug: API requests with a trailing forward slash [\#3330](https://github.com/codeigniter4/CodeIgniter4/issues/3330) -- Bug: Pager pagination \( page value in url \) [\#3328](https://github.com/codeigniter4/CodeIgniter4/issues/3328) -- Bug: View layout name hierarchy is not displaying correctly on Debugbar Views [\#3327](https://github.com/codeigniter4/CodeIgniter4/issues/3327) -- Bug: php spark migrate on testing environment doesn't work [\#3309](https://github.com/codeigniter4/CodeIgniter4/issues/3309) -- Bug: Empty Entity date attributes receive current timestamp when accessed [\#3251](https://github.com/codeigniter4/CodeIgniter4/issues/3251) -- Bug: no Filter arguments passed [\#3216](https://github.com/codeigniter4/CodeIgniter4/issues/3216) -- Bug: Url Helper have a bug [\#3180](https://github.com/codeigniter4/CodeIgniter4/issues/3180) -- $pager-\>links\(\) not working inside view layouts [\#3164](https://github.com/codeigniter4/CodeIgniter4/issues/3164) -- Bug: AH01075: Error dispatching request on Basic CI4 [\#3110](https://github.com/codeigniter4/CodeIgniter4/issues/3110) -- Bug: Using assertJSONFragment with respond\(\) in ResponseTrait [\#3079](https://github.com/codeigniter4/CodeIgniter4/issues/3079) -- Bug: Filters 'except' option not removing DebugToolbar comment for view [\#3002](https://github.com/codeigniter4/CodeIgniter4/issues/3002) -- Bug: useSoftDeletes with like function [\#2380](https://github.com/codeigniter4/CodeIgniter4/issues/2380) - -**Closed issues:** - -- Seed Command in document Not found [\#4154](https://github.com/codeigniter4/CodeIgniter4/issues/4154) -- I18n/L10n: auto update translations [\#4151](https://github.com/codeigniter4/CodeIgniter4/issues/4151) -- Calling update\(\) with an Entity throws an error [\#4143](https://github.com/codeigniter4/CodeIgniter4/issues/4143) -- BaseConfig property issue [\#4140](https://github.com/codeigniter4/CodeIgniter4/issues/4140) -- Bug: [\#4126](https://github.com/codeigniter4/CodeIgniter4/issues/4126) -- Bug: a new installation of codeignitor 4.0.4 show 404 error Sorry! Cannot seem to find the page you were looking for. [\#4111](https://github.com/codeigniter4/CodeIgniter4/issues/4111) -- Bug PHP SPARK MIGRATE [\#4101](https://github.com/codeigniter4/CodeIgniter4/issues/4101) -- Pagination work in local, but error in server [\#4096](https://github.com/codeigniter4/CodeIgniter4/issues/4096) -- updated\_at field is filled with the current timestamp when inserting new data along with created\_at [\#4038](https://github.com/codeigniter4/CodeIgniter4/issues/4038) -- Config for pager [\#4030](https://github.com/codeigniter4/CodeIgniter4/issues/4030) -- setUpdateBatch [\#4025](https://github.com/codeigniter4/CodeIgniter4/issues/4025) -- Empty html-file created when moving uploaded files with $img-\>move\(\) [\#4020](https://github.com/codeigniter4/CodeIgniter4/issues/4020) -- php spark serve error on php 8 [\#3980](https://github.com/codeigniter4/CodeIgniter4/issues/3980) -- Bug: PHP8 session flush data is not deleted [\#3974](https://github.com/codeigniter4/CodeIgniter4/issues/3974) -- PHP8: Deprecate required param after optional [\#3957](https://github.com/codeigniter4/CodeIgniter4/issues/3957) -- Undefined function [\#3954](https://github.com/codeigniter4/CodeIgniter4/issues/3954) -- Missing function locale\_set\_default\(...\) in Codeigniter 4 [\#3953](https://github.com/codeigniter4/CodeIgniter4/issues/3953) -- Language\en\Language.php is only used in testing [\#3948](https://github.com/codeigniter4/CodeIgniter4/issues/3948) -- Bug: locale\_set\_default causing "Whoops" failure in MacOS PHP 7.3.9 on develop branch [\#3933](https://github.com/codeigniter4/CodeIgniter4/issues/3933) -- Pdo driver not found [\#3922](https://github.com/codeigniter4/CodeIgniter4/issues/3922) -- Mysqli Backup utility? [\#3906](https://github.com/codeigniter4/CodeIgniter4/issues/3906) -- Relationships - Many to many [\#3885](https://github.com/codeigniter4/CodeIgniter4/issues/3885) -- Bug: How can remove Codeinatore default icon [\#3883](https://github.com/codeigniter4/CodeIgniter4/issues/3883) -- How Can remove CodeIgniter4 icon [\#3882](https://github.com/codeigniter4/CodeIgniter4/issues/3882) -- Codeigniter review 2021 [\#3880](https://github.com/codeigniter4/CodeIgniter4/issues/3880) -- url\_title doesn't work with decimal number in title [\#3878](https://github.com/codeigniter4/CodeIgniter4/issues/3878) -- IncomingRequest Class "hasFile" function does not exist as described in the documentation. [\#3852](https://github.com/codeigniter4/CodeIgniter4/issues/3852) -- Dynamically changing supported languages [\#3844](https://github.com/codeigniter4/CodeIgniter4/issues/3844) -- CodeIgniter\Database\Exceptions\DatabaseException \#8 [\#3826](https://github.com/codeigniter4/CodeIgniter4/issues/3826) -- Blank page show [\#3812](https://github.com/codeigniter4/CodeIgniter4/issues/3812) -- Bug: folder tests not found [\#3807](https://github.com/codeigniter4/CodeIgniter4/issues/3807) -- Typo in the doc: cache\_info\(\) [\#3800](https://github.com/codeigniter4/CodeIgniter4/issues/3800) -- only\_full\_group\_by - mysqli\_sql\_exception \#1055 - MySql 5.7.24 [\#3795](https://github.com/codeigniter4/CodeIgniter4/issues/3795) -- Dev: Split ModelTest [\#3792](https://github.com/codeigniter4/CodeIgniter4/issues/3792) -- The formatMessage function of the Language class works intermittently [\#3784](https://github.com/codeigniter4/CodeIgniter4/issues/3784) -- .htaccess problem with syntax [\#3778](https://github.com/codeigniter4/CodeIgniter4/issues/3778) -- The page does not open correctly:\( [\#3770](https://github.com/codeigniter4/CodeIgniter4/issues/3770) -- Restore method for CodeIgniter Model. [\#3767](https://github.com/codeigniter4/CodeIgniter4/issues/3767) -- Bug: Model update\(\) only set first array value [\#3764](https://github.com/codeigniter4/CodeIgniter4/issues/3764) -- Bug: CLI: Exception view shows full path [\#3763](https://github.com/codeigniter4/CodeIgniter4/issues/3763) -- Bug: Maximum execution time exceeds while handling IPv6 Reverse Proxy IPs [\#3760](https://github.com/codeigniter4/CodeIgniter4/issues/3760) -- Fabricators documentation error [\#3743](https://github.com/codeigniter4/CodeIgniter4/issues/3743) -- `is\_unique` is not considering the db prefix in checking for unique values in db [\#3741](https://github.com/codeigniter4/CodeIgniter4/issues/3741) -- Grouping routes should let me pass additional variables within the closure function [\#3691](https://github.com/codeigniter4/CodeIgniter4/issues/3691) -- cannot find Ftp library. [\#3679](https://github.com/codeigniter4/CodeIgniter4/issues/3679) -- Make sessions never expire with $config\['sess\_expiration'\] = -1 [\#3677](https://github.com/codeigniter4/CodeIgniter4/issues/3677) -- Migration trouble [\#3624](https://github.com/codeigniter4/CodeIgniter4/issues/3624) -- Files as optional in validation rules [\#3619](https://github.com/codeigniter4/CodeIgniter4/issues/3619) -- $\_SERVER should not have .env file info [\#3615](https://github.com/codeigniter4/CodeIgniter4/issues/3615) -- Bug pars xml [\#3588](https://github.com/codeigniter4/CodeIgniter4/issues/3588) -- Bug: When have multi filter class, and "before" function return true,other filters after this filter not working [\#3579](https://github.com/codeigniter4/CodeIgniter4/issues/3579) -- can not install on linux os [\#3572](https://github.com/codeigniter4/CodeIgniter4/issues/3572) -- Incorrect grouping function names in documentation [\#3551](https://github.com/codeigniter4/CodeIgniter4/issues/3551) -- New Edge Browser missing from user agents config [\#3513](https://github.com/codeigniter4/CodeIgniter4/issues/3513) -- Form validation does not have a rule to validate if field value differs from a string [\#3462](https://github.com/codeigniter4/CodeIgniter4/issues/3462) -- updateBatch does not accept entities [\#3451](https://github.com/codeigniter4/CodeIgniter4/issues/3451) -- Support PSR4 [\#3405](https://github.com/codeigniter4/CodeIgniter4/issues/3405) -- CodeIgniter4 is ready to work with PHP 7.4? [\#3389](https://github.com/codeigniter4/CodeIgniter4/issues/3389) -- Using CodeIgniter4 with Oracle Database [\#3388](https://github.com/codeigniter4/CodeIgniter4/issues/3388) -- Can't get Controllers subfolder working [\#3347](https://github.com/codeigniter4/CodeIgniter4/issues/3347) -- Why redirect\(\)-\>to\(\) doesn't work inside events Model? [\#3346](https://github.com/codeigniter4/CodeIgniter4/issues/3346) -- Bug: requests PUT type without segment redirects to index method instead update method [\#3343](https://github.com/codeigniter4/CodeIgniter4/issues/3343) -- Migrations not working [\#3317](https://github.com/codeigniter4/CodeIgniter4/issues/3317) -- CURL Error htts SSL [\#3314](https://github.com/codeigniter4/CodeIgniter4/issues/3314) -- codeigniter composer global installer [\#3266](https://github.com/codeigniter4/CodeIgniter4/issues/3266) -- Bug: Migrations Deprecated [\#3195](https://github.com/codeigniter4/CodeIgniter4/issues/3195) -- Managed to reproduce this issue. Was uploading an image with jquery.dm-uploader.min.js, and trying to do some image manipulation, but when I got this error: [\#3174](https://github.com/codeigniter4/CodeIgniter4/issues/3174) -- Make the Model work without auto\_increment primary key [\#3134](https://github.com/codeigniter4/CodeIgniter4/issues/3134) -- Call to undefined function CodeIgniter\Debug\current\_url\(\) [\#3106](https://github.com/codeigniter4/CodeIgniter4/issues/3106) -- Bug: select\(\) & selectSum\(\) not consistent / aligned with docs [\#3019](https://github.com/codeigniter4/CodeIgniter4/issues/3019) -- Remove /public in URL not working if using localhost [\#2930](https://github.com/codeigniter4/CodeIgniter4/issues/2930) -- The problem is in getting the file sharing by Mime [\#2732](https://github.com/codeigniter4/CodeIgniter4/issues/2732) -- Migration on PostgreSQL failes for tables with foreign keys [\#2575](https://github.com/codeigniter4/CodeIgniter4/issues/2575) -- Automatic Entity cast for Parser [\#2317](https://github.com/codeigniter4/CodeIgniter4/issues/2317) -- Feature: Cache Driver - Serialize/Unserialize Objects [\#2111](https://github.com/codeigniter4/CodeIgniter4/issues/2111) -- Model set $escape problem [\#1929](https://github.com/codeigniter4/CodeIgniter4/issues/1929) -- \I18n\Time setTimezone does not work as expected [\#1807](https://github.com/codeigniter4/CodeIgniter4/issues/1807) -- TODO Database utility backup builder [\#1257](https://github.com/codeigniter4/CodeIgniter4/issues/1257) -- Port FTP library from CI3 [\#506](https://github.com/codeigniter4/CodeIgniter4/issues/506) -- Port MSSql Database driver from CI3 [\#503](https://github.com/codeigniter4/CodeIgniter4/issues/503) - -**Merged pull requests:** - -- Finishing touches to generator refactor [\#4197](https://github.com/codeigniter4/CodeIgniter4/pull/4197) ([paulbalandan](https://github.com/paulbalandan)) -- Add additional empty checks after field protection for update/insert. [\#4195](https://github.com/codeigniter4/CodeIgniter4/pull/4195) ([sfadschm](https://github.com/sfadschm)) -- Minor fixes in Common.php [\#4192](https://github.com/codeigniter4/CodeIgniter4/pull/4192) ([kenjis](https://github.com/kenjis)) -- Fix Parser file path in ViewException message is empty [\#4191](https://github.com/codeigniter4/CodeIgniter4/pull/4191) ([kenjis](https://github.com/kenjis)) -- docs: Fix double "the" [\#4190](https://github.com/codeigniter4/CodeIgniter4/pull/4190) ([kenjis](https://github.com/kenjis)) -- Fix typo in IncomingRequestTest [\#4189](https://github.com/codeigniter4/CodeIgniter4/pull/4189) ([kenjis](https://github.com/kenjis)) -- feat: add methods to get page numbers in PagerRenderer [\#4188](https://github.com/codeigniter4/CodeIgniter4/pull/4188) ([kenjis](https://github.com/kenjis)) -- fix: UploadedFile::store\(\) return type inconsistancy [\#4187](https://github.com/codeigniter4/CodeIgniter4/pull/4187) ([kenjis](https://github.com/kenjis)) -- Add STL mime support [\#4186](https://github.com/codeigniter4/CodeIgniter4/pull/4186) ([MGatner](https://github.com/MGatner)) -- \[Rector\] Run Rector when composer.json updated [\#4185](https://github.com/codeigniter4/CodeIgniter4/pull/4185) ([samsonasik](https://github.com/samsonasik)) -- new array helper: array\_flatten\_with\_dots [\#4184](https://github.com/codeigniter4/CodeIgniter4/pull/4184) ([paulbalandan](https://github.com/paulbalandan)) -- fix: BaseBuilder::getCompiledDelete\(\) runs real query [\#4181](https://github.com/codeigniter4/CodeIgniter4/pull/4181) ([kenjis](https://github.com/kenjis)) -- fix the missing / hidden userguide [\#4175](https://github.com/codeigniter4/CodeIgniter4/pull/4175) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Revert "Update phpstan/phpstan requirement from 0.12.69 to 0.12.70" [\#4173](https://github.com/codeigniter4/CodeIgniter4/pull/4173) ([samsonasik](https://github.com/samsonasik)) -- Add missing escaping in error\_exception.php [\#4171](https://github.com/codeigniter4/CodeIgniter4/pull/4171) ([kenjis](https://github.com/kenjis)) -- Update phpstan/phpstan requirement from 0.12.69 to 0.12.70 [\#4170](https://github.com/codeigniter4/CodeIgniter4/pull/4170) ([dependabot[bot]](https://github.com/apps/dependabot)) -- add phpunit.xml.dist to .gitattributes for admin/framework [\#4163](https://github.com/codeigniter4/CodeIgniter4/pull/4163) ([samsonasik](https://github.com/samsonasik)) -- Fix strict checking for SQLite3 memory filename [\#4161](https://github.com/codeigniter4/CodeIgniter4/pull/4161) ([paulbalandan](https://github.com/paulbalandan)) -- Discuss dbcreate in userguide [\#4160](https://github.com/codeigniter4/CodeIgniter4/pull/4160) ([paulbalandan](https://github.com/paulbalandan)) -- Fix misplaced closing tbody [\#4159](https://github.com/codeigniter4/CodeIgniter4/pull/4159) ([paulbalandan](https://github.com/paulbalandan)) -- \[Develop\] Fixes \#4114 Cannot declare class Config\App error on running PHPUnit [\#4157](https://github.com/codeigniter4/CodeIgniter4/pull/4157) ([samsonasik](https://github.com/samsonasik)) -- Specifically exclude migrations from class mapping [\#4156](https://github.com/codeigniter4/CodeIgniter4/pull/4156) ([paulbalandan](https://github.com/paulbalandan)) -- config: add logger.threshold in env as comment [\#4153](https://github.com/codeigniter4/CodeIgniter4/pull/4153) ([kenjis](https://github.com/kenjis)) -- Update phpstan/phpstan requirement from 0.12.68 to 0.12.69 [\#4152](https://github.com/codeigniter4/CodeIgniter4/pull/4152) ([dependabot[bot]](https://github.com/apps/dependabot)) -- convert indentation from tabs to spaces & update code-block \[changelogs, database\] [\#4150](https://github.com/codeigniter4/CodeIgniter4/pull/4150) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Fix for retrieving migration history [\#4147](https://github.com/codeigniter4/CodeIgniter4/pull/4147) ([michalsn](https://github.com/michalsn)) -- Fix phpstan notice [\#4146](https://github.com/codeigniter4/CodeIgniter4/pull/4146) ([samsonasik](https://github.com/samsonasik)) -- Update docs for Entity [\#4145](https://github.com/codeigniter4/CodeIgniter4/pull/4145) ([michalsn](https://github.com/michalsn)) -- update faker [\#4139](https://github.com/codeigniter4/CodeIgniter4/pull/4139) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- docs: add table of contents in 2 libs [\#4138](https://github.com/codeigniter4/CodeIgniter4/pull/4138) ([kenjis](https://github.com/kenjis)) -- PHPStan Fixes [\#4136](https://github.com/codeigniter4/CodeIgniter4/pull/4136) ([MGatner](https://github.com/MGatner)) -- prep\_url\(\) with https:// [\#4135](https://github.com/codeigniter4/CodeIgniter4/pull/4135) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Update phpstan/phpstan requirement from 0.12.65 to 0.12.68 [\#4134](https://github.com/codeigniter4/CodeIgniter4/pull/4134) ([dependabot[bot]](https://github.com/apps/dependabot)) -- set uppercase [\#4132](https://github.com/codeigniter4/CodeIgniter4/pull/4132) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- remove useless "raw html" [\#4131](https://github.com/codeigniter4/CodeIgniter4/pull/4131) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Fix errors/html/error\_exception.php [\#4128](https://github.com/codeigniter4/CodeIgniter4/pull/4128) ([kenjis](https://github.com/kenjis)) -- set note-block for some notes [\#4127](https://github.com/codeigniter4/CodeIgniter4/pull/4127) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Fix `@var` in SessionTestCase. [\#4124](https://github.com/codeigniter4/CodeIgniter4/pull/4124) ([kenjis](https://github.com/kenjis)) -- Fix Session phpdoc [\#4123](https://github.com/codeigniter4/CodeIgniter4/pull/4123) ([kenjis](https://github.com/kenjis)) -- Refactor Generators [\#4121](https://github.com/codeigniter4/CodeIgniter4/pull/4121) ([mostafakhudair](https://github.com/mostafakhudair)) -- Fix few typos. [\#4119](https://github.com/codeigniter4/CodeIgniter4/pull/4119) ([npwsamarasinghe](https://github.com/npwsamarasinghe)) -- Precise exception trace [\#4118](https://github.com/codeigniter4/CodeIgniter4/pull/4118) ([paulbalandan](https://github.com/paulbalandan)) -- Wrap extract calls in IIFEs in View [\#4113](https://github.com/codeigniter4/CodeIgniter4/pull/4113) ([paulbalandan](https://github.com/paulbalandan)) -- Fix Request::withMethod\(\) [\#4112](https://github.com/codeigniter4/CodeIgniter4/pull/4112) ([paulbalandan](https://github.com/paulbalandan)) -- Cache remember [\#4107](https://github.com/codeigniter4/CodeIgniter4/pull/4107) ([agungsugiarto](https://github.com/agungsugiarto)) -- docs: change sample code of redirect\(\) to be more common [\#4106](https://github.com/codeigniter4/CodeIgniter4/pull/4106) ([kenjis](https://github.com/kenjis)) -- Add Cache File mode [\#4103](https://github.com/codeigniter4/CodeIgniter4/pull/4103) ([MGatner](https://github.com/MGatner)) -- Clarify Renderer discrepancy [\#4102](https://github.com/codeigniter4/CodeIgniter4/pull/4102) ([MGatner](https://github.com/MGatner)) -- Catch DateTime failure [\#4097](https://github.com/codeigniter4/CodeIgniter4/pull/4097) ([MGatner](https://github.com/MGatner)) -- Fix URL type. [\#4095](https://github.com/codeigniter4/CodeIgniter4/pull/4095) ([npwsamarasinghe](https://github.com/npwsamarasinghe)) -- Fixed a bug where a newline was treated as a valid value even if it was included at the end. [\#4094](https://github.com/codeigniter4/CodeIgniter4/pull/4094) ([ytetsuro](https://github.com/ytetsuro)) -- BaseModel/Model - Removed $escape from doUpdate [\#4090](https://github.com/codeigniter4/CodeIgniter4/pull/4090) ([najdanovicivan](https://github.com/najdanovicivan)) -- BaseConnection - Added automatic handling of query class for 3rd party drivers [\#4089](https://github.com/codeigniter4/CodeIgniter4/pull/4089) ([najdanovicivan](https://github.com/najdanovicivan)) -- Fix loss of escape value and data in the model [\#4088](https://github.com/codeigniter4/CodeIgniter4/pull/4088) ([michalsn](https://github.com/michalsn)) -- Use getMimeType instead of getClientMimeType. [\#4085](https://github.com/codeigniter4/CodeIgniter4/pull/4085) ([sfadschm](https://github.com/sfadschm)) -- fix codeblock in installing\_composer.rst [\#4083](https://github.com/codeigniter4/CodeIgniter4/pull/4083) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Fixing a bug in Message::isJSON [\#4081](https://github.com/codeigniter4/CodeIgniter4/pull/4081) ([caswell-wc](https://github.com/caswell-wc)) -- Get JSON Variable [\#4080](https://github.com/codeigniter4/CodeIgniter4/pull/4080) ([caswell-wc](https://github.com/caswell-wc)) -- Fix userguide indent [\#4078](https://github.com/codeigniter4/CodeIgniter4/pull/4078) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- DebugToolbar - Handle Query display in Query class [\#4077](https://github.com/codeigniter4/CodeIgniter4/pull/4077) ([najdanovicivan](https://github.com/najdanovicivan)) -- Update userguide indentation [\#4075](https://github.com/codeigniter4/CodeIgniter4/pull/4075) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- docs: fix models indentation [\#4073](https://github.com/codeigniter4/CodeIgniter4/pull/4073) ([kenjis](https://github.com/kenjis)) -- BaseModel/Model - Attempt to rework escape parameter [\#4072](https://github.com/codeigniter4/CodeIgniter4/pull/4072) ([najdanovicivan](https://github.com/najdanovicivan)) -- Model/BaseModel - Fix primary key and add @throws for builder method [\#4071](https://github.com/codeigniter4/CodeIgniter4/pull/4071) ([najdanovicivan](https://github.com/najdanovicivan)) -- Fix DOMParser rules to search also outside the body tag [\#4070](https://github.com/codeigniter4/CodeIgniter4/pull/4070) ([michalsn](https://github.com/michalsn)) -- Warn users on system messages being for internal use [\#4068](https://github.com/codeigniter4/CodeIgniter4/pull/4068) ([paulbalandan](https://github.com/paulbalandan)) -- Remove discussion on LoggerAwareTrait [\#4067](https://github.com/codeigniter4/CodeIgniter4/pull/4067) ([paulbalandan](https://github.com/paulbalandan)) -- PHPStan Ignore File [\#4065](https://github.com/codeigniter4/CodeIgniter4/pull/4065) ([MGatner](https://github.com/MGatner)) -- site\_url tests [\#4063](https://github.com/codeigniter4/CodeIgniter4/pull/4063) ([MGatner](https://github.com/MGatner)) -- Use full table name with schema for SQLSRV [\#4058](https://github.com/codeigniter4/CodeIgniter4/pull/4058) ([michalsn](https://github.com/michalsn)) -- fix userguide config path [\#4057](https://github.com/codeigniter4/CodeIgniter4/pull/4057) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Solidate mime type guessing. [\#4056](https://github.com/codeigniter4/CodeIgniter4/pull/4056) ([sfadschm](https://github.com/sfadschm)) -- Add mime type for rar files [\#4054](https://github.com/codeigniter4/CodeIgniter4/pull/4054) ([michalsn](https://github.com/michalsn)) -- Fix for deleting session flash data in php8 [\#4053](https://github.com/codeigniter4/CodeIgniter4/pull/4053) ([michalsn](https://github.com/michalsn)) -- Added omitted function parameter description. [\#4052](https://github.com/codeigniter4/CodeIgniter4/pull/4052) ([francis94c](https://github.com/francis94c)) -- New Year 2021 [\#4051](https://github.com/codeigniter4/CodeIgniter4/pull/4051) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Fix countAll\(\) docs [\#4050](https://github.com/codeigniter4/CodeIgniter4/pull/4050) ([kenjis](https://github.com/kenjis)) -- adds BaseResult::getNumRows\(\). adds getNumRows to various DBMS Result classes [\#4049](https://github.com/codeigniter4/CodeIgniter4/pull/4049) ([sneakyimp](https://github.com/sneakyimp)) -- \[UG\] a comma after use: i.e., and e.g., [\#4042](https://github.com/codeigniter4/CodeIgniter4/pull/4042) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Fix database test case test [\#4041](https://github.com/codeigniter4/CodeIgniter4/pull/4041) ([kenjis](https://github.com/kenjis)) -- Add initDriver Method [\#4040](https://github.com/codeigniter4/CodeIgniter4/pull/4040) ([mostafakhudair](https://github.com/mostafakhudair)) -- docs: fix general indentation [\#4039](https://github.com/codeigniter4/CodeIgniter4/pull/4039) ([kenjis](https://github.com/kenjis)) -- fix codeblock [\#4037](https://github.com/codeigniter4/CodeIgniter4/pull/4037) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Remove 'tests' [\#4034](https://github.com/codeigniter4/CodeIgniter4/pull/4034) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Alias Forge with use operator [\#4032](https://github.com/codeigniter4/CodeIgniter4/pull/4032) ([mostafakhudair](https://github.com/mostafakhudair)) -- New line for License copyright [\#4029](https://github.com/codeigniter4/CodeIgniter4/pull/4029) ([totoprayogo1916](https://github.com/totoprayogo1916)) -- Rename Sqlsrv driver [\#4023](https://github.com/codeigniter4/CodeIgniter4/pull/4023) ([mostafakhudair](https://github.com/mostafakhudair)) -- PHP 8 Actions [\#4012](https://github.com/codeigniter4/CodeIgniter4/pull/4012) ([MGatner](https://github.com/MGatner)) -- feat: make migration/seed settings flexible on database testing [\#3993](https://github.com/codeigniter4/CodeIgniter4/pull/3993) ([kenjis](https://github.com/kenjis)) -- Deprecate redundant HTTP keys [\#3973](https://github.com/codeigniter4/CodeIgniter4/pull/3973) ([paulbalandan](https://github.com/paulbalandan)) -- Replace Core Services [\#3943](https://github.com/codeigniter4/CodeIgniter4/pull/3943) ([MGatner](https://github.com/MGatner)) -- Handling requests sent back from filters [\#3900](https://github.com/codeigniter4/CodeIgniter4/pull/3900) ([caswell-wc](https://github.com/caswell-wc)) -- DX: Split Model testing into several subunits [\#3891](https://github.com/codeigniter4/CodeIgniter4/pull/3891) ([paulbalandan](https://github.com/paulbalandan)) - -## [v4.0.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.4) (2020-07-15) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.3...v4.0.4) - -**Implemented enhancements:** - -- Bug: incorrect type - system/Database/MYSQLi/Connection.php [\#2996](https://github.com/codeigniter4/CodeIgniter4/issues/2996) -- Bug: CI\_DEBUG incorrect type and fails strict validation [\#2975](https://github.com/codeigniter4/CodeIgniter4/issues/2975) - -**Fixed bugs:** - -- Bug: Fix protocol version for DownloadResponse [\#3320](https://github.com/codeigniter4/CodeIgniter4/pull/3320) -- Bug: Add Honeypot::$container to env file [\#3318](https://github.com/codeigniter4/CodeIgniter4/pull/3318) -- Bug: Add multibyte support on DOMParser::see() [\#3324](https://github.com/codeigniter4/CodeIgniter4/pull/3324) -- Bug: Image width debug-bar has conflict [\#3323](https://github.com/codeigniter4/CodeIgniter4/pull/3323) -- Bug: Setting Encryption Service Key in app/Config/Encyption.php [\#3297](https://github.com/codeigniter4/CodeIgniter4/issues/3297) -- Bug: CodeIgniter\I18n -\> Class 'Locale' not found [\#3294](https://github.com/codeigniter4/CodeIgniter4/issues/3294) -- bug cURL - Parse header and Body [\#3261](https://github.com/codeigniter4/CodeIgniter4/issues/3261) -- Bug: Forced HTTPS wrong redirect [\#3260](https://github.com/codeigniter4/CodeIgniter4/issues/3260) -- Bug: [\#3255](https://github.com/codeigniter4/CodeIgniter4/issues/3255) -- Testing ajax header problems [\#3246](https://github.com/codeigniter4/CodeIgniter4/issues/3246) -- Bug: Url's not working [\#3245](https://github.com/codeigniter4/CodeIgniter4/issues/3245) -- Missing code formatting for migration block in documentation [\#3243](https://github.com/codeigniter4/CodeIgniter4/issues/3243) -- Hint message in tutorial does not correspond to the example code shown [\#3242](https://github.com/codeigniter4/CodeIgniter4/issues/3242) -- How to call resource route [\#3239](https://github.com/codeigniter4/CodeIgniter4/issues/3239) -- Can't successfully validate CLI::prompt when field param has periods [\#3233](https://github.com/codeigniter4/CodeIgniter4/issues/3233) -- Bug: set\_checkbox, set\_radio not working default true. [\#3228](https://github.com/codeigniter4/CodeIgniter4/issues/3228) -- Bug: Validation wrong behaviour - Session mixed with internal validator state [\#3210](https://github.com/codeigniter4/CodeIgniter4/issues/3210) -- Bug: CLI: Smell on reading parameters [\#3205](https://github.com/codeigniter4/CodeIgniter4/issues/3205) -- Bug: MySQL Errors are not reported [\#3204](https://github.com/codeigniter4/CodeIgniter4/issues/3204) -- Bug: Error exception layout when its an error inside a view where code can go outside the limits [\#3199](https://github.com/codeigniter4/CodeIgniter4/issues/3199) -- Bug: MessageFormatter argType of "String" isn't valid [\#3191](https://github.com/codeigniter4/CodeIgniter4/issues/3191) -- Bug: pagination not working with segment [\#3188](https://github.com/codeigniter4/CodeIgniter4/issues/3188) -- Bug: is\_image validation is vulnerable [\#3184](https://github.com/codeigniter4/CodeIgniter4/issues/3184) -- Bug: model-\>save\(\) insert/update data [\#3177](https://github.com/codeigniter4/CodeIgniter4/issues/3177) -- Bug: The CLI::color cannot create multicolored strings if ordering of strings is reversed [\#3173](https://github.com/codeigniter4/CodeIgniter4/issues/3173) -- Bug: Missing function locale\_set\_default\(...\) [\#3171](https://github.com/codeigniter4/CodeIgniter4/issues/3171) -- Router RegEx not working. [\#3169](https://github.com/codeigniter4/CodeIgniter4/issues/3169) -- Bug: BaseBuilder::insertBatch\(\) has 3 params and not 4 [\#3158](https://github.com/codeigniter4/CodeIgniter4/issues/3158) -- Bug: Issue using image library when original image and resized are the same size [\#3146](https://github.com/codeigniter4/CodeIgniter4/issues/3146) -- Bug: cannot call constructor on controller. [\#3145](https://github.com/codeigniter4/CodeIgniter4/issues/3145) -- Bug: CodeIgniter 4.0.3 Controller will not display pdf files in browser [\#3144](https://github.com/codeigniter4/CodeIgniter4/issues/3144) -- Bug: $request-\>getVar\('test', FILTER\_VALIDATE\_INT\) does not work if the input is an array [\#3128](https://github.com/codeigniter4/CodeIgniter4/issues/3128) -- Bug: A wrong escape on BaseBuilder::set\(\) [\#3127](https://github.com/codeigniter4/CodeIgniter4/issues/3127) -- Bug: Can't override Translations Package Files / Keys [\#3125](https://github.com/codeigniter4/CodeIgniter4/issues/3125) -- Bug: Validation rules max\_size [\#3122](https://github.com/codeigniter4/CodeIgniter4/issues/3122) -- Bug: Pagination not working with soft deleted items [\#3121](https://github.com/codeigniter4/CodeIgniter4/issues/3121) -- Bug: SQLite3 database file created in /public folder [\#3113](https://github.com/codeigniter4/CodeIgniter4/issues/3113) -- Bug: RedisHandler does not working on sessionExpiration is zero [\#3111](https://github.com/codeigniter4/CodeIgniter4/issues/3111) -- Bug: Implicit controller methods failing [\#3105](https://github.com/codeigniter4/CodeIgniter4/issues/3105) -- Bug: Custom Validation Error Messages [\#3097](https://github.com/codeigniter4/CodeIgniter4/issues/3097) -- Bug: API\ResponseTrait::respondNoContent return content-type header [\#3087](https://github.com/codeigniter4/CodeIgniter4/issues/3087) -- Bug: Running Feature Tests on multiple endpoints using filters [\#3085](https://github.com/codeigniter4/CodeIgniter4/issues/3085) -- Bug: FeatureResponse::isOk and redirects [\#3072](https://github.com/codeigniter4/CodeIgniter4/issues/3072) -- Documentation: "First Application" form validating before submitting [\#3071](https://github.com/codeigniter4/CodeIgniter4/issues/3071) -- Bug: Fatal error: Cannot declare class CodeIgniter\Exceptions\PageNotFoundException [\#3067](https://github.com/codeigniter4/CodeIgniter4/issues/3067) -- Bug: Risky feature tests with Controller returns [\#3063](https://github.com/codeigniter4/CodeIgniter4/issues/3063) -- Bug: Documentation news app project [\#3054](https://github.com/codeigniter4/CodeIgniter4/issues/3054) -- Bug: ClassMethod [\#3050](https://github.com/codeigniter4/CodeIgniter4/issues/3050) -- Bug: CodeIgniter\Router\RouteCollection-\>fillRouteParams\(\) ErrorException on 'from' parameter containing regex pattern with '|' symbol [\#3048](https://github.com/codeigniter4/CodeIgniter4/issues/3048) -- Bug: Database connection [\#3043](https://github.com/codeigniter4/CodeIgniter4/issues/3043) -- Bug: Route redirection not working [\#3041](https://github.com/codeigniter4/CodeIgniter4/issues/3041) -- Bug: Model::getValidationRules\(\) cant handle ::$validationRules if its a string [\#3039](https://github.com/codeigniter4/CodeIgniter4/issues/3039) -- Bug: Body data from curlrequest become unreadable when endpoint return long data [\#3034](https://github.com/codeigniter4/CodeIgniter4/issues/3034) -- Bug: File Validation not validate correctly. [\#3032](https://github.com/codeigniter4/CodeIgniter4/issues/3032) -- Bug: Fatal error when no user-agent is available [\#3029](https://github.com/codeigniter4/CodeIgniter4/issues/3029) -- Bug: Not possible manipulate results of renderSection\(\) on View Layouts [\#3028](https://github.com/codeigniter4/CodeIgniter4/issues/3028) -- Bug: Helpers at non-default locations are not found [\#3026](https://github.com/codeigniter4/CodeIgniter4/issues/3026) -- Bug: Most validation rules enforce requirement of the field [\#3025](https://github.com/codeigniter4/CodeIgniter4/issues/3025) -- Bug: [\#3021](https://github.com/codeigniter4/CodeIgniter4/issues/3021) -- Bug: getPostGet\($index\) return $\_POST if there is no $index in post and get [\#3020](https://github.com/codeigniter4/CodeIgniter4/issues/3020) -- Bug: Multiple File Uploads validation rule "uploaded\[inputName\]" does not work when the input name is an Array [\#3018](https://github.com/codeigniter4/CodeIgniter4/issues/3018) -- Bug: delete\_files\(\) and hidden directories [\#3015](https://github.com/codeigniter4/CodeIgniter4/issues/3015) -- Bug: Model::Insert\(\) does not throw exception when object without data is passed as parameter [\#2998](https://github.com/codeigniter4/CodeIgniter4/issues/2998) -- Bug: Force download [\#2995](https://github.com/codeigniter4/CodeIgniter4/issues/2995) -- Bug: The example of "The Test Class“ is not working in Documets [\#2993](https://github.com/codeigniter4/CodeIgniter4/issues/2993) -- Bug: Difference in behaviour of native DateTime::setTimezone\(\) and CI's Time:setTimezone\(\) [\#2989](https://github.com/codeigniter4/CodeIgniter4/issues/2989) -- Bug: Blank Page on Production Server [\#2980](https://github.com/codeigniter4/CodeIgniter4/issues/2980) -- Bug: Mistake in uri\_string\(\) documentation [\#2972](https://github.com/codeigniter4/CodeIgniter4/issues/2972) -- Bug: route\_to\(\) does not return relative part of the path [\#2971](https://github.com/codeigniter4/CodeIgniter4/issues/2971) -- Bug: Encrypter-\>decrypt issue? [\#2970](https://github.com/codeigniter4/CodeIgniter4/issues/2970) -- Bug: form\_upload second parameter VALUE is not used in source code [\#2967](https://github.com/codeigniter4/CodeIgniter4/issues/2967) -- Bug: There's no way to change default spark serve port using code [\#2966](https://github.com/codeigniter4/CodeIgniter4/issues/2966) -- Bug: 'method' is an empty string in PATH\_INFO causes Unhandled Exception [\#2965](https://github.com/codeigniter4/CodeIgniter4/issues/2965) -- Bug: URI class - working with segments is really strange [\#2962](https://github.com/codeigniter4/CodeIgniter4/issues/2962) -- Bug: Documentation Issue [\#2960](https://github.com/codeigniter4/CodeIgniter4/issues/2960) -- Query Builder set update not working correctly [\#2959](https://github.com/codeigniter4/CodeIgniter4/issues/2959) -- Bug: Validation permit\_empty does no work together with required\_with and required\_without [\#2953](https://github.com/codeigniter4/CodeIgniter4/issues/2953) -- Bug: Validation tries to validate inputs even for empty values with no `required` parameter [\#2951](https://github.com/codeigniter4/CodeIgniter4/issues/2951) -- Bug: Request URI segment is our of range CI 4.0.3 [\#2949](https://github.com/codeigniter4/CodeIgniter4/issues/2949) -- Bug: start migration in manual mode [\#2942](https://github.com/codeigniter4/CodeIgniter4/issues/2942) -- valid\_url rule from validation not working correctly same as valid\_url|required [\#2941](https://github.com/codeigniter4/CodeIgniter4/issues/2941) -- redirect route when route is more complex [\#2937](https://github.com/codeigniter4/CodeIgniter4/issues/2937) -- set\_value does not work with NULL as second parameter [\#2935](https://github.com/codeigniter4/CodeIgniter4/issues/2935) -- Bug: init of $data arrays in controllers [\#2933](https://github.com/codeigniter4/CodeIgniter4/issues/2933) -- Bug: Translation key separated by dot [\#2932](https://github.com/codeigniter4/CodeIgniter4/issues/2932) -- Bug: Model Instantiation [\#2924](https://github.com/codeigniter4/CodeIgniter4/issues/2924) -- Model [\#2923](https://github.com/codeigniter4/CodeIgniter4/issues/2923) -- Bug: CURLRequest baseURI option in user guide [\#2922](https://github.com/codeigniter4/CodeIgniter4/issues/2922) -- Bug: Not bug, but Fix Documentation, please [\#2920](https://github.com/codeigniter4/CodeIgniter4/issues/2920) -- Bug: Postgresql API call delete use -\>connID-\>affected\_rows after Model::delete\(\) got error [\#2918](https://github.com/codeigniter4/CodeIgniter4/issues/2918) -- Multiple table query Model first [\#2885](https://github.com/codeigniter4/CodeIgniter4/issues/2885) -- Bug: pager "prev" and "next" links pointing to wrong URIs [\#2881](https://github.com/codeigniter4/CodeIgniter4/issues/2881) -- Bug: Automatic no CLI colors for Windows terminals [\#2849](https://github.com/codeigniter4/CodeIgniter4/issues/2849) -- Bug: $format in ResourceController is ignored [\#2828](https://github.com/codeigniter4/CodeIgniter4/issues/2828) -- Bug: "Type is not supported" in Postgresql POST restful [\#2812](https://github.com/codeigniter4/CodeIgniter4/issues/2812) -- Bug: Cookie Helper and Response class issue [\#2783](https://github.com/codeigniter4/CodeIgniter4/issues/2783) -- Bug: Models, useSoftDeletes not found in findAll [\#2658](https://github.com/codeigniter4/CodeIgniter4/issues/2658) -- Feature: About the SameSite COOKIE RFC changes for PHP \> 7.3 [\#2374](https://github.com/codeigniter4/CodeIgniter4/issues/2374) - -**Closed issues:** - -- Language folders inside locale folders [\#3300](https://github.com/codeigniter4/CodeIgniter4/issues/3300) -- Encryption Class - Decrypting: authentication failed [\#3258](https://github.com/codeigniter4/CodeIgniter4/issues/3258) -- form\_upload second parameter VALUE was not used in source code, i found a way to do it. [\#3256](https://github.com/codeigniter4/CodeIgniter4/issues/3256) -- erorr line in resize image using fit in visual studio code [\#3249](https://github.com/codeigniter4/CodeIgniter4/issues/3249) -- Testing withSession\(\) generates an error. [\#3190](https://github.com/codeigniter4/CodeIgniter4/issues/3190) -- Why is getGetPost\(\) returning all data instead of null when index not found? [\#3187](https://github.com/codeigniter4/CodeIgniter4/issues/3187) -- Request setGlobal not works [\#3186](https://github.com/codeigniter4/CodeIgniter4/issues/3186) -- Cannot extend core HTTPException class [\#3178](https://github.com/codeigniter4/CodeIgniter4/issues/3178) -- Add this relationship in model. [\#3170](https://github.com/codeigniter4/CodeIgniter4/issues/3170) -- Execute bootstrapEnvironment\(\) & detectEnvironment\(\) before Services::exceptions\(\) [\#3138](https://github.com/codeigniter4/CodeIgniter4/issues/3138) -- Bag pars array to xml [\#3092](https://github.com/codeigniter4/CodeIgniter4/issues/3092) -- utf-8 slug character doesn't work in url [\#3089](https://github.com/codeigniter4/CodeIgniter4/issues/3089) -- Grammar in comment [\#3064](https://github.com/codeigniter4/CodeIgniter4/issues/3064) -- line 374 in system/Entity.php json\_encode add JSON\_UNESCAPED\_UNICODE [\#3059](https://github.com/codeigniter4/CodeIgniter4/issues/3059) -- Link to User Guide on README.md [\#3053](https://github.com/codeigniter4/CodeIgniter4/issues/3053) -- How to set private properties in CodeIgniter\Database\BaseResult::getCustomResultObject\(\) [\#3051](https://github.com/codeigniter4/CodeIgniter4/issues/3051) -- url\_title not detecting some special characteres [\#3038](https://github.com/codeigniter4/CodeIgniter4/issues/3038) -- Error: No input file specified. [\#3030](https://github.com/codeigniter4/CodeIgniter4/issues/3030) -- Can findAll function in modeling data accept 3rd parameter reset [\#3024](https://github.com/codeigniter4/CodeIgniter4/issues/3024) -- Class 'App\Models\UserModel' not found [\#3014](https://github.com/codeigniter4/CodeIgniter4/issues/3014) -- Image reorient with exif [\#3006](https://github.com/codeigniter4/CodeIgniter4/issues/3006) -- set cookieHTTPOnly not work [\#2999](https://github.com/codeigniter4/CodeIgniter4/issues/2999) -- \[Feature request\] Slugs Link [\#2988](https://github.com/codeigniter4/CodeIgniter4/issues/2988) -- \[Feature request\] End processing app [\#2982](https://github.com/codeigniter4/CodeIgniter4/issues/2982) -- Pager should have getTotalResults method function [\#2954](https://github.com/codeigniter4/CodeIgniter4/issues/2954) -- URL rewrite problem [\#2925](https://github.com/codeigniter4/CodeIgniter4/issues/2925) -- Translation file not get correct locale in 4.0.3 [\#2921](https://github.com/codeigniter4/CodeIgniter4/issues/2921) -- Why this route does not work ? [\#2919](https://github.com/codeigniter4/CodeIgniter4/issues/2919) -- Cache redis or memcached [\#2909](https://github.com/codeigniter4/CodeIgniter4/issues/2909) -- Cookie helper not woking [\#2848](https://github.com/codeigniter4/CodeIgniter4/issues/2848) -- Bug: Routing group filter not working on nested or complex routes [\#2390](https://github.com/codeigniter4/CodeIgniter4/issues/2390) - -**Merged pull requests:** - -- Changelog update [\#3322](https://github.com/codeigniter4/CodeIgniter4/pull/3322) ([michalsn](https://github.com/michalsn)) -- Changelog for 4.0.4 update [\#3321](https://github.com/codeigniter4/CodeIgniter4/pull/3321) ([michalsn](https://github.com/michalsn)) -- Fix protocol version for DownloadResponse [\#3320](https://github.com/codeigniter4/CodeIgniter4/pull/3320) ([michalsn](https://github.com/michalsn)) -- Add Honeypot::$container to env file [\#3318](https://github.com/codeigniter4/CodeIgniter4/pull/3318) ([paulbalandan](https://github.com/paulbalandan)) -- Pass filter arguments to after\(\) and before\(\) methods [\#3316](https://github.com/codeigniter4/CodeIgniter4/pull/3316) ([tangix](https://github.com/tangix)) -- count on \Config\Services [\#3308](https://github.com/codeigniter4/CodeIgniter4/pull/3308) ([mostafakhudair](https://github.com/mostafakhudair)) -- Add hex2bin prefix handling for encryption key [\#3307](https://github.com/codeigniter4/CodeIgniter4/pull/3307) ([michalsn](https://github.com/michalsn)) -- add break; in foreach at Time::getDst\(\) when daylightSaving set [\#3305](https://github.com/codeigniter4/CodeIgniter4/pull/3305) ([samsonasik](https://github.com/samsonasik)) -- New command: cache:clear [\#3304](https://github.com/codeigniter4/CodeIgniter4/pull/3304) ([lonnieezell](https://github.com/lonnieezell)) -- force\_https didn’t force https [\#3302](https://github.com/codeigniter4/CodeIgniter4/pull/3302) ([colethorsen](https://github.com/colethorsen)) -- add test for CommandRunner::\_remap\(\) with empty first params [\#3301](https://github.com/codeigniter4/CodeIgniter4/pull/3301) ([samsonasik](https://github.com/samsonasik)) -- FieldData -\> add typeName field and length field [\#3299](https://github.com/codeigniter4/CodeIgniter4/pull/3299) ([devorama](https://github.com/devorama)) -- reduce repetitive getDefaultNamespace\(\) and controllerName\(\) function call in Router [\#3298](https://github.com/codeigniter4/CodeIgniter4/pull/3298) ([samsonasik](https://github.com/samsonasik)) -- Fix PHPDocs for Filters [\#3296](https://github.com/codeigniter4/CodeIgniter4/pull/3296) ([paulbalandan](https://github.com/paulbalandan)) -- Fix PHPDocs for HTTP [\#3295](https://github.com/codeigniter4/CodeIgniter4/pull/3295) ([paulbalandan](https://github.com/paulbalandan)) -- Update phpdoc.dist.xml [\#3293](https://github.com/codeigniter4/CodeIgniter4/pull/3293) ([paulbalandan](https://github.com/paulbalandan)) -- Fix for force\_https\(\) function [\#3292](https://github.com/codeigniter4/CodeIgniter4/pull/3292) ([michalsn](https://github.com/michalsn)) -- Fix PHPDocs for I18n [\#3291](https://github.com/codeigniter4/CodeIgniter4/pull/3291) ([paulbalandan](https://github.com/paulbalandan)) -- Fix PHPDocs for Router [\#3290](https://github.com/codeigniter4/CodeIgniter4/pull/3290) ([paulbalandan](https://github.com/paulbalandan)) -- Fix PHPDocs for CLI [\#3289](https://github.com/codeigniter4/CodeIgniter4/pull/3289) ([paulbalandan](https://github.com/paulbalandan)) -- add JSON\_NUMERIC\_CHECK to json encode options [\#3288](https://github.com/codeigniter4/CodeIgniter4/pull/3288) ([devorama](https://github.com/devorama)) -- typo fix s/Memcached/Redis in RedisHandler cache [\#3285](https://github.com/codeigniter4/CodeIgniter4/pull/3285) ([samsonasik](https://github.com/samsonasik)) -- optimize Session : use foreach instead of for with count when possible [\#3284](https://github.com/codeigniter4/CodeIgniter4/pull/3284) ([samsonasik](https://github.com/samsonasik)) -- using strpos instead of substr when possible [\#3283](https://github.com/codeigniter4/CodeIgniter4/pull/3283) ([samsonasik](https://github.com/samsonasik)) -- optimize Database BaseBuilder : use foreach instead of for with count when possible [\#3282](https://github.com/codeigniter4/CodeIgniter4/pull/3282) ([samsonasik](https://github.com/samsonasik)) -- optimize RouteCollection : use foreach instead of for with count when possible [\#3281](https://github.com/codeigniter4/CodeIgniter4/pull/3281) ([samsonasik](https://github.com/samsonasik)) -- optimize FileLocator autoloader : use foreach instead of for with count when possible [\#3280](https://github.com/codeigniter4/CodeIgniter4/pull/3280) ([samsonasik](https://github.com/samsonasik)) -- Fix "100 Continue" header handling in CURLRequest class [\#3274](https://github.com/codeigniter4/CodeIgniter4/pull/3274) ([michalsn](https://github.com/michalsn)) -- cs fix in Session class [\#3272](https://github.com/codeigniter4/CodeIgniter4/pull/3272) ([samsonasik](https://github.com/samsonasik)) -- Throttler code style update [\#3271](https://github.com/codeigniter4/CodeIgniter4/pull/3271) ([michalsn](https://github.com/michalsn)) -- cs : remove unused import use statements and sort use statements [\#3270](https://github.com/codeigniter4/CodeIgniter4/pull/3270) ([samsonasik](https://github.com/samsonasik)) -- Add more URI class tests to fully illustrate current behavior [\#3269](https://github.com/codeigniter4/CodeIgniter4/pull/3269) ([michalsn](https://github.com/michalsn)) -- Fix Image::save\(\) when target value is null [\#3268](https://github.com/codeigniter4/CodeIgniter4/pull/3268) ([michalsn](https://github.com/michalsn)) -- Use named variable in honeypot container [\#3267](https://github.com/codeigniter4/CodeIgniter4/pull/3267) ([michalsn](https://github.com/michalsn)) -- Check server headers via Request class [\#3265](https://github.com/codeigniter4/CodeIgniter4/pull/3265) ([michalsn](https://github.com/michalsn)) -- PHPUnit-annotate untestable code in CLI [\#3264](https://github.com/codeigniter4/CodeIgniter4/pull/3264) ([paulbalandan](https://github.com/paulbalandan)) -- Update phpdocs of Cache library [\#3263](https://github.com/codeigniter4/CodeIgniter4/pull/3263) ([paulbalandan](https://github.com/paulbalandan)) -- Update htaccess [\#3262](https://github.com/codeigniter4/CodeIgniter4/pull/3262) ([paulbalandan](https://github.com/paulbalandan)) -- Fixes \#3125 : add ability to override existing translation en in system language from App [\#3254](https://github.com/codeigniter4/CodeIgniter4/pull/3254) ([samsonasik](https://github.com/samsonasik)) -- Add Fabricator model error [\#3253](https://github.com/codeigniter4/CodeIgniter4/pull/3253) ([MGatner](https://github.com/MGatner)) -- Implement model callback overrides [\#3252](https://github.com/codeigniter4/CodeIgniter4/pull/3252) ([MGatner](https://github.com/MGatner)) -- Fix PHPDocBlock of Images library [\#3250](https://github.com/codeigniter4/CodeIgniter4/pull/3250) ([paulbalandan](https://github.com/paulbalandan)) -- Update html\_helper.rst [\#3248](https://github.com/codeigniter4/CodeIgniter4/pull/3248) ([avegacms](https://github.com/avegacms)) -- Update html\_helper.php [\#3247](https://github.com/codeigniter4/CodeIgniter4/pull/3247) ([avegacms](https://github.com/avegacms)) -- Syntax fix for migration.rst [\#3244](https://github.com/codeigniter4/CodeIgniter4/pull/3244) ([paulbalandan](https://github.com/paulbalandan)) -- Run apidocs action only when system files are changed [\#3241](https://github.com/codeigniter4/CodeIgniter4/pull/3241) ([paulbalandan](https://github.com/paulbalandan)) -- Fix formatting issue in userguide/outgoing/view\_parser [\#3240](https://github.com/codeigniter4/CodeIgniter4/pull/3240) ([Connum](https://github.com/Connum)) -- Add ability to call commands programatically. [\#3238](https://github.com/codeigniter4/CodeIgniter4/pull/3238) ([lonnieezell](https://github.com/lonnieezell)) -- Reset Filters between feature tests [\#3237](https://github.com/codeigniter4/CodeIgniter4/pull/3237) ([MGatner](https://github.com/MGatner)) -- Fix CLI::validate\(\) usage when using dot sign [\#3236](https://github.com/codeigniter4/CodeIgniter4/pull/3236) ([michalsn](https://github.com/michalsn)) -- Remove cilexer from gitignore [\#3235](https://github.com/codeigniter4/CodeIgniter4/pull/3235) ([paulbalandan](https://github.com/paulbalandan)) -- Feature seed command [\#3234](https://github.com/codeigniter4/CodeIgniter4/pull/3234) ([abilioposada](https://github.com/abilioposada)) -- more test Files\File class [\#3232](https://github.com/codeigniter4/CodeIgniter4/pull/3232) ([samsonasik](https://github.com/samsonasik)) -- more tests on Autoloader\FileLocator [\#3231](https://github.com/codeigniter4/CodeIgniter4/pull/3231) ([samsonasik](https://github.com/samsonasik)) -- Fix set\_checkbox\(\) and set\_radio\(\) when default is set to true [\#3229](https://github.com/codeigniter4/CodeIgniter4/pull/3229) ([michalsn](https://github.com/michalsn)) -- Use Throwable in catch block of filesystem helper [\#3227](https://github.com/codeigniter4/CodeIgniter4/pull/3227) ([paulbalandan](https://github.com/paulbalandan)) -- Check color support for STDERR for CLI::error [\#3226](https://github.com/codeigniter4/CodeIgniter4/pull/3226) ([paulbalandan](https://github.com/paulbalandan)) -- Fix 'exit code 23' in apidocs action [\#3225](https://github.com/codeigniter4/CodeIgniter4/pull/3225) ([paulbalandan](https://github.com/paulbalandan)) -- Fix result object handling in Model class [\#3224](https://github.com/codeigniter4/CodeIgniter4/pull/3224) ([michalsn](https://github.com/michalsn)) -- Fix update query to return false on error [\#3223](https://github.com/codeigniter4/CodeIgniter4/pull/3223) ([michalsn](https://github.com/michalsn)) -- Fix insert Entity object on postgres [\#3222](https://github.com/codeigniter4/CodeIgniter4/pull/3222) ([MashinaMashina](https://github.com/MashinaMashina)) -- DatabaseTestCase migrations [\#3221](https://github.com/codeigniter4/CodeIgniter4/pull/3221) ([MGatner](https://github.com/MGatner)) -- Allow ignoring \*.db inside folders [\#3220](https://github.com/codeigniter4/CodeIgniter4/pull/3220) ([paulbalandan](https://github.com/paulbalandan)) -- SQLite3 escapeChar fix [\#3219](https://github.com/codeigniter4/CodeIgniter4/pull/3219) ([michalsn](https://github.com/michalsn)) -- Remove underscore in UG build path [\#3218](https://github.com/codeigniter4/CodeIgniter4/pull/3218) ([paulbalandan](https://github.com/paulbalandan)) -- add test for Filters with empty except [\#3215](https://github.com/codeigniter4/CodeIgniter4/pull/3215) ([samsonasik](https://github.com/samsonasik)) -- remove unnecessary foreach in RouteCollection::checkSubdomains\(\) [\#3214](https://github.com/codeigniter4/CodeIgniter4/pull/3214) ([samsonasik](https://github.com/samsonasik)) -- Add Fabricator counts [\#3213](https://github.com/codeigniter4/CodeIgniter4/pull/3213) ([MGatner](https://github.com/MGatner)) -- Cast currentPage value to integer in Pager class [\#3209](https://github.com/codeigniter4/CodeIgniter4/pull/3209) ([michalsn](https://github.com/michalsn)) -- Allow dashes in the CLI segment [\#3208](https://github.com/codeigniter4/CodeIgniter4/pull/3208) ([michalsn](https://github.com/michalsn)) -- Fix for getting database error [\#3207](https://github.com/codeigniter4/CodeIgniter4/pull/3207) ([michalsn](https://github.com/michalsn)) -- Fixed \#3199 [\#3203](https://github.com/codeigniter4/CodeIgniter4/pull/3203) ([mpmont](https://github.com/mpmont)) -- Fix extra slash in URI [\#3202](https://github.com/codeigniter4/CodeIgniter4/pull/3202) ([MGatner](https://github.com/MGatner)) -- Update for FabricatorLiveTest [\#3201](https://github.com/codeigniter4/CodeIgniter4/pull/3201) ([michalsn](https://github.com/michalsn)) -- Add closing parenthesis [\#3200](https://github.com/codeigniter4/CodeIgniter4/pull/3200) ([abilioposada](https://github.com/abilioposada)) -- Hide in bootstrap4 [\#3197](https://github.com/codeigniter4/CodeIgniter4/pull/3197) ([Sosko](https://github.com/Sosko)) -- The permissions of the new file should be modified, not the old file [\#3196](https://github.com/codeigniter4/CodeIgniter4/pull/3196) ([wangyupeng](https://github.com/wangyupeng)) -- Fix default value for page in Model::paginate\(\) [\#3194](https://github.com/codeigniter4/CodeIgniter4/pull/3194) ([michalsn](https://github.com/michalsn)) -- Test Case Mocking [\#3193](https://github.com/codeigniter4/CodeIgniter4/pull/3193) ([MGatner](https://github.com/MGatner)) -- ArgType 'String' is not valid in MessageFormatter class \(fixes \#3191\) [\#3192](https://github.com/codeigniter4/CodeIgniter4/pull/3192) ([HughieW](https://github.com/HughieW)) -- Bugfix: Multipart Content-Length [\#3189](https://github.com/codeigniter4/CodeIgniter4/pull/3189) ([MGatner](https://github.com/MGatner)) -- Add apidocs action [\#3183](https://github.com/codeigniter4/CodeIgniter4/pull/3183) ([paulbalandan](https://github.com/paulbalandan)) -- Improve CLI clear screen [\#3182](https://github.com/codeigniter4/CodeIgniter4/pull/3182) ([paulbalandan](https://github.com/paulbalandan)) -- Refactor color detection in CLI [\#3181](https://github.com/codeigniter4/CodeIgniter4/pull/3181) ([paulbalandan](https://github.com/paulbalandan)) -- Use explicit 'PHP\_EOL' in wordwrap [\#3179](https://github.com/codeigniter4/CodeIgniter4/pull/3179) ([paulbalandan](https://github.com/paulbalandan)) -- Add webp support to Image class [\#3176](https://github.com/codeigniter4/CodeIgniter4/pull/3176) ([michalsn](https://github.com/michalsn)) -- Fix for multicolored strings in CLI [\#3175](https://github.com/codeigniter4/CodeIgniter4/pull/3175) ([michalsn](https://github.com/michalsn)) -- Add Unicode support for regular expressions in router [\#3172](https://github.com/codeigniter4/CodeIgniter4/pull/3172) ([michalsn](https://github.com/michalsn)) -- Add the upload artifact action [\#3167](https://github.com/codeigniter4/CodeIgniter4/pull/3167) ([paulbalandan](https://github.com/paulbalandan)) -- More robust color support detection in CLI [\#3165](https://github.com/codeigniter4/CodeIgniter4/pull/3165) ([paulbalandan](https://github.com/paulbalandan)) -- Fix testMode\(\) for batch methods in Model [\#3163](https://github.com/codeigniter4/CodeIgniter4/pull/3163) ([michalsn](https://github.com/michalsn)) -- Update delete\_files\(\) helper function [\#3162](https://github.com/codeigniter4/CodeIgniter4/pull/3162) ([michalsn](https://github.com/michalsn)) -- Refresh Composer files [\#3153](https://github.com/codeigniter4/CodeIgniter4/pull/3153) ([MGatner](https://github.com/MGatner)) -- use writable directory for sqlite default location [\#3151](https://github.com/codeigniter4/CodeIgniter4/pull/3151) ([samsonasik](https://github.com/samsonasik)) -- Add webp support to Image Manipulation Class [\#3084](https://github.com/codeigniter4/CodeIgniter4/pull/3084) ([nicojmb](https://github.com/nicojmb)) -- Bug fix in Throttler class check validation [\#2873](https://github.com/codeigniter4/CodeIgniter4/pull/2873) ([jlamim](https://github.com/jlamim)) - -## [v4.0.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.3) (2020-05-01) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/4.0.2...v4.0.3) - -**Fixed bugs:** - -- Bug: is\_unique validation rule with model-\>save\(\) fails [\#2906](https://github.com/codeigniter4/CodeIgniter4/issues/2906) -- Bug: Debugging toolbar not showing [\#2893](https://github.com/codeigniter4/CodeIgniter4/issues/2893) -- Bug: database query bug [\#2890](https://github.com/codeigniter4/CodeIgniter4/issues/2890) -- Bug: Routes - missing \(:id\) [\#2889](https://github.com/codeigniter4/CodeIgniter4/issues/2889) -- Bug: Typo example "Using Named Routes" [\#2888](https://github.com/codeigniter4/CodeIgniter4/issues/2888) -- Bug: I am able to make POST request to a GET route [\#2880](https://github.com/codeigniter4/CodeIgniter4/issues/2880) -- Bug: Argument 1 passed to CodeIgniter\Database\BaseResult::getFirstRow\(\) must be of the type string, null given, called in /system/Model.php on line 383 [\#2877](https://github.com/codeigniter4/CodeIgniter4/issues/2877) -- Bug: Can't override already defined language in pager [\#2875](https://github.com/codeigniter4/CodeIgniter4/issues/2875) -- Installation Problem: Composer installation halts while installing kint-php/kint [\#2863](https://github.com/codeigniter4/CodeIgniter4/issues/2863) -- Bug: `composer require codeigniter4/translations` without stable version [\#2862](https://github.com/codeigniter4/CodeIgniter4/issues/2862) -- Bug: Method Spoofing Validation [\#2855](https://github.com/codeigniter4/CodeIgniter4/issues/2855) -- Bug: You made it just as impossible to get going as Laravel – BUMMER! [\#2850](https://github.com/codeigniter4/CodeIgniter4/issues/2850) -- Bug: localised validation messages [\#2845](https://github.com/codeigniter4/CodeIgniter4/issues/2845) -- Bug: Commands discovery in custom namespaces [\#2840](https://github.com/codeigniter4/CodeIgniter4/issues/2840) -- Bug: When the $index parameter of getGetPost or getPostGet is null, you will get an error result [\#2839](https://github.com/codeigniter4/CodeIgniter4/issues/2839) -- Bug: delete\_cookie\(\) doesn't work [\#2836](https://github.com/codeigniter4/CodeIgniter4/issues/2836) -- Bug: Model save method not working if PRIMARY KEY is VARCHAR [\#2835](https://github.com/codeigniter4/CodeIgniter4/issues/2835) -- Bug: Alias Filter with Multiple Filter Class Doesn't Work [\#2831](https://github.com/codeigniter4/CodeIgniter4/issues/2831) -- Bug: Kint Config isnt loaded [\#2830](https://github.com/codeigniter4/CodeIgniter4/issues/2830) -- Bug: RouteCollection::resource\(\) doesn't work with grouped rules [\#2829](https://github.com/codeigniter4/CodeIgniter4/issues/2829) -- Bug: $forge Property in Seeder Class Never Initialize [\#2825](https://github.com/codeigniter4/CodeIgniter4/issues/2825) -- Bug: getSegments\(\) returns an array with 2 empty strings when accessing / [\#2822](https://github.com/codeigniter4/CodeIgniter4/issues/2822) -- Bug: Cell Caching in View Cells and SOLVE! [\#2821](https://github.com/codeigniter4/CodeIgniter4/issues/2821) -- Bug: saveData option doesn't work in Views [\#2818](https://github.com/codeigniter4/CodeIgniter4/issues/2818) -- Bug: Validation placeholder not being replaced [\#2817](https://github.com/codeigniter4/CodeIgniter4/issues/2817) -- Bug: Problems with QueryBuilder when run multiple queries one by one [\#2800](https://github.com/codeigniter4/CodeIgniter4/issues/2800) -- Bug: Routing placeholder in "controller"part of route doesn't work [\#2787](https://github.com/codeigniter4/CodeIgniter4/issues/2787) -- Bug: session\(\)-\>push\(\) Strange behavior [\#2786](https://github.com/codeigniter4/CodeIgniter4/issues/2786) -- Bug: php spark serve [\#2784](https://github.com/codeigniter4/CodeIgniter4/issues/2784) -- Bug: Can't paginate query with group by [\#2776](https://github.com/codeigniter4/CodeIgniter4/issues/2776) -- Bug: negotiateLocale bug in Safari with fr-ca locale [\#2774](https://github.com/codeigniter4/CodeIgniter4/issues/2774) -- Bug: Controller in Sub Directory is not working [\#2764](https://github.com/codeigniter4/CodeIgniter4/issues/2764) -- Bug: rename release By "v" [\#2763](https://github.com/codeigniter4/CodeIgniter4/issues/2763) -- Bug: db query '?' bind is not working when use sql with ':=' operator. [\#2762](https://github.com/codeigniter4/CodeIgniter4/issues/2762) -- Bug: Multiple select validation problem [\#2757](https://github.com/codeigniter4/CodeIgniter4/issues/2757) -- Bug: Official Site is not working [\#2749](https://github.com/codeigniter4/CodeIgniter4/issues/2749) -- Bug: Logger context placeholders {file} and {line} are wrong [\#2743](https://github.com/codeigniter4/CodeIgniter4/issues/2743) -- Bug: Decimal validation fails without leading digit [\#2740](https://github.com/codeigniter4/CodeIgniter4/issues/2740) -- Bug: Model insert Created\_at and updated\_at get when new record added [\#2737](https://github.com/codeigniter4/CodeIgniter4/issues/2737) -- Bug: appendHeader 500 error if header does not exist [\#2730](https://github.com/codeigniter4/CodeIgniter4/issues/2730) -- Bug: codeigniter4 download link 404 resource not found [\#2727](https://github.com/codeigniter4/CodeIgniter4/issues/2727) -- Bug: Logger `path` property ignored [\#2725](https://github.com/codeigniter4/CodeIgniter4/issues/2725) -- Bug: $this-\>request-\>getPost\(\) is empty when json is send by postman [\#2720](https://github.com/codeigniter4/CodeIgniter4/issues/2720) -- Bug: open path /0 at uri got error "Class Home does not exist" on development environment [\#2716](https://github.com/codeigniter4/CodeIgniter4/issues/2716) -- Bug: calling countAllResults after find\($id\) produce wrong result [\#2705](https://github.com/codeigniter4/CodeIgniter4/issues/2705) -- Bug: $routes-\>cli\(\) accessible via web browser if autoroute is true [\#2704](https://github.com/codeigniter4/CodeIgniter4/issues/2704) -- Bug: Controllers and Views in subdirectories not working [\#2701](https://github.com/codeigniter4/CodeIgniter4/issues/2701) -- Bug: undefined model method should throw exception [\#2688](https://github.com/codeigniter4/CodeIgniter4/issues/2688) -- Bug: The custom error config of validation is not working [\#2678](https://github.com/codeigniter4/CodeIgniter4/issues/2678) -- Bug: Can't test redirect\(\)-\>route\('routename'\), while redirect\(\)-\>to\('path'\) is working with ControllerTester [\#2676](https://github.com/codeigniter4/CodeIgniter4/issues/2676) -- Bug: php spark migrate:create File -n NameSpace doesn't create migration class under NameSpace/Database/Migrations directory with composer autoload [\#2664](https://github.com/codeigniter4/CodeIgniter4/issues/2664) -- Bug: \I18n\Time object displaying +1 year when object date is set to 2021-12-31 [\#2663](https://github.com/codeigniter4/CodeIgniter4/issues/2663) -- Bug: Route options filter didn't working [\#2654](https://github.com/codeigniter4/CodeIgniter4/issues/2654) -- Bug: Error in Seeder [\#2653](https://github.com/codeigniter4/CodeIgniter4/issues/2653) -- Bug: spark no longer lists function when used by without any parameters [\#2645](https://github.com/codeigniter4/CodeIgniter4/issues/2645) -- Bug: Number Helper, Currency Fraction issue [\#2634](https://github.com/codeigniter4/CodeIgniter4/issues/2634) -- Bug: forceHTTPS method ignores baseURL configuration when redirecting [\#2633](https://github.com/codeigniter4/CodeIgniter4/issues/2633) -- While serving Application on CLI using different port debugbar is still using a default 8080 port [\#2630](https://github.com/codeigniter4/CodeIgniter4/issues/2630) -- Bug: spark migrate -all with appstarter [\#2627](https://github.com/codeigniter4/CodeIgniter4/issues/2627) -- Bug: Problem when compiled vendor as PHAR file [\#2623](https://github.com/codeigniter4/CodeIgniter4/issues/2623) -- Bug: debugbar javascript error [\#2621](https://github.com/codeigniter4/CodeIgniter4/issues/2621) -- Bug: ResourceController json response always empty [\#2617](https://github.com/codeigniter4/CodeIgniter4/issues/2617) -- Bug: Chrome logger does not work. [\#2616](https://github.com/codeigniter4/CodeIgniter4/issues/2616) -- Bug: [\#2608](https://github.com/codeigniter4/CodeIgniter4/issues/2608) -- User Guide is not in HTML in the download file [\#2607](https://github.com/codeigniter4/CodeIgniter4/issues/2607) -- Unnecessary files in the download installation [\#2606](https://github.com/codeigniter4/CodeIgniter4/issues/2606) -- Bug: Class 'Kint\Renderer\Renderer' not found [\#2605](https://github.com/codeigniter4/CodeIgniter4/issues/2605) -- Bug: Codeigniter4/framework composer.json not updated [\#2601](https://github.com/codeigniter4/CodeIgniter4/issues/2601) -- \[Docs\] Loading Environment into Configuration documentation described wrong [\#2554](https://github.com/codeigniter4/CodeIgniter4/issues/2554) -- Bug: Sessions dont work on PostgreSQL [\#2546](https://github.com/codeigniter4/CodeIgniter4/issues/2546) -- Bug: router service adds backslash to controllername if route is configured [\#2520](https://github.com/codeigniter4/CodeIgniter4/issues/2520) -- Bug: JSONFormatter-\>format\(\) cannot handle errordata, only outputs it's own error [\#2434](https://github.com/codeigniter4/CodeIgniter4/issues/2434) -- Bug: HTTP Feature Testing only runs the FIRST test [\#2393](https://github.com/codeigniter4/CodeIgniter4/issues/2393) -- Bug: Spark issue with PHP install location [\#2367](https://github.com/codeigniter4/CodeIgniter4/issues/2367) -- spark route issue [\#2194](https://github.com/codeigniter4/CodeIgniter4/issues/2194) - -**Closed issues:** - -- Modular MVP on CI4 [\#2900](https://github.com/codeigniter4/CodeIgniter4/issues/2900) -- About javascript: void \(0\); [\#2887](https://github.com/codeigniter4/CodeIgniter4/issues/2887) -- Entity returns null when used on the model [\#2838](https://github.com/codeigniter4/CodeIgniter4/issues/2838) -- php spark migrate -g does not work [\#2832](https://github.com/codeigniter4/CodeIgniter4/issues/2832) -- Bug: Namespacing of app/Config folder vs. app/Controller [\#2826](https://github.com/codeigniter4/CodeIgniter4/issues/2826) -- Controller Call to a member function getPost\(\) on null [\#2823](https://github.com/codeigniter4/CodeIgniter4/issues/2823) -- QueryBuilder - Does not support JOIN in UPDATE [\#2799](https://github.com/codeigniter4/CodeIgniter4/issues/2799) -- Database model error when limiting delete\(\) [\#2780](https://github.com/codeigniter4/CodeIgniter4/issues/2780) -- codeigniter4/codeigniter4 package not exists in packagist [\#2753](https://github.com/codeigniter4/CodeIgniter4/issues/2753) -- datamap Entities not works! [\#2747](https://github.com/codeigniter4/CodeIgniter4/issues/2747) -- Error: Call to undefined function CodeIgniter\CLI\mb\_strpos\(\) [\#2746](https://github.com/codeigniter4/CodeIgniter4/issues/2746) -- CodeIgniter\Log\Logger::logPath property is never used. [\#2738](https://github.com/codeigniter4/CodeIgniter4/issues/2738) -- Bug: set\_radio\(\) in Form Helper does not work when radio button value equals "0" [\#2728](https://github.com/codeigniter4/CodeIgniter4/issues/2728) -- Array validation has a problem [\#2714](https://github.com/codeigniter4/CodeIgniter4/issues/2714) -- delete cookie not working [\#2700](https://github.com/codeigniter4/CodeIgniter4/issues/2700) -- remove default language local from url [\#2682](https://github.com/codeigniter4/CodeIgniter4/issues/2682) -- OpenSSLHandler: Encrypt/Decrypt [\#2680](https://github.com/codeigniter4/CodeIgniter4/issues/2680) -- RESTFUL API with CORS problem [\#2667](https://github.com/codeigniter4/CodeIgniter4/issues/2667) -- I guess there's no the third parameter [\#2657](https://github.com/codeigniter4/CodeIgniter4/issues/2657) -- set ci4 repo default branch = master [\#2643](https://github.com/codeigniter4/CodeIgniter4/issues/2643) -- BUG: 4.0.2 Kint not found [\#2639](https://github.com/codeigniter4/CodeIgniter4/issues/2639) -- Feature: Migrate:Rollback/Refresh confirmation in production environment [\#2385](https://github.com/codeigniter4/CodeIgniter4/issues/2385) - -**Merged pull requests:** - -- 4.0.3 release [\#2912](https://github.com/codeigniter4/CodeIgniter4/pull/2912) ([lonnieezell](https://github.com/lonnieezell)) -- url\_title\(\) used CI3 style in user guide [\#2911](https://github.com/codeigniter4/CodeIgniter4/pull/2911) ([jreklund](https://github.com/jreklund)) -- fix undefined class 'CodeIgniter' [\#2910](https://github.com/codeigniter4/CodeIgniter4/pull/2910) ([PingZii](https://github.com/PingZii)) -- Improved subjects in Controller and Routing chapter [\#2908](https://github.com/codeigniter4/CodeIgniter4/pull/2908) ([jreklund](https://github.com/jreklund)) -- Fix Model::first\(\) only use orderBy\(\) when group by is not empty [\#2907](https://github.com/codeigniter4/CodeIgniter4/pull/2907) ([samsonasik](https://github.com/samsonasik)) -- Allow bypassing content negotiation during API responses. [\#2904](https://github.com/codeigniter4/CodeIgniter4/pull/2904) ([lonnieezell](https://github.com/lonnieezell)) -- Ugtweaks [\#2903](https://github.com/codeigniter4/CodeIgniter4/pull/2903) ([lonnieezell](https://github.com/lonnieezell)) -- Carbonads [\#2902](https://github.com/codeigniter4/CodeIgniter4/pull/2902) ([lonnieezell](https://github.com/lonnieezell)) -- Added information about the new features of the Pagination library [\#2901](https://github.com/codeigniter4/CodeIgniter4/pull/2901) ([jlamim](https://github.com/jlamim)) -- New features for pagination [\#2899](https://github.com/codeigniter4/CodeIgniter4/pull/2899) ([jlamim](https://github.com/jlamim)) -- Fixed lang\(\) example in user guide [\#2898](https://github.com/codeigniter4/CodeIgniter4/pull/2898) ([nmolinos](https://github.com/nmolinos)) -- Make validation placeholders always available [\#2897](https://github.com/codeigniter4/CodeIgniter4/pull/2897) ([jreklund](https://github.com/jreklund)) -- \[ci skip\] Add `make.bat` for Windows users [\#2895](https://github.com/codeigniter4/CodeIgniter4/pull/2895) ([paulbalandan](https://github.com/paulbalandan)) -- Added ability to delete row with string primary key via Model::delete\($id\) [\#2894](https://github.com/codeigniter4/CodeIgniter4/pull/2894) ([samsonasik](https://github.com/samsonasik)) -- Update of the pagination template to make the correct use of the locale [\#2892](https://github.com/codeigniter4/CodeIgniter4/pull/2892) ([jlamim](https://github.com/jlamim)) -- \[ci skip\] route placeholders 'id' to 'num' [\#2891](https://github.com/codeigniter4/CodeIgniter4/pull/2891) ([Instrye](https://github.com/Instrye)) -- \[ci skip\] fix warnings on compiling user guide [\#2886](https://github.com/codeigniter4/CodeIgniter4/pull/2886) ([paulbalandan](https://github.com/paulbalandan)) -- Added more Common functions and improved rendering in userguide [\#2884](https://github.com/codeigniter4/CodeIgniter4/pull/2884) ([jreklund](https://github.com/jreklund)) -- Build Your First Application used url\_title incorrectly [\#2883](https://github.com/codeigniter4/CodeIgniter4/pull/2883) ([jreklund](https://github.com/jreklund)) -- \[User guide\] Correcting some details in the part that talks about model and entities [\#2878](https://github.com/codeigniter4/CodeIgniter4/pull/2878) ([jlamim](https://github.com/jlamim)) -- Shifted basic URI Routing examples down [\#2874](https://github.com/codeigniter4/CodeIgniter4/pull/2874) ([nmolinos](https://github.com/nmolinos)) -- Better locale matching against broad groups. Fixes \#2774 [\#2872](https://github.com/codeigniter4/CodeIgniter4/pull/2872) ([lonnieezell](https://github.com/lonnieezell)) -- Fixes session active detection on force\_https function and add more test CodeIgniter::forceSecureAccess\(\) run force\_https\(\) [\#2871](https://github.com/codeigniter4/CodeIgniter4/pull/2871) ([samsonasik](https://github.com/samsonasik)) -- clean up use statements: remove unused and sort [\#2870](https://github.com/codeigniter4/CodeIgniter4/pull/2870) ([samsonasik](https://github.com/samsonasik)) -- more test for View::renderString\(\) for null tempData [\#2869](https://github.com/codeigniter4/CodeIgniter4/pull/2869) ([samsonasik](https://github.com/samsonasik)) -- Localized label in validation rules [\#2868](https://github.com/codeigniter4/CodeIgniter4/pull/2868) ([michalsn](https://github.com/michalsn)) -- \[ci skip\] update translations version [\#2867](https://github.com/codeigniter4/CodeIgniter4/pull/2867) ([Instrye](https://github.com/Instrye)) -- Initialize $forge property in Seeder Class - fixes \#2825 [\#2864](https://github.com/codeigniter4/CodeIgniter4/pull/2864) ([jlamim](https://github.com/jlamim)) -- fix. saveData not work [\#2861](https://github.com/codeigniter4/CodeIgniter4/pull/2861) ([Instrye](https://github.com/Instrye)) -- fix. getGetPost and getPostGet can't work in index empty [\#2860](https://github.com/codeigniter4/CodeIgniter4/pull/2860) ([Instrye](https://github.com/Instrye)) -- \[ci skip\]fix. getHeader return header object [\#2859](https://github.com/codeigniter4/CodeIgniter4/pull/2859) ([Instrye](https://github.com/Instrye)) -- fix. filters alias multiple [\#2857](https://github.com/codeigniter4/CodeIgniter4/pull/2857) ([Instrye](https://github.com/Instrye)) -- \[ci skip\] typo fix Initial Configuration & Set Up [\#2856](https://github.com/codeigniter4/CodeIgniter4/pull/2856) ([samsonasik](https://github.com/samsonasik)) -- Enclose file paths in double quotes to capture spaces [\#2853](https://github.com/codeigniter4/CodeIgniter4/pull/2853) ([paulbalandan](https://github.com/paulbalandan)) -- Strip directory separators from auto-generated cell cache name. Fixes… [\#2851](https://github.com/codeigniter4/CodeIgniter4/pull/2851) ([lonnieezell](https://github.com/lonnieezell)) -- Normalize dir separator of Exceptions::cleanPath and added more paths to clean [\#2847](https://github.com/codeigniter4/CodeIgniter4/pull/2847) ([paulbalandan](https://github.com/paulbalandan)) -- Improve readability in the userguide with a fixed size [\#2846](https://github.com/codeigniter4/CodeIgniter4/pull/2846) ([jreklund](https://github.com/jreklund)) -- Fixed Issue \#2840 on discovery of classes by FileLocator [\#2844](https://github.com/codeigniter4/CodeIgniter4/pull/2844) ([paulbalandan](https://github.com/paulbalandan)) -- add $segment parameter in pager call by Model.php [\#2843](https://github.com/codeigniter4/CodeIgniter4/pull/2843) ([paul45](https://github.com/paul45)) -- Improve flash of unstyled content in userguide [\#2842](https://github.com/codeigniter4/CodeIgniter4/pull/2842) ([jreklund](https://github.com/jreklund)) -- Add English message for "string" validation rule [\#2841](https://github.com/codeigniter4/CodeIgniter4/pull/2841) ([rmilecki](https://github.com/rmilecki)) -- more tests for Common functions [\#2837](https://github.com/codeigniter4/CodeIgniter4/pull/2837) ([samsonasik](https://github.com/samsonasik)) -- Pagination: open page \> pageCount get last page [\#2834](https://github.com/codeigniter4/CodeIgniter4/pull/2834) ([samsonasik](https://github.com/samsonasik)) -- add ability for nested language definition [\#2833](https://github.com/codeigniter4/CodeIgniter4/pull/2833) ([samsonasik](https://github.com/samsonasik)) -- Documentation fixes [\#2827](https://github.com/codeigniter4/CodeIgniter4/pull/2827) ([pjio](https://github.com/pjio)) -- fix. URI path is empty [\#2824](https://github.com/codeigniter4/CodeIgniter4/pull/2824) ([Instrye](https://github.com/Instrye)) -- ignore coverage on exit and die [\#2820](https://github.com/codeigniter4/CodeIgniter4/pull/2820) ([samsonasik](https://github.com/samsonasik)) -- add respondUpdated\(\) method into API\ResponseTrait [\#2816](https://github.com/codeigniter4/CodeIgniter4/pull/2816) ([samsonasik](https://github.com/samsonasik)) -- ignore coverage on !CI\_DEBUG [\#2814](https://github.com/codeigniter4/CodeIgniter4/pull/2814) ([samsonasik](https://github.com/samsonasik)) -- Fix missing InvalidArgumentException in Database\BaseBuilder [\#2813](https://github.com/codeigniter4/CodeIgniter4/pull/2813) ([samsonasik](https://github.com/samsonasik)) -- Ensure $\_SERVER\['SCRIPT\_NAME'\] ends with PHP [\#2810](https://github.com/codeigniter4/CodeIgniter4/pull/2810) ([willnode](https://github.com/willnode)) -- make named constructor in Exception classes consistent: use return instead of throw [\#2809](https://github.com/codeigniter4/CodeIgniter4/pull/2809) ([samsonasik](https://github.com/samsonasik)) -- Check if dataset is empty before Model update. [\#2808](https://github.com/codeigniter4/CodeIgniter4/pull/2808) ([vibbow](https://github.com/vibbow)) -- test Controller::validate\(\) with string rules [\#2807](https://github.com/codeigniter4/CodeIgniter4/pull/2807) ([samsonasik](https://github.com/samsonasik)) -- clean up buffer tweak in FeatureTestCaseTest [\#2805](https://github.com/codeigniter4/CodeIgniter4/pull/2805) ([samsonasik](https://github.com/samsonasik)) -- using realpath\(\) for define $pathsPath in index.php [\#2804](https://github.com/codeigniter4/CodeIgniter4/pull/2804) ([samsonasik](https://github.com/samsonasik)) -- add ext-mbstring to required and update regex that sanitize file name [\#2803](https://github.com/codeigniter4/CodeIgniter4/pull/2803) ([samsonasik](https://github.com/samsonasik)) -- Add resetting QBFrom part [\#2802](https://github.com/codeigniter4/CodeIgniter4/pull/2802) ([michalsn](https://github.com/michalsn)) -- Update Routes.php [\#2801](https://github.com/codeigniter4/CodeIgniter4/pull/2801) ([mostafakhudair](https://github.com/mostafakhudair)) -- add more test for Entity : 100% tested [\#2798](https://github.com/codeigniter4/CodeIgniter4/pull/2798) ([samsonasik](https://github.com/samsonasik)) -- \[ci skip\] Fix download badge total shows [\#2797](https://github.com/codeigniter4/CodeIgniter4/pull/2797) ([samsonasik](https://github.com/samsonasik)) -- test for I18n\Time::toFormattedDateString [\#2796](https://github.com/codeigniter4/CodeIgniter4/pull/2796) ([samsonasik](https://github.com/samsonasik)) -- test Logger::determineFile\(\) for no stack trace [\#2795](https://github.com/codeigniter4/CodeIgniter4/pull/2795) ([samsonasik](https://github.com/samsonasik)) -- test CLI\CLI::strlen\(null\) [\#2794](https://github.com/codeigniter4/CodeIgniter4/pull/2794) ([samsonasik](https://github.com/samsonasik)) -- test for API\ResponseTrait::format\(\) with format is not json or xml [\#2793](https://github.com/codeigniter4/CodeIgniter4/pull/2793) ([samsonasik](https://github.com/samsonasik)) -- test for View\Cell::render\(\) with class has initController\(\) method [\#2792](https://github.com/codeigniter4/CodeIgniter4/pull/2792) ([samsonasik](https://github.com/samsonasik)) -- test Autoloader::initialize\(\) with composer path not found [\#2791](https://github.com/codeigniter4/CodeIgniter4/pull/2791) ([samsonasik](https://github.com/samsonasik)) -- add ability to replace {locale} to request-\>getLocale\(\) in form\_open\('action'\) [\#2790](https://github.com/codeigniter4/CodeIgniter4/pull/2790) ([samsonasik](https://github.com/samsonasik)) -- test for IncomingRequest::getFileMultiple\(\) [\#2789](https://github.com/codeigniter4/CodeIgniter4/pull/2789) ([samsonasik](https://github.com/samsonasik)) -- add MockEmail class [\#2788](https://github.com/codeigniter4/CodeIgniter4/pull/2788) ([samsonasik](https://github.com/samsonasik)) -- test for CodeIgniter\Config\Services::email\(\) [\#2785](https://github.com/codeigniter4/CodeIgniter4/pull/2785) ([samsonasik](https://github.com/samsonasik)) -- make Model::paginate\(\) use default perPage from Config\Pager-\>perPage if $perPage parameter not passed [\#2782](https://github.com/codeigniter4/CodeIgniter4/pull/2782) ([samsonasik](https://github.com/samsonasik)) -- \#2780 - LIMIT. [\#2781](https://github.com/codeigniter4/CodeIgniter4/pull/2781) ([nowackipawel](https://github.com/nowackipawel)) -- \[ci skip\] \_remap method must have return [\#2779](https://github.com/codeigniter4/CodeIgniter4/pull/2779) ([Instrye](https://github.com/Instrye)) -- Rework get\_filenames [\#2778](https://github.com/codeigniter4/CodeIgniter4/pull/2778) ([MGatner](https://github.com/MGatner)) -- Fix \#2776 add ability to paginate\(\) query with group by [\#2777](https://github.com/codeigniter4/CodeIgniter4/pull/2777) ([samsonasik](https://github.com/samsonasik)) -- Update on "Build Your First Application" [\#2775](https://github.com/codeigniter4/CodeIgniter4/pull/2775) ([jreklund](https://github.com/jreklund)) -- Fix ? bind with := bind [\#2773](https://github.com/codeigniter4/CodeIgniter4/pull/2773) ([musmanikram](https://github.com/musmanikram)) -- Fixed some styling in "Installation" chapter \[ci skip\] [\#2772](https://github.com/codeigniter4/CodeIgniter4/pull/2772) ([jreklund](https://github.com/jreklund)) -- Uncommented tests, mistake in my previous PR :\( [\#2767](https://github.com/codeigniter4/CodeIgniter4/pull/2767) ([musmanikram](https://github.com/musmanikram)) -- fix. MYSQLI::DBDebug can't woker [\#2755](https://github.com/codeigniter4/CodeIgniter4/pull/2755) ([Instrye](https://github.com/Instrye)) -- fix. delete\_cookite can't delete alreday set Cookie [\#2709](https://github.com/codeigniter4/CodeIgniter4/pull/2709) ([Instrye](https://github.com/Instrye)) -- Re-write userguide to support Python 3 and future proofing Sphinx [\#2671](https://github.com/codeigniter4/CodeIgniter4/pull/2671) ([jreklund](https://github.com/jreklund)) -- Colored table in CLI [\#2624](https://github.com/codeigniter4/CodeIgniter4/pull/2624) ([enix-app](https://github.com/enix-app)) - -## [4.0.2](https://github.com/codeigniter4/CodeIgniter4/tree/4.0.2) (2020-02-25) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.1...4.0.2) - -**Fixed bugs:** - -- Bug: Your requirements could not be resolved to an installable set of packages. [\#2613](https://github.com/codeigniter4/CodeIgniter4/issues/2613) - -**Merged pull requests:** - -- Removed unused test class that was causing appstarter not to work from CLI. [\#2614](https://github.com/codeigniter4/CodeIgniter4/pull/2614) ([lonnieezell](https://github.com/lonnieezell)) -- \[UG\] Fix all Sphinx warnings [\#2611](https://github.com/codeigniter4/CodeIgniter4/pull/2611) ([LittleJ](https://github.com/LittleJ)) -- \[UG\] Sphinx\_rtd\_theme fixes and improvements [\#2610](https://github.com/codeigniter4/CodeIgniter4/pull/2610) ([LittleJ](https://github.com/LittleJ)) - -## [v4.0.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.1) (2020-02-24) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/4.0.0...v4.0.1) - -**Fixed bugs:** - -- Bug: Difficult architecture of the codeigniter4 project [\#2602](https://github.com/codeigniter4/CodeIgniter4/issues/2602) -- Bug: mentioned rc4 in the changelog file of V4 userguide [\#2599](https://github.com/codeigniter4/CodeIgniter4/issues/2599) - -**Merged pull requests:** - -- \[doc\] Removal of the 'rc' parameter from the installation / update co… [\#2604](https://github.com/codeigniter4/CodeIgniter4/pull/2604) ([jlamim](https://github.com/jlamim)) - -## [4.0.0](https://github.com/codeigniter4/CodeIgniter4/tree/4.0.0) (2020-02-24) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.4...4.0.0) - -**Fixed bugs:** - -- Bug: CI cant display error with API Response Trait [\#2586](https://github.com/codeigniter4/CodeIgniter4/issues/2586) -- Bug: testOrHavingBy\(\) requires a select [\#2584](https://github.com/codeigniter4/CodeIgniter4/issues/2584) -- Bug: Email-\>validateEmail\(\) on wrong email address leads to TypeError [\#2580](https://github.com/codeigniter4/CodeIgniter4/issues/2580) -- Bug: $forge-\>dropColumn not allowing arrays [\#2576](https://github.com/codeigniter4/CodeIgniter4/issues/2576) -- Bug: CI4 set ID to NULL when I try insert a row [\#2557](https://github.com/codeigniter4/CodeIgniter4/issues/2557) -- "Required" rules are not being enforced at all by the model [\#2555](https://github.com/codeigniter4/CodeIgniter4/issues/2555) -- Bug: Debug Bar showing before the head tag [\#2545](https://github.com/codeigniter4/CodeIgniter4/issues/2545) -- Bug: Docs / Library Reference / Validation: missing important information on reset\(\) method [\#2535](https://github.com/codeigniter4/CodeIgniter4/issues/2535) -- Bug: system/Config/BaseService.php getSharedInstance\(\) will not return mocks with non-lowercase key [\#2534](https://github.com/codeigniter4/CodeIgniter4/issues/2534) -- Bug: multi-column WHERE not prefixed [\#2532](https://github.com/codeigniter4/CodeIgniter4/issues/2532) -- Bug: CodeIgniter\Files\File::getSize\(\) - wrong type of result [\#2476](https://github.com/codeigniter4/CodeIgniter4/issues/2476) -- Bug: Move work with $\_GET\['page'\] from Model to Pager [\#2467](https://github.com/codeigniter4/CodeIgniter4/issues/2467) -- Bug: ImageHandler has no reality checks [\#2421](https://github.com/codeigniter4/CodeIgniter4/issues/2421) -- Bug: No default HTTP protocol version is set when creating a new Response [\#2383](https://github.com/codeigniter4/CodeIgniter4/issues/2383) -- Bug: Filter wildcards ignore default methods [\#2455](https://github.com/codeigniter4/CodeIgniter4/issues/2455) -- trailing slash cause redirect to root [\#2445](https://github.com/codeigniter4/CodeIgniter4/issues/2445) -- Bug: Commands cannot accept many paths [\#2148](https://github.com/codeigniter4/CodeIgniter4/issues/2148) - -**Closed issues:** - -- Add an 'alpha\_numeric\_punct' rule to FormatRules [\#2549](https://github.com/codeigniter4/CodeIgniter4/issues/2549) -- Feature: Forge, Adding Foreign key for table already created [\#2543](https://github.com/codeigniter4/CodeIgniter4/issues/2543) -- Error In UserGuide [\#2530](https://github.com/codeigniter4/CodeIgniter4/issues/2530) -- Lack of test on Travis for PHP 7.4 and tests don't run on upcoming PHP versions [\#2293](https://github.com/codeigniter4/CodeIgniter4/issues/2293) -- Feature: model helper [\#2292](https://github.com/codeigniter4/CodeIgniter4/issues/2292) -- Issue with layouts renderer and sections =\> merge sections in one view [\#2491](https://github.com/codeigniter4/CodeIgniter4/issues/2491) -- Update package dependency version for kint-php/kint [\#2373](https://github.com/codeigniter4/CodeIgniter4/issues/2373) - -**Merged pull requests:** - -- Deprecate Devstarter, add `builds` [\#2598](https://github.com/codeigniter4/CodeIgniter4/pull/2598) ([MGatner](https://github.com/MGatner)) -- Fix typo issues causing Sphinx warnings + Update "Welcome page" screenshot [\#2597](https://github.com/codeigniter4/CodeIgniter4/pull/2597) ([LittleJ](https://github.com/LittleJ)) -- Update sphinx\_rtd\_theme from version 0.2.4 to version 0.4.3 [\#2596](https://github.com/codeigniter4/CodeIgniter4/pull/2596) ([LittleJ](https://github.com/LittleJ)) -- Fixed Bug: testOrHavingBy\(\) requires a select | \#2584 [\#2595](https://github.com/codeigniter4/CodeIgniter4/pull/2595) ([jlamim](https://github.com/jlamim)) -- Add Slack to the "Support" section of the documentation [\#2594](https://github.com/codeigniter4/CodeIgniter4/pull/2594) ([LittleJ](https://github.com/LittleJ)) -- Fixed links to the AJAX Requests details page [\#2593](https://github.com/codeigniter4/CodeIgniter4/pull/2593) ([jlamim](https://github.com/jlamim)) -- Add color scheme information in the documentation [\#2592](https://github.com/codeigniter4/CodeIgniter4/pull/2592) ([LittleJ](https://github.com/LittleJ)) -- User Guide authentication recommendations [\#2591](https://github.com/codeigniter4/CodeIgniter4/pull/2591) ([MGatner](https://github.com/MGatner)) -- Add a "Go further" section on the welcome page [\#2590](https://github.com/codeigniter4/CodeIgniter4/pull/2590) ([LittleJ](https://github.com/LittleJ)) -- Add DotEnv::parse\(\) [\#2588](https://github.com/codeigniter4/CodeIgniter4/pull/2588) ([MGatner](https://github.com/MGatner)) -- Better debug routes [\#2587](https://github.com/codeigniter4/CodeIgniter4/pull/2587) ([atishamte](https://github.com/atishamte)) -- Typos change in contributing.md [\#2583](https://github.com/codeigniter4/CodeIgniter4/pull/2583) ([atishamte](https://github.com/atishamte)) -- Fix type error in email validation [\#2582](https://github.com/codeigniter4/CodeIgniter4/pull/2582) ([musmanikram](https://github.com/musmanikram)) -- Fixed Markdown heading [\#2581](https://github.com/codeigniter4/CodeIgniter4/pull/2581) ([ImMaax](https://github.com/ImMaax)) -- Cache FileHandler unlink exception [\#2579](https://github.com/codeigniter4/CodeIgniter4/pull/2579) ([MGatner](https://github.com/MGatner)) -- Fix drop column with array [\#2578](https://github.com/codeigniter4/CodeIgniter4/pull/2578) ([musmanikram](https://github.com/musmanikram)) -- Refactor Tests [\#2577](https://github.com/codeigniter4/CodeIgniter4/pull/2577) ([MGatner](https://github.com/MGatner)) -- Subfolder base\_url\(\) with parameter [\#2574](https://github.com/codeigniter4/CodeIgniter4/pull/2574) ([MGatner](https://github.com/MGatner)) -- Image verification [\#2573](https://github.com/codeigniter4/CodeIgniter4/pull/2573) ([MGatner](https://github.com/MGatner)) -- Use default protocol if unspecified [\#2572](https://github.com/codeigniter4/CodeIgniter4/pull/2572) ([MGatner](https://github.com/MGatner)) -- Retain CLI segments [\#2571](https://github.com/codeigniter4/CodeIgniter4/pull/2571) ([MGatner](https://github.com/MGatner)) -- Model's set method should accept not only string [\#2570](https://github.com/codeigniter4/CodeIgniter4/pull/2570) ([nowackipawel](https://github.com/nowackipawel)) -- Use lowercase service names [\#2569](https://github.com/codeigniter4/CodeIgniter4/pull/2569) ([MGatner](https://github.com/MGatner)) -- Apply User Guide code style to Errors [\#2567](https://github.com/codeigniter4/CodeIgniter4/pull/2567) ([MGatner](https://github.com/MGatner)) -- Move debug toolbar after head tag. Fixes \#2545 [\#2566](https://github.com/codeigniter4/CodeIgniter4/pull/2566) ([MGatner](https://github.com/MGatner)) -- Updates To Kint Loading [\#2565](https://github.com/codeigniter4/CodeIgniter4/pull/2565) ([najdanovicivan](https://github.com/najdanovicivan)) -- Updated loader and composer script to use Kint 3.3 for \#2373 [\#2564](https://github.com/codeigniter4/CodeIgniter4/pull/2564) ([lonnieezell](https://github.com/lonnieezell)) -- Added rule "alpha\_numeric\_punct" [\#2562](https://github.com/codeigniter4/CodeIgniter4/pull/2562) ([dafriend](https://github.com/dafriend)) -- Fix - Add ajax to docs "General " page \[ci skip\] [\#2561](https://github.com/codeigniter4/CodeIgniter4/pull/2561) ([dafriend](https://github.com/dafriend)) -- MySQLi: Incorrect DBDebug flag used for connection charset [\#2558](https://github.com/codeigniter4/CodeIgniter4/pull/2558) ([jreklund](https://github.com/jreklund)) -- Update File.php [\#2552](https://github.com/codeigniter4/CodeIgniter4/pull/2552) ([thanhtaivtt](https://github.com/thanhtaivtt)) -- disable buffer check on "testing" environment [\#2551](https://github.com/codeigniter4/CodeIgniter4/pull/2551) ([samsonasik](https://github.com/samsonasik)) -- Improved view: welcome\_message.php [\#2550](https://github.com/codeigniter4/CodeIgniter4/pull/2550) ([Vizzielli](https://github.com/Vizzielli)) -- Add retry creation server when the port is used [\#2544](https://github.com/codeigniter4/CodeIgniter4/pull/2544) ([thanhtaivtt](https://github.com/thanhtaivtt)) -- New "welcome" page [\#2541](https://github.com/codeigniter4/CodeIgniter4/pull/2541) ([LittleJ](https://github.com/LittleJ)) -- valid\_ip removed $data which was causing exception [\#2540](https://github.com/codeigniter4/CodeIgniter4/pull/2540) ([nowackipawel](https://github.com/nowackipawel)) -- explanation of reset\(\) method in section Working With Validation fixes \#2535 [\#2539](https://github.com/codeigniter4/CodeIgniter4/pull/2539) ([bivanbi](https://github.com/bivanbi)) -- Update TravisCI config for PHP7.4 [\#2537](https://github.com/codeigniter4/CodeIgniter4/pull/2537) ([musmanikram](https://github.com/musmanikram)) -- Fix multi-column WHERE not prefixed with DBPrefix [\#2533](https://github.com/codeigniter4/CodeIgniter4/pull/2533) ([musmanikram](https://github.com/musmanikram)) -- Update images.rst [\#2529](https://github.com/codeigniter4/CodeIgniter4/pull/2529) ([avegacms](https://github.com/avegacms)) -- Added new model helper method. [\#2514](https://github.com/codeigniter4/CodeIgniter4/pull/2514) ([lonnieezell](https://github.com/lonnieezell)) -- Debug bar: Dark/light mode + Complete CSS refactoring [\#2478](https://github.com/codeigniter4/CodeIgniter4/pull/2478) ([LittleJ](https://github.com/LittleJ)) -- WIP Update Validation.php [\#2083](https://github.com/codeigniter4/CodeIgniter4/pull/2083) ([MohKari](https://github.com/MohKari)) - -## [v4.0.0-rc.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.4) (2020-02-07) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.3...v4.0.0-rc.4) - -**Fixed bugs:** - -- Bug: Class 'Locale' not found when I try to use Time::parse\(\) [\#2519](https://github.com/codeigniter4/CodeIgniter4/issues/2519) -- Bug: Wrong Links for pager when having subfolders [\#2505](https://github.com/codeigniter4/CodeIgniter4/issues/2505) -- View Cell render not be call initController [\#2500](https://github.com/codeigniter4/CodeIgniter4/issues/2500) -- Bug: user guide compile failed. [\#2492](https://github.com/codeigniter4/CodeIgniter4/issues/2492) -- Bug: setAutoRoute to false not working [\#2480](https://github.com/codeigniter4/CodeIgniter4/issues/2480) -- Bug: CodeIgniter\Router\Router-\>checkRoutes\(\) strpos\(\) expects parameter 1 to be string, array given [\#2479](https://github.com/codeigniter4/CodeIgniter4/issues/2479) -- Bug: Unable to use ul\(\) helper with out modification [\#2473](https://github.com/codeigniter4/CodeIgniter4/issues/2473) -- Bug: Cannot pass value in the function in ControllerTest [\#2470](https://github.com/codeigniter4/CodeIgniter4/issues/2470) -- Bug: $useTimestamps cause insert\(\) exception [\#2469](https://github.com/codeigniter4/CodeIgniter4/issues/2469) -- Bug: prototype of setBody\($data\); method in CodeIgniter\HTTP\Message should be changed to setBody\(string $data\); [\#2466](https://github.com/codeigniter4/CodeIgniter4/issues/2466) -- Don't update it. It's rubbishBug: [\#2463](https://github.com/codeigniter4/CodeIgniter4/issues/2463) -- Bug: Autoload over Composer [\#2461](https://github.com/codeigniter4/CodeIgniter4/issues/2461) -- Bug: The docs say `composer install` instead of `composer required` [\#2457](https://github.com/codeigniter4/CodeIgniter4/issues/2457) -- Bug: if not CSPEnaled but i have some [\#2456](https://github.com/codeigniter4/CodeIgniter4/issues/2456) -- Bug: IsAJAX\(\) relies on inconsistent headers [\#2454](https://github.com/codeigniter4/CodeIgniter4/issues/2454) -- Bug: [\#2448](https://github.com/codeigniter4/CodeIgniter4/issues/2448) -- Bug: Double use where and etc [\#2444](https://github.com/codeigniter4/CodeIgniter4/issues/2444) -- Bug: Double use esc function with form\_input and etc... [\#2443](https://github.com/codeigniter4/CodeIgniter4/issues/2443) -- Bug: Entity casts do not cast the original data [\#2441](https://github.com/codeigniter4/CodeIgniter4/issues/2441) -- Bug: namespace view returns empty value [\#2440](https://github.com/codeigniter4/CodeIgniter4/issues/2440) -- Bug: php spark Call to undefined function CodeIgniter\Autoloader\get\_filenames\(\) [\#2439](https://github.com/codeigniter4/CodeIgniter4/issues/2439) -- Bug: Curly brace deprecation [\#2430](https://github.com/codeigniter4/CodeIgniter4/issues/2430) -- Bug: Routes and Namespace [\#2423](https://github.com/codeigniter4/CodeIgniter4/issues/2423) -- Bug: Validation not working [\#2418](https://github.com/codeigniter4/CodeIgniter4/issues/2418) -- Bug: Baseservice - getSharedInstance [\#2414](https://github.com/codeigniter4/CodeIgniter4/issues/2414) -- Bug: base\_url\(\) and redirect\(\) not honoring baseURL with paths [\#2409](https://github.com/codeigniter4/CodeIgniter4/issues/2409) -- form\_input double escaping data why ? [\#2405](https://github.com/codeigniter4/CodeIgniter4/issues/2405) -- Bug: initController not called in ResourceController [\#2404](https://github.com/codeigniter4/CodeIgniter4/issues/2404) -- Bug: [\#2397](https://github.com/codeigniter4/CodeIgniter4/issues/2397) -- URL Helper safe\_mailto UTF8Bug: [\#2396](https://github.com/codeigniter4/CodeIgniter4/issues/2396) -- CSRF Filter redirect back not working [\#2395](https://github.com/codeigniter4/CodeIgniter4/issues/2395) -- Bug: 404 error page override with cache [\#2391](https://github.com/codeigniter4/CodeIgniter4/issues/2391) -- Bug: Mixed migration formats don't order [\#2386](https://github.com/codeigniter4/CodeIgniter4/issues/2386) -- Bug: \CodeIgniter\Model::validate\(\) returns TRUE if $data is empty [\#2384](https://github.com/codeigniter4/CodeIgniter4/issues/2384) -- Bug: Usage of `static::methodName` in CodeIgniter\Config\Services prevents Service overriding [\#2376](https://github.com/codeigniter4/CodeIgniter4/issues/2376) -- Bug: Duplicate headers in response [\#2375](https://github.com/codeigniter4/CodeIgniter4/issues/2375) -- Bug: Nothing work with minimal config \(DIRECTORY SEPARATOR\) [\#2370](https://github.com/codeigniter4/CodeIgniter4/issues/2370) -- Bug: current\_url function not working as expected. [\#2365](https://github.com/codeigniter4/CodeIgniter4/issues/2365) -- Bug: localhost development server after edit the content not updated or reloaded [\#2363](https://github.com/codeigniter4/CodeIgniter4/issues/2363) -- Bug: with the parser, nl2br in a foreach duplicates entries. [\#2360](https://github.com/codeigniter4/CodeIgniter4/issues/2360) -- Bug: Prevents the use of global functions with parameters \[Validation\] [\#2357](https://github.com/codeigniter4/CodeIgniter4/issues/2357) -- Bug: lang\('app.name'\) should prefer APPPATH.Language/Validation/en/app.php over installed packages [\#2354](https://github.com/codeigniter4/CodeIgniter4/issues/2354) -- Bug: Inappropriate delimiter used in fillRouteParams [\#2353](https://github.com/codeigniter4/CodeIgniter4/issues/2353) -- Bug: Please there is issue on the time and date guide in codeigniter 4 [\#2351](https://github.com/codeigniter4/CodeIgniter4/issues/2351) -- Bug: Model\(\)-\>find\(null\) should return null value [\#2350](https://github.com/codeigniter4/CodeIgniter4/issues/2350) -- Bug: URL, Redirect and Pagination misbehave [\#2347](https://github.com/codeigniter4/CodeIgniter4/issues/2347) -- Bug: Toolbar ErrorException Division by zero [\#2340](https://github.com/codeigniter4/CodeIgniter4/issues/2340) -- Bug: Cannot pass a string param with a space to a custom parser plugin [\#2318](https://github.com/codeigniter4/CodeIgniter4/issues/2318) -- Bug: Logger Path Duplicated [\#2286](https://github.com/codeigniter4/CodeIgniter4/issues/2286) -- Bug: Email: SMTP Protocol Implementation @ Data Termination [\#2274](https://github.com/codeigniter4/CodeIgniter4/issues/2274) -- Bug: Redirect to route ignores path set in baseurl [\#2119](https://github.com/codeigniter4/CodeIgniter4/issues/2119) - -**Closed issues:** - -- $routes not do the job as well [\#2531](https://github.com/codeigniter4/CodeIgniter4/issues/2531) -- Multiple composer.json handling in codeigniter!! [\#2528](https://github.com/codeigniter4/CodeIgniter4/issues/2528) -- Mention about events in the upgrading doc pages [\#2521](https://github.com/codeigniter4/CodeIgniter4/issues/2521) -- Missing Constant in E-Mail [\#2512](https://github.com/codeigniter4/CodeIgniter4/issues/2512) -- Image Manipulation Class [\#2498](https://github.com/codeigniter4/CodeIgniter4/issues/2498) -- Schema param in the .env file [\#2483](https://github.com/codeigniter4/CodeIgniter4/issues/2483) -- system\Database\MigrationRunner-\>regress\(\) resets instance variable $namespace to null [\#2474](https://github.com/codeigniter4/CodeIgniter4/issues/2474) -- Issue passing data to views [\#2464](https://github.com/codeigniter4/CodeIgniter4/issues/2464) -- currentURL & previousURL doesnt work in parser [\#2460](https://github.com/codeigniter4/CodeIgniter4/issues/2460) -- Double quotes [\#2459](https://github.com/codeigniter4/CodeIgniter4/issues/2459) -- Feature about Localization [\#2419](https://github.com/codeigniter4/CodeIgniter4/issues/2419) -- Documentation Fix Needed [\#2412](https://github.com/codeigniter4/CodeIgniter4/issues/2412) -- No such file or Directory found In Ubuntu 19.10 [\#2394](https://github.com/codeigniter4/CodeIgniter4/issues/2394) -- previous\_url\(\) not loading the base path together [\#2378](https://github.com/codeigniter4/CodeIgniter4/issues/2378) -- Wrong Logo on GitHub page [\#2372](https://github.com/codeigniter4/CodeIgniter4/issues/2372) -- How to use the pagination with view parser? [\#2371](https://github.com/codeigniter4/CodeIgniter4/issues/2371) -- Feature Request: Validation: in\_db\[table.field\] [\#2366](https://github.com/codeigniter4/CodeIgniter4/issues/2366) -- Feature request [\#2361](https://github.com/codeigniter4/CodeIgniter4/issues/2361) -- Feature: AJAX route option [\#2310](https://github.com/codeigniter4/CodeIgniter4/issues/2310) -- Return value of CodeIgniter\Database\BaseConnection::getConnectStart\(\) must be of the type float, null returned [\#2158](https://github.com/codeigniter4/CodeIgniter4/issues/2158) -- Create Security Guideline [\#73](https://github.com/codeigniter4/CodeIgniter4/issues/73) - -**Merged pull requests:** - -- Update manual.rst [\#2527](https://github.com/codeigniter4/CodeIgniter4/pull/2527) ([avegacms](https://github.com/avegacms)) -- Page in the official documentation on ajax requests with iSAJAX\(\) fixes \#2454 [\#2526](https://github.com/codeigniter4/CodeIgniter4/pull/2526) ([jlamim](https://github.com/jlamim)) -- Remove incorrect inline doc type [\#2525](https://github.com/codeigniter4/CodeIgniter4/pull/2525) ([MGatner](https://github.com/MGatner)) -- Restore namespace after regress. Fixes \#2474 [\#2524](https://github.com/codeigniter4/CodeIgniter4/pull/2524) ([MGatner](https://github.com/MGatner)) -- Replace legacy CI3 constant. Fixes \#2512 [\#2523](https://github.com/codeigniter4/CodeIgniter4/pull/2523) ([MGatner](https://github.com/MGatner)) -- Adding Events information in the 'Upgrading from 3.x to 4.x' section [\#2522](https://github.com/codeigniter4/CodeIgniter4/pull/2522) ([jlamim](https://github.com/jlamim)) -- Fix pager URI to work in subfolders. [\#2518](https://github.com/codeigniter4/CodeIgniter4/pull/2518) ([lonnieezell](https://github.com/lonnieezell)) -- HTML Helper - Fix attribute type for lists [\#2516](https://github.com/codeigniter4/CodeIgniter4/pull/2516) ([najdanovicivan](https://github.com/najdanovicivan)) -- Layout Renderer Fix [\#2515](https://github.com/codeigniter4/CodeIgniter4/pull/2515) ([najdanovicivan](https://github.com/najdanovicivan)) -- \[ci skip\] Typo in userguide "Entity Classes - Business Logic" [\#2513](https://github.com/codeigniter4/CodeIgniter4/pull/2513) ([jreklund](https://github.com/jreklund)) -- Database add highlight [\#2511](https://github.com/codeigniter4/CodeIgniter4/pull/2511) ([MashinaMashina](https://github.com/MashinaMashina)) -- Revert Renderer section reset [\#2509](https://github.com/codeigniter4/CodeIgniter4/pull/2509) ([MGatner](https://github.com/MGatner)) -- Update ordering of search locations for better prioritization. Fixes \#2354 [\#2507](https://github.com/codeigniter4/CodeIgniter4/pull/2507) ([lonnieezell](https://github.com/lonnieezell)) -- Proposal: HTTP Response - Fix crash on CSP methods CSP is disabled [\#2506](https://github.com/codeigniter4/CodeIgniter4/pull/2506) ([najdanovicivan](https://github.com/najdanovicivan)) -- BaseConnection - Nullable return type in getConnectStart\(\) [\#2504](https://github.com/codeigniter4/CodeIgniter4/pull/2504) ([najdanovicivan](https://github.com/najdanovicivan)) -- View Renderer - Reset sections after generating the ouput [\#2502](https://github.com/codeigniter4/CodeIgniter4/pull/2502) ([najdanovicivan](https://github.com/najdanovicivan)) -- view\_cell call controller on initController method. [\#2501](https://github.com/codeigniter4/CodeIgniter4/pull/2501) ([byazrail](https://github.com/byazrail)) -- View Parser - Fix ParsePair\(\) with filter [\#2499](https://github.com/codeigniter4/CodeIgniter4/pull/2499) ([najdanovicivan](https://github.com/najdanovicivan)) -- Fix splitQueryPart\(\) [\#2497](https://github.com/codeigniter4/CodeIgniter4/pull/2497) ([MashinaMashina](https://github.com/MashinaMashina)) -- Use site\_url for RedirectResponse. Fixes \#2119 [\#2496](https://github.com/codeigniter4/CodeIgniter4/pull/2496) ([lonnieezell](https://github.com/lonnieezell)) -- \[ci skip\] update toolbar userguide [\#2495](https://github.com/codeigniter4/CodeIgniter4/pull/2495) ([Instrye](https://github.com/Instrye)) -- Debug Toolbar - Fix Debugbar-Time header, Render in \ [\#2494](https://github.com/codeigniter4/CodeIgniter4/pull/2494) ([najdanovicivan](https://github.com/najdanovicivan)) -- fix sphinx version. [\#2493](https://github.com/codeigniter4/CodeIgniter4/pull/2493) ([ytetsuro](https://github.com/ytetsuro)) -- fix. Toolbar init view Error [\#2490](https://github.com/codeigniter4/CodeIgniter4/pull/2490) ([Instrye](https://github.com/Instrye)) -- Fix pager [\#2489](https://github.com/codeigniter4/CodeIgniter4/pull/2489) ([MashinaMashina](https://github.com/MashinaMashina)) -- Update current\_url and previous\_url in the docs for View Parser. Fixes \#2460 [\#2486](https://github.com/codeigniter4/CodeIgniter4/pull/2486) ([lonnieezell](https://github.com/lonnieezell)) -- Typo in user guide "Running via the Command Line" [\#2485](https://github.com/codeigniter4/CodeIgniter4/pull/2485) ([jreklund](https://github.com/jreklund)) -- Services request add URI Core System extend support [\#2482](https://github.com/codeigniter4/CodeIgniter4/pull/2482) ([byazrail](https://github.com/byazrail)) -- Fix \#2479. Priority Redirection. [\#2481](https://github.com/codeigniter4/CodeIgniter4/pull/2481) ([Instrye](https://github.com/Instrye)) -- ControllerTest should work without URI specified. Fixes \#2470 [\#2472](https://github.com/codeigniter4/CodeIgniter4/pull/2472) ([lonnieezell](https://github.com/lonnieezell)) -- Transition from Zend Escaper to Laminas Escaper [\#2471](https://github.com/codeigniter4/CodeIgniter4/pull/2471) ([lonnieezell](https://github.com/lonnieezell)) -- Fix impossible length for migration table id. [\#2462](https://github.com/codeigniter4/CodeIgniter4/pull/2462) ([ytetsuro](https://github.com/ytetsuro)) -- Replace `composer install` by `composer require` [\#2458](https://github.com/codeigniter4/CodeIgniter4/pull/2458) ([SteeveDroz](https://github.com/SteeveDroz)) -- \[ci skip\] Error correction in reference to Query Builder emptyTable m… [\#2452](https://github.com/codeigniter4/CodeIgniter4/pull/2452) ([jlamim](https://github.com/jlamim)) -- CRITICAL when $\_SESSION is null / Argument 2 passed to dot\_array\_search\(\) must be \[\] [\#2450](https://github.com/codeigniter4/CodeIgniter4/pull/2450) ([nowackipawel](https://github.com/nowackipawel)) -- User Guide: Query Builder selectCount - error correction in example [\#2449](https://github.com/codeigniter4/CodeIgniter4/pull/2449) ([jlamim](https://github.com/jlamim)) -- Existing File checks \(Nowackipawel/patch-69\) [\#2447](https://github.com/codeigniter4/CodeIgniter4/pull/2447) ([MGatner](https://github.com/MGatner)) -- DB Insert Ignore \(Tada5hi/database-feature\) [\#2446](https://github.com/codeigniter4/CodeIgniter4/pull/2446) ([MGatner](https://github.com/MGatner)) -- Nice array view in debug toolbar [\#2438](https://github.com/codeigniter4/CodeIgniter4/pull/2438) ([MashinaMashina](https://github.com/MashinaMashina)) -- \[ci skip\] Fix Message method reference [\#2436](https://github.com/codeigniter4/CodeIgniter4/pull/2436) ([MGatner](https://github.com/MGatner)) -- Inserting through a model should respect all validation rules. Fixes \#2384 [\#2433](https://github.com/codeigniter4/CodeIgniter4/pull/2433) ([lonnieezell](https://github.com/lonnieezell)) -- Fix curly brace deprecation in php 7.4 [\#2432](https://github.com/codeigniter4/CodeIgniter4/pull/2432) ([musmanikram](https://github.com/musmanikram)) -- fix. safe\_mailto multi-byte safe [\#2429](https://github.com/codeigniter4/CodeIgniter4/pull/2429) ([Instrye](https://github.com/Instrye)) -- Add $recipients property to Config\Email [\#2427](https://github.com/codeigniter4/CodeIgniter4/pull/2427) ([dafriend](https://github.com/dafriend)) -- Add hex validation rule, test, Guide [\#2426](https://github.com/codeigniter4/CodeIgniter4/pull/2426) ([MGatner](https://github.com/MGatner)) -- fix: Router setDefaultNameSpace can't worker [\#2425](https://github.com/codeigniter4/CodeIgniter4/pull/2425) ([Instrye](https://github.com/Instrye)) -- Don't show duplicate Date headers when running under PHPs server. Fixes \#2375 [\#2422](https://github.com/codeigniter4/CodeIgniter4/pull/2422) ([lonnieezell](https://github.com/lonnieezell)) -- Change current\_url\(\) to use cloned URI [\#2420](https://github.com/codeigniter4/CodeIgniter4/pull/2420) ([MGatner](https://github.com/MGatner)) -- Revise Encryption Service Documentation \[ci skip\] [\#2417](https://github.com/codeigniter4/CodeIgniter4/pull/2417) ([dafriend](https://github.com/dafriend)) -- Add missing closing braces of condition 'hasError\(\)' under Check If… [\#2416](https://github.com/codeigniter4/CodeIgniter4/pull/2416) ([musmanikram](https://github.com/musmanikram)) -- Add 'nullable' to MySQL field data [\#2415](https://github.com/codeigniter4/CodeIgniter4/pull/2415) ([MGatner](https://github.com/MGatner)) -- fix. toolbar file 301 [\#2413](https://github.com/codeigniter4/CodeIgniter4/pull/2413) ([Instrye](https://github.com/Instrye)) -- \#2318 - fix parse params of plugin [\#2411](https://github.com/codeigniter4/CodeIgniter4/pull/2411) ([oleg1540](https://github.com/oleg1540)) -- Looks like a typo. [\#2410](https://github.com/codeigniter4/CodeIgniter4/pull/2410) ([AndiKod](https://github.com/AndiKod)) -- Ensure previous\_url\(\) gets accurate URI. [\#2408](https://github.com/codeigniter4/CodeIgniter4/pull/2408) ([lonnieezell](https://github.com/lonnieezell)) -- Fix url helper functions to work when site hosted in subfolders. [\#2407](https://github.com/codeigniter4/CodeIgniter4/pull/2407) ([lonnieezell](https://github.com/lonnieezell)) -- Fix issue \#2391 CodeIgniter::display404errors\(\) [\#2406](https://github.com/codeigniter4/CodeIgniter4/pull/2406) ([dafriend](https://github.com/dafriend)) -- Removed pointless isset\(\) check [\#2402](https://github.com/codeigniter4/CodeIgniter4/pull/2402) ([dafriend](https://github.com/dafriend)) -- Remove pointless check from conditional [\#2401](https://github.com/codeigniter4/CodeIgniter4/pull/2401) ([dafriend](https://github.com/dafriend)) -- Remove redundant check in conditionals [\#2400](https://github.com/codeigniter4/CodeIgniter4/pull/2400) ([dafriend](https://github.com/dafriend)) -- Revise Controllers Documentation \[ci skip\] [\#2399](https://github.com/codeigniter4/CodeIgniter4/pull/2399) ([dafriend](https://github.com/dafriend)) -- Edit .htaccess [\#2398](https://github.com/codeigniter4/CodeIgniter4/pull/2398) ([MashinaMashina](https://github.com/MashinaMashina)) -- Add validation function `is\_not\_unique` [\#2392](https://github.com/codeigniter4/CodeIgniter4/pull/2392) ([kennylajara](https://github.com/kennylajara)) -- Confer silent status to nested seeders [\#2389](https://github.com/codeigniter4/CodeIgniter4/pull/2389) ([MGatner](https://github.com/MGatner)) -- Fix copypaste command comment [\#2388](https://github.com/codeigniter4/CodeIgniter4/pull/2388) ([MGatner](https://github.com/MGatner)) -- Use only digits for migrations order [\#2387](https://github.com/codeigniter4/CodeIgniter4/pull/2387) ([MGatner](https://github.com/MGatner)) -- quick fix postgresql insert id [\#2382](https://github.com/codeigniter4/CodeIgniter4/pull/2382) ([iam-adty](https://github.com/iam-adty)) -- Fix: Use of CodeIgniter\Config\Services prevents Service overriding [\#2381](https://github.com/codeigniter4/CodeIgniter4/pull/2381) ([dafriend](https://github.com/dafriend)) -- Replace null log file extension check [\#2379](https://github.com/codeigniter4/CodeIgniter4/pull/2379) ([MGatner](https://github.com/MGatner)) -- Docs Rev: Replacing Core Classes \[ci skip\] [\#2377](https://github.com/codeigniter4/CodeIgniter4/pull/2377) ([dafriend](https://github.com/dafriend)) -- Remove LoggerAwareTrait from Email class [\#2369](https://github.com/codeigniter4/CodeIgniter4/pull/2369) ([dafriend](https://github.com/dafriend)) -- Remove log\_message from Email::\_\_construct [\#2368](https://github.com/codeigniter4/CodeIgniter4/pull/2368) ([dafriend](https://github.com/dafriend)) -- Email config doesn't incorporate .env items [\#2364](https://github.com/codeigniter4/CodeIgniter4/pull/2364) ([dafriend](https://github.com/dafriend)) -- Fix SMTP protocol problem [\#2362](https://github.com/codeigniter4/CodeIgniter4/pull/2362) ([jim-parry](https://github.com/jim-parry)) -- Bugfix Model after event data [\#2359](https://github.com/codeigniter4/CodeIgniter4/pull/2359) ([MGatner](https://github.com/MGatner)) -- Fix Logger config [\#2358](https://github.com/codeigniter4/CodeIgniter4/pull/2358) ([jim-parry](https://github.com/jim-parry)) -- Fix typo in comments of Services.php [\#2356](https://github.com/codeigniter4/CodeIgniter4/pull/2356) ([mladoux](https://github.com/mladoux)) -- Fix method name to 'toDateString\(\)' in Date and Times user guide [\#2352](https://github.com/codeigniter4/CodeIgniter4/pull/2352) ([musmanikram](https://github.com/musmanikram)) -- Inccorectly formated JSON response , if body is string [\#2276](https://github.com/codeigniter4/CodeIgniter4/pull/2276) ([nowackipawel](https://github.com/nowackipawel)) - -## [v4.0.0-rc.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.3) (2019-10-19) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.2b...v4.0.0-rc.3) - -**Fixed bugs:** - -- Route can not work: Controller or its method is not found [\#2299](https://github.com/codeigniter4/CodeIgniter4/issues/2299) -- CURLRequest - supplied argument is not a valid File-Handle resource \#Windows x64 [\#2202](https://github.com/codeigniter4/CodeIgniter4/issues/2202) -- FilterExceptions result in blank page [\#2077](https://github.com/codeigniter4/CodeIgniter4/issues/2077) - -**Closed issues:** - -- Codeigniter 4 not redirecting well. is not including the baseUrl well on redirect [\#2342](https://github.com/codeigniter4/CodeIgniter4/issues/2342) -- Session variable value set to 0 [\#2334](https://github.com/codeigniter4/CodeIgniter4/issues/2334) -- Undefined variable: errors SYSTEMPATH/Validation/Validation.php at line 651 [\#2331](https://github.com/codeigniter4/CodeIgniter4/issues/2331) -- Router Regex not working with controllers [\#2330](https://github.com/codeigniter4/CodeIgniter4/issues/2330) -- --host,--port and others are not working [\#2329](https://github.com/codeigniter4/CodeIgniter4/issues/2329) -- Type juggling can be eliminated if these three small changes are made [\#2326](https://github.com/codeigniter4/CodeIgniter4/issues/2326) -- url\_title doesn't handle diacritics [\#2323](https://github.com/codeigniter4/CodeIgniter4/issues/2323) -- View Cell Feature ? [\#2322](https://github.com/codeigniter4/CodeIgniter4/issues/2322) -- autoRoute function issue in case of sub-directory [\#2319](https://github.com/codeigniter4/CodeIgniter4/issues/2319) -- Can't store multidimensional data with Session Library [\#2309](https://github.com/codeigniter4/CodeIgniter4/issues/2309) -- Model\(\)-\>find\(\) return NULL for existing row [\#2306](https://github.com/codeigniter4/CodeIgniter4/issues/2306) -- Requesting Model::getValidationRules\(\) documentation [\#2304](https://github.com/codeigniter4/CodeIgniter4/issues/2304) -- Routes Not working [\#2301](https://github.com/codeigniter4/CodeIgniter4/issues/2301) -- ViewPath cannot be moved only another path added [\#2291](https://github.com/codeigniter4/CodeIgniter4/issues/2291) -- Version not updated? [\#2287](https://github.com/codeigniter4/CodeIgniter4/issues/2287) -- \_remap is not working [\#2277](https://github.com/codeigniter4/CodeIgniter4/issues/2277) -- Debug Toolbar error not found tpl error and fix [\#2275](https://github.com/codeigniter4/CodeIgniter4/issues/2275) -- cURL request returns 404 [\#2250](https://github.com/codeigniter4/CodeIgniter4/issues/2250) -- Problem with renaming deleted\_at column inside model [\#2248](https://github.com/codeigniter4/CodeIgniter4/issues/2248) -- App\Config\Routes loaded twice [\#2203](https://github.com/codeigniter4/CodeIgniter4/issues/2203) -- Feature idea: Model results by key [\#2167](https://github.com/codeigniter4/CodeIgniter4/issues/2167) -- Remove "separator" comment between function declarations? [\#2146](https://github.com/codeigniter4/CodeIgniter4/issues/2146) -- find\(\) is returning one character string instead of boolean [\#2096](https://github.com/codeigniter4/CodeIgniter4/issues/2096) -- Database Groups in Migrations [\#2087](https://github.com/codeigniter4/CodeIgniter4/issues/2087) -- "Cannot call session save handler in a recursive manner" [\#2056](https://github.com/codeigniter4/CodeIgniter4/issues/2056) -- Model afterInsert return originals? [\#2045](https://github.com/codeigniter4/CodeIgniter4/issues/2045) -- debug toolbar renderTimeline couses a non well formed numeric value encountered error [\#2034](https://github.com/codeigniter4/CodeIgniter4/issues/2034) -- Mysql update , affectedRows return bug [\#2003](https://github.com/codeigniter4/CodeIgniter4/issues/2003) -- Add validation on exists database before created [\#1759](https://github.com/codeigniter4/CodeIgniter4/issues/1759) - -**Merged pull requests:** - -- Prep changelog for RC.3 \[ci skip\] [\#2349](https://github.com/codeigniter4/CodeIgniter4/pull/2349) ([jim-parry](https://github.com/jim-parry)) -- CodeIgniter Foundation gets copyright \[ci skip\] [\#2348](https://github.com/codeigniter4/CodeIgniter4/pull/2348) ([jim-parry](https://github.com/jim-parry)) -- Fix FilerHandlerTest.php wierdness [\#2346](https://github.com/codeigniter4/CodeIgniter4/pull/2346) ([dafriend](https://github.com/dafriend)) -- Tests readme polish [\#2345](https://github.com/codeigniter4/CodeIgniter4/pull/2345) ([dafriend](https://github.com/dafriend)) -- Setup vs Set Up [\#2344](https://github.com/codeigniter4/CodeIgniter4/pull/2344) ([dafriend](https://github.com/dafriend)) -- User guide minor fixes. Fix class names and code area. [\#2343](https://github.com/codeigniter4/CodeIgniter4/pull/2343) ([natanfelles](https://github.com/natanfelles)) -- Simplify Validation::getErrors\(\) [\#2341](https://github.com/codeigniter4/CodeIgniter4/pull/2341) ([dafriend](https://github.com/dafriend)) -- Fix Session::get\('key'\) returns null when value is \(int\) 0 [\#2339](https://github.com/codeigniter4/CodeIgniter4/pull/2339) ([dafriend](https://github.com/dafriend)) -- Revert RedirectException change [\#2338](https://github.com/codeigniter4/CodeIgniter4/pull/2338) ([MGatner](https://github.com/MGatner)) -- \[ci skip\] Guide: Minor grammar corrections [\#2337](https://github.com/codeigniter4/CodeIgniter4/pull/2337) ([dafriend](https://github.com/dafriend)) -- Correct cleaning of namespaces in FileLocater for better Windows compatibility. See \#2203 [\#2336](https://github.com/codeigniter4/CodeIgniter4/pull/2336) ([lonnieezell](https://github.com/lonnieezell)) -- \[ci skip\] Guide: RESTful table formatting [\#2333](https://github.com/codeigniter4/CodeIgniter4/pull/2333) ([MGatner](https://github.com/MGatner)) -- Change after methods to use actual data [\#2332](https://github.com/codeigniter4/CodeIgniter4/pull/2332) ([MGatner](https://github.com/MGatner)) -- Update Application Structure [\#2328](https://github.com/codeigniter4/CodeIgniter4/pull/2328) ([kenjis](https://github.com/kenjis)) -- Correct the routing UG page [\#2327](https://github.com/codeigniter4/CodeIgniter4/pull/2327) ([jim-parry](https://github.com/jim-parry)) -- Fix bug in url\_title\(\) function with diacritics [\#2325](https://github.com/codeigniter4/CodeIgniter4/pull/2325) ([michalsn](https://github.com/michalsn)) -- Renderer Toolbar Debug Toggle [\#2324](https://github.com/codeigniter4/CodeIgniter4/pull/2324) ([MGatner](https://github.com/MGatner)) -- \[ci skip\] Update RESTful User Guide [\#2321](https://github.com/codeigniter4/CodeIgniter4/pull/2321) ([MGatner](https://github.com/MGatner)) -- Add getValidationRules\(\) to model UG page [\#2316](https://github.com/codeigniter4/CodeIgniter4/pull/2316) ([jim-parry](https://github.com/jim-parry)) -- Enhance Toolbar::renderTimeline [\#2315](https://github.com/codeigniter4/CodeIgniter4/pull/2315) ([jim-parry](https://github.com/jim-parry)) -- RESTful User Guide cleanup [\#2313](https://github.com/codeigniter4/CodeIgniter4/pull/2313) ([MGatner](https://github.com/MGatner)) -- BaseBuilder variable type fix [\#2312](https://github.com/codeigniter4/CodeIgniter4/pull/2312) ([TysiacSzescset](https://github.com/TysiacSzescset)) -- Convert all language returns to single quote [\#2311](https://github.com/codeigniter4/CodeIgniter4/pull/2311) ([MGatner](https://github.com/MGatner)) -- Bugfix extra autoroute slashes [\#2308](https://github.com/codeigniter4/CodeIgniter4/pull/2308) ([MGatner](https://github.com/MGatner)) -- Resolve session save handler issue [\#2307](https://github.com/codeigniter4/CodeIgniter4/pull/2307) ([jim-parry](https://github.com/jim-parry)) -- Fix curl debug bug [\#2305](https://github.com/codeigniter4/CodeIgniter4/pull/2305) ([michalsn](https://github.com/michalsn)) -- Use DBGroup variable from migration class if defined [\#2303](https://github.com/codeigniter4/CodeIgniter4/pull/2303) ([michalsn](https://github.com/michalsn)) -- Fix MySql \_fromTables\(\) [\#2302](https://github.com/codeigniter4/CodeIgniter4/pull/2302) ([pjsde](https://github.com/pjsde)) -- \[ci skip\] Routes collector for toolbar should not die when a method name is calculated through \_remap [\#2300](https://github.com/codeigniter4/CodeIgniter4/pull/2300) ([lonnieezell](https://github.com/lonnieezell)) -- fix issue on session\_regenerate. [\#2298](https://github.com/codeigniter4/CodeIgniter4/pull/2298) ([pjsde](https://github.com/pjsde)) -- Add counted\(\) to Inflector Helper [\#2296](https://github.com/codeigniter4/CodeIgniter4/pull/2296) ([MGatner](https://github.com/MGatner)) -- Test set\(\) method in Builder class more [\#2295](https://github.com/codeigniter4/CodeIgniter4/pull/2295) ([michalsn](https://github.com/michalsn)) -- Fix Code Modules documentation for psr4 namespace configuration [\#2290](https://github.com/codeigniter4/CodeIgniter4/pull/2290) ([romaven](https://github.com/romaven)) -- Don't restrict model's access to properties in a read-only manner [\#2289](https://github.com/codeigniter4/CodeIgniter4/pull/2289) ([lonnieezell](https://github.com/lonnieezell)) -- Fix line numbering in Debug/Exceptions class [\#2288](https://github.com/codeigniter4/CodeIgniter4/pull/2288) ([michalsn](https://github.com/michalsn)) -- Fix error with Host header for CURLRequest class [\#2285](https://github.com/codeigniter4/CodeIgniter4/pull/2285) ([michalsn](https://github.com/michalsn)) -- Fix getErrors\(\) for validation with redirect [\#2284](https://github.com/codeigniter4/CodeIgniter4/pull/2284) ([michalsn](https://github.com/michalsn)) -- Rename collectors \_\*.tpl.php to \_\*.tpl [\#2283](https://github.com/codeigniter4/CodeIgniter4/pull/2283) ([MGatner](https://github.com/MGatner)) -- Bug in CSRF parameter cleanup [\#2279](https://github.com/codeigniter4/CodeIgniter4/pull/2279) ([michalsn](https://github.com/michalsn)) -- WIP fix store\(\) default value bug [\#2123](https://github.com/codeigniter4/CodeIgniter4/pull/2123) ([s-proj](https://github.com/s-proj)) -- WIP Added validation on exists database before created for MySQLi… [\#2100](https://github.com/codeigniter4/CodeIgniter4/pull/2100) ([oleg1540](https://github.com/oleg1540)) - -## [v4.0.0-rc.2b](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.2b) (2019-09-28) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.2.1...v4.0.0-rc.2b) - -**Merged pull requests:** - -- Fix user guide for Message class [\#2282](https://github.com/codeigniter4/CodeIgniter4/pull/2282) ([michalsn](https://github.com/michalsn)) -- Handle X-CSRF-TOKEN - CSRF [\#2272](https://github.com/codeigniter4/CodeIgniter4/pull/2272) ([nowackipawel](https://github.com/nowackipawel)) -- QUICKFIX Batch Update Where Reset [\#2252](https://github.com/codeigniter4/CodeIgniter4/pull/2252) ([searchy2](https://github.com/searchy2)) - -## [v4.0.0-rc.2.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.2.1) (2019-09-28) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.2...v4.0.0-rc.2.1) - -**Closed issues:** - -- listTables\(\) failing to use correct prefix [\#2210](https://github.com/codeigniter4/CodeIgniter4/issues/2210) -- Query Builder Class documentation [\#2140](https://github.com/codeigniter4/CodeIgniter4/issues/2140) - -## [v4.0.0-rc.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.2) (2019-09-27) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.1...v4.0.0-rc.2) - -**Fixed bugs:** - -- Add magic \_\_isset to classes with \_\_get [\#2219](https://github.com/codeigniter4/CodeIgniter4/issues/2219) -- CIDatabaseTestCase double-deletes tables [\#2206](https://github.com/codeigniter4/CodeIgniter4/issues/2206) -- Locals problems [\#2195](https://github.com/codeigniter4/CodeIgniter4/issues/2195) -- Translations are not loaded from composer repository [\#2120](https://github.com/codeigniter4/CodeIgniter4/issues/2120) - -**Closed issues:** - -- Query grouping not working for HAVING clause [\#2247](https://github.com/codeigniter4/CodeIgniter4/issues/2247) -- $builder-\>like\(\) not support for HAVING clause [\#2242](https://github.com/codeigniter4/CodeIgniter4/issues/2242) -- invalid switch parameter [\#2239](https://github.com/codeigniter4/CodeIgniter4/issues/2239) -- DateTime::createFromFormat\(\) parameter incorrect [\#2238](https://github.com/codeigniter4/CodeIgniter4/issues/2238) -- strlen\(\) expects parameter string, integer given [\#2237](https://github.com/codeigniter4/CodeIgniter4/issues/2237) -- ini\_set\(\) expects parameter 2 to be string, integer given [\#2236](https://github.com/codeigniter4/CodeIgniter4/issues/2236) -- ini\_set\(\) expects parameter 2 to be string, integer given [\#2235](https://github.com/codeigniter4/CodeIgniter4/issues/2235) -- \# ini\_set\(\) expects parameter 2 to be string, integer given [\#2234](https://github.com/codeigniter4/CodeIgniter4/issues/2234) -- Extending The Model [\#2223](https://github.com/codeigniter4/CodeIgniter4/issues/2223) -- BUG curl\_setopt\_array\(\): supplied argument is not a valid file-handle resource [\#2222](https://github.com/codeigniter4/CodeIgniter4/issues/2222) -- How do I dynamically modify the configuration? [\#2214](https://github.com/codeigniter4/CodeIgniter4/issues/2214) -- Document the "whoops" error page [\#2198](https://github.com/codeigniter4/CodeIgniter4/issues/2198) -- Fail to open system/bootstrap.php [\#2193](https://github.com/codeigniter4/CodeIgniter4/issues/2193) -- Function lang\(\) / Type of return value [\#2192](https://github.com/codeigniter4/CodeIgniter4/issues/2192) -- Can we use codeigniter 4 on live server?? [\#2188](https://github.com/codeigniter4/CodeIgniter4/issues/2188) -- Custom query in model CI4 [\#2187](https://github.com/codeigniter4/CodeIgniter4/issues/2187) -- conflict between php zlib.output\_compression and output buffering [\#2182](https://github.com/codeigniter4/CodeIgniter4/issues/2182) -- API Trait documentation fix - failValidationError [\#2176](https://github.com/codeigniter4/CodeIgniter4/issues/2176) -- Validation issue on multiple file upload [\#2175](https://github.com/codeigniter4/CodeIgniter4/issues/2175) -- exif\_read\_data [\#2161](https://github.com/codeigniter4/CodeIgniter4/issues/2161) -- Database count methods [\#2159](https://github.com/codeigniter4/CodeIgniter4/issues/2159) -- Devstarter $salt [\#2156](https://github.com/codeigniter4/CodeIgniter4/issues/2156) -- Migration migrate, rollback and create problems [\#2147](https://github.com/codeigniter4/CodeIgniter4/issues/2147) -- Query Builder getWhere Crash [\#2143](https://github.com/codeigniter4/CodeIgniter4/issues/2143) -- View: $parser-\>render\(\); [\#2086](https://github.com/codeigniter4/CodeIgniter4/issues/2086) -- Return value of lang\(\) must be of the type string, array returned [\#2075](https://github.com/codeigniter4/CodeIgniter4/issues/2075) -- Wrong links for pager [\#2016](https://github.com/codeigniter4/CodeIgniter4/issues/2016) -- base\_url\(\) value dropped between namespaces [\#1942](https://github.com/codeigniter4/CodeIgniter4/issues/1942) -- Unable to use \_remap without default method in controller [\#1928](https://github.com/codeigniter4/CodeIgniter4/issues/1928) -- RESTful resources [\#1765](https://github.com/codeigniter4/CodeIgniter4/issues/1765) - -**Merged pull requests:** - -- Fix changelog \[ci skip\] [\#2273](https://github.com/codeigniter4/CodeIgniter4/pull/2273) ([jim-parry](https://github.com/jim-parry)) -- fix ResourcePresenter::setModel\(\) [\#2271](https://github.com/codeigniter4/CodeIgniter4/pull/2271) ([pjsde](https://github.com/pjsde)) -- groupStart\(\) refactorization [\#2270](https://github.com/codeigniter4/CodeIgniter4/pull/2270) ([michalsn](https://github.com/michalsn)) -- testMode\(\) method for BaseBuilder [\#2269](https://github.com/codeigniter4/CodeIgniter4/pull/2269) ([michalsn](https://github.com/michalsn)) -- Validation session use only if exists [\#2268](https://github.com/codeigniter4/CodeIgniter4/pull/2268) ([jim-parry](https://github.com/jim-parry)) -- Tests setUp and tearDown: void [\#2267](https://github.com/codeigniter4/CodeIgniter4/pull/2267) ([MGatner](https://github.com/MGatner)) -- RC.2 release prep [\#2266](https://github.com/codeigniter4/CodeIgniter4/pull/2266) ([jim-parry](https://github.com/jim-parry)) -- Fix a validation issue on multiple file upload [\#2265](https://github.com/codeigniter4/CodeIgniter4/pull/2265) ([pjsde](https://github.com/pjsde)) -- fix. Parser allow other extension [\#2264](https://github.com/codeigniter4/CodeIgniter4/pull/2264) ([Instrye](https://github.com/Instrye)) -- Fix parameter type in Debug/Exceptions [\#2262](https://github.com/codeigniter4/CodeIgniter4/pull/2262) ([jim-parry](https://github.com/jim-parry)) -- Fix lang\(\) signature [\#2261](https://github.com/codeigniter4/CodeIgniter4/pull/2261) ([jim-parry](https://github.com/jim-parry)) -- Explain the whoops page [\#2260](https://github.com/codeigniter4/CodeIgniter4/pull/2260) ([jim-parry](https://github.com/jim-parry)) -- Add URI & url\_helper tests [\#2259](https://github.com/codeigniter4/CodeIgniter4/pull/2259) ([jim-parry](https://github.com/jim-parry)) -- Several updates to the HAVING clauses [\#2257](https://github.com/codeigniter4/CodeIgniter4/pull/2257) ([michalsn](https://github.com/michalsn)) -- Fix invalid parameters [\#2253](https://github.com/codeigniter4/CodeIgniter4/pull/2253) ([pjsde](https://github.com/pjsde)) -- EXIF not supported for GIF [\#2246](https://github.com/codeigniter4/CodeIgniter4/pull/2246) ([jim-parry](https://github.com/jim-parry)) -- Fix class ref parameter types [\#2245](https://github.com/codeigniter4/CodeIgniter4/pull/2245) ([jim-parry](https://github.com/jim-parry)) -- Fix ini\_set parameter type [\#2241](https://github.com/codeigniter4/CodeIgniter4/pull/2241) ([jim-parry](https://github.com/jim-parry)) -- Handle JSON POSTs in CSRF [\#2240](https://github.com/codeigniter4/CodeIgniter4/pull/2240) ([nowackipawel](https://github.com/nowackipawel)) -- Fixes BaseBuilder getWhere\(\) bug [\#2232](https://github.com/codeigniter4/CodeIgniter4/pull/2232) ([michalsn](https://github.com/michalsn)) -- Add magic \_\_isset to classes with \_\_get [\#2231](https://github.com/codeigniter4/CodeIgniter4/pull/2231) ([MGatner](https://github.com/MGatner)) -- Add escape to SQLite \_listTables\(\) [\#2230](https://github.com/codeigniter4/CodeIgniter4/pull/2230) ([MGatner](https://github.com/MGatner)) -- MySQLi escapeLikeStringDirect\(\) [\#2229](https://github.com/codeigniter4/CodeIgniter4/pull/2229) ([MGatner](https://github.com/MGatner)) -- Exclude `sqlite\_%` from listTables\(\) [\#2228](https://github.com/codeigniter4/CodeIgniter4/pull/2228) ([MGatner](https://github.com/MGatner)) -- fix. CONTRIBUTING.md link [\#2226](https://github.com/codeigniter4/CodeIgniter4/pull/2226) ([Instrye](https://github.com/Instrye)) -- \[ci skip\] Fix malformed table in view\_parser.rst [\#2225](https://github.com/codeigniter4/CodeIgniter4/pull/2225) ([jim-parry](https://github.com/jim-parry)) -- change new \Config\Database\(\) to config\('Database'\) [\#2224](https://github.com/codeigniter4/CodeIgniter4/pull/2224) ([techoner](https://github.com/techoner)) -- Documentation fixes [\#2221](https://github.com/codeigniter4/CodeIgniter4/pull/2221) ([najdanovicivan](https://github.com/najdanovicivan)) -- Typo corrected [\#2218](https://github.com/codeigniter4/CodeIgniter4/pull/2218) ([dangereyes88](https://github.com/dangereyes88)) -- Update uri.rst [\#2216](https://github.com/codeigniter4/CodeIgniter4/pull/2216) ([dangereyes88](https://github.com/dangereyes88)) -- Filter listTables cache response on constrainPrefix [\#2213](https://github.com/codeigniter4/CodeIgniter4/pull/2213) ([MGatner](https://github.com/MGatner)) -- Add listTable\(\) tests [\#2211](https://github.com/codeigniter4/CodeIgniter4/pull/2211) ([MGatner](https://github.com/MGatner)) -- Add trace\(\) [\#2209](https://github.com/codeigniter4/CodeIgniter4/pull/2209) ([MGatner](https://github.com/MGatner)) -- Add $db-\>getPrefix\(\) [\#2208](https://github.com/codeigniter4/CodeIgniter4/pull/2208) ([MGatner](https://github.com/MGatner)) -- Fix empty\(\) bug on DBPrefix [\#2205](https://github.com/codeigniter4/CodeIgniter4/pull/2205) ([MGatner](https://github.com/MGatner)) -- Foreign key columns [\#2201](https://github.com/codeigniter4/CodeIgniter4/pull/2201) ([MGatner](https://github.com/MGatner)) -- Notify Kint of dd alias [\#2200](https://github.com/codeigniter4/CodeIgniter4/pull/2200) ([MGatner](https://github.com/MGatner)) -- Add getForeignKeyData to User Guide [\#2199](https://github.com/codeigniter4/CodeIgniter4/pull/2199) ([MGatner](https://github.com/MGatner)) -- Update Session.php [\#2197](https://github.com/codeigniter4/CodeIgniter4/pull/2197) ([cstechsandesh](https://github.com/cstechsandesh)) -- Migration rollback reverse [\#2191](https://github.com/codeigniter4/CodeIgniter4/pull/2191) ([MGatner](https://github.com/MGatner)) -- \[ci skip\] Fix name of ForeignKeyChecks [\#2190](https://github.com/codeigniter4/CodeIgniter4/pull/2190) ([MGatner](https://github.com/MGatner)) -- missing return [\#2189](https://github.com/codeigniter4/CodeIgniter4/pull/2189) ([titounnes](https://github.com/titounnes)) -- Fix case on "Seeds/" directory [\#2184](https://github.com/codeigniter4/CodeIgniter4/pull/2184) ([MGatner](https://github.com/MGatner)) -- Check `defined` for constants [\#2183](https://github.com/codeigniter4/CodeIgniter4/pull/2183) ([MGatner](https://github.com/MGatner)) -- Remove copy-paste extraneous text [\#2181](https://github.com/codeigniter4/CodeIgniter4/pull/2181) ([MGatner](https://github.com/MGatner)) -- Fix \_fromTables\(\) [\#2174](https://github.com/codeigniter4/CodeIgniter4/pull/2174) ([pjsde](https://github.com/pjsde)) -- Fix for CURL for 'debug' option [\#2168](https://github.com/codeigniter4/CodeIgniter4/pull/2168) ([MGatner](https://github.com/MGatner)) - -## [v4.0.0-rc.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.1) (2019-09-03) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-beta.4...v4.0.0-rc.1) - -**Implemented enhancements:** - -- BaseConfig should support array values with dot syntax [\#454](https://github.com/codeigniter4/CodeIgniter4/issues/454) - -**Closed issues:** - -- \[internal function\]: CodeIgniter\Debug\Exceptions-\>shutdownHandler\(\) [\#2173](https://github.com/codeigniter4/CodeIgniter4/issues/2173) -- Message-\>setHeader allowing duplicates [\#2170](https://github.com/codeigniter4/CodeIgniter4/issues/2170) -- CLI: Exit status [\#2163](https://github.com/codeigniter4/CodeIgniter4/issues/2163) -- QB countAllResults shouldn't use LIMIT settings [\#2152](https://github.com/codeigniter4/CodeIgniter4/issues/2152) -- BaseBuilder::get\(\) resets query even if reset = false [\#2141](https://github.com/codeigniter4/CodeIgniter4/issues/2141) -- Some migrations not running [\#2139](https://github.com/codeigniter4/CodeIgniter4/issues/2139) -- Migrations Refactor Namespaces [\#2138](https://github.com/codeigniter4/CodeIgniter4/issues/2138) -- $primaryKey forcefully 'needs' to be auto\_increment [\#2133](https://github.com/codeigniter4/CodeIgniter4/issues/2133) -- response data not set [\#2124](https://github.com/codeigniter4/CodeIgniter4/issues/2124) -- RESTful behaviour [\#2122](https://github.com/codeigniter4/CodeIgniter4/issues/2122) -- Redis [\#2121](https://github.com/codeigniter4/CodeIgniter4/issues/2121) -- Toolbar download bug [\#2117](https://github.com/codeigniter4/CodeIgniter4/issues/2117) -- Packagist not updated with latest release? [\#2115](https://github.com/codeigniter4/CodeIgniter4/issues/2115) -- Fatal error Installing using composer [\#2114](https://github.com/codeigniter4/CodeIgniter4/issues/2114) -- Allow loading Common.php function overrides [\#2101](https://github.com/codeigniter4/CodeIgniter4/issues/2101) -- Result from database was auto encoded when using Entity [\#2088](https://github.com/codeigniter4/CodeIgniter4/issues/2088) -- Honeypot does not close the form [\#2084](https://github.com/codeigniter4/CodeIgniter4/issues/2084) -- Imagick Image library handler return array instead of boolean [\#2029](https://github.com/codeigniter4/CodeIgniter4/issues/2029) -- Migrations command should use the UTC datetime when creating new migrations [\#2018](https://github.com/codeigniter4/CodeIgniter4/issues/2018) -- FileLocator-\>getNamespaces with parameter [\#1866](https://github.com/codeigniter4/CodeIgniter4/issues/1866) - -**Merged pull requests:** - -- Fix query builder user guide page [\#2180](https://github.com/codeigniter4/CodeIgniter4/pull/2180) ([jim-parry](https://github.com/jim-parry)) -- RC.1 prep [\#2179](https://github.com/codeigniter4/CodeIgniter4/pull/2179) ([jim-parry](https://github.com/jim-parry)) -- Add fallback for missing finfo\_open [\#2178](https://github.com/codeigniter4/CodeIgniter4/pull/2178) ([MGatner](https://github.com/MGatner)) -- Fix missing form close tag [\#2177](https://github.com/codeigniter4/CodeIgniter4/pull/2177) ([jim-parry](https://github.com/jim-parry)) -- Base FeatureTestCase on CIUnitTestCase [\#2172](https://github.com/codeigniter4/CodeIgniter4/pull/2172) ([jim-parry](https://github.com/jim-parry)) -- Setheader dupes [\#2171](https://github.com/codeigniter4/CodeIgniter4/pull/2171) ([MGatner](https://github.com/MGatner)) -- Add $quality usage for Image Library [\#2169](https://github.com/codeigniter4/CodeIgniter4/pull/2169) ([MGatner](https://github.com/MGatner)) -- Cookie error [\#2166](https://github.com/codeigniter4/CodeIgniter4/pull/2166) ([pjsde](https://github.com/pjsde)) -- RESTful help [\#2165](https://github.com/codeigniter4/CodeIgniter4/pull/2165) ([jim-parry](https://github.com/jim-parry)) -- Exit error code on CLI Command failure [\#2164](https://github.com/codeigniter4/CodeIgniter4/pull/2164) ([MGatner](https://github.com/MGatner)) -- User Guide updates for Common.php [\#2162](https://github.com/codeigniter4/CodeIgniter4/pull/2162) ([MGatner](https://github.com/MGatner)) -- Add BaseBuilder SelectCount [\#2160](https://github.com/codeigniter4/CodeIgniter4/pull/2160) ([MGatner](https://github.com/MGatner)) -- Update migrations config [\#2157](https://github.com/codeigniter4/CodeIgniter4/pull/2157) ([jim-parry](https://github.com/jim-parry)) -- Include .gitignore in starters [\#2155](https://github.com/codeigniter4/CodeIgniter4/pull/2155) ([MGatner](https://github.com/MGatner)) -- Fix email & migrations docs; update changelog [\#2154](https://github.com/codeigniter4/CodeIgniter4/pull/2154) ([jim-parry](https://github.com/jim-parry)) -- Bug fix countAllResults with LIMIT [\#2153](https://github.com/codeigniter4/CodeIgniter4/pull/2153) ([tangix](https://github.com/tangix)) -- ImageMagick-\>save\(\) return value [\#2151](https://github.com/codeigniter4/CodeIgniter4/pull/2151) ([MGatner](https://github.com/MGatner)) -- New logic for Image-\>fit\(\) [\#2150](https://github.com/codeigniter4/CodeIgniter4/pull/2150) ([MGatner](https://github.com/MGatner)) -- listNamespaceFiles: Ensure trailing slash [\#2149](https://github.com/codeigniter4/CodeIgniter4/pull/2149) ([MGatner](https://github.com/MGatner)) -- Remove UserModel reference from Home controller [\#2145](https://github.com/codeigniter4/CodeIgniter4/pull/2145) ([andreportaro](https://github.com/andreportaro)) -- Update Redis legacy function [\#2144](https://github.com/codeigniter4/CodeIgniter4/pull/2144) ([MGatner](https://github.com/MGatner)) -- Fixing BuilderBase resetting when getting the SQL [\#2142](https://github.com/codeigniter4/CodeIgniter4/pull/2142) ([tangix](https://github.com/tangix)) -- New Migration Logic [\#2137](https://github.com/codeigniter4/CodeIgniter4/pull/2137) ([MGatner](https://github.com/MGatner)) -- Migrations user guide fixes [\#2136](https://github.com/codeigniter4/CodeIgniter4/pull/2136) ([MGatner](https://github.com/MGatner)) -- Encryption [\#2135](https://github.com/codeigniter4/CodeIgniter4/pull/2135) ([jim-parry](https://github.com/jim-parry)) -- Fix localization writeup [\#2134](https://github.com/codeigniter4/CodeIgniter4/pull/2134) ([jim-parry](https://github.com/jim-parry)) -- Update migration User Guide [\#2132](https://github.com/codeigniter4/CodeIgniter4/pull/2132) ([MGatner](https://github.com/MGatner)) -- Added No Content response to API\ResponseTrait [\#2131](https://github.com/codeigniter4/CodeIgniter4/pull/2131) ([tangix](https://github.com/tangix)) -- Add setFileName\(\) to DownloadResponse [\#2129](https://github.com/codeigniter4/CodeIgniter4/pull/2129) ([MGatner](https://github.com/MGatner)) -- guessExtension fallback to clientExtension [\#2128](https://github.com/codeigniter4/CodeIgniter4/pull/2128) ([MGatner](https://github.com/MGatner)) -- Update limit function since $offset is nullable [\#2127](https://github.com/codeigniter4/CodeIgniter4/pull/2127) ([vibbow](https://github.com/vibbow)) -- Limit storePreviousURL to certain requests [\#2126](https://github.com/codeigniter4/CodeIgniter4/pull/2126) ([MGatner](https://github.com/MGatner)) -- Updated redis session handler to support redis 5.0.x [\#2125](https://github.com/codeigniter4/CodeIgniter4/pull/2125) ([tangix](https://github.com/tangix)) -- Disabled Toolbar on downloads [\#2118](https://github.com/codeigniter4/CodeIgniter4/pull/2118) ([MGatner](https://github.com/MGatner)) -- Add Image-\>convert\(\) [\#2113](https://github.com/codeigniter4/CodeIgniter4/pull/2113) ([MGatner](https://github.com/MGatner)) -- Update `Entity.php` `\_\_isset` method [\#2112](https://github.com/codeigniter4/CodeIgniter4/pull/2112) ([vibbow](https://github.com/vibbow)) -- Added app/Common.php [\#2110](https://github.com/codeigniter4/CodeIgniter4/pull/2110) ([jason-napolitano](https://github.com/jason-napolitano)) -- Fix typo in checking if exists db\_connect\(\) [\#2109](https://github.com/codeigniter4/CodeIgniter4/pull/2109) ([xbotkaj](https://github.com/xbotkaj)) -- Original email port [\#2092](https://github.com/codeigniter4/CodeIgniter4/pull/2092) ([jim-parry](https://github.com/jim-parry)) -- Fix prevent soft delete all without conditions set [\#2090](https://github.com/codeigniter4/CodeIgniter4/pull/2090) ([rino7](https://github.com/rino7)) -- Update BaseConfig.php [\#2082](https://github.com/codeigniter4/CodeIgniter4/pull/2082) ([zl59503020](https://github.com/zl59503020)) -- WIP: Migration updates for more wholistic functionality [\#2065](https://github.com/codeigniter4/CodeIgniter4/pull/2065) ([lonnieezell](https://github.com/lonnieezell)) -- clean base controller code [\#2046](https://github.com/codeigniter4/CodeIgniter4/pull/2046) ([behnampro](https://github.com/behnampro)) -- Fix CSRF hash regeneration [\#2027](https://github.com/codeigniter4/CodeIgniter4/pull/2027) ([Workoverflow](https://github.com/Workoverflow)) -- WIP Verbiage revisions [\#2010](https://github.com/codeigniter4/CodeIgniter4/pull/2010) ([kydojo](https://github.com/kydojo)) -- Subqueries in BaseBuilder [\#2001](https://github.com/codeigniter4/CodeIgniter4/pull/2001) ([iRedds](https://github.com/iRedds)) - -## [v4.0.0-beta.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-beta.4) (2019-07-25) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-beta.3...v4.0.0-beta.4) - -**Fixed bugs:** - -- Controller filters match too loosely. [\#2038](https://github.com/codeigniter4/CodeIgniter4/issues/2038) - -**Closed issues:** - -- File-\>getDestination fails without extension [\#2103](https://github.com/codeigniter4/CodeIgniter4/issues/2103) -- User Guide: UploadedFile Class [\#2102](https://github.com/codeigniter4/CodeIgniter4/issues/2102) -- Worries about postgresql errors [\#2097](https://github.com/codeigniter4/CodeIgniter4/issues/2097) -- README.md - Link to Announcement on Forums is a 404? [\#2094](https://github.com/codeigniter4/CodeIgniter4/issues/2094) -- Entity castAsJson returns an empty array [\#2080](https://github.com/codeigniter4/CodeIgniter4/issues/2080) -- Migrations Sequential field information is required [\#2076](https://github.com/codeigniter4/CodeIgniter4/issues/2076) -- function gussExtension return wrong result, return csv instead of right answer "txt" or "text" [\#2066](https://github.com/codeigniter4/CodeIgniter4/issues/2066) -- Unexpected empty "query" property when returning CodeIgniter\HTTP\URI [\#2062](https://github.com/codeigniter4/CodeIgniter4/issues/2062) -- Multiple rules for file upload always return false [\#2061](https://github.com/codeigniter4/CodeIgniter4/issues/2061) -- The assets of the public\_folder are not loaded [\#2047](https://github.com/codeigniter4/CodeIgniter4/issues/2047) -- Modify Model's deleted field to be a date [\#2041](https://github.com/codeigniter4/CodeIgniter4/issues/2041) -- Filter Config not quite working with Routes? [\#2037](https://github.com/codeigniter4/CodeIgniter4/issues/2037) -- force\_https\(\) doesn't redirect [\#2033](https://github.com/codeigniter4/CodeIgniter4/issues/2033) -- URI segments passed in as method parameters skips segments with value as 0 \(zero\) [\#2032](https://github.com/codeigniter4/CodeIgniter4/issues/2032) -- /System/Debug/Toolbar/Collectors/Routes.php on line 83 [\#2028](https://github.com/codeigniter4/CodeIgniter4/issues/2028) -- php spark not working [\#2025](https://github.com/codeigniter4/CodeIgniter4/issues/2025) -- PR\#2012 caused 404 exception in spark [\#2021](https://github.com/codeigniter4/CodeIgniter4/issues/2021) -- Cache config [\#2017](https://github.com/codeigniter4/CodeIgniter4/issues/2017) -- CodeIgniter\Entity Setter doesn't work [\#2013](https://github.com/codeigniter4/CodeIgniter4/issues/2013) -- validation match\[x\] don't work anymore... if custom setter is used. [\#2006](https://github.com/codeigniter4/CodeIgniter4/issues/2006) -- Paths issue when moving Views outside of app folder [\#1998](https://github.com/codeigniter4/CodeIgniter4/issues/1998) -- View Parser Register Plugins as closures not works! [\#1997](https://github.com/codeigniter4/CodeIgniter4/issues/1997) -- View Parser site\_url not works? [\#1995](https://github.com/codeigniter4/CodeIgniter4/issues/1995) -- CURLRequest not respecting debug flag [\#1994](https://github.com/codeigniter4/CodeIgniter4/issues/1994) -- Entity null values cause database error [\#1992](https://github.com/codeigniter4/CodeIgniter4/issues/1992) -- SQLite driver throws exception when using dropForeignKey [\#1982](https://github.com/codeigniter4/CodeIgniter4/issues/1982) -- Security: DotEnv loads DB password plaintext in $\_SERVER [\#1969](https://github.com/codeigniter4/CodeIgniter4/issues/1969) -- Feature: FK Constraint Enable/Disable [\#1964](https://github.com/codeigniter4/CodeIgniter4/issues/1964) -- redirect\($namedRoute\) missing helpful exception [\#1953](https://github.com/codeigniter4/CodeIgniter4/issues/1953) - -**Merged pull requests:** - -- Update the starters [\#2108](https://github.com/codeigniter4/CodeIgniter4/pull/2108) ([jim-parry](https://github.com/jim-parry)) -- Prep for beta.4 [\#2107](https://github.com/codeigniter4/CodeIgniter4/pull/2107) ([jim-parry](https://github.com/jim-parry)) -- File & UploadFile Fixes [\#2104](https://github.com/codeigniter4/CodeIgniter4/pull/2104) ([MGatner](https://github.com/MGatner)) -- Timezone select [\#2091](https://github.com/codeigniter4/CodeIgniter4/pull/2091) ([MGatner](https://github.com/MGatner)) -- JSON format checking improved [\#2081](https://github.com/codeigniter4/CodeIgniter4/pull/2081) ([nowackipawel](https://github.com/nowackipawel)) -- Update config\(\) to check all namespaces [\#2079](https://github.com/codeigniter4/CodeIgniter4/pull/2079) ([MGatner](https://github.com/MGatner)) -- Throttler can access bucket for bucket life time [\#2074](https://github.com/codeigniter4/CodeIgniter4/pull/2074) ([MohKari](https://github.com/MohKari)) -- Fix autoloader.rst formatting [\#2071](https://github.com/codeigniter4/CodeIgniter4/pull/2071) ([jim-parry](https://github.com/jim-parry)) -- validation rule: then -\> than \(spelling\) [\#2069](https://github.com/codeigniter4/CodeIgniter4/pull/2069) ([nowackipawel](https://github.com/nowackipawel)) -- Bugfix file locator slash error [\#2064](https://github.com/codeigniter4/CodeIgniter4/pull/2064) ([MGatner](https://github.com/MGatner)) -- Ensure query vars are part of request-\>uri. Fixes \#2062 [\#2063](https://github.com/codeigniter4/CodeIgniter4/pull/2063) ([lonnieezell](https://github.com/lonnieezell)) -- Cache Drive Backups [\#2060](https://github.com/codeigniter4/CodeIgniter4/pull/2060) ([MohKari](https://github.com/MohKari)) -- Add multi-path support to `locateFile\(\)` [\#2059](https://github.com/codeigniter4/CodeIgniter4/pull/2059) ([MGatner](https://github.com/MGatner)) -- Add model exceptions for missing/invalid dateFormat [\#2054](https://github.com/codeigniter4/CodeIgniter4/pull/2054) ([MGatner](https://github.com/MGatner)) -- Change Model's deleted flag to a deleted\_at datetime/timestamp. Fixes \#2041 [\#2053](https://github.com/codeigniter4/CodeIgniter4/pull/2053) ([lonnieezell](https://github.com/lonnieezell)) -- Add various tests for \(not\) null [\#2052](https://github.com/codeigniter4/CodeIgniter4/pull/2052) ([MGatner](https://github.com/MGatner)) -- Soft deletes use deleted\_at [\#2051](https://github.com/codeigniter4/CodeIgniter4/pull/2051) ([MGatner](https://github.com/MGatner)) -- Stash insert ID before event trigger [\#2050](https://github.com/codeigniter4/CodeIgniter4/pull/2050) ([MGatner](https://github.com/MGatner)) -- Zero params should be passed through when routing. Fixes \#2032 [\#2043](https://github.com/codeigniter4/CodeIgniter4/pull/2043) ([lonnieezell](https://github.com/lonnieezell)) -- SQLite3 now supports dropping foreign keys. Fixes \#1982 [\#2042](https://github.com/codeigniter4/CodeIgniter4/pull/2042) ([lonnieezell](https://github.com/lonnieezell)) -- Update CURLRequest.php [\#2040](https://github.com/codeigniter4/CodeIgniter4/pull/2040) ([nowackipawel](https://github.com/nowackipawel)) -- Restrict filter matching of uris so they require an exact match. Fixes \#2038 [\#2039](https://github.com/codeigniter4/CodeIgniter4/pull/2039) ([lonnieezell](https://github.com/lonnieezell)) -- Make `force\_https\(\)` send headers before exit [\#2036](https://github.com/codeigniter4/CodeIgniter4/pull/2036) ([MGatner](https://github.com/MGatner)) -- Various typos and Guide corrections [\#2035](https://github.com/codeigniter4/CodeIgniter4/pull/2035) ([MGatner](https://github.com/MGatner)) -- Fallback to server request for default method [\#2031](https://github.com/codeigniter4/CodeIgniter4/pull/2031) ([MGatner](https://github.com/MGatner)) -- Support the new `router` service in Debug Toolbar [\#2030](https://github.com/codeigniter4/CodeIgniter4/pull/2030) ([MGatner](https://github.com/MGatner)) -- Extension Pager::makeLinks \(optional grup name\) [\#2026](https://github.com/codeigniter4/CodeIgniter4/pull/2026) ([nowackipawel](https://github.com/nowackipawel)) -- Refactor the way the router and route collection determine the current HTTP verb. [\#2024](https://github.com/codeigniter4/CodeIgniter4/pull/2024) ([lonnieezell](https://github.com/lonnieezell)) -- SQLite and Mysql driver additional tests and migration runner test fixes [\#2019](https://github.com/codeigniter4/CodeIgniter4/pull/2019) ([lonnieezell](https://github.com/lonnieezell)) -- Direct user to follow the upgrade steps after installation [\#2015](https://github.com/codeigniter4/CodeIgniter4/pull/2015) ([agmckee](https://github.com/agmckee)) -- Added a new Session/ArrayHandler that can be used during testing. [\#2014](https://github.com/codeigniter4/CodeIgniter4/pull/2014) ([lonnieezell](https://github.com/lonnieezell)) -- Use request-\>method for HTTP verb [\#2012](https://github.com/codeigniter4/CodeIgniter4/pull/2012) ([MGatner](https://github.com/MGatner)) -- Set the raw data array without any mutations for the Entity [\#2011](https://github.com/codeigniter4/CodeIgniter4/pull/2011) ([iRedds](https://github.com/iRedds)) -- Add `patch` method to command "routes" [\#2008](https://github.com/codeigniter4/CodeIgniter4/pull/2008) ([MGatner](https://github.com/MGatner)) -- Plugin closures docs update and test [\#2005](https://github.com/codeigniter4/CodeIgniter4/pull/2005) ([lonnieezell](https://github.com/lonnieezell)) -- Allow hasChanged\(\) without parameter [\#2004](https://github.com/codeigniter4/CodeIgniter4/pull/2004) ([MGatner](https://github.com/MGatner)) -- Entity refactor [\#2002](https://github.com/codeigniter4/CodeIgniter4/pull/2002) ([lonnieezell](https://github.com/lonnieezell)) -- use CodeIgniter\Controller; not needed since Home Controller extends … [\#1999](https://github.com/codeigniter4/CodeIgniter4/pull/1999) ([titounnes](https://github.com/titounnes)) -- Attempting to fix CURLRequest debug issue. \#1994 [\#1996](https://github.com/codeigniter4/CodeIgniter4/pull/1996) ([lonnieezell](https://github.com/lonnieezell)) -- argument set\(\) must by type of string - cannot agree [\#1989](https://github.com/codeigniter4/CodeIgniter4/pull/1989) ([nowackipawel](https://github.com/nowackipawel)) -- Prevent reverseRoute from searching closures [\#1959](https://github.com/codeigniter4/CodeIgniter4/pull/1959) ([MGatner](https://github.com/MGatner)) - -## [v4.0.0-beta.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-beta.3) (2019-05-06) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-beta.1...v4.0.0-beta.3) - -**Fixed bugs:** - -- travis-ci build broken [\#1830](https://github.com/codeigniter4/CodeIgniter4/issues/1830) -- \[Re-opened\] 404 File not found when running CodeIgniter on local Apache web server and virtual hosts [\#1400](https://github.com/codeigniter4/CodeIgniter4/issues/1400) -- MySQLi SSL verify [\#1219](https://github.com/codeigniter4/CodeIgniter4/issues/1219) - -**Closed issues:** - -- SQLite driver doesn't drop indexes when dropping a table [\#1983](https://github.com/codeigniter4/CodeIgniter4/issues/1983) -- About mysqli options MYSQLI\_OPT\_INT\_AND\_FLOAT\_NATIVE [\#1979](https://github.com/codeigniter4/CodeIgniter4/issues/1979) -- Toolbar won't accept custom collectors [\#1971](https://github.com/codeigniter4/CodeIgniter4/issues/1971) -- 404 Error in pathinfo mode [\#1965](https://github.com/codeigniter4/CodeIgniter4/issues/1965) -- A controller parameter without default value creates ReflectionException error [\#1948](https://github.com/codeigniter4/CodeIgniter4/issues/1948) -- Wrong datetime on history debug toolbar [\#1944](https://github.com/codeigniter4/CodeIgniter4/issues/1944) -- Appstarter is not working after installation [\#1941](https://github.com/codeigniter4/CodeIgniter4/issues/1941) -- AppStarter Vanilla Installation - No hint that writable directory is not writable [\#1934](https://github.com/codeigniter4/CodeIgniter4/issues/1934) -- Updating appstarter with composer does not take composer.phar into account [\#1932](https://github.com/codeigniter4/CodeIgniter4/issues/1932) -- Problem with extending core class. [\#1922](https://github.com/codeigniter4/CodeIgniter4/issues/1922) -- Debug Toolbar causes error if an array is provided as session value since Beta 2 [\#1919](https://github.com/codeigniter4/CodeIgniter4/issues/1919) -- Wrong controller filter processing because of faulty regular expression generation [\#1907](https://github.com/codeigniter4/CodeIgniter4/issues/1907) -- Toolbar not supporting IE11 \(realXHR.responseURL\) [\#1905](https://github.com/codeigniter4/CodeIgniter4/issues/1905) -- Validation Always Print Error Message [\#1903](https://github.com/codeigniter4/CodeIgniter4/issues/1903) -- Using soft deletes can lead to mysql ambiguous exception [\#1881](https://github.com/codeigniter4/CodeIgniter4/issues/1881) -- Error when running `php spark serve` \(spaces in folder names\) [\#1880](https://github.com/codeigniter4/CodeIgniter4/issues/1880) -- Class 'CodeIgniter\Filters\DebugToolbar' not found [\#1871](https://github.com/codeigniter4/CodeIgniter4/issues/1871) -- Should unmatched cache\(\) return null? [\#1870](https://github.com/codeigniter4/CodeIgniter4/issues/1870) -- Class 'CodeIgniter\Test\CIUnitTestCase' not found when testing in Appstarter project [\#1864](https://github.com/codeigniter4/CodeIgniter4/issues/1864) -- Public methods in system/Controller [\#1849](https://github.com/codeigniter4/CodeIgniter4/issues/1849) -- Controller in folder - method not accessible unless I set a route on Linux - Windows OK [\#1841](https://github.com/codeigniter4/CodeIgniter4/issues/1841) -- Routing to hardcoded ids does not work [\#1838](https://github.com/codeigniter4/CodeIgniter4/issues/1838) -- Form Validation: required\_with and required\_without implementations [\#1837](https://github.com/codeigniter4/CodeIgniter4/issues/1837) -- Events.php on method $callable parameter accepts only callable [\#1835](https://github.com/codeigniter4/CodeIgniter4/issues/1835) -- Controller response property overriding by ControllerResponse inPHPUnit [\#1834](https://github.com/codeigniter4/CodeIgniter4/issues/1834) -- ValidationInterface run method $data attribute should be nullable [\#1833](https://github.com/codeigniter4/CodeIgniter4/issues/1833) -- Fail to render view in a view with layout [\#1826](https://github.com/codeigniter4/CodeIgniter4/issues/1826) -- UploadedFile::move and File::move have different implementation [\#1825](https://github.com/codeigniter4/CodeIgniter4/issues/1825) -- Missing documentation: parser is not able to handle nested loops [\#1821](https://github.com/codeigniter4/CodeIgniter4/issues/1821) -- Is hashId function missing? [\#1801](https://github.com/codeigniter4/CodeIgniter4/issues/1801) -- Parser is not able to handle nested loops [\#1799](https://github.com/codeigniter4/CodeIgniter4/issues/1799) -- Routing rules order \[suspended / probably csrf\] [\#1798](https://github.com/codeigniter4/CodeIgniter4/issues/1798) -- I need to call session\(\) if I want to be able to use old\(\) in the forms. [\#1795](https://github.com/codeigniter4/CodeIgniter4/issues/1795) -- Output getting buffered when running via command line [\#1792](https://github.com/codeigniter4/CodeIgniter4/issues/1792) -- Wrong CodeIgniter::handleRequest method definition [\#1786](https://github.com/codeigniter4/CodeIgniter4/issues/1786) -- File::move is not moving file [\#1785](https://github.com/codeigniter4/CodeIgniter4/issues/1785) -- Question about date helper [\#1783](https://github.com/codeigniter4/CodeIgniter4/issues/1783) -- Intention or bug? File::move does not update path [\#1782](https://github.com/codeigniter4/CodeIgniter4/issues/1782) -- Small typos in documentation section "Taking Advantage of Spl" [\#1781](https://github.com/codeigniter4/CodeIgniter4/issues/1781) -- Documentation mistake: Model::save does not return a boolean only [\#1780](https://github.com/codeigniter4/CodeIgniter4/issues/1780) -- Toolbar::run produces incompatible data for json\_encode [\#1779](https://github.com/codeigniter4/CodeIgniter4/issues/1779) -- History::setFiles may crash when reading empty file [\#1778](https://github.com/codeigniter4/CodeIgniter4/issues/1778) -- Can't set subquery as WHERE condition. [\#1775](https://github.com/codeigniter4/CodeIgniter4/issues/1775) -- Ignoring 'required' validation rule for inserts. [\#1773](https://github.com/codeigniter4/CodeIgniter4/issues/1773) -- save\(\) method trying to insert instead of update [\#1770](https://github.com/codeigniter4/CodeIgniter4/issues/1770) -- Controller Test / Feature Testing output issues [\#1767](https://github.com/codeigniter4/CodeIgniter4/issues/1767) -- MigrationRunner::version should return "current version string on success" [\#1766](https://github.com/codeigniter4/CodeIgniter4/issues/1766) -- DIRECTORY\_SEPARATOR / Different Behavior under Windows [\#1760](https://github.com/codeigniter4/CodeIgniter4/issues/1760) -- HTTP Feature Testing not working [\#1710](https://github.com/codeigniter4/CodeIgniter4/issues/1710) -- alpha4-\>5 requires to have primary key in every model/table [\#1706](https://github.com/codeigniter4/CodeIgniter4/issues/1706) -- route\_to\('name'\); does not work for other subdomains [\#1697](https://github.com/codeigniter4/CodeIgniter4/issues/1697) -- Router issue - overwriting. [\#1692](https://github.com/codeigniter4/CodeIgniter4/issues/1692) -- Using Memcache as Session Handler cause exception during regenerate. [\#1676](https://github.com/codeigniter4/CodeIgniter4/issues/1676) -- Model's without primary keys get pagination counts wrong [\#1597](https://github.com/codeigniter4/CodeIgniter4/issues/1597) -- Unable set ENVIRONMENT with Spark [\#1268](https://github.com/codeigniter4/CodeIgniter4/issues/1268) -- WIP Improve unit tests [\#512](https://github.com/codeigniter4/CodeIgniter4/issues/512) - -**Merged pull requests:** - -- Prep for beta.3 [\#1990](https://github.com/codeigniter4/CodeIgniter4/pull/1990) ([jim-parry](https://github.com/jim-parry)) -- Correct API docblock problems for phpdocs [\#1987](https://github.com/codeigniter4/CodeIgniter4/pull/1987) ([jim-parry](https://github.com/jim-parry)) -- Update docblock version to 4.0.0 [\#1986](https://github.com/codeigniter4/CodeIgniter4/pull/1986) ([jim-parry](https://github.com/jim-parry)) -- Fix filter processing. Fixes \#1907 [\#1985](https://github.com/codeigniter4/CodeIgniter4/pull/1985) ([jim-parry](https://github.com/jim-parry)) -- Add footing to HTML Table [\#1984](https://github.com/codeigniter4/CodeIgniter4/pull/1984) ([jim-parry](https://github.com/jim-parry)) -- Using soft deletes should not return an ambiguous field message when joining tables. Closes \#1881 [\#1981](https://github.com/codeigniter4/CodeIgniter4/pull/1981) ([lonnieezell](https://github.com/lonnieezell)) -- Corrected return value for Session/RedisHandler::read to string, per PHP specs [\#1980](https://github.com/codeigniter4/CodeIgniter4/pull/1980) ([lonnieezell](https://github.com/lonnieezell)) -- Implement HTML Table for CI4 [\#1978](https://github.com/codeigniter4/CodeIgniter4/pull/1978) ([jim-parry](https://github.com/jim-parry)) -- Test/featuretestcase [\#1977](https://github.com/codeigniter4/CodeIgniter4/pull/1977) ([jim-parry](https://github.com/jim-parry)) -- Fix validation rules table format [\#1975](https://github.com/codeigniter4/CodeIgniter4/pull/1975) ([jim-parry](https://github.com/jim-parry)) -- Remove framework classes from the autoloader classmap. [\#1974](https://github.com/codeigniter4/CodeIgniter4/pull/1974) ([lonnieezell](https://github.com/lonnieezell)) -- Defaultfixes [\#1973](https://github.com/codeigniter4/CodeIgniter4/pull/1973) ([lonnieezell](https://github.com/lonnieezell)) -- Toolbar fix for custom collectors [\#1972](https://github.com/codeigniter4/CodeIgniter4/pull/1972) ([MGatner](https://github.com/MGatner)) -- Add back filter arguments [\#1970](https://github.com/codeigniter4/CodeIgniter4/pull/1970) ([MGatner](https://github.com/MGatner)) -- Fixed pathinfo mode 404 error, rebuild array index of uri segments from array\_filter\(\) [\#1968](https://github.com/codeigniter4/CodeIgniter4/pull/1968) ([viosion](https://github.com/viosion)) -- String type primary key should also wrap into an array during db update [\#1963](https://github.com/codeigniter4/CodeIgniter4/pull/1963) ([vibbow](https://github.com/vibbow)) -- WIP - Fix side issue [\#1962](https://github.com/codeigniter4/CodeIgniter4/pull/1962) ([vibbow](https://github.com/vibbow)) -- Fix Debugbar url tail slash issue [\#1961](https://github.com/codeigniter4/CodeIgniter4/pull/1961) ([vibbow](https://github.com/vibbow)) -- New generic string validation rule. [\#1957](https://github.com/codeigniter4/CodeIgniter4/pull/1957) ([lonnieezell](https://github.com/lonnieezell)) -- Use Null Coalesce Operator [\#1956](https://github.com/codeigniter4/CodeIgniter4/pull/1956) ([carusogabriel](https://github.com/carusogabriel)) -- Travis-CI build failed fix [\#1955](https://github.com/codeigniter4/CodeIgniter4/pull/1955) ([atishamte](https://github.com/atishamte)) -- Fix validation table format [\#1954](https://github.com/codeigniter4/CodeIgniter4/pull/1954) ([jim-parry](https://github.com/jim-parry)) -- Add Validations for `equals\(\)` and `not\_equals\(\)` [\#1952](https://github.com/codeigniter4/CodeIgniter4/pull/1952) ([MGatner](https://github.com/MGatner)) -- System typos changes & code cleanup [\#1951](https://github.com/codeigniter4/CodeIgniter4/pull/1951) ([atishamte](https://github.com/atishamte)) -- Fix some side issue [\#1950](https://github.com/codeigniter4/CodeIgniter4/pull/1950) ([vibbow](https://github.com/vibbow)) -- Toobar/Routes correction [\#1949](https://github.com/codeigniter4/CodeIgniter4/pull/1949) ([atishamte](https://github.com/atishamte)) -- Fix BaseConfig didn't load Registrar files properly [\#1947](https://github.com/codeigniter4/CodeIgniter4/pull/1947) ([vibbow](https://github.com/vibbow)) -- Fix datetime extraction from debugbar file [\#1945](https://github.com/codeigniter4/CodeIgniter4/pull/1945) ([soft2u](https://github.com/soft2u)) -- Model, Entity, Exception & Migration test cases [\#1943](https://github.com/codeigniter4/CodeIgniter4/pull/1943) ([atishamte](https://github.com/atishamte)) -- Remove section that prevents hotlinking [\#1939](https://github.com/codeigniter4/CodeIgniter4/pull/1939) ([MGatner](https://github.com/MGatner)) -- Database typos changes [\#1938](https://github.com/codeigniter4/CodeIgniter4/pull/1938) ([atishamte](https://github.com/atishamte)) -- Docs: improve app testing writeup [\#1936](https://github.com/codeigniter4/CodeIgniter4/pull/1936) ([jim-parry](https://github.com/jim-parry)) -- Update phpunit.xml scripts. Fixes \#1932 [\#1935](https://github.com/codeigniter4/CodeIgniter4/pull/1935) ([jim-parry](https://github.com/jim-parry)) -- having \(Is NULL deletion\) [\#1933](https://github.com/codeigniter4/CodeIgniter4/pull/1933) ([nowackipawel](https://github.com/nowackipawel)) -- Toolbar IE11 fix [\#1931](https://github.com/codeigniter4/CodeIgniter4/pull/1931) ([REJack](https://github.com/REJack)) -- Model Changes w.r.t. \#1773 [\#1930](https://github.com/codeigniter4/CodeIgniter4/pull/1930) ([atishamte](https://github.com/atishamte)) -- Entity exception for non existed props. [\#1927](https://github.com/codeigniter4/CodeIgniter4/pull/1927) ([nowackipawel](https://github.com/nowackipawel)) -- Docs: update installation guide [\#1926](https://github.com/codeigniter4/CodeIgniter4/pull/1926) ([jim-parry](https://github.com/jim-parry)) -- removed $\_SERVER\['CI\_ENVIRONMENT'\] [\#1925](https://github.com/codeigniter4/CodeIgniter4/pull/1925) ([truelineinfotech](https://github.com/truelineinfotech)) -- missing return [\#1923](https://github.com/codeigniter4/CodeIgniter4/pull/1923) ([titounnes](https://github.com/titounnes)) -- JSONFormatter [\#1918](https://github.com/codeigniter4/CodeIgniter4/pull/1918) ([nowackipawel](https://github.com/nowackipawel)) -- Database Test Cases [\#1917](https://github.com/codeigniter4/CodeIgniter4/pull/1917) ([atishamte](https://github.com/atishamte)) -- Check if the value is string [\#1916](https://github.com/codeigniter4/CodeIgniter4/pull/1916) ([daif](https://github.com/daif)) -- Fix for POST + JSON \(Content-Length added\) [\#1915](https://github.com/codeigniter4/CodeIgniter4/pull/1915) ([nowackipawel](https://github.com/nowackipawel)) -- Housekeeping - prep for beta.2 [\#1914](https://github.com/codeigniter4/CodeIgniter4/pull/1914) ([jim-parry](https://github.com/jim-parry)) -- More RouteCollection tests for overwriting. Closes \#1692 [\#1913](https://github.com/codeigniter4/CodeIgniter4/pull/1913) ([jim-parry](https://github.com/jim-parry)) -- Additional RouteCollectionTests [\#1912](https://github.com/codeigniter4/CodeIgniter4/pull/1912) ([jim-parry](https://github.com/jim-parry)) -- JSON Cast exception test cases [\#1911](https://github.com/codeigniter4/CodeIgniter4/pull/1911) ([atishamte](https://github.com/atishamte)) -- Added print method to CLI library so you can print multiple times on same line [\#1910](https://github.com/codeigniter4/CodeIgniter4/pull/1910) ([lonnieezell](https://github.com/lonnieezell)) -- Add filter parameters to User Guide [\#1908](https://github.com/codeigniter4/CodeIgniter4/pull/1908) ([MGatner](https://github.com/MGatner)) -- SubQuery related test cases w.r.t \#1775 [\#1906](https://github.com/codeigniter4/CodeIgniter4/pull/1906) ([atishamte](https://github.com/atishamte)) -- BaseBuilder Corrections [\#1902](https://github.com/codeigniter4/CodeIgniter4/pull/1902) ([atishamte](https://github.com/atishamte)) -- Update .htaccess for better security and caching [\#1900](https://github.com/codeigniter4/CodeIgniter4/pull/1900) ([atishamte](https://github.com/atishamte)) -- Database Forge correction [\#1899](https://github.com/codeigniter4/CodeIgniter4/pull/1899) ([atishamte](https://github.com/atishamte)) -- Toolbar fix w.r.t \#1779 [\#1897](https://github.com/codeigniter4/CodeIgniter4/pull/1897) ([atishamte](https://github.com/atishamte)) -- Mysql connection issue with MYSQLI\_CLIENT\_SSL\_DONT\_VERIFY\_SERVER\_CERT \#1219 [\#1896](https://github.com/codeigniter4/CodeIgniter4/pull/1896) ([atishamte](https://github.com/atishamte)) -- Unmatched Cache Library `get\(\)` return null [\#1895](https://github.com/codeigniter4/CodeIgniter4/pull/1895) ([MGatner](https://github.com/MGatner)) -- New method Find Column w.r.t. \#1619 [\#1861](https://github.com/codeigniter4/CodeIgniter4/pull/1861) ([atishamte](https://github.com/atishamte)) - -## [v4.0.0-beta.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-beta.1) (2019-03-01) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0.0-alpha.5...v4.0.0-beta.1) - -**Fixed bugs:** - -- Sequential Migrations don't run to the correct version [\#1666](https://github.com/codeigniter4/CodeIgniter4/issues/1666) - -**Closed issues:** - -- Back references not working in route [\#1761](https://github.com/codeigniter4/CodeIgniter4/issues/1761) -- setDefaultController not working as expected [\#1758](https://github.com/codeigniter4/CodeIgniter4/issues/1758) -- Spark doesn't work when using devstarter [\#1748](https://github.com/codeigniter4/CodeIgniter4/issues/1748) -- required\_with and required\_without validation rules [\#1735](https://github.com/codeigniter4/CodeIgniter4/issues/1735) -- validation rule password never match [\#1728](https://github.com/codeigniter4/CodeIgniter4/issues/1728) -- Entities errors [\#1727](https://github.com/codeigniter4/CodeIgniter4/issues/1727) -- Loading namespace helpers doesn't work as expected CI4 alpha5 [\#1726](https://github.com/codeigniter4/CodeIgniter4/issues/1726) -- spark migrate:latest ErrorException alpha5 [\#1724](https://github.com/codeigniter4/CodeIgniter4/issues/1724) -- redirect\(\)-\>to lost $baseURL config [\#1721](https://github.com/codeigniter4/CodeIgniter4/issues/1721) -- Bug Report: Seeding [\#1720](https://github.com/codeigniter4/CodeIgniter4/issues/1720) -- Spark missing arguments [\#1718](https://github.com/codeigniter4/CodeIgniter4/issues/1718) -- Model required validation rule not working [\#1717](https://github.com/codeigniter4/CodeIgniter4/issues/1717) -- ZendEscaper - duplicate? [\#1716](https://github.com/codeigniter4/CodeIgniter4/issues/1716) -- Why we required form pointed to correct url? [\#1713](https://github.com/codeigniter4/CodeIgniter4/issues/1713) -- Why there is only 1 function in ArrayHelper? Can we introduce more? [\#1711](https://github.com/codeigniter4/CodeIgniter4/issues/1711) -- CodeIgniter\Model::cleanValidationRules\(\) must be of the type array, string given [\#1707](https://github.com/codeigniter4/CodeIgniter4/issues/1707) -- alpha 4-\>5 query param binding [\#1705](https://github.com/codeigniter4/CodeIgniter4/issues/1705) -- failValidationError\($description\) [\#1702](https://github.com/codeigniter4/CodeIgniter4/issues/1702) -- Bug : changing viewsDirectory misses errors folder when exception occures [\#1701](https://github.com/codeigniter4/CodeIgniter4/issues/1701) -- Cannot define complex routes , i.e. date [\#1700](https://github.com/codeigniter4/CodeIgniter4/issues/1700) -- lang bug or not? \(empty translations\) [\#1698](https://github.com/codeigniter4/CodeIgniter4/issues/1698) -- Issue Extend Core Class [\#1653](https://github.com/codeigniter4/CodeIgniter4/issues/1653) -- Turn OFF getMyProperty\(\) method during DB save. [\#1646](https://github.com/codeigniter4/CodeIgniter4/issues/1646) -- Model class crashes when handling complex validation rules [\#1574](https://github.com/codeigniter4/CodeIgniter4/issues/1574) -- Database ForgeTest hiccup [\#1478](https://github.com/codeigniter4/CodeIgniter4/issues/1478) -- SQLLite3 Forge needs better column handling [\#1255](https://github.com/codeigniter4/CodeIgniter4/issues/1255) -- TODO BaseConnection needs better error handling [\#1254](https://github.com/codeigniter4/CodeIgniter4/issues/1254) -- Model Alternative Keys [\#428](https://github.com/codeigniter4/CodeIgniter4/issues/428) - -**Merged pull requests:** - -- Housekeeping for beta.1 [\#1774](https://github.com/codeigniter4/CodeIgniter4/pull/1774) ([jim-parry](https://github.com/jim-parry)) -- Helper changes [\#1768](https://github.com/codeigniter4/CodeIgniter4/pull/1768) ([atishamte](https://github.com/atishamte)) -- Fix routing when no default route has been specified. Fixes \#1758 [\#1764](https://github.com/codeigniter4/CodeIgniter4/pull/1764) ([lonnieezell](https://github.com/lonnieezell)) -- Ensure validation works in Model with errors as part of rules. Fixes \#1574 [\#1763](https://github.com/codeigniter4/CodeIgniter4/pull/1763) ([lonnieezell](https://github.com/lonnieezell)) -- Correct the unneeded double-quote \(typo\) [\#1757](https://github.com/codeigniter4/CodeIgniter4/pull/1757) ([smhnaji](https://github.com/smhnaji)) -- lowercase 'vfsStream' in composer files [\#1755](https://github.com/codeigniter4/CodeIgniter4/pull/1755) ([MGatner](https://github.com/MGatner)) -- Fixed typo preventing link format [\#1752](https://github.com/codeigniter4/CodeIgniter4/pull/1752) ([MGatner](https://github.com/MGatner)) -- Guide: Moving misplaced text under correct heading [\#1751](https://github.com/codeigniter4/CodeIgniter4/pull/1751) ([MGatner](https://github.com/MGatner)) -- Remove reference to Encryption Key in User Guide [\#1750](https://github.com/codeigniter4/CodeIgniter4/pull/1750) ([MGatner](https://github.com/MGatner)) -- Adding environment to .env [\#1749](https://github.com/codeigniter4/CodeIgniter4/pull/1749) ([MGatner](https://github.com/MGatner)) -- Updated composite key tests for SQLite3 support. Fixes \#1478 [\#1745](https://github.com/codeigniter4/CodeIgniter4/pull/1745) ([lonnieezell](https://github.com/lonnieezell)) -- Update entity docs for current framework state. Fixes \#1727 [\#1744](https://github.com/codeigniter4/CodeIgniter4/pull/1744) ([lonnieezell](https://github.com/lonnieezell)) -- Manually sort migrations found instead of relying on the OS. Fixes \#1666 [\#1743](https://github.com/codeigniter4/CodeIgniter4/pull/1743) ([lonnieezell](https://github.com/lonnieezell)) -- Fix required\_without rule bug. [\#1742](https://github.com/codeigniter4/CodeIgniter4/pull/1742) ([bangbangda](https://github.com/bangbangda)) -- Helpers with a specific namespace can be loaded now. Fixes \#1726 [\#1741](https://github.com/codeigniter4/CodeIgniter4/pull/1741) ([lonnieezell](https://github.com/lonnieezell)) -- Refactor test support for app starter [\#1740](https://github.com/codeigniter4/CodeIgniter4/pull/1740) ([jim-parry](https://github.com/jim-parry)) -- Fix typo [\#1739](https://github.com/codeigniter4/CodeIgniter4/pull/1739) ([vibbow](https://github.com/vibbow)) -- Fix required\_with rule bug. Fixes \#1728 [\#1738](https://github.com/codeigniter4/CodeIgniter4/pull/1738) ([bangbangda](https://github.com/bangbangda)) -- Added support for dropTable and modifyTable with SQLite driver [\#1737](https://github.com/codeigniter4/CodeIgniter4/pull/1737) ([lonnieezell](https://github.com/lonnieezell)) -- Accommodate long travis execution times [\#1736](https://github.com/codeigniter4/CodeIgniter4/pull/1736) ([jim-parry](https://github.com/jim-parry)) -- Fix increment and decrement errors with Postgres [\#1733](https://github.com/codeigniter4/CodeIgniter4/pull/1733) ([lonnieezell](https://github.com/lonnieezell)) -- Don't check from CLI in Routes. Fixes \#1724 [\#1732](https://github.com/codeigniter4/CodeIgniter4/pull/1732) ([lonnieezell](https://github.com/lonnieezell)) -- Revert "Ensure isn't checked during RouteCollection calls when called from CLI" [\#1731](https://github.com/codeigniter4/CodeIgniter4/pull/1731) ([lonnieezell](https://github.com/lonnieezell)) -- Ensure isn't checked during RouteCollection calls when called from CLI [\#1730](https://github.com/codeigniter4/CodeIgniter4/pull/1730) ([lonnieezell](https://github.com/lonnieezell)) -- New View Layout functionality for simple template functionality. [\#1729](https://github.com/codeigniter4/CodeIgniter4/pull/1729) ([lonnieezell](https://github.com/lonnieezell)) -- Update Request.php [\#1725](https://github.com/codeigniter4/CodeIgniter4/pull/1725) ([HieuPT7](https://github.com/HieuPT7)) -- Log an error if redis authentication is failed. [\#1723](https://github.com/codeigniter4/CodeIgniter4/pull/1723) ([vibbow](https://github.com/vibbow)) -- Seeder adds default namespace to seeds [\#1722](https://github.com/codeigniter4/CodeIgniter4/pull/1722) ([lonnieezell](https://github.com/lonnieezell)) -- Update Cache RedisHandler to support select database. [\#1719](https://github.com/codeigniter4/CodeIgniter4/pull/1719) ([vibbow](https://github.com/vibbow)) -- minors \(Model.php\) [\#1712](https://github.com/codeigniter4/CodeIgniter4/pull/1712) ([nowackipawel](https://github.com/nowackipawel)) -- Fix/rc [\#1709](https://github.com/codeigniter4/CodeIgniter4/pull/1709) ([jim-parry](https://github.com/jim-parry)) -- UploadFile - language support [\#1708](https://github.com/codeigniter4/CodeIgniter4/pull/1708) ([nowackipawel](https://github.com/nowackipawel)) -- Fix viewsDirectory bug Fixes \#1701 [\#1704](https://github.com/codeigniter4/CodeIgniter4/pull/1704) ([bangbangda](https://github.com/bangbangda)) -- Fix install link in user guide [\#1699](https://github.com/codeigniter4/CodeIgniter4/pull/1699) ([jim-parry](https://github.com/jim-parry)) -- Fix page structure etc [\#1696](https://github.com/codeigniter4/CodeIgniter4/pull/1696) ([jim-parry](https://github.com/jim-parry)) -- Tidy up code blocks in the user guide [\#1695](https://github.com/codeigniter4/CodeIgniter4/pull/1695) ([jim-parry](https://github.com/jim-parry)) - -## [v4.0.0.0-alpha.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0.0-alpha.5) (2019-01-30) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.5...v4.0.0.0-alpha.5) - -**Implemented enhancements:** - -- Cache system should support site-wide prefixes [\#1659](https://github.com/codeigniter4/CodeIgniter4/issues/1659) - -**Fixed bugs:** - -- Problem with Database BaseBuilder binds [\#1226](https://github.com/codeigniter4/CodeIgniter4/issues/1226) - -**Closed issues:** - -- DB query not working with disabled escaping [\#1687](https://github.com/codeigniter4/CodeIgniter4/issues/1687) -- migrate:refresh ArgumentCountError [\#1682](https://github.com/codeigniter4/CodeIgniter4/issues/1682) -- Do I need to clear old session files manually? [\#1681](https://github.com/codeigniter4/CodeIgniter4/issues/1681) -- Pagination / pager-\>links\(\) double encodes links [\#1680](https://github.com/codeigniter4/CodeIgniter4/issues/1680) -- Document Method Spoofing for forms. [\#1668](https://github.com/codeigniter4/CodeIgniter4/issues/1668) -- insertBatch with escape=false still escapes values [\#1667](https://github.com/codeigniter4/CodeIgniter4/issues/1667) -- Filters should not be case sensitive [\#1664](https://github.com/codeigniter4/CodeIgniter4/issues/1664) -- RouteCollection::discoverRoutes incomplete [\#1662](https://github.com/codeigniter4/CodeIgniter4/issues/1662) -- Feature request make is\_unique - more than one field. [\#1655](https://github.com/codeigniter4/CodeIgniter4/issues/1655) -- Toolbar logs tab - not logging [\#1651](https://github.com/codeigniter4/CodeIgniter4/issues/1651) -- DebugToolbar - too much recursion [\#1650](https://github.com/codeigniter4/CodeIgniter4/issues/1650) -- \[documentation\] Typing mistake in transaction example [\#1639](https://github.com/codeigniter4/CodeIgniter4/issues/1639) -- Transaction documentation error and/or bug [\#1638](https://github.com/codeigniter4/CodeIgniter4/issues/1638) -- Bug : pagination broken when using 1 as perPage [\#1628](https://github.com/codeigniter4/CodeIgniter4/issues/1628) -- View data not being passed between each call? [\#1621](https://github.com/codeigniter4/CodeIgniter4/issues/1621) -- Composer Installation downloads app and application folder. [\#1620](https://github.com/codeigniter4/CodeIgniter4/issues/1620) -- countAllResults\(\) should respect soft deletes [\#1617](https://github.com/codeigniter4/CodeIgniter4/issues/1617) -- redirect function don't redirect to base\_url [\#1611](https://github.com/codeigniter4/CodeIgniter4/issues/1611) -- Memory issue - Toolbar collects every query [\#1607](https://github.com/codeigniter4/CodeIgniter4/issues/1607) -- Pls remove string type in parameter $group at Database::forge [\#1605](https://github.com/codeigniter4/CodeIgniter4/issues/1605) -- SQL JOIN : bad aliasing on join with prefixed db tables [\#1599](https://github.com/codeigniter4/CodeIgniter4/issues/1599) -- Model's update method fails when validation rules exist [\#1584](https://github.com/codeigniter4/CodeIgniter4/issues/1584) -- maybe need to modify session garbage collector section. \(FileHandler\) [\#1565](https://github.com/codeigniter4/CodeIgniter4/issues/1565) -- Maybe routes has problem. \[setTranslateURIDashes\] [\#1564](https://github.com/codeigniter4/CodeIgniter4/issues/1564) -- ErrorException Trying to get property 'affected\_rows' of non-object [\#1559](https://github.com/codeigniter4/CodeIgniter4/issues/1559) -- UG - typo in Managing Apps [\#1558](https://github.com/codeigniter4/CodeIgniter4/issues/1558) -- Database migration uses wrong database when initialising migration classes [\#1532](https://github.com/codeigniter4/CodeIgniter4/issues/1532) -- Database migration table not correctly created when a non-default database connection is used [\#1531](https://github.com/codeigniter4/CodeIgniter4/issues/1531) -- MYSQL : orderBy\(\) considers CASE statement as a column [\#1528](https://github.com/codeigniter4/CodeIgniter4/issues/1528) -- getCompiledSelect\(\) return query without binds [\#1526](https://github.com/codeigniter4/CodeIgniter4/issues/1526) -- Commit pre-hook misbehaving [\#1404](https://github.com/codeigniter4/CodeIgniter4/issues/1404) -- Lack of proper instruction in documentation for changing Application and System folder name [\#1366](https://github.com/codeigniter4/CodeIgniter4/issues/1366) -- SubQueries \(tables from outside of the current model\) [\#1175](https://github.com/codeigniter4/CodeIgniter4/issues/1175) -- FileHandler Garbage Collector fails to delete expired session files. [\#942](https://github.com/codeigniter4/CodeIgniter4/issues/942) - -**Merged pull requests:** - -- Update changelog for alpha.5 [\#1694](https://github.com/codeigniter4/CodeIgniter4/pull/1694) ([jim-parry](https://github.com/jim-parry)) -- Docs/tutorial [\#1693](https://github.com/codeigniter4/CodeIgniter4/pull/1693) ([jim-parry](https://github.com/jim-parry)) -- Update the running docs [\#1691](https://github.com/codeigniter4/CodeIgniter4/pull/1691) ([jim-parry](https://github.com/jim-parry)) -- Rework install docs [\#1690](https://github.com/codeigniter4/CodeIgniter4/pull/1690) ([jim-parry](https://github.com/jim-parry)) -- Model Validation Fix [\#1689](https://github.com/codeigniter4/CodeIgniter4/pull/1689) ([lonnieezell](https://github.com/lonnieezell)) -- Add copyright blocks to filters [\#1688](https://github.com/codeigniter4/CodeIgniter4/pull/1688) ([jim-parry](https://github.com/jim-parry)) -- Refactor/filters [\#1686](https://github.com/codeigniter4/CodeIgniter4/pull/1686) ([jim-parry](https://github.com/jim-parry)) -- Fix admin - app starter creation [\#1685](https://github.com/codeigniter4/CodeIgniter4/pull/1685) ([jim-parry](https://github.com/jim-parry)) -- Updating session id cleanup for filehandler. Fixes \#1681 Fixes \#1565 [\#1684](https://github.com/codeigniter4/CodeIgniter4/pull/1684) ([lonnieezell](https://github.com/lonnieezell)) -- Fix migrate:refresh bug Fixes \#1682 [\#1683](https://github.com/codeigniter4/CodeIgniter4/pull/1683) ([bangbangda](https://github.com/bangbangda)) -- save\_path - for memcached \(Session.php\) + sess\_prefix \(..Handler.php\) [\#1679](https://github.com/codeigniter4/CodeIgniter4/pull/1679) ([nowackipawel](https://github.com/nowackipawel)) -- fix route not replacing forward slashes [\#1678](https://github.com/codeigniter4/CodeIgniter4/pull/1678) ([puschie286](https://github.com/puschie286)) -- Implement Don't Escape feature for db engine [\#1677](https://github.com/codeigniter4/CodeIgniter4/pull/1677) ([lonnieezell](https://github.com/lonnieezell)) -- Add missing test group directives [\#1675](https://github.com/codeigniter4/CodeIgniter4/pull/1675) ([jim-parry](https://github.com/jim-parry)) -- Changelog alpha.5 so far [\#1674](https://github.com/codeigniter4/CodeIgniter4/pull/1674) ([jim-parry](https://github.com/jim-parry)) -- Updated download & installation docs [\#1673](https://github.com/codeigniter4/CodeIgniter4/pull/1673) ([jim-parry](https://github.com/jim-parry)) -- Update Autoloader.php [\#1672](https://github.com/codeigniter4/CodeIgniter4/pull/1672) ([zl59503020](https://github.com/zl59503020)) -- Update docs [\#1671](https://github.com/codeigniter4/CodeIgniter4/pull/1671) ([jim-parry](https://github.com/jim-parry)) -- Update PHP dependency to 7.2 [\#1670](https://github.com/codeigniter4/CodeIgniter4/pull/1670) ([jim-parry](https://github.com/jim-parry)) -- Enhance Parser & Plugin testing [\#1669](https://github.com/codeigniter4/CodeIgniter4/pull/1669) ([jim-parry](https://github.com/jim-parry)) -- Composer PSR4 namespaces are now part of the modules auto-discovery [\#1665](https://github.com/codeigniter4/CodeIgniter4/pull/1665) ([lonnieezell](https://github.com/lonnieezell)) -- Fix bind issue that occurred when using whereIn or orWhereIn with a c… [\#1663](https://github.com/codeigniter4/CodeIgniter4/pull/1663) ([lonnieezell](https://github.com/lonnieezell)) -- Migrations Tests and database tweaks [\#1660](https://github.com/codeigniter4/CodeIgniter4/pull/1660) ([lonnieezell](https://github.com/lonnieezell)) -- DBGroup in \_\_get\(\), allows to validate "database" data outside the model. [\#1656](https://github.com/codeigniter4/CodeIgniter4/pull/1656) ([nowackipawel](https://github.com/nowackipawel)) -- Toolbar - Return Logger::$logCache items when collecting [\#1654](https://github.com/codeigniter4/CodeIgniter4/pull/1654) ([natanfelles](https://github.com/natanfelles)) -- remove php 7.3 from "allow\_failures" in travis config [\#1649](https://github.com/codeigniter4/CodeIgniter4/pull/1649) ([samsonasik](https://github.com/samsonasik)) -- Update "managing apps" docs [\#1648](https://github.com/codeigniter4/CodeIgniter4/pull/1648) ([jim-parry](https://github.com/jim-parry)) -- Fix transaction enabling confusing \(docu\) [\#1645](https://github.com/codeigniter4/CodeIgniter4/pull/1645) ([puschie286](https://github.com/puschie286)) -- Remove Email module [\#1643](https://github.com/codeigniter4/CodeIgniter4/pull/1643) ([jim-parry](https://github.com/jim-parry)) -- CSP nonce attribute value in "" [\#1642](https://github.com/codeigniter4/CodeIgniter4/pull/1642) ([nowackipawel](https://github.com/nowackipawel)) -- More unit testing tweaks [\#1641](https://github.com/codeigniter4/CodeIgniter4/pull/1641) ([jim-parry](https://github.com/jim-parry)) -- Update getCompiledX methods in BaseBuilder to return fully compiled q… [\#1640](https://github.com/codeigniter4/CodeIgniter4/pull/1640) ([lonnieezell](https://github.com/lonnieezell)) -- Fix starter README [\#1637](https://github.com/codeigniter4/CodeIgniter4/pull/1637) ([kenjis](https://github.com/kenjis)) -- Refactor Files module [\#1636](https://github.com/codeigniter4/CodeIgniter4/pull/1636) ([jim-parry](https://github.com/jim-parry)) -- Unit testing enhancements [\#1635](https://github.com/codeigniter4/CodeIgniter4/pull/1635) ([jim-parry](https://github.com/jim-parry)) -- Uses csrf\_field and form\_hidden instead of inline-html in form\_open [\#1633](https://github.com/codeigniter4/CodeIgniter4/pull/1633) ([nowackipawel](https://github.com/nowackipawel)) -- DBGroup should be passed to -\>run instead of -\>setRules [\#1632](https://github.com/codeigniter4/CodeIgniter4/pull/1632) ([nowackipawel](https://github.com/nowackipawel)) -- move use statement after License doc at UploadedFile class [\#1631](https://github.com/codeigniter4/CodeIgniter4/pull/1631) ([samsonasik](https://github.com/samsonasik)) -- Update copyright to 2019 [\#1630](https://github.com/codeigniter4/CodeIgniter4/pull/1630) ([jim-parry](https://github.com/jim-parry)) -- "application" to "app" directory doc and comments and welcome\_message clean up [\#1629](https://github.com/codeigniter4/CodeIgniter4/pull/1629) ([samsonasik](https://github.com/samsonasik)) -- clean up Paths::$viewDirectory property [\#1626](https://github.com/codeigniter4/CodeIgniter4/pull/1626) ([samsonasik](https://github.com/samsonasik)) -- fix. After matches is not set empty [\#1625](https://github.com/codeigniter4/CodeIgniter4/pull/1625) ([Instrye](https://github.com/Instrye)) -- Property was not cast if was defined as nullable. [\#1623](https://github.com/codeigniter4/CodeIgniter4/pull/1623) ([nowackipawel](https://github.com/nowackipawel)) -- Nullable support for \_\_set. [\#1622](https://github.com/codeigniter4/CodeIgniter4/pull/1622) ([nowackipawel](https://github.com/nowackipawel)) -- Fix View config merge order [\#1616](https://github.com/codeigniter4/CodeIgniter4/pull/1616) ([jim-parry](https://github.com/jim-parry)) -- Typo in documentation [\#1613](https://github.com/codeigniter4/CodeIgniter4/pull/1613) ([tpw1314](https://github.com/tpw1314)) -- WIP img fix\(?\) - html\_helper [\#1538](https://github.com/codeigniter4/CodeIgniter4/pull/1538) ([nowackipawel](https://github.com/nowackipawel)) - -## [v4.0.0-alpha.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.5) (2018-12-15) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.4...v4.0.0-alpha.5) - -## [v4.0.0-alpha.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.4) (2018-12-15) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.3...v4.0.0-alpha.4) - -**Implemented enhancements:** - -- WIP Vagrant and/or Docker support [\#1452](https://github.com/codeigniter4/CodeIgniter4/issues/1452) - -**Closed issues:** - -- Custom routing rule not match the User Guide [\#1609](https://github.com/codeigniter4/CodeIgniter4/issues/1609) -- Memory leak - binds not being cleared after find\(\) [\#1604](https://github.com/codeigniter4/CodeIgniter4/issues/1604) -- Soft deletes need parentheses around proceeding query? [\#1592](https://github.com/codeigniter4/CodeIgniter4/issues/1592) -- Cannot use Model first\(\) and update\(\) in table without primary key [\#1583](https://github.com/codeigniter4/CodeIgniter4/issues/1583) -- tried to allocate [\#1578](https://github.com/codeigniter4/CodeIgniter4/issues/1578) -- Database Migrations inconsistent info about sequential type [\#1577](https://github.com/codeigniter4/CodeIgniter4/issues/1577) -- Wrong or not precise documentation of magic \_\_get and \_\_set methods in section Handling Business Logic [\#1568](https://github.com/codeigniter4/CodeIgniter4/issues/1568) -- Entity's fill method is not supporting key mapping [\#1567](https://github.com/codeigniter4/CodeIgniter4/issues/1567) -- Ability to add namespace to FileLocator class [\#1552](https://github.com/codeigniter4/CodeIgniter4/issues/1552) -- Should log file contain plain text database username passwords etc? [\#1542](https://github.com/codeigniter4/CodeIgniter4/issues/1542) -- Issues with redirects - had to use exit to make it work and blank page [\#1501](https://github.com/codeigniter4/CodeIgniter4/issues/1501) -- Use of undefined constant BASEPATH [\#1439](https://github.com/codeigniter4/CodeIgniter4/issues/1439) -- MYSQL : BETWEEN operator loses condition value on JOIN in \(:\) used [\#1403](https://github.com/codeigniter4/CodeIgniter4/issues/1403) -- The problem in catching exceptions [\#1274](https://github.com/codeigniter4/CodeIgniter4/issues/1274) -- TODO Language needs improved locating [\#1262](https://github.com/codeigniter4/CodeIgniter4/issues/1262) -- Email attachment [\#1008](https://github.com/codeigniter4/CodeIgniter4/issues/1008) - -**Merged pull requests:** - -- Alpha.4 release prep [\#1612](https://github.com/codeigniter4/CodeIgniter4/pull/1612) ([jim-parry](https://github.com/jim-parry)) -- Test, fix & enhance Language [\#1610](https://github.com/codeigniter4/CodeIgniter4/pull/1610) ([jim-parry](https://github.com/jim-parry)) -- Note about environment configuration in UG [\#1608](https://github.com/codeigniter4/CodeIgniter4/pull/1608) ([jim-parry](https://github.com/jim-parry)) -- release framework script clean up [\#1606](https://github.com/codeigniter4/CodeIgniter4/pull/1606) ([samsonasik](https://github.com/samsonasik)) -- Flesh out I18n testing [\#1603](https://github.com/codeigniter4/CodeIgniter4/pull/1603) ([jim-parry](https://github.com/jim-parry)) -- Model's first and update didn't work primary key-less tables [\#1602](https://github.com/codeigniter4/CodeIgniter4/pull/1602) ([lonnieezell](https://github.com/lonnieezell)) -- clean up \Config\Services in Common.php [\#1601](https://github.com/codeigniter4/CodeIgniter4/pull/1601) ([samsonasik](https://github.com/samsonasik)) -- admin/starter/composer.json clean up [\#1600](https://github.com/codeigniter4/CodeIgniter4/pull/1600) ([samsonasik](https://github.com/samsonasik)) -- use $defaultGroup as default value for database session DBGroup [\#1598](https://github.com/codeigniter4/CodeIgniter4/pull/1598) ([puschie286](https://github.com/puschie286)) -- Retry handle fatal error via pre\_system [\#1595](https://github.com/codeigniter4/CodeIgniter4/pull/1595) ([samsonasik](https://github.com/samsonasik)) -- Fix Toolbar invalid css [\#1594](https://github.com/codeigniter4/CodeIgniter4/pull/1594) ([puschie286](https://github.com/puschie286)) -- Flesh out the Test package testing [\#1593](https://github.com/codeigniter4/CodeIgniter4/pull/1593) ([jim-parry](https://github.com/jim-parry)) -- Fix Toolbar file loading throw exception [\#1589](https://github.com/codeigniter4/CodeIgniter4/pull/1589) ([puschie286](https://github.com/puschie286)) -- Fix site\_url generate invalid url [\#1588](https://github.com/codeigniter4/CodeIgniter4/pull/1588) ([puschie286](https://github.com/puschie286)) -- Add Language fallback [\#1587](https://github.com/codeigniter4/CodeIgniter4/pull/1587) ([natanfelles](https://github.com/natanfelles)) -- Fix model namespace in tutorial [\#1586](https://github.com/codeigniter4/CodeIgniter4/pull/1586) ([jim-parry](https://github.com/jim-parry)) -- Type hint MigrationRunner methods [\#1585](https://github.com/codeigniter4/CodeIgniter4/pull/1585) ([natanfelles](https://github.com/natanfelles)) -- Fix changelog index & common functions UG indent [\#1582](https://github.com/codeigniter4/CodeIgniter4/pull/1582) ([jim-parry](https://github.com/jim-parry)) -- ContentSecurityPolicy testing & enhancement [\#1581](https://github.com/codeigniter4/CodeIgniter4/pull/1581) ([jim-parry](https://github.com/jim-parry)) -- Use Absolute Paths [\#1579](https://github.com/codeigniter4/CodeIgniter4/pull/1579) ([natanfelles](https://github.com/natanfelles)) -- Testing13/http [\#1576](https://github.com/codeigniter4/CodeIgniter4/pull/1576) ([jim-parry](https://github.com/jim-parry)) -- Adds ?integer, ?double, ?string, etc. cast types :\) [\#1575](https://github.com/codeigniter4/CodeIgniter4/pull/1575) ([nowackipawel](https://github.com/nowackipawel)) -- Lessons learned [\#1573](https://github.com/codeigniter4/CodeIgniter4/pull/1573) ([jim-parry](https://github.com/jim-parry)) -- Toolbar updates [\#1571](https://github.com/codeigniter4/CodeIgniter4/pull/1571) ([natanfelles](https://github.com/natanfelles)) -- Test esc\(\) with different encodings and ignore app-only helpers [\#1569](https://github.com/codeigniter4/CodeIgniter4/pull/1569) ([natanfelles](https://github.com/natanfelles)) -- id attribute support added for csrf\_field [\#1563](https://github.com/codeigniter4/CodeIgniter4/pull/1563) ([nowackipawel](https://github.com/nowackipawel)) -- Integrates Autoloader and FileLocator [\#1562](https://github.com/codeigniter4/CodeIgniter4/pull/1562) ([natanfelles](https://github.com/natanfelles)) -- Update Connection.php [\#1561](https://github.com/codeigniter4/CodeIgniter4/pull/1561) ([nowackipawel](https://github.com/nowackipawel)) -- remove \ prefix on use statements [\#1557](https://github.com/codeigniter4/CodeIgniter4/pull/1557) ([samsonasik](https://github.com/samsonasik)) -- using protected intead of public modifier for setUp\(\) function in tests [\#1556](https://github.com/codeigniter4/CodeIgniter4/pull/1556) ([samsonasik](https://github.com/samsonasik)) -- autoload clean up: remove Psr\Log namespace from composer.json [\#1555](https://github.com/codeigniter4/CodeIgniter4/pull/1555) ([samsonasik](https://github.com/samsonasik)) -- remove manual define "system/" directory prefix at ComposerScripts [\#1551](https://github.com/codeigniter4/CodeIgniter4/pull/1551) ([samsonasik](https://github.com/samsonasik)) -- allows to set empty html attr [\#1548](https://github.com/codeigniter4/CodeIgniter4/pull/1548) ([nowackipawel](https://github.com/nowackipawel)) -- Add Vagrantfile [\#1459](https://github.com/codeigniter4/CodeIgniter4/pull/1459) ([natanfelles](https://github.com/natanfelles)) - -## [v4.0.0-alpha.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.3) (2018-11-30) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.2...v4.0.0-alpha.3) - -**Implemented enhancements:** - -- Events should pass it's arguments by reference [\#1298](https://github.com/codeigniter4/CodeIgniter4/issues/1298) -- Feature request. Small but useful. Entity class. [\#1176](https://github.com/codeigniter4/CodeIgniter4/issues/1176) - -**Fixed bugs:** - -- \Config\Database::connect returns unexpected result for custom config values [\#1533](https://github.com/codeigniter4/CodeIgniter4/issues/1533) -- Old\(\) - Seems to have an issue with retrieving array values [\#1492](https://github.com/codeigniter4/CodeIgniter4/issues/1492) -- Language is not merging with modules [\#1433](https://github.com/codeigniter4/CodeIgniter4/issues/1433) -- RedirectResponse does not set cookies [\#1393](https://github.com/codeigniter4/CodeIgniter4/issues/1393) -- ThrottleTest intermittent failure [\#1382](https://github.com/codeigniter4/CodeIgniter4/issues/1382) -- Support replacer functions on validations [\#1363](https://github.com/codeigniter4/CodeIgniter4/issues/1363) -- Filter on route group only attach / [\#1247](https://github.com/codeigniter4/CodeIgniter4/issues/1247) -- TypeError in MemcachedHandler::\_\_construct\(\) [\#1204](https://github.com/codeigniter4/CodeIgniter4/issues/1204) -- Required\_without validation rule [\#1007](https://github.com/codeigniter4/CodeIgniter4/issues/1007) -- Division by zero - fresh install [\#979](https://github.com/codeigniter4/CodeIgniter4/issues/979) - -**Closed issues:** - -- need change file\_exists to is\_file? [\#1543](https://github.com/codeigniter4/CodeIgniter4/issues/1543) -- Docs : some links are broken due to lack of prefix CodeIgniter4 [\#1537](https://github.com/codeigniter4/CodeIgniter4/issues/1537) -- Spelling mistake /wrong function reference in documentation "Handling Business Logic" [\#1535](https://github.com/codeigniter4/CodeIgniter4/issues/1535) -- Sample code in Entity documentation contains useless statement [\#1534](https://github.com/codeigniter4/CodeIgniter4/issues/1534) -- Model events why is there no beforeFind? [\#1527](https://github.com/codeigniter4/CodeIgniter4/issues/1527) -- \[Documentation\] Bug in code example for "Validating $\_POST data" [\#1520](https://github.com/codeigniter4/CodeIgniter4/issues/1520) -- Before filters with sessions break php spark serve [\#1519](https://github.com/codeigniter4/CodeIgniter4/issues/1519) -- Missing documentation for placeholders in validation errors [\#1503](https://github.com/codeigniter4/CodeIgniter4/issues/1503) -- no $baseURL set will be notice "The baseURL value must be set" [\#1476](https://github.com/codeigniter4/CodeIgniter4/issues/1476) -- $field parameter as string at Forge::addField [\#1474](https://github.com/codeigniter4/CodeIgniter4/issues/1474) -- FeatureTestCaseTest still broken [\#1446](https://github.com/codeigniter4/CodeIgniter4/issues/1446) -- Unit test output not captured [\#1435](https://github.com/codeigniter4/CodeIgniter4/issues/1435) -- Response setJSON body \$key set value [\#1522](https://github.com/codeigniter4/CodeIgniter4/pull/1522) ([samsonasik](https://github.com/samsonasik)) -- .gitignore clean up [\#1521](https://github.com/codeigniter4/CodeIgniter4/pull/1521) ([samsonasik](https://github.com/samsonasik)) -- Small typo: changed setCreatedOn to setCreatedAt [\#1518](https://github.com/codeigniter4/CodeIgniter4/pull/1518) ([obozdag](https://github.com/obozdag)) -- move .htaccess from per-directory in writable/{directory} to writable/ [\#1517](https://github.com/codeigniter4/CodeIgniter4/pull/1517) ([samsonasik](https://github.com/samsonasik)) -- More secure redirection [\#1513](https://github.com/codeigniter4/CodeIgniter4/pull/1513) ([jim-parry](https://github.com/jim-parry)) -- remove unused use statements [\#1509](https://github.com/codeigniter4/CodeIgniter4/pull/1509) ([samsonasik](https://github.com/samsonasik)) -- remove duplicate strtolower\(\) call in URI::setScheme\(\) call [\#1508](https://github.com/codeigniter4/CodeIgniter4/pull/1508) ([samsonasik](https://github.com/samsonasik)) -- Fix multi "empty" string separated by "," marked as valid emails [\#1507](https://github.com/codeigniter4/CodeIgniter4/pull/1507) ([samsonasik](https://github.com/samsonasik)) -- Flesh out HTTP/File unit testing [\#1506](https://github.com/codeigniter4/CodeIgniter4/pull/1506) ([jim-parry](https://github.com/jim-parry)) -- Do not exit until all Response is completed [\#1505](https://github.com/codeigniter4/CodeIgniter4/pull/1505) ([natanfelles](https://github.com/natanfelles)) -- Revert RedirectResponse changes [\#1504](https://github.com/codeigniter4/CodeIgniter4/pull/1504) ([jim-parry](https://github.com/jim-parry)) -- Revert to buggy oldInput [\#1502](https://github.com/codeigniter4/CodeIgniter4/pull/1502) ([jim-parry](https://github.com/jim-parry)) -- Ignoring errors suppressed by @ [\#1500](https://github.com/codeigniter4/CodeIgniter4/pull/1500) ([samsonasik](https://github.com/samsonasik)) -- Fix form\_helper's set\_value writeup [\#1499](https://github.com/codeigniter4/CodeIgniter4/pull/1499) ([jim-parry](https://github.com/jim-parry)) -- Add CURLRequest helper methods [\#1498](https://github.com/codeigniter4/CodeIgniter4/pull/1498) ([natanfelles](https://github.com/natanfelles)) -- Remove unused RedirectException and add some PHPDocs [\#1497](https://github.com/codeigniter4/CodeIgniter4/pull/1497) ([natanfelles](https://github.com/natanfelles)) -- Fix Common::old\(\) [\#1496](https://github.com/codeigniter4/CodeIgniter4/pull/1496) ([jim-parry](https://github.com/jim-parry)) -- Add URI segment test [\#1495](https://github.com/codeigniter4/CodeIgniter4/pull/1495) ([natanfelles](https://github.com/natanfelles)) -- Method naming [\#1494](https://github.com/codeigniter4/CodeIgniter4/pull/1494) ([ghost](https://github.com/ghost)) -- Error logging [\#1491](https://github.com/codeigniter4/CodeIgniter4/pull/1491) ([jim-parry](https://github.com/jim-parry)) -- Changelog\(s\) restructure [\#1490](https://github.com/codeigniter4/CodeIgniter4/pull/1490) ([jim-parry](https://github.com/jim-parry)) -- Add CLI::strlen\(\) [\#1489](https://github.com/codeigniter4/CodeIgniter4/pull/1489) ([natanfelles](https://github.com/natanfelles)) -- Load Language strings from other locations [\#1488](https://github.com/codeigniter4/CodeIgniter4/pull/1488) ([natanfelles](https://github.com/natanfelles)) -- Test RedirectResponse problem report [\#1486](https://github.com/codeigniter4/CodeIgniter4/pull/1486) ([jim-parry](https://github.com/jim-parry)) -- missing slash [\#1484](https://github.com/codeigniter4/CodeIgniter4/pull/1484) ([titounnes](https://github.com/titounnes)) -- Small typo in Session\Handlers\BaseHandler.php [\#1483](https://github.com/codeigniter4/CodeIgniter4/pull/1483) ([obozdag](https://github.com/obozdag)) -- doc fix: query binding fix in Seeds documentation [\#1482](https://github.com/codeigniter4/CodeIgniter4/pull/1482) ([samsonasik](https://github.com/samsonasik)) -- RedisHandler test clean up: remove unneeded 2nd parameter in \_\_construct [\#1481](https://github.com/codeigniter4/CodeIgniter4/pull/1481) ([samsonasik](https://github.com/samsonasik)) -- Fix Language Key-File confusion [\#1480](https://github.com/codeigniter4/CodeIgniter4/pull/1480) ([puschie286](https://github.com/puschie286)) -- Yet another time test to fix [\#1479](https://github.com/codeigniter4/CodeIgniter4/pull/1479) ([jim-parry](https://github.com/jim-parry)) -- Add Response send testing [\#1477](https://github.com/codeigniter4/CodeIgniter4/pull/1477) ([jim-parry](https://github.com/jim-parry)) -- Correct phpdocs for Forge::addField\(\) [\#1475](https://github.com/codeigniter4/CodeIgniter4/pull/1475) ([jim-parry](https://github.com/jim-parry)) -- Fuzzify another time test [\#1473](https://github.com/codeigniter4/CodeIgniter4/pull/1473) ([jim-parry](https://github.com/jim-parry)) -- HTTP\Response cookie testing & missing functionality [\#1472](https://github.com/codeigniter4/CodeIgniter4/pull/1472) ([jim-parry](https://github.com/jim-parry)) -- remove unused local variable $result in XMLFormatter::format\(\) [\#1471](https://github.com/codeigniter4/CodeIgniter4/pull/1471) ([samsonasik](https://github.com/samsonasik)) -- Allow create table with array field constraints [\#1470](https://github.com/codeigniter4/CodeIgniter4/pull/1470) ([natanfelles](https://github.com/natanfelles)) -- use static:: instead of self:: for call protected/public functions as well [\#1469](https://github.com/codeigniter4/CodeIgniter4/pull/1469) ([samsonasik](https://github.com/samsonasik)) -- Fix FeatureTestCaseTest output buffer [\#1468](https://github.com/codeigniter4/CodeIgniter4/pull/1468) ([puschie286](https://github.com/puschie286)) -- Provide time testing within tolerance [\#1467](https://github.com/codeigniter4/CodeIgniter4/pull/1467) ([jim-parry](https://github.com/jim-parry)) -- Fix phpdocs for BaseBuilder [\#1466](https://github.com/codeigniter4/CodeIgniter4/pull/1466) ([jim-parry](https://github.com/jim-parry)) -- use static:: instead of self:: for protected and public properties [\#1465](https://github.com/codeigniter4/CodeIgniter4/pull/1465) ([samsonasik](https://github.com/samsonasik)) -- remove unused use statements [\#1464](https://github.com/codeigniter4/CodeIgniter4/pull/1464) ([samsonasik](https://github.com/samsonasik)) -- Fix the remaining bcit-ci references [\#1463](https://github.com/codeigniter4/CodeIgniter4/pull/1463) ([jim-parry](https://github.com/jim-parry)) -- Typo fix: donload -\> download [\#1461](https://github.com/codeigniter4/CodeIgniter4/pull/1461) ([samsonasik](https://github.com/samsonasik)) -- remove unneeded ternary check at HoneyPot::hasContent\(\) [\#1460](https://github.com/codeigniter4/CodeIgniter4/pull/1460) ([samsonasik](https://github.com/samsonasik)) -- WIP use $paths-\>systemDirectory in public/index.php [\#1457](https://github.com/codeigniter4/CodeIgniter4/pull/1457) ([samsonasik](https://github.com/samsonasik)) -- Beef up HTTP URI & Response testing [\#1456](https://github.com/codeigniter4/CodeIgniter4/pull/1456) ([jim-parry](https://github.com/jim-parry)) -- WIP un-ignore application/Database/Migrations directory from .gitignore [\#1455](https://github.com/codeigniter4/CodeIgniter4/pull/1455) ([samsonasik](https://github.com/samsonasik)) -- add missing break; in loop at Email::getEncoding\(\) [\#1454](https://github.com/codeigniter4/CodeIgniter4/pull/1454) ([samsonasik](https://github.com/samsonasik)) -- BugFix if there extension has only one mime type \(string\) [\#1453](https://github.com/codeigniter4/CodeIgniter4/pull/1453) ([nowackipawel](https://github.com/nowackipawel)) -- remove unneeded $session-\>start\(\); check on RedirectResponse::ensureSession\(\) [\#1451](https://github.com/codeigniter4/CodeIgniter4/pull/1451) ([samsonasik](https://github.com/samsonasik)) -- phpcbf: fix all at once [\#1450](https://github.com/codeigniter4/CodeIgniter4/pull/1450) ([natanfelles](https://github.com/natanfelles)) -- Simplify how to get indexData from mysql/mariadb [\#1449](https://github.com/codeigniter4/CodeIgniter4/pull/1449) ([natanfelles](https://github.com/natanfelles)) -- documentation: add missing application structures: Database, Filters, ThirdParty directory [\#1448](https://github.com/codeigniter4/CodeIgniter4/pull/1448) ([samsonasik](https://github.com/samsonasik)) -- add missing break; on loop cards to get card info at CreditCardRules::valid\_cc\_number\(\) [\#1447](https://github.com/codeigniter4/CodeIgniter4/pull/1447) ([samsonasik](https://github.com/samsonasik)) -- using existing is\_cli\(\) function in HTTP\IncomingRequest::isCLI\(\) [\#1445](https://github.com/codeigniter4/CodeIgniter4/pull/1445) ([samsonasik](https://github.com/samsonasik)) -- Dox for reorganized repo admin \(4of4\) [\#1444](https://github.com/codeigniter4/CodeIgniter4/pull/1444) ([jim-parry](https://github.com/jim-parry)) -- Fixes \#1435 : unit test output not captured [\#1443](https://github.com/codeigniter4/CodeIgniter4/pull/1443) ([samsonasik](https://github.com/samsonasik)) -- remove form view in application/View/ and form helper usage in create new items tutorial [\#1442](https://github.com/codeigniter4/CodeIgniter4/pull/1442) ([samsonasik](https://github.com/samsonasik)) -- Access to model's last inserted ID [\#1440](https://github.com/codeigniter4/CodeIgniter4/pull/1440) ([nowackipawel](https://github.com/nowackipawel)) -- Tailor the last few repo org names \(3of4\) [\#1438](https://github.com/codeigniter4/CodeIgniter4/pull/1438) ([jim-parry](https://github.com/jim-parry)) -- Replace repo org name in MOST php docs \(2 of 4\) [\#1437](https://github.com/codeigniter4/CodeIgniter4/pull/1437) ([jim-parry](https://github.com/jim-parry)) -- Change github organization name in docs \(1of4\) [\#1436](https://github.com/codeigniter4/CodeIgniter4/pull/1436) ([jim-parry](https://github.com/jim-parry)) -- Use mb\_strlen to get length of columns [\#1432](https://github.com/codeigniter4/CodeIgniter4/pull/1432) ([natanfelles](https://github.com/natanfelles)) -- can't call run\(\) method with params from commands migrations. [\#1431](https://github.com/codeigniter4/CodeIgniter4/pull/1431) ([bangbangda](https://github.com/bangbangda)) -- performance improvement in Database\BaseResult to use truthy check instead of count\($var\) when possible [\#1426](https://github.com/codeigniter4/CodeIgniter4/pull/1426) ([samsonasik](https://github.com/samsonasik)) -- Ensure FileHandlerTest uses MockFileHandler [\#1425](https://github.com/codeigniter4/CodeIgniter4/pull/1425) ([jim-parry](https://github.com/jim-parry)) -- Fix FileMovingTest leaving cruft [\#1424](https://github.com/codeigniter4/CodeIgniter4/pull/1424) ([jim-parry](https://github.com/jim-parry)) -- Fix Controller use validate bug Fixes \#1419 [\#1423](https://github.com/codeigniter4/CodeIgniter4/pull/1423) ([bangbangda](https://github.com/bangbangda)) -- normalize composer.json [\#1418](https://github.com/codeigniter4/CodeIgniter4/pull/1418) ([samsonasik](https://github.com/samsonasik)) -- add php 7.3 to travis config [\#1394](https://github.com/codeigniter4/CodeIgniter4/pull/1394) ([samsonasik](https://github.com/samsonasik)) -- Add Header Link Pagination [\#622](https://github.com/codeigniter4/CodeIgniter4/pull/622) ([natanfelles](https://github.com/natanfelles)) - -## [v4.0.0-alpha.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.2) (2018-10-26) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.1...v4.0.0-alpha.2) - -**Implemented enhancements:** - -- Filter in the router [\#1315](https://github.com/codeigniter4/CodeIgniter4/issues/1315) -- Making Views path changeable [\#1296](https://github.com/codeigniter4/CodeIgniter4/issues/1296) - -**Fixed bugs:** - -- Error in user guide for session config [\#1330](https://github.com/codeigniter4/CodeIgniter4/issues/1330) -- Route in the News Tutorial Routes are ERROR all over. [\#1240](https://github.com/codeigniter4/CodeIgniter4/issues/1240) -- Time testing in travis-ci wonky [\#1229](https://github.com/codeigniter4/CodeIgniter4/issues/1229) - -**Closed issues:** - -- CLI CommandRunner is trying to instantiate abstract classes [\#1349](https://github.com/codeigniter4/CodeIgniter4/issues/1349) -- redirect\(\)-\> problem [\#1346](https://github.com/codeigniter4/CodeIgniter4/issues/1346) -- Question new form validation rule [\#1332](https://github.com/codeigniter4/CodeIgniter4/issues/1332) -- Download Response Not working [\#1331](https://github.com/codeigniter4/CodeIgniter4/issues/1331) -- Incorrect Error Message ? [\#1328](https://github.com/codeigniter4/CodeIgniter4/issues/1328) -- Wrong variable reference [\#1324](https://github.com/codeigniter4/CodeIgniter4/issues/1324) -- Model Pagination: Problem with total rows [\#1318](https://github.com/codeigniter4/CodeIgniter4/issues/1318) -- Should display an exception when minimum PHP version not met. [\#1307](https://github.com/codeigniter4/CodeIgniter4/issues/1307) -- News Tutorial Error on form submit [\#1301](https://github.com/codeigniter4/CodeIgniter4/issues/1301) -- Small Typo Correction [\#1299](https://github.com/codeigniter4/CodeIgniter4/issues/1299) -- Making config variable global as CI3 [\#1297](https://github.com/codeigniter4/CodeIgniter4/issues/1297) -- Config files aren't discovered automatically when using the config\(\) function. [\#1293](https://github.com/codeigniter4/CodeIgniter4/issues/1293) -- News Tutorial post riute still not working [\#1292](https://github.com/codeigniter4/CodeIgniter4/issues/1292) -- Form Validation [\#1290](https://github.com/codeigniter4/CodeIgniter4/issues/1290) -- News Tutorial Routes [\#1288](https://github.com/codeigniter4/CodeIgniter4/issues/1288) -- I want to separate responsibility of Cast from Entity. [\#1287](https://github.com/codeigniter4/CodeIgniter4/issues/1287) -- error 404 in routing with controller in subdirectories [\#1276](https://github.com/codeigniter4/CodeIgniter4/issues/1276) -- TODO Extending helpers [\#1264](https://github.com/codeigniter4/CodeIgniter4/issues/1264) -- TODO MockResponse needs cookies solution [\#1263](https://github.com/codeigniter4/CodeIgniter4/issues/1263) -- TODO url\_helper needs fixing [\#1260](https://github.com/codeigniter4/CodeIgniter4/issues/1260) -- TODO FileLocator better path checking [\#1252](https://github.com/codeigniter4/CodeIgniter4/issues/1252) -- TODO FileLocator filename sanitizing [\#1251](https://github.com/codeigniter4/CodeIgniter4/issues/1251) -- BUG in form\_hidden with associative array [\#1244](https://github.com/codeigniter4/CodeIgniter4/issues/1244) -- Save entity after selected find results in null data [\#1234](https://github.com/codeigniter4/CodeIgniter4/issues/1234) -- System/Database/Database.php::loadForge returns Connection when using custom DBDriver [\#1225](https://github.com/codeigniter4/CodeIgniter4/issues/1225) - -**Merged pull requests:** - -- Add timing assertion to CIUnitTestCase [\#1361](https://github.com/codeigniter4/CodeIgniter4/pull/1361) ([jim-parry](https://github.com/jim-parry)) -- Testing/commands [\#1356](https://github.com/codeigniter4/CodeIgniter4/pull/1356) ([jim-parry](https://github.com/jim-parry)) -- Handle duplicate HTTP verb and generic rules properly [\#1355](https://github.com/codeigniter4/CodeIgniter4/pull/1355) ([jim-parry](https://github.com/jim-parry)) -- Refresh changelog [\#1352](https://github.com/codeigniter4/CodeIgniter4/pull/1352) ([jim-parry](https://github.com/jim-parry)) -- Checks if class is instantiable and is a command [\#1350](https://github.com/codeigniter4/CodeIgniter4/pull/1350) ([natanfelles](https://github.com/natanfelles)) -- Fix sphinx formatting in sessions [\#1348](https://github.com/codeigniter4/CodeIgniter4/pull/1348) ([jim-parry](https://github.com/jim-parry)) -- Fix sphinx formatting in sessions [\#1347](https://github.com/codeigniter4/CodeIgniter4/pull/1347) ([jim-parry](https://github.com/jim-parry)) -- Toolbar Styles [\#1342](https://github.com/codeigniter4/CodeIgniter4/pull/1342) ([lonnieezell](https://github.com/lonnieezell)) -- Make viewpath configurable in Paths.php. Fixes \#1296 [\#1341](https://github.com/codeigniter4/CodeIgniter4/pull/1341) ([lonnieezell](https://github.com/lonnieezell)) -- Update docs for downloads to reflect the need to return it. Fixes \#1331 [\#1340](https://github.com/codeigniter4/CodeIgniter4/pull/1340) ([lonnieezell](https://github.com/lonnieezell)) -- Fix error where Forge class might not be returned. Fixes \#1225 [\#1339](https://github.com/codeigniter4/CodeIgniter4/pull/1339) ([lonnieezell](https://github.com/lonnieezell)) -- Filter in the router Fixes \#1315 [\#1337](https://github.com/codeigniter4/CodeIgniter4/pull/1337) ([bangbangda](https://github.com/bangbangda)) -- Revert alpha.2 [\#1336](https://github.com/codeigniter4/CodeIgniter4/pull/1336) ([jim-parry](https://github.com/jim-parry)) -- Proposed changelog for alpha.2 [\#1334](https://github.com/codeigniter4/CodeIgniter4/pull/1334) ([jim-parry](https://github.com/jim-parry)) -- Error in user guide for session config. Fixes \#1330 [\#1333](https://github.com/codeigniter4/CodeIgniter4/pull/1333) ([bangbangda](https://github.com/bangbangda)) -- Tweaks [\#1329](https://github.com/codeigniter4/CodeIgniter4/pull/1329) ([lonnieezell](https://github.com/lonnieezell)) -- FIX form\_hidden and form\_open - value escaping as is in form\_input. [\#1327](https://github.com/codeigniter4/CodeIgniter4/pull/1327) ([nowackipawel](https://github.com/nowackipawel)) -- Fix doc error : show\_404\(\) doesn't exist any more [\#1323](https://github.com/codeigniter4/CodeIgniter4/pull/1323) ([bvrignaud](https://github.com/bvrignaud)) -- Added missing xml\_helper UG page [\#1321](https://github.com/codeigniter4/CodeIgniter4/pull/1321) ([jim-parry](https://github.com/jim-parry)) -- Testing/entity [\#1319](https://github.com/codeigniter4/CodeIgniter4/pull/1319) ([jim-parry](https://github.com/jim-parry)) -- Refactor TimeTest [\#1316](https://github.com/codeigniter4/CodeIgniter4/pull/1316) ([jim-parry](https://github.com/jim-parry)) -- Fix & expand Honeypot & its tests [\#1314](https://github.com/codeigniter4/CodeIgniter4/pull/1314) ([jim-parry](https://github.com/jim-parry)) -- Clean exception [\#1313](https://github.com/codeigniter4/CodeIgniter4/pull/1313) ([lonnieezell](https://github.com/lonnieezell)) -- Add headerEmited \(or not\) assertions to CIUnitTestCase [\#1312](https://github.com/codeigniter4/CodeIgniter4/pull/1312) ([jim-parry](https://github.com/jim-parry)) -- Entities store an original stack of values to compare against so we d… [\#1311](https://github.com/codeigniter4/CodeIgniter4/pull/1311) ([lonnieezell](https://github.com/lonnieezell)) -- Testing3/http [\#1306](https://github.com/codeigniter4/CodeIgniter4/pull/1306) ([jim-parry](https://github.com/jim-parry)) -- Change chdir\('public'\) to chdir\($public\) [\#1305](https://github.com/codeigniter4/CodeIgniter4/pull/1305) ([titounnes](https://github.com/titounnes)) -- Refactor script name stripping in parseRequestURI\(\) [\#1304](https://github.com/codeigniter4/CodeIgniter4/pull/1304) ([jim-parry](https://github.com/jim-parry)) -- Testing/http [\#1303](https://github.com/codeigniter4/CodeIgniter4/pull/1303) ([jim-parry](https://github.com/jim-parry)) -- Exception:No Formatter defined for mime type '' [\#1302](https://github.com/codeigniter4/CodeIgniter4/pull/1302) ([bangbangda](https://github.com/bangbangda)) -- Allow redirect with Query Vars from the current request. [\#1300](https://github.com/codeigniter4/CodeIgniter4/pull/1300) ([lonnieezell](https://github.com/lonnieezell)) -- Fix grammar in front controller comment. [\#1295](https://github.com/codeigniter4/CodeIgniter4/pull/1295) ([mdwheele](https://github.com/mdwheele)) -- Updated final tutorial page. Fixes \#1292 [\#1294](https://github.com/codeigniter4/CodeIgniter4/pull/1294) ([lonnieezell](https://github.com/lonnieezell)) -- Allows extending of helpers. Fixes \#1264 [\#1291](https://github.com/codeigniter4/CodeIgniter4/pull/1291) ([lonnieezell](https://github.com/lonnieezell)) -- Cookies [\#1286](https://github.com/codeigniter4/CodeIgniter4/pull/1286) ([lonnieezell](https://github.com/lonnieezell)) -- Ensure current HTTP verb routes are matched prior to any \* matched ro… [\#1285](https://github.com/codeigniter4/CodeIgniter4/pull/1285) ([lonnieezell](https://github.com/lonnieezell)) -- Entities [\#1283](https://github.com/codeigniter4/CodeIgniter4/pull/1283) ([lonnieezell](https://github.com/lonnieezell)) -- system/Test/FeatureTestCase::setupRequest\(\), minor fixes phpdoc block… [\#1282](https://github.com/codeigniter4/CodeIgniter4/pull/1282) ([fmertins](https://github.com/fmertins)) -- Tut [\#1281](https://github.com/codeigniter4/CodeIgniter4/pull/1281) ([lonnieezell](https://github.com/lonnieezell)) -- Add contributing reference to user guide [\#1280](https://github.com/codeigniter4/CodeIgniter4/pull/1280) ([jim-parry](https://github.com/jim-parry)) -- Fix/timing [\#1273](https://github.com/codeigniter4/CodeIgniter4/pull/1273) ([jim-parry](https://github.com/jim-parry)) -- Fix undefined variable "heading" in cli 404 [\#1272](https://github.com/codeigniter4/CodeIgniter4/pull/1272) ([samsonasik](https://github.com/samsonasik)) -- remove inexistent "CodeIgniter\Loader" from AutoloadConfig::classmap [\#1271](https://github.com/codeigniter4/CodeIgniter4/pull/1271) ([samsonasik](https://github.com/samsonasik)) -- Release notes & process [\#1269](https://github.com/codeigniter4/CodeIgniter4/pull/1269) ([jim-parry](https://github.com/jim-parry)) -- Fix \#1244 \(form\_hidden declaration\) [\#1245](https://github.com/codeigniter4/CodeIgniter4/pull/1245) ([bvrignaud](https://github.com/bvrignaud)) -- 【Unsolicited PR】I changed the download method to testable. [\#1239](https://github.com/codeigniter4/CodeIgniter4/pull/1239) ([ytetsuro](https://github.com/ytetsuro)) -- Optional parameter for resetSelect\(\) call in Builder's countAll\(\); [\#1217](https://github.com/codeigniter4/CodeIgniter4/pull/1217) ([nowackipawel](https://github.com/nowackipawel)) -- Fix undefined function xml\_convert at Database\BaseUtils [\#1209](https://github.com/codeigniter4/CodeIgniter4/pull/1209) ([samsonasik](https://github.com/samsonasik)) - -## [v4.0.0-alpha.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.1) (2018-09-29) - -[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/0d92381e74730331626b49e6f259d9073727c4f1...v4.0.0-alpha.1) - -**Implemented enhancements:** - -- Parser plugins should be allowed to have non-paired directives [\#547](https://github.com/codeigniter4/CodeIgniter4/issues/547) -- Modify View Parser to support quoted phrases [\#537](https://github.com/codeigniter4/CodeIgniter4/issues/537) -- Move Formatting functionality from the API namespace to it's own namespace [\#451](https://github.com/codeigniter4/CodeIgniter4/issues/451) -- Add method spoofing for forms. [\#432](https://github.com/codeigniter4/CodeIgniter4/issues/432) -- Add overwrite option to UploadedFile-\>move\(\) [\#275](https://github.com/codeigniter4/CodeIgniter4/issues/275) -- Add Download method to Response object. [\#208](https://github.com/codeigniter4/CodeIgniter4/issues/208) -- Cache/Session fallback to predis/predis composer package if phpredis not available [\#192](https://github.com/codeigniter4/CodeIgniter4/issues/192) -- View: saveData [\#181](https://github.com/codeigniter4/CodeIgniter4/issues/181) -- url\_helper implementation [\#157](https://github.com/codeigniter4/CodeIgniter4/issues/157) -- Add true 'prepare' functionality to Queries [\#131](https://github.com/codeigniter4/CodeIgniter4/issues/131) -- Add :hash placeholder for URI parameters [\#130](https://github.com/codeigniter4/CodeIgniter4/issues/130) -- \[URI\] Provide ability for query vars manipulation [\#119](https://github.com/codeigniter4/CodeIgniter4/issues/119) -- \[FilesCollection\] Allow getFiles with dot syntax [\#112](https://github.com/codeigniter4/CodeIgniter4/issues/112) -- Database to fire events [\#105](https://github.com/codeigniter4/CodeIgniter4/issues/105) -- New Toolbar Collector for Events [\#84](https://github.com/codeigniter4/CodeIgniter4/issues/84) -- Debug Toolbar Enhancements [\#83](https://github.com/codeigniter4/CodeIgniter4/issues/83) -- Add indexing functions to Forge [\#65](https://github.com/codeigniter4/CodeIgniter4/issues/65) -- Refactor DB Backup to stream to file instead of holding in memory [\#64](https://github.com/codeigniter4/CodeIgniter4/issues/64) -- Add support for foreign keys to the Forge [\#63](https://github.com/codeigniter4/CodeIgniter4/issues/63) -- Headers must support multiple headers with same name [\#16](https://github.com/codeigniter4/CodeIgniter4/issues/16) -- Add 'secure' option for Routes [\#10](https://github.com/codeigniter4/CodeIgniter4/issues/10) -- \[Test Helper\] assertLogged [\#9](https://github.com/codeigniter4/CodeIgniter4/issues/9) -- Additional Logger Enhancements [\#8](https://github.com/codeigniter4/CodeIgniter4/issues/8) -- Implement Content Secure Policy [\#6](https://github.com/codeigniter4/CodeIgniter4/issues/6) -- Replace our escaper with Zend Escaper [\#5](https://github.com/codeigniter4/CodeIgniter4/issues/5) -- Redirect Security [\#4](https://github.com/codeigniter4/CodeIgniter4/issues/4) -- Reverse Routing [\#3](https://github.com/codeigniter4/CodeIgniter4/issues/3) - -**Fixed bugs:** - -- Testing output buffer not closed [\#1230](https://github.com/codeigniter4/CodeIgniter4/issues/1230) -- XML formater xmltoarray indexed array incorrect [\#577](https://github.com/codeigniter4/CodeIgniter4/issues/577) -- stringify\_attributes method MUST escape the values [\#282](https://github.com/codeigniter4/CodeIgniter4/issues/282) -- base\_url\(\) function doesn't work properly when it used on page with uri segments [\#240](https://github.com/codeigniter4/CodeIgniter4/issues/240) -- mysqli update bug [\#229](https://github.com/codeigniter4/CodeIgniter4/issues/229) -- database update [\#201](https://github.com/codeigniter4/CodeIgniter4/issues/201) -- Paginating Multiple Results - user Guides [\#196](https://github.com/codeigniter4/CodeIgniter4/issues/196) -- 'Filters' Bug [\#188](https://github.com/codeigniter4/CodeIgniter4/issues/188) -- POST and debugbar [\#172](https://github.com/codeigniter4/CodeIgniter4/issues/172) -- url\_helper functions don't correctly apply $baseURL [\#155](https://github.com/codeigniter4/CodeIgniter4/issues/155) -- Routes ending in '/' redirect oddly [\#147](https://github.com/codeigniter4/CodeIgniter4/issues/147) -- Error when trying access URI with Global Function's name [\#136](https://github.com/codeigniter4/CodeIgniter4/issues/136) -- CLI problem with progress complete's message [\#135](https://github.com/codeigniter4/CodeIgniter4/issues/135) - -**Closed issues:** - -- validation error [\#1214](https://github.com/codeigniter4/CodeIgniter4/issues/1214) -- How to use Controller own constructer? [\#1208](https://github.com/codeigniter4/CodeIgniter4/issues/1208) -- autoload psr4 is not right. [\#1205](https://github.com/codeigniter4/CodeIgniter4/issues/1205) -- multiple rules in validate\(\) for File Upload not working [\#1201](https://github.com/codeigniter4/CodeIgniter4/issues/1201) -- can't use the same model to update, delete and insert record [\#1193](https://github.com/codeigniter4/CodeIgniter4/issues/1193) -- $myModel-\>find\(string "value-of-my-primary-key"\) [\#1188](https://github.com/codeigniter4/CodeIgniter4/issues/1188) -- Undefined variable: \_SESSION in command php spark [\#1183](https://github.com/codeigniter4/CodeIgniter4/issues/1183) -- Typographical error [\#1179](https://github.com/codeigniter4/CodeIgniter4/issues/1179) -- CSP + .kint d\(foo\) [\#1174](https://github.com/codeigniter4/CodeIgniter4/issues/1174) -- DebugBar -\> Server Error 500 [\#1170](https://github.com/codeigniter4/CodeIgniter4/issues/1170) -- NULL in select is escaped \(mysqli\_sql\_exception\) [\#1169](https://github.com/codeigniter4/CodeIgniter4/issues/1169) -- Routing for "cli" actions. [\#1166](https://github.com/codeigniter4/CodeIgniter4/issues/1166) -- Create Auto-discovery system [\#1161](https://github.com/codeigniter4/CodeIgniter4/issues/1161) -- Routing wrong default value [\#1139](https://github.com/codeigniter4/CodeIgniter4/issues/1139) -- $session-\>push gets wrong [\#1136](https://github.com/codeigniter4/CodeIgniter4/issues/1136) -- Toolbar Oldest files delete bug [\#1135](https://github.com/codeigniter4/CodeIgniter4/issues/1135) -- redirect helper : redirectResponse is ignored [\#1127](https://github.com/codeigniter4/CodeIgniter4/issues/1127) -- redirect to route ignore baseurl [\#1126](https://github.com/codeigniter4/CodeIgniter4/issues/1126) -- redirect-\>route wrong docu or default values [\#1125](https://github.com/codeigniter4/CodeIgniter4/issues/1125) -- \#1109 breaks route setup/uri parsing [\#1114](https://github.com/codeigniter4/CodeIgniter4/issues/1114) -- \[TESTS\] Session tests for php 7.2 [\#1106](https://github.com/codeigniter4/CodeIgniter4/issues/1106) -- set\_cookie - not working [\#1103](https://github.com/codeigniter4/CodeIgniter4/issues/1103) -- safe\_mailto - not working [\#1102](https://github.com/codeigniter4/CodeIgniter4/issues/1102) -- CLI tool sorting wrong [\#1099](https://github.com/codeigniter4/CodeIgniter4/issues/1099) -- Issues with redirect [\#1098](https://github.com/codeigniter4/CodeIgniter4/issues/1098) -- Can't use validate with regex\_match\[\] [\#1084](https://github.com/codeigniter4/CodeIgniter4/issues/1084) -- problem with redirect\(\) withInput\(\) when validation [\#1081](https://github.com/codeigniter4/CodeIgniter4/issues/1081) -- Redis Handler Fails [\#1079](https://github.com/codeigniter4/CodeIgniter4/issues/1079) -- about cache path [\#1078](https://github.com/codeigniter4/CodeIgniter4/issues/1078) -- validation error [\#1077](https://github.com/codeigniter4/CodeIgniter4/issues/1077) -- \#Request. Features for REST server. [\#1076](https://github.com/codeigniter4/CodeIgniter4/issues/1076) -- Database Migrations [\#1075](https://github.com/codeigniter4/CodeIgniter4/issues/1075) -- Codeigniter/Model - Select Database Table Fields [\#1072](https://github.com/codeigniter4/CodeIgniter4/issues/1072) -- New Config helper [\#1071](https://github.com/codeigniter4/CodeIgniter4/issues/1071) -- HTTP\ResponseTest Language Problem [\#1069](https://github.com/codeigniter4/CodeIgniter4/issues/1069) -- CLI Error [\#1068](https://github.com/codeigniter4/CodeIgniter4/issues/1068) -- Entity \_options dates [\#1061](https://github.com/codeigniter4/CodeIgniter4/issues/1061) -- class Locale not found when using I18n/Time on xampp localhost [\#1059](https://github.com/codeigniter4/CodeIgniter4/issues/1059) -- Cookie not working [\#1057](https://github.com/codeigniter4/CodeIgniter4/issues/1057) -- Where is class 'MessageFormatter' [\#1054](https://github.com/codeigniter4/CodeIgniter4/issues/1054) -- Is CI 4 ready for production, please? [\#1051](https://github.com/codeigniter4/CodeIgniter4/issues/1051) -- Router 404 [\#1050](https://github.com/codeigniter4/CodeIgniter4/issues/1050) -- Toolbar - memory usage [\#1049](https://github.com/codeigniter4/CodeIgniter4/issues/1049) -- Session saving [\#1045](https://github.com/codeigniter4/CodeIgniter4/issues/1045) -- $Email-\>initialize\($config\) not work! [\#1042](https://github.com/codeigniter4/CodeIgniter4/issues/1042) -- Class '\CodeIgniter\Database\postgre\Connection' not found [\#1038](https://github.com/codeigniter4/CodeIgniter4/issues/1038) -- Tutorial controller Pages and file\_exists\(\) case sensitivity [\#1030](https://github.com/codeigniter4/CodeIgniter4/issues/1030) -- options base\_uri not being based from curlrequest client instantiation [\#1029](https://github.com/codeigniter4/CodeIgniter4/issues/1029) -- Missing method in db result [\#1022](https://github.com/codeigniter4/CodeIgniter4/issues/1022) -- Email Config $fromEmail not work [\#1021](https://github.com/codeigniter4/CodeIgniter4/issues/1021) -- Class 'CodeIgniter\PageNotFoundException' not found [\#1016](https://github.com/codeigniter4/CodeIgniter4/issues/1016) -- Redirect glitch [\#1013](https://github.com/codeigniter4/CodeIgniter4/issues/1013) -- CSRF Error [\#1012](https://github.com/codeigniter4/CodeIgniter4/issues/1012) -- Php serv CLI stopped working [\#1006](https://github.com/codeigniter4/CodeIgniter4/issues/1006) -- Unit testing broken in travis-ci [\#1003](https://github.com/codeigniter4/CodeIgniter4/issues/1003) -- empty php\_errors.log file [\#1001](https://github.com/codeigniter4/CodeIgniter4/issues/1001) -- \[Help\] setVar\('body', $view, 'raw'\) [\#1000](https://github.com/codeigniter4/CodeIgniter4/issues/1000) -- It's blank page and set $baseURL [\#999](https://github.com/codeigniter4/CodeIgniter4/issues/999) -- Unable to use another controller'method in one controller? [\#997](https://github.com/codeigniter4/CodeIgniter4/issues/997) -- CodeIgniter\Session\Handlers\FileHandler Class and writable\session Directory not found while using .env [\#994](https://github.com/codeigniter4/CodeIgniter4/issues/994) -- route\_to\(\) function not work if greater than 3 parameters. [\#992](https://github.com/codeigniter4/CodeIgniter4/issues/992) -- Redirect Back [\#991](https://github.com/codeigniter4/CodeIgniter4/issues/991) -- helper method should accept more than one filename.... [\#987](https://github.com/codeigniter4/CodeIgniter4/issues/987) -- New Feature Request - Sub queries using query builder class [\#985](https://github.com/codeigniter4/CodeIgniter4/issues/985) -- MySQL join / missing value for field which was used in join. [\#983](https://github.com/codeigniter4/CodeIgniter4/issues/983) -- Documentation - Session Library - session\(\)-\>start\(\) [\#982](https://github.com/codeigniter4/CodeIgniter4/issues/982) -- Cache with handler file woking incorrect on windown os [\#978](https://github.com/codeigniter4/CodeIgniter4/issues/978) -- Formatter for: "content-type: application/json" [\#977](https://github.com/codeigniter4/CodeIgniter4/issues/977) -- route\_to function working incorrect [\#975](https://github.com/codeigniter4/CodeIgniter4/issues/975) -- Getters and Setters in the Model [\#974](https://github.com/codeigniter4/CodeIgniter4/issues/974) -- About Replacing Core Classes! [\#973](https://github.com/codeigniter4/CodeIgniter4/issues/973) -- CodeIgniter 4 should use PSR-2 [\#972](https://github.com/codeigniter4/CodeIgniter4/issues/972) -- Model Validations and Insert/Update Batchs [\#967](https://github.com/codeigniter4/CodeIgniter4/issues/967) -- function old\(\) not allow input name type array [\#966](https://github.com/codeigniter4/CodeIgniter4/issues/966) -- test [\#963](https://github.com/codeigniter4/CodeIgniter4/issues/963) -- namespace error in centos7 [\#959](https://github.com/codeigniter4/CodeIgniter4/issues/959) -- Session cannot be saved [\#958](https://github.com/codeigniter4/CodeIgniter4/issues/958) -- Model calling Query Builder replace\(\) [\#957](https://github.com/codeigniter4/CodeIgniter4/issues/957) -- gzuncompress\(\): data error [\#956](https://github.com/codeigniter4/CodeIgniter4/issues/956) -- Additional space in a parameter which is a string \(mysql\) [\#955](https://github.com/codeigniter4/CodeIgniter4/issues/955) -- Pager with search queries [\#950](https://github.com/codeigniter4/CodeIgniter4/issues/950) -- IncomingRequest getUserAgent problem only object [\#948](https://github.com/codeigniter4/CodeIgniter4/issues/948) -- url\_helper current\_url https fix. [\#947](https://github.com/codeigniter4/CodeIgniter4/issues/947) -- Validation "matches" rule is not getting the label from the corresponding field [\#946](https://github.com/codeigniter4/CodeIgniter4/issues/946) -- no luck setting up user\_guide [\#944](https://github.com/codeigniter4/CodeIgniter4/issues/944) -- mysqli\_sql\_exception: No such file or directory [\#943](https://github.com/codeigniter4/CodeIgniter4/issues/943) -- Major Security Flaw - Database Credentials get leaked. [\#935](https://github.com/codeigniter4/CodeIgniter4/issues/935) -- Debug Toolbar is not loaded when CSP is enabled [\#934](https://github.com/codeigniter4/CodeIgniter4/issues/934) -- This is a problem with frequent operations [\#933](https://github.com/codeigniter4/CodeIgniter4/issues/933) -- Redirects not working [\#931](https://github.com/codeigniter4/CodeIgniter4/issues/931) -- Model fillPlaceholders\(\) rule reference [\#930](https://github.com/codeigniter4/CodeIgniter4/issues/930) -- session in not working on database [\#925](https://github.com/codeigniter4/CodeIgniter4/issues/925) -- Twig [\#919](https://github.com/codeigniter4/CodeIgniter4/issues/919) -- setDefaultNamespace Sub directories [\#917](https://github.com/codeigniter4/CodeIgniter4/issues/917) -- Load language issue [\#913](https://github.com/codeigniter4/CodeIgniter4/issues/913) -- Find a bug ,CSRFVerify [\#912](https://github.com/codeigniter4/CodeIgniter4/issues/912) -- validation form\_error\(\) [\#911](https://github.com/codeigniter4/CodeIgniter4/issues/911) -- About Filters matching rules [\#908](https://github.com/codeigniter4/CodeIgniter4/issues/908) -- Prepared Query Update problem [\#904](https://github.com/codeigniter4/CodeIgniter4/issues/904) -- Model::first\(\) ambiguous id error [\#903](https://github.com/codeigniter4/CodeIgniter4/issues/903) -- Add Model beforeDelete property [\#902](https://github.com/codeigniter4/CodeIgniter4/issues/902) -- passing form\_validation\(\) errors with redirect\(\) to any view | and include methods for post only by using \[ HTTP verbs in routes \] and Resource route [\#900](https://github.com/codeigniter4/CodeIgniter4/issues/900) -- Database failover error on postgresql [\#899](https://github.com/codeigniter4/CodeIgniter4/issues/899) -- valid\_email validation error [\#898](https://github.com/codeigniter4/CodeIgniter4/issues/898) -- Multi language \( on same page \) support [\#891](https://github.com/codeigniter4/CodeIgniter4/issues/891) -- About environnement error\_reporting [\#889](https://github.com/codeigniter4/CodeIgniter4/issues/889) -- Subfolders for lang\(\) [\#887](https://github.com/codeigniter4/CodeIgniter4/issues/887) -- Urgent issues [\#875](https://github.com/codeigniter4/CodeIgniter4/issues/875) -- remark [\#871](https://github.com/codeigniter4/CodeIgniter4/issues/871) -- Bug fix mysqli transaction function call [\#870](https://github.com/codeigniter4/CodeIgniter4/issues/870) -- Debugbar SSL request javascript problem [\#867](https://github.com/codeigniter4/CodeIgniter4/issues/867) -- Backslashes being escaped in where conditions. [\#866](https://github.com/codeigniter4/CodeIgniter4/issues/866) -- Model::delete method [\#865](https://github.com/codeigniter4/CodeIgniter4/issues/865) -- about SessionHandlerInterface error [\#864](https://github.com/codeigniter4/CodeIgniter4/issues/864) -- Respond Error Pages by Content-Type [\#863](https://github.com/codeigniter4/CodeIgniter4/issues/863) -- When codeigniter 4 will be released ? [\#860](https://github.com/codeigniter4/CodeIgniter4/issues/860) -- Catch in Controllers/Checks.php [\#859](https://github.com/codeigniter4/CodeIgniter4/issues/859) -- View render function LFI\(local arbitray file include\) issue [\#857](https://github.com/codeigniter4/CodeIgniter4/issues/857) -- Suggestion regarding codeigniter 4 [\#856](https://github.com/codeigniter4/CodeIgniter4/issues/856) -- Problem with using Entity class \(Could not execute App\Entities\::\_\_construct\(\)\) [\#855](https://github.com/codeigniter4/CodeIgniter4/issues/855) -- Config Unexpectedly Being Overwritten by $\_ENV variable [\#853](https://github.com/codeigniter4/CodeIgniter4/issues/853) -- Use PHP 7.0 or 7.1 typehints? [\#847](https://github.com/codeigniter4/CodeIgniter4/issues/847) -- There is something wrong with ViewTest::testRenderScrapsDataByDefault [\#846](https://github.com/codeigniter4/CodeIgniter4/issues/846) -- Session in filter [\#840](https://github.com/codeigniter4/CodeIgniter4/issues/840) -- Moved uploaded files doesn't retain new filename. [\#839](https://github.com/codeigniter4/CodeIgniter4/issues/839) -- Unable to use like in where? [\#838](https://github.com/codeigniter4/CodeIgniter4/issues/838) -- Status of the version [\#837](https://github.com/codeigniter4/CodeIgniter4/issues/837) -- when set cache to file, when get a error [\#836](https://github.com/codeigniter4/CodeIgniter4/issues/836) -- Bug toolbar [\#834](https://github.com/codeigniter4/CodeIgniter4/issues/834) -- Table name can not use the alias [\#831](https://github.com/codeigniter4/CodeIgniter4/issues/831) -- Validation - permit\_empty [\#830](https://github.com/codeigniter4/CodeIgniter4/issues/830) -- Unable to install Codeigniter using composer [\#829](https://github.com/codeigniter4/CodeIgniter4/issues/829) -- Throttler usage [\#827](https://github.com/codeigniter4/CodeIgniter4/issues/827) -- Display Label on Form Validation [\#826](https://github.com/codeigniter4/CodeIgniter4/issues/826) -- Question about Filters [\#824](https://github.com/codeigniter4/CodeIgniter4/issues/824) -- uri-\>getHost\(\) [\#815](https://github.com/codeigniter4/CodeIgniter4/issues/815) -- Error changing databases CodeIgniter\Database\MySQLi\Connection::dbSelect\(\) [\#813](https://github.com/codeigniter4/CodeIgniter4/issues/813) -- Create a skeleton system for codeigniter [\#806](https://github.com/codeigniter4/CodeIgniter4/issues/806) -- Feature - CLI Prompt with Validation [\#800](https://github.com/codeigniter4/CodeIgniter4/issues/800) -- Routing problem /sth-abc-\>App:sth/foo \(ok\) and /sth/abc-\>App:sth/foo \(nok\) [\#799](https://github.com/codeigniter4/CodeIgniter4/issues/799) -- Update with delete value [\#796](https://github.com/codeigniter4/CodeIgniter4/issues/796) -- redirect\(\)-\>to\(\) is changing value of the base64 parameter [\#790](https://github.com/codeigniter4/CodeIgniter4/issues/790) -- View Parser escaping data even if passed 'raw' as context in setData\(\) [\#788](https://github.com/codeigniter4/CodeIgniter4/issues/788) -- Cant instantiate Parser Directly. [\#787](https://github.com/codeigniter4/CodeIgniter4/issues/787) -- about redis error [\#783](https://github.com/codeigniter4/CodeIgniter4/issues/783) -- Validation issue and routing issue [\#782](https://github.com/codeigniter4/CodeIgniter4/issues/782) -- Query binding stopped working [\#781](https://github.com/codeigniter4/CodeIgniter4/issues/781) -- Entity exception in line 270 and 143 \(current version\). [\#780](https://github.com/codeigniter4/CodeIgniter4/issues/780) -- about Error Handling [\#778](https://github.com/codeigniter4/CodeIgniter4/issues/778) -- $this-\>CI-\>request-\>getIPAddress\(\) protected [\#776](https://github.com/codeigniter4/CodeIgniter4/issues/776) -- How do you access the $this bound data of the controller in the view? Ci3 is OK, ci4 doesn't seem to work [\#775](https://github.com/codeigniter4/CodeIgniter4/issues/775) -- I wish I could call the $this of the controller in the business model. What's the solution? [\#774](https://github.com/codeigniter4/CodeIgniter4/issues/774) -- Ignore\_value should be the value of ID, how do I get it? [\#772](https://github.com/codeigniter4/CodeIgniter4/issues/772) -- Can you increase the function of unique values in the test database? [\#771](https://github.com/codeigniter4/CodeIgniter4/issues/771) -- about where\(null\) [\#770](https://github.com/codeigniter4/CodeIgniter4/issues/770) -- redirect\(\)-\>to\(\) problem with output buffering [\#769](https://github.com/codeigniter4/CodeIgniter4/issues/769) -- Class 'App\Models\NewsModel' not found [\#766](https://github.com/codeigniter4/CodeIgniter4/issues/766) -- Undefined property: Config\App::$errorViewPath [\#765](https://github.com/codeigniter4/CodeIgniter4/issues/765) -- Zend/Escaper too big ,its not necessary [\#764](https://github.com/codeigniter4/CodeIgniter4/issues/764) -- .htaccess on php7.0.12 nts not work. [\#763](https://github.com/codeigniter4/CodeIgniter4/issues/763) -- DebugBar Routes Collector throws ErrorException when optional parameter is not passed to controller [\#762](https://github.com/codeigniter4/CodeIgniter4/issues/762) -- Debug View change page structure [\#761](https://github.com/codeigniter4/CodeIgniter4/issues/761) -- Debug Tool: Show view filepath [\#758](https://github.com/codeigniter4/CodeIgniter4/issues/758) -- PHPCBF - CodeIgniter4-Standard [\#757](https://github.com/codeigniter4/CodeIgniter4/issues/757) -- about errorViewPath [\#745](https://github.com/codeigniter4/CodeIgniter4/issues/745) -- Combine Composer with Install [\#744](https://github.com/codeigniter4/CodeIgniter4/issues/744) -- I have multiple applications. How can I share helpers and Class Map in multiple applications? [\#742](https://github.com/codeigniter4/CodeIgniter4/issues/742) -- I have multiple applications. How can I share helpers and Class Map in multiple applications? [\#741](https://github.com/codeigniter4/CodeIgniter4/issues/741) -- When can the fourth version be released, too much [\#738](https://github.com/codeigniter4/CodeIgniter4/issues/738) -- about Sub-directories [\#737](https://github.com/codeigniter4/CodeIgniter4/issues/737) -- About 【Use Config\Services as CodeIgniter\Services】 [\#735](https://github.com/codeigniter4/CodeIgniter4/issues/735) -- Mysqli Driver doesn't throw sql level exceptions / error [\#734](https://github.com/codeigniter4/CodeIgniter4/issues/734) -- LogicException \#403 - CSRF [\#733](https://github.com/codeigniter4/CodeIgniter4/issues/733) -- User guide for ci4 is not responsive on mobile.\(screen width less then 769px\) [\#730](https://github.com/codeigniter4/CodeIgniter4/issues/730) -- Composer intall, as described in documentation, points to different github repository\(!\) [\#726](https://github.com/codeigniter4/CodeIgniter4/issues/726) -- Fatal error when trying to run using PHP built in server [\#725](https://github.com/codeigniter4/CodeIgniter4/issues/725) -- Passing array as argument in Parser Plugin not working [\#724](https://github.com/codeigniter4/CodeIgniter4/issues/724) -- $request-\>uri-\>getSegment\(\); return error [\#722](https://github.com/codeigniter4/CodeIgniter4/issues/722) -- Double quotation in getFieldData\(\); [\#721](https://github.com/codeigniter4/CodeIgniter4/issues/721) -- Should Model class implement the magic setter/getter methods for protected properties? [\#720](https://github.com/codeigniter4/CodeIgniter4/issues/720) -- Forge class trying to create UNSIGNED integer fields in Postgresql [\#719](https://github.com/codeigniter4/CodeIgniter4/issues/719) -- Tests Failing [\#717](https://github.com/codeigniter4/CodeIgniter4/issues/717) -- Form validation not working when Passing Empty array \[ \] [\#715](https://github.com/codeigniter4/CodeIgniter4/issues/715) -- Redirects to wrong incomplete URL [\#710](https://github.com/codeigniter4/CodeIgniter4/issues/710) -- pg\_escape\_literal\(\) expects parameter 1 to be resource, boolean given [\#709](https://github.com/codeigniter4/CodeIgniter4/issues/709) -- Separate DebugToolbar from CI\_DEBUG [\#707](https://github.com/codeigniter4/CodeIgniter4/issues/707) -- common.php [\#706](https://github.com/codeigniter4/CodeIgniter4/issues/706) -- Parser::parsePair fails if template contains a $ sign [\#705](https://github.com/codeigniter4/CodeIgniter4/issues/705) -- view parser throws InvalidArgumentException [\#704](https://github.com/codeigniter4/CodeIgniter4/issues/704) -- Auto redirect to root folder [\#703](https://github.com/codeigniter4/CodeIgniter4/issues/703) -- ErrorException \#1 htmlspecialchars\(\) expects parameter 1 to be string [\#702](https://github.com/codeigniter4/CodeIgniter4/issues/702) -- Support for Labels in Validation [\#696](https://github.com/codeigniter4/CodeIgniter4/issues/696) -- Why is system/bootstrap.php lowercase? [\#695](https://github.com/codeigniter4/CodeIgniter4/issues/695) -- To much escaping in where [\#691](https://github.com/codeigniter4/CodeIgniter4/issues/691) -- session in DB not working [\#690](https://github.com/codeigniter4/CodeIgniter4/issues/690) -- imagecopyresampled\(\): supplied resource is not a valid Image resource [\#689](https://github.com/codeigniter4/CodeIgniter4/issues/689) -- Postgres json column search error [\#680](https://github.com/codeigniter4/CodeIgniter4/issues/680) -- multiple File upload issue [\#679](https://github.com/codeigniter4/CodeIgniter4/issues/679) -- Postgres Json type error on save [\#677](https://github.com/codeigniter4/CodeIgniter4/issues/677) -- show strange chracters in kint.php inside system/ThirdParty folder. [\#676](https://github.com/codeigniter4/CodeIgniter4/issues/676) -- Need Parser plugin for validation errors. [\#675](https://github.com/codeigniter4/CodeIgniter4/issues/675) -- Fatal error: Cannot use Config\Services as Services [\#674](https://github.com/codeigniter4/CodeIgniter4/issues/674) -- Routing issue - bad args in function call. [\#672](https://github.com/codeigniter4/CodeIgniter4/issues/672) -- Entity Concerns [\#662](https://github.com/codeigniter4/CodeIgniter4/issues/662) -- Undefined method CLIRequest::isSecure\(\) [\#656](https://github.com/codeigniter4/CodeIgniter4/issues/656) -- PREG\_QUOTE and SQL String Binding [\#655](https://github.com/codeigniter4/CodeIgniter4/issues/655) -- Discussions on Pagination hasNext\(\) and hasPrevious\(\) [\#651](https://github.com/codeigniter4/CodeIgniter4/issues/651) -- Feature requested- Auth and Template Engine [\#647](https://github.com/codeigniter4/CodeIgniter4/issues/647) -- route\_to doesn't work for post routes [\#642](https://github.com/codeigniter4/CodeIgniter4/issues/642) -- CURL response header parsing \(100-continue\) [\#638](https://github.com/codeigniter4/CodeIgniter4/issues/638) -- Q: Support of array notations is not longer supported? [\#627](https://github.com/codeigniter4/CodeIgniter4/issues/627) -- Formvalidation not skip validation rules if field not required and field data empty [\#614](https://github.com/codeigniter4/CodeIgniter4/issues/614) -- Route naming and Groups [\#612](https://github.com/codeigniter4/CodeIgniter4/issues/612) -- Image resizing issue [\#610](https://github.com/codeigniter4/CodeIgniter4/issues/610) -- Too much escaping negative int \(int \< 0\) [\#606](https://github.com/codeigniter4/CodeIgniter4/issues/606) -- Problem with Sth3Model loading from Sth2Model when Sth3Model is in subdiretory \(Subdir1\) [\#605](https://github.com/codeigniter4/CodeIgniter4/issues/605) -- I'm not sure but captcha seems currently unavailable. Would it be included in ci4? [\#604](https://github.com/codeigniter4/CodeIgniter4/issues/604) -- Issue with UploadFile \[tempName == name/originalName\] ? [\#602](https://github.com/codeigniter4/CodeIgniter4/issues/602) -- Command Line Seeding Error [\#601](https://github.com/codeigniter4/CodeIgniter4/issues/601) -- form\_open\_multipart\('', \[\], $hidden\) [\#598](https://github.com/codeigniter4/CodeIgniter4/issues/598) -- Minor issue with docs - form helper \(there is no html\_escape function\) - shoud be esc [\#595](https://github.com/codeigniter4/CodeIgniter4/issues/595) -- $session-\>destroy\(\) and $session-\>stop\(\) do not work? [\#592](https://github.com/codeigniter4/CodeIgniter4/issues/592) -- file upload issue [\#591](https://github.com/codeigniter4/CodeIgniter4/issues/591) -- \IncomingRequest method getFiles\(\) - Should really return only arrays? [\#590](https://github.com/codeigniter4/CodeIgniter4/issues/590) -- Third argument '$param' missing in validation getErrorMessage [\#589](https://github.com/codeigniter4/CodeIgniter4/issues/589) -- Error on Postgre insertID [\#587](https://github.com/codeigniter4/CodeIgniter4/issues/587) -- \InputRequest $request-\>getFiles\(\) doesn't return FileCollection instance [\#586](https://github.com/codeigniter4/CodeIgniter4/issues/586) -- Documentation out of date? discoverLocal\(\) [\#581](https://github.com/codeigniter4/CodeIgniter4/issues/581) -- Storing data [\#580](https://github.com/codeigniter4/CodeIgniter4/issues/580) -- Propose Moving Model::classToArray\(\) method to Entity class as public method [\#579](https://github.com/codeigniter4/CodeIgniter4/issues/579) -- camelize helper function incorrect [\#576](https://github.com/codeigniter4/CodeIgniter4/issues/576) -- helper function uses include instead of include\_once [\#575](https://github.com/codeigniter4/CodeIgniter4/issues/575) -- system/Validation/Views/list.php fix [\#574](https://github.com/codeigniter4/CodeIgniter4/issues/574) -- \[FEATURE\] Model Class: updateWhere\(\) [\#572](https://github.com/codeigniter4/CodeIgniter4/issues/572) -- Model Order [\#571](https://github.com/codeigniter4/CodeIgniter4/issues/571) -- Minor thing with View file not found exception... [\#570](https://github.com/codeigniter4/CodeIgniter4/issues/570) -- Error: redirect\(\) - ltrim\(\) expects parameter 1 to be string, object given [\#568](https://github.com/codeigniter4/CodeIgniter4/issues/568) -- Tutorial is Wrong [\#562](https://github.com/codeigniter4/CodeIgniter4/issues/562) -- Model Hooks/Events Suggestion [\#557](https://github.com/codeigniter4/CodeIgniter4/issues/557) -- Initialize on Model \_\_construct [\#556](https://github.com/codeigniter4/CodeIgniter4/issues/556) -- Event trigger "pre\_system" not found [\#555](https://github.com/codeigniter4/CodeIgniter4/issues/555) -- Validation Issues [\#550](https://github.com/codeigniter4/CodeIgniter4/issues/550) -- Model Entity suggestion [\#549](https://github.com/codeigniter4/CodeIgniter4/issues/549) -- CodeIgniter\Files\File\(\) and FileNotFoundException issue with creating new files [\#548](https://github.com/codeigniter4/CodeIgniter4/issues/548) -- override core files [\#546](https://github.com/codeigniter4/CodeIgniter4/issues/546) -- CodeIgniter\Format\JSONFormatter needs a look. [\#544](https://github.com/codeigniter4/CodeIgniter4/issues/544) -- getenv non covered system method [\#543](https://github.com/codeigniter4/CodeIgniter4/issues/543) -- Couldn't manage without index.php [\#541](https://github.com/codeigniter4/CodeIgniter4/issues/541) -- Debug bar: Uncaught ErrorException: str\_repeat\(\): Second argument has to be greater than or equal to 0 [\#538](https://github.com/codeigniter4/CodeIgniter4/issues/538) -- Create localization filters for View Parser to aid in formatting numbers [\#536](https://github.com/codeigniter4/CodeIgniter4/issues/536) -- Create lang plugin for the View Parser [\#535](https://github.com/codeigniter4/CodeIgniter4/issues/535) -- Minor: Debug bar showing htmlentities \(\\) [\#531](https://github.com/codeigniter4/CodeIgniter4/issues/531) -- Autoloading not functional with custom namespaces outside application dir [\#529](https://github.com/codeigniter4/CodeIgniter4/issues/529) -- where i extends Controller, and rewrite \_\_construct, get error [\#527](https://github.com/codeigniter4/CodeIgniter4/issues/527) -- Debug/ImageException.php failed to open stream: No such file or directory [\#525](https://github.com/codeigniter4/CodeIgniter4/issues/525) -- Superglobals reset [\#524](https://github.com/codeigniter4/CodeIgniter4/issues/524) -- Bring ENV definition back out to the index file. [\#519](https://github.com/codeigniter4/CodeIgniter4/issues/519) -- Exception issue [\#517](https://github.com/codeigniter4/CodeIgniter4/issues/517) -- Port Zip library from CI3 [\#505](https://github.com/codeigniter4/CodeIgniter4/issues/505) -- Port Encryption library from CI3 [\#504](https://github.com/codeigniter4/CodeIgniter4/issues/504) -- Port SQLite database driver from CI3 [\#502](https://github.com/codeigniter4/CodeIgniter4/issues/502) -- A template engine suggession in this phase [\#500](https://github.com/codeigniter4/CodeIgniter4/issues/500) -- \_\_set in \App\Entities\Sample doesnt works [\#499](https://github.com/codeigniter4/CodeIgniter4/issues/499) -- Router rule: \(:alphanum\) doesn't work [\#498](https://github.com/codeigniter4/CodeIgniter4/issues/498) -- Undefined index: host [\#497](https://github.com/codeigniter4/CodeIgniter4/issues/497) -- Unreasonable default parameter [\#482](https://github.com/codeigniter4/CodeIgniter4/issues/482) -- Model issues [\#479](https://github.com/codeigniter4/CodeIgniter4/issues/479) -- Route Blocking [\#474](https://github.com/codeigniter4/CodeIgniter4/issues/474) -- Class 'CodeIgniter\Hooks\Hooks' not found [\#473](https://github.com/codeigniter4/CodeIgniter4/issues/473) -- Work with entities and validation bug [\#472](https://github.com/codeigniter4/CodeIgniter4/issues/472) -- route bug or not? [\#470](https://github.com/codeigniter4/CodeIgniter4/issues/470) -- serve.php option --host no effect on line 37 [\#469](https://github.com/codeigniter4/CodeIgniter4/issues/469) -- File class should stand on its own [\#468](https://github.com/codeigniter4/CodeIgniter4/issues/468) -- Database can't save connect instances because var "$group" default is NULL [\#466](https://github.com/codeigniter4/CodeIgniter4/issues/466) -- redirect\(\) issue [\#465](https://github.com/codeigniter4/CodeIgniter4/issues/465) -- Join not working in Query Builder [\#464](https://github.com/codeigniter4/CodeIgniter4/issues/464) -- If the controller’s method's parameter has a default value , the program will show 500 error [\#461](https://github.com/codeigniter4/CodeIgniter4/issues/461) -- Wrong ROOTPATH on console [\#460](https://github.com/codeigniter4/CodeIgniter4/issues/460) -- Rename "public" Folder Not Working [\#453](https://github.com/codeigniter4/CodeIgniter4/issues/453) -- Environment File [\#452](https://github.com/codeigniter4/CodeIgniter4/issues/452) -- Currency Localisation Not Working [\#448](https://github.com/codeigniter4/CodeIgniter4/issues/448) -- session getFlashdata\(\) is not removing the flash data [\#446](https://github.com/codeigniter4/CodeIgniter4/issues/446) -- CURLRequest: HTTPHEADER and POSTFIELDS [\#445](https://github.com/codeigniter4/CodeIgniter4/issues/445) -- Language Line Prepend Recommendation [\#443](https://github.com/codeigniter4/CodeIgniter4/issues/443) -- Debug Toolbar Array Post Data [\#442](https://github.com/codeigniter4/CodeIgniter4/issues/442) -- Migration File & Class Name Issues [\#437](https://github.com/codeigniter4/CodeIgniter4/issues/437) -- Validation Rule Constants [\#436](https://github.com/codeigniter4/CodeIgniter4/issues/436) -- CURLRequest: Only variables should be passed by reference [\#434](https://github.com/codeigniter4/CodeIgniter4/issues/434) -- CLI new commands [\#433](https://github.com/codeigniter4/CodeIgniter4/issues/433) -- issue in phpunit [\#429](https://github.com/codeigniter4/CodeIgniter4/issues/429) -- Model Class To Array Does Not Use Get Magic Method Or Allowed Fields Property [\#427](https://github.com/codeigniter4/CodeIgniter4/issues/427) -- Response 404 not same satus get it [\#425](https://github.com/codeigniter4/CodeIgniter4/issues/425) -- Response Trait Fail Server Error Method [\#424](https://github.com/codeigniter4/CodeIgniter4/issues/424) -- Validation Required Rule Only Accepts Arrays or Strings [\#423](https://github.com/codeigniter4/CodeIgniter4/issues/423) -- CLI with parameters is not working [\#422](https://github.com/codeigniter4/CodeIgniter4/issues/422) -- API Response Trait :: failValidationError\(\) Status Code 422 [\#420](https://github.com/codeigniter4/CodeIgniter4/issues/420) -- Validation Get & Set Rule Group [\#419](https://github.com/codeigniter4/CodeIgniter4/issues/419) -- One-Off Validation for a Single Value [\#418](https://github.com/codeigniter4/CodeIgniter4/issues/418) -- Language File Array Support [\#414](https://github.com/codeigniter4/CodeIgniter4/issues/414) -- Pagination: How to trans string param when using $pager-\>links\(\)? [\#413](https://github.com/codeigniter4/CodeIgniter4/issues/413) -- Validation Exceptions [\#412](https://github.com/codeigniter4/CodeIgniter4/issues/412) -- Incorrect .htaccess [\#410](https://github.com/codeigniter4/CodeIgniter4/issues/410) -- Call to a member function getResult\(\) on boolean [\#409](https://github.com/codeigniter4/CodeIgniter4/issues/409) -- Writable directory question [\#407](https://github.com/codeigniter4/CodeIgniter4/issues/407) -- Paginate Class [\#406](https://github.com/codeigniter4/CodeIgniter4/issues/406) -- Base URL Not Working With HTTPS [\#396](https://github.com/codeigniter4/CodeIgniter4/issues/396) -- How to current page detailed information on router or etc? [\#393](https://github.com/codeigniter4/CodeIgniter4/issues/393) -- timer long time getElapsedTime problem [\#390](https://github.com/codeigniter4/CodeIgniter4/issues/390) -- Problem with system bootstrap file needs to be fixed! [\#389](https://github.com/codeigniter4/CodeIgniter4/issues/389) -- redirect reverseRoute problem [\#387](https://github.com/codeigniter4/CodeIgniter4/issues/387) -- Hooks::on not working \(it was working some time ago\) [\#383](https://github.com/codeigniter4/CodeIgniter4/issues/383) -- disable coveralls auto comment on pull request [\#382](https://github.com/codeigniter4/CodeIgniter4/issues/382) -- phpunit don't work \(fatal error\) on PHP 7.0.0 [\#373](https://github.com/codeigniter4/CodeIgniter4/issues/373) -- session id validation when using php7.1 [\#371](https://github.com/codeigniter4/CodeIgniter4/issues/371) -- travis test coverage report service [\#370](https://github.com/codeigniter4/CodeIgniter4/issues/370) -- The efficiency of 'insertBatch' function [\#368](https://github.com/codeigniter4/CodeIgniter4/issues/368) -- Run phpunit with --coverage-text/html got Error: Undefined variable: matchIP [\#363](https://github.com/codeigniter4/CodeIgniter4/issues/363) -- Extending Core Classes [\#358](https://github.com/codeigniter4/CodeIgniter4/issues/358) -- Validation field name issue? [\#356](https://github.com/codeigniter4/CodeIgniter4/issues/356) -- Old Cache-Control header tag remove? [\#355](https://github.com/codeigniter4/CodeIgniter4/issues/355) -- $this-\>request-\>getPost\(\) // Subkey not included data [\#353](https://github.com/codeigniter4/CodeIgniter4/issues/353) -- Routes static parameter problem not found page [\#352](https://github.com/codeigniter4/CodeIgniter4/issues/352) -- bug-form\_open\_multipart [\#345](https://github.com/codeigniter4/CodeIgniter4/issues/345) -- Recently ci4 everything goes well? I look ci the space named [\#336](https://github.com/codeigniter4/CodeIgniter4/issues/336) -- Request setBody not working [\#332](https://github.com/codeigniter4/CodeIgniter4/issues/332) -- HTTP/URI, CreateURIString yield unexpected result \(http:/// instead of http://\) [\#331](https://github.com/codeigniter4/CodeIgniter4/issues/331) -- session class: inconsistency on get\(\) between document and code [\#330](https://github.com/codeigniter4/CodeIgniter4/issues/330) -- Model.php Return Type Object causes Error when Saving. \(Fixed with this modification\) [\#329](https://github.com/codeigniter4/CodeIgniter4/issues/329) -- a bug about set\(\) [\#325](https://github.com/codeigniter4/CodeIgniter4/issues/325) -- `google map` in Mysqli Could be a Mistake [\#324](https://github.com/codeigniter4/CodeIgniter4/issues/324) -- Modules Controllers sub-directory problem [\#322](https://github.com/codeigniter4/CodeIgniter4/issues/322) -- Post action after Toolbar -\> Vars -\> Headers Content-Type 2 lines [\#321](https://github.com/codeigniter4/CodeIgniter4/issues/321) -- Database builder-\>table\(\) connection reference problem [\#320](https://github.com/codeigniter4/CodeIgniter4/issues/320) -- RedisHandler, Session regenerate id problem [\#318](https://github.com/codeigniter4/CodeIgniter4/issues/318) -- view\_cell optional variables array\_key\_exists null fix. [\#317](https://github.com/codeigniter4/CodeIgniter4/issues/317) -- sessionDriver Database not available [\#315](https://github.com/codeigniter4/CodeIgniter4/issues/315) -- Add support for Content-MD5 headers [\#314](https://github.com/codeigniter4/CodeIgniter4/issues/314) -- Scalar type declaration 'string' must be unqualified [\#312](https://github.com/codeigniter4/CodeIgniter4/issues/312) -- Now ci4 product development can be used? Or how long before they can be released? Very much looking forward [\#311](https://github.com/codeigniter4/CodeIgniter4/issues/311) -- Sessions in Debug Bar Could be a Mistake [\#310](https://github.com/codeigniter4/CodeIgniter4/issues/310) -- Should namespaces be used or fully written at method heads [\#309](https://github.com/codeigniter4/CodeIgniter4/issues/309) -- More filtering issues with Query [\#306](https://github.com/codeigniter4/CodeIgniter4/issues/306) -- Too much escaping in DB? [\#302](https://github.com/codeigniter4/CodeIgniter4/issues/302) -- Error Cell caching [\#297](https://github.com/codeigniter4/CodeIgniter4/issues/297) -- A suggestion for debug exceptions layout [\#295](https://github.com/codeigniter4/CodeIgniter4/issues/295) -- getSharedInstance doesn't work on costum services [\#294](https://github.com/codeigniter4/CodeIgniter4/issues/294) -- Separated services default with trait [\#292](https://github.com/codeigniter4/CodeIgniter4/issues/292) -- HTML-Purifier [\#291](https://github.com/codeigniter4/CodeIgniter4/issues/291) -- Implement native ORM [\#289](https://github.com/codeigniter4/CodeIgniter4/issues/289) -- Igniter 4 services [\#287](https://github.com/codeigniter4/CodeIgniter4/issues/287) -- Error Documentation - Tutorial [\#286](https://github.com/codeigniter4/CodeIgniter4/issues/286) -- Request is doing the filter\_var even if value is not in the $\_REQUEST array [\#285](https://github.com/codeigniter4/CodeIgniter4/issues/285) -- controller routing [\#284](https://github.com/codeigniter4/CodeIgniter4/issues/284) -- URI Routing issues [\#278](https://github.com/codeigniter4/CodeIgniter4/issues/278) -- Email Library [\#276](https://github.com/codeigniter4/CodeIgniter4/issues/276) -- sql error [\#273](https://github.com/codeigniter4/CodeIgniter4/issues/273) -- DB Needs Transactions implemented. [\#268](https://github.com/codeigniter4/CodeIgniter4/issues/268) -- Error Call to undefined method CodeIgniter\Database\MySQLi\Connection::close\(\) [\#267](https://github.com/codeigniter4/CodeIgniter4/issues/267) -- Application/ThirdParty folder necessity [\#265](https://github.com/codeigniter4/CodeIgniter4/issues/265) -- ErrorException when Connecting to Multiple Databases [\#255](https://github.com/codeigniter4/CodeIgniter4/issues/255) -- Toolbar.php :: ErrorException: Array to string conversion [\#254](https://github.com/codeigniter4/CodeIgniter4/issues/254) -- The Hooks feature does not work [\#248](https://github.com/codeigniter4/CodeIgniter4/issues/248) -- $baseUrl problem with Router [\#238](https://github.com/codeigniter4/CodeIgniter4/issues/238) -- File upload bugs [\#236](https://github.com/codeigniter4/CodeIgniter4/issues/236) -- standardize comments [\#234](https://github.com/codeigniter4/CodeIgniter4/issues/234) -- 3.1.0 Email Library Corrupting PDF Attachments [\#220](https://github.com/codeigniter4/CodeIgniter4/issues/220) -- Error DotEnv.php on line 121 [\#216](https://github.com/codeigniter4/CodeIgniter4/issues/216) -- Typography Helper [\#214](https://github.com/codeigniter4/CodeIgniter4/issues/214) -- Security Helper [\#213](https://github.com/codeigniter4/CodeIgniter4/issues/213) -- Number Helper [\#212](https://github.com/codeigniter4/CodeIgniter4/issues/212) -- Text Helper [\#211](https://github.com/codeigniter4/CodeIgniter4/issues/211) -- Inflector Helper [\#210](https://github.com/codeigniter4/CodeIgniter4/issues/210) -- HTML Helper [\#209](https://github.com/codeigniter4/CodeIgniter4/issues/209) -- FileSystem "Helper" [\#207](https://github.com/codeigniter4/CodeIgniter4/issues/207) -- Date Helper [\#206](https://github.com/codeigniter4/CodeIgniter4/issues/206) -- Cookie Helper [\#205](https://github.com/codeigniter4/CodeIgniter4/issues/205) -- Missing {memory\_usage} ? [\#197](https://github.com/codeigniter4/CodeIgniter4/issues/197) -- Exception or disable toolbar for specific URI [\#195](https://github.com/codeigniter4/CodeIgniter4/issues/195) -- Config Settings Usability [\#186](https://github.com/codeigniter4/CodeIgniter4/issues/186) -- Binders [\#185](https://github.com/codeigniter4/CodeIgniter4/issues/185) -- Mess Detector rules [\#184](https://github.com/codeigniter4/CodeIgniter4/issues/184) -- Coding Standards Fixer rules [\#183](https://github.com/codeigniter4/CodeIgniter4/issues/183) -- Code Sniffer Rules [\#182](https://github.com/codeigniter4/CodeIgniter4/issues/182) -- Placing view template outside of the 'Views' dir when using view\(\) [\#180](https://github.com/codeigniter4/CodeIgniter4/issues/180) -- Controller return output instead of echo view [\#179](https://github.com/codeigniter4/CodeIgniter4/issues/179) -- Honeypot Filter [\#176](https://github.com/codeigniter4/CodeIgniter4/issues/176) -- Form Helper [\#174](https://github.com/codeigniter4/CodeIgniter4/issues/174) -- ILIKE-based portion of the query for PostgreSQL [\#173](https://github.com/codeigniter4/CodeIgniter4/issues/173) -- “&get\_instance\(\)” in the Ci3 how to use it? [\#166](https://github.com/codeigniter4/CodeIgniter4/issues/166) -- system/Database/BaseConnection.php Change in getFieldNames\(\) method [\#164](https://github.com/codeigniter4/CodeIgniter4/issues/164) -- Error in session FileHandler and BaseHandler [\#152](https://github.com/codeigniter4/CodeIgniter4/issues/152) -- No listFields\(\) method in Postgre connection [\#151](https://github.com/codeigniter4/CodeIgniter4/issues/151) -- Controller Filters [\#150](https://github.com/codeigniter4/CodeIgniter4/issues/150) -- insert bug [\#149](https://github.com/codeigniter4/CodeIgniter4/issues/149) -- Router striping real dirpath from the urls. [\#148](https://github.com/codeigniter4/CodeIgniter4/issues/148) -- Problem throw error for default controller [\#146](https://github.com/codeigniter4/CodeIgniter4/issues/146) -- Routing issues. [\#145](https://github.com/codeigniter4/CodeIgniter4/issues/145) -- Pagination Library [\#142](https://github.com/codeigniter4/CodeIgniter4/issues/142) -- \[i18n\] Localization In Core [\#141](https://github.com/codeigniter4/CodeIgniter4/issues/141) -- Language [\#140](https://github.com/codeigniter4/CodeIgniter4/issues/140) -- Parser [\#139](https://github.com/codeigniter4/CodeIgniter4/issues/139) -- Application directory is missing the Helpers folder [\#133](https://github.com/codeigniter4/CodeIgniter4/issues/133) -- HTTP/Request.php Error [\#132](https://github.com/codeigniter4/CodeIgniter4/issues/132) -- Public properties issue? [\#124](https://github.com/codeigniter4/CodeIgniter4/issues/124) -- ci4 support websocket it? [\#121](https://github.com/codeigniter4/CodeIgniter4/issues/121) -- View Cells [\#116](https://github.com/codeigniter4/CodeIgniter4/issues/116) -- Cache Engine [\#115](https://github.com/codeigniter4/CodeIgniter4/issues/115) -- Image Class [\#114](https://github.com/codeigniter4/CodeIgniter4/issues/114) -- Uploader Class [\#113](https://github.com/codeigniter4/CodeIgniter4/issues/113) -- API Response Trait [\#86](https://github.com/codeigniter4/CodeIgniter4/issues/86) -- phpDocumentor bug [\#85](https://github.com/codeigniter4/CodeIgniter4/issues/85) -- Reserved method name [\#76](https://github.com/codeigniter4/CodeIgniter4/issues/76) -- Provide Throttler Filter [\#75](https://github.com/codeigniter4/CodeIgniter4/issues/75) -- Ensure docs are updated for current code. [\#72](https://github.com/codeigniter4/CodeIgniter4/issues/72) -- Writing Testing Docs [\#71](https://github.com/codeigniter4/CodeIgniter4/issues/71) -- Update Tutorial [\#70](https://github.com/codeigniter4/CodeIgniter4/issues/70) -- Review Contribution Guidelines [\#69](https://github.com/codeigniter4/CodeIgniter4/issues/69) -- Database Connection setDatabase\(\) and getVersion\(\) methods [\#68](https://github.com/codeigniter4/CodeIgniter4/issues/68) -- BaseConfig getEnvValue type juggling [\#67](https://github.com/codeigniter4/CodeIgniter4/issues/67) -- Migrations should track history per db group. [\#66](https://github.com/codeigniter4/CodeIgniter4/issues/66) -- Allow passing custom config values to Config\Database::connect\(\) [\#62](https://github.com/codeigniter4/CodeIgniter4/issues/62) -- SessionInterface: unset\(\) vs remove\(\) [\#60](https://github.com/codeigniter4/CodeIgniter4/issues/60) -- Remove Query Builder caching [\#59](https://github.com/codeigniter4/CodeIgniter4/issues/59) -- Why is `$getShared = false` the default? [\#55](https://github.com/codeigniter4/CodeIgniter4/issues/55) -- Why IncomingRequest has setCookie\(\)? [\#52](https://github.com/codeigniter4/CodeIgniter4/issues/52) -- AutoRoute and method arguments [\#45](https://github.com/codeigniter4/CodeIgniter4/issues/45) -- Rename the loader class [\#39](https://github.com/codeigniter4/CodeIgniter4/issues/39) -- exit\(\) prevents from testing [\#31](https://github.com/codeigniter4/CodeIgniter4/issues/31) -- getHeaders [\#27](https://github.com/codeigniter4/CodeIgniter4/issues/27) -- SYSDIR [\#25](https://github.com/codeigniter4/CodeIgniter4/issues/25) -- Coding style checker [\#21](https://github.com/codeigniter4/CodeIgniter4/issues/21) -- Test folder structure [\#20](https://github.com/codeigniter4/CodeIgniter4/issues/20) -- Namespace for test case classes [\#17](https://github.com/codeigniter4/CodeIgniter4/issues/17) -- Missing protocolVersion in response header [\#15](https://github.com/codeigniter4/CodeIgniter4/issues/15) -- Problem with Code Coverage Reporting [\#13](https://github.com/codeigniter4/CodeIgniter4/issues/13) -- Class 'Config\App' not found [\#12](https://github.com/codeigniter4/CodeIgniter4/issues/12) -- Can't get Code Coverage [\#7](https://github.com/codeigniter4/CodeIgniter4/issues/7) -- APPPATH, SYSPATH and similar constants. Rename? [\#2](https://github.com/codeigniter4/CodeIgniter4/issues/2) - -**Merged pull requests:** - -- Adjusting the release build scripts [\#1266](https://github.com/codeigniter4/CodeIgniter4/pull/1266) ([jim-parry](https://github.com/jim-parry)) -- WIP Fix docs re PHP server [\#1265](https://github.com/codeigniter4/CodeIgniter4/pull/1265) ([jim-parry](https://github.com/jim-parry)) -- Release prep part 1 [\#1248](https://github.com/codeigniter4/CodeIgniter4/pull/1248) ([jim-parry](https://github.com/jim-parry)) -- Tweaking the release builder [\#1246](https://github.com/codeigniter4/CodeIgniter4/pull/1246) ([jim-parry](https://github.com/jim-parry)) -- Move Response & APIResponseTrait to outgoing section of UG [\#1243](https://github.com/codeigniter4/CodeIgniter4/pull/1243) ([jim-parry](https://github.com/jim-parry)) -- workaround for buffer problem [\#1242](https://github.com/codeigniter4/CodeIgniter4/pull/1242) ([puschie286](https://github.com/puschie286)) -- Docs/restructure [\#1241](https://github.com/codeigniter4/CodeIgniter4/pull/1241) ([jim-parry](https://github.com/jim-parry)) -- doc fix: replace validation\_errors\(\) function with \Config\Services::validation\(\)-\>listErrors\(\) [\#1238](https://github.com/codeigniter4/CodeIgniter4/pull/1238) ([samsonasik](https://github.com/samsonasik)) -- doc fix: remove unneeded call helper\(url\) as already bootstrapped by default [\#1237](https://github.com/codeigniter4/CodeIgniter4/pull/1237) ([samsonasik](https://github.com/samsonasik)) -- Fix gh-pages deployment [\#1236](https://github.com/codeigniter4/CodeIgniter4/pull/1236) ([jim-parry](https://github.com/jim-parry)) -- Doc/fixes [\#1235](https://github.com/codeigniter4/CodeIgniter4/pull/1235) ([jim-parry](https://github.com/jim-parry)) -- typo in unset [\#1233](https://github.com/codeigniter4/CodeIgniter4/pull/1233) ([titounnes](https://github.com/titounnes)) -- Release build script [\#1231](https://github.com/codeigniter4/CodeIgniter4/pull/1231) ([jim-parry](https://github.com/jim-parry)) -- Fix user guide errors [\#1228](https://github.com/codeigniter4/CodeIgniter4/pull/1228) ([jim-parry](https://github.com/jim-parry)) -- Admin script for user guide build & deploy [\#1227](https://github.com/codeigniter4/CodeIgniter4/pull/1227) ([jim-parry](https://github.com/jim-parry)) -- use short array syntax [\#1223](https://github.com/codeigniter4/CodeIgniter4/pull/1223) ([samsonasik](https://github.com/samsonasik)) -- doc fix: FormatterInterface namespace [\#1222](https://github.com/codeigniter4/CodeIgniter4/pull/1222) ([samsonasik](https://github.com/samsonasik)) -- Improved division logic of validation rules. [\#1220](https://github.com/codeigniter4/CodeIgniter4/pull/1220) ([ytetsuro](https://github.com/ytetsuro)) -- Docs/contributing [\#1218](https://github.com/codeigniter4/CodeIgniter4/pull/1218) ([jim-parry](https://github.com/jim-parry)) -- Niggly fixes [\#1216](https://github.com/codeigniter4/CodeIgniter4/pull/1216) ([jim-parry](https://github.com/jim-parry)) -- Autodiscovery [\#1215](https://github.com/codeigniter4/CodeIgniter4/pull/1215) ([lonnieezell](https://github.com/lonnieezell)) -- Fix warnings in welcome\_message.php [\#1211](https://github.com/codeigniter4/CodeIgniter4/pull/1211) ([puschie286](https://github.com/puschie286)) -- Correct helper tests namespace [\#1207](https://github.com/codeigniter4/CodeIgniter4/pull/1207) ([jim-parry](https://github.com/jim-parry)) -- Validation Class - corresponding about the escaped separator. [\#1203](https://github.com/codeigniter4/CodeIgniter4/pull/1203) ([ytetsuro](https://github.com/ytetsuro)) -- Fixes FileRules::max\_size\(\) to use file-\>getSize\(\) instead of number\_formatted size [\#1199](https://github.com/codeigniter4/CodeIgniter4/pull/1199) ([samsonasik](https://github.com/samsonasik)) -- use Validation-\>getErrors\(\) call instead of Valdation::errors to handle errors that came from session [\#1197](https://github.com/codeigniter4/CodeIgniter4/pull/1197) ([samsonasik](https://github.com/samsonasik)) -- allows to get table and primary key name out of the model [\#1196](https://github.com/codeigniter4/CodeIgniter4/pull/1196) ([nowackipawel](https://github.com/nowackipawel)) -- pagination - optional page number [\#1195](https://github.com/codeigniter4/CodeIgniter4/pull/1195) ([nowackipawel](https://github.com/nowackipawel)) -- add writable/session directory and set default App::sessionSavePath to it [\#1194](https://github.com/codeigniter4/CodeIgniter4/pull/1194) ([samsonasik](https://github.com/samsonasik)) -- Fix travis error build on Router and RouteCollection [\#1192](https://github.com/codeigniter4/CodeIgniter4/pull/1192) ([samsonasik](https://github.com/samsonasik)) -- add bool type hint for getShared parameter [\#1191](https://github.com/codeigniter4/CodeIgniter4/pull/1191) ([samsonasik](https://github.com/samsonasik)) -- Catch Email Exceptions [\#1190](https://github.com/codeigniter4/CodeIgniter4/pull/1190) ([puschie286](https://github.com/puschie286)) -- $myModel-\>find\(string "value-of-my-primary-key"\) \#1188 [\#1189](https://github.com/codeigniter4/CodeIgniter4/pull/1189) ([nowackipawel](https://github.com/nowackipawel)) -- Adds valid\_json which is using json\_last\_error\(\) === JSON\_ERROR\_NONE [\#1187](https://github.com/codeigniter4/CodeIgniter4/pull/1187) ([nowackipawel](https://github.com/nowackipawel)) -- remove start\(\) call on session\(\) function call at Validation [\#1185](https://github.com/codeigniter4/CodeIgniter4/pull/1185) ([samsonasik](https://github.com/samsonasik)) -- remove unused salt in Model [\#1184](https://github.com/codeigniter4/CodeIgniter4/pull/1184) ([samsonasik](https://github.com/samsonasik)) -- log file:failed to delete buffer. No buffer to delete. [\#1182](https://github.com/codeigniter4/CodeIgniter4/pull/1182) ([bangbangda](https://github.com/bangbangda)) -- use string type hint in $file parameter in DotEnv::\_\_construct\(\) [\#1181](https://github.com/codeigniter4/CodeIgniter4/pull/1181) ([samsonasik](https://github.com/samsonasik)) -- Adjust log level to match RFC 5424 [\#1178](https://github.com/codeigniter4/CodeIgniter4/pull/1178) ([sugenganthos](https://github.com/sugenganthos)) -- Update Response.php [\#1173](https://github.com/codeigniter4/CodeIgniter4/pull/1173) ([sugenganthos](https://github.com/sugenganthos)) -- comparison fix at CIDatabaseTestCase [\#1172](https://github.com/codeigniter4/CodeIgniter4/pull/1172) ([samsonasik](https://github.com/samsonasik)) -- remove if \($template==forums/categories\) check in View/Parser [\#1171](https://github.com/codeigniter4/CodeIgniter4/pull/1171) ([samsonasik](https://github.com/samsonasik)) -- show PageNotFoundException message [\#1168](https://github.com/codeigniter4/CodeIgniter4/pull/1168) ([puschie286](https://github.com/puschie286)) -- make name parameter in HTTP\Header mandatory [\#1164](https://github.com/codeigniter4/CodeIgniter4/pull/1164) ([samsonasik](https://github.com/samsonasik)) -- Fixed insufficient validation of parameters related to pager. [\#1162](https://github.com/codeigniter4/CodeIgniter4/pull/1162) ([ytetsuro](https://github.com/ytetsuro)) -- remove unneeded helper\('url'\) call in plugins and form helper as already called in bootstrap file [\#1160](https://github.com/codeigniter4/CodeIgniter4/pull/1160) ([samsonasik](https://github.com/samsonasik)) -- reduce str\_replace in View/Parser::render\(\) [\#1159](https://github.com/codeigniter4/CodeIgniter4/pull/1159) ([samsonasik](https://github.com/samsonasik)) -- add missing string parameter type hint in Autoload methods [\#1158](https://github.com/codeigniter4/CodeIgniter4/pull/1158) ([samsonasik](https://github.com/samsonasik)) -- remove unneeded isset\($options\[hostname\]\) when next check is !empty\($options\[hostname\]\) [\#1157](https://github.com/codeigniter4/CodeIgniter4/pull/1157) ([samsonasik](https://github.com/samsonasik)) -- remove commented and never used methods in View\Parser class [\#1156](https://github.com/codeigniter4/CodeIgniter4/pull/1156) ([samsonasik](https://github.com/samsonasik)) -- Fixes \#1135 : Toolbar oldest file delete and show maximum in the list as App::toolbarMaxHistory [\#1155](https://github.com/codeigniter4/CodeIgniter4/pull/1155) ([samsonasik](https://github.com/samsonasik)) -- add ext-intl to require at composer.json [\#1153](https://github.com/codeigniter4/CodeIgniter4/pull/1153) ([samsonasik](https://github.com/samsonasik)) -- remove unneeded \(string\) cast as type hinted [\#1152](https://github.com/codeigniter4/CodeIgniter4/pull/1152) ([samsonasik](https://github.com/samsonasik)) -- implements session-\>push\(\) [\#1151](https://github.com/codeigniter4/CodeIgniter4/pull/1151) ([samsonasik](https://github.com/samsonasik)) -- SplFileInfo type case [\#1150](https://github.com/codeigniter4/CodeIgniter4/pull/1150) ([samsonasik](https://github.com/samsonasik)) -- Update views.rst [\#1149](https://github.com/codeigniter4/CodeIgniter4/pull/1149) ([sugenganthos](https://github.com/sugenganthos)) -- remove unneeded @todo for Time::setMonth\(\) to check max [\#1148](https://github.com/codeigniter4/CodeIgniter4/pull/1148) ([samsonasik](https://github.com/samsonasik)) -- fix RedirectResponse::route and added test [\#1147](https://github.com/codeigniter4/CodeIgniter4/pull/1147) ([puschie286](https://github.com/puschie286)) -- Fix uri detection with no index in uri [\#1146](https://github.com/codeigniter4/CodeIgniter4/pull/1146) ([puschie286](https://github.com/puschie286)) -- remove unneeded isset\($\_SESSION\) when next check is !empty\($\_SESSION\) [\#1145](https://github.com/codeigniter4/CodeIgniter4/pull/1145) ([samsonasik](https://github.com/samsonasik)) -- Add a namespace to the Pages.php [\#1143](https://github.com/codeigniter4/CodeIgniter4/pull/1143) ([fdjkgh580](https://github.com/fdjkgh580)) -- Add a namespace to the controller. [\#1142](https://github.com/codeigniter4/CodeIgniter4/pull/1142) ([fdjkgh580](https://github.com/fdjkgh580)) -- remove unused use statements [\#1141](https://github.com/codeigniter4/CodeIgniter4/pull/1141) ([samsonasik](https://github.com/samsonasik)) -- implements @todo max day in current month at Time::setDay\(\) [\#1140](https://github.com/codeigniter4/CodeIgniter4/pull/1140) ([samsonasik](https://github.com/samsonasik)) -- remove unneeded \(int\) casting as use int type hint or certainly an int [\#1138](https://github.com/codeigniter4/CodeIgniter4/pull/1138) ([samsonasik](https://github.com/samsonasik)) -- Update html\_helper.php [\#1133](https://github.com/codeigniter4/CodeIgniter4/pull/1133) ([WaldemarStanislawski](https://github.com/WaldemarStanislawski)) -- update to latest php-coveralls [\#1131](https://github.com/codeigniter4/CodeIgniter4/pull/1131) ([samsonasik](https://github.com/samsonasik)) -- Update View.php [\#1130](https://github.com/codeigniter4/CodeIgniter4/pull/1130) ([sugenganthos](https://github.com/sugenganthos)) -- Fix debugbar loading while csp is enabled [\#1129](https://github.com/codeigniter4/CodeIgniter4/pull/1129) ([puschie286](https://github.com/puschie286)) -- Run session tests in separate processes - fix for \#1106 [\#1128](https://github.com/codeigniter4/CodeIgniter4/pull/1128) ([andreif23](https://github.com/andreif23)) -- Feature/sqlite [\#793](https://github.com/codeigniter4/CodeIgniter4/pull/793) ([lonnieezell](https://github.com/lonnieezell)) - - - -\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* +## [v4.7.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.4) (2026-07-07) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.3...v4.7.4) + +### Security + +* **IncomingRequest:** *HTTPS detection via client-supplied headers* was fixed. + ``IncomingRequest::isSecure()`` now trusts the ``X-Forwarded-Proto`` and + ``Front-End-Https`` headers only when the request comes from a trusted proxy + configured in ``Config\App::$proxyIPs``. + See the `Security advisory GHSA-7wmf-pw8j-mc78 `_ + for more information. + +* **Query Builder:** Fixed a SQL injection vulnerability in ``deleteBatch()``. + When ``deleteBatch()`` was used together with ``where()`` conditions, the + bound values from the WHERE clause were substituted into the generated SQL + with their escape flag ignored, so they were never escaped or quoted. The + WHERE binds are now escaped in the same way as a regular ``delete()``. + + See the `Security advisory GHSA-c9w5-rwh3-7pm9 `_ + for more information. + +* **UploadedFile:** ``UploadedFile::move()`` now sanitizes the client-provided + filename when called without a second argument. Previously, the unsanitized + client filename was used as the default, allowing path traversal sequences + (e.g. ``../../public/shell.php``) to write the uploaded file outside the + intended directory. A name explicitly passed as the second argument is not + sanitized and remains the caller's responsibility. + See the `Security advisory GHSA-hhmc-q9hp-r662 `_ + for more information. + +* **Validation:** The ``is_image`` and ``mime_in`` file upload validation rules + now also verify non-empty client filename extensions. Previously, these rules + classified an upload solely by its content-derived MIME type, so a file with a + dangerous client extension (for example, a ``.php`` file prepended with image + magic bytes) could pass validation while keeping its original extension on + disk. + See the `Security advisory GHSA-mmj4-63m4-r6h5 `_ + for more information. + + The ``is_image`` rule now rejects uploads when a non-empty client filename + extension is not an image extension. The ``mime_in`` rule now rejects uploads + when a non-empty client filename extension does not match the detected file + content, matching the same extension/content agreement used by ``ext_in``. + Files uploaded without any extension (such as JavaScript ``Blob`` uploads) + are still accepted, since the rules validate the file content. + +### Fixed Bugs + +* fix: prevent updateBatch with existing where conditions by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10236 +* fix: detect Safari version from Version token by @memleakd in https://github.com/codeigniter4/CodeIgniter4/pull/10251 +* fix: nested transformer request scope leakage by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10281 +* fix(model): pass $recursive parameter to parent in objectToRawArray by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10258 +* fix: preserve enclosing stream filter when using `MockInputOutput` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10307 +* fix: skip already-translated keys in `spark lang:find` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10308 +* fix(Filters): check both keys and values in `InvalidChars` arrays by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10303 +* fix: use dynamic lockMaxRetries limit in RedisHandler by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10295 +* fix: preserve sub-namespace when generating Entity from Model (i.e., `--return entity`) by @xgrind in https://github.com/codeigniter4/CodeIgniter4/pull/10232 +* fix: `env()` TypeError for non-string `$_SERVER` values + `esc()` fixes by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10305 +* fix: unify casing in BaseService::injectMock by @ThomasMeschke in https://github.com/codeigniter4/CodeIgniter4/pull/10316 +* fix: normalize SodiumHandler params and padding failures by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10321 +* fix(Validation): correct required_without logic and prevent array key warnings by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10328 +* fix: handle null ini values in phpini check by @Will-thom in https://github.com/codeigniter4/CodeIgniter4/pull/10233 +* fix: preserve zero values in XML export by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10367 +* fix: support array indexes in `getPostGet()` and `getGetPost()` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10362 + +### Refactoring + +* refactor: narrow `Image` original dimension types to `int` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10326 +* refactor(HTTP): optimize file path parsing in `download()` method by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10330 +* refactor(database): optimize groupGetType by caching it inside BaseBuilder loops by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10340 +* refactor: bump to phpstan-codeigniter v2.1 by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10312 +* refactor: replace type `mixed` with more specific types by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10345 +* refactor: optimize `prepQuotedPrintable()` with hash lookup and int-length tracking by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10344 +* refactor: add precise `array` phpdocs for `CLI` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10354 +* refactor(database): optimize `_processForeignKeys()` string allocations and loop invariants by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10351 + +## [v4.7.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.3) (2026-05-22) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.2...v4.7.3) + +### Security + +* **Validation**: *Uploaded file extension validation bypass in `ext_in` rule* + The ``ext_in`` file upload validation rule now validates the client filename extension and verifies that it + matches the detected MIME type. Previously, ``ext_in`` only checked the MIME-derived guessed extension, so + a file with a mismatched client extension could pass validation. + + See the [GHSA-2gr4-ppc7-7mhx security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-2gr4-ppc7-7mhx) for more information. Credits to @z3moo and @teebow1e for reporting the issue. + +### Fixed Bugs + +* fix: make Autoloader composer path injectable to fix parallel test race condition by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10082 +* fix: store SPL closures in `register()` so `unregister()` can remove them by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10097 +* fix: ensure output buffer is closed after use of `command()` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10099 +* fix: preserve null values in Validation::getValidated() by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10101 +* fix: refactor inconsistent behavior on `CLI::write()` and `CLI::error()` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10106 +* fix: ensure calling `env` command with options only would not throw by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10114 +* fix: suppress stty stderr leak in `CLI::generateDimensions()` when stdin is not a TTY by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10124 +* fix: reset Kint CSP state in worker mode by @memleakd in https://github.com/codeigniter4/CodeIgniter4/pull/10139 +* fix: make `Time::createFromTimestamp` locale-independent by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10151 +* fix: SQLSRV driver's `decrement()` method by @patel-vansh in https://github.com/codeigniter4/CodeIgniter4/pull/10155 +* fix: suppress tput stderr leak when TERM is not present by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10167 +* fix: support third-party loggers in toolbar logs collector by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10173 +* fix: PostgreSQL Builder's `increment()` and `decrement()` methods not working for numeric columns by @patel-vansh in https://github.com/codeigniter4/CodeIgniter4/pull/10172 +* fix: preserve cached table list shape by @memleakd in https://github.com/codeigniter4/CodeIgniter4/pull/10179 +* fix: harden regex matching on `key:generate` command by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10183 +* fix: restore deep dot-notation traversal in `Language::getLine()` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10189 +* fix: make frankenphp-worker.php template idempotent on watcher restart by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10191 +* fix: `Entity::normalizeValue()` must handle `UnitEnum` before `toArray()` by @maniaba in https://github.com/codeigniter4/CodeIgniter4/pull/10137 +* fix: recognize off zlib output compression value by @memleakd in https://github.com/codeigniter4/CodeIgniter4/pull/10193 +* fix: escape `--host` option in `serve` command by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10203 + +### Refactoring + +* refactor: add full testing for `logs:clear` command by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10090 +* refactor: add full testing for `debugbar:clear` command by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10093 +* refactor: pass `--do-not-cache-result` to prevent shared cache corruption by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10098 +* refactor: add full testing for `cache:clear` command by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10094 +* refactor: rename `-h` option of `routes` command as `--handler` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10113 +* refactor: further rename `--handler` to `--sort-by-handler` for `routes` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10125 +* refactor: UX: `ClearLogs::execute()` error message is misleading after interactive `'n'` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10126 +* refactor: simplify `FileLocator::listFiles()` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10142 +* refactor: reduce PHPStan child return type baseline by @memleakd in https://github.com/codeigniter4/CodeIgniter4/pull/10165 +* refactor: remove PHPStan callable signature baseline by @memleakd in https://github.com/codeigniter4/CodeIgniter4/pull/10166 + +## [v4.7.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.2) (2026-03-24) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.1...v4.7.2) + +### Fixed Bugs + +* fix: preserve JSON body when CSRF token is sent in header by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10064 + +## [v4.7.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.1) (2026-03-22) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.0...v4.7.1) + +### Breaking Changes + +* fix: SQLite3 config type handling for `.env` overrides by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10037 + +### Fixed Bugs + +* fix: escape CSP nonce attributes in JSON responses by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9938 +* fix: correct `savePath` check in `MemcachedHandler` constructor by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9941 +* fix: preserve index field in `updateBatch()` when `updateOnlyChanged` is `true` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9944 +* fix: Hardcoded CSP Nonce Tags in ResponseTrait by @patel-vansh in https://github.com/codeigniter4/CodeIgniter4/pull/9937 +* fix: initialize standalone toolbar by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9950 +* fix: add fallback for `appOverridesFolder` config in View by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9958 +* fix: avoid double-prefixing in `BaseConnection::callFunction()` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9959 +* fix: generate inputs for all route params in Debug Toolbar by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9964 +* fix: preserve Postgre casts when converting named placeholders in prepared queries by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9960 +* fix: prevent extra query and invalid size in `Model::chunk()` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9961 +* fix: worker mode events cleanup by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9997 +* fix: add nonce to script-src-elem and style-src-elem when configured by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9999 +* fix: `FeatureTestTrait::withRoutes()` may throw all sorts of errors on invalid HTTP methods by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10004 +* fix: validation when key does not exists by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10006 +* fix: handle HTTP/2 responses without a reason phrase in CURLRequest by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10050 + +### Refactoring + +* chore: signature for the `$headers` param in `FeatureTestTrait::withHeaders()` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9932 +* refactor: implement development versions for `CodeIgniter::CI_VERSION` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9951 +* feat: Add `builds next` option by @neznaika0 in https://github.com/codeigniter4/CodeIgniter4/pull/9946 +* refactor: use `__unserialize` instead of `__wakeup` in `TimeTrait` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9957 +* refactor: remove `Exceptions::isImplicitNullableDeprecationError` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9965 +* refactor: fix `Security` test fail by itself by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9969 +* refactor: make random-order API tests deterministic by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9983 +* refactor: make random-order CLI tests deterministic by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9998 +* refactor: fix phpstan no type specified ValidationModelTest by @adiprsa in https://github.com/codeigniter4/CodeIgniter4/pull/10008 +* refactor: fix dependency on test execution order by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10014 +* refactor: update tests with old entities definition by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10026 + +## [v4.7.0](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.0) (2026-02-01) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.6.5...v4.7.0) + +### Breaking Changes + +* feat: require double curly braces for placeholders in `regex_match` rule by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9597 +* feat(cache): add `deleteMatching` method definition in CacheInterface by @yassinedoghri in https://github.com/codeigniter4/CodeIgniter4/pull/9809 +* feat(cache): add native types to all CacheInterface methods by @yassinedoghri in https://github.com/codeigniter4/CodeIgniter4/pull/9811 +* feat(entity): deep change tracking for objects and arrays by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9779 +* feat(model): primary key validation by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9840 +* feat(entity): properly convert arrays of entities in `toRawArray()` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9841 +* feat: add configurable status code filtering for `PageCache` filter by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9856 +* fix: inconsistent `key` handling in encryption by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9868 +* refactor: complete `QueryInterface` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9892 +* feat: add `remember()` to `CacheInterface` by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/9875 +* refactor: Use native return types instead of using `#[ReturnTypeWillChange]` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9900 + +### Fixed Bugs + +* fix: ucfirst all cookie samesite values by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9564 +* fix: controller attribute filters with parameters by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9769 +* fix: Fixed test Transformers by @neznaika0 in https://github.com/codeigniter4/CodeIgniter4/pull/9778 +* fix: signal trait by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9846 + +### New Features + +* feat: signals by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9690 +* feat(app): Added controller attributes by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/9745 +* feat: API transformers by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/9763 +* feat: FrankenPHP Worker Mode by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9889 + +### Enhancements + +* feat: add email/smtp plain auth method by @ip-qi in https://github.com/codeigniter4/CodeIgniter4/pull/9462 +* feat: rewrite `ImageMagickHandler` to rely solely on the PHP `imagick` extension by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9526 +* feat: add `Time::addCalendarMonths()` and `Time::subCalendarMonths()` methods by @christianberkman in https://github.com/codeigniter4/CodeIgniter4/pull/9528 +* feat: add `clearMetadata()` method to provide privacy options when using imagick handler by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9538 +* feat: add `dns_cache_timeout` for option `CURLRequest` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9553 +* feat: added `fresh_connect` options to `CURLRequest` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9559 +* feat: update `CookieInterface::EXPIRES_FORMAT` to use date format per RFC 7231 by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9563 +* feat: share connection & DNS Cache to `CURLRequest` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9557 +* feat: add option to change default behaviour of `JSONFormatter` max depth by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9585 +* feat: customizable `.env` directory path by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/9631 +* feat: migrations lock by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9660 +* feat: uniform rendering of stack trace from failed DB operations by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9677 +* feat: make `insertBatch()` and `updateBatch()` respect model rules by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9708 +* feat: add enum casting by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9752 +* feat(app): Added pagination response to API ResponseTrait by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/9758 +* feat: update robots definition for `UserAgent` class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9782 +* feat: added `async` & `persistent` options to Cache Redis by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9792 +* feat: Add support for HTTP status in `ResponseCache` by @sk757a in https://github.com/codeigniter4/CodeIgniter4/pull/9855 +* feat: prevent `Maximum call stack size exceeded` on client-managed requests by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/9852 +* feat: add `isPast()` and `isFuture()` time convenience methods by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/9861 +* feat: allow overriding namespaced views via `app/Views` directory by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/9860 +* feat: make DebugToolbar smarter about detecting binary/streamed responses by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/9862 +* feat: complete `Superglobals` implementation by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9858 +* feat: encryption key rotation by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9870 +* feat: APCu caching driver by @sk757a in https://github.com/codeigniter4/CodeIgniter4/pull/9874 +* feat: added ``persistent`` config item to redis handler `Session` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9793 +* feat: Add CSP3 `script-src-elem` directive by @mark-unwin in https://github.com/codeigniter4/CodeIgniter4/pull/9722 +* feat: Add support for CSP3 keyword-sources by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9906 +* feat: enclose hash-based CSP directive values in single quotes by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9908 +* feat: add support for more CSP3 directives by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9909 +* feat: add support for CSP3 `report-to` directive by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9910 + +### Refactoring + +* refactor: cleanup code in `Email` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9570 +* refactor: remove deprecated types in random_string() helper by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9592 +* refactor: do not use future-deprecated `DATE_RFC7231` constant by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9657 +* refactor: remove `curl_close` has no effect since PHP 8.0 by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9683 +* refactor: remove `finfo_close` has no effect since PHP 8.1 by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9684 +* refactor: remove `imagedestroy` has no effect since PHP 8.0 by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9688 +* refactor: deprecated PHP 8.5 constant `FILTER_DEFAULT` for `filter_*()` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9699 +* chore: bump minimum required `PHP 8.2` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/9701 +* refactor: add the `SensitiveParameter` attribute to methods dealing with sensitive info by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9710 +* fix: Remove check ext-json by @neznaika0 in https://github.com/codeigniter4/CodeIgniter4/pull/9713 +* refactor(app): Standardize subdomain detection logic by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/9751 +* refactor: Types for `BaseModel`, `Model` and dependencies by @neznaika0 in https://github.com/codeigniter4/CodeIgniter4/pull/9830 +* chore: remove IncomingRequest deprecations by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/9851 +* refactor: Session library by @neznaika0 in https://github.com/codeigniter4/CodeIgniter4/pull/9831 +* refactor: Superglobals - remove property promotion and fix PHPDocs by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9871 +* refactor: Rework `Entity` class by @neznaika0 in https://github.com/codeigniter4/CodeIgniter4/pull/9878 +* refactor: compare `$db->connID` to `false` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9891 +* refactor: cleanup `ContentSecurityPolicy` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9904 +* refactor: deprecate `CodeIgniter\HTTP\ContentSecurityPolicy::$nonces` since never used by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9905 + +For the changelog of v4.6, see [CHANGELOG_4.6.md](./changelogs/CHANGELOG_4.6.md).
+For the changelog of v4.5, see [CHANGELOG_4.5.md](./changelogs/CHANGELOG_4.5.md).
+For the changelog of v4.4, see [CHANGELOG_4.4.md](./changelogs/CHANGELOG_4.4.md).
+For the changelog of v4.3, see [CHANGELOG_4.3.md](./changelogs/CHANGELOG_4.3.md).
+For the changelog of v4.2, see [CHANGELOG_4.2.md](./changelogs/CHANGELOG_4.2.md).
+For the changelog of v4.1, see [CHANGELOG_4.1.md](./changelogs/CHANGELOG_4.1.md).
+For the changelog of v4.0, see [CHANGELOG_4.0.md](./changelogs/CHANGELOG_4.0.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18abe9ce7468..3688517a265a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,150 +1,6 @@ # Contributing to CodeIgniter4 -## Contributions +CodeIgniter is a community driven project and accepts contributions of +code and documentation from the community. -We expect all contributions to conform to our -[style guide](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/styleguide.md), -be commented (inside the PHP source files), be documented (in the -[user guide](https://codeigniter4.github.io/userguide/)), and unit tested (in -the [test folder](https://github.com/codeigniter4/CodeIgniter4/tree/develop/tests)). -There is a [Contributing to CodeIgniter](./contributing/README.md) section in the repository which describes the contribution process; this page is an overview. - -Note, we expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite -to prove the code works. If pull requests are not accompanied by relevant tests, they will likely be closed. -Since we are a team of volunteers, we don't have any more time to work on the framework than you do. Please -make it as painless for your contributions to be included as possible. If you need help with getting tests -running on your local machines, ask for help on the forums. We would be happy to help out. - -The [Open Source Guide](https://opensource.guide/) is a good first read for those new to contributing to open source! -## Issues - -Issues are a quick way to point out a bug. If you find a bug or documentation error in CodeIgniter then please make sure that: - -1. There is not already an open [Issue](https://github.com/codeigniter4/CodeIgniter4/issues) -2. The Issue has not already been fixed (check the develop branch or look for [closed Issues](https://github.com/codeigniter4/CodeIgniter4/issues?q=is%3Aissue+is%3Aclosed)) -3. It's not something really obvious that you can fix yourself - -Reporting Issues is helpful, but an even [better approach](./contributing/workflow.md) is to send a -[Pull Request](https://help.github.com/en/articles/creating-a-pull-request), which is done by -[Forking](https://help.github.com/en/articles/fork-a-repo) the main repository and making -a [Commit](https://help.github.com/en/desktop/contributing-to-projects/committing-and-reviewing-changes-to-your-project) -to your own copy of the project. This will require you to use the version control system called [Git](https://git-scm.com/). - -## Guidelines - -Before we look into how to contribute to CodeIgniter4, here are some guidelines. If your Pull Requests fail -to pass these guidelines, they will be declined, and you will need to re-submit when you’ve made the changes. -This might sound a bit tough, but it is required for us to maintain the quality of the codebase. - -### PHP Style - -All code must meet the [Style Guide](./contributing/styleguide.md). -This makes certain that all submitted code is of the same format as the existing code and ensures that the codebase will be as readable as possible. - -### Documentation - -If you change anything that requires a change to documentation, then you will need to add to the documentation. New classes, methods, parameters, changing default values, etc. are all changes that require a change to documentation. Also, the [changelog](https://codeigniter4.github.io/CodeIgniter4/changelogs/index.html) must be updated for every change, and [PHPDoc](https://github.com/codeigniter4/CodeIgniter4/blob/develop/phpdoc.dist.xml) blocks must be maintained. - -### Compatibility - -CodeIgniter4 requires [PHP 7.3](https://php.net/releases/7_3_0.php). - -### Branching - -CodeIgniter4 uses the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/) branching model -which requires all Pull Requests to be sent to the __"develop"__ branch; this is where the next planned version will be developed. - -The __"master"__ branch will always contain the latest stable version and is kept clean so a "hotfix" (e.g. an -emergency security patch) can be applied to the "master" branch to create a new version, without worrying -about other features holding it up. For this reason, all commits need to be made to the "develop" branch, -and any sent to the "master" branch will be closed automatically. If you have multiple changes to submit, -please place all changes into their own branch on your fork. - -**One thing at a time:** A pull request should only contain one change. That does not mean only one commit, -but one change - however many commits it took. The reason for this is that if you change X and Y, -but send a pull request for both at the same time, we might really want X but disagree with Y, -meaning we cannot merge the request. Using the Git-Flow branching model you can create new -branches for both of these features and send two requests. - -A reminder: **please use separate branches for each of your PRs** - it will make it easier for you to keep -changes separate from each other and from whatever else you are doing with your repository! - -### Signing - -You must [GPG-sign](./contributing/signing.md) your work, certifying that you either wrote the work or -otherwise have the right to pass it on to an open-source project. This is *not* just a "signed-off-by" -commit, but instead, a digitally signed one. - -### Static Analysis on PHP code - -We cannot, at all times, guarantee that all PHP code submitted on pull requests to be working well without -actually running the code. For this reason, we make use of two static analysis tools, [PHPStan][1] -and [Rector][2] to do the analysis for us. - -These tools have already been integrated into our CI/CD workflow to minimize unannounced bugs. Pull requests -are expected that their code will pass these two. In your local machine, you can manually run these tools -so that you can fix whatever errors that pop up with your submission. - -PHPStan is expected to scan the entire framework by running this command in your terminal: - - vendor/bin/phpstan analyse - -Rector, on the other hand, can be run on the specific files you modified or added: - - vendor/bin/rector process --dry-run path/to/file - -[1]: https://github.com/phpstan/phpstan-src -[2]: https://github.com/rector/rector - -### Breaking Changes - -In general, any change that would disrupt existing uses of the framework is considered a "breaking change" and will not be favorably considered. A few specific examples to pay attention to: - -1. New classes/properties/constants in `system` are acceptable, but anything in the `app` directory that will be used in `system` should be backwards-compatible. -2. Any changes to non-private methods must be backwards-compatible with the original definition. -3. Deleting non-private properties or methods without prior deprecation notices is frowned upon and will likely be closed. -4. Deleting or renaming public classes and interfaces, as well as those not marked as `@internal`, without prior deprecation notices or not providing fallback solutions will also not be favorably considered. - -## How-to Guide - -The best way to contribute is to fork the CodeIgniter4 repository, and "clone" that to your development area. That sounds like some jargon, but "forking" on GitHub means "making a copy of that repo to your account" and "cloning" means "copying that code to your environment so you can work on it". - -1. Set up Git ([Windows](https://git-scm.com/download/win), [Mac](https://git-scm.com/download/mac), & [Linux](https://git-scm.com/download/linux)). -2. Go to the [CodeIgniter4 repository](https://github.com/codeigniter4/CodeIgniter4). -3. [Fork](https://help.github.com/en/articles/fork-a-repo) it (to your Github account). -4. [Clone](https://help.github.com/en/articles/cloning-a-repository) your CodeIgniter repository: `git@github.com:\/CodeIgniter4.git` -5. Create a new [branch](https://help.github.com/en/articles/about-branches) in your project for each set of changes you want to make. -6. Fix existing bugs on the [Issue tracker](https://github.com/codeigniter4/CodeIgniter4/issues) after confirming that no one else is working on them. -7. [Commit](https://help.github.com/en/desktop/contributing-to-projects/committing-and-reviewing-changes-to-your-project) the changed files in your contribution branch. -8. Commit messages are expected to be descriptive of what you changed specifically. Commit messages like -"Fixes #1234" would be asked by the reviewer to be revised. -9. If there are intermediate commits that are not meaningful to the overall PR, such as "Fixed error on style guide", "Fixed phpstan error", "Fixing mistake in code", and other related commits, it is advised to squash your commits so that we can have a clean commit history. -10. If you have touched PHP code, run static analysis. -11. Run unit tests on the specific file you modified. If there are no existing tests yet, please create one. -12. Make sure the tests pass to have a higher chance of merging. -13. [Push](https://docs.github.com/en/github/using-git/pushing-commits-to-a-remote-repository) your contribution branch to your fork. -14. Send a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork). - -The codebase maintainers will now be alerted to the submission and someone from the team will respond. If your change fails to meet the guidelines, it will be rejected or feedback will be provided to help you improve it. - -Once the maintainer handling your pull request is satisfied with it they will approve the pull request and merge it into the "develop" branch. Your patch will now be part of the next release! - -### Keeping your fork up-to-date - -Unlike systems like Subversion, Git can have multiple remotes. A remote is the name for the URL of a Git repository. By default, your fork will have a remote named "origin", which points to your fork, but you can add another remote named "codeigniter", which points to `git://github.com/codeigniter4/CodeIgniter4.git`. This is a read-only remote, but you can pull from this develop branch to update your own. - -If you are using the command-line, you can do the following to update your fork to the latest changes: - -1. `git remote add codeigniter git://github.com/codeigniter4/CodeIgniter4.git` -2. `git pull codeigniter develop` -3. `git push origin develop` - -Your fork is now up to date. This should be done regularly and, at the least, before you submit a pull request. - -## Translations Installation - -If you wish to contribute to the system message translations, -then fork and clone the [translations repository](https://github.com/codeigniter4/translations) -separately from the codebase. - -These are two independent repositories! +If you'd like to contribute, please read the [Contributing to CodeIgniter](./contributing/README.md). diff --git a/DCO.txt b/DCO.txt deleted file mode 100644 index a404c0d38b0d..000000000000 --- a/DCO.txt +++ /dev/null @@ -1,25 +0,0 @@ -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(1) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(2) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(3) The contribution was provided directly to me by some other - person who certified (1), (2) or (3) and I have not modified - it. - -(4) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. diff --git a/LICENSE b/LICENSE index daac9e5bb4f5..24728f607f02 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The MIT License (MIT) Copyright (c) 2014-2019 British Columbia Institute of Technology -Copyright (c) 2019-2021 CodeIgniter Foundation +Copyright (c) 2019-present CodeIgniter Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index db5aeb9eca37..000000000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,22 +0,0 @@ -Each pull request should address a single issue and have a meaningful title. - -**Description** -Explain what you have changed, and why. - -**Checklist:** -- [ ] Securely signed commits -- [ ] Component(s) with PHPdocs -- [ ] Unit testing, with >80% coverage -- [ ] User guide updated -- [ ] Conforms to style guide - ----------Remove from here down in your description---------- - -**Notes** -- Pull requests must be in English -- If the PR solves an issue, reference it with a suitable verb and the issue number -(e.g. fixes 12345) -- Unsolicited pull requests will be considered, but there is no guarantee of acceptance -- Pull requests should be from a feature branch in the contributor's fork of the repository - to the develop branch of the project repository - diff --git a/README.md b/README.md index 5d9d007b91e2..6a4e1025a8f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # CodeIgniter 4 Development -[![Build Status](https://github.com/codeigniter4/CodeIgniter4/workflows/PHPUnit/badge.svg)](https://github.com/codeigniter4/CodeIgniter4/actions?query=workflow%3A%22PHPUnit%22) +[![PHPUnit](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/test-phpunit.yml/badge.svg)](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/test-phpunit.yml) +[![PHPStan](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/test-phpstan.yml/badge.svg)](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/test-phpstan.yml) +[![Psalm](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/test-psalm.yml/badge.svg)](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/test-psalm.yml) [![Coverage Status](https://coveralls.io/repos/github/codeigniter4/CodeIgniter4/badge.svg?branch=develop)](https://coveralls.io/github/codeigniter4/CodeIgniter4?branch=develop) [![Downloads](https://poser.pugx.org/codeigniter4/framework/downloads)](https://packagist.org/packages/codeigniter4/framework) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/codeigniter4/CodeIgniter4)](https://packagist.org/packages/codeigniter4/framework) @@ -12,26 +14,26 @@ ## What is CodeIgniter? CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure. -More information can be found at the [official site](http://codeigniter.com). +More information can be found at the [official site](https://codeigniter.com). This repository holds the source code for CodeIgniter 4 only. Version 4 is a complete rewrite to bring the quality and the code into a more modern version, while still keeping as many of the things intact that has made people love the framework over the years. -More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. +More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums. ### Documentation -The [User Guide](https://codeigniter4.github.io/userguide/) is the primary documentation for CodeIgniter 4. +The [User Guide](https://codeigniter.com/user_guide/) is the primary documentation for CodeIgniter 4. -The current **in-progress** User Guide can be found [here](https://codeigniter4.github.io/CodeIgniter4/). +You will also find the [current **in-progress** User Guide](https://codeigniter4.github.io/CodeIgniter4/). As with the rest of the framework, it is a work in progress, and will see changes over time to structure, explanations, etc. You might also be interested in the [API documentation](https://codeigniter4.github.io/api/) for the framework components. ## Important Change with index.php -index.php is no longer in the root of the project! It has been moved inside the *public* folder, +`index.php` is no longer in the root of the project! It has been moved inside the *public* folder, for better security and separation of components. This means that you should configure your web server to "point" to your project's *public* folder, and @@ -46,15 +48,16 @@ CodeIgniter is developed completely on a volunteer basis. As such, please give u for your issues to be reviewed. If you haven't heard from one of the team in that time period, feel free to leave a comment on the issue so that it gets brought back to our attention. -We use Github issues to track **BUGS** and to track approved **DEVELOPMENT** work packages. -We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss -FEATURE REQUESTS. +> [!IMPORTANT] +> We use GitHub issues to track **BUGS** and to track approved **DEVELOPMENT** work packages. +> We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss +> FEATURE REQUESTS. If you raise an issue here that pertains to support or a feature request, it will be closed! If you are not sure if you have found a bug, raise a thread on the forum first - someone else may have encountered the same thing. -Before raising a new Github issue, please check that your bug hasn't already +Before raising a new GitHub issue, please check that your bug hasn't already been reported or fixed. We use pull requests (PRs) for CONTRIBUTIONS to the repository. @@ -69,30 +72,38 @@ to optional packages, with their own repository. ## Contributing -We **are** accepting contributions from the community! +We **are** accepting contributions from the community! It doesn't matter whether you can code, write documentation, or help find bugs, +all contributions are welcome. -We will try to manage the process somewhat, by adding a ["help wanted" label](https://github.com/codeigniter4/CodeIgniter4/labels/help%20wanted) to those that we are -specifically interested in at any point in time. Join the discussion for those issues and let us know -if you want to take the lead on one of them. +Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/README.md). -At this time, we are not looking for out-of-scope contributions, only those that would be considered part of our controlled evolution! +CodeIgniter has had thousands on contributions from people since its creation. This project would not be what it is without them. -Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/CodeIgniter4/blob/develop/CONTRIBUTING.md) section in the user guide. + + + -## Server Requirements +Made with [contrib.rocks](https://contrib.rocks). -PHP version 7.3 or higher is required, with the following extensions installed: +## Server Requirements +PHP version 8.2 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) -- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library - [mbstring](http://php.net/manual/en/mbstring.installation.php) +> [!WARNING] +> - The end of life date for PHP 7.4 was November 28, 2022. +> - The end of life date for PHP 8.0 was November 26, 2023. +> - The end of life date for PHP 8.1 was December 31, 2025. +> - If you are still using a PHP version below 8.2, you should upgrade immediately. +> - The end of life date for PHP 8.2 will be December 31, 2026. + Additionally, make sure that the following extensions are enabled in your PHP: - json (enabled by default - don't turn it off) -- xml (enabled by default - don't turn it off) -- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) +- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL +- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library ## Running CodeIgniter Tests diff --git a/SECURITY.md b/SECURITY.md index 7879188492b5..87b894ee713c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -20,6 +20,7 @@ This person will coordinate the fix and release process, involving the following - Confirm the problem and determine the affected versions. - Audit code to find any potential similar problems. - Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible. +- Publish security advisories at https://github.com/codeigniter4/CodeIgniter4/security/advisories ## Comments on this Policy diff --git a/Vagrantfile.dist b/Vagrantfile.dist deleted file mode 100644 index 857f92314a40..000000000000 --- a/Vagrantfile.dist +++ /dev/null @@ -1,203 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# https://github.com/hashicorp/vagrant/issues/9442#issuecomment-374785457 -unless Vagrant::DEFAULT_SERVER_URL.frozen? - Vagrant::DEFAULT_SERVER_URL.replace('https://vagrantcloud.com') -end - -Vagrant.configure("2") do |config| - # VM Box - config.vm.box = "ubuntu/bionic64" - # Automatic box update checking - config.vm.box_check_update = true - - # CodeIgniter virtual host - config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" - # Code Coverage virtual host - config.vm.network "forwarded_port", guest: 81, host: 8081, host_ip: "127.0.0.1" - # User Guide virtual host - config.vm.network "forwarded_port", guest: 82, host: 8082, host_ip: "127.0.0.1" - # MySQL server - #config.vm.network "forwarded_port", guest: 3306, host: 3307, host_ip: "127.0.0.1" - # PostgreSQL server - #config.vm.network "forwarded_port", guest: 5432, host: 5433, host_ip: "127.0.0.1" - # Memcached server - #config.vm.network "forwarded_port", guest: 11211, host: 11212, host_ip: "127.0.0.1" - # Redis server - #config.vm.network "forwarded_port", guest: 6379, host: 6380, host_ip: "127.0.0.1" - - # Add "192.168.10.10 ${VIRTUALHOST}" in your host file to access by domain - #config.vm.network "private_network", ip: "192.168.10.10" - - # Same path set in the $CODEIGNITER_PATH Provision - # "virtualbox" type allow auto-sync host to guest and guest to host - # but chmod does not work... tests will fail. - # Default rsync__args except "--copy-links", to allow phpunit correctly works by symlink - config.vm.synced_folder ".", "/var/www/codeigniter", type: "rsync", rsync__args: ["--verbose", "--archive", "--delete", "-z"] - - # Provider-specific configuration - config.vm.provider "virtualbox" do |vb| - # Display the VirtualBox GUI when booting the machine - vb.gui = false - # Customize the amount of memory on the VM: - vb.memory = "1024" - end - - # Provision - config.vm.provision "shell", inline: <<-SHELL - MYSQL_ROOT_PASS="password" - PGSQL_ROOT_PASS="password" - VIRTUALHOST="localhost" - CODEIGNITER_PATH="/var/www/codeigniter" - PHP_VERSION=7.3 - PGSQL_VERSION=10 - #APT_PROXY="192.168.10.1:3142" - - grep -q "127.0.0.1 ${VIRTUALHOST}" /etc/hosts || echo "127.0.0.1 ${VIRTUALHOST}" >> /etc/hosts - - # Creates a swap file if necessary - RAM=`awk '/MemTotal/ {print $2}' /proc/meminfo` - if [ $RAM -lt 1000000 ] && [ ! -f /swap/swapfile ]; then - echo "================================================================================" - echo "Adding swap" - echo "================================================================================" - echo "This process may take a few minutes. Please wait..." - mkdir /swap - dd if=/dev/zero of=/swap/swapfile bs=1024 count=1000000 - chmod 600 /swap/swapfile - mkswap /swap/swapfile - swapon /swap/swapfile - echo "/swap/swapfile swap swap defaults 0 0" >> /etc/fstab - echo "Done." - fi - - # Prepare to use APT Proxy - if [ ! -z $APT_PROXY ]; then - if [ ! -f /etc/apt/sources.list-origin ]; then - cp /etc/apt/sources.list /etc/apt/sources.list-origin - fi - sed -i "s/archive.ubuntu.com/${APT_PROXY}/" /etc/apt/sources.list - sed -i "s/security.ubuntu.com/${APT_PROXY}/" /etc/apt/sources.list - fi - - export DEBIAN_FRONTEND=noninteractive - - echo "================================================================================" - echo "Updating and Installing Required Packages" - echo "================================================================================" - - add-apt-repository ppa:ondrej/php - - apt-get update - - debconf-set-selections <<< "mysql-server mysql-server/root_password password ${MYSQL_ROOT_PASS}" - debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${MYSQL_ROOT_PASS}" - - apt-get install -y \ - php$PHP_VERSION apache2 composer \ - php-intl php-mbstring php-xml php-zip php-xdebug \ - php-mysql mysql-server mysql-client \ - php-pgsql postgresql-$PGSQL_VERSION \ - php-sqlite3 sqlite3 \ - php-memcached memcached \ - php-redis redis-server \ - php-curl curl \ - php-gd php-imagick \ - python-pip - - pip install sphinx sphinxcontrib-phpdomain - - apt-get autoclean - - echo "================================================================================" - echo "Preparing User Guide" - echo "================================================================================" - - cd "${CODEIGNITER_PATH}/user_guide_src/cilexer" - python setup.py install - cd .. - make html - - echo "================================================================================" - echo "Configuring Databases" - echo "================================================================================" - - sed -i "s/^bind-address/#bind-address/" /etc/mysql/mysql.conf.d/mysqld.cnf - mysql -e "CREATE DATABASE IF NOT EXISTS codeigniter COLLATE 'utf8_general_ci'; - UPDATE mysql.user SET Host='%' WHERE user='root'; - GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; - FLUSH PRIVILEGES;" -uroot -p$MYSQL_ROOT_PASS - systemctl restart mysql - - sed -i "s/^#listen_addresses = 'localhost'/listen_addresses = '*'/" /etc/postgresql/$PGSQL_VERSION/main/postgresql.conf - grep -q "host all root all md5" /etc/postgresql/$PGSQL_VERSION/main/pg_hba.conf || echo "host all root all md5" >> /etc/postgresql/$PGSQL_VERSION/main/pg_hba.conf - sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='root'" | grep -q 1 || sudo -u postgres psql -c "CREATE ROLE root WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN" - sudo -u postgres psql -c "ALTER ROLE root WITH PASSWORD '${PGSQL_ROOT_PASS}'" - sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='codeigniter'" | grep -q 1 ||sudo -u postgres psql -c "CREATE DATABASE codeigniter" - sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE codeigniter TO root" - systemctl restart postgresql - - echo "================================================================================" - echo "Configuring Memcached and Redis" - echo "================================================================================" - - sed -i "s/^bind 127.0.0.1/#bind 127.0.0.1/" /etc/redis/redis.conf - sed -i "s/^protected-mode yes/protected-mode no/" /etc/redis/redis.conf - sed -i "s/^-l 127.0.0.1/#-l 127.0.0.1/" /etc/memcached.conf - systemctl restart redis - systemctl restart memcached - - echo "================================================================================" - echo "Configuring Virtual Hosts" - echo "================================================================================" - - mkdir -p "${CODEIGNITER_PATH}/build/coverage-html" - mkdir -p "${CODEIGNITER_PATH}/public" - mkdir -p "${CODEIGNITER_PATH}/user_guide_src/build/html" - mkdir -p "${CODEIGNITER_PATH}/writable/apache" - chown -R vagrant:vagrant $CODEIGNITER_PATH - - # Creates a symlink in the user home - if [ ! -d /home/vagrant/codeigniter ]; then - ln -s $CODEIGNITER_PATH /home/vagrant/codeigniter - fi - - sed -i "s/APACHE_RUN_USER=www-data/APACHE_RUN_USER=vagrant/" /etc/apache2/envvars - sed -i "s/APACHE_RUN_GROUP=www-data/APACHE_RUN_GROUP=vagrant/" /etc/apache2/envvars - grep -q "Listen 81" /etc/apache2/ports.conf || sed -i "s/^Listen 80/Listen 80\\nListen 81\\nListen 82/" /etc/apache2/ports.conf - sed -i "s/^display_errors = Off/display_errors = On/" /etc/php/7.3/apache2/php.ini - sed -i "s/^display_startup_errors = Off/display_startup_errors = On/" /etc/php/7.3/apache2/php.ini - - echo "ServerName ${VIRTUALHOST} - - DirectoryIndex index.html index.php - Options All - AllowOverride All - - - ServerAdmin vagrant@localhost - DocumentRoot ${CODEIGNITER_PATH}/public - ErrorLog ${CODEIGNITER_PATH}/writable/apache/error.log - CustomLog ${CODEIGNITER_PATH}/writable/apache/custom.log combined - - - DocumentRoot ${CODEIGNITER_PATH}/build/coverage-html - - - DocumentRoot ${CODEIGNITER_PATH}/user_guide_src/build/html - -" > /etc/apache2/sites-available/codeigniter.conf - - a2enmod rewrite - a2dissite 000-default.conf - a2ensite codeigniter.conf - systemctl restart apache2 - - echo "================================================================================" - echo "Services Status" - echo "================================================================================" - service --status-all - - SHELL -end diff --git a/admin/README.md b/admin/README.md index 52747ca3eeaa..a4048bf59152 100644 --- a/admin/README.md +++ b/admin/README.md @@ -4,35 +4,33 @@ This folder contains tools or docs useful for project maintainers. ## Repositories inside https://github.com/codeigniter4 -- **CodeIgniter4** is the main development repository. - It supports issues and pull requests, and has a rule to enforce GPG-signed commits. - In addition to the framework source, it includes unit testing and documentation source. - The three repositories following are built from this one as part of the release workflow. +- **CodeIgniter4** is the main development repository. + It supports issues and pull requests, and has a rule to enforce GPG-signed commits. + In addition to the framework source, it includes unit testing and documentation source. + The three repositories following are built from this one as part of the release workflow. This repo is meant to be forked by contributors. - -- **framework** is the released developer repository. +- **framework** is the released developer repository. It contains all the main pieces of the framework that developers would use to - build their apps, but not the framework unit testing or the user guide source. - It is meant to be downloaded by developers, or composer-installed. + build their apps, but not the framework unit testing or the user guide source. + It is meant to be downloaded by developers, or composer-installed. This is a read-only repository. -- **appstarter** is the released application starter repository. +- **appstarter** is the released application starter repository. It is derived from the framework's `app` and `public` folders, with - a composer requirement dependency to pull in the framework itself. - It is meant to be downloaded or composer-installed. + a composer requirement dependency to pull in the framework itself. + It is meant to be downloaded or composer-installed. This is a read-only repository. -- **userguide** is released documentation publishing repository. +- **userguide** is released documentation publishing repository. It contains built versions of the user guide, corresponding to the - framework releases. - It could be downloaded, forked or potentially composer-installed. + framework releases. + It could be downloaded, forked or potentially composer-installed. This is a read-only repository. - -- **coding-standard** is the coding style standards repository. - It contains PHP CodeSniffer rules to ensure consistent code style - within the framework itself. +- **coding-standard** is the coding style standards repository. + It contains PHP-CS-Fixer rules to ensure consistent code style + within the framework itself. It is meant to be composer-installed. - **translations** is the repository holding official translations of - the locale-dependent system messages. - It is community-maintained, and accepts issues and pull requests. + the locale-dependent system messages. + It is community-maintained, and accepts issues and pull requests. It could be downloaded, forked or composer-installed. ## Contributor Scripts @@ -43,47 +41,41 @@ This folder contains tools or docs useful for project maintainers. to be added as part of a git commit, ensuring that they conform to the framework coding style standards, and automatically fixing what can be. -## Maintainer Scripts - -- **release-config** holds variables used for the maintainer & release building -- **docbot** re-builds the user guide from the RST source for it, - and optionally deploys it to the `gh-pages` branch of the main - repository (if the user running it has maintainer rights on that repo). - See the [writeup](./docbot.md). - -## Release Building Scripts - -*Do not use these scripts! They are left here for reference only.* - -The release workflow is detailed in its own writeup; these are the main -scripts used by the release manager: - -- **release** builds a new release branch in the main repo, for vetting. - This includes updating version dependencies or constants, - generating version(s) of the user guide; and possibly - moving or ignoring stuff, distinguishing release from development. - If successful, it will update the `config` file, with the version number - in it, and it will run the related scripts following, to revise - the release distributions. - Usage: `admin/release version qualifier` -- **release-userguide** builds the distributable userguide repo. - It could be used on its own, but is normally part of `release`. -- **release-deploy** pushes the release changes to the appropriate github - repositories. Tag & create releases on GitHub. This is not easily reversible! - Usage: `admin/release-deploy version qualifier` -- **release-revert** can be used to restore your repositories to the state they - were in before you started a release. **IF** you haven't deployed. - This is in case you decide not to proceed with the release, for any reason. - Remember to be polite when running it. - +## Release Scripts + +The release process is detailed in [RELEASE.md](./RELEASE.md). These scripts +are used as part of that process: + +- **generate-changelog.php** prepends the CHANGELOG.md entry for a new + release, built from GitHub's auto-generated release notes and the + SECURITY section of the version's detailed changelog. + Usage: `php admin/generate-changelog.php 4.x.x [--dry-run]` +- **prepare-release.php** creates the `release-4.x.x` branch and updates + version references in the framework source, the user guide, and the + distribution build script. + Usage: `php admin/prepare-release.php 4.x.x` +- **create-new-changelog.php** creates the changelog and upgrade guide + stubs for the next version and adds them to their index files. + Usage: `php admin/create-new-changelog.php ` +- **check-pr-labels.php** flags PRs merged after a release (default: the + latest) that appear to be missing the labels used to generate the + changelog. + Usage: `php admin/check-pr-labels.php []` +- **update-upgrade-guide.php** fills the "Config" and "All Changes" sections + of the version's upgrade guide with the project space files changed since + the last release. + Usage: `php admin/update-upgrade-guide.php [--dry-run]` +- **sync-release-branches.php** checks out the target branch and merges + upstream and the source branch into it, as done at several points of the + release process. Without `--push`, the result is left unpushed for review. + Usage: `php admin/sync-release-branches.php [--push]` ## Other Stuff -- **release-notes.bb** is a boilerplate for forum announcements of a new release. - It is marked up using [BBcode](https://en.wikipedia.org/wiki/BBCode). - The **framework** and **starter** subfolders contain files that will over-ride those from the development repository, when the distribution repositories are built. -- The subfolders inside `admin` contain "next release" files in the case of +- The subfolders inside `admin` contain "next release" files in the case of `codeigniter4` and over-written distribution files in the other cases. -- The CHANGELOG.md file is auto-generated using the [GitHub Changelog Generator](https://github.com/github-changelog-generator/github-changelog-generator) +- The CHANGELOG.md file is generated from GitHub's auto-generated release + notes, as described in [RELEASE.md](./RELEASE.md). diff --git a/admin/RELEASE.md b/admin/RELEASE.md index 822bd1f5f154..75f2a0fbf35a 100644 --- a/admin/RELEASE.md +++ b/admin/RELEASE.md @@ -1,97 +1,254 @@ # Release Process -> Documentation guide based on the releases of `4.0.5` and `4.1.0` on January 31, 2021. -> Updated for `4.1.2` on May 17, 2021. -> -MGatner +> Documentation guide based on the releases of `4.0.5` and `4.1.0` on January 31, 2021 (MGatner) +> +> Updated for `4.5.0` on April 7, 2024 (kenjis) +> Updated for `4.6.0` on January 19, 2025 (paulbalandan) +> Updated for `4.7.5` on July 8, 2026 (paulbalandan) -## Preparation +## Notation -* Work off direct clones of the repos so the release branches persist for a time -* Clone both **codeigniter4/CodeIgniter4** and **codeigniter4/userguide** and resolve any necessary PRs -* Vet the **admin/** folders for any removed hidden files (Action deploy scripts *do not remove these*) -* Generate a new **CHANGELOG.md** ahead of time using [GitHub Changelog Generator](https://github.com/github-changelog-generator/github-changelog-generator): -``` -github_changelog_generator --user codeigniter4 --project codeigniter4 --since-tag v4.0.4 --future-release v4.0.5 --token {your_github_token} -...or -github_changelog_generator --user codeigniter4 --project codeigniter4 --since-commit "2021-02-01 13:26:28" --future-release v4.0.5 --token {your_github_token} -``` - -## CodeIgniter4 - -> Note: Most changes that need noting in the User Guide and docs should have been included -> with their PR, so this process assumes you will not be generating much new content. - -* Create a new branch `release-4.x.x` -* Update **system/CodeIgniter.php** with the new version number: `const CI_VERSION = '4.x.x';` -* Update **user_guide_src/source/conf.py** with the new `version = '4.x'` (if applicable) and `release = '4.x.x'` -* Replace **CHANGELOG.md** with the new version generated above -* Set the date in **user_guide_src/source/changelogs/{version}.rst** to format `Release Date: January 31, 2021` -* Create a new changelog for the next version at **user_guide_src/source/changelogs/{next_version}.rst** and add it to **index.rst** -* If there are additional upgrade steps, create **user_guide_src/source/installation/upgrade_{ver}.rst** and add it to **upgrading.rst** -* Commit the changes with "Prep for 4.x.x release" and push to origin -* Create a new PR from `release-4.x.x` to `develop`: - * Title: "Prep for 4.x.x release" - * Decription: "Updates changelog and version references for `4.x.x`." (plus checklist) -* Let all tests run, then review and merge the PR -* Create a new PR from `develop` to `master`: - * Title: "4.x.x Ready code" - * Description: blank -* Merge the PR then fast-forward `develop` to catch the merge commit -* Create a new Release: - * Version: "v4.x.x" - * Title: "CodeIgniter 4.x.x" - * Description: -``` -CodeIgniter 4.x.x release. +- `4.x.x`: The new release version. (e.g., `4.5.3`) +- `4.y`: The next minor version. (e.g., `4.6`) +- `4.z`: The next next minor version. (e.g., `4.7`) -See the changelog: https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md -``` -* Watch for the "Deploy Framework" Action to make sure **framework** and **appstarter** get updated +> [!NOTE] +> Copy this file, and replace the versions above with the actual versions. -## User Guide +## Merge `develop` branch into next minor version branch `4.y` -> See "Sphinx Installation" below if you run into issues during `make` +Before starting the release process, if there are commits in `develop` branch that +are not merged into `4.y` branch, merge them. This is because if conflicts occur, +merging will take time. -* Still in the **CodeIgniter4** repo enter the **user_guide_src** directory -* Clear out any old build files: `rm -rf build/` -* Build the HTML version of the User Guide: `make html` -* Build the ePub version of the User Guide: `make epub` -* Switch to the **userguide** repo and create a new branch `release-4.x.x` -* Replace **docs/** with **CodeIgniter4/user_guide_src/build/html** -* Ensure the file **docs/.nojekyll** exists or GitHub Pages will ignore folders with an underscore prefix -* Copy **CodeIgniter4/user_guide_src/build/epub/CodeIgniter.epub** to **./CodeIgniter4.x.x.epub** -* Commit the changes with "Update for 4.x.x" and push to origin -* Create a new PR from `release-4.x.x` to `develop`: - * Title: "Update for 4.x.x" - * Description: blank -* Merge the PR -* Create a new Release: - * Version: "v4.x.x" - * Title: "CodeIgniter 4.x.x User Guide" - * Description: "CodeIgniter 4.x.x User Guide" -* Watch for the "github pages" Environment to make sure the deployment succeeds +* [ ] Merge `develop` into `4.y`: + ```console + php admin/sync-release-branches.php 4.y develop --push + ``` -## Website +## [Minor version only] Merge minor version branch into `develop` -Currently the User Guide on the website has to be updated manually. Visit Jim's user home -where the served directory **codeigniter.com** exists. Copy the latest **docs** folder from -the User Guide repo to **public/userguide4** and browse to the website to make sure it works. +If you release a new minor version. -## Announcement +* [ ] Create PR to merge `4.y` into `develop`: + * Title: `4.y.0 Merge code` + * Description: blank +* [ ] Rename the current minor version (e.g., `4.5`) in Setting > Branches > + "Branch protection rules" to the next minor version (e.g. `4.5` → `4.6`). +* [ ] Delete the merged `4.y` branch (this closes all PRs to the branch). -> Note: This forum is restricted to administrators. +## Preparation -* Make a new topic in the "News & Discussion" forums: https://forum.codeigniter.com/forum-2.html -* The content is somewhat organic, but should include any major features and changes as well as a link to the User Guide's changelog +Work off direct clones of the repos so the release branches persist for a time. + +* [ ] Clone both **codeigniter4/CodeIgniter4** and **codeigniter4/userguide** and + resolve any necessary PRs + ```console + rm -rf CodeIgniter4.bk userguide.bk + mv CodeIgniter4 CodeIgniter4.bk + mv userguide userguide.bk + git clone git@github.com:codeigniter4/CodeIgniter4.git + git clone git@github.com:codeigniter4/userguide.git + ``` +* [ ] Vet the **admin/** folders for any removed hidden files (Action deploy scripts + *do not remove these*) + ```console + cd CodeIgniter4 + git diff --name-status origin/master admin/ + ``` + +## Changelog + +When generating the changelog, each pull request to be included must have one of +the following [labels](https://github.com/codeigniter4/CodeIgniter4/labels): +- **bug** ... PRs that fix bugs +- **enhancement** ... PRs to improve existing functionalities +- **new feature** ... PRs for new features +- **refactor** ... PRs to refactor + +PRs with breaking changes must have the following additional label: +- **breaking change** ... PRs that may break existing functionalities + +At any time, missing labels can be checked for with +`php admin/check-pr-labels.php`, which flags PRs merged since the last release +that appear to lack a changelog label. It requires the authenticated +[GitHub CLI](https://cli.github.com/). + +### Generate Changelog + +The changelog is generated from GitHub's auto-generated release notes with a +script. It requires the authenticated [GitHub CLI](https://cli.github.com/). + +The script also copies the security fixes from the SECURITY section of +**user_guide_src/source/changelogs/v4.x.x.rst**, so any security advisory PRs +must be merged and documented there before generating the changelog. + +* [ ] Run `php admin/generate-changelog.php 4.x.x --dry-run` and check the output. + * The script lists the PRs that have none of the changelog labels. If any of + them should be included in the changelog, add a label to the PR and run + the script again. +* [ ] Run `php admin/generate-changelog.php 4.x.x` to prepend the new entry to + **CHANGELOG.md**. + +If the script cannot be used, the same notes can be generated from the +[Releases](https://github.com/codeigniter4/CodeIgniter4/releases) page: +click "Draft a new release", choose the tag `v4.x.x` (create new tag on +publish) with target `develop`, and click "Generate release notes". Copy the +resulting contents into **CHANGELOG.md** and adjust the format to match the +existing content. + +## Process + +> [!NOTE] +> Most changes that need noting in the User Guide and docs should have +> been included with their PR, so this process assumes you will not be +> generating much new content. + +* [ ] Merge any security advisory PRs in private forks. +* [ ] Check that **CHANGELOG.md** contains the entry for the new version, generated above. +* [ ] Update **user_guide_src/source/changelogs/v4.x.x.rst** + * Remove the section titles that have no items +* [ ] Update **user_guide_src/source/installation/upgrade_4xx.rst** + * [ ] Run `php admin/update-upgrade-guide.php 4.x.x` to fill in the "Config" and + "All Changes" sections with the project space files changed since the last release. + * The "Config" section is not modified if it already has content. The script + prints any missing entries to merge manually. + * Add notes to the "Config" entries as needed. + * [ ] Remove the section titles that have no items + * [ ] [Minor version only] Update the "from" version in the title, (e.g., `from 4.3.x` → `from 4.3.8`). +* [ ] Run `php admin/prepare-release.php 4.x.x` and push to origin. + * The above command does the following: + * Create a new branch `release-4.x.x` + * Update **system/CodeIgniter.php** with the new version number: + `const CI_VERSION = '4.x.x';` + * Update **user_guide_src/source/conf.py** with the new `version = '4.x'` + and `release = '4.x.x'`. + * Update **user_guide_src/source/changelogs/{version}.rst** + * Set the date to format `Release Date: January 31, 2021` + * Update **phpdoc.dist.xml** with the new `CodeIgniter v4.x API` + and `` + * Update **admin/starter/builds**: + * Set `define('LATEST_RELEASE', '^4.x')` + * Set `define('NEXT_MINOR', '^4.y-dev')`. + * If the major version changes, you need to manually change to `define('NEXT_MINOR', '^5.0-dev')`. + * Commit the changes with `Prep for 4.x.x release` +* [ ] Create a new PR from `release-4.x.x` to `develop`: + * Title: `Prep for 4.x.x release` + * Description: + ``` + Updates changelog and version references for 4.x.x. + + Previous version: #xxxx + Release Code: TODO + New Changelog: TODO + + (plus checklist) + ``` + +* [ ] Let all tests run, then review and merge the PR. +* [ ] Create a new PR from `develop` to `master`: + * Title: `4.x.x Ready code` + * Description: blank +* [ ] Merge the PR and wait for all tests. +* [ ] Create a new Release: + * Choose a tag: `v4.x.x` (Create new tag: v4.x.x on publish) + * Target: `master` + * Title: `CodeIgniter 4.x.x` + * Description: + ``` + CodeIgniter 4.x.x release. + + See the changelog: https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md + + ## New Contributors + * + + **Full Changelog**: https://github.com/codeigniter4/CodeIgniter4/compare/v4.x.w...v4.x.x + ``` + Click the "Generate release notes" button, and get the "New Contributors". +* [ ] Watch the "[Verify Release](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/verify-release.yml)" + workflow and make sure it passes. It does the following: + * Wait for the "[Deploy Distributable Repos](https://github.com/codeigniter4/CodeIgniter4/actions/workflows/deploy-distributables.yml)" + action to update **framework**, **appstarter**, and **userguide** + * Install `appstarter` from Packagist, verify it pulls framework `v4.x.x`, + and run its tests + * Verify that the user guide deployments succeeded ("Deploy Production" and + "pages build and deployment" in the UG repo) and that **CodeIgniter4.x.x.epub** + was added + * If it fails, fix the cause and re-run it via "Run workflow" with the tag `v4.x.x`. +* [ ] Fast-forward `develop` branch to catch the merge commit from `master` + ```console + php admin/sync-release-branches.php develop master --push + ``` +* [ ] Update the next minor version branch `4.y`: + ```console + php admin/sync-release-branches.php 4.y develop --push + ``` +* [ ] [Minor version only] Create the new next minor version branch `4.z`: + ```console + git fetch upstream + git switch develop + git switch -c 4.z + git push upstream HEAD + ``` +* [ ] Request CVEs and publish any security advisories that were resolved from private forks + (note: publishing is restricted to administrators). +* [ ] Announce the release on the forums and Slack channel + (note: this forum is restricted to administrators). + * Make a new topic in the "News & Discussion" forums: + https://forum.codeigniter.com/forum-2.html + * The content is somewhat organic, but should include any major features and + changes as well as a link to the User Guide's changelog +* [ ] Run `php admin/create-new-changelog.php ` + * The above command does the following: + * Create **user_guide_src/source/changelogs/{next_version}.rst** and add it to + **index.rst** (See **next-changelog-*.rst**) + * Create **user_guide_src/source/installation/upgrade_{next_version}.rst** and add it to + **upgrading.rst** (See **next-upgrading-guide.rst**) +* [ ] Create a PR for new changelog and upgrade for the next version ## Appendix ### Sphinx Installation -You may need to install Sphinx and its dependencies prior to building the User Guide. +You may need to install Sphinx and its dependencies prior to building the User +Guide. + This worked seamlessly on Ubuntu 20.04: -``` +```console sudo apt install python3-sphinx sudo pip3 install sphinxcontrib-phpdomain sudo pip3 install sphinx_rtd_theme ``` + +### Manual User Guide Process + +> [!NOTE] +> This process is normally not needed. The "Deploy Distributable Repos" action +> builds and deploys the User Guide automatically when a release is published. +> Follow these steps only if the automatic deployment fails. + +* [ ] Still in the **CodeIgniter4** repo enter the **user_guide_src** directory +* [ ] Clear out any old build files: `rm -rf build/` +* [ ] Build the HTML version of the User Guide: `make html` +* [ ] Build the ePub version of the User Guide: `make epub` +* [ ] Switch to the **userguide** repo and create a new branch `release-4.x.x` +* [ ] Replace **docs/** with **CodeIgniter4/user_guide_src/build/html** +* [ ] Ensure the file **docs/.nojekyll** exists or GitHub Pages will ignore folders + with an underscore prefix +* [ ] Copy **CodeIgniter4/user_guide_src/build/epub/CodeIgniter.epub** to + **./CodeIgniter4.x.x.epub** +* [ ] Commit the changes with "Update for 4.x.x" and push to origin +* [ ] Create a new PR from `release-4.x.x` to `develop`: + * Title: "Update for 4.x.x" + * Description: blank +* [ ] Merge the PR +* [ ] Create a new Release: + * Version: "v4.x.x" + * Title: "CodeIgniter 4.x.x User Guide" + * Description: "CodeIgniter 4.x.x User Guide" +* [ ] Watch for the "github pages" Environment to make sure the deployment succeeds + +The User Guide website should update itself via the deploy GitHub Action. Should +this fail the server must be updated manually. See repo and hosting details in +the deploy script at the User Guide repo. diff --git a/admin/alldocs b/admin/alldocs deleted file mode 100755 index 12d0b410ff84..000000000000 --- a/admin/alldocs +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -# Rebuild and deploy all CodeIgniter4 docs -# - -. admin/docbot $1 -. admin/apibot $1 \ No newline at end of file diff --git a/admin/apibot b/admin/apibot deleted file mode 100755 index 08b451a04bbb..000000000000 --- a/admin/apibot +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Rebuild and deploy CodeIgniter4 under-development user guide -# -# This is a quick way to test user guide changes, and if they -# look good, to push them to the gh-pages branch of the -# development repository. -# -# This is not meant for updating the "stable" user guide. - -UPSTREAM=https://github.com/codeigniter4/api.git - -# Prepare the nested repo clone folder -rm -rf build/api* -mkdir -p build/api/docs - -# Get ready for git -cd build/api -git init -git remote add origin $UPSTREAM -git fetch -git checkout master -git reset --hard origin/master -rm -r docs/* - -# Make the new user guide -cd ../.. -phpdoc -cp -R api/build/* build/api/docs - -# All done? -if [ $# -lt 1 ]; then - exit 0 -fi - -# Optionally update the remote repo -if [ $1 = "deploy" ]; then - cd build/api - git add . - git commit -S -m "APIbot synching" - git push -f origin master -fi diff --git a/admin/apibot.md b/admin/apibot.md deleted file mode 100644 index ef22277069ec..000000000000 --- a/admin/apibot.md +++ /dev/null @@ -1,36 +0,0 @@ -# apibot - -Builds & deploys API docs. - -The in-progress CI4 API docs, warts & all, are rebuilt and -then copied to a nested -repository clone (`build/api`), with the result -optionally pushed to the `master` branch of the `api` repo. -That would then be publically visible as the in-progress -version of the [API](https://codeigniter4.github.io/api/). - -## Requirements - -You must have phpDocumentor installed, with a `phpdoc` alias installed globally. - -## Audience - -This script is intended for use by framework maintainers, -i.e. someone with commit rights on the CI4 repository. - -You will be prompted for your github credentials and -GPG-signing key as appropriate. - -## Usage - -Inside a shell prompt, in the project root: - - `admin/apibot [deploy]` - -If "deploy" is not added, the script execution is considered -a trial run, and nothing is pushed to the repo. - -Whether or not deployed, the results are left inside -`build/api` (which is git ignored). - -Generate these and the userguide together with the 'alldocs' script. diff --git a/admin/check-pr-labels.php b/admin/check-pr-labels.php new file mode 100644 index 000000000000..a1a2f7d9c33e --- /dev/null +++ b/admin/check-pr-labels.php @@ -0,0 +1,203 @@ +&1", $repo, $number), $files, $exitCode); + + if ($exitCode !== 0) { + return true; + } + + foreach ($files as $file) { + if (str_starts_with($file, 'system/')) { + return true; + } + } + + return false; +} + +chdir(__DIR__ . '/..'); + +$ansi = stream_isatty(STDOUT); +$repo = 'codeigniter4/CodeIgniter4'; + +// Labels used by the changelog categories. See ".github/release.yml". +$changelogLabels = ['breaking change', 'bug', 'enhancement', 'new feature', 'refactor']; + +// PR title types that map to a changelog label. +$typeToLabel = [ + 'fix' => 'bug', + 'feat' => 'new feature', + 'perf' => 'enhancement', + 'refactor' => 'refactor', +]; + +// PR title types that need no changelog label. +$typesWithoutLabel = ['chore', 'ci', 'docs', 'style', 'test']; + +// Release process PRs carry no labels. +$releaseTitles = '/\A(?:Prep for \d+\.\d+\.\d+ release|\d+\.\d+\.\d+ (?:Ready|Merge) code)\z/'; + +$tag = null; + +foreach (array_slice($argv, 1) as $arg) { + if (preg_match('/\Av?(\d+\.\d+\.\d+)\z/', $arg, $matches) === 1) { + $tag = "v{$matches[1]}"; + + continue; + } + + echo sprintf("Usage: php %s [version]\n", $argv[0]); + echo sprintf("E.g.,: php %s 4.7.3\n", $argv[0]); + echo "Checks the PRs merged after the given release. Defaults to the latest release.\n"; + + exit(1); +} + +$endpoint = $tag === null ? sprintf('repos/%s/releases/latest', $repo) : sprintf('repos/%s/releases/tags/%s', $repo, $tag); +exec(sprintf('gh api %s 2>&1', $endpoint), $output, $exitCode); + +if ($exitCode !== 0) { + echo sprintf("Failed to fetch the release %s from GitHub:\n", $tag ?? '(latest)'); + echo implode("\n", $output) . "\n"; + + exit(1); +} + +$release = json_decode(implode("\n", $output), true); +$since = $release['published_at'] ?? ''; + +echo sprintf("Checking PRs merged since %s (%s).\n", $release['tag_name'], $since); + +$command = sprintf( + 'gh pr list --repo %s --search %s --json number,title,labels,baseRefName,url --limit 300 2>&1', + $repo, + escapeshellarg(sprintf('is:merged -base:master merged:>%s', $since)), +); +exec($command, $prOutput, $exitCode); + +if ($exitCode !== 0) { + echo "Failed to fetch the merged PRs from GitHub:\n"; + echo implode("\n", $prOutput) . "\n"; + + exit(1); +} + +$pulls = json_decode(implode("\n", $prOutput), true); + +if (! is_array($pulls)) { + echo "Unexpected response from GitHub.\n"; + + exit(1); +} + +echo sprintf("Found %d merged PRs.\n\n", count($pulls)); + +$missingLabel = []; +$needManualLook = []; + +foreach ($pulls as $pull) { + $labels = array_column($pull['labels'], 'name'); + $hasLabel = array_intersect($changelogLabels, $labels) !== []; + $type = null; + + if (preg_match('/\A(\w+)(?:\([^)]*\))?:/', $pull['title'], $matches) === 1) { + $type = $matches[1]; + } + + if ($hasLabel || in_array($type, $typesWithoutLabel, true)) { + continue; + } + + if (preg_match($releaseTitles, $pull['title']) === 1) { + continue; + } + + if ($type !== null && isset($typeToLabel[$type])) { + // The refactor label applies only to refactoring in system/. Test-only + // refactors may use the testing label, which is not in the changelog. + if ($type === 'refactor' && ! touches_system_dir($repo, (int) $pull['number'])) { + continue; + } + + $missingLabel[] = [ + 'number' => $pull['number'], + 'title' => $pull['title'], + 'base' => $pull['baseRefName'], + 'url' => $pull['url'], + 'label' => $typeToLabel[$type], + ]; + } else { + $needManualLook[] = [ + 'number' => $pull['number'], + 'title' => $pull['title'], + 'base' => $pull['baseRefName'], + 'url' => $pull['url'], + ]; + } +} + +if ($missingLabel !== []) { + echo color('PRs that appear to be missing a changelog label:', '1', $ansi) . "\n"; + + foreach ($missingLabel as $pull) { + echo sprintf("%s [suggested: %s]\n", format_pull_line($pull, $ansi), color($pull['label'], '33', $ansi)); + } + + echo "\nTo add the suggested labels, run the following commands (drop any that are not warranted):\n"; + + foreach ($missingLabel as $pull) { + echo sprintf("gh pr edit %d --repo %s --add-label \"%s\"\n", $pull['number'], $repo, $pull['label']); + } + + echo "\n"; +} + +if ($needManualLook !== []) { + echo color('PRs with no changelog label and no recognized title type (check manually):', '1', $ansi) . "\n"; + + foreach ($needManualLook as $pull) { + echo format_pull_line($pull, $ansi) . "\n"; + } + + echo "\n"; +} + +if ($missingLabel === []) { + echo color('No PRs are missing changelog labels.', '32', $ansi) . "\n"; + + exit(0); +} + +exit(1); diff --git a/admin/create-new-changelog.php b/admin/create-new-changelog.php new file mode 100644 index 000000000000..32ac5fdaa52c --- /dev/null +++ b/admin/create-new-changelog.php @@ -0,0 +1,99 @@ + [--dry-run]\n"; + echo "E.g. : php {$argv[0]} 4.4.3 4.4.4 --dry-run\n"; + + exit(1); +} + +// Gets version number from argument. +$currentVersion = $argv[1]; // e.g., '4.4.3' +$currentVersionParts = explode('.', $currentVersion, 3); +$currentMinorVersion = $currentVersionParts[0] . '.' . $currentVersionParts[1]; +$newVersion = $argv[2]; // e.g., '4.4.4' +$newVersionParts = explode('.', $newVersion, 3); +$newMinorVersion = $newVersionParts[0] . '.' . $newVersionParts[1]; +$isMinorUpdate = $currentMinorVersion !== $newMinorVersion; + +// Creates a branch for release +if (! in_array('--dry-run', $argv, true)) { + if (! $isMinorUpdate) { + system('git switch develop'); + } + + system("git switch -c docs-changelog-{$newVersion}"); + system("git switch docs-changelog-{$newVersion}"); +} + +// Copy changelog +$newChangelog = "./user_guide_src/source/changelogs/v{$newVersion}.rst"; +$changelogIndex = './user_guide_src/source/changelogs/index.rst'; + +if ($isMinorUpdate) { + copy('./admin/next-changelog-minor.rst', $newChangelog); +} else { + copy('./admin/next-changelog-patch.rst', $newChangelog); +} + +// Replace version in CodeIgniter.php to {version}-dev. +replace_file_content( + './system/CodeIgniter.php', + '/public const CI_VERSION = \'.*?\';/u', + "public const CI_VERSION = '{$newVersion}-dev';", +); + +// Add changelog to index.rst. +replace_file_content( + $changelogIndex, + '/\.\. toctree::\n :titlesonly:\n/u', + ".. toctree::\n :titlesonly:\n\n v{$newVersion}", +); + +// Replace {version} +$underline = str_repeat('#', mb_strlen("Version {$newVersion}")); +replace_file_content( + $newChangelog, + '/#################\nVersion {version}\n#################/u', + "{$underline}\nVersion {$newVersion}\n{$underline}", +); +replace_file_content($newChangelog, '/{version}/u', $newVersion); + +// Copy upgrading +$versionWithoutDots = str_replace('.', '', $newVersion); +$newUpgrading = "./user_guide_src/source/installation/upgrade_{$versionWithoutDots}.rst"; +$upgradingIndex = './user_guide_src/source/installation/upgrading.rst'; +copy('./admin/next-upgrading-guide.rst', $newUpgrading); + +// Add upgrading to upgrading.rst. +replace_file_content( + $upgradingIndex, + '/ backward_compatibility_notes\n/u', + " backward_compatibility_notes\n\n upgrade_{$versionWithoutDots}", +); + +// Replace {version} +$underline = str_repeat('#', mb_strlen("Upgrading from {$currentVersion} to {$newVersion}")); +replace_file_content( + $newUpgrading, + '/##############################\nUpgrading from {version} to {version}\n##############################/u', + "{$underline}\nUpgrading from {$currentVersion} to {$newVersion}\n{$underline}", +); + +if (! in_array('--dry-run', $argv, true)) { + system('git add ./system/CodeIgniter.php'); + system("git add {$newChangelog} {$changelogIndex}"); + system("git add {$newUpgrading} {$upgradingIndex}"); + system("git commit -m \"docs: add changelog and upgrade for v{$newVersion}\""); +} diff --git a/admin/css/debug-toolbar/README.md b/admin/css/debug-toolbar/README.md new file mode 100644 index 000000000000..2c70e8147eae --- /dev/null +++ b/admin/css/debug-toolbar/README.md @@ -0,0 +1 @@ +See [contributing/css.md](../../../contributing/css.md). diff --git a/admin/css/debug-toolbar/_settings.scss b/admin/css/debug-toolbar/_settings.scss index 06a51de634bb..1bb1386a46a0 100644 --- a/admin/css/debug-toolbar/_settings.scss +++ b/admin/css/debug-toolbar/_settings.scss @@ -1,7 +1,7 @@ // FONT // ========================================================================== */ -// Standard "sans-serif" font stack used by Github +// Standard "sans-serif" font stack used by GitHub $base-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; // Default size, all other styles are based on this size diff --git a/admin/css/debug-toolbar/_theme-dark.scss b/admin/css/debug-toolbar/_theme-dark.scss index 801b75664f22..83176023a87e 100644 --- a/admin/css/debug-toolbar/_theme-dark.scss +++ b/admin/css/debug-toolbar/_theme-dark.scss @@ -2,23 +2,23 @@ // ========================================================================== */ // The "box-shadow" mixin uses colors -@import '_mixins'; +@use '_mixins'; // Graphic charter -@import '_graphic-charter'; +@use '_graphic-charter'; // DEBUG ICON // ========================================================================== */ #debug-icon { - background-color: $t-dark; - @include box-shadow(0, 0, 4px, $m-gray); + background-color: graphic-charter.$t-dark; + @include mixins.box-shadow(0, 0, 4px, graphic-charter.$m-gray); a:active, a:link, a:visited { - color: $g-orange; + color: graphic-charter.$g-orange; } } @@ -27,8 +27,8 @@ // ========================================================================== */ #debug-bar { - background-color: $t-dark; - color: $m-gray; + background-color: graphic-charter.$t-dark; + color: graphic-charter.$m-gray; // Reset to prevent conflict with other CSS files h1, @@ -44,35 +44,35 @@ button, .toolbar { background-color: transparent; - color: $m-gray; + color: graphic-charter.$m-gray; } // Buttons button { - background-color: $t-dark; + background-color: graphic-charter.$t-dark; } // Tables table { strong { - color: $g-orange; + color: graphic-charter.$g-orange; } tbody tr { &:hover { - background-color: $g-gray; + background-color: graphic-charter.$g-gray; } &.current { - background-color: $m-orange; + background-color: graphic-charter.$m-orange; td { - color: $t-dark; + color: graphic-charter.$t-dark; } &:hover td { - background-color: $g-red; - color: $t-light; + background-color: graphic-charter.$g-red; + color: graphic-charter.$t-light; } } } @@ -80,8 +80,8 @@ // The toolbar .toolbar { - background-color: $g-gray; - @include box-shadow(0, 0, 4px, $g-gray); + background-color: graphic-charter.$g-gray; + @include mixins.box-shadow(0, 0, 4px, graphic-charter.$g-gray); img { filter: brightness(0) invert(1); @@ -91,24 +91,24 @@ // Fixed top &.fixed-top { .toolbar { - @include box-shadow(0, 0, 4px, $g-gray); + @include mixins.box-shadow(0, 0, 4px, graphic-charter.$g-gray); } .tab { - @include box-shadow(0, 1px, 4px, $g-gray); + @include mixins.box-shadow(0, 1px, 4px, graphic-charter.$g-gray); } } // "Muted" elements .muted { - color: $m-gray; + color: graphic-charter.$m-gray; td { - color: $g-gray; + color: graphic-charter.$g-gray; } &:hover td { - color: $m-gray; + color: graphic-charter.$m-gray; } } @@ -121,34 +121,34 @@ // The toolbar menus .ci-label { &.active { - background-color: $t-dark; + background-color: graphic-charter.$t-dark; } &:hover { - background-color: $t-dark; + background-color: graphic-charter.$t-dark; } .badge { - background-color: $g-blue; - color: $m-gray; + background-color: graphic-charter.$g-red; + color: graphic-charter.$t-light; } } // The tabs container .tab { - background-color: $t-dark; - @include box-shadow(0, -1px, 4px, $g-gray); + background-color: graphic-charter.$t-dark; + @include mixins.box-shadow(0, -1px, 4px, graphic-charter.$g-gray); } // The "Timeline" tab .timeline { th, td { - border-color: $g-gray; + border-color: graphic-charter.$g-gray; } .timer { - background-color: $g-orange; + background-color: graphic-charter.$g-orange; } } } @@ -158,10 +158,10 @@ // ========================================================================== */ .debug-view.show-view { - border-color: $g-orange; + border-color: graphic-charter.$g-orange; } .debug-view-path { - background-color: $m-orange; - color: $g-gray; + background-color: graphic-charter.$m-orange; + color: graphic-charter.$g-gray; } diff --git a/admin/css/debug-toolbar/_theme-light.scss b/admin/css/debug-toolbar/_theme-light.scss index 41dbb9175fde..9aa0a5a2c9b4 100644 --- a/admin/css/debug-toolbar/_theme-light.scss +++ b/admin/css/debug-toolbar/_theme-light.scss @@ -2,23 +2,23 @@ // ========================================================================== */ // The "box-shadow" mixin uses colors -@import '_mixins'; +@use '_mixins'; // Graphic charter -@import '_graphic-charter'; +@use '_graphic-charter'; // DEBUG ICON // ========================================================================== */ #debug-icon { - background-color: $t-light; - @include box-shadow(0, 0, 4px, $m-gray); + background-color: graphic-charter.$t-light; + @include mixins.box-shadow(0, 0, 4px, graphic-charter.$m-gray); a:active, a:link, a:visited { - color: $g-orange; + color: graphic-charter.$g-orange; } } @@ -27,8 +27,8 @@ // ========================================================================== */ #debug-bar { - background-color: $t-light; - color: $g-gray; + background-color: graphic-charter.$t-light; + color: graphic-charter.$g-gray; // Reset to prevent conflict with other CSS files h1, @@ -44,31 +44,31 @@ button, .toolbar { background-color: transparent; - color: $g-gray; + color: graphic-charter.$g-gray; } // Buttons button { - background-color: $t-light; + background-color: graphic-charter.$t-light; } // Tables table { strong { - color: $g-orange; + color: graphic-charter.$g-orange; } tbody tr { &:hover { - background-color: $m-gray; + background-color: graphic-charter.$m-gray; } &.current { - background-color: $m-orange; + background-color: graphic-charter.$m-orange; &:hover td { - background-color: $g-red; - color: $t-light; + background-color: graphic-charter.$g-red; + color: graphic-charter.$t-light; } } } @@ -76,8 +76,8 @@ // The toolbar .toolbar { - background-color: $t-light; - @include box-shadow(0, 0, 4px, $m-gray); + background-color: graphic-charter.$t-light; + @include mixins.box-shadow(0, 0, 4px, graphic-charter.$m-gray); img { filter: brightness(0) invert(0.4); @@ -87,24 +87,24 @@ // Fixed top &.fixed-top { .toolbar { - @include box-shadow(0, 0, 4px, $m-gray); + @include mixins.box-shadow(0, 0, 4px, graphic-charter.$m-gray); } .tab { - @include box-shadow(0, 1px, 4px, $m-gray); + @include mixins.box-shadow(0, 1px, 4px, graphic-charter.$m-gray); } } // "Muted" elements .muted { - color: $g-gray; + color: graphic-charter.$g-gray; td { - color: $m-gray; + color: graphic-charter.$m-gray; } &:hover td { - color: $g-gray; + color: graphic-charter.$g-gray; } } @@ -117,34 +117,34 @@ // The toolbar menus .ci-label { &.active { - background-color: $m-gray; + background-color: graphic-charter.$m-gray; } &:hover { - background-color: $m-gray; + background-color: graphic-charter.$m-gray; } .badge { - background-color: $g-blue; - color: $t-light; + background-color: graphic-charter.$g-red; + color: graphic-charter.$t-light; } } // The tabs container .tab { - background-color: $t-light; - @include box-shadow(0, -1px, 4px, $m-gray); + background-color: graphic-charter.$t-light; + @include mixins.box-shadow(0, -1px, 4px, graphic-charter.$m-gray); } // The "Timeline" tab .timeline { th, td { - border-color: $m-gray; + border-color: graphic-charter.$m-gray; } .timer { - background-color: $g-orange; + background-color: graphic-charter.$g-orange; } } } @@ -154,10 +154,10 @@ // ========================================================================== */ .debug-view.show-view { - border-color: $g-orange; + border-color: graphic-charter.$g-orange; } .debug-view-path { - background-color: $m-orange; - color: $g-gray; + background-color: graphic-charter.$m-orange; + color: graphic-charter.$g-gray; } diff --git a/admin/css/debug-toolbar/toolbar.scss b/admin/css/debug-toolbar/toolbar.scss index c73510182ac7..767c908bf176 100644 --- a/admin/css/debug-toolbar/toolbar.scss +++ b/admin/css/debug-toolbar/toolbar.scss @@ -10,9 +10,9 @@ // IMPORTS // ========================================================================== */ -@import '_mixins'; -@import '_settings'; - +@use "sass:meta"; +@use '_mixins'; +@use '_settings'; // DEBUG ICON // ========================================================================== */ @@ -29,13 +29,15 @@ width: 36px; // Spacing - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; // Content clear: both; text-align: center; + cursor: pointer; + a svg { margin: 8px; max-width: 20px; @@ -56,6 +58,10 @@ // DEBUG BAR // ========================================================================== */ +.debug-bar-vars { + cursor: pointer; +} + #debug-bar { // Position bottom: 0; @@ -71,22 +77,17 @@ line-height: 36px; // Typography - font-family: $base-font; - font-size: $base-size; + font-family: settings.$base-font; + font-size: settings.$base-size; font-weight: 400; // General elements h1 { - bottom: 0; - display: inline-block; - font-size: $base-size - 2; + display: flex; font-weight: normal; - margin: 0 16px 0 0; + margin: 0 0 0 auto; padding: 0; - position: absolute; - right: 30px; - text-align: left; - top: 0; + font-family: settings.$base-font; svg { width: 16px; @@ -95,7 +96,8 @@ } h2 { - font-size: $base-size; + font-weight: bold; + font-size: settings.$base-size; margin: 0; padding: 5px 0 10px 0; @@ -105,7 +107,7 @@ } h3 { - font-size: $base-size - 4; + font-size: settings.$base-size - 4; font-weight: 200; margin: 0 0 0 10px; padding: 0; @@ -113,7 +115,7 @@ } p { - font-size: $base-size - 4; + font-size: settings.$base-size - 4; margin: 0 0 0 15px; padding: 0; } @@ -128,7 +130,7 @@ button { border: 1px solid; - @include border-radius(4px); + @include mixins.border-radius(4px); cursor: pointer; line-height: 15px; @@ -139,7 +141,7 @@ table { border-collapse: collapse; - font-size: $base-size - 2; + font-size: settings.$base-size - 2; line-height: normal; // Tables indentation @@ -205,10 +207,19 @@ overflow: hidden; overflow-y: auto; padding: 0 12px 0 12px; - + // Give room for OS X scrollbar white-space: nowrap; z-index: 10000; + // Endless rotate + .rotate { + animation: toolbar-rotate 9s linear infinite; + } + @keyframes toolbar-rotate { + to { + transform: rotate(360deg); + } + } } // Fixed top @@ -225,34 +236,27 @@ // The toolbar preferences #toolbar-position, #toolbar-theme { - a { - padding: 0 6px; - display: inline-flex; - vertical-align: top; + padding: 0 6px; + display: inline-flex; + vertical-align: top; + cursor: pointer; - &:hover { - text-decoration: none; - } + &:hover { + text-decoration: none; } } // The "Open/Close" toggle #debug-bar-link { - bottom: 0; - display: inline-block; - font-size: $base-size; - line-height: 36px; + display: flex; padding: 6px; - position: absolute; - right: 10px; - top: 0; - width: 24px; + cursor: pointer; } // The toolbar menus .ci-label { display: inline-flex; - font-size: $base-size - 2; + font-size: settings.$base-size - 2; &:hover { cursor: pointer; @@ -275,7 +279,7 @@ // The toolbar notification badges .badge { - @include border-radius(12px); + @include mixins.border-radius(12px); display: inline-block; font-size: 75%; font-weight: bold; @@ -290,6 +294,8 @@ // The tabs container .tab { + height: fit-content; + text-align: left; bottom: 35px; display: none; left: 0; @@ -304,12 +310,14 @@ // The "Timeline" tab .timeline { + position: static; + display: table; margin-left: 0; width: 100%; th { border-left: 1px solid; - font-size: $base-size - 4; + font-size: settings.$base-size - 4; font-weight: 200; padding: 5px 5px 10px 5px; position: relative; @@ -348,7 +356,7 @@ } .timer { - @include border-radius(4px); + @include mixins.border-radius(4px); display: inline-block; padding: 5px; position: absolute; @@ -403,6 +411,11 @@ text-align: right; } } + + // show tab + &>.debug-bar-dblock { + display: block; + } } @@ -416,7 +429,7 @@ .debug-view-path { font-family: monospace; - font-size: $base-size - 4; + font-size: settings.$base-size - 4; letter-spacing: normal; min-height: 16px; padding: 2px; @@ -445,21 +458,46 @@ } } +@media screen and (max-width: 768px) { + #debug-bar { + table { + display: block; + overflow-x: auto; + font-size: 12px; + margin: 5px 5px 10px 5px; + + td, + th { + padding: 4px 6px; + } + } + + .timeline { + display: block; + white-space: nowrap; + font-size: 12px; + } + + .toolbar { + overflow-x: auto; + } + } + } // THEMES // ========================================================================== */ // Default theme is "Light" -@import '_theme-light'; +@include meta.load-css('_theme-light'); // If the browser supports "prefers-color-scheme" and the scheme is "Dark" @media (prefers-color-scheme: dark) { - @import '_theme-dark'; + @include meta.load-css('_theme-dark'); } // If we force the "Dark" theme #toolbarContainer.dark { - @import '_theme-dark'; + @include meta.load-css('_theme-dark'); td[data-debugbar-route] input[type=text] { background: #000; @@ -469,10 +507,9 @@ // If we force the "Light" theme #toolbarContainer.light { - @import '_theme-light'; + @include meta.load-css('_theme-light'); } - // LAYOUT HELPERS // ========================================================================== */ @@ -488,8 +525,8 @@ width: 70px; } -.debug-bar-width140p { - width: 140px; +.debug-bar-width190p { + width: 190px; } .debug-bar-width20e { @@ -515,3 +552,47 @@ .debug-bar-noverflow { overflow: hidden; } + +.debug-bar-dtableRow { + display: table-row; +} + +.debug-bar-dinlineBlock { + display: inline-block; +} + +.debug-bar-pointer { + cursor: pointer; +} + +.debug-bar-mleft4 { + margin-left: 4px; +} + +.debug-bar-level-0 { + --level: 0; +} + +.debug-bar-level-1 { + --level: 1; +} + +.debug-bar-level-2 { + --level: 2; +} + +.debug-bar-level-3 { + --level: 3; +} + +.debug-bar-level-4 { + --level: 4; +} + +.debug-bar-level-5 { + --level: 5; +} + +.debug-bar-level-6 { + --level: 6; +} diff --git a/admin/docbot b/admin/docbot deleted file mode 100755 index c4dd8229c9ac..000000000000 --- a/admin/docbot +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Rebuild and deploy CodeIgniter4 under-development user guide -# -# This is a quick way to test user guide changes, and if they -# look good, to push them to the gh-pages branch of the -# development repository. -# -# This is not meant for updating the "stable" user guide. - -UPSTREAM=https://github.com/codeigniter4/CodeIgniter4.git - -# Prepare the nested repo clone folder -cd user_guide_src -rm -rf build/* -mkdir build/html - -# Get ready for git -cd build/html -git init -git remote add origin $UPSTREAM -git fetch -git checkout gh-pages -git reset --hard origin/gh-pages -rm -r * - -# Make the new user guide -cd ../.. -make html - -# All done? -if [ $# -lt 1 ]; then - exit 0 -fi - -# Optionally update the remote repo -if [ $1 = "deploy" ]; then - cd build/html - git add . - git commit -S -m "Docbot synching" - git push -f origin gh-pages -fi \ No newline at end of file diff --git a/admin/docbot.md b/admin/docbot.md deleted file mode 100644 index f40b81a35d77..000000000000 --- a/admin/docbot.md +++ /dev/null @@ -1,39 +0,0 @@ -# docbot - -Builds & deploys user guide. - -The in-progress CI4 user guide, warts & all, is rebuilt in a nested -repository clone (`user_guide_src/build/html`), with the result -optionally pushed to the `gh-pages` branch of the repo. -That would then be publically visible as the in-progress -version of the [User Guide](https://codeigniter4.github.io/CodeIgniter4/). - -## Requirements - -You must have python & sphinx installed. - -## Audience - -This script is intended for use by framework maintainers, -i.e. someone with commit rights on the CI4 repository. - -This script wraps the conventional user guide building, -i.e. `user_guide_src/make html`, with additional -steps. - -You will be prompted for your github credentials and -GPG-signing key as appropriate. - -## Usage - -Inside a shell prompt, in the project root: - - `admin/docbot [deploy]` - -If "deploy" is not added, the script execution is considered -a trial run, and nothing is pushed to the repo. - -Whether or not deployed, the results are left inside -user_guide_src/build (which is git ignored). - -Generate these and the API docs together with the 'alldocs' script. diff --git a/admin/framework/.gitattributes b/admin/framework/.gitattributes new file mode 100644 index 000000000000..99484cbc3c97 --- /dev/null +++ b/admin/framework/.gitattributes @@ -0,0 +1,3 @@ +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/admin/framework/.github/workflows/close-pull-request.yml b/admin/framework/.github/workflows/close-pull-request.yml new file mode 100644 index 000000000000..ed698e3e33c1 --- /dev/null +++ b/admin/framework/.github/workflows/close-pull-request.yml @@ -0,0 +1,24 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened, reopened] + +permissions: + pull-requests: write + +jobs: + main: + runs-on: ubuntu-24.04 + steps: + - name: Close PR with nice message + 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 + 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/.gitignore b/admin/framework/.gitignore index 11abea699408..56b9f10d9160 100644 --- a/admin/framework/.gitignore +++ b/admin/framework/.gitignore @@ -48,37 +48,38 @@ Vagrantfile #------------------------- # Temporary Files #------------------------- -writable/cache/* -!writable/cache/index.html +/writable/cache/* +!/writable/cache/index.html -writable/logs/* -!writable/logs/index.html +/writable/logs/* +!/writable/logs/index.html -writable/session/* -!writable/session/index.html +/writable/session/* +!/writable/session/index.html -writable/uploads/* -!writable/uploads/index.html +/writable/uploads/* +!/writable/uploads/index.html -writable/debugbar/* +/writable/debugbar/* +!/writable/debugbar/index.html php_errors.log #------------------------- # User Guide Temp Files #------------------------- -user_guide_src/build/* -user_guide_src/cilexer/build/* -user_guide_src/cilexer/dist/* -user_guide_src/cilexer/pycilexer.egg-info/* +/user_guide_src/build/* +/user_guide_src/cilexer/build/* +/user_guide_src/cilexer/dist/* +/user_guide_src/cilexer/pycilexer.egg-info/* #------------------------- # Test Files #------------------------- -tests/coverage* +/tests/coverage* # Don't save phpunit under version control. -phpunit +/phpunit #------------------------- # Composer @@ -99,15 +100,15 @@ _modules/* .idea/ *.iml -# Netbeans -nbproject/ -build/ -nbbuild/ -dist/ -nbdist/ -nbactions.xml -nb-configuration.xml -.nb-gradle/ +# NetBeans +/nbproject/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/nbactions.xml +/nb-configuration.xml +/.nb-gradle/ # Sublime Text *.tmlanguage.cache @@ -122,6 +123,4 @@ nb-configuration.xml .vscode/ /results/ -/phpunit*.xml -/.phpunit.*.cache - +/phpunit.xml diff --git a/admin/framework/README.md b/admin/framework/README.md index 1f95a415d37a..dd39ef950968 100644 --- a/admin/framework/README.md +++ b/admin/framework/README.md @@ -3,17 +3,16 @@ ## What is CodeIgniter? CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure. -More information can be found at the [official site](http://codeigniter.com). +More information can be found at the [official site](https://codeigniter.com). -This repository holds the distributable version of the framework, -including the user guide. It has been built from the +This repository holds the distributable version of the framework. +It has been built from the [development repository](https://github.com/codeigniter4/CodeIgniter4). -More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. - -The user guide corresponding to this version of the framework can be found -[here](https://codeigniter4.github.io/userguide/). +More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums. +You can read the [user guide](https://codeigniter.com/user_guide/) +corresponding to the latest version of the framework. ## Important Change with index.php @@ -28,7 +27,7 @@ framework are exposed. ## Repository Management -We use Github issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages. +We use GitHub issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages. We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss FEATURE REQUESTS. @@ -43,14 +42,20 @@ Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/ ## Server Requirements -PHP version 7.3 or higher is required, with the following extensions installed: +PHP version 8.2 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) -- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library +- [mbstring](http://php.net/manual/en/mbstring.installation.php) + +> [!WARNING] +> - The end of life date for PHP 7.4 was November 28, 2022. +> - The end of life date for PHP 8.0 was November 26, 2023. +> - The end of life date for PHP 8.1 was December 31, 2025. +> - If you are still using below PHP 8.2, you should upgrade immediately. +> - The end of life date for PHP 8.2 will be December 31, 2026. Additionally, make sure that the following extensions are enabled in your PHP: - json (enabled by default - don't turn it off) -- [mbstring](http://php.net/manual/en/mbstring.installation.php) -- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) -- xml (enabled by default - don't turn it off) +- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL +- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library diff --git a/admin/framework/composer.json b/admin/framework/composer.json index 6c02a4b71eb9..ab34d89fd260 100644 --- a/admin/framework/composer.json +++ b/admin/framework/composer.json @@ -1,30 +1,54 @@ { "name": "codeigniter4/framework", - "type": "project", "description": "The CodeIgniter framework v4", - "homepage": "https://codeigniter.com", "license": "MIT", + "type": "project", + "homepage": "https://codeigniter.com", + "support": { + "forum": "https://forum.codeigniter.com/", + "source": "https://github.com/codeigniter4/CodeIgniter4", + "slack": "https://codeigniterchat.slack.com" + }, "require": { - "php": "^7.3 || ^8.0", - "ext-curl": "*", + "php": "^8.2", "ext-intl": "*", - "ext-json": "*", "ext-mbstring": "*", - "kint-php/kint": "^3.3", - "laminas/laminas-escaper": "^2.9", - "psr/log": "^1.1" + "laminas/laminas-escaper": "^2.18", + "psr/log": "^3.0" }, "require-dev": { - "codeigniter/coding-standard": "^1.1", - "fakerphp/faker": "^1.9", - "friendsofphp/php-cs-fixer": "^3.1", - "mikey179/vfsstream": "^1.6", - "nexusphp/cs-config": "^3.3", - "phpunit/phpunit": "^9.1", - "predis/predis": "^1.1" + "codeigniter/coding-standard": "^1.7", + "fakerphp/faker": "^1.24", + "friendsofphp/php-cs-fixer": "^3.47.1", + "kint-php/kint": "^6.1", + "mikey179/vfsstream": "^1.6.12", + "nexusphp/cs-config": "^3.6", + "phpunit/phpunit": "^10.5.16 || ^11.2", + "predis/predis": "^3.0" }, "suggest": { - "ext-fileinfo": "Improves mime type detection for files" + "ext-apcu": "If you use Cache class ApcuHandler", + "ext-curl": "If you use CURLRequest class", + "ext-dom": "If you use TestResponse", + "ext-exif": "If you run Image class tests", + "ext-fileinfo": "Improves mime type detection for files", + "ext-gd": "If you use Image class GDHandler", + "ext-imagick": "If you use Image class ImageMagickHandler", + "ext-libxml": "If you use TestResponse", + "ext-memcache": "If you use Cache class MemcachedHandler with Memcache", + "ext-memcached": "If you use Cache class MemcachedHandler with Memcached", + "ext-mysqli": "If you use MySQL", + "ext-oci8": "If you use Oracle Database", + "ext-pcntl": "If you use Signals", + "ext-pgsql": "If you use PostgreSQL", + "ext-posix": "If you use Signals", + "ext-readline": "Improves CLI::input() usability", + "ext-redis": "If you use Cache class RedisHandler", + "ext-simplexml": "If you format XML", + "ext-sodium": "If you use Encryption SodiumHandler", + "ext-sqlite3": "If you use SQLite3", + "ext-sqlsrv": "If you use SQL Server", + "ext-xdebug": "If you use CIUnitTestCase::assertHeaderEmitted()" }, "autoload": { "psr-4": { @@ -34,15 +58,12 @@ "**/Database/Migrations/**" ] }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, "scripts": { - "post-update-cmd": [ - "CodeIgniter\\ComposerScripts::postUpdate" - ], "test": "phpunit" - }, - "support": { - "forum": "http://forum.codeigniter.com/", - "source": "https://github.com/codeigniter4/CodeIgniter4", - "slack": "https://codeigniterchat.slack.com" } } diff --git a/admin/framework/phpunit.dist.xml b/admin/framework/phpunit.dist.xml new file mode 100644 index 000000000000..98e56e2141a5 --- /dev/null +++ b/admin/framework/phpunit.dist.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + ./tests + + + + + + + + + + + + ./app + + + ./app/Views + ./app/Config/Routes.php + + + + + + + + + + + + + + + + diff --git a/admin/framework/phpunit.xml.dist b/admin/framework/phpunit.xml.dist deleted file mode 100644 index 51c5fddc530b..000000000000 --- a/admin/framework/phpunit.xml.dist +++ /dev/null @@ -1,57 +0,0 @@ - - - - - ./app - - - ./app/Views - ./app/Config/Routes.php - - - - - - - - - - - ./tests - - - - - - - - - - - - - - - - - - - diff --git a/admin/generate-changelog.php b/admin/generate-changelog.php new file mode 100644 index 000000000000..f6578cb1372f --- /dev/null +++ b/admin/generate-changelog.php @@ -0,0 +1,153 @@ + [--dry-run] + */ +chdir(__DIR__ . '/..'); + +$args = array_slice($argv, 1); +$options = array_values(array_filter($args, static fn (string $arg): bool => str_starts_with($arg, '--'))); +$params = array_values(array_diff($args, $options)); + +if (count($params) !== 1 || preg_match('/\A\d+\.\d+\.\d+\z/', $params[0]) !== 1) { + echo "Usage: php {$argv[0]} [--dry-run]\n"; + echo "E.g.,: php {$argv[0]} 4.7.5\n"; + + exit(1); +} + +$version = $params[0]; +$dryRun = in_array('--dry-run', $options, true); +$repo = 'codeigniter4/CodeIgniter4'; + +$changelogPath = './CHANGELOG.md'; +$changelog = file_get_contents($changelogPath); + +if (! $dryRun && str_contains($changelog, "## [v{$version}]")) { + echo "CHANGELOG.md already contains an entry for v{$version}.\n"; + + exit(1); +} + +// Fetches the auto-generated release notes. +$command = sprintf( + 'gh api repos/%s/releases/generate-notes -f tag_name=v%s -f target_commitish=develop 2>&1', + $repo, + $version, +); +exec($command, $output, $exitCode); + +if ($exitCode !== 0) { + echo "Failed to fetch the release notes from GitHub:\n"; + echo implode("\n", $output) . "\n"; + + exit(1); +} + +$response = json_decode(implode("\n", $output), true); +$body = $response['body'] ?? ''; + +// Parses the release notes into categories. See ".github/release.yml". +$sections = []; +$current = null; +$previousTag = null; + +foreach (preg_split('/\R/', $body) as $line) { + if (preg_match('/^### (.+)$/', $line, $matches) === 1) { + $current = $matches[1]; + $sections[$current] = []; + + continue; + } + + if (str_starts_with($line, '## ')) { + $current = null; + + continue; + } + + if (preg_match('~^\*\*Full Changelog\*\*: https://github\.com/[^/]+/[^/]+/compare/(.+?)\.\.\.~', $line, $matches) === 1) { + $previousTag = $matches[1]; + + continue; + } + + if ($current !== null && str_starts_with($line, '* ')) { + $sections[$current][] = $line; + } +} + +if ($previousTag === null) { + echo "Could not determine the previous tag from the release notes.\n"; + + exit(1); +} + +// PRs in the catch-all category have none of the changelog labels. They are +// listed for checking only and are not part of the generated entry. +$othersTitle = 'Others (Only for checking. Remove this category)'; +$others = $sections[$othersTitle] ?? []; +unset($sections[$othersTitle]); + +// Extracts the security fixes from the SECURITY section of the detailed +// changelog, if any. These come from security advisories, not from PRs, so +// they are not part of the generated release notes. +$rstPath = "./user_guide_src/source/changelogs/v{$version}.rst"; +$security = ''; + +if (is_file($rstPath)) { + $rst = file_get_contents($rstPath); + + if (preg_match('/^\*+\nSECURITY\n\*+\n(.*?)(?=^\*+$|\z)/msu', $rst, $matches) === 1) { + $security = trim($matches[1], "\n"); + $security = preg_replace('/^- /mu', '* ', $security); + } +} + +$date = date('Y-m-d'); +$entry = "## [v{$version}](https://github.com/{$repo}/tree/v{$version}) ({$date})\n"; +$entry .= "[Full Changelog](https://github.com/{$repo}/compare/{$previousTag}...v{$version})\n"; + +if ($security !== '') { + $entry .= "\n### Security\n\n{$security}\n"; +} + +foreach ($sections as $title => $items) { + if ($items === []) { + continue; + } + + $entry .= "\n### {$title}\n\n"; + $entry .= implode("\n", $items) . "\n"; +} + +if ($others !== []) { + echo "The following PRs have no changelog label and were NOT included.\n"; + echo "If any of them belong in the changelog, label the PR and run this script again:\n"; + echo implode("\n", $others) . "\n\n"; +} + +if ($dryRun) { + echo $entry; + + exit(0); +} + +$updated = preg_replace('/\A# Changelog\n\n/', "# Changelog\n\n{$entry}\n", $changelog, 1, $count); + +if ($count !== 1) { + echo 'Could not find the "# Changelog" header in CHANGELOG.md.' . "\n"; + + exit(1); +} + +file_put_contents($changelogPath, $updated); + +echo "Added the v{$version} entry to CHANGELOG.md.\n"; diff --git a/admin/module/.gitignore b/admin/module/.gitignore deleted file mode 100644 index e82f5252fbc4..000000000000 --- a/admin/module/.gitignore +++ /dev/null @@ -1,127 +0,0 @@ -#------------------------- -# Operating Specific Junk Files -#------------------------- - -# OS X -.DS_Store -.AppleDouble -.LSOverride - -# OS X Thumbnails -._* - -# Windows image file caches -Thumbs.db -ehthumbs.db -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Linux -*~ - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -#------------------------- -# Environment Files -#------------------------- -# These should never be under version control, -# as it poses a security risk. -.env -.vagrant -Vagrantfile - -#------------------------- -# Temporary Files -#------------------------- -writable/cache/* -!writable/cache/index.html - -writable/logs/* -!writable/logs/index.html - -writable/session/* -!writable/session/index.html - -writable/uploads/* -!writable/uploads/index.html - -writable/debugbar/* - -php_errors.log - -#------------------------- -# User Guide Temp Files -#------------------------- -user_guide_src/build/* -user_guide_src/cilexer/build/* -user_guide_src/cilexer/dist/* -user_guide_src/cilexer/pycilexer.egg-info/* - -#------------------------- -# Test Files -#------------------------- -tests/coverage* - -# Don't save phpunit under version control. -phpunit - -#------------------------- -# Composer -#------------------------- -vendor/ -composer.lock - -#------------------------- -# IDE / Development Files -#------------------------- - -# Modules Testing -_modules/* - -# phpenv local config -.php-version - -# Jetbrains editors (PHPStorm, etc) -.idea/ -*.iml - -# Netbeans -nbproject/ -build/ -nbbuild/ -dist/ -nbdist/ -nbactions.xml -nb-configuration.xml -.nb-gradle/ - -# Sublime Text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache -*.sublime-workspace -*.sublime-project -.phpintel -/api/ - -# Visual Studio Code -.vscode/ - -/results/ -/phpunit*.xml -/.phpunit.*.cache diff --git a/admin/module/composer.json b/admin/module/composer.json deleted file mode 100644 index 70197a43010c..000000000000 --- a/admin/module/composer.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "mydomain/mymodule", - "description": "CodeIgniter4 starter module", - "homepage": "https://codeigniter.com", - "license": "MIT", - "require": { - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "codeigniter4/codeigniter4": "dev-develop", - "fakerphp/faker": "^1.9", - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^9.1" - }, - "suggest": { - "ext-fileinfo": "Improves mime type detection for files" - }, - "autoload-dev": { - "psr-4": { - "Tests\\Support\\": "tests/_support" - } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/codeigniter4/codeigniter4" - } - ], - "minimum-stability": "dev", - "scripts": { - "test": "phpunit" - }, - "support": { - "forum": "http://forum.codeigniter.com/", - "source": "https://github.com/codeigniter4/CodeIgniter4", - "slack": "https://codeigniterchat.slack.com" - } -} diff --git a/admin/module/phpunit.xml.dist b/admin/module/phpunit.xml.dist deleted file mode 100644 index 39d1dd2dde54..000000000000 --- a/admin/module/phpunit.xml.dist +++ /dev/null @@ -1,57 +0,0 @@ - - - - - ./src - - - ./src/Views - ./src/Config/Routes.php - - - - - - - - - - - ./tests - - - - - - - - - - - - - - - - - - - diff --git a/admin/module/tests/README.md b/admin/module/tests/README.md deleted file mode 100644 index 3c05eeae92e7..000000000000 --- a/admin/module/tests/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Running Module Tests - -This is the quick-start to CodeIgniter testing. Its intent is to describe what -it takes to set up your module and get it ready to run unit tests. -It is not intended to be a full description of the test features that you can -use to test your module. Those details can be found in the documentation. - -## Resources -* [CodeIgniter 4 User Guide on Testing](https://codeigniter4.github.io/userguide/testing/index.html) -* [PHPUnit docs](https://phpunit.readthedocs.io/en/8.5/index.html) - -## Requirements - -It is recommended to use the latest version of PHPUnit. At the time of this -writing we are running version 8.5.13. Support for this has been built into the -**composer.json** file that ships with CodeIgniter and can easily be installed -via [Composer](https://getcomposer.org/) if you don't already have it installed globally. - - > composer install - -If running under OS X or Linux, you can create a symbolic link to make running tests a touch nicer. - - > ln -s ./vendor/bin/phpunit ./phpunit - -You also need to install [XDebug](https://xdebug.org/index.php) in order -for code coverage to be calculated successfully. - -## Setting Up - -A number of the tests use a running database. -In order to set up the database edit the details for the `tests` group in -**phpunit.xml**. Make sure that you provide a database engine that is currently running -on your machine. More details on a test database setup are in the -*Docs>>Testing>>Testing Your Database* section of the documentation. - -## Running the tests - -The entire test suite can be run by simply typing one command-line command from the main directory. - - > ./phpunit - -You can limit tests to those within a single test directory by specifying the -directory name after phpunit. - - > ./phpunit app/Models - -## Generating Code Coverage - -To generate coverage information, including HTML reports you can view in your browser, -you can use the following command: - - > ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m - -This runs all of the tests again collecting information about how many lines, -functions, and files are tested. It also reports the percentage of the code that is covered by tests. -It is collected in two formats: a simple text file that provides an overview as well -as a comprehensive collection of HTML files that show the status of every line of code in the project. - -The text file can be found at **tests/coverage.txt**. -The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser. - -## PHPUnit XML Configuration - -The repository has a ``phpunit.xml.dist`` file in the project root that's used for -PHPUnit configuration. This is used to provide a default configuration if you -do not have your own configuration file in the project root. - -The normal practice would be to copy ``phpunit.xml.dist`` to ``phpunit.xml`` -(which is git ignored), and to tailor it as you see fit. -For instance, you might wish to exclude database tests, or automatically generate -HTML code coverage reports. - -## Test Cases - -Every test needs a *test case*, or class that your tests extend. CodeIgniter 4 -provides a few that you may use directly: -* `CodeIgniter\Test\CIUnitTestCase` - for basic tests with no other service needs -* `CodeIgniter\Test\DatabaseTestTrait` - for tests that need database access - -Most of the time you will want to write your own test cases to hold functions and services -common to your test suites. - -## Creating Tests - -All tests go in the **tests/** directory. Each test file is a class that extends a -**Test Case** (see above) and contains methods for the individual tests. These method -names must start with the word "test" and should have descriptive names for precisely what -they are testing: -`testUserCanModifyFile()` `testOutputColorMatchesInput()` `testIsLoggedInFailsWithInvalidUser()` - -Writing tests is an art, and there are many resources available to help learn how. -Review the links above and always pay attention to your code coverage. - -### Database Tests - -Tests can include migrating, seeding, and testing against a mock or live1 database. -Be sure to modify the test case (or create your own) to point to your seed and migrations -and include any additional steps to be run before tests in the `setUp()` method. - -1 Note: If you are using database tests that require a live database connection -you will need to rename **phpunit.xml.dist** to **phpunit.xml**, uncomment the database -configuration lines and add your connection details. Prevent **phpunit.xml** from being -tracked in your repo by adding it to **.gitignore**. diff --git a/admin/module/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php b/admin/module/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php deleted file mode 100644 index 2bbdcfe51994..000000000000 --- a/admin/module/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php +++ /dev/null @@ -1,37 +0,0 @@ -forge->addField('id'); - $this->forge->addField([ - 'name' => ['type' => 'varchar', 'constraint' => 31], - 'uid' => ['type' => 'varchar', 'constraint' => 31], - 'class' => ['type' => 'varchar', 'constraint' => 63], - 'icon' => ['type' => 'varchar', 'constraint' => 31], - 'summary' => ['type' => 'varchar', 'constraint' => 255], - 'created_at' => ['type' => 'datetime', 'null' => true], - 'updated_at' => ['type' => 'datetime', 'null' => true], - 'deleted_at' => ['type' => 'datetime', 'null' => true], - ]); - - $this->forge->addKey('name'); - $this->forge->addKey('uid'); - $this->forge->addKey(['deleted_at', 'id']); - $this->forge->addKey('created_at'); - - $this->forge->createTable('factories'); - } - - public function down() - { - $this->forge->dropTable('factories'); - } -} diff --git a/admin/module/tests/_support/Database/Seeds/ExampleSeeder.php b/admin/module/tests/_support/Database/Seeds/ExampleSeeder.php deleted file mode 100644 index f67bf8fb8cae..000000000000 --- a/admin/module/tests/_support/Database/Seeds/ExampleSeeder.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Test Factory', - 'uid' => 'test001', - 'class' => 'Factories\Tests\NewFactory', - 'icon' => 'fas fa-puzzle-piece', - 'summary' => 'Longer sample text for testing', - ], - [ - 'name' => 'Widget Factory', - 'uid' => 'widget', - 'class' => 'Factories\Tests\WidgetPlant', - 'icon' => 'fas fa-puzzle-piece', - 'summary' => 'Create widgets in your factory', - ], - [ - 'name' => 'Evil Factory', - 'uid' => 'evil-maker', - 'class' => 'Factories\Evil\MyFactory', - 'icon' => 'fas fa-book-dead', - 'summary' => 'Abandon all hope, ye who enter here', - ], - ]; - - $builder = $this->db->table('factories'); - - foreach ($factories as $factory) { - $builder->insert($factory); - } - } -} diff --git a/admin/module/tests/_support/DatabaseTestCase.php b/admin/module/tests/_support/DatabaseTestCase.php deleted file mode 100644 index fd067a585c87..000000000000 --- a/admin/module/tests/_support/DatabaseTestCase.php +++ /dev/null @@ -1,61 +0,0 @@ -mockSession(); - } - - /** - * Pre-loads the mock session driver into $this->session. - * - * @var string - */ - protected function mockSession() - { - $config = config('App'); - $this->session = new MockSession(new ArrayHandler($config, '0.0.0.0'), $config); - \Config\Services::injectMock('session', $this->session); - } -} diff --git a/admin/module/tests/database/ExampleDatabaseTest.php b/admin/module/tests/database/ExampleDatabaseTest.php deleted file mode 100644 index 203b1261c7e0..000000000000 --- a/admin/module/tests/database/ExampleDatabaseTest.php +++ /dev/null @@ -1,45 +0,0 @@ -findAll(); - - // Make sure the count is as expected - $this->assertCount(3, $objects); - } - - public function testSoftDeleteLeavesRow() - { - $model = new ExampleModel(); - $this->setPrivateProperty($model, 'useSoftDeletes', true); - $this->setPrivateProperty($model, 'tempUseSoftDeletes', true); - - $object = $model->first(); - $model->delete($object->id); - - // The model should no longer find it - $this->assertNull($model->find($object->id)); - - // ... but it should still be in the database - $result = $model->builder()->where('id', $object->id)->get()->getResult(); - - $this->assertCount(1, $result); - } -} diff --git a/admin/module/tests/session/ExampleSessionTest.php b/admin/module/tests/session/ExampleSessionTest.php deleted file mode 100644 index 65dd37c6b498..000000000000 --- a/admin/module/tests/session/ExampleSessionTest.php +++ /dev/null @@ -1,21 +0,0 @@ -session->set('logged_in', 123); - - $value = $this->session->get('logged_in'); - - $this->assertSame(123, $value); - } -} diff --git a/admin/module/tests/unit/ExampleTest.php b/admin/module/tests/unit/ExampleTest.php deleted file mode 100644 index 4306ddfe55d5..000000000000 --- a/admin/module/tests/unit/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertTrue($test); - } -} diff --git a/admin/next-changelog-minor.rst b/admin/next-changelog-minor.rst new file mode 100644 index 000000000000..c22f0be7d9f3 --- /dev/null +++ b/admin/next-changelog-minor.rst @@ -0,0 +1,84 @@ +################# +Version {version} +################# + +Release Date: Unreleased + +**{version} release of CodeIgniter4** + +.. contents:: + :local: + :depth: 3 + +********** +Highlights +********** + +- TBD + +******** +BREAKING +******** + +Behavior Changes +================ + +Interface Changes +================= + +Method Signature Changes +======================== + +************ +Enhancements +************ + +Commands +======== + +Testing +======= + +Database +======== + +Query Builder +------------- + +Forge +----- + +Others +------ + +Model +===== + +Libraries +========= + +Helpers and Functions +===================== + +Others +====== + +*************** +Message Changes +*************** + +******* +Changes +******* + +************ +Deprecations +************ + +********** +Bugs Fixed +********** + +See the repo's +`CHANGELOG.md `_ +for a complete list of bugs fixed. diff --git a/admin/next-changelog-patch.rst b/admin/next-changelog-patch.rst new file mode 100644 index 000000000000..ed2ba70570b3 --- /dev/null +++ b/admin/next-changelog-patch.rst @@ -0,0 +1,35 @@ +################# +Version {version} +################# + +Release Date: Unreleased + +**{version} release of CodeIgniter4** + +.. contents:: + :local: + :depth: 3 + +******** +BREAKING +******** + +*************** +Message Changes +*************** + +******* +Changes +******* + +************ +Deprecations +************ + +********** +Bugs Fixed +********** + +See the repo's +`CHANGELOG.md `_ +for a complete list of bugs fixed. diff --git a/admin/next-upgrading-guide.rst b/admin/next-upgrading-guide.rst new file mode 100644 index 000000000000..e38bebe3c4a7 --- /dev/null +++ b/admin/next-upgrading-guide.rst @@ -0,0 +1,55 @@ +############################## +Upgrading from {version} to {version} +############################## + +Please refer to the upgrade instructions corresponding to your installation method. + +- :ref:`Composer Installation App Starter Upgrading ` +- :ref:`Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading ` +- :ref:`Manual Installation Upgrading ` + +.. contents:: + :local: + :depth: 2 + +********************** +Mandatory File Changes +********************** + +**************** +Breaking Changes +**************** + +********************* +Breaking Enhancements +********************* + +************* +Project Files +************* + +Some files in the **project space** (root, app, public, writable) received updates. Due to +these files being outside of the **system** scope they will not be changed without your intervention. + +.. note:: There are some third-party CodeIgniter modules available to assist + with merging changes to the project space: + `Explore on Packagist `_. + +Content Changes +=============== + +The following files received significant changes (including deprecations or visual adjustments) +and it is recommended that you merge the updated versions with your application: + +Config +------ + +- @TODO + +All Changes +=========== + +This is a list of all files in the **project space** that received changes; +many will be simple comments or formatting that have no effect on the runtime: + +- @TODO diff --git a/admin/next.rst b/admin/next.rst deleted file mode 100644 index 94e4dcad7f4c..000000000000 --- a/admin/next.rst +++ /dev/null @@ -1,14 +0,0 @@ -Version |version| -==================================================== - -Release Date: Not released - -**Next alpha release of CodeIgniter4** - - -The list of changed files follows, with PR numbers shown. - - -PRs merged: ------------ - diff --git a/admin/pre-commit b/admin/pre-commit index 4f1e7d11f544..19bec4f4726b 100644 --- a/admin/pre-commit +++ b/admin/pre-commit @@ -20,47 +20,14 @@ if [ "$STAGED_PHP_FILES" != "" ]; then done fi -if [ "$FILES" != "" ]; then - echo "Running PHPStan..." - - # Run on whole codebase - if [ -d /proc/cygdrive ]; then - ./vendor/bin/phpstan analyse - else - php ./vendor/bin/phpstan analyse - fi - - if [ $? != 0 ]; then - echo "Fix the phpstan error(s) before commit." - exit 1 - fi -fi - if [ "$FILES" != "" ]; then echo "Running PHP CS Fixer..." # Run on whole codebase to skip on unnecessary filtering - # Run first on app, admin, public - if [ -d /proc/cygdrive ]; then - ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff --config=.no-header.php-cs-fixer.dist.php - else - php ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff --config=.no-header.php-cs-fixer.dist.php - fi - - if [ $? != 0 ]; then - echo "Files in app, admin, or public are not following the coding standards. Please fix them before commit." - exit 1 - fi - - # Next, run on system, tests, utils, and root PHP files - if [ -d /proc/cygdrive ]; then - ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff - else - php ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff - fi + composer cs if [ $? != 0 ]; then - echo "Files in system, tests, utils, or root are not following the coding standards. Please fix them before commit." + echo "There are PHP files which are not following the coding standards. Please fix them before commit." exit 1 fi fi diff --git a/admin/prepare-release.php b/admin/prepare-release.php new file mode 100644 index 000000000000..d68ae249b604 --- /dev/null +++ b/admin/prepare-release.php @@ -0,0 +1,88 @@ +" . PHP_EOL; + echo "E.g.,: php {$argv[0]} 4.4.3" . PHP_EOL; + + exit(1); +} + +// Gets version number from argument. +$version = $argv[1]; // e.g., '4.4.3' +$versionParts = explode('.', $version); +$minor = $versionParts[0] . '.' . $versionParts[1]; + +// Note: Major version will change someday (4.x..5.x) - update manually. +$nextMinor = $versionParts[0] . '.' . $versionParts[1] + 1; + +// Creates a branch for release. +system('git switch develop'); +system('git branch -D release-' . $version); +system('git switch -c release-' . $version); + +// Updates version number in "CodeIgniter.php". +replace_file_content( + './system/CodeIgniter.php', + '/public const CI_VERSION = \'.*?\';/u', + "public const CI_VERSION = '{$version}';", +); + +// Updates version number in "conf.py". +replace_file_content( + './user_guide_src/source/conf.py', + '/^version = \'.*?\'/mu', + "version = '{$minor}'", +); +replace_file_content( + './user_guide_src/source/conf.py', + '/^release = \'.*?\'/mu', + "release = '{$version}'", +); + +// Updates version number in "phpdoc.dist.xml". +replace_file_content( + './phpdoc.dist.xml', + '!CodeIgniter v.*? API!mu', + "CodeIgniter v{$minor} API", +); +replace_file_content( + './phpdoc.dist.xml', + '//mu', + "", +); + +// Updates release date in changelogs. +$date = date('F j, Y'); +replace_file_content( + "./user_guide_src/source/changelogs/v{$version}.rst", + '/^Release Date: .*/mu', + "Release Date: {$date}", +); + +// Update appstarter/builds script +replace_file_content( + './admin/starter/builds', + '/define\(\'LATEST_RELEASE\', \'.*?\'\);/mu', + "define('LATEST_RELEASE', '^{$minor}');", +); +replace_file_content( + './admin/starter/builds', + '/define\(\'NEXT_MINOR\', \'.*?\'\);/mu', + "define('NEXT_MINOR', '^{$nextMinor}-dev');", +); + +// Commits +system('git add -u'); +system('git commit -m "Prep for ' . $version . ' release"'); diff --git a/admin/release b/admin/release deleted file mode 100755 index d14167f0502e..000000000000 --- a/admin/release +++ /dev/null @@ -1,145 +0,0 @@ -#!/bin/bash - -## Build framework release branch & distributables - -# Setup variables -. admin/release-config - -echo -e "${BOLD}${COLOR}CodeIgniter4 release builder${NORMAL}" -echo '----------------------------' - -#--------------------------------------------------- -# Check arguments -echo -e "${BOLD}Checking arguments...${NORMAL}" - -if [ $# -lt 1 ]; then - echo -e "${BOLD}Usage: admin/release version# pre-release-qualifier${NORMAL}" - exit 1 -fi - -#--------------------------------------------------- -# Create the release branch -echo -e "${BOLD}Creating $which $branch to $action ${NORMAL}" - -git checkout develop -git branch -d $branch &>/dev/null # remove the branch if there -git checkout -b $branch -composer update - -#--------------------------------------------------- -# Update version dependencies -echo -e "${BOLD}Updating version dependencies${NORMAL}" - -function check_unique { - count=`grep -c '$1' < $2 | wc -l` - if [ $count -ne 1 ]; then - echo -e "${BOLD}${COLOR}$2 has ${count} occurrences of '$1'${NORMAL}" - exit 1 - fi -} - -# Make sure there is only one line to affect in each file -check_unique "const CI_VERSION" 'system/CodeIgniter.php' -check_unique "release =" 'user_guide_src/source/conf.py' -check_unique "|release|" 'user_guide_src/source/changelogs/index.rst' -check_unique "Release Date.*Not Released" 'user_guide_src/source/changelogs/index.rst' - -# CI_VERSION definition in system/CodeIgniter.php -sed -i "/const CI_VERSION/s/'.*'/'${RELEASE}'/" system/CodeIgniter.php - -# release substitution variable in user_guide_src/source/conf.py -sed -i "/release =/s/'.*'/'${RELEASE}'/" user_guide_src/source/conf.py - -# version & date in user_guide_src/source/index.rst -sed -i "/|release|/s/|.*|/${RELEASE}/" user_guide_src/source/changelogs/index.rst -sed -i "/Release Date/s/Not Released/$(date +'%B %d, %Y')/" user_guide_src/source/changelogs/index.rst -sed -i "/|version|/s/|version|/${RELEASE}/" user_guide_src/source/changelogs/index.rst - -# version & date in user_guide_src/source/next.rst -sed -i "/Release Date/s/Not Released/$(date +'%B %d, %Y')/" user_guide_src/source/changelogs/next.rst -sed -i "/|version|/s/|version|/${RELEASE}/" user_guide_src/source/changelogs/next.rst - -# establish version-specific changelog -sed -i "|changelogs/next|s|changeslog/next|changelogs/v{$RELEASE}|" user_guide_src/source/changelogs/index.rst -mv user_guide_src/source/changelogs/next.rst user_guide_src/source/changelogs/v${RELEASE}.rst -touch user_guide_src/source/changelogs/next.rst -cp admin/next.rst user_guide_src/source/changelogs/next.rst - -#--------------------------------------------------- -# Setup the distribution folders -echo -e "${BOLD}Building distribution folders${NORMAL}" - -function setup_repo { - echo -e "${BOLD}... $1${NORMAL}" - if [ -d dist/$1 ]; then - rm -rf dist/$1 - fi - mkdir dist/$1 - cd dist/$1 - git init - git remote add origin ${CI_ORG}/$1.git - git fetch - git checkout master - git checkout -b $branch - cd $CI_DIR -} - -if [ -d dist ]; then - rm -rf dist/ -fi -mkdir dist - -setup_repo userguide - -#--------------------------------------------------- -# Housekeeping - make sure writable is flushed of test files -# at least, test files that crop up on my system :-/ -rm -f writable/cache/H* -rm -f writable/cache/d* -rm -f writable/cache/s* -rm -f writable/debugbar/debug* -rm -f writable/logs/log* - -#--------------------------------------------------- -# Generate the user guide -echo -e "${BOLD}Generate the user guide${NORMAL}" - -cd user_guide_src - -# make the UG -rm -rf build/* -echo -e "${BOLD}... HTML version${NORMAL}" -make html -touch build/html/.nojekyll -echo -e "${BOLD}... epub version${NORMAL}" -make epub - -cd ${CI_DIR} - -# add changelog preamble -file=user_guide_src/source/changelogs/index.rst -sed -i "4 a Version |version|" $file -sed -i "5 a ====================================================" $file -sed -i "6 G" $file -sed -i "7 a Release Date: Not Released" $file -sed -i "8 G" $file -sed -i "9 a **Next release of CodeIgniter4**" $file -sed -i "10 G" $file -sed -i "11 G" $file -sed -i "12 a :doc:\`See all the changes. \`" $file -sed -i "13 G" $file - -#--------------------------------------------------- -echo -e "${BOLD}Commit the release branch${NORMAL}" -git add . -git commit -S -m "Release ${RELEASE}" - -#--------------------------------------------------- -# Build the distributables - -. admin/release-userguide - -#--------------------------------------------------- -# Done for now -echo -e "${BOLD}Your $branch branch is ready to inspect.${NORMAL}" -echo -e "${BOLD}Follow the directions in workflow.md to continue.${NORMAL}" diff --git a/admin/release-config b/admin/release-config deleted file mode 100644 index 8a969af81c68..000000000000 --- a/admin/release-config +++ /dev/null @@ -1,26 +0,0 @@ -# Variables used for release building - -if [ -z "$CI_ORG" ]; then - - # Initialize variables - CI_ORG=https://github.com/codeigniter4 - CI_DIR=`pwd` - - BOLD='\033[1m' - NORMAL='\033[0m' - COLOR='\033[1;31m' - ERROR='\033[0;31m' - - qualifier= - which=release - - version=$1 - if [ $# -gt 1 ]; then - qualifier="-${2}" - which='pre-release' - fi - - RELEASE=$version$qualifier - branch="release-$RELEASE" - -fi diff --git a/admin/release-deploy b/admin/release-deploy deleted file mode 100755 index fe70284024fc..000000000000 --- a/admin/release-deploy +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -## Push local changes to github - -echo -e "${BOLD}${COLOR}CodeIgniter4 release deployment${NORMAL}" -echo '-------------------------------' - -. admin/release-config - -echo -e "${BOLD}Merge release into develop${NORMAL}" -git checkout develop -git merge $branch -git push origin develop -git push ${CI_ORG}/CodeIgniter4 develop - -echo -e "${BOLD}Merge develop into master${NORMAL}" -git checkout master -git merge develop -git push origin master -git push ${CI_ORG}/CodeIgniter4 master - -echo -e "${BOLD}Pushing to the user guide repository${NORMAL}" -cd ${CI_DIR}/dist/userguide -git push origin master - -cd ${CI_DIR} - -#--------------------------------------------------- -# Phew! -echo -e "${BOLD}Congratulations - we have liftoff${NORMAL}" -echo "Don't forget to announce this release on the forum and on twitter!" diff --git a/admin/release-notes.bb b/admin/release-notes.bb deleted file mode 100755 index 6147a011cde1..000000000000 --- a/admin/release-notes.bb +++ /dev/null @@ -1,43 +0,0 @@ -CAUTION: THIS FILE IS A MIX OF BBCODE & MARKDOWN... NEEDS PROOFING - -CodeIgniter-4.0.0-alpha.1 launches today, after a lengthy build-up :) - -Huge shoutout to Lonnie Ezell for all of his hard work getting the vision -and the core implementation in place! - -This is an early pre-release of 4.0.0. It is not suitable for production! - -There are several possible downloads, that you can see on the -[url=https://github.com/codeigniter4/CodeIgniter4/releases/tag/v4.0.0-alpha.1]release page[/url]) - -- the runnable versions as a -[zip](https://github.com/codeigniter4/CodeIgniter4/releases/download/v4.0.0-alpha.1/CodeIgniter-4.0.0-alpha.1.zip) or a -[tarball](https://github.com/codeigniter4/CodeIgniter4/releases/download/v4.0.0-alpha.1/CodeIgniter-4.0.0-alpha.1.tar.gz)/ -- the developer versions of the framework (with contributor components) as -a [zip](https://github.com/codeigniter4/CodeIgniter4/archive/v4.0.0-alpha.1.zip) or a -[tarball](https://github.com/codeigniter4/CodeIgniter4/archive/v4.0.0-alpha.1.tar.gz)/ -- and finally the [epub](https://github.com/codeigniter4/CodeIgniter4/releases/download/v4.0.0-alpha.1/CodeIgniter-4.0.0-alpha.1.epub) version of the user guide for this release. - -The release has all the major features in place, but there are still gaps -and issues. See ... - -- [Bugs in the 4.0.0-alpha.1](https://github.com/codeigniter4/CodeIgniter4/issues?q=is%3Aopen+is%3Aissue+milestone%3A4.0.0-alpha) -- [Things that we definitely want to see fixed before first proper release](https://github.com/codeigniter4/CodeIgniter4/issues?q=is%3Aopen+is%3Aissue+milestone%3A4.0.0) -- [Features we would like to see in the initial proper release](), if possible, else the next update to it -- [Features that we are consciously deferring to later](https://github.com/codeigniter4/CodeIgniter4/issues?q=is%3Aopen+is%3Aissue+milestone%3A4.1.0) - -What we need now is feedback from the community ... - -- What doesn't work well (raise a post in [url=https://forum.codeigniter.com/forum-27.html]CodeIgniter 4 Development[/url]). -If you have found a bug, then by all means create a new issue on the repo. -- What you think is missing (raise a post in [url=https://forum.codeigniter.com/forum-29.html]CodeIgniter 4 Feature Requests[/url]) -We will soon start the [url=https://forum.codeigniter.com/forum-33.html]candidate new feature[/url] posts, -for those items in the feature requests forum that appear to have traction -- Problems that you are having using the release (raise a post in [url=https://forum.codeigniter.com/forum-30.html]CodeIgniter 4 Support[/url]) -Do NOT create repo issues with support questions - we are using github for bug and work package tracking - -Do NOT post support questions or feature requests in response to this thread - those -will be deleted. We are trying to make the best of the -limited resources that we have! - -Thank you, and ENJOY! \ No newline at end of file diff --git a/admin/release-revert b/admin/release-revert deleted file mode 100755 index 1c8a4eacd504..000000000000 --- a/admin/release-revert +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -## Revert local repos to pre-release state -echo -e "${BOLD}${COLOR}CodeIgniter4 release revert${NORMAL}" -echo '---------------------------' - -if [ $# -lt 2 ]; then - echo "You forgot the magic word" - exit 1 -fi -if [ $1 != 'please' ]; then - echo "What do you say?" - exit 1 -fi - -. admin/release-config - -echo -e "${BOLD}Reverting the main repository${NORMAL}" -git checkout master -git pull -f ${CI_ORG}/CodeIgniter4 master -git checkout develop -git pull -f${CI_ORG}/CodeIgniter4 develop - -#--------------------------------------------------- -# Phew! -echo -e "${BOLD}Congratulations - we have aborted liftoff${NORMAL}" diff --git a/admin/release-userguide b/admin/release-userguide deleted file mode 100755 index 6a8beb7bcbaf..000000000000 --- a/admin/release-userguide +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -## Build user guide distributable - -# Setup variables -. admin/release-config -TARGET=dist/userguide -cd $TARGET -git checkout $branch - -#--------------------------------------------------- -echo -e "${BOLD}Build the user guide distributable${NORMAL}" - -cp -rf ${CI_DIR}/user_guide_src/build/html/* docs -cp -rf ${CI_DIR}/user_guide_src/build/epub/CodeIgniter4.epub ./CodeIgniter${RELEASE}.epub - -#--------------------------------------------------- -# And finally, get ready for merging -echo -e "${BOLD}Assemble the pieces...${NORMAL}" -git add . -git commit -S -m "Release ${RELEASE}" -git checkout master -git merge $branch - -cd $CI_DIR - -#--------------------------------------------------- -# Done for now -echo -e "${BOLD}Distributable user guide ready..${NORMAL}" diff --git a/admin/starter/.github/workflows/close-pull-request.yml b/admin/starter/.github/workflows/close-pull-request.yml new file mode 100644 index 000000000000..ed698e3e33c1 --- /dev/null +++ b/admin/starter/.github/workflows/close-pull-request.yml @@ -0,0 +1,24 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened, reopened] + +permissions: + pull-requests: write + +jobs: + main: + runs-on: ubuntu-24.04 + steps: + - name: Close PR with nice message + 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 + 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/.github/workflows/phpunit.yml b/admin/starter/.github/workflows/phpunit.yml index 5e37bd91e504..308e0565835f 100644 --- a/admin/starter/.github/workflows/phpunit.yml +++ b/admin/starter/.github/workflows/phpunit.yml @@ -2,27 +2,31 @@ name: PHPUnit on: pull_request: - branches: + branches: - develop +permissions: + contents: read + jobs: main: name: Build and test strategy: matrix: - php-versions: ['7.3', '7.4'] - - runs-on: ubuntu-latest + php-versions: ['8.2', '8.5'] - if: "!contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ubuntu-24.04 + if: (! contains(github.event.pull_request.title, '[ci skip]')) steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0 with: php-version: ${{ matrix.php-versions }} tools: composer, pecl, phpunit @@ -31,17 +35,17 @@ jobs: - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v2 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ steps.composer-cache.outputs.COMPOSER_CACHE_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies - run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + run: composer install --no-progress --no-interaction # To prevent rate limiting you may need to supply an OAuth token in Settings > Secrets # env: # https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens diff --git a/admin/starter/.gitignore b/admin/starter/.gitignore index 11abea699408..56b9f10d9160 100644 --- a/admin/starter/.gitignore +++ b/admin/starter/.gitignore @@ -48,37 +48,38 @@ Vagrantfile #------------------------- # Temporary Files #------------------------- -writable/cache/* -!writable/cache/index.html +/writable/cache/* +!/writable/cache/index.html -writable/logs/* -!writable/logs/index.html +/writable/logs/* +!/writable/logs/index.html -writable/session/* -!writable/session/index.html +/writable/session/* +!/writable/session/index.html -writable/uploads/* -!writable/uploads/index.html +/writable/uploads/* +!/writable/uploads/index.html -writable/debugbar/* +/writable/debugbar/* +!/writable/debugbar/index.html php_errors.log #------------------------- # User Guide Temp Files #------------------------- -user_guide_src/build/* -user_guide_src/cilexer/build/* -user_guide_src/cilexer/dist/* -user_guide_src/cilexer/pycilexer.egg-info/* +/user_guide_src/build/* +/user_guide_src/cilexer/build/* +/user_guide_src/cilexer/dist/* +/user_guide_src/cilexer/pycilexer.egg-info/* #------------------------- # Test Files #------------------------- -tests/coverage* +/tests/coverage* # Don't save phpunit under version control. -phpunit +/phpunit #------------------------- # Composer @@ -99,15 +100,15 @@ _modules/* .idea/ *.iml -# Netbeans -nbproject/ -build/ -nbbuild/ -dist/ -nbdist/ -nbactions.xml -nb-configuration.xml -.nb-gradle/ +# NetBeans +/nbproject/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/nbactions.xml +/nb-configuration.xml +/.nb-gradle/ # Sublime Text *.tmlanguage.cache @@ -122,6 +123,4 @@ nb-configuration.xml .vscode/ /results/ -/phpunit*.xml -/.phpunit.*.cache - +/phpunit.xml diff --git a/admin/starter/README.md b/admin/starter/README.md index 41ce92110d7a..45f98af6033e 100644 --- a/admin/starter/README.md +++ b/admin/starter/README.md @@ -3,16 +3,16 @@ ## What is CodeIgniter? CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure. -More information can be found at the [official site](http://codeigniter.com). +More information can be found at the [official site](https://codeigniter.com). This repository holds a composer-installable app starter. It has been built from the [development repository](https://github.com/codeigniter4/CodeIgniter4). -More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. +More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums. -The user guide corresponding to this version of the framework can be found -[here](https://codeigniter4.github.io/userguide/). +You can read the [user guide](https://codeigniter.com/user_guide/) +corresponding to the latest version of the framework. ## Installation & updates @@ -41,7 +41,7 @@ framework are exposed. ## Repository Management -We use Github issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages. +We use GitHub issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages. We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss FEATURE REQUESTS. @@ -50,14 +50,20 @@ Problems with it can be raised on our forum, or as issues in the main repository ## Server Requirements -PHP version 7.3 or higher is required, with the following extensions installed: +PHP version 8.2 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) -- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library +- [mbstring](http://php.net/manual/en/mbstring.installation.php) + +> [!WARNING] +> - The end of life date for PHP 7.4 was November 28, 2022. +> - The end of life date for PHP 8.0 was November 26, 2023. +> - The end of life date for PHP 8.1 was December 31, 2025. +> - If you are still using below PHP 8.2, you should upgrade immediately. +> - The end of life date for PHP 8.2 will be December 31, 2026. Additionally, make sure that the following extensions are enabled in your PHP: - json (enabled by default - don't turn it off) -- [mbstring](http://php.net/manual/en/mbstring.installation.php) -- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) -- xml (enabled by default - don't turn it off) +- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL +- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library diff --git a/admin/starter/app/Config/Paths.php b/admin/starter/app/Config/Paths.php index d97bbb35a820..ee2d7ff3ba71 100644 --- a/admin/starter/app/Config/Paths.php +++ b/admin/starter/app/Config/Paths.php @@ -12,6 +12,9 @@ * share a system folder between multiple applications, and more. * * All paths are relative to the project's root folder. + * + * NOTE: This class is required prior to Autoloader instantiation, + * and does not extend BaseConfig. */ class Paths { @@ -22,10 +25,8 @@ class Paths * * This must contain the name of your "system" folder. Include * the path if the folder is not in the same directory as this file. - * - * @var string */ - public $systemDirectory = __DIR__ . '/../../vendor/codeigniter4/framework/system'; + public string $systemDirectory = __DIR__ . '/../../vendor/codeigniter4/framework/system'; /** * --------------------------------------------------------------- @@ -34,14 +35,12 @@ class Paths * * If you want this front controller to use a different "app" * folder than the default one you can set its name here. The folder - * can also be renamed or relocated anywhere on your getServer. If - * you do, use a full getServer path. + * can also be renamed or relocated anywhere on your server. If + * you do, use a full server path. * * @see http://codeigniter.com/user_guide/general/managing_apps.html - * - * @var string */ - public $appDirectory = __DIR__ . '/..'; + public string $appDirectory = __DIR__ . '/..'; /** * --------------------------------------------------------------- @@ -53,10 +52,8 @@ class Paths * need write permission to a single place that can be tucked away * for maximum security, keeping it out of the app and/or * system directories. - * - * @var string */ - public $writableDirectory = __DIR__ . '/../../writable'; + public string $writableDirectory = __DIR__ . '/../../writable'; /** * --------------------------------------------------------------- @@ -64,10 +61,8 @@ class Paths * --------------------------------------------------------------- * * This variable must contain the name of your "tests" directory. - * - * @var string */ - public $testsDirectory = __DIR__ . '/../../tests'; + public string $testsDirectory = __DIR__ . '/../../tests'; /** * --------------------------------------------------------------- @@ -78,8 +73,18 @@ class Paths * contains the view files used by your application. By * default this is in `app/Views`. This value * is used when no value is provided to `Services::renderer()`. + */ + public string $viewDirectory = __DIR__ . '/../Views'; + + /** + * --------------------------------------------------------------- + * ENVIRONMENT DIRECTORY NAME + * --------------------------------------------------------------- * - * @var string + * This variable must contain the name of the directory where + * the .env file is located. + * Please consider security implications when changing this + * value - the directory should not be publicly accessible. */ - public $viewDirectory = __DIR__ . '/../Views'; + public string $envDirectory = __DIR__ . '/../../'; } diff --git a/admin/starter/builds b/admin/starter/builds index 268e7a819c22..c68c0894e93b 100755 --- a/admin/starter/builds +++ b/admin/starter/builds @@ -1,7 +1,9 @@ #!/usr/bin/env php 'vcs', - 'url' => GITHUB_URL, - ]; - } - - // Define the "require" - $array['require']['codeigniter4/codeigniter4'] = 'dev-develop'; - unset($array['require']['codeigniter4/framework']); - } - - // Release - else - { - // Clear 'minimum-stability' - unset($array['minimum-stability']); - - // If the repo is configured then clear it - if (isset($array['repositories'])) - { - // Check for the CodeIgniter repo - foreach ($array['repositories'] as $i => $repository) - { - if ($repository['url'] == GITHUB_URL) - { - unset($array['repositories'][$i]); - break; - } - } - if (empty($array['repositories'])) - { - unset($array['repositories']); - } - } - - // Define the "require" - $array['require']['codeigniter4/framework'] = LATEST_RELEASE; - unset($array['require']['codeigniter4/codeigniter4']); - } - - // Write out a new composer.json - file_put_contents($file, json_encode($array, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES) . PHP_EOL); - $modified[] = $file; - } - else - { - echo 'Warning: Unable to decode composer.json! Skipping...' . PHP_EOL; - } - } - else - { - echo 'Warning: Unable to read composer.json! Skipping...' . PHP_EOL; - } +if (is_file($file)) { + $contents = file_get_contents($file); + + if ((string) $contents !== '') { + $array = json_decode($contents, true); + + if (is_array($array)) { + if ($branch !== 'release') { + $array['minimum-stability'] = 'dev'; + $array['prefer-stable'] = true; + $array['repositories'] ??= []; + + $found = false; + + foreach ($array['repositories'] as $repository) { + if ($repository['url'] === GITHUB_URL) { + $found = true; + break; + } + } + + if (! $found) { + $array['repositories'][] = [ + 'type' => 'vcs', + 'url' => GITHUB_URL, + ]; + } + + $array['require']['codeigniter4/codeigniter4'] = $branch === 'next' ? NEXT_MINOR : DEVELOP_BRANCH; + unset($array['require']['codeigniter4/framework']); + } else { + unset($array['minimum-stability']); + + if (isset($array['repositories'])) { + foreach ($array['repositories'] as $i => $repository) { + if ($repository['url'] === GITHUB_URL) { + unset($array['repositories'][$i]); + break; + } + } + + if ($array['repositories'] === []) { + unset($array['repositories']); + } + } + + $array['require']['codeigniter4/framework'] = LATEST_RELEASE; + unset($array['require']['codeigniter4/codeigniter4']); + } + + file_put_contents($file, json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL); + + $modified[] = $file; + } else { + echo 'Warning: Unable to decode composer.json! Skipping...' . PHP_EOL; + } + } else { + echo 'Warning: Unable to read composer.json! Skipping...' . PHP_EOL; + } } -// Paths config and PHPUnit XMLs $files = [ - __DIR__ . DIRECTORY_SEPARATOR . 'app/Config/Paths.php', - __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml.dist', - __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml', + __DIR__ . DIRECTORY_SEPARATOR . 'app/Config/Paths.php', + __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.dist.xml', + __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml', ]; -foreach ($files as $file) -{ - if (is_file($file)) - { - $contents = file_get_contents($file); - - // Development - if ($dev) - { - $contents = str_replace('vendor/codeigniter4/framework', 'vendor/codeigniter4/codeigniter4', $contents); - } - - // Release - else - { - $contents = str_replace('vendor/codeigniter4/codeigniter4', 'vendor/codeigniter4/framework', $contents); - } - - file_put_contents($file, $contents); - $modified[] = $file; - } -} +foreach ($files as $file) { + if (is_file($file)) { + $contents = file_get_contents($file); + + if ($branch !== 'release') { + $contents = str_replace('vendor/codeigniter4/framework', 'vendor/codeigniter4/codeigniter4', $contents); + } else { + $contents = str_replace('vendor/codeigniter4/codeigniter4', 'vendor/codeigniter4/framework', $contents); + } -if (empty($modified)) -{ - echo 'No files modified' . PHP_EOL; + file_put_contents($file, $contents); + + $modified[] = $file; + } } -else -{ - echo 'The following files were modified:' . PHP_EOL; - foreach ($modified as $file) - { - echo " * {$file}" . PHP_EOL; - } - echo 'Run `composer update` to sync changes with your vendor folder' . PHP_EOL; + +if ($modified === []) { + echo 'No files modified.' . PHP_EOL; +} else { + echo 'The following files were modified:' . PHP_EOL; + + foreach ($modified as $file) { + echo " * {$file}" . PHP_EOL; + } } + +echo 'Run `composer update` to sync changes with your vendor folder.' . PHP_EOL; +echo 'Don\'t forget to update the project files in app folder from "vendor/codeigniter4/*/app/".' . PHP_EOL; diff --git a/admin/starter/composer.json b/admin/starter/composer.json index 9f7dc492cbc8..d47149e0287f 100644 --- a/admin/starter/composer.json +++ b/admin/starter/composer.json @@ -1,25 +1,27 @@ { "name": "codeigniter4/appstarter", - "type": "project", "description": "CodeIgniter4 starter app", - "homepage": "https://codeigniter.com", "license": "MIT", + "type": "project", + "homepage": "https://codeigniter.com", + "support": { + "forum": "https://forum.codeigniter.com/", + "source": "https://github.com/codeigniter4/CodeIgniter4", + "slack": "https://codeigniterchat.slack.com" + }, "require": { - "php": "^7.3 || ^8.0", - "codeigniter4/framework": "^4" + "php": "^8.2", + "codeigniter4/framework": "^4.7" }, "require-dev": { "fakerphp/faker": "^1.9", "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^9.1" - }, - "suggest": { - "ext-fileinfo": "Improves mime type detection for files" + "phpunit/phpunit": "^10.5.16" }, "autoload": { "psr-4": { - "App\\": "app", - "Config\\": "app/Config" + "App\\": "app/", + "Config\\": "app/Config/" }, "exclude-from-classmap": [ "**/Database/Migrations/**" @@ -30,12 +32,12 @@ "Tests\\Support\\": "tests/_support" } }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, "scripts": { "test": "phpunit" - }, - "support": { - "forum": "http://forum.codeigniter.com/", - "source": "https://github.com/codeigniter4/CodeIgniter4", - "slack": "https://codeigniterchat.slack.com" } } diff --git a/admin/starter/phpunit.dist.xml b/admin/starter/phpunit.dist.xml new file mode 100644 index 000000000000..d9d2c6ade852 --- /dev/null +++ b/admin/starter/phpunit.dist.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + ./tests + + + + + + + + + + + + ./app + + + ./app/Views + ./app/Config/Routes.php + + + + + + + + + + + + + + + + diff --git a/admin/starter/phpunit.xml.dist b/admin/starter/phpunit.xml.dist deleted file mode 100644 index df6a6259f73e..000000000000 --- a/admin/starter/phpunit.xml.dist +++ /dev/null @@ -1,57 +0,0 @@ - - - - - ./app - - - ./app/Views - ./app/Config/Routes.php - - - - - - - - - - - ./tests - - - - - - - - - - - - - - - - - - - diff --git a/admin/starter/tests/.htaccess b/admin/starter/tests/.htaccess new file mode 100644 index 000000000000..3462048add78 --- /dev/null +++ b/admin/starter/tests/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/admin/starter/tests/README.md b/admin/starter/tests/README.md index 1edb10176e21..d5b12eea54f7 100644 --- a/admin/starter/tests/README.md +++ b/admin/starter/tests/README.md @@ -1,89 +1,102 @@ # Running Application Tests -This is the quick-start to CodeIgniter testing. Its intent is to describe what -it takes to set up your application and get it ready to run unit tests. -It is not intended to be a full description of the test features that you can +This is the quick-start to CodeIgniter testing. Its intent is to describe what +it takes to set up your application and get it ready to run unit tests. +It is not intended to be a full description of the test features that you can use to test your application. Those details can be found in the documentation. ## Resources -* [CodeIgniter 4 User Guide on Testing](https://codeigniter4.github.io/userguide/testing/index.html) -* [PHPUnit docs](https://phpunit.readthedocs.io/en/8.5/index.html) + +* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html) +* [PHPUnit docs](https://phpunit.de/documentation.html) +* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830) ## Requirements -It is recommended to use the latest version of PHPUnit. At the time of this -writing we are running version 8.5.13. Support for this has been built into the -**composer.json** file that ships with CodeIgniter and can easily be installed +It is recommended to use the latest version of PHPUnit. At the time of this +writing, we are running version 9.x. Support for this has been built into the +**composer.json** file that ships with CodeIgniter and can easily be installed via [Composer](https://getcomposer.org/) if you don't already have it installed globally. - > composer install +```console +> composer install +``` -If running under OS X or Linux, you can create a symbolic link to make running tests a touch nicer. +If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer. - > ln -s ./vendor/bin/phpunit ./phpunit +```console +> ln -s ./vendor/bin/phpunit ./phpunit +``` -You also need to install [XDebug](https://xdebug.org/index.php) in order -for code coverage to be calculated successfully. +You also need to install [XDebug](https://xdebug.org/docs/install) in order +for code coverage to be calculated successfully. After installing `XDebug`, you must add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage. ## Setting Up -A number of the tests use a running database. -In order to set up the database edit the details for the `tests` group in -**app/Config/Database.php** or **phpunit.xml**. Make sure that you provide a database engine -that is currently running on your machine. More details on a test database setup are in the -*Docs>>Testing>>Testing Your Database* section of the documentation. - -If you want to run the tests without using live database you can -exclude @DatabaseLive group. Or make a copy of **phpunit.dist.xml** - -call it **phpunit.xml** - and comment out the named "database". This will make -the tests run quite a bit faster. +A number of the tests use a running database. +In order to set up the database edit the details for the `tests` group in +**app/Config/Database.php** or **.env**. +Make sure that you provide a database engine that is currently running on your machine. +More details on a test database setup are in the +[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation. ## Running the tests The entire test suite can be run by simply typing one command-line command from the main directory. - > ./phpunit +```console +> ./phpunit +``` + +If you are using Windows, use the following command. -You can limit tests to those within a single test directory by specifying the -directory name after phpunit. +```console +> vendor\bin\phpunit +``` - > ./phpunit app/Models +You can limit tests to those within a single test directory by specifying the +directory name after phpunit. + +```console +> ./phpunit app/Models +``` ## Generating Code Coverage -To generate coverage information, including HTML reports you can view in your browser, -you can use the following command: +To generate coverage information, including HTML reports you can view in your browser, +you can use the following command: - > ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m +```console +> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m +``` -This runs all of the tests again collecting information about how many lines, -functions, and files are tested. It also reports the percentage of the code that is covered by tests. -It is collected in two formats: a simple text file that provides an overview as well -as a comprehensive collection of HTML files that show the status of every line of code in the project. +This runs all of the tests again collecting information about how many lines, +functions, and files are tested. It also reports the percentage of the code that is covered by tests. +It is collected in two formats: a simple text file that provides an overview as well +as a comprehensive collection of HTML files that show the status of every line of code in the project. -The text file can be found at **tests/coverage.txt**. +The text file can be found at **tests/coverage.txt**. The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser. ## PHPUnit XML Configuration -The repository has a ``phpunit.xml.dist`` file in the project root that's used for +The repository has a ``phpunit.dist.xml`` file in the project root that's used for PHPUnit configuration. This is used to provide a default configuration if you do not have your own configuration file in the project root. -The normal practice would be to copy ``phpunit.xml.dist`` to ``phpunit.xml`` +The normal practice would be to copy ``phpunit.dist.xml`` to ``phpunit.xml`` (which is git ignored), and to tailor it as you see fit. -For instance, you might wish to exclude database tests, or automatically generate +For instance, you might wish to exclude database tests, or automatically generate HTML code coverage reports. ## Test Cases Every test needs a *test case*, or class that your tests extend. CodeIgniter 4 -provides a few that you may use directly: -* `CodeIgniter\Test\CIUnitTestCase` - for basic tests with no other service needs -* `CodeIgniter\Test\DatabaseTestTrait` - for tests that need database access +provides one class that you may use directly: +* `CodeIgniter\Test\CIUnitTestCase` -Most of the time you will want to write your own test cases to hold functions and services -common to your test suites. +Most of the time you will want to write your own test cases that extend `CIUnitTestCase` +to hold functions and services common to your test suites. ## Creating Tests @@ -98,11 +111,8 @@ Review the links above and always pay attention to your code coverage. ### Database Tests -Tests can include migrating, seeding, and testing against a mock or live1 database. +Tests can include migrating, seeding, and testing against a mock or live database. Be sure to modify the test case (or create your own) to point to your seed and migrations and include any additional steps to be run before tests in the `setUp()` method. - -1 Note: If you are using database tests that require a live database connection -you will need to rename **phpunit.xml.dist** to **phpunit.xml**, uncomment the database -configuration lines and add your connection details. Prevent **phpunit.xml** from being -tracked in your repo by adding it to **.gitignore**. +See [Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) +for details. diff --git a/admin/starter/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php b/admin/starter/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php index 2bbdcfe51994..a73356d3479a 100644 --- a/admin/starter/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php +++ b/admin/starter/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php @@ -8,7 +8,7 @@ class ExampleMigration extends Migration { protected $DBGroup = 'tests'; - public function up() + public function up(): void { $this->forge->addField('id'); $this->forge->addField([ @@ -30,7 +30,7 @@ public function up() $this->forge->createTable('factories'); } - public function down() + public function down(): void { $this->forge->dropTable('factories'); } diff --git a/admin/starter/tests/_support/Database/Seeds/ExampleSeeder.php b/admin/starter/tests/_support/Database/Seeds/ExampleSeeder.php index f67bf8fb8cae..619fc27a7292 100644 --- a/admin/starter/tests/_support/Database/Seeds/ExampleSeeder.php +++ b/admin/starter/tests/_support/Database/Seeds/ExampleSeeder.php @@ -6,7 +6,7 @@ class ExampleSeeder extends Seeder { - public function run() + public function run(): void { $factories = [ [ diff --git a/admin/starter/tests/_support/Libraries/ConfigReader.php b/admin/starter/tests/_support/Libraries/ConfigReader.php index 40d057acbe22..0bb4a8fcf598 100644 --- a/admin/starter/tests/_support/Libraries/ConfigReader.php +++ b/admin/starter/tests/_support/Libraries/ConfigReader.php @@ -2,6 +2,8 @@ namespace Tests\Support\Libraries; +use Config\App; + /** * Class ConfigReader * @@ -9,7 +11,7 @@ * loading external values. Used to read actual local values from * a config file. */ -class ConfigReader extends \Config\App +class ConfigReader extends App { public function __construct() { diff --git a/admin/starter/tests/_support/Models/ExampleModel.php b/admin/starter/tests/_support/Models/ExampleModel.php index a71009e74f8b..f0687e9b1985 100644 --- a/admin/starter/tests/_support/Models/ExampleModel.php +++ b/admin/starter/tests/_support/Models/ExampleModel.php @@ -6,22 +6,18 @@ class ExampleModel extends Model { - protected $table = 'factories'; - protected $primaryKey = 'id'; - + protected $table = 'factories'; + protected $primaryKey = 'id'; protected $returnType = 'object'; protected $useSoftDeletes = false; - - protected $allowedFields = [ + protected $allowedFields = [ 'name', 'uid', 'class', 'icon', 'summary', ]; - - protected $useTimestamps = true; - + protected $useTimestamps = true; protected $validationRules = []; protected $validationMessages = []; protected $skipValidation = false; diff --git a/admin/starter/tests/database/ExampleDatabaseTest.php b/admin/starter/tests/database/ExampleDatabaseTest.php index 5d13836f9dc6..d6c3bb9e5187 100644 --- a/admin/starter/tests/database/ExampleDatabaseTest.php +++ b/admin/starter/tests/database/ExampleDatabaseTest.php @@ -2,6 +2,7 @@ use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; +use Tests\Support\Database\Seeds\ExampleSeeder; use Tests\Support\Models\ExampleModel; /** @@ -11,7 +12,9 @@ final class ExampleDatabaseTest extends CIUnitTestCase { use DatabaseTestTrait; - public function testModelFindAll() + protected $seed = ExampleSeeder::class; + + public function testModelFindAll(): void { $model = new ExampleModel(); @@ -22,12 +25,13 @@ public function testModelFindAll() $this->assertCount(3, $objects); } - public function testSoftDeleteLeavesRow() + public function testSoftDeleteLeavesRow(): void { $model = new ExampleModel(); $this->setPrivateProperty($model, 'useSoftDeletes', true); $this->setPrivateProperty($model, 'tempUseSoftDeletes', true); + /** @var stdClass $object */ $object = $model->first(); $model->delete($object->id); diff --git a/admin/starter/tests/index.html b/admin/starter/tests/index.html new file mode 100644 index 000000000000..b702fbc3967b --- /dev/null +++ b/admin/starter/tests/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/admin/starter/tests/session/ExampleSessionTest.php b/admin/starter/tests/session/ExampleSessionTest.php index 151036412146..33242a477112 100644 --- a/admin/starter/tests/session/ExampleSessionTest.php +++ b/admin/starter/tests/session/ExampleSessionTest.php @@ -7,9 +7,11 @@ */ final class ExampleSessionTest extends CIUnitTestCase { - public function testSessionSimple() + public function testSessionSimple(): void { - $this->session->set('logged_in', 123); - $this->assertSame(123, $this->session->get('logged_in')); + $session = service('session'); + + $session->set('logged_in', 123); + $this->assertSame(123, $session->get('logged_in')); } } diff --git a/admin/starter/tests/unit/HealthTest.php b/admin/starter/tests/unit/HealthTest.php index ab3e2aa1d524..1df24df823a7 100644 --- a/admin/starter/tests/unit/HealthTest.php +++ b/admin/starter/tests/unit/HealthTest.php @@ -2,7 +2,6 @@ use CodeIgniter\Test\CIUnitTestCase; use Config\App; -use Config\Services; use Tests\Support\Libraries\ConfigReader; /** @@ -10,14 +9,14 @@ */ final class HealthTest extends CIUnitTestCase { - public function testIsDefinedAppPath() + public function testIsDefinedAppPath(): void { $this->assertTrue(defined('APPPATH')); } - public function testBaseUrlHasBeenSet() + public function testBaseUrlHasBeenSet(): void { - $validation = Services::validation(); + $validation = service('validation'); $env = false; @@ -28,23 +27,23 @@ public function testBaseUrlHasBeenSet() if ($env) { // BaseURL in .env is a valid URL? - // phpunit.xml.dist sets app.baseURL in $_SERVER + // phpunit.dist.xml sets app.baseURL in $_SERVER // So if you set app.baseURL in .env, it takes precedence $config = new App(); $this->assertTrue( $validation->check($config->baseURL, 'valid_url'), - 'baseURL "' . $config->baseURL . '" in .env is not valid URL' + 'baseURL "' . $config->baseURL . '" in .env is not valid URL', ); } // Get the baseURL in app/Config/App.php - // You can't use Config\App, because phpunit.xml.dist sets app.baseURL + // You can't use Config\App, because phpunit.dist.xml sets app.baseURL $reader = new ConfigReader(); // BaseURL in app/Config/App.php is a valid URL? $this->assertTrue( $validation->check($reader->baseURL, 'valid_url'), - 'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL' + 'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL', ); } } diff --git a/admin/sync-release-branches.php b/admin/sync-release-branches.php new file mode 100644 index 000000000000..4f82a424bc65 --- /dev/null +++ b/admin/sync-release-branches.php @@ -0,0 +1,96 @@ + [--push] + */ +function run_command(string $command): void +{ + echo sprintf("$ %s\n", $command); + system($command, $exitCode); + + if ($exitCode !== 0) { + exit($exitCode); + } +} + +function merge_branch(string $ref): void +{ + echo sprintf("$ git merge %s\n", $ref); + system(sprintf('git merge %s', escapeshellarg($ref)), $exitCode); + + if ($exitCode !== 0) { + echo sprintf("\nMerging %s failed. Resolve the conflicts and run \"git merge --continue\",\n", $ref); + echo "then push with \"git push upstream HEAD\".\n"; + + exit(1); + } +} + +chdir(__DIR__ . '/..'); + +$args = array_slice($argv, 1); +$options = array_values(array_filter($args, static fn (string $arg): bool => str_starts_with($arg, '--'))); +$params = array_values(array_diff($args, $options)); + +$isBranch = static fn (string $name): bool => preg_match('/\A(?:develop|master|\d+\.\d+)\z/', $name) === 1; + +if ( + count($params) !== 2 + || ! $isBranch($params[0]) + || ! $isBranch($params[1]) + || $params[0] === $params[1] + || array_diff($options, ['--push']) !== [] +) { + echo sprintf("Usage: php %s [--push]\n", $argv[0]); + echo sprintf("E.g.,: php %s 4.8 develop --push # merges develop into 4.8\n", $argv[0]); + echo sprintf(" php %s develop master --push # merges master into develop\n", $argv[0]); + echo "Checks out , merges upstream/ and then upstream/ into it.\n"; + echo "Without --push, the result is left unpushed for review.\n"; + + exit(1); +} + +[$target, $source] = $params; +$push = in_array('--push', $options, true); + +exec('git remote get-url upstream 2>&1', $remoteOutput, $exitCode); + +if ($exitCode !== 0) { + echo "The \"upstream\" remote is not configured.\n"; + + exit(1); +} + +exec('git status --porcelain 2>&1', $statusOutput, $exitCode); + +if ($exitCode !== 0 || $statusOutput !== []) { + echo "The working tree is not clean. Commit or stash your changes first.\n"; + + exit(1); +} + +run_command('git fetch upstream'); + +exec(sprintf('git rev-parse --verify --quiet refs/heads/%s 2>&1', escapeshellarg($target)), $verifyOutput, $exitCode); + +if ($exitCode === 0) { + run_command(sprintf('git switch %s', escapeshellarg($target))); +} else { + run_command(sprintf('git switch -c %s upstream/%s', escapeshellarg($target), escapeshellarg($target))); +} + +merge_branch("upstream/{$target}"); +merge_branch("upstream/{$source}"); + +if ($push) { + run_command('git push upstream HEAD'); + + echo sprintf("Merged upstream/%s into %s and pushed to upstream.\n", $source, $target); +} else { + echo sprintf("Merged upstream/%s into %s. Review the result, then run \"git push upstream HEAD\".\n", $source, $target); +} diff --git a/admin/update-upgrade-guide.php b/admin/update-upgrade-guide.php new file mode 100644 index 000000000000..c80f0a75b635 --- /dev/null +++ b/admin/update-upgrade-guide.php @@ -0,0 +1,197 @@ + [--dry-run] + */ +function previous_release_tag(string $version): ?string +{ + // Not `git describe`: the base must be the highest stable tag below the + // given version regardless of the checked out branch, and prerelease + // tags (e.g. v4.0.0-rc.4) must be excluded. + exec("git tag -l 'v*' 2>&1", $tags, $exitCode); + + if ($exitCode !== 0) { + return null; + } + + $tags = array_filter( + $tags, + static fn (string $tag): bool => preg_match('/\Av\d+\.\d+\.\d+\z/', $tag) === 1 + && version_compare($tag, "v{$version}", '<'), + ); + + if ($tags === []) { + return null; + } + + usort($tags, version_compare(...)); + + return end($tags); +} + +/** + * @param list $items + */ +function bullets(array $items): string +{ + return implode("\n", array_map(static fn (string $item): string => "- {$item}", $items)); +} + +chdir(__DIR__ . '/..'); + +$args = array_slice($argv, 1); +$options = array_values(array_filter($args, static fn (string $arg): bool => str_starts_with($arg, '--'))); +$params = array_values(array_diff($args, $options)); + +if (count($params) !== 1 || preg_match('/\A\d+\.\d+\.\d+\z/', $params[0]) !== 1) { + echo sprintf("Usage: php %s [--dry-run]\n", $argv[0]); + echo sprintf("E.g.,: php %s 4.7.5\n", $argv[0]); + + exit(1); +} + +$version = $params[0]; +$dryRun = in_array('--dry-run', $options, true); + +$upgradePath = sprintf('./user_guide_src/source/installation/upgrade_%s.rst', str_replace('.', '', $version)); + +if (! is_file($upgradePath)) { + echo sprintf("%s is not found. Run \"php admin/create-new-changelog.php\" first.\n", $upgradePath); + + exit(1); +} + +$baseTag = previous_release_tag($version); + +if ($baseTag === null) { + echo "Could not determine the previous release tag. Are the tags fetched?\n"; + + exit(1); +} + +echo sprintf("Checking the project space files changed since %s.\n", $baseTag); + +// Paths that are not part of the project space. Keep in sync with the +// distribution repos: `tests/` is not used there, see "admin/starter/tests". +$excludes = [ + ':!.github/', ':!admin/', ':!changelogs/', ':!contributing/', + ':!system/', ':!tests/', ':!user_guide_src/', ':!utils/', + ':!*.json', ':!*.xml', ':!*.dist', ':!rector.php', ':!structarmed.php', + ':!phpstan*', ':!psalm*', ':!.php-cs-fixer.*', ':!LICENSE', ':!CHANGELOG.md', +]; + +$command = sprintf( + 'git diff --name-status %s -- . %s 2>&1', + escapeshellarg($baseTag), + implode(' ', array_map(escapeshellarg(...), $excludes)), +); +exec($command, $diffOutput, $exitCode); + +if ($exitCode !== 0) { + echo "Failed to diff against the latest release tag:\n"; + echo implode("\n", $diffOutput) . "\n"; + + exit(1); +} + +$allChanges = []; +$configChanged = []; +$configNew = []; + +foreach ($diffOutput as $line) { + $fields = preg_split('/\t/', $line); + + if ($fields === false || count($fields) < 2) { + continue; + } + + $status = $fields[0][0]; + $path = end($fields); + + $allChanges[] = $status === 'D' ? "{$path} (deleted)" : $path; + + if (! str_starts_with($path, 'app/Config/')) { + continue; + } + + if ($status === 'A') { + $configNew[] = $path; + } elseif ($status !== 'D') { + $configChanged[] = $path; + } +} + +sort($allChanges); +sort($configChanged); +sort($configNew); + +// Builds the "Config" section contents. +$configContent = bullets($configChanged); + +if ($configNew !== []) { + $configContent .= ($configContent === '' ? '' : "\n\n") . "These files are new in this release:\n\n" . bullets($configNew); +} + +if ($configContent === '') { + $configContent = '- No config files were changed in this release.'; +} + +// Builds the "All Changes" section contents. +$allChangesContent = $allChanges === [] + ? '- No project files were changed in this release.' + : bullets($allChanges); + +if ($dryRun) { + echo "\nConfig\n------\n\n{$configContent}\n"; + echo "\nAll Changes\n===========\n\n{$allChangesContent}\n"; + + exit(0); +} + +$rst = file_get_contents($upgradePath); + +// Fills the "Config" section, only if it still has the placeholder. Entries +// may have been added ahead of time for changes that need discussion, and +// those must not be overwritten. +if (preg_match('/^Config\n------\n\n(.*?)(?=^[^\s-][^\n]*\n(?:-+|=+)$|\z)/msu', $rst, $matches) !== 1) { + echo "Could not find the \"Config\" section in {$upgradePath}. Update it manually.\n"; +} elseif (trim($matches[1]) === '- @TODO') { + $rst = str_replace("Config\n------\n\n{$matches[1]}", "Config\n------\n\n{$configContent}\n\n", $rst); + + echo "The \"Config\" section was updated. Add notes to each entry as needed.\n"; +} else { + $mentioned = []; + + if (preg_match_all('~app/Config/[\w./-]+~u', $matches[1], $found) > 0) { + $mentioned = $found[0]; + } + + $missing = array_diff([...$configChanged, ...$configNew], $mentioned); + + if ($missing === []) { + echo "The \"Config\" section already has content and mentions all changed config files. It was not modified.\n"; + } else { + echo "The \"Config\" section already has content, so it was not modified.\n"; + echo "Merge these missing entries manually:\n" . bullets(array_values($missing)) . "\n"; + } +} + +// Fills the "All Changes" section, replacing any previous list. +$pattern = '/^(All Changes\n===========\n\n.*?:\n\n)(.*?)(?=^\*+$|\z)/msu'; +$updated = preg_replace($pattern, "\$1{$allChangesContent}\n", $rst, 1, $count); + +if ($count !== 1) { + echo "Could not find the \"All Changes\" section in {$upgradePath}. Update it manually.\n"; + + exit(1); +} + +file_put_contents($upgradePath, $updated); + +echo sprintf("The \"All Changes\" section of %s was updated.\n", $upgradePath); +echo "Remember to remove the section titles that have no items.\n"; diff --git a/admin/userguide/.github/scripts/deploy.sh b/admin/userguide/.github/scripts/deploy.sh new file mode 100755 index 000000000000..43e0675db0e6 --- /dev/null +++ b/admin/userguide/.github/scripts/deploy.sh @@ -0,0 +1,23 @@ +#!/bin/sh -e + +# Deploys the User Guide to the production +# website. Triggered by updates to the GitHub +# codeigniter4/userguide repo's master branch. +# See https://github.com/codeigniter4/userguide/blob/master/.github/workflows/deploy.yml + +REPO="/opt/userguide" +SITE="/home/public_html/userguides/userguide4" + +if [ "$(id -u)" = "0" ]; then + echo "Cannot be run as root. Please run as the user for deployment." + exit 1 +fi + +cd "$REPO" +git switch master +git pull + +cp -R "$REPO/docs" "$SITE.new" +mv "$SITE" "$SITE.old" +mv "$SITE.new" "$SITE" +rm -rf "$SITE.old" diff --git a/admin/userguide/.github/workflows/close-pull-request.yml b/admin/userguide/.github/workflows/close-pull-request.yml new file mode 100644 index 000000000000..ed698e3e33c1 --- /dev/null +++ b/admin/userguide/.github/workflows/close-pull-request.yml @@ -0,0 +1,24 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened, reopened] + +permissions: + pull-requests: write + +jobs: + main: + runs-on: ubuntu-24.04 + steps: + - name: Close PR with nice message + 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 + 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/userguide/.github/workflows/deploy.yml b/admin/userguide/.github/workflows/deploy.yml new file mode 100644 index 000000000000..8dad96764a03 --- /dev/null +++ b/admin/userguide/.github/workflows/deploy.yml @@ -0,0 +1,24 @@ +# Deploys the User Guide to the production +# website whenever master branch is updated +name: Deploy Production + +on: + push: + branches: + - master + +permissions: {} + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - name: executing remote ssh commands using ssh key + uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.KEY }} + port: ${{ secrets.PORT }} + script: /opt/userguide/.github/scripts/deploy.sh diff --git a/admin/userguide/.gitignore b/admin/userguide/.gitignore new file mode 100644 index 000000000000..d0eba7f4bdaa --- /dev/null +++ b/admin/userguide/.gitignore @@ -0,0 +1,13 @@ +*/config/development +*/logs/log-*.php +!*/logs/index.html +*/cache/* +!*/cache/index.html +!*/cache/.htaccess + +user_guide_src/build/* +user_guide_src/cilexer/build/* +user_guide_src/cilexer/dist/* +user_guide_src/cilexer/pycilexer.egg-info/* + +/vendor/ diff --git a/admin/userguide/README.md b/admin/userguide/README.md index 10d85692bc38..0ea07075db61 100644 --- a/admin/userguide/README.md +++ b/admin/userguide/README.md @@ -1,20 +1,15 @@ # CodeIgniter 4 User Guide -## What is CodeIgniter? - -CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure. +CodeIgniter 4 is a PHP web framework that is light, fast, flexible, and secure. More information can be found at the [official site](http://codeigniter.com). -This repository holds a composer-installable pre-built user guide for the framework. -It has been built from the -[development repository](https://github.com/codeigniter4/CodeIgniter4). - -More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. - -## Installation & updates +**This is a read-only repository used to publish the user guide for the current release.** +It is built automatically as part of the framework release workflow, and pull +requests are not accepted here. -`composer require codeigniter4/userguide` will install a copy -of the user guide inside your project, at -`vendor/codeigniter4/userguide`. You can then `composer update` whenever -there is a new release of the framework. +Development is done in the [main repository](https://github.com/codeigniter4/codeigniter4). +If you find problems with the user guide, please submit a correcting pull request there. +If you feel that features are missing or unclear, please comment on our +[forum](https://forum.codeigniter.com/index.php), +in the appropriate CodeIgniter4 subforum. diff --git a/admin/userguide/composer.json b/admin/userguide/composer.json deleted file mode 100644 index a8fc259202fd..000000000000 --- a/admin/userguide/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "codeigniter4/userguide", - "type": "project", - "description": "CodeIgniter4 user guide", - "homepage": "https://codeigniter.com", - "license": "MIT", - "require": { - "php": "^7.3 || ^8.0", - "codeigniter4/framework": "^4" - }, - "support": { - "forum": "http://forum.codeigniter.com/", - "source": "https://github.com/codeigniter4/CodeIgniter4", - "slack": "https://codeigniterchat.slack.com" - } -} diff --git a/admin/workflow.md b/admin/workflow.md deleted file mode 100644 index 85e38e018ec2..000000000000 --- a/admin/workflow.md +++ /dev/null @@ -1,92 +0,0 @@ -# Workflow - -The main repo has two branches of interest: "master" (stable) and "develop" (in progress). -There might be other feature branches, but they are not relevant to this process. - -Once "develop" is ready for a new release, the general workflow is to - -release prep (`admin/release `)... -- create a "release" branch from develop -- update version dependencies or constants -- generate version(s) of the user guide -- move or ignore stuff, distinguishing release from development -- test that all is as it should be -- merge the release branch into "master" & "develop" - -After these have been vetted ... -- push the release(s) to github (`admin/release-deploy `) -- **manually** create the release & tag it on GitHub, based on master - Include any supplementary binaries as part of releases. -- Confirm the GitHub release action successfully deploys `appstarter` and `framework` -- **manually** post a sticky announcement thread on the forum -- **manually** tweet the release announcement - -## Assumptions - -You (a maintainer) have forked the main CodeIgniter4 repo, -and the git alias `origin`, in your local clone, refers to your fork. -The `config` script defines an additional alias, `CI_ORG`, which refers to the -CodeIgniter 4 organization on github. -This separation keeps the release branch isolated for any testing you want to do. - -The `develop` branch of the main repo should be "clean", and ready for -a release. This means that the changelog and upgrading instructions -have been suitably edited. - -This script is not intended to deal with hotfixes, i.e. PRs against -`master` that need to also be merged into `develop`, probably -as part of a bug fix minor release. - -## GitHub Action - -There is an action defined to run on any release publish event: -**.github/workflows/deploy.yml**. This will cascade any release made from -the main repo to the distribution repos, `appstarter` and `framework`. In order -for the action to authenticate you must create a Personal Access Token and add it -as a repo secret `ACCESS_TOKEN`. It is recommended that the PAT be to a secure bot -account with organization access that is dedicated for this purpose. - -If for some reason a release needs to be made that should not cascade the easiest -solution is to disable repo actions temporarily. - -## Usage - -Inside a shell prompt, in the project root: - - `admin/release version [qualifier]` - -Nothing is pushed to the repo at this point. You have a chance to vet -the repository changes. - -The "version" should follow semantic versioning, e.g. `4.0.6`, and the -version number should be higher than the current released one. - -The "qualifier" argument is a suffix to add to the version -for a pre-release, e.g. `beta.2` or `rc.41`. - -Examples: -- `admin/release 4.0.0 alpha.1` would prepare the "4.0.0-alpha.1" pre-release PR -- `admin/release 4.0.0` would prepare the "4.0.0" release PR -- `admin/release peanut butter banana` would complain and tell you to read these directions - -Once you have vetted the `dist` folder inside your local repo, you -can merge & push everything with - - `admin/release-deploy please` - -On github.com, create an appropriate release (or pre-release), -with any optional files from the root of `dist`. - -## Release notes - -On launch of a new release, a release notes post should be made in the -announcements subforum. The planned text for it (so it can be previewed -by admins) is in `admin/release_notes.bb`. - -## Audience - -These scripts are intended for use by framework maintainers, -i.e. someone with commit rights on the CI4 repositories. - -You will be prompted for your github credentials and -GPG-signing key as appropriate. diff --git a/app/.htaccess b/app/.htaccess index f24db0accc73..3462048add78 100644 --- a/app/.htaccess +++ b/app/.htaccess @@ -1,6 +1,6 @@ - Require all denied + Require all denied - Deny from all + Deny from all diff --git a/app/Common.php b/app/Common.php index a74d46d1ed5c..95f554425236 100644 --- a/app/Common.php +++ b/app/Common.php @@ -4,12 +4,12 @@ * The goal of this file is to allow developers a location * where they can overwrite core procedural functions and * replace them with their own. This file is loaded during - * the bootstrap process and is called during the frameworks + * the bootstrap process and is called during the framework's * execution. * * This can be looked at as a `master helper` file that is * loaded early on, and may also contain additional functions * that you'd like to use throughout your entire application * - * @see: https://codeigniter4.github.io/CodeIgniter4/ + * @see: https://codeigniter.com/user_guide/extending/common.html */ diff --git a/app/Config/App.php b/app/Config/App.php index 88b295e9a010..b761da772ad1 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -11,51 +11,77 @@ 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/ + * E.g., http://example.com/ + */ + public string $baseURL = 'http://localhost:8080/'; + + /** + * Allowed Hostnames in the Site URL other than the hostname in the baseURL. + * If you want to accept multiple Hostnames, set this. * - * 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. + * E.g., + * When your site URL ($baseURL) is 'http://example.com/', and your site + * also accepts 'http://media.example.com/' and 'http://accounts.example.com/': + * ['media.example.com', 'accounts.example.com'] * - * @var string + * @var list */ - public $baseURL = 'http://localhost:8080/'; + public array $allowedHostnames = []; /** * -------------------------------------------------------------------------- * Index File * -------------------------------------------------------------------------- * - * Typically this will be your index.php file, unless you've renamed it to - * something else. If you are using mod_rewrite to remove the page set this - * variable so that it is blank. - * - * @var string + * Typically, this will be your `index.php` file, unless you've renamed it to + * something else. If you have configured your web server to remove this file + * from your site URIs, set this variable to an empty string. */ - public $indexPage = 'index.php'; + public string $indexPage = 'index.php'; /** * -------------------------------------------------------------------------- * URI PROTOCOL * -------------------------------------------------------------------------- * - * This item determines which getServer global should be used to retrieve the - * URI string. The default setting of 'REQUEST_URI' works for most servers. + * This item determines which server global should be used to retrieve the + * URI string. The default setting of 'REQUEST_URI' works for most servers. * If your links do not seem to work, try one of the other delicious flavors: * - * 'REQUEST_URI' Uses $_SERVER['REQUEST_URI'] - * 'QUERY_STRING' Uses $_SERVER['QUERY_STRING'] - * 'PATH_INFO' Uses $_SERVER['PATH_INFO'] + * 'REQUEST_URI': Uses $_SERVER['REQUEST_URI'] + * 'QUERY_STRING': Uses $_SERVER['QUERY_STRING'] + * 'PATH_INFO': Uses $_SERVER['PATH_INFO'] * * WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded! - * - * @var string */ - public $uriProtocol = 'REQUEST_URI'; + public string $uriProtocol = 'REQUEST_URI'; + + /* + |-------------------------------------------------------------------------- + | Allowed URL Characters + |-------------------------------------------------------------------------- + | + | This lets you specify which characters are permitted within your URLs. + | When someone tries to submit a URL with disallowed characters they will + | get a warning message. + | + | As a security measure you are STRONGLY encouraged to restrict URLs to + | as few characters as possible. + | + | By default, only these are allowed: `a-z 0-9~%.:_-` + | + | Set an empty string to allow all characters -- but only if you are insane. + | + | The configured value is actually a regular expression character group + | and it will be used as: '/\A[]+\z/iu' + | + | DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!! + | + */ + public string $permittedURIChars = 'a-z 0-9~%.:_\-'; /** * -------------------------------------------------------------------------- @@ -66,10 +92,8 @@ class App extends BaseConfig * is viewing the site from. It affects the language strings and other * strings (like currency markers, numbers, etc), that your program * should run under for this request. - * - * @var string */ - public $defaultLocale = 'en'; + public string $defaultLocale = 'en'; /** * -------------------------------------------------------------------------- @@ -80,10 +104,8 @@ class App extends BaseConfig * language to use based on the value of the Accept-Language header. * * If false, no automatic detection will be performed. - * - * @var bool */ - public $negotiateLocale = false; + public bool $negotiateLocale = false; /** * -------------------------------------------------------------------------- @@ -94,9 +116,11 @@ class App extends BaseConfig * by the application in descending order of priority. If no match is * found, the first locale will be used. * - * @var string[] + * IncomingRequest::setLocale() also uses this list. + * + * @var list */ - public $supportedLocales = ['en']; + public array $supportedLocales = ['en']; /** * -------------------------------------------------------------------------- @@ -106,9 +130,10 @@ class App extends BaseConfig * The default timezone that will be used in your application to display * dates with the date helper, and can be retrieved through app_timezone() * - * @var string + * @see https://www.php.net/manual/en/timezones.php for list of timezones + * supported by PHP. */ - public $appTimezone = 'America/Chicago'; + public string $appTimezone = 'UTC'; /** * -------------------------------------------------------------------------- @@ -119,210 +144,20 @@ class App extends BaseConfig * that require a character set to be provided. * * @see http://php.net/htmlspecialchars for a list of supported charsets. - * - * @var string */ - public $charset = 'UTF-8'; + public string $charset = 'UTF-8'; /** * -------------------------------------------------------------------------- - * URI PROTOCOL + * Force Global Secure Requests * -------------------------------------------------------------------------- * * If true, this will force every request made to this application to be * made via a secure connection (HTTPS). If the incoming request is not * secure, the user will be redirected to a secure version of the page - * and the HTTP Strict Transport Security header will be set. - * - * @var bool - */ - public $forceGlobalSecureRequests = false; - - /** - * -------------------------------------------------------------------------- - * Session Driver - * -------------------------------------------------------------------------- - * - * The session storage driver to use: - * - `CodeIgniter\Session\Handlers\FileHandler` - * - `CodeIgniter\Session\Handlers\DatabaseHandler` - * - `CodeIgniter\Session\Handlers\MemcachedHandler` - * - `CodeIgniter\Session\Handlers\RedisHandler` - * - * @var string - */ - public $sessionDriver = 'CodeIgniter\Session\Handlers\FileHandler'; - - /** - * -------------------------------------------------------------------------- - * Session Cookie Name - * -------------------------------------------------------------------------- - * - * The session cookie name, must contain only [0-9a-z_-] characters - * - * @var string - */ - public $sessionCookieName = 'ci_session'; - - /** - * -------------------------------------------------------------------------- - * Session Expiration - * -------------------------------------------------------------------------- - * - * The number of SECONDS you want the session to last. - * Setting to 0 (zero) means expire when the browser is closed. - * - * @var int - */ - public $sessionExpiration = 7200; - - /** - * -------------------------------------------------------------------------- - * Session Save Path - * -------------------------------------------------------------------------- - * - * The location to save sessions to and is driver dependent. - * - * For the 'files' driver, it's a path to a writable directory. - * WARNING: Only absolute paths are supported! - * - * For the 'database' driver, it's a table name. - * Please read up the manual for the format with other session drivers. - * - * IMPORTANT: You are REQUIRED to set a valid save path! - * - * @var string - */ - public $sessionSavePath = WRITEPATH . 'session'; - - /** - * -------------------------------------------------------------------------- - * Session Match IP - * -------------------------------------------------------------------------- - * - * Whether to match the user's IP address when reading the session data. - * - * WARNING: If you're using the database driver, don't forget to update - * your session table's PRIMARY KEY when changing this setting. - * - * @var bool - */ - public $sessionMatchIP = false; - - /** - * -------------------------------------------------------------------------- - * Session Time to Update - * -------------------------------------------------------------------------- - * - * How many seconds between CI regenerating the session ID. - * - * @var int - */ - public $sessionTimeToUpdate = 300; - - /** - * -------------------------------------------------------------------------- - * Session Regenerate Destroy - * -------------------------------------------------------------------------- - * - * Whether to destroy session data associated with the old session ID - * when auto-regenerating the session ID. When set to FALSE, the data - * will be later deleted by the garbage collector. - * - * @var bool - */ - public $sessionRegenerateDestroy = false; - - /** - * -------------------------------------------------------------------------- - * Cookie Prefix - * -------------------------------------------------------------------------- - * - * Set a cookie name prefix if you need to avoid collisions. - * - * @var string - * - * @deprecated use Config\Cookie::$prefix property instead. - */ - public $cookiePrefix = ''; - - /** - * -------------------------------------------------------------------------- - * Cookie Domain - * -------------------------------------------------------------------------- - * - * Set to `.your-domain.com` for site-wide cookies. - * - * @var string - * - * @deprecated use Config\Cookie::$domain property instead. - */ - public $cookieDomain = ''; - - /** - * -------------------------------------------------------------------------- - * Cookie Path - * -------------------------------------------------------------------------- - * - * Typically will be a forward slash. - * - * @var string - * - * @deprecated use Config\Cookie::$path property instead. - */ - public $cookiePath = '/'; - - /** - * -------------------------------------------------------------------------- - * Cookie Secure - * -------------------------------------------------------------------------- - * - * Cookie will only be set if a secure HTTPS connection exists. - * - * @var bool - * - * @deprecated use Config\Cookie::$secure property instead. - */ - public $cookieSecure = false; - - /** - * -------------------------------------------------------------------------- - * Cookie HttpOnly - * -------------------------------------------------------------------------- - * - * Cookie will only be accessible via HTTP(S) (no JavaScript). - * - * @var bool - * - * @deprecated use Config\Cookie::$httponly property instead. + * and the HTTP Strict Transport Security (HSTS) header will be set. */ - public $cookieHTTPOnly = true; - - /** - * -------------------------------------------------------------------------- - * Cookie SameSite - * -------------------------------------------------------------------------- - * - * Configure cookie SameSite setting. Allowed values are: - * - None - * - Lax - * - Strict - * - '' - * - * Alternatively, you can use the constant names: - * - `Cookie::SAMESITE_NONE` - * - `Cookie::SAMESITE_LAX` - * - `Cookie::SAMESITE_STRICT` - * - * Defaults to `Lax` for compatibility with modern browsers. Setting `''` - * (empty string) means default SameSite attribute set by browsers (`Lax`) - * will be set on cookies. If set to `None`, `$cookieSecure` must also be set. - * - * @var string - * - * @deprecated use Config\Cookie::$samesite property instead. - */ - public $cookieSameSite = 'Lax'; + public bool $forceGlobalSecureRequests = false; /** * -------------------------------------------------------------------------- @@ -331,116 +166,21 @@ class App extends BaseConfig * * If your server is behind a reverse proxy, you must whitelist the proxy * IP addresses from which CodeIgniter should trust headers such as - * HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify + * X-Forwarded-For or Client-IP in order to properly identify * the visitor's IP address. * - * You can use both an array or a comma-separated list of proxy addresses, - * as well as specifying whole subnets. Here are a few examples: - * - * Comma-separated: '10.0.1.200,192.168.5.0/24' - * Array: ['10.0.1.200', '192.168.5.0/24'] - * - * @var string|string[] - */ - public $proxyIPs = ''; - - /** - * -------------------------------------------------------------------------- - * CSRF Token Name - * -------------------------------------------------------------------------- - * - * The token name. - * - * @deprecated Use `Config\Security` $tokenName property instead of using this property. - * - * @var string - */ - public $CSRFTokenName = 'csrf_test_name'; - - /** - * -------------------------------------------------------------------------- - * CSRF Header Name - * -------------------------------------------------------------------------- - * - * The header name. - * - * @deprecated Use `Config\Security` $headerName property instead of using this property. - * - * @var string - */ - public $CSRFHeaderName = 'X-CSRF-TOKEN'; - - /** - * -------------------------------------------------------------------------- - * CSRF Cookie Name - * -------------------------------------------------------------------------- - * - * The cookie name. - * - * @deprecated Use `Config\Security` $cookieName property instead of using this property. - * - * @var string - */ - public $CSRFCookieName = 'csrf_cookie_name'; - - /** - * -------------------------------------------------------------------------- - * CSRF Expire - * -------------------------------------------------------------------------- - * - * The number in seconds the token should expire. + * You need to set a proxy IP address or IP address with subnets and + * the HTTP header for the client IP address. * - * @deprecated Use `Config\Security` $expire property instead of using this property. + * Here are some examples: + * [ + * '10.0.1.200' => 'X-Forwarded-For', + * '192.168.5.0/24' => 'X-Real-IP', + * ] * - * @var int + * @var array */ - public $CSRFExpire = 7200; - - /** - * -------------------------------------------------------------------------- - * CSRF Regenerate - * -------------------------------------------------------------------------- - * - * Regenerate token on every submission? - * - * @deprecated Use `Config\Security` $regenerate property instead of using this property. - * - * @var bool - */ - public $CSRFRegenerate = true; - - /** - * -------------------------------------------------------------------------- - * CSRF Redirect - * -------------------------------------------------------------------------- - * - * Redirect to previous page with error on failure? - * - * @deprecated Use `Config\Security` $redirect property instead of using this property. - * - * @var bool - */ - public $CSRFRedirect = true; - - /** - * -------------------------------------------------------------------------- - * CSRF SameSite - * -------------------------------------------------------------------------- - * - * Setting for CSRF SameSite cookie token. Allowed values are: - * - None - * - Lax - * - Strict - * - '' - * - * Defaults to `Lax` as recommended in this link: - * - * @see https://portswigger.net/web-security/csrf/samesite-cookies - * @deprecated Use `Config\Security` $samesite property instead of using this property. - * - * @var string - */ - public $CSRFSameSite = 'Lax'; + public array $proxyIPs = []; /** * -------------------------------------------------------------------------- @@ -457,8 +197,6 @@ class App extends BaseConfig * * @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/ * @see http://www.w3.org/TR/CSP/ - * - * @var bool */ - public $CSPEnabled = false; + public bool $CSPEnabled = false; } diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index ee27e3b2ce18..9a928241a0eb 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -13,7 +13,10 @@ * can find the files as needed. * * NOTE: If you use an identical key in $psr4 or $classmap, then - * the values in this file will overwrite the framework's values. + * the values in this file will overwrite the framework's values. + * + * NOTE: This class is required prior to Autoloader instantiation, + * and does not extend BaseConfig. */ class Autoload extends AutoloadConfig { @@ -25,24 +28,17 @@ class Autoload extends AutoloadConfig * their location on the file system. These are used by the autoloader * to locate files the first time they have been instantiated. * - * The '/app' and '/system' directories are already mapped for you. - * you may change the name of the 'App' namespace if you wish, + * The 'Config' (APPPATH . 'Config') and 'CodeIgniter' (SYSTEMPATH) are + * already mapped for you. + * + * You may change the name of the 'App' namespace if you wish, * but this should be done prior to creating any namespaced classes, * else you will need to modify all of those classes for this to work. * - * Prototype: - *``` - * $psr4 = [ - * 'CodeIgniter' => SYSTEMPATH, - * 'App' => APPPATH - * ]; - *``` - * - * @var array + * @var array|string> */ public $psr4 = [ - APP_NAMESPACE => APPPATH, // For custom app namespace - 'Config' => APPPATH . 'Config', + APP_NAMESPACE => APPPATH, ]; /** @@ -56,11 +52,9 @@ class Autoload extends AutoloadConfig * were being autoloaded through a namespace. * * Prototype: - *``` * $classmap = [ * 'MyClass' => '/path/to/class/file.php' * ]; - *``` * * @var array */ @@ -75,13 +69,24 @@ class Autoload extends AutoloadConfig * or for loading functions. * * Prototype: - * ``` - * $files = [ - * '/path/to/my/file.php', - * ]; - * ``` + * $files = [ + * '/path/to/my/file.php', + * ]; * - * @var array + * @var list */ public $files = []; + + /** + * ------------------------------------------------------------------- + * Helpers + * ------------------------------------------------------------------- + * Prototype: + * $helpers = [ + * 'form', + * ]; + * + * @var list + */ + public $helpers = []; } diff --git a/app/Config/Boot/development.php b/app/Config/Boot/development.php index 05a861258fc9..a868447a8e37 100644 --- a/app/Config/Boot/development.php +++ b/app/Config/Boot/development.php @@ -7,8 +7,10 @@ | In development, we want to show as many errors as possible to help | make sure they don't make it to production. And save us hours of | painful debugging. + | + | If you set 'display_errors' to '1', CI4's detailed error report will show. */ -error_reporting(-1); +error_reporting(E_ALL); ini_set('display_errors', '1'); /* diff --git a/app/Config/Boot/production.php b/app/Config/Boot/production.php index 21d25805674a..1822cf583f82 100644 --- a/app/Config/Boot/production.php +++ b/app/Config/Boot/production.php @@ -6,9 +6,13 @@ |-------------------------------------------------------------------------- | Don't show ANY in production environments. Instead, let the system catch | it and display a generic error message. + | + | If you set 'display_errors' to '1', CI4's detailed error report will show. */ +error_reporting(E_ALL & ~E_DEPRECATED); +// If you want to suppress more types of errors. +// error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); ini_set('display_errors', '0'); -error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); /* |-------------------------------------------------------------------------- diff --git a/app/Config/Boot/testing.php b/app/Config/Boot/testing.php index e07a1d43f43e..40b6ca83cf3f 100644 --- a/app/Config/Boot/testing.php +++ b/app/Config/Boot/testing.php @@ -1,5 +1,11 @@ + * + * @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect + */ + public array $shareConnectionOptions = [ + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_DNS, + ]; + + /** + * -------------------------------------------------------------------------- + * CURLRequest Share Options + * -------------------------------------------------------------------------- + * + * Whether share options between requests or not. + * + * If true, all the options won't be reset between requests. + * It may cause an error request with unnecessary headers. + */ + public bool $shareOptions = false; +} diff --git a/app/Config/Cache.php b/app/Config/Cache.php index 2d1fea90c9d1..38ac5419d84c 100644 --- a/app/Config/Cache.php +++ b/app/Config/Cache.php @@ -2,6 +2,8 @@ namespace Config; +use CodeIgniter\Cache\CacheInterface; +use CodeIgniter\Cache\Handlers\ApcuHandler; use CodeIgniter\Cache\Handlers\DummyHandler; use CodeIgniter\Cache\Handlers\FileHandler; use CodeIgniter\Cache\Handlers\MemcachedHandler; @@ -19,10 +21,8 @@ class Cache extends BaseConfig * * The name of the preferred handler that should be used. If for some reason * it is not available, the $backupHandler will be used in its place. - * - * @var string */ - public $handler = 'file'; + public string $handler = 'file'; /** * -------------------------------------------------------------------------- @@ -32,43 +32,8 @@ class Cache extends BaseConfig * The name of the handler that will be used in case the first one is * unreachable. Often, 'file' is used here since the filesystem is * always available, though that's not always practical for the app. - * - * @var string - */ - public $backupHandler = 'dummy'; - - /** - * -------------------------------------------------------------------------- - * Cache Directory Path - * -------------------------------------------------------------------------- - * - * The path to where cache files should be stored, if using a file-based - * system. - * - * @var string - * - * @deprecated Use the driver-specific variant under $file - */ - public $storePath = WRITEPATH . 'cache/'; - - /** - * -------------------------------------------------------------------------- - * Cache Include Query String - * -------------------------------------------------------------------------- - * - * Whether to take the URL query string into consideration when generating - * output cache files. Valid options are: - * - * false = Disabled - * true = Enabled, take all query parameters into account. - * Please be aware that this may result in numerous cache - * files generated for the same page over and over again. - * array('q') = Enabled, but only take into account the specified list - * of query parameters. - * - * @var bool|string[] */ - public $cacheQueryString = false; + public string $backupHandler = 'dummy'; /** * -------------------------------------------------------------------------- @@ -77,10 +42,8 @@ class Cache extends BaseConfig * * This string is added to all cache item names to help avoid collisions * if you run multiple applications with the same cache engine. - * - * @var string */ - public $prefix = ''; + public string $prefix = ''; /** * -------------------------------------------------------------------------- @@ -92,10 +55,8 @@ class Cache extends BaseConfig * WARNING: This is not used by framework handlers where 60 seconds is * hard-coded, but may be useful to projects and modules. This will replace * the hard-coded value in a future release. - * - * @var int */ - public $ttl = 60; + public int $ttl = 60; /** * -------------------------------------------------------------------------- @@ -105,22 +66,22 @@ class Cache extends BaseConfig * A string of reserved characters that will not be allowed in keys or tags. * Strings that violate this restriction will cause handlers to throw. * Default: {}()/\@: - * Note: The default set is required for PSR-6 compliance. * - * @var string + * NOTE: The default set is required for PSR-6 compliance. */ - public $reservedCharacters = '{}()/\@:'; + public string $reservedCharacters = '{}()/\@:'; /** * -------------------------------------------------------------------------- * File settings * -------------------------------------------------------------------------- + * * Your file storage preferences can be specified below, if you are using * the File driver. * - * @var array + * @var array{storePath?: string, mode?: int} */ - public $file = [ + public array $file = [ 'storePath' => WRITEPATH . 'cache/', 'mode' => 0640, ]; @@ -129,14 +90,15 @@ class Cache extends BaseConfig * ------------------------------------------------------------------------- * Memcached settings * ------------------------------------------------------------------------- + * * Your Memcached servers can be specified below, if you are using * the Memcached drivers. * * @see https://codeigniter.com/user_guide/libraries/caching.html#memcached * - * @var array + * @var array{host?: string, port?: int, weight?: int, raw?: bool} */ - public $memcached = [ + public array $memcached = [ 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 1, @@ -147,17 +109,28 @@ class Cache extends BaseConfig * ------------------------------------------------------------------------- * Redis settings * ------------------------------------------------------------------------- + * * Your Redis server can be specified below, if you are using * the Redis or Predis drivers. * - * @var array + * @var array{ + * host?: string, + * password?: string|null, + * port?: int, + * timeout?: int, + * async?: bool, + * persistent?: bool, + * database?: int + * } */ - public $redis = [ - 'host' => '127.0.0.1', - 'password' => null, - 'port' => 6379, - 'timeout' => 0, - 'database' => 0, + public array $redis = [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + 'async' => false, // specific to Predis and ignored by the native Redis extension + 'persistent' => false, + 'database' => 0, ]; /** @@ -168,9 +141,10 @@ class Cache extends BaseConfig * This is an array of cache engine alias' and class names. Only engines * that are listed here are allowed to be used. * - * @var array + * @var array> */ - public $validHandlers = [ + public array $validHandlers = [ + 'apcu' => ApcuHandler::class, 'dummy' => DummyHandler::class, 'file' => FileHandler::class, 'memcached' => MemcachedHandler::class, @@ -178,4 +152,47 @@ class Cache extends BaseConfig 'redis' => RedisHandler::class, 'wincache' => WincacheHandler::class, ]; + + /** + * -------------------------------------------------------------------------- + * Web Page Caching: Cache Include Query String + * -------------------------------------------------------------------------- + * + * Whether to take the URL query string into consideration when generating + * output cache files. Valid options are: + * + * false = Disabled + * true = Enabled, take all query parameters into account. + * Please be aware that this may result in numerous cache + * files generated for the same page over and over again. + * ['q'] = Enabled, but only take into account the specified list + * of query parameters. + * + * @var bool|list + */ + public $cacheQueryString = false; + + /** + * -------------------------------------------------------------------------- + * Web Page Caching: Cache Status Codes + * -------------------------------------------------------------------------- + * + * HTTP status codes that are allowed to be cached. Only responses with + * these status codes will be cached by the PageCache filter. + * + * Default: [] - Cache all status codes (backward compatible) + * + * Recommended: [200] - Only cache successful responses + * + * You can also use status codes like: + * [200, 404, 410] - Cache successful responses and specific error codes + * [200, 201, 202, 203, 204] - All 2xx successful responses + * + * WARNING: Using [] may cache temporary error pages (404, 500, etc). + * Consider restricting to [200] for production applications to avoid + * caching errors that should be temporary. + * + * @var list + */ + public array $cacheStatusCodes = []; } diff --git a/app/Config/Constants.php b/app/Config/Constants.php index 8f8498a58a0a..fb56bb1c5b03 100644 --- a/app/Config/Constants.php +++ b/app/Config/Constants.php @@ -38,9 +38,9 @@ defined('HOUR') || define('HOUR', 3600); defined('DAY') || define('DAY', 86400); defined('WEEK') || define('WEEK', 604800); -defined('MONTH') || define('MONTH', 2592000); -defined('YEAR') || define('YEAR', 31536000); -defined('DECADE') || define('DECADE', 315360000); +defined('MONTH') || define('MONTH', 2_592_000); +defined('YEAR') || define('YEAR', 31_536_000); +defined('DECADE') || define('DECADE', 315_360_000); /* | -------------------------------------------------------------------------- @@ -67,13 +67,13 @@ | http://tldp.org/LDP/abs/html/exitcodes.html | */ -defined('EXIT_SUCCESS') || define('EXIT_SUCCESS', 0); // no errors -defined('EXIT_ERROR') || define('EXIT_ERROR', 1); // generic error -defined('EXIT_CONFIG') || define('EXIT_CONFIG', 3); // configuration error -defined('EXIT_UNKNOWN_FILE') || define('EXIT_UNKNOWN_FILE', 4); // file not found -defined('EXIT_UNKNOWN_CLASS') || define('EXIT_UNKNOWN_CLASS', 5); // unknown class +defined('EXIT_SUCCESS') || define('EXIT_SUCCESS', 0); // no errors +defined('EXIT_ERROR') || define('EXIT_ERROR', 1); // generic error +defined('EXIT_CONFIG') || define('EXIT_CONFIG', 3); // configuration error +defined('EXIT_UNKNOWN_FILE') || define('EXIT_UNKNOWN_FILE', 4); // file not found +defined('EXIT_UNKNOWN_CLASS') || define('EXIT_UNKNOWN_CLASS', 5); // unknown class defined('EXIT_UNKNOWN_METHOD') || define('EXIT_UNKNOWN_METHOD', 6); // unknown class member -defined('EXIT_USER_INPUT') || define('EXIT_USER_INPUT', 7); // invalid user input -defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database error -defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code -defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code +defined('EXIT_USER_INPUT') || define('EXIT_USER_INPUT', 7); // invalid user input +defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database error +defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code +defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code diff --git a/app/Config/ContentSecurityPolicy.php b/app/Config/ContentSecurityPolicy.php index 6fa5bd7b4cc7..f64a9af22b0a 100644 --- a/app/Config/ContentSecurityPolicy.php +++ b/app/Config/ContentSecurityPolicy.php @@ -15,64 +15,93 @@ */ class ContentSecurityPolicy extends BaseConfig { - //------------------------------------------------------------------------- + // ------------------------------------------------------------------------- // Broadbrush CSP management - //------------------------------------------------------------------------- + // ------------------------------------------------------------------------- /** * Default CSP report context - * - * @var bool */ - public $reportOnly = false; + public bool $reportOnly = false; /** * Specifies a URL where a browser will send reports * when a content security policy is violated. - * - * @var string|null */ - public $reportURI; + public ?string $reportURI = null; + + /** + * Specifies a reporting endpoint to which violation reports ought to be sent. + */ + public ?string $reportTo = null; /** * Instructs user agents to rewrite URL schemes, changing * HTTP to HTTPS. This directive is for websites with * large numbers of old URLs that need to be rewritten. - * - * @var bool */ - public $upgradeInsecureRequests = false; + public bool $upgradeInsecureRequests = false; - //------------------------------------------------------------------------- - // Sources allowed - // Note: once you set a policy to 'none', it cannot be further restricted - //------------------------------------------------------------------------- + // ------------------------------------------------------------------------- + // CSP DIRECTIVES SETTINGS + // NOTE: once you set a policy to 'none', it cannot be further restricted + // ------------------------------------------------------------------------- /** - * Will default to self if not overridden + * Will default to `'self'` if not overridden * - * @var string|string[]|null + * @var list|string|null */ public $defaultSrc; /** * Lists allowed scripts' URLs. * - * @var string|string[] + * @var list|string */ public $scriptSrc = 'self'; + /** + * Specifies valid sources for JavaScript + - -
-
-

getCode() ? ' #' . $exception->getCode() : '') ?>

-

- getMessage())) ?> - getMessage())) ?>" - rel="noreferrer" target="_blank">search → -

-
-
- - -
-

at line

- - -
- -
- -
- -
- - - -
- - -
- -
    - $row) : ?> - -
  1. -

    - - - +

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

    getCode() ? ' #' . $exception->getCode() : '') ?>

    +

    + getMessage())) ?> + getMessage())) ?>" + rel="noreferrer" target="_blank">search → +

    +
    +
    + + +
    +

    at line

    + + +
    + +
    + +
    + +
    + getPrevious()) { + $last = $prevException; + ?> + +
    +    Caused by:
    +    getCode() ? ' #' . $prevException->getCode() : '') ?>
    +
    +    getMessage())) ?>
    +    getMessage())) ?>"
    +       rel="noreferrer" target="_blank">search →
    +    getFile()) . ':' . $prevException->getLine()) ?>
    +    
    + + +
    + + +
    + + + +
    + + +
    + +
      + $row) : ?> + +
    1. +

      + + + - - {PHP internal code} - - - - -   —   - - - ( arguments ) -

      - - - + {PHP internal code} + + + + +   —   + + + ( arguments ) +
      +
      + + getParameters(); } foreach ($row['args'] as $key => $value) : ?> - - - - - - -
      name : "#{$key}") ?>
      -
      - - () - - - - -   —   () - -

      - - - -
      - -
      - -
    2. - - -
    - -
    - - -
    - - + name : "#{$key}") ?> +
    + + + + +
    + + () + + + + +   —   () + +

    + + + +
    + +
    + +
  2. + + +
+ +
+ + +
+ + -

$

- - - - - - - - - - $value) : ?> - - - - - - -
KeyValue
- - - -
- -
- - - - - - -

Constants

- - - - - - - - - - $value) : ?> - - - - - - -
KeyValue
- - - -
- -
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pathuri) ?>
HTTP MethodgetMethod(true)) ?>
IP AddressgetIPAddress()) ?>
Is AJAX Request?isAJAX() ? 'yes' : 'no' ?>
Is CLI Request?isCLI() ? 'yes' : 'no' ?>
Is Secure Request?isSecure() ? 'yes' : 'no' ?>
User AgentgetUserAgent()->getAgentString()) ?>
- - - - - $ + + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + +
+ +
+ + + + + + +

Constants

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + +
+ +
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathgetUri()) ?>
HTTP MethodgetMethod()) ?>
IP AddressgetIPAddress()) ?>
Is AJAX Request?isAJAX() ? 'yes' : 'no' ?>
Is CLI Request?isCLI() ? 'yes' : 'no' ?>
Is Secure Request?isSecure() ? 'yes' : 'no' ?>
User AgentgetUserAgent()->getAgentString()) ?>
+ + + + + - - -

$

- - - - - - - - - - $value) : ?> - - - - - - -
KeyValue
- - - -
- -
- - - - - -
- No $_GET, $_POST, or $_COOKIE Information to show. -
- - - - getHeaders(); ?> - - -

Headers

- - - - - - - - - - - - - - - - - - - -
HeaderValue
getName(), 'html') ?>getValueLine(), 'html') ?>
- - -
- - - + +

$

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + +
+ +
+ + + + + +
+ No $_GET, $_POST, or $_COOKIE Information to show. +
+ + + + headers(); ?> + + +

Headers

+ + + + + + + + + + $value) : ?> + + + + + + +
HeaderValue
+ getValueLine(), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +
+ + +
+ + + setStatusCode(http_response_code()); ?> -
- - - - - -
Response StatusgetStatusCode() . ' - ' . $response->getReason()) ?>
- - getHeaders(); ?> - - - -

Headers

- - - - - - - - - - $value) : ?> - - - - - - -
HeaderValue
getHeaderLine($name), 'html') ?>
- - -
- - -
- - -
    - -
  1. - -
-
- - -
- - - - - - - - - - - - - - - - -
Memory Usage
Peak Memory Usage:
Memory Limit:
- -
- -
- - - - +
+ + + + + +
Response StatusgetStatusCode() . ' - ' . $response->getReasonPhrase()) ?>
+ + headers(); ?> + +

Headers

+ + + + + + + + + + $value) : ?> + + + + + + +
HeaderValue
+ getHeaderLine($name), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +
+ + +
+ + +
+ + +
    + +
  1. + +
+
+ + +
+ + + + + + + + + + + + + + + + +
Memory Usage
Peak Memory Usage:
Memory Limit:
+ +
+ + + + + diff --git a/app/Views/errors/html/production.php b/app/Views/errors/html/production.php index cca49c2ed9c3..2f59a8de1bc7 100644 --- a/app/Views/errors/html/production.php +++ b/app/Views/errors/html/production.php @@ -1,24 +1,24 @@ - - + + - Whoops! + <?= lang('Errors.whoops') ?> - + -
+
-

Whoops!

+

-

We seem to have hit a snag. Please try again later...

+

-
+
diff --git a/app/Views/welcome_message.php b/app/Views/welcome_message.php index 9ee2e427c308..c18eca3c9ed3 100644 --- a/app/Views/welcome_message.php +++ b/app/Views/welcome_message.php @@ -1,229 +1,235 @@ - - Welcome to CodeIgniter 4! - - - - - - - + + Welcome to CodeIgniter 4! + + + + + + +
- - -
- -

Welcome to CodeIgniter

- -

The small framework with powerful features

- -
+ + +
+ +

Welcome to CodeIgniter

+ +

The small framework with powerful features

+ +
@@ -231,91 +237,92 @@
-

About this page

+

About this page

-

The page you are looking at is being generated dynamically by CodeIgniter.

+

The page you are looking at is being generated dynamically by CodeIgniter.

-

If you would like to edit this page you will find it located at:

+

If you would like to edit this page you will find it located at:

-
app/Views/welcome_message.php
+
app/Views/welcome_message.php
-

The corresponding controller for this page can be found at:

+

The corresponding controller for this page can be found at:

-
app/Controllers/Home.php
+
app/Controllers/Home.php
-
+
-

Go further

+

Go further

-

- - Learn -

+

+ + Learn +

-

The User Guide contains an introduction, tutorial, a number of "how to" - guides, and then reference documentation for the components that make up - the framework. Check the User Guide !

+

The User Guide contains an introduction, tutorial, a number of "how to" + guides, and then reference documentation for the components that make up + the framework. Check the User Guide !

-

- - Discuss -

+

+ + Discuss +

-

CodeIgniter is a community-developed open source project, with several - venues for the community members to gather and exchange ideas. View all - the threads on CodeIgniter's forum, or chat on Slack !

+

CodeIgniter is a community-developed open source project, with several + venues for the community members to gather and exchange ideas. View all + the threads on CodeIgniter's forum, or chat on Slack !

-

- - Contribute -

+

+ + Contribute +

-

CodeIgniter is a community driven project and accepts contributions - of code and documentation from the community. Why not - - join us ?

+

CodeIgniter is a community driven project and accepts contributions + of code and documentation from the community. Why not + + join us ?

-
+
-
+
-

Page rendered in {elapsed_time} seconds

+

Page rendered in {elapsed_time} seconds using {memory_usage} MB of memory.

-

Environment:

+

Environment:

-
+
-
+
-

© CodeIgniter Foundation. CodeIgniter is open source project released under the MIT - open source licence.

+

© CodeIgniter Foundation. CodeIgniter is open source project released under the MIT + open source licence.

-
+
- diff --git a/app/index.html b/app/index.html index b702fbc3967b..69df4e1dff68 100644 --- a/app/index.html +++ b/app/index.html @@ -1,7 +1,7 @@ - 403 Forbidden + 403 Forbidden diff --git a/changelogs/CHANGELOG_4.0.md b/changelogs/CHANGELOG_4.0.md new file mode 100644 index 000000000000..57492ecaed6a --- /dev/null +++ b/changelogs/CHANGELOG_4.0.md @@ -0,0 +1,2618 @@ +# Changelog 4.0 + +## [v4.0.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.5) (2021-01-31) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.4...v4.0.5) + +**Deprecations:** + +- `CodeIgniter\Database\ModelFactory` is now deprecated in favor of `CodeIgniter\Config\Factories::models()` +- `CodeIgniter\Config\Config` is now deprecated in favor of `CodeIgniter\Config\Factories::config()` +- HTTP Layer Refactor: Numerous deprecations have been made towards a transition to a PSR-compliant HTTP layer. [See the User Guide](user_guide_src/source/installation/upgrade_405.rst) + +**Mime Type Detection** + +- `Config\Mimes::guessExtensionFromType` now only reverse searches the `$mimes` array if no extension is proposed (i.e., usually not for uploaded files). +- The fallback values of `UploadedFile->getExtension()` and `UploadedFile->guessExtension()` have been changed. `UploadedFile->getExtension()` now returns `$this->getClientExtension()` instead of `''`; `UploadedFile->guessExtension()` now returns `''` instead of `$this->getClientExtension()`. +These changes increase security when handling uploaded files as the client can no longer force a wrong mime type on the application. However, these might affect how file extensions are detected in your application. + +**Implemented enhancements:** + +- Bug: controller routing on modules not working [\#3927](https://github.com/codeigniter4/CodeIgniter4/issues/3927) +- CLI: method prompt should accept array validation rules [\#3766](https://github.com/codeigniter4/CodeIgniter4/issues/3766) +- Validation: permit\_empty, optional arguments [\#3670](https://github.com/codeigniter4/CodeIgniter4/issues/3670) +- php 8 support [\#3498](https://github.com/codeigniter4/CodeIgniter4/issues/3498) +- getRoutesOptions should return the controller and method if available [\#3445](https://github.com/codeigniter4/CodeIgniter4/issues/3445) +- before function in FilterInterface is missing response param [\#2085](https://github.com/codeigniter4/CodeIgniter4/issues/2085) +- Feature Request: Centralized loggedInUser before RC [\#2055](https://github.com/codeigniter4/CodeIgniter4/issues/2055) + +**Fixed bugs:** + +- Bug: UploadedFile::store\(\) can't return null [\#4183](https://github.com/codeigniter4/CodeIgniter4/issues/4183) +- Bug: BaseBuilder::getCompiledDelete\(\) runs real query [\#4180](https://github.com/codeigniter4/CodeIgniter4/issues/4180) +- Bug: Deprecated: Required parameter $userAgent follows optional parameter $body in /opt/lampp/htdocs/framework-4.0.4/system/HTTP/IncomingRequest.php on line 161 [\#4172](https://github.com/codeigniter4/CodeIgniter4/issues/4172) +- Bug: table template closes tbody after tfoot [\#4155](https://github.com/codeigniter4/CodeIgniter4/issues/4155) +- Bug: delete\_cookie\(\) helper not working [\#4149](https://github.com/codeigniter4/CodeIgniter4/issues/4149) +- Bug: Required parameter $userAgent follows optional parameter $body in [\#4148](https://github.com/codeigniter4/CodeIgniter4/issues/4148) +- Bug: spark issue [\#4144](https://github.com/codeigniter4/CodeIgniter4/issues/4144) +- Bug: PostgreSQL driver issues [\#4142](https://github.com/codeigniter4/CodeIgniter4/issues/4142) +- Bug: phpunit coverage report causes `Cannot declare class Config\App, because the name is already in use` [\#4114](https://github.com/codeigniter4/CodeIgniter4/issues/4114) +- Bug: Wrong file/line in exceptions created by "factory" methods [\#4110](https://github.com/codeigniter4/CodeIgniter4/issues/4110) +- Bug: Request::withMethod\(\) uses deprecated code [\#4109](https://github.com/codeigniter4/CodeIgniter4/issues/4109) +- Bug: View rendering, extracted variables overwrites existed variables\(arguments and etc.\) [\#4108](https://github.com/codeigniter4/CodeIgniter4/issues/4108) +- Bug: Loss of escape value and data in the model [\#4087](https://github.com/codeigniter4/CodeIgniter4/issues/4087) +- Bug: classes overwrites parameter defaults, phpDoc's defined in interfaces [\#4086](https://github.com/codeigniter4/CodeIgniter4/issues/4086) +- Bug: getRandomName Return file extension .csv when upload docx or xlsx becouse mimetype application/octet-stream are set [\#4084](https://github.com/codeigniter4/CodeIgniter4/issues/4084) +- Bug: Required parameter $from follows optional parameter $verbs [\#4076](https://github.com/codeigniter4/CodeIgniter4/issues/4076) +- Bug: username input form shows root as a default value [\#4062](https://github.com/codeigniter4/CodeIgniter4/issues/4062) +- Bug: Issue with pagination [\#4045](https://github.com/codeigniter4/CodeIgniter4/issues/4045) +- Bug: Model calls builder before initializing [\#4036](https://github.com/codeigniter4/CodeIgniter4/issues/4036) +- Bug: can't run migrations when CI\_ENVIRONMENT = testing [\#4033](https://github.com/codeigniter4/CodeIgniter4/issues/4033) +- Bug: can't get environment variable with dot [\#4026](https://github.com/codeigniter4/CodeIgniter4/issues/4026) +- Bug: CodeIgniter model not recognizing custom deletedField in find queries [\#3999](https://github.com/codeigniter4/CodeIgniter4/issues/3999) +- Bug: assertSee\(\) can not assert title tag. [\#3984](https://github.com/codeigniter4/CodeIgniter4/issues/3984) +- Bug: RAR file detected as CSV [\#3979](https://github.com/codeigniter4/CodeIgniter4/issues/3979) +- Bug: Session Initialization via DatabaseHandler [\#3978](https://github.com/codeigniter4/CodeIgniter4/issues/3978) +- Bug: required\_with validation rule does not work with arrays [\#3965](https://github.com/codeigniter4/CodeIgniter4/issues/3965) +- Bug: helper cookie not working [\#3939](https://github.com/codeigniter4/CodeIgniter4/issues/3939) +- Bug: Uploaded SRT files are saved as CSV [\#3921](https://github.com/codeigniter4/CodeIgniter4/issues/3921) +- Bug: Pre-commit hook for Phpstan and sniffer stop working after e111f04d74569e413c5aede3ed9bd9fa1ce7dca2 [\#3920](https://github.com/codeigniter4/CodeIgniter4/issues/3920) +- Bug: Documentation Example Leads to Bug [\#3914](https://github.com/codeigniter4/CodeIgniter4/issues/3914) +- Bug: Route filter runs twice [\#3902](https://github.com/codeigniter4/CodeIgniter4/issues/3902) +- Bug: Premature empty check in Model-\>update function. [\#3896](https://github.com/codeigniter4/CodeIgniter4/issues/3896) +- Bug: Sqldrv problems to insert when change DBPrefix [\#3881](https://github.com/codeigniter4/CodeIgniter4/issues/3881) +- Bug: CI4 won't recognise current namespace for language files [\#3867](https://github.com/codeigniter4/CodeIgniter4/issues/3867) +- Unexpected validation bug [\#3859](https://github.com/codeigniter4/CodeIgniter4/issues/3859) +- Bug: single\_service does not set service's arguments correctly [\#3854](https://github.com/codeigniter4/CodeIgniter4/issues/3854) +- Bug: debugbar should not insert code in code tag [\#3847](https://github.com/codeigniter4/CodeIgniter4/issues/3847) +- Parser won't properly parse tags that have a similar name [\#3841](https://github.com/codeigniter4/CodeIgniter4/issues/3841) +- Bug: insertBatch not generating createdField [\#3838](https://github.com/codeigniter4/CodeIgniter4/issues/3838) +- Bug: Parser - Replacements happening even if key does not fully matches { value } [\#3825](https://github.com/codeigniter4/CodeIgniter4/issues/3825) +- Bug: Parser preg\_replace\_callback exception if template has \# [\#3824](https://github.com/codeigniter4/CodeIgniter4/issues/3824) +- Translation function 'lang' does not work as described [\#3822](https://github.com/codeigniter4/CodeIgniter4/issues/3822) +- Bug: File rewrite.php is always lost in large-request unhandled error [\#3818](https://github.com/codeigniter4/CodeIgniter4/issues/3818) +- Bug: Model::builder\(\) ignores parameter [\#3793](https://github.com/codeigniter4/CodeIgniter4/issues/3793) +- Bug: Warning no tests found in CodeIgiter\Tests\... [\#3788](https://github.com/codeigniter4/CodeIgniter4/issues/3788) +- Bug: Class '\CodeIgniter\Database\pdo\Connection' not found [\#3785](https://github.com/codeigniter4/CodeIgniter4/issues/3785) +- Bug: Composer php spark migrate error [\#3771](https://github.com/codeigniter4/CodeIgniter4/issues/3771) +- Bug: ORDER BY RANDOM ON SQLite3 [\#3768](https://github.com/codeigniter4/CodeIgniter4/issues/3768) +- Bug: CLI: generateDimensions fails with a uncaught exception when exec is disabled [\#3762](https://github.com/codeigniter4/CodeIgniter4/issues/3762) +- Bug: createTable if not exists not working [\#3757](https://github.com/codeigniter4/CodeIgniter4/issues/3757) +- Bug: SQLite drop column corrupts table cache [\#3752](https://github.com/codeigniter4/CodeIgniter4/issues/3752) +- Bug: route filters don't apply for different methods [\#3733](https://github.com/codeigniter4/CodeIgniter4/issues/3733) +- /system/Images/Image.php image\(\)-\>copy\(\) mkdir [\#3732](https://github.com/codeigniter4/CodeIgniter4/issues/3732) +- Bug: route bug [\#3731](https://github.com/codeigniter4/CodeIgniter4/issues/3731) +- page not redirecting. when i redirecting by calling function [\#3729](https://github.com/codeigniter4/CodeIgniter4/issues/3729) +- Bug: \Config\Services::image\(\)-\>flatten\(\) [\#3728](https://github.com/codeigniter4/CodeIgniter4/issues/3728) +- Bug: Parser will overwrite the value if the initial variable is same [\#3726](https://github.com/codeigniter4/CodeIgniter4/issues/3726) +- Bug: Validating Json Requests [\#3719](https://github.com/codeigniter4/CodeIgniter4/issues/3719) +- Bug: view caching not work on windows [\#3711](https://github.com/codeigniter4/CodeIgniter4/issues/3711) +- Bug: getRoutesOptions return wrong when I have \>2 routes has same name, but different method \(HTTPVerb\) [\#3700](https://github.com/codeigniter4/CodeIgniter4/issues/3700) +- Bug: Validation with request raw body. [\#3694](https://github.com/codeigniter4/CodeIgniter4/issues/3694) +- Image format webp is not preserve transparent [\#3690](https://github.com/codeigniter4/CodeIgniter4/issues/3690) +- problem: [\#3686](https://github.com/codeigniter4/CodeIgniter4/issues/3686) +- Documentation Error [\#3668](https://github.com/codeigniter4/CodeIgniter4/issues/3668) +- Bug: BaseBuilder's query is wrong [\#3659](https://github.com/codeigniter4/CodeIgniter4/issues/3659) +- Bug: The lifetime of the CSRF check cookie cannot be set to 0 \(Session\). [\#3655](https://github.com/codeigniter4/CodeIgniter4/issues/3655) +- Bug: isRedirect does not respond depending on how redirects are set up. [\#3654](https://github.com/codeigniter4/CodeIgniter4/issues/3654) +- Bug: SQL Error when countAllResults, groupBy and DBPrefix used together [\#3651](https://github.com/codeigniter4/CodeIgniter4/issues/3651) +- Bug: helper current\_url\(\) return wrong protocol [\#3648](https://github.com/codeigniter4/CodeIgniter4/issues/3648) +- Bug: IncomingRequest::setLocale\(\) [\#3640](https://github.com/codeigniter4/CodeIgniter4/issues/3640) +- CodeIgniter\Database\Exceptions\DatabaseException \#8 Unable to connect to the database. [\#3639](https://github.com/codeigniter4/CodeIgniter4/issues/3639) +- Bug: Cannot use view filter on array values [\#3630](https://github.com/codeigniter4/CodeIgniter4/issues/3630) +- Bug: Changelog navigation is not working properly [\#3625](https://github.com/codeigniter4/CodeIgniter4/issues/3625) +- Bug: Routing 404 Override confusion under route group [\#3623](https://github.com/codeigniter4/CodeIgniter4/issues/3623) +- Bug: Target batch not found when running `php spark migrate:rollback` [\#3620](https://github.com/codeigniter4/CodeIgniter4/issues/3620) +- Bug: getHeaders returns Array with no values [\#3616](https://github.com/codeigniter4/CodeIgniter4/issues/3616) +- Ignore this, was a false report [\#3611](https://github.com/codeigniter4/CodeIgniter4/issues/3611) +- Bug: Incorrect type that fails strict\_types=1 [\#3610](https://github.com/codeigniter4/CodeIgniter4/issues/3610) +- The isAJAX method does not exist in filters [\#3604](https://github.com/codeigniter4/CodeIgniter4/issues/3604) +- Bug: `current\_url\(\)` helper returns wrong url with slash between host and port [\#3603](https://github.com/codeigniter4/CodeIgniter4/issues/3603) +- Bug: Connection ID unknown immediately after connection [\#3601](https://github.com/codeigniter4/CodeIgniter4/issues/3601) +- Bug: Join Query is not working in Library [\#3600](https://github.com/codeigniter4/CodeIgniter4/issues/3600) +- Bug: Database cache not working [\#3597](https://github.com/codeigniter4/CodeIgniter4/issues/3597) +- Bug: Issue in the route, redirect to parent domain, if you put / at the end of url [\#3595](https://github.com/codeigniter4/CodeIgniter4/issues/3595) +- Bug: Segments Also Include The Segments In BASE\_URL [\#3594](https://github.com/codeigniter4/CodeIgniter4/issues/3594) +- Bug: Route Filters doesn't restart in FeatureTestCase. [\#3591](https://github.com/codeigniter4/CodeIgniter4/issues/3591) +- Bug: CURL call returns always code 200 in case de server uses HTTP/2 [\#3586](https://github.com/codeigniter4/CodeIgniter4/issues/3586) +- Bug: Language folders inside locale folders NOT Working ! [\#3582](https://github.com/codeigniter4/CodeIgniter4/issues/3582) +- Bug: Uninitialized string offset: 1 CI 4 [\#3573](https://github.com/codeigniter4/CodeIgniter4/issues/3573) +- Deprecated assertArraySubset in PHPUnit8 used in FeatureResponse::assertJSONFragment [\#3562](https://github.com/codeigniter4/CodeIgniter4/issues/3562) +- Wrong HTTP status code [\#3558](https://github.com/codeigniter4/CodeIgniter4/issues/3558) +- Bug: Invalid serialization data for DateTime object [\#3553](https://github.com/codeigniter4/CodeIgniter4/issues/3553) +- Bug: Setting session expires parameter via $this-\>sessionExpiration [\#3543](https://github.com/codeigniter4/CodeIgniter4/issues/3543) +- Bug: Upload file validation, $model-\>validate return true if failed [\#3532](https://github.com/codeigniter4/CodeIgniter4/issues/3532) +- Bug: Inconsistent behavior of view renderer on Windows and Linux [\#3529](https://github.com/codeigniter4/CodeIgniter4/issues/3529) +- Bug: Add dash to parser plugin regex [\#3523](https://github.com/codeigniter4/CodeIgniter4/issues/3523) +- Bug: When Cronjob run the ip address of the request is 0.0.0.0 [\#3512](https://github.com/codeigniter4/CodeIgniter4/issues/3512) +- Bug: Inconsistency in replace\(\) method [\#3510](https://github.com/codeigniter4/CodeIgniter4/issues/3510) +- Bug: setPath function in UploadedFile.php is writing the index.html wrong [\#3506](https://github.com/codeigniter4/CodeIgniter4/issues/3506) +- Bug: Cannot get session data after server redirecting [\#3503](https://github.com/codeigniter4/CodeIgniter4/issues/3503) +- Bug: Database group defined in .env doesn't work [\#3497](https://github.com/codeigniter4/CodeIgniter4/issues/3497) +- Bug: I cant upload mp4 files [\#3494](https://github.com/codeigniter4/CodeIgniter4/issues/3494) +- Bug: Error message for matches rule doesn't support nested params [\#3492](https://github.com/codeigniter4/CodeIgniter4/issues/3492) +- CI\_VERSION = '4.0.0-beta.4'; session 文件file缓存问题失效问题;session\_start\(\): Failed to decode session object. Session has been destroyed [\#3485](https://github.com/codeigniter4/CodeIgniter4/issues/3485) +- Bug: onlyDeleted\(\) conflicts to paginate\(\) [\#3482](https://github.com/codeigniter4/CodeIgniter4/issues/3482) +- Bug: Unable to connect to the database [\#3477](https://github.com/codeigniter4/CodeIgniter4/issues/3477) +- Bug: Argument 1 passed to CodeIgniter\Config\Services::request\(\) must be an instance of Config\App or null, instance of BackEnd\Config\App [\#3475](https://github.com/codeigniter4/CodeIgniter4/issues/3475) +- Bug: Fatal error running [\#3473](https://github.com/codeigniter4/CodeIgniter4/issues/3473) +- Bug: set\(\) doesn't work on DateTime database field types [\#3471](https://github.com/codeigniter4/CodeIgniter4/issues/3471) +- before\(\) Filters that are executed are missing from codeigniter debug toolbar. [\#3470](https://github.com/codeigniter4/CodeIgniter4/issues/3470) +- Bug: Model insert method always insert with current datetime on updatedField [\#3469](https://github.com/codeigniter4/CodeIgniter4/issues/3469) +- Bug: The search function in the documentation is not working [\#3458](https://github.com/codeigniter4/CodeIgniter4/issues/3458) +- Bug: env variable database.default.dsn not working for mysqli [\#3456](https://github.com/codeigniter4/CodeIgniter4/issues/3456) +- Bug: [\#3453](https://github.com/codeigniter4/CodeIgniter4/issues/3453) +- Bug: form\_textarea in form\_helper does not create specified 'rows' [\#3452](https://github.com/codeigniter4/CodeIgniter4/issues/3452) +- Bug: afterUpdate event return array instead of key value [\#3450](https://github.com/codeigniter4/CodeIgniter4/issues/3450) +- Using Cronjob with php line argument while using Crontab redirects the script and never executes [\#3444](https://github.com/codeigniter4/CodeIgniter4/issues/3444) +- Bug: Support for SameSite cookie setting missing [\#3442](https://github.com/codeigniter4/CodeIgniter4/issues/3442) +- Bug: Validation mime\_in SVG files not working correctly [\#3439](https://github.com/codeigniter4/CodeIgniter4/issues/3439) +- Bug: Cannot declare class Config\Paths, because the name is already in use [\#3434](https://github.com/codeigniter4/CodeIgniter4/issues/3434) +- Bug: delete\_cookie\(\) helper function not working [\#3433](https://github.com/codeigniter4/CodeIgniter4/issues/3433) +- Bug: insertBatch not working correctly [\#3432](https://github.com/codeigniter4/CodeIgniter4/issues/3432) +- Feature request : CodeIgniter\File with SplFileInfo does not have a method to rewrite parts of the file only to append an CSV style row [\#3431](https://github.com/codeigniter4/CodeIgniter4/issues/3431) +- Bug: SMTP to port 465 should use TLS from the start [\#3429](https://github.com/codeigniter4/CodeIgniter4/issues/3429) +- Bug: Form data and file is not receiving well formed with PUT Method [\#3417](https://github.com/codeigniter4/CodeIgniter4/issues/3417) +- Bug: form\_textarea helper row and col defaults not overwriting when defined as $extra [\#3412](https://github.com/codeigniter4/CodeIgniter4/issues/3412) +- Encryption Class does not allow to change digest parameter from SHA512 to SHA256 or others [\#3404](https://github.com/codeigniter4/CodeIgniter4/issues/3404) +- Bug: table.Array in first\(\) ... - and complex primary keys! ;-\) [\#3394](https://github.com/codeigniter4/CodeIgniter4/issues/3394) +- Error: Email SMTP configured wrong gives error [\#3390](https://github.com/codeigniter4/CodeIgniter4/issues/3390) +- Bug: CodeIgniter\Router\Router hasLocale returns true even if {locale} is absent [\#3386](https://github.com/codeigniter4/CodeIgniter4/issues/3386) +- Bug: Logs Collector isn't collecting logs for Debug Toolbar [\#3376](https://github.com/codeigniter4/CodeIgniter4/issues/3376) +- Bug: Entity's original is not set [\#3370](https://github.com/codeigniter4/CodeIgniter4/issues/3370) +- Bug: warning in Routes.php [\#3369](https://github.com/codeigniter4/CodeIgniter4/issues/3369) +- Bug: Model can not insert Entity [\#3368](https://github.com/codeigniter4/CodeIgniter4/issues/3368) +- Bug: stringify\_attributes\(\) looks unfinished. [\#3363](https://github.com/codeigniter4/CodeIgniter4/issues/3363) +- Bug: php spark migrate throws wrongful CodeIgniter\Database\Exceptions\DatabaseException Unable to connect to the database [\#3359](https://github.com/codeigniter4/CodeIgniter4/issues/3359) +- Bug: Image watermark after save.. text not align in center middle when resolution lower and higher. [\#3356](https://github.com/codeigniter4/CodeIgniter4/issues/3356) +- Bug: SQL Lite driver with Builder -\> InsertBatch has intermittend lastError reporting issues [\#3350](https://github.com/codeigniter4/CodeIgniter4/issues/3350) +- Bug: isCLI error when calling a controller from a Command Line [\#3342](https://github.com/codeigniter4/CodeIgniter4/issues/3342) +- Bug: missing clear upgrade instructions [\#3332](https://github.com/codeigniter4/CodeIgniter4/issues/3332) +- Bug: API requests with a trailing forward slash [\#3330](https://github.com/codeigniter4/CodeIgniter4/issues/3330) +- Bug: Pager pagination \( page value in url \) [\#3328](https://github.com/codeigniter4/CodeIgniter4/issues/3328) +- Bug: View layout name hierarchy is not displaying correctly on Debugbar Views [\#3327](https://github.com/codeigniter4/CodeIgniter4/issues/3327) +- Bug: php spark migrate on testing environment doesn't work [\#3309](https://github.com/codeigniter4/CodeIgniter4/issues/3309) +- Bug: Empty Entity date attributes receive current timestamp when accessed [\#3251](https://github.com/codeigniter4/CodeIgniter4/issues/3251) +- Bug: no Filter arguments passed [\#3216](https://github.com/codeigniter4/CodeIgniter4/issues/3216) +- Bug: Url Helper have a bug [\#3180](https://github.com/codeigniter4/CodeIgniter4/issues/3180) +- $pager-\>links\(\) not working inside view layouts [\#3164](https://github.com/codeigniter4/CodeIgniter4/issues/3164) +- Bug: AH01075: Error dispatching request on Basic CI4 [\#3110](https://github.com/codeigniter4/CodeIgniter4/issues/3110) +- Bug: Using assertJSONFragment with respond\(\) in ResponseTrait [\#3079](https://github.com/codeigniter4/CodeIgniter4/issues/3079) +- Bug: Filters 'except' option not removing DebugToolbar comment for view [\#3002](https://github.com/codeigniter4/CodeIgniter4/issues/3002) +- Bug: useSoftDeletes with like function [\#2380](https://github.com/codeigniter4/CodeIgniter4/issues/2380) + +**Closed issues:** + +- Seed Command in document Not found [\#4154](https://github.com/codeigniter4/CodeIgniter4/issues/4154) +- I18n/L10n: auto update translations [\#4151](https://github.com/codeigniter4/CodeIgniter4/issues/4151) +- Calling update\(\) with an Entity throws an error [\#4143](https://github.com/codeigniter4/CodeIgniter4/issues/4143) +- BaseConfig property issue [\#4140](https://github.com/codeigniter4/CodeIgniter4/issues/4140) +- Bug: [\#4126](https://github.com/codeigniter4/CodeIgniter4/issues/4126) +- Bug: a new installation of codeignitor 4.0.4 show 404 error Sorry! Cannot seem to find the page you were looking for. [\#4111](https://github.com/codeigniter4/CodeIgniter4/issues/4111) +- Bug PHP SPARK MIGRATE [\#4101](https://github.com/codeigniter4/CodeIgniter4/issues/4101) +- Pagination work in local, but error in server [\#4096](https://github.com/codeigniter4/CodeIgniter4/issues/4096) +- updated\_at field is filled with the current timestamp when inserting new data along with created\_at [\#4038](https://github.com/codeigniter4/CodeIgniter4/issues/4038) +- Config for pager [\#4030](https://github.com/codeigniter4/CodeIgniter4/issues/4030) +- setUpdateBatch [\#4025](https://github.com/codeigniter4/CodeIgniter4/issues/4025) +- Empty html-file created when moving uploaded files with $img-\>move\(\) [\#4020](https://github.com/codeigniter4/CodeIgniter4/issues/4020) +- php spark serve error on php 8 [\#3980](https://github.com/codeigniter4/CodeIgniter4/issues/3980) +- Bug: PHP8 session flush data is not deleted [\#3974](https://github.com/codeigniter4/CodeIgniter4/issues/3974) +- PHP8: Deprecate required param after optional [\#3957](https://github.com/codeigniter4/CodeIgniter4/issues/3957) +- Undefined function [\#3954](https://github.com/codeigniter4/CodeIgniter4/issues/3954) +- Missing function locale\_set\_default\(...\) in Codeigniter 4 [\#3953](https://github.com/codeigniter4/CodeIgniter4/issues/3953) +- Language\en\Language.php is only used in testing [\#3948](https://github.com/codeigniter4/CodeIgniter4/issues/3948) +- Bug: locale\_set\_default causing "Whoops" failure in MacOS PHP 7.3.9 on develop branch [\#3933](https://github.com/codeigniter4/CodeIgniter4/issues/3933) +- Pdo driver not found [\#3922](https://github.com/codeigniter4/CodeIgniter4/issues/3922) +- Mysqli Backup utility? [\#3906](https://github.com/codeigniter4/CodeIgniter4/issues/3906) +- Relationships - Many to many [\#3885](https://github.com/codeigniter4/CodeIgniter4/issues/3885) +- Bug: How can remove Codeinatore default icon [\#3883](https://github.com/codeigniter4/CodeIgniter4/issues/3883) +- How Can remove CodeIgniter4 icon [\#3882](https://github.com/codeigniter4/CodeIgniter4/issues/3882) +- Codeigniter review 2021 [\#3880](https://github.com/codeigniter4/CodeIgniter4/issues/3880) +- url\_title doesn't work with decimal number in title [\#3878](https://github.com/codeigniter4/CodeIgniter4/issues/3878) +- IncomingRequest Class "hasFile" function does not exist as described in the documentation. [\#3852](https://github.com/codeigniter4/CodeIgniter4/issues/3852) +- Dynamically changing supported languages [\#3844](https://github.com/codeigniter4/CodeIgniter4/issues/3844) +- CodeIgniter\Database\Exceptions\DatabaseException \#8 [\#3826](https://github.com/codeigniter4/CodeIgniter4/issues/3826) +- Blank page show [\#3812](https://github.com/codeigniter4/CodeIgniter4/issues/3812) +- Bug: folder tests not found [\#3807](https://github.com/codeigniter4/CodeIgniter4/issues/3807) +- Typo in the doc: cache\_info\(\) [\#3800](https://github.com/codeigniter4/CodeIgniter4/issues/3800) +- only\_full\_group\_by - mysqli\_sql\_exception \#1055 - MySql 5.7.24 [\#3795](https://github.com/codeigniter4/CodeIgniter4/issues/3795) +- Dev: Split ModelTest [\#3792](https://github.com/codeigniter4/CodeIgniter4/issues/3792) +- The formatMessage function of the Language class works intermittently [\#3784](https://github.com/codeigniter4/CodeIgniter4/issues/3784) +- .htaccess problem with syntax [\#3778](https://github.com/codeigniter4/CodeIgniter4/issues/3778) +- The page does not open correctly:\( [\#3770](https://github.com/codeigniter4/CodeIgniter4/issues/3770) +- Restore method for CodeIgniter Model. [\#3767](https://github.com/codeigniter4/CodeIgniter4/issues/3767) +- Bug: Model update\(\) only set first array value [\#3764](https://github.com/codeigniter4/CodeIgniter4/issues/3764) +- Bug: CLI: Exception view shows full path [\#3763](https://github.com/codeigniter4/CodeIgniter4/issues/3763) +- Bug: Maximum execution time exceeds while handling IPv6 Reverse Proxy IPs [\#3760](https://github.com/codeigniter4/CodeIgniter4/issues/3760) +- Fabricators documentation error [\#3743](https://github.com/codeigniter4/CodeIgniter4/issues/3743) +- `is\_unique` is not considering the db prefix in checking for unique values in db [\#3741](https://github.com/codeigniter4/CodeIgniter4/issues/3741) +- Grouping routes should let me pass additional variables within the closure function [\#3691](https://github.com/codeigniter4/CodeIgniter4/issues/3691) +- cannot find Ftp library. [\#3679](https://github.com/codeigniter4/CodeIgniter4/issues/3679) +- Make sessions never expire with $config\['sess\_expiration'\] = -1 [\#3677](https://github.com/codeigniter4/CodeIgniter4/issues/3677) +- Migration trouble [\#3624](https://github.com/codeigniter4/CodeIgniter4/issues/3624) +- Files as optional in validation rules [\#3619](https://github.com/codeigniter4/CodeIgniter4/issues/3619) +- $\_SERVER should not have .env file info [\#3615](https://github.com/codeigniter4/CodeIgniter4/issues/3615) +- Bug pars xml [\#3588](https://github.com/codeigniter4/CodeIgniter4/issues/3588) +- Bug: When have multi filter class, and "before" function return true,other filters after this filter not working [\#3579](https://github.com/codeigniter4/CodeIgniter4/issues/3579) +- can not install on linux os [\#3572](https://github.com/codeigniter4/CodeIgniter4/issues/3572) +- Incorrect grouping function names in documentation [\#3551](https://github.com/codeigniter4/CodeIgniter4/issues/3551) +- New Edge Browser missing from user agents config [\#3513](https://github.com/codeigniter4/CodeIgniter4/issues/3513) +- Form validation does not have a rule to validate if field value differs from a string [\#3462](https://github.com/codeigniter4/CodeIgniter4/issues/3462) +- updateBatch does not accept entities [\#3451](https://github.com/codeigniter4/CodeIgniter4/issues/3451) +- Support PSR4 [\#3405](https://github.com/codeigniter4/CodeIgniter4/issues/3405) +- CodeIgniter4 is ready to work with PHP 7.4? [\#3389](https://github.com/codeigniter4/CodeIgniter4/issues/3389) +- Using CodeIgniter4 with Oracle Database [\#3388](https://github.com/codeigniter4/CodeIgniter4/issues/3388) +- Can't get Controllers subfolder working [\#3347](https://github.com/codeigniter4/CodeIgniter4/issues/3347) +- Why redirect\(\)-\>to\(\) doesn't work inside events Model? [\#3346](https://github.com/codeigniter4/CodeIgniter4/issues/3346) +- Bug: requests PUT type without segment redirects to index method instead update method [\#3343](https://github.com/codeigniter4/CodeIgniter4/issues/3343) +- Migrations not working [\#3317](https://github.com/codeigniter4/CodeIgniter4/issues/3317) +- CURL Error htts SSL [\#3314](https://github.com/codeigniter4/CodeIgniter4/issues/3314) +- codeigniter composer global installer [\#3266](https://github.com/codeigniter4/CodeIgniter4/issues/3266) +- Bug: Migrations Deprecated [\#3195](https://github.com/codeigniter4/CodeIgniter4/issues/3195) +- Managed to reproduce this issue. Was uploading an image with jquery.dm-uploader.min.js, and trying to do some image manipulation, but when I got this error: [\#3174](https://github.com/codeigniter4/CodeIgniter4/issues/3174) +- Make the Model work without auto\_increment primary key [\#3134](https://github.com/codeigniter4/CodeIgniter4/issues/3134) +- Call to undefined function CodeIgniter\Debug\current\_url\(\) [\#3106](https://github.com/codeigniter4/CodeIgniter4/issues/3106) +- Bug: select\(\) & selectSum\(\) not consistent / aligned with docs [\#3019](https://github.com/codeigniter4/CodeIgniter4/issues/3019) +- Remove /public in URL not working if using localhost [\#2930](https://github.com/codeigniter4/CodeIgniter4/issues/2930) +- The problem is in getting the file sharing by Mime [\#2732](https://github.com/codeigniter4/CodeIgniter4/issues/2732) +- Migration on PostgreSQL failes for tables with foreign keys [\#2575](https://github.com/codeigniter4/CodeIgniter4/issues/2575) +- Automatic Entity cast for Parser [\#2317](https://github.com/codeigniter4/CodeIgniter4/issues/2317) +- Feature: Cache Driver - Serialize/Unserialize Objects [\#2111](https://github.com/codeigniter4/CodeIgniter4/issues/2111) +- Model set $escape problem [\#1929](https://github.com/codeigniter4/CodeIgniter4/issues/1929) +- \I18n\Time setTimezone does not work as expected [\#1807](https://github.com/codeigniter4/CodeIgniter4/issues/1807) +- TODO Database utility backup builder [\#1257](https://github.com/codeigniter4/CodeIgniter4/issues/1257) +- Port FTP library from CI3 [\#506](https://github.com/codeigniter4/CodeIgniter4/issues/506) +- Port MSSql Database driver from CI3 [\#503](https://github.com/codeigniter4/CodeIgniter4/issues/503) + +**Merged pull requests:** + +- Finishing touches to generator refactor [\#4197](https://github.com/codeigniter4/CodeIgniter4/pull/4197) ([paulbalandan](https://github.com/paulbalandan)) +- Add additional empty checks after field protection for update/insert. [\#4195](https://github.com/codeigniter4/CodeIgniter4/pull/4195) ([sfadschm](https://github.com/sfadschm)) +- Minor fixes in Common.php [\#4192](https://github.com/codeigniter4/CodeIgniter4/pull/4192) ([kenjis](https://github.com/kenjis)) +- Fix Parser file path in ViewException message is empty [\#4191](https://github.com/codeigniter4/CodeIgniter4/pull/4191) ([kenjis](https://github.com/kenjis)) +- docs: Fix double "the" [\#4190](https://github.com/codeigniter4/CodeIgniter4/pull/4190) ([kenjis](https://github.com/kenjis)) +- Fix typo in IncomingRequestTest [\#4189](https://github.com/codeigniter4/CodeIgniter4/pull/4189) ([kenjis](https://github.com/kenjis)) +- feat: add methods to get page numbers in PagerRenderer [\#4188](https://github.com/codeigniter4/CodeIgniter4/pull/4188) ([kenjis](https://github.com/kenjis)) +- fix: UploadedFile::store\(\) return type inconsistancy [\#4187](https://github.com/codeigniter4/CodeIgniter4/pull/4187) ([kenjis](https://github.com/kenjis)) +- Add STL mime support [\#4186](https://github.com/codeigniter4/CodeIgniter4/pull/4186) ([MGatner](https://github.com/MGatner)) +- \[Rector\] Run Rector when composer.json updated [\#4185](https://github.com/codeigniter4/CodeIgniter4/pull/4185) ([samsonasik](https://github.com/samsonasik)) +- new array helper: array\_flatten\_with\_dots [\#4184](https://github.com/codeigniter4/CodeIgniter4/pull/4184) ([paulbalandan](https://github.com/paulbalandan)) +- fix: BaseBuilder::getCompiledDelete\(\) runs real query [\#4181](https://github.com/codeigniter4/CodeIgniter4/pull/4181) ([kenjis](https://github.com/kenjis)) +- fix the missing / hidden userguide [\#4175](https://github.com/codeigniter4/CodeIgniter4/pull/4175) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Revert "Update phpstan/phpstan requirement from 0.12.69 to 0.12.70" [\#4173](https://github.com/codeigniter4/CodeIgniter4/pull/4173) ([samsonasik](https://github.com/samsonasik)) +- Add missing escaping in error\_exception.php [\#4171](https://github.com/codeigniter4/CodeIgniter4/pull/4171) ([kenjis](https://github.com/kenjis)) +- Update phpstan/phpstan requirement from 0.12.69 to 0.12.70 [\#4170](https://github.com/codeigniter4/CodeIgniter4/pull/4170) ([dependabot[bot]](https://github.com/apps/dependabot)) +- add phpunit.xml.dist to .gitattributes for admin/framework [\#4163](https://github.com/codeigniter4/CodeIgniter4/pull/4163) ([samsonasik](https://github.com/samsonasik)) +- Fix strict checking for SQLite3 memory filename [\#4161](https://github.com/codeigniter4/CodeIgniter4/pull/4161) ([paulbalandan](https://github.com/paulbalandan)) +- Discuss dbcreate in userguide [\#4160](https://github.com/codeigniter4/CodeIgniter4/pull/4160) ([paulbalandan](https://github.com/paulbalandan)) +- Fix misplaced closing tbody [\#4159](https://github.com/codeigniter4/CodeIgniter4/pull/4159) ([paulbalandan](https://github.com/paulbalandan)) +- \[Develop\] Fixes \#4114 Cannot declare class Config\App error on running PHPUnit [\#4157](https://github.com/codeigniter4/CodeIgniter4/pull/4157) ([samsonasik](https://github.com/samsonasik)) +- Specifically exclude migrations from class mapping [\#4156](https://github.com/codeigniter4/CodeIgniter4/pull/4156) ([paulbalandan](https://github.com/paulbalandan)) +- config: add logger.threshold in env as comment [\#4153](https://github.com/codeigniter4/CodeIgniter4/pull/4153) ([kenjis](https://github.com/kenjis)) +- Update phpstan/phpstan requirement from 0.12.68 to 0.12.69 [\#4152](https://github.com/codeigniter4/CodeIgniter4/pull/4152) ([dependabot[bot]](https://github.com/apps/dependabot)) +- convert indentation from tabs to spaces & update code-block \[changelogs, database\] [\#4150](https://github.com/codeigniter4/CodeIgniter4/pull/4150) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Fix for retrieving migration history [\#4147](https://github.com/codeigniter4/CodeIgniter4/pull/4147) ([michalsn](https://github.com/michalsn)) +- Fix phpstan notice [\#4146](https://github.com/codeigniter4/CodeIgniter4/pull/4146) ([samsonasik](https://github.com/samsonasik)) +- Update docs for Entity [\#4145](https://github.com/codeigniter4/CodeIgniter4/pull/4145) ([michalsn](https://github.com/michalsn)) +- update faker [\#4139](https://github.com/codeigniter4/CodeIgniter4/pull/4139) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- docs: add table of contents in 2 libs [\#4138](https://github.com/codeigniter4/CodeIgniter4/pull/4138) ([kenjis](https://github.com/kenjis)) +- PHPStan Fixes [\#4136](https://github.com/codeigniter4/CodeIgniter4/pull/4136) ([MGatner](https://github.com/MGatner)) +- prep\_url\(\) with https:// [\#4135](https://github.com/codeigniter4/CodeIgniter4/pull/4135) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Update phpstan/phpstan requirement from 0.12.65 to 0.12.68 [\#4134](https://github.com/codeigniter4/CodeIgniter4/pull/4134) ([dependabot[bot]](https://github.com/apps/dependabot)) +- set uppercase [\#4132](https://github.com/codeigniter4/CodeIgniter4/pull/4132) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- remove useless "raw html" [\#4131](https://github.com/codeigniter4/CodeIgniter4/pull/4131) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Fix errors/html/error\_exception.php [\#4128](https://github.com/codeigniter4/CodeIgniter4/pull/4128) ([kenjis](https://github.com/kenjis)) +- set note-block for some notes [\#4127](https://github.com/codeigniter4/CodeIgniter4/pull/4127) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Fix `@var` in SessionTestCase. [\#4124](https://github.com/codeigniter4/CodeIgniter4/pull/4124) ([kenjis](https://github.com/kenjis)) +- Fix Session phpdoc [\#4123](https://github.com/codeigniter4/CodeIgniter4/pull/4123) ([kenjis](https://github.com/kenjis)) +- Refactor Generators [\#4121](https://github.com/codeigniter4/CodeIgniter4/pull/4121) ([mostafakhudair](https://github.com/mostafakhudair)) +- Fix few typos. [\#4119](https://github.com/codeigniter4/CodeIgniter4/pull/4119) ([npwsamarasinghe](https://github.com/npwsamarasinghe)) +- Precise exception trace [\#4118](https://github.com/codeigniter4/CodeIgniter4/pull/4118) ([paulbalandan](https://github.com/paulbalandan)) +- Wrap extract calls in IIFEs in View [\#4113](https://github.com/codeigniter4/CodeIgniter4/pull/4113) ([paulbalandan](https://github.com/paulbalandan)) +- Fix Request::withMethod\(\) [\#4112](https://github.com/codeigniter4/CodeIgniter4/pull/4112) ([paulbalandan](https://github.com/paulbalandan)) +- Cache remember [\#4107](https://github.com/codeigniter4/CodeIgniter4/pull/4107) ([agungsugiarto](https://github.com/agungsugiarto)) +- docs: change sample code of redirect\(\) to be more common [\#4106](https://github.com/codeigniter4/CodeIgniter4/pull/4106) ([kenjis](https://github.com/kenjis)) +- Add Cache File mode [\#4103](https://github.com/codeigniter4/CodeIgniter4/pull/4103) ([MGatner](https://github.com/MGatner)) +- Clarify Renderer discrepancy [\#4102](https://github.com/codeigniter4/CodeIgniter4/pull/4102) ([MGatner](https://github.com/MGatner)) +- Catch DateTime failure [\#4097](https://github.com/codeigniter4/CodeIgniter4/pull/4097) ([MGatner](https://github.com/MGatner)) +- Fix URL type. [\#4095](https://github.com/codeigniter4/CodeIgniter4/pull/4095) ([npwsamarasinghe](https://github.com/npwsamarasinghe)) +- Fixed a bug where a newline was treated as a valid value even if it was included at the end. [\#4094](https://github.com/codeigniter4/CodeIgniter4/pull/4094) ([ytetsuro](https://github.com/ytetsuro)) +- BaseModel/Model - Removed $escape from doUpdate [\#4090](https://github.com/codeigniter4/CodeIgniter4/pull/4090) ([najdanovicivan](https://github.com/najdanovicivan)) +- BaseConnection - Added automatic handling of query class for 3rd party drivers [\#4089](https://github.com/codeigniter4/CodeIgniter4/pull/4089) ([najdanovicivan](https://github.com/najdanovicivan)) +- Fix loss of escape value and data in the model [\#4088](https://github.com/codeigniter4/CodeIgniter4/pull/4088) ([michalsn](https://github.com/michalsn)) +- Use getMimeType instead of getClientMimeType. [\#4085](https://github.com/codeigniter4/CodeIgniter4/pull/4085) ([sfadschm](https://github.com/sfadschm)) +- fix codeblock in installing\_composer.rst [\#4083](https://github.com/codeigniter4/CodeIgniter4/pull/4083) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Fixing a bug in Message::isJSON [\#4081](https://github.com/codeigniter4/CodeIgniter4/pull/4081) ([caswell-wc](https://github.com/caswell-wc)) +- Get JSON Variable [\#4080](https://github.com/codeigniter4/CodeIgniter4/pull/4080) ([caswell-wc](https://github.com/caswell-wc)) +- Fix userguide indent [\#4078](https://github.com/codeigniter4/CodeIgniter4/pull/4078) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- DebugToolbar - Handle Query display in Query class [\#4077](https://github.com/codeigniter4/CodeIgniter4/pull/4077) ([najdanovicivan](https://github.com/najdanovicivan)) +- Update userguide indentation [\#4075](https://github.com/codeigniter4/CodeIgniter4/pull/4075) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- docs: fix models indentation [\#4073](https://github.com/codeigniter4/CodeIgniter4/pull/4073) ([kenjis](https://github.com/kenjis)) +- BaseModel/Model - Attempt to rework escape parameter [\#4072](https://github.com/codeigniter4/CodeIgniter4/pull/4072) ([najdanovicivan](https://github.com/najdanovicivan)) +- Model/BaseModel - Fix primary key and add @throws for builder method [\#4071](https://github.com/codeigniter4/CodeIgniter4/pull/4071) ([najdanovicivan](https://github.com/najdanovicivan)) +- Fix DOMParser rules to search also outside the body tag [\#4070](https://github.com/codeigniter4/CodeIgniter4/pull/4070) ([michalsn](https://github.com/michalsn)) +- Warn users on system messages being for internal use [\#4068](https://github.com/codeigniter4/CodeIgniter4/pull/4068) ([paulbalandan](https://github.com/paulbalandan)) +- Remove discussion on LoggerAwareTrait [\#4067](https://github.com/codeigniter4/CodeIgniter4/pull/4067) ([paulbalandan](https://github.com/paulbalandan)) +- PHPStan Ignore File [\#4065](https://github.com/codeigniter4/CodeIgniter4/pull/4065) ([MGatner](https://github.com/MGatner)) +- site\_url tests [\#4063](https://github.com/codeigniter4/CodeIgniter4/pull/4063) ([MGatner](https://github.com/MGatner)) +- Use full table name with schema for SQLSRV [\#4058](https://github.com/codeigniter4/CodeIgniter4/pull/4058) ([michalsn](https://github.com/michalsn)) +- fix userguide config path [\#4057](https://github.com/codeigniter4/CodeIgniter4/pull/4057) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Solidate mime type guessing. [\#4056](https://github.com/codeigniter4/CodeIgniter4/pull/4056) ([sfadschm](https://github.com/sfadschm)) +- Add mime type for rar files [\#4054](https://github.com/codeigniter4/CodeIgniter4/pull/4054) ([michalsn](https://github.com/michalsn)) +- Fix for deleting session flash data in php8 [\#4053](https://github.com/codeigniter4/CodeIgniter4/pull/4053) ([michalsn](https://github.com/michalsn)) +- Added omitted function parameter description. [\#4052](https://github.com/codeigniter4/CodeIgniter4/pull/4052) ([francis94c](https://github.com/francis94c)) +- New Year 2021 [\#4051](https://github.com/codeigniter4/CodeIgniter4/pull/4051) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Fix countAll\(\) docs [\#4050](https://github.com/codeigniter4/CodeIgniter4/pull/4050) ([kenjis](https://github.com/kenjis)) +- adds BaseResult::getNumRows\(\). adds getNumRows to various DBMS Result classes [\#4049](https://github.com/codeigniter4/CodeIgniter4/pull/4049) ([sneakyimp](https://github.com/sneakyimp)) +- \[UG\] a comma after use: i.e., and e.g., [\#4042](https://github.com/codeigniter4/CodeIgniter4/pull/4042) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Fix database test case test [\#4041](https://github.com/codeigniter4/CodeIgniter4/pull/4041) ([kenjis](https://github.com/kenjis)) +- Add initDriver Method [\#4040](https://github.com/codeigniter4/CodeIgniter4/pull/4040) ([mostafakhudair](https://github.com/mostafakhudair)) +- docs: fix general indentation [\#4039](https://github.com/codeigniter4/CodeIgniter4/pull/4039) ([kenjis](https://github.com/kenjis)) +- fix codeblock [\#4037](https://github.com/codeigniter4/CodeIgniter4/pull/4037) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Remove 'tests' [\#4034](https://github.com/codeigniter4/CodeIgniter4/pull/4034) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Alias Forge with use operator [\#4032](https://github.com/codeigniter4/CodeIgniter4/pull/4032) ([mostafakhudair](https://github.com/mostafakhudair)) +- New line for License copyright [\#4029](https://github.com/codeigniter4/CodeIgniter4/pull/4029) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Rename Sqlsrv driver [\#4023](https://github.com/codeigniter4/CodeIgniter4/pull/4023) ([mostafakhudair](https://github.com/mostafakhudair)) +- PHP 8 Actions [\#4012](https://github.com/codeigniter4/CodeIgniter4/pull/4012) ([MGatner](https://github.com/MGatner)) +- feat: make migration/seed settings flexible on database testing [\#3993](https://github.com/codeigniter4/CodeIgniter4/pull/3993) ([kenjis](https://github.com/kenjis)) +- Deprecate redundant HTTP keys [\#3973](https://github.com/codeigniter4/CodeIgniter4/pull/3973) ([paulbalandan](https://github.com/paulbalandan)) +- Replace Core Services [\#3943](https://github.com/codeigniter4/CodeIgniter4/pull/3943) ([MGatner](https://github.com/MGatner)) +- Handling requests sent back from filters [\#3900](https://github.com/codeigniter4/CodeIgniter4/pull/3900) ([caswell-wc](https://github.com/caswell-wc)) +- DX: Split Model testing into several subunits [\#3891](https://github.com/codeigniter4/CodeIgniter4/pull/3891) ([paulbalandan](https://github.com/paulbalandan)) + +## [v4.0.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.4) (2020-07-15) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.3...v4.0.4) + +**Implemented enhancements:** + +- Bug: incorrect type - system/Database/MYSQLi/Connection.php [\#2996](https://github.com/codeigniter4/CodeIgniter4/issues/2996) +- Bug: CI\_DEBUG incorrect type and fails strict validation [\#2975](https://github.com/codeigniter4/CodeIgniter4/issues/2975) + +**Fixed bugs:** + +- Bug: Fix protocol version for DownloadResponse [\#3320](https://github.com/codeigniter4/CodeIgniter4/pull/3320) +- Bug: Add Honeypot::$container to env file [\#3318](https://github.com/codeigniter4/CodeIgniter4/pull/3318) +- Bug: Add multibyte support on DOMParser::see() [\#3324](https://github.com/codeigniter4/CodeIgniter4/pull/3324) +- Bug: Image width debug-bar has conflict [\#3323](https://github.com/codeigniter4/CodeIgniter4/pull/3323) +- Bug: Setting Encryption Service Key in app/Config/Encyption.php [\#3297](https://github.com/codeigniter4/CodeIgniter4/issues/3297) +- Bug: CodeIgniter\I18n -\> Class 'Locale' not found [\#3294](https://github.com/codeigniter4/CodeIgniter4/issues/3294) +- bug cURL - Parse header and Body [\#3261](https://github.com/codeigniter4/CodeIgniter4/issues/3261) +- Bug: Forced HTTPS wrong redirect [\#3260](https://github.com/codeigniter4/CodeIgniter4/issues/3260) +- Bug: [\#3255](https://github.com/codeigniter4/CodeIgniter4/issues/3255) +- Testing ajax header problems [\#3246](https://github.com/codeigniter4/CodeIgniter4/issues/3246) +- Bug: Url's not working [\#3245](https://github.com/codeigniter4/CodeIgniter4/issues/3245) +- Missing code formatting for migration block in documentation [\#3243](https://github.com/codeigniter4/CodeIgniter4/issues/3243) +- Hint message in tutorial does not correspond to the example code shown [\#3242](https://github.com/codeigniter4/CodeIgniter4/issues/3242) +- How to call resource route [\#3239](https://github.com/codeigniter4/CodeIgniter4/issues/3239) +- Can't successfully validate CLI::prompt when field param has periods [\#3233](https://github.com/codeigniter4/CodeIgniter4/issues/3233) +- Bug: set\_checkbox, set\_radio not working default true. [\#3228](https://github.com/codeigniter4/CodeIgniter4/issues/3228) +- Bug: Validation wrong behaviour - Session mixed with internal validator state [\#3210](https://github.com/codeigniter4/CodeIgniter4/issues/3210) +- Bug: CLI: Smell on reading parameters [\#3205](https://github.com/codeigniter4/CodeIgniter4/issues/3205) +- Bug: MySQL Errors are not reported [\#3204](https://github.com/codeigniter4/CodeIgniter4/issues/3204) +- Bug: Error exception layout when its an error inside a view where code can go outside the limits [\#3199](https://github.com/codeigniter4/CodeIgniter4/issues/3199) +- Bug: MessageFormatter argType of "String" isn't valid [\#3191](https://github.com/codeigniter4/CodeIgniter4/issues/3191) +- Bug: pagination not working with segment [\#3188](https://github.com/codeigniter4/CodeIgniter4/issues/3188) +- Bug: is\_image validation is vulnerable [\#3184](https://github.com/codeigniter4/CodeIgniter4/issues/3184) +- Bug: model-\>save\(\) insert/update data [\#3177](https://github.com/codeigniter4/CodeIgniter4/issues/3177) +- Bug: The CLI::color cannot create multicolored strings if ordering of strings is reversed [\#3173](https://github.com/codeigniter4/CodeIgniter4/issues/3173) +- Bug: Missing function locale\_set\_default\(...\) [\#3171](https://github.com/codeigniter4/CodeIgniter4/issues/3171) +- Router RegEx not working. [\#3169](https://github.com/codeigniter4/CodeIgniter4/issues/3169) +- Bug: BaseBuilder::insertBatch\(\) has 3 params and not 4 [\#3158](https://github.com/codeigniter4/CodeIgniter4/issues/3158) +- Bug: Issue using image library when original image and resized are the same size [\#3146](https://github.com/codeigniter4/CodeIgniter4/issues/3146) +- Bug: cannot call constructor on controller. [\#3145](https://github.com/codeigniter4/CodeIgniter4/issues/3145) +- Bug: CodeIgniter 4.0.3 Controller will not display pdf files in browser [\#3144](https://github.com/codeigniter4/CodeIgniter4/issues/3144) +- Bug: $request-\>getVar\('test', FILTER\_VALIDATE\_INT\) does not work if the input is an array [\#3128](https://github.com/codeigniter4/CodeIgniter4/issues/3128) +- Bug: A wrong escape on BaseBuilder::set\(\) [\#3127](https://github.com/codeigniter4/CodeIgniter4/issues/3127) +- Bug: Can't override Translations Package Files / Keys [\#3125](https://github.com/codeigniter4/CodeIgniter4/issues/3125) +- Bug: Validation rules max\_size [\#3122](https://github.com/codeigniter4/CodeIgniter4/issues/3122) +- Bug: Pagination not working with soft deleted items [\#3121](https://github.com/codeigniter4/CodeIgniter4/issues/3121) +- Bug: SQLite3 database file created in /public folder [\#3113](https://github.com/codeigniter4/CodeIgniter4/issues/3113) +- Bug: RedisHandler does not working on sessionExpiration is zero [\#3111](https://github.com/codeigniter4/CodeIgniter4/issues/3111) +- Bug: Implicit controller methods failing [\#3105](https://github.com/codeigniter4/CodeIgniter4/issues/3105) +- Bug: Custom Validation Error Messages [\#3097](https://github.com/codeigniter4/CodeIgniter4/issues/3097) +- Bug: API\ResponseTrait::respondNoContent return content-type header [\#3087](https://github.com/codeigniter4/CodeIgniter4/issues/3087) +- Bug: Running Feature Tests on multiple endpoints using filters [\#3085](https://github.com/codeigniter4/CodeIgniter4/issues/3085) +- Bug: FeatureResponse::isOk and redirects [\#3072](https://github.com/codeigniter4/CodeIgniter4/issues/3072) +- Documentation: "First Application" form validating before submitting [\#3071](https://github.com/codeigniter4/CodeIgniter4/issues/3071) +- Bug: Fatal error: Cannot declare class CodeIgniter\Exceptions\PageNotFoundException [\#3067](https://github.com/codeigniter4/CodeIgniter4/issues/3067) +- Bug: Risky feature tests with Controller returns [\#3063](https://github.com/codeigniter4/CodeIgniter4/issues/3063) +- Bug: Documentation news app project [\#3054](https://github.com/codeigniter4/CodeIgniter4/issues/3054) +- Bug: ClassMethod [\#3050](https://github.com/codeigniter4/CodeIgniter4/issues/3050) +- Bug: CodeIgniter\Router\RouteCollection-\>fillRouteParams\(\) ErrorException on 'from' parameter containing regex pattern with '|' symbol [\#3048](https://github.com/codeigniter4/CodeIgniter4/issues/3048) +- Bug: Database connection [\#3043](https://github.com/codeigniter4/CodeIgniter4/issues/3043) +- Bug: Route redirection not working [\#3041](https://github.com/codeigniter4/CodeIgniter4/issues/3041) +- Bug: Model::getValidationRules\(\) cant handle ::$validationRules if its a string [\#3039](https://github.com/codeigniter4/CodeIgniter4/issues/3039) +- Bug: Body data from curlrequest become unreadable when endpoint return long data [\#3034](https://github.com/codeigniter4/CodeIgniter4/issues/3034) +- Bug: File Validation not validate correctly. [\#3032](https://github.com/codeigniter4/CodeIgniter4/issues/3032) +- Bug: Fatal error when no user-agent is available [\#3029](https://github.com/codeigniter4/CodeIgniter4/issues/3029) +- Bug: Not possible manipulate results of renderSection\(\) on View Layouts [\#3028](https://github.com/codeigniter4/CodeIgniter4/issues/3028) +- Bug: Helpers at non-default locations are not found [\#3026](https://github.com/codeigniter4/CodeIgniter4/issues/3026) +- Bug: Most validation rules enforce requirement of the field [\#3025](https://github.com/codeigniter4/CodeIgniter4/issues/3025) +- Bug: [\#3021](https://github.com/codeigniter4/CodeIgniter4/issues/3021) +- Bug: getPostGet\($index\) return $\_POST if there is no $index in post and get [\#3020](https://github.com/codeigniter4/CodeIgniter4/issues/3020) +- Bug: Multiple File Uploads validation rule "uploaded\[inputName\]" does not work when the input name is an Array [\#3018](https://github.com/codeigniter4/CodeIgniter4/issues/3018) +- Bug: delete\_files\(\) and hidden directories [\#3015](https://github.com/codeigniter4/CodeIgniter4/issues/3015) +- Bug: Model::Insert\(\) does not throw exception when object without data is passed as parameter [\#2998](https://github.com/codeigniter4/CodeIgniter4/issues/2998) +- Bug: Force download [\#2995](https://github.com/codeigniter4/CodeIgniter4/issues/2995) +- Bug: The example of "The Test Class“ is not working in Documets [\#2993](https://github.com/codeigniter4/CodeIgniter4/issues/2993) +- Bug: Difference in behaviour of native DateTime::setTimezone\(\) and CI's Time:setTimezone\(\) [\#2989](https://github.com/codeigniter4/CodeIgniter4/issues/2989) +- Bug: Blank Page on Production Server [\#2980](https://github.com/codeigniter4/CodeIgniter4/issues/2980) +- Bug: Mistake in uri\_string\(\) documentation [\#2972](https://github.com/codeigniter4/CodeIgniter4/issues/2972) +- Bug: route\_to\(\) does not return relative part of the path [\#2971](https://github.com/codeigniter4/CodeIgniter4/issues/2971) +- Bug: Encrypter-\>decrypt issue? [\#2970](https://github.com/codeigniter4/CodeIgniter4/issues/2970) +- Bug: form\_upload second parameter VALUE is not used in source code [\#2967](https://github.com/codeigniter4/CodeIgniter4/issues/2967) +- Bug: There's no way to change default spark serve port using code [\#2966](https://github.com/codeigniter4/CodeIgniter4/issues/2966) +- Bug: 'method' is an empty string in PATH\_INFO causes Unhandled Exception [\#2965](https://github.com/codeigniter4/CodeIgniter4/issues/2965) +- Bug: URI class - working with segments is really strange [\#2962](https://github.com/codeigniter4/CodeIgniter4/issues/2962) +- Bug: Documentation Issue [\#2960](https://github.com/codeigniter4/CodeIgniter4/issues/2960) +- Query Builder set update not working correctly [\#2959](https://github.com/codeigniter4/CodeIgniter4/issues/2959) +- Bug: Validation permit\_empty does no work together with required\_with and required\_without [\#2953](https://github.com/codeigniter4/CodeIgniter4/issues/2953) +- Bug: Validation tries to validate inputs even for empty values with no `required` parameter [\#2951](https://github.com/codeigniter4/CodeIgniter4/issues/2951) +- Bug: Request URI segment is our of range CI 4.0.3 [\#2949](https://github.com/codeigniter4/CodeIgniter4/issues/2949) +- Bug: start migration in manual mode [\#2942](https://github.com/codeigniter4/CodeIgniter4/issues/2942) +- valid\_url rule from validation not working correctly same as valid\_url|required [\#2941](https://github.com/codeigniter4/CodeIgniter4/issues/2941) +- redirect route when route is more complex [\#2937](https://github.com/codeigniter4/CodeIgniter4/issues/2937) +- set\_value does not work with NULL as second parameter [\#2935](https://github.com/codeigniter4/CodeIgniter4/issues/2935) +- Bug: init of $data arrays in controllers [\#2933](https://github.com/codeigniter4/CodeIgniter4/issues/2933) +- Bug: Translation key separated by dot [\#2932](https://github.com/codeigniter4/CodeIgniter4/issues/2932) +- Bug: Model Instantiation [\#2924](https://github.com/codeigniter4/CodeIgniter4/issues/2924) +- Model [\#2923](https://github.com/codeigniter4/CodeIgniter4/issues/2923) +- Bug: CURLRequest baseURI option in user guide [\#2922](https://github.com/codeigniter4/CodeIgniter4/issues/2922) +- Bug: Not bug, but Fix Documentation, please [\#2920](https://github.com/codeigniter4/CodeIgniter4/issues/2920) +- Bug: Postgresql API call delete use -\>connID-\>affected\_rows after Model::delete\(\) got error [\#2918](https://github.com/codeigniter4/CodeIgniter4/issues/2918) +- Multiple table query Model first [\#2885](https://github.com/codeigniter4/CodeIgniter4/issues/2885) +- Bug: pager "prev" and "next" links pointing to wrong URIs [\#2881](https://github.com/codeigniter4/CodeIgniter4/issues/2881) +- Bug: Automatic no CLI colors for Windows terminals [\#2849](https://github.com/codeigniter4/CodeIgniter4/issues/2849) +- Bug: $format in ResourceController is ignored [\#2828](https://github.com/codeigniter4/CodeIgniter4/issues/2828) +- Bug: "Type is not supported" in Postgresql POST restful [\#2812](https://github.com/codeigniter4/CodeIgniter4/issues/2812) +- Bug: Cookie Helper and Response class issue [\#2783](https://github.com/codeigniter4/CodeIgniter4/issues/2783) +- Bug: Models, useSoftDeletes not found in findAll [\#2658](https://github.com/codeigniter4/CodeIgniter4/issues/2658) +- Feature: About the SameSite COOKIE RFC changes for PHP \> 7.3 [\#2374](https://github.com/codeigniter4/CodeIgniter4/issues/2374) + +**Closed issues:** + +- Language folders inside locale folders [\#3300](https://github.com/codeigniter4/CodeIgniter4/issues/3300) +- Encryption Class - Decrypting: authentication failed [\#3258](https://github.com/codeigniter4/CodeIgniter4/issues/3258) +- form\_upload second parameter VALUE was not used in source code, i found a way to do it. [\#3256](https://github.com/codeigniter4/CodeIgniter4/issues/3256) +- erorr line in resize image using fit in visual studio code [\#3249](https://github.com/codeigniter4/CodeIgniter4/issues/3249) +- Testing withSession\(\) generates an error. [\#3190](https://github.com/codeigniter4/CodeIgniter4/issues/3190) +- Why is getGetPost\(\) returning all data instead of null when index not found? [\#3187](https://github.com/codeigniter4/CodeIgniter4/issues/3187) +- Request setGlobal not works [\#3186](https://github.com/codeigniter4/CodeIgniter4/issues/3186) +- Cannot extend core HTTPException class [\#3178](https://github.com/codeigniter4/CodeIgniter4/issues/3178) +- Add this relationship in model. [\#3170](https://github.com/codeigniter4/CodeIgniter4/issues/3170) +- Execute bootstrapEnvironment\(\) & detectEnvironment\(\) before Services::exceptions\(\) [\#3138](https://github.com/codeigniter4/CodeIgniter4/issues/3138) +- Bag pars array to xml [\#3092](https://github.com/codeigniter4/CodeIgniter4/issues/3092) +- utf-8 slug character doesn't work in url [\#3089](https://github.com/codeigniter4/CodeIgniter4/issues/3089) +- Grammar in comment [\#3064](https://github.com/codeigniter4/CodeIgniter4/issues/3064) +- line 374 in system/Entity.php json\_encode add JSON\_UNESCAPED\_UNICODE [\#3059](https://github.com/codeigniter4/CodeIgniter4/issues/3059) +- Link to User Guide on README.md [\#3053](https://github.com/codeigniter4/CodeIgniter4/issues/3053) +- How to set private properties in CodeIgniter\Database\BaseResult::getCustomResultObject\(\) [\#3051](https://github.com/codeigniter4/CodeIgniter4/issues/3051) +- url\_title not detecting some special characteres [\#3038](https://github.com/codeigniter4/CodeIgniter4/issues/3038) +- Error: No input file specified. [\#3030](https://github.com/codeigniter4/CodeIgniter4/issues/3030) +- Can findAll function in modeling data accept 3rd parameter reset [\#3024](https://github.com/codeigniter4/CodeIgniter4/issues/3024) +- Class 'App\Models\UserModel' not found [\#3014](https://github.com/codeigniter4/CodeIgniter4/issues/3014) +- Image reorient with exif [\#3006](https://github.com/codeigniter4/CodeIgniter4/issues/3006) +- set cookieHTTPOnly not work [\#2999](https://github.com/codeigniter4/CodeIgniter4/issues/2999) +- \[Feature request\] Slugs Link [\#2988](https://github.com/codeigniter4/CodeIgniter4/issues/2988) +- \[Feature request\] End processing app [\#2982](https://github.com/codeigniter4/CodeIgniter4/issues/2982) +- Pager should have getTotalResults method function [\#2954](https://github.com/codeigniter4/CodeIgniter4/issues/2954) +- URL rewrite problem [\#2925](https://github.com/codeigniter4/CodeIgniter4/issues/2925) +- Translation file not get correct locale in 4.0.3 [\#2921](https://github.com/codeigniter4/CodeIgniter4/issues/2921) +- Why this route does not work ? [\#2919](https://github.com/codeigniter4/CodeIgniter4/issues/2919) +- Cache redis or memcached [\#2909](https://github.com/codeigniter4/CodeIgniter4/issues/2909) +- Cookie helper not woking [\#2848](https://github.com/codeigniter4/CodeIgniter4/issues/2848) +- Bug: Routing group filter not working on nested or complex routes [\#2390](https://github.com/codeigniter4/CodeIgniter4/issues/2390) + +**Merged pull requests:** + +- Changelog update [\#3322](https://github.com/codeigniter4/CodeIgniter4/pull/3322) ([michalsn](https://github.com/michalsn)) +- Changelog for 4.0.4 update [\#3321](https://github.com/codeigniter4/CodeIgniter4/pull/3321) ([michalsn](https://github.com/michalsn)) +- Fix protocol version for DownloadResponse [\#3320](https://github.com/codeigniter4/CodeIgniter4/pull/3320) ([michalsn](https://github.com/michalsn)) +- Add Honeypot::$container to env file [\#3318](https://github.com/codeigniter4/CodeIgniter4/pull/3318) ([paulbalandan](https://github.com/paulbalandan)) +- Pass filter arguments to after\(\) and before\(\) methods [\#3316](https://github.com/codeigniter4/CodeIgniter4/pull/3316) ([tangix](https://github.com/tangix)) +- count on \Config\Services [\#3308](https://github.com/codeigniter4/CodeIgniter4/pull/3308) ([mostafakhudair](https://github.com/mostafakhudair)) +- Add hex2bin prefix handling for encryption key [\#3307](https://github.com/codeigniter4/CodeIgniter4/pull/3307) ([michalsn](https://github.com/michalsn)) +- add break; in foreach at Time::getDst\(\) when daylightSaving set [\#3305](https://github.com/codeigniter4/CodeIgniter4/pull/3305) ([samsonasik](https://github.com/samsonasik)) +- New command: cache:clear [\#3304](https://github.com/codeigniter4/CodeIgniter4/pull/3304) ([lonnieezell](https://github.com/lonnieezell)) +- force\_https didn't force https [\#3302](https://github.com/codeigniter4/CodeIgniter4/pull/3302) ([colethorsen](https://github.com/colethorsen)) +- add test for CommandRunner::\_remap\(\) with empty first params [\#3301](https://github.com/codeigniter4/CodeIgniter4/pull/3301) ([samsonasik](https://github.com/samsonasik)) +- FieldData -\> add typeName field and length field [\#3299](https://github.com/codeigniter4/CodeIgniter4/pull/3299) ([devorama](https://github.com/devorama)) +- reduce repetitive getDefaultNamespace\(\) and controllerName\(\) function call in Router [\#3298](https://github.com/codeigniter4/CodeIgniter4/pull/3298) ([samsonasik](https://github.com/samsonasik)) +- Fix PHPDocs for Filters [\#3296](https://github.com/codeigniter4/CodeIgniter4/pull/3296) ([paulbalandan](https://github.com/paulbalandan)) +- Fix PHPDocs for HTTP [\#3295](https://github.com/codeigniter4/CodeIgniter4/pull/3295) ([paulbalandan](https://github.com/paulbalandan)) +- Update phpdoc.dist.xml [\#3293](https://github.com/codeigniter4/CodeIgniter4/pull/3293) ([paulbalandan](https://github.com/paulbalandan)) +- Fix for force\_https\(\) function [\#3292](https://github.com/codeigniter4/CodeIgniter4/pull/3292) ([michalsn](https://github.com/michalsn)) +- Fix PHPDocs for I18n [\#3291](https://github.com/codeigniter4/CodeIgniter4/pull/3291) ([paulbalandan](https://github.com/paulbalandan)) +- Fix PHPDocs for Router [\#3290](https://github.com/codeigniter4/CodeIgniter4/pull/3290) ([paulbalandan](https://github.com/paulbalandan)) +- Fix PHPDocs for CLI [\#3289](https://github.com/codeigniter4/CodeIgniter4/pull/3289) ([paulbalandan](https://github.com/paulbalandan)) +- add JSON\_NUMERIC\_CHECK to json encode options [\#3288](https://github.com/codeigniter4/CodeIgniter4/pull/3288) ([devorama](https://github.com/devorama)) +- typo fix s/Memcached/Redis in RedisHandler cache [\#3285](https://github.com/codeigniter4/CodeIgniter4/pull/3285) ([samsonasik](https://github.com/samsonasik)) +- optimize Session : use foreach instead of for with count when possible [\#3284](https://github.com/codeigniter4/CodeIgniter4/pull/3284) ([samsonasik](https://github.com/samsonasik)) +- using strpos instead of substr when possible [\#3283](https://github.com/codeigniter4/CodeIgniter4/pull/3283) ([samsonasik](https://github.com/samsonasik)) +- optimize Database BaseBuilder : use foreach instead of for with count when possible [\#3282](https://github.com/codeigniter4/CodeIgniter4/pull/3282) ([samsonasik](https://github.com/samsonasik)) +- optimize RouteCollection : use foreach instead of for with count when possible [\#3281](https://github.com/codeigniter4/CodeIgniter4/pull/3281) ([samsonasik](https://github.com/samsonasik)) +- optimize FileLocator autoloader : use foreach instead of for with count when possible [\#3280](https://github.com/codeigniter4/CodeIgniter4/pull/3280) ([samsonasik](https://github.com/samsonasik)) +- Fix "100 Continue" header handling in CURLRequest class [\#3274](https://github.com/codeigniter4/CodeIgniter4/pull/3274) ([michalsn](https://github.com/michalsn)) +- cs fix in Session class [\#3272](https://github.com/codeigniter4/CodeIgniter4/pull/3272) ([samsonasik](https://github.com/samsonasik)) +- Throttler code style update [\#3271](https://github.com/codeigniter4/CodeIgniter4/pull/3271) ([michalsn](https://github.com/michalsn)) +- cs : remove unused import use statements and sort use statements [\#3270](https://github.com/codeigniter4/CodeIgniter4/pull/3270) ([samsonasik](https://github.com/samsonasik)) +- Add more URI class tests to fully illustrate current behavior [\#3269](https://github.com/codeigniter4/CodeIgniter4/pull/3269) ([michalsn](https://github.com/michalsn)) +- Fix Image::save\(\) when target value is null [\#3268](https://github.com/codeigniter4/CodeIgniter4/pull/3268) ([michalsn](https://github.com/michalsn)) +- Use named variable in honeypot container [\#3267](https://github.com/codeigniter4/CodeIgniter4/pull/3267) ([michalsn](https://github.com/michalsn)) +- Check server headers via Request class [\#3265](https://github.com/codeigniter4/CodeIgniter4/pull/3265) ([michalsn](https://github.com/michalsn)) +- PHPUnit-annotate untestable code in CLI [\#3264](https://github.com/codeigniter4/CodeIgniter4/pull/3264) ([paulbalandan](https://github.com/paulbalandan)) +- Update phpdocs of Cache library [\#3263](https://github.com/codeigniter4/CodeIgniter4/pull/3263) ([paulbalandan](https://github.com/paulbalandan)) +- Update htaccess [\#3262](https://github.com/codeigniter4/CodeIgniter4/pull/3262) ([paulbalandan](https://github.com/paulbalandan)) +- Fixes \#3125 : add ability to override existing translation en in system language from App [\#3254](https://github.com/codeigniter4/CodeIgniter4/pull/3254) ([samsonasik](https://github.com/samsonasik)) +- Add Fabricator model error [\#3253](https://github.com/codeigniter4/CodeIgniter4/pull/3253) ([MGatner](https://github.com/MGatner)) +- Implement model callback overrides [\#3252](https://github.com/codeigniter4/CodeIgniter4/pull/3252) ([MGatner](https://github.com/MGatner)) +- Fix PHPDocBlock of Images library [\#3250](https://github.com/codeigniter4/CodeIgniter4/pull/3250) ([paulbalandan](https://github.com/paulbalandan)) +- Update html\_helper.rst [\#3248](https://github.com/codeigniter4/CodeIgniter4/pull/3248) ([avegacms](https://github.com/avegacms)) +- Update html\_helper.php [\#3247](https://github.com/codeigniter4/CodeIgniter4/pull/3247) ([avegacms](https://github.com/avegacms)) +- Syntax fix for migration.rst [\#3244](https://github.com/codeigniter4/CodeIgniter4/pull/3244) ([paulbalandan](https://github.com/paulbalandan)) +- Run apidocs action only when system files are changed [\#3241](https://github.com/codeigniter4/CodeIgniter4/pull/3241) ([paulbalandan](https://github.com/paulbalandan)) +- Fix formatting issue in userguide/outgoing/view\_parser [\#3240](https://github.com/codeigniter4/CodeIgniter4/pull/3240) ([Connum](https://github.com/Connum)) +- Add ability to call commands programatically. [\#3238](https://github.com/codeigniter4/CodeIgniter4/pull/3238) ([lonnieezell](https://github.com/lonnieezell)) +- Reset Filters between feature tests [\#3237](https://github.com/codeigniter4/CodeIgniter4/pull/3237) ([MGatner](https://github.com/MGatner)) +- Fix CLI::validate\(\) usage when using dot sign [\#3236](https://github.com/codeigniter4/CodeIgniter4/pull/3236) ([michalsn](https://github.com/michalsn)) +- Remove cilexer from gitignore [\#3235](https://github.com/codeigniter4/CodeIgniter4/pull/3235) ([paulbalandan](https://github.com/paulbalandan)) +- Feature seed command [\#3234](https://github.com/codeigniter4/CodeIgniter4/pull/3234) ([abilioposada](https://github.com/abilioposada)) +- more test Files\File class [\#3232](https://github.com/codeigniter4/CodeIgniter4/pull/3232) ([samsonasik](https://github.com/samsonasik)) +- more tests on Autoloader\FileLocator [\#3231](https://github.com/codeigniter4/CodeIgniter4/pull/3231) ([samsonasik](https://github.com/samsonasik)) +- Fix set\_checkbox\(\) and set\_radio\(\) when default is set to true [\#3229](https://github.com/codeigniter4/CodeIgniter4/pull/3229) ([michalsn](https://github.com/michalsn)) +- Use Throwable in catch block of filesystem helper [\#3227](https://github.com/codeigniter4/CodeIgniter4/pull/3227) ([paulbalandan](https://github.com/paulbalandan)) +- Check color support for STDERR for CLI::error [\#3226](https://github.com/codeigniter4/CodeIgniter4/pull/3226) ([paulbalandan](https://github.com/paulbalandan)) +- Fix 'exit code 23' in apidocs action [\#3225](https://github.com/codeigniter4/CodeIgniter4/pull/3225) ([paulbalandan](https://github.com/paulbalandan)) +- Fix result object handling in Model class [\#3224](https://github.com/codeigniter4/CodeIgniter4/pull/3224) ([michalsn](https://github.com/michalsn)) +- Fix update query to return false on error [\#3223](https://github.com/codeigniter4/CodeIgniter4/pull/3223) ([michalsn](https://github.com/michalsn)) +- Fix insert Entity object on postgres [\#3222](https://github.com/codeigniter4/CodeIgniter4/pull/3222) ([MashinaMashina](https://github.com/MashinaMashina)) +- DatabaseTestCase migrations [\#3221](https://github.com/codeigniter4/CodeIgniter4/pull/3221) ([MGatner](https://github.com/MGatner)) +- Allow ignoring \*.db inside folders [\#3220](https://github.com/codeigniter4/CodeIgniter4/pull/3220) ([paulbalandan](https://github.com/paulbalandan)) +- SQLite3 escapeChar fix [\#3219](https://github.com/codeigniter4/CodeIgniter4/pull/3219) ([michalsn](https://github.com/michalsn)) +- Remove underscore in UG build path [\#3218](https://github.com/codeigniter4/CodeIgniter4/pull/3218) ([paulbalandan](https://github.com/paulbalandan)) +- add test for Filters with empty except [\#3215](https://github.com/codeigniter4/CodeIgniter4/pull/3215) ([samsonasik](https://github.com/samsonasik)) +- remove unnecessary foreach in RouteCollection::checkSubdomains\(\) [\#3214](https://github.com/codeigniter4/CodeIgniter4/pull/3214) ([samsonasik](https://github.com/samsonasik)) +- Add Fabricator counts [\#3213](https://github.com/codeigniter4/CodeIgniter4/pull/3213) ([MGatner](https://github.com/MGatner)) +- Cast currentPage value to integer in Pager class [\#3209](https://github.com/codeigniter4/CodeIgniter4/pull/3209) ([michalsn](https://github.com/michalsn)) +- Allow dashes in the CLI segment [\#3208](https://github.com/codeigniter4/CodeIgniter4/pull/3208) ([michalsn](https://github.com/michalsn)) +- Fix for getting database error [\#3207](https://github.com/codeigniter4/CodeIgniter4/pull/3207) ([michalsn](https://github.com/michalsn)) +- Fixed \#3199 [\#3203](https://github.com/codeigniter4/CodeIgniter4/pull/3203) ([mpmont](https://github.com/mpmont)) +- Fix extra slash in URI [\#3202](https://github.com/codeigniter4/CodeIgniter4/pull/3202) ([MGatner](https://github.com/MGatner)) +- Update for FabricatorLiveTest [\#3201](https://github.com/codeigniter4/CodeIgniter4/pull/3201) ([michalsn](https://github.com/michalsn)) +- Add closing parenthesis [\#3200](https://github.com/codeigniter4/CodeIgniter4/pull/3200) ([abilioposada](https://github.com/abilioposada)) +- Hide in bootstrap4 [\#3197](https://github.com/codeigniter4/CodeIgniter4/pull/3197) ([Sosko](https://github.com/Sosko)) +- The permissions of the new file should be modified, not the old file [\#3196](https://github.com/codeigniter4/CodeIgniter4/pull/3196) ([wangyupeng](https://github.com/wangyupeng)) +- Fix default value for page in Model::paginate\(\) [\#3194](https://github.com/codeigniter4/CodeIgniter4/pull/3194) ([michalsn](https://github.com/michalsn)) +- Test Case Mocking [\#3193](https://github.com/codeigniter4/CodeIgniter4/pull/3193) ([MGatner](https://github.com/MGatner)) +- ArgType 'String' is not valid in MessageFormatter class \(fixes \#3191\) [\#3192](https://github.com/codeigniter4/CodeIgniter4/pull/3192) ([HughieW](https://github.com/HughieW)) +- Bugfix: Multipart Content-Length [\#3189](https://github.com/codeigniter4/CodeIgniter4/pull/3189) ([MGatner](https://github.com/MGatner)) +- Add apidocs action [\#3183](https://github.com/codeigniter4/CodeIgniter4/pull/3183) ([paulbalandan](https://github.com/paulbalandan)) +- Improve CLI clear screen [\#3182](https://github.com/codeigniter4/CodeIgniter4/pull/3182) ([paulbalandan](https://github.com/paulbalandan)) +- Refactor color detection in CLI [\#3181](https://github.com/codeigniter4/CodeIgniter4/pull/3181) ([paulbalandan](https://github.com/paulbalandan)) +- Use explicit 'PHP\_EOL' in wordwrap [\#3179](https://github.com/codeigniter4/CodeIgniter4/pull/3179) ([paulbalandan](https://github.com/paulbalandan)) +- Add webp support to Image class [\#3176](https://github.com/codeigniter4/CodeIgniter4/pull/3176) ([michalsn](https://github.com/michalsn)) +- Fix for multicolored strings in CLI [\#3175](https://github.com/codeigniter4/CodeIgniter4/pull/3175) ([michalsn](https://github.com/michalsn)) +- Add Unicode support for regular expressions in router [\#3172](https://github.com/codeigniter4/CodeIgniter4/pull/3172) ([michalsn](https://github.com/michalsn)) +- Add the upload artifact action [\#3167](https://github.com/codeigniter4/CodeIgniter4/pull/3167) ([paulbalandan](https://github.com/paulbalandan)) +- More robust color support detection in CLI [\#3165](https://github.com/codeigniter4/CodeIgniter4/pull/3165) ([paulbalandan](https://github.com/paulbalandan)) +- Fix testMode\(\) for batch methods in Model [\#3163](https://github.com/codeigniter4/CodeIgniter4/pull/3163) ([michalsn](https://github.com/michalsn)) +- Update delete\_files\(\) helper function [\#3162](https://github.com/codeigniter4/CodeIgniter4/pull/3162) ([michalsn](https://github.com/michalsn)) +- Refresh Composer files [\#3153](https://github.com/codeigniter4/CodeIgniter4/pull/3153) ([MGatner](https://github.com/MGatner)) +- use writable directory for sqlite default location [\#3151](https://github.com/codeigniter4/CodeIgniter4/pull/3151) ([samsonasik](https://github.com/samsonasik)) +- Add webp support to Image Manipulation Class [\#3084](https://github.com/codeigniter4/CodeIgniter4/pull/3084) ([nicojmb](https://github.com/nicojmb)) +- Bug fix in Throttler class check validation [\#2873](https://github.com/codeigniter4/CodeIgniter4/pull/2873) ([jlamim](https://github.com/jlamim)) + +## [v4.0.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.3) (2020-05-01) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/4.0.2...v4.0.3) + +**Fixed bugs:** + +- Bug: is\_unique validation rule with model-\>save\(\) fails [\#2906](https://github.com/codeigniter4/CodeIgniter4/issues/2906) +- Bug: Debugging toolbar not showing [\#2893](https://github.com/codeigniter4/CodeIgniter4/issues/2893) +- Bug: database query bug [\#2890](https://github.com/codeigniter4/CodeIgniter4/issues/2890) +- Bug: Routes - missing \(:id\) [\#2889](https://github.com/codeigniter4/CodeIgniter4/issues/2889) +- Bug: Typo example "Using Named Routes" [\#2888](https://github.com/codeigniter4/CodeIgniter4/issues/2888) +- Bug: I am able to make POST request to a GET route [\#2880](https://github.com/codeigniter4/CodeIgniter4/issues/2880) +- Bug: Argument 1 passed to CodeIgniter\Database\BaseResult::getFirstRow\(\) must be of the type string, null given, called in /system/Model.php on line 383 [\#2877](https://github.com/codeigniter4/CodeIgniter4/issues/2877) +- Bug: Can't override already defined language in pager [\#2875](https://github.com/codeigniter4/CodeIgniter4/issues/2875) +- Installation Problem: Composer installation halts while installing kint-php/kint [\#2863](https://github.com/codeigniter4/CodeIgniter4/issues/2863) +- Bug: `composer require codeigniter4/translations` without stable version [\#2862](https://github.com/codeigniter4/CodeIgniter4/issues/2862) +- Bug: Method Spoofing Validation [\#2855](https://github.com/codeigniter4/CodeIgniter4/issues/2855) +- Bug: You made it just as impossible to get going as Laravel – BUMMER! [\#2850](https://github.com/codeigniter4/CodeIgniter4/issues/2850) +- Bug: localised validation messages [\#2845](https://github.com/codeigniter4/CodeIgniter4/issues/2845) +- Bug: Commands discovery in custom namespaces [\#2840](https://github.com/codeigniter4/CodeIgniter4/issues/2840) +- Bug: When the $index parameter of getGetPost or getPostGet is null, you will get an error result [\#2839](https://github.com/codeigniter4/CodeIgniter4/issues/2839) +- Bug: delete\_cookie\(\) doesn't work [\#2836](https://github.com/codeigniter4/CodeIgniter4/issues/2836) +- Bug: Model save method not working if PRIMARY KEY is VARCHAR [\#2835](https://github.com/codeigniter4/CodeIgniter4/issues/2835) +- Bug: Alias Filter with Multiple Filter Class Doesn't Work [\#2831](https://github.com/codeigniter4/CodeIgniter4/issues/2831) +- Bug: Kint Config isnt loaded [\#2830](https://github.com/codeigniter4/CodeIgniter4/issues/2830) +- Bug: RouteCollection::resource\(\) doesn't work with grouped rules [\#2829](https://github.com/codeigniter4/CodeIgniter4/issues/2829) +- Bug: $forge Property in Seeder Class Never Initialize [\#2825](https://github.com/codeigniter4/CodeIgniter4/issues/2825) +- Bug: getSegments\(\) returns an array with 2 empty strings when accessing / [\#2822](https://github.com/codeigniter4/CodeIgniter4/issues/2822) +- Bug: Cell Caching in View Cells and SOLVE! [\#2821](https://github.com/codeigniter4/CodeIgniter4/issues/2821) +- Bug: saveData option doesn't work in Views [\#2818](https://github.com/codeigniter4/CodeIgniter4/issues/2818) +- Bug: Validation placeholder not being replaced [\#2817](https://github.com/codeigniter4/CodeIgniter4/issues/2817) +- Bug: Problems with QueryBuilder when run multiple queries one by one [\#2800](https://github.com/codeigniter4/CodeIgniter4/issues/2800) +- Bug: Routing placeholder in "controller"part of route doesn't work [\#2787](https://github.com/codeigniter4/CodeIgniter4/issues/2787) +- Bug: session\(\)-\>push\(\) Strange behavior [\#2786](https://github.com/codeigniter4/CodeIgniter4/issues/2786) +- Bug: php spark serve [\#2784](https://github.com/codeigniter4/CodeIgniter4/issues/2784) +- Bug: Can't paginate query with group by [\#2776](https://github.com/codeigniter4/CodeIgniter4/issues/2776) +- Bug: negotiateLocale bug in Safari with fr-ca locale [\#2774](https://github.com/codeigniter4/CodeIgniter4/issues/2774) +- Bug: Controller in Sub Directory is not working [\#2764](https://github.com/codeigniter4/CodeIgniter4/issues/2764) +- Bug: rename release By "v" [\#2763](https://github.com/codeigniter4/CodeIgniter4/issues/2763) +- Bug: db query '?' bind is not working when use sql with ':=' operator. [\#2762](https://github.com/codeigniter4/CodeIgniter4/issues/2762) +- Bug: Multiple select validation problem [\#2757](https://github.com/codeigniter4/CodeIgniter4/issues/2757) +- Bug: Official Site is not working [\#2749](https://github.com/codeigniter4/CodeIgniter4/issues/2749) +- Bug: Logger context placeholders {file} and {line} are wrong [\#2743](https://github.com/codeigniter4/CodeIgniter4/issues/2743) +- Bug: Decimal validation fails without leading digit [\#2740](https://github.com/codeigniter4/CodeIgniter4/issues/2740) +- Bug: Model insert Created\_at and updated\_at get when new record added [\#2737](https://github.com/codeigniter4/CodeIgniter4/issues/2737) +- Bug: appendHeader 500 error if header does not exist [\#2730](https://github.com/codeigniter4/CodeIgniter4/issues/2730) +- Bug: codeigniter4 download link 404 resource not found [\#2727](https://github.com/codeigniter4/CodeIgniter4/issues/2727) +- Bug: Logger `path` property ignored [\#2725](https://github.com/codeigniter4/CodeIgniter4/issues/2725) +- Bug: $this-\>request-\>getPost\(\) is empty when json is send by postman [\#2720](https://github.com/codeigniter4/CodeIgniter4/issues/2720) +- Bug: open path /0 at uri got error "Class Home does not exist" on development environment [\#2716](https://github.com/codeigniter4/CodeIgniter4/issues/2716) +- Bug: calling countAllResults after find\($id\) produce wrong result [\#2705](https://github.com/codeigniter4/CodeIgniter4/issues/2705) +- Bug: $routes-\>cli\(\) accessible via web browser if autoroute is true [\#2704](https://github.com/codeigniter4/CodeIgniter4/issues/2704) +- Bug: Controllers and Views in subdirectories not working [\#2701](https://github.com/codeigniter4/CodeIgniter4/issues/2701) +- Bug: undefined model method should throw exception [\#2688](https://github.com/codeigniter4/CodeIgniter4/issues/2688) +- Bug: The custom error config of validation is not working [\#2678](https://github.com/codeigniter4/CodeIgniter4/issues/2678) +- Bug: Can't test redirect\(\)-\>route\('routename'\), while redirect\(\)-\>to\('path'\) is working with ControllerTester [\#2676](https://github.com/codeigniter4/CodeIgniter4/issues/2676) +- Bug: php spark migrate:create File -n NameSpace doesn't create migration class under NameSpace/Database/Migrations directory with composer autoload [\#2664](https://github.com/codeigniter4/CodeIgniter4/issues/2664) +- Bug: \I18n\Time object displaying +1 year when object date is set to 2021-12-31 [\#2663](https://github.com/codeigniter4/CodeIgniter4/issues/2663) +- Bug: Route options filter didn't working [\#2654](https://github.com/codeigniter4/CodeIgniter4/issues/2654) +- Bug: Error in Seeder [\#2653](https://github.com/codeigniter4/CodeIgniter4/issues/2653) +- Bug: spark no longer lists function when used by without any parameters [\#2645](https://github.com/codeigniter4/CodeIgniter4/issues/2645) +- Bug: Number Helper, Currency Fraction issue [\#2634](https://github.com/codeigniter4/CodeIgniter4/issues/2634) +- Bug: forceHTTPS method ignores baseURL configuration when redirecting [\#2633](https://github.com/codeigniter4/CodeIgniter4/issues/2633) +- While serving Application on CLI using different port debugbar is still using a default 8080 port [\#2630](https://github.com/codeigniter4/CodeIgniter4/issues/2630) +- Bug: spark migrate -all with appstarter [\#2627](https://github.com/codeigniter4/CodeIgniter4/issues/2627) +- Bug: Problem when compiled vendor as PHAR file [\#2623](https://github.com/codeigniter4/CodeIgniter4/issues/2623) +- Bug: debugbar javascript error [\#2621](https://github.com/codeigniter4/CodeIgniter4/issues/2621) +- Bug: ResourceController json response always empty [\#2617](https://github.com/codeigniter4/CodeIgniter4/issues/2617) +- Bug: Chrome logger does not work. [\#2616](https://github.com/codeigniter4/CodeIgniter4/issues/2616) +- Bug: [\#2608](https://github.com/codeigniter4/CodeIgniter4/issues/2608) +- User Guide is not in HTML in the download file [\#2607](https://github.com/codeigniter4/CodeIgniter4/issues/2607) +- Unnecessary files in the download installation [\#2606](https://github.com/codeigniter4/CodeIgniter4/issues/2606) +- Bug: Class 'Kint\Renderer\Renderer' not found [\#2605](https://github.com/codeigniter4/CodeIgniter4/issues/2605) +- Bug: Codeigniter4/framework composer.json not updated [\#2601](https://github.com/codeigniter4/CodeIgniter4/issues/2601) +- \[Docs\] Loading Environment into Configuration documentation described wrong [\#2554](https://github.com/codeigniter4/CodeIgniter4/issues/2554) +- Bug: Sessions dont work on PostgreSQL [\#2546](https://github.com/codeigniter4/CodeIgniter4/issues/2546) +- Bug: router service adds backslash to controllername if route is configured [\#2520](https://github.com/codeigniter4/CodeIgniter4/issues/2520) +- Bug: JSONFormatter-\>format\(\) cannot handle errordata, only outputs it's own error [\#2434](https://github.com/codeigniter4/CodeIgniter4/issues/2434) +- Bug: HTTP Feature Testing only runs the FIRST test [\#2393](https://github.com/codeigniter4/CodeIgniter4/issues/2393) +- Bug: Spark issue with PHP install location [\#2367](https://github.com/codeigniter4/CodeIgniter4/issues/2367) +- spark route issue [\#2194](https://github.com/codeigniter4/CodeIgniter4/issues/2194) + +**Closed issues:** + +- Modular MVP on CI4 [\#2900](https://github.com/codeigniter4/CodeIgniter4/issues/2900) +- About javascript: void \(0\); [\#2887](https://github.com/codeigniter4/CodeIgniter4/issues/2887) +- Entity returns null when used on the model [\#2838](https://github.com/codeigniter4/CodeIgniter4/issues/2838) +- php spark migrate -g does not work [\#2832](https://github.com/codeigniter4/CodeIgniter4/issues/2832) +- Bug: Namespacing of app/Config folder vs. app/Controller [\#2826](https://github.com/codeigniter4/CodeIgniter4/issues/2826) +- Controller Call to a member function getPost\(\) on null [\#2823](https://github.com/codeigniter4/CodeIgniter4/issues/2823) +- QueryBuilder - Does not support JOIN in UPDATE [\#2799](https://github.com/codeigniter4/CodeIgniter4/issues/2799) +- Database model error when limiting delete\(\) [\#2780](https://github.com/codeigniter4/CodeIgniter4/issues/2780) +- codeigniter4/codeigniter4 package not exists in packagist [\#2753](https://github.com/codeigniter4/CodeIgniter4/issues/2753) +- datamap Entities not works! [\#2747](https://github.com/codeigniter4/CodeIgniter4/issues/2747) +- Error: Call to undefined function CodeIgniter\CLI\mb\_strpos\(\) [\#2746](https://github.com/codeigniter4/CodeIgniter4/issues/2746) +- CodeIgniter\Log\Logger::logPath property is never used. [\#2738](https://github.com/codeigniter4/CodeIgniter4/issues/2738) +- Bug: set\_radio\(\) in Form Helper does not work when radio button value equals "0" [\#2728](https://github.com/codeigniter4/CodeIgniter4/issues/2728) +- Array validation has a problem [\#2714](https://github.com/codeigniter4/CodeIgniter4/issues/2714) +- delete cookie not working [\#2700](https://github.com/codeigniter4/CodeIgniter4/issues/2700) +- remove default language local from url [\#2682](https://github.com/codeigniter4/CodeIgniter4/issues/2682) +- OpenSSLHandler: Encrypt/Decrypt [\#2680](https://github.com/codeigniter4/CodeIgniter4/issues/2680) +- RESTFUL API with CORS problem [\#2667](https://github.com/codeigniter4/CodeIgniter4/issues/2667) +- I guess there's no the third parameter [\#2657](https://github.com/codeigniter4/CodeIgniter4/issues/2657) +- set ci4 repo default branch = master [\#2643](https://github.com/codeigniter4/CodeIgniter4/issues/2643) +- BUG: 4.0.2 Kint not found [\#2639](https://github.com/codeigniter4/CodeIgniter4/issues/2639) +- Feature: Migrate:Rollback/Refresh confirmation in production environment [\#2385](https://github.com/codeigniter4/CodeIgniter4/issues/2385) + +**Merged pull requests:** + +- 4.0.3 release [\#2912](https://github.com/codeigniter4/CodeIgniter4/pull/2912) ([lonnieezell](https://github.com/lonnieezell)) +- url\_title\(\) used CI3 style in user guide [\#2911](https://github.com/codeigniter4/CodeIgniter4/pull/2911) ([jreklund](https://github.com/jreklund)) +- fix undefined class 'CodeIgniter' [\#2910](https://github.com/codeigniter4/CodeIgniter4/pull/2910) ([PingZii](https://github.com/PingZii)) +- Improved subjects in Controller and Routing chapter [\#2908](https://github.com/codeigniter4/CodeIgniter4/pull/2908) ([jreklund](https://github.com/jreklund)) +- Fix Model::first\(\) only use orderBy\(\) when group by is not empty [\#2907](https://github.com/codeigniter4/CodeIgniter4/pull/2907) ([samsonasik](https://github.com/samsonasik)) +- Allow bypassing content negotiation during API responses. [\#2904](https://github.com/codeigniter4/CodeIgniter4/pull/2904) ([lonnieezell](https://github.com/lonnieezell)) +- Ugtweaks [\#2903](https://github.com/codeigniter4/CodeIgniter4/pull/2903) ([lonnieezell](https://github.com/lonnieezell)) +- Carbonads [\#2902](https://github.com/codeigniter4/CodeIgniter4/pull/2902) ([lonnieezell](https://github.com/lonnieezell)) +- Added information about the new features of the Pagination library [\#2901](https://github.com/codeigniter4/CodeIgniter4/pull/2901) ([jlamim](https://github.com/jlamim)) +- New features for pagination [\#2899](https://github.com/codeigniter4/CodeIgniter4/pull/2899) ([jlamim](https://github.com/jlamim)) +- Fixed lang\(\) example in user guide [\#2898](https://github.com/codeigniter4/CodeIgniter4/pull/2898) ([nmolinos](https://github.com/nmolinos)) +- Make validation placeholders always available [\#2897](https://github.com/codeigniter4/CodeIgniter4/pull/2897) ([jreklund](https://github.com/jreklund)) +- \[ci skip\] Add `make.bat` for Windows users [\#2895](https://github.com/codeigniter4/CodeIgniter4/pull/2895) ([paulbalandan](https://github.com/paulbalandan)) +- Added ability to delete row with string primary key via Model::delete\($id\) [\#2894](https://github.com/codeigniter4/CodeIgniter4/pull/2894) ([samsonasik](https://github.com/samsonasik)) +- Update of the pagination template to make the correct use of the locale [\#2892](https://github.com/codeigniter4/CodeIgniter4/pull/2892) ([jlamim](https://github.com/jlamim)) +- \[ci skip\] route placeholders 'id' to 'num' [\#2891](https://github.com/codeigniter4/CodeIgniter4/pull/2891) ([Instrye](https://github.com/Instrye)) +- \[ci skip\] fix warnings on compiling user guide [\#2886](https://github.com/codeigniter4/CodeIgniter4/pull/2886) ([paulbalandan](https://github.com/paulbalandan)) +- Added more Common functions and improved rendering in userguide [\#2884](https://github.com/codeigniter4/CodeIgniter4/pull/2884) ([jreklund](https://github.com/jreklund)) +- Build Your First Application used url\_title incorrectly [\#2883](https://github.com/codeigniter4/CodeIgniter4/pull/2883) ([jreklund](https://github.com/jreklund)) +- \[User guide\] Correcting some details in the part that talks about model and entities [\#2878](https://github.com/codeigniter4/CodeIgniter4/pull/2878) ([jlamim](https://github.com/jlamim)) +- Shifted basic URI Routing examples down [\#2874](https://github.com/codeigniter4/CodeIgniter4/pull/2874) ([nmolinos](https://github.com/nmolinos)) +- Better locale matching against broad groups. Fixes \#2774 [\#2872](https://github.com/codeigniter4/CodeIgniter4/pull/2872) ([lonnieezell](https://github.com/lonnieezell)) +- Fixes session active detection on force\_https function and add more test CodeIgniter::forceSecureAccess\(\) run force\_https\(\) [\#2871](https://github.com/codeigniter4/CodeIgniter4/pull/2871) ([samsonasik](https://github.com/samsonasik)) +- clean up use statements: remove unused and sort [\#2870](https://github.com/codeigniter4/CodeIgniter4/pull/2870) ([samsonasik](https://github.com/samsonasik)) +- more test for View::renderString\(\) for null tempData [\#2869](https://github.com/codeigniter4/CodeIgniter4/pull/2869) ([samsonasik](https://github.com/samsonasik)) +- Localized label in validation rules [\#2868](https://github.com/codeigniter4/CodeIgniter4/pull/2868) ([michalsn](https://github.com/michalsn)) +- \[ci skip\] update translations version [\#2867](https://github.com/codeigniter4/CodeIgniter4/pull/2867) ([Instrye](https://github.com/Instrye)) +- Initialize $forge property in Seeder Class - fixes \#2825 [\#2864](https://github.com/codeigniter4/CodeIgniter4/pull/2864) ([jlamim](https://github.com/jlamim)) +- fix. saveData not work [\#2861](https://github.com/codeigniter4/CodeIgniter4/pull/2861) ([Instrye](https://github.com/Instrye)) +- fix. getGetPost and getPostGet can't work in index empty [\#2860](https://github.com/codeigniter4/CodeIgniter4/pull/2860) ([Instrye](https://github.com/Instrye)) +- \[ci skip\]fix. getHeader return header object [\#2859](https://github.com/codeigniter4/CodeIgniter4/pull/2859) ([Instrye](https://github.com/Instrye)) +- fix. filters alias multiple [\#2857](https://github.com/codeigniter4/CodeIgniter4/pull/2857) ([Instrye](https://github.com/Instrye)) +- \[ci skip\] typo fix Initial Configuration & Set Up [\#2856](https://github.com/codeigniter4/CodeIgniter4/pull/2856) ([samsonasik](https://github.com/samsonasik)) +- Enclose file paths in double quotes to capture spaces [\#2853](https://github.com/codeigniter4/CodeIgniter4/pull/2853) ([paulbalandan](https://github.com/paulbalandan)) +- Strip directory separators from auto-generated cell cache name. Fixes… [\#2851](https://github.com/codeigniter4/CodeIgniter4/pull/2851) ([lonnieezell](https://github.com/lonnieezell)) +- Normalize dir separator of Exceptions::cleanPath and added more paths to clean [\#2847](https://github.com/codeigniter4/CodeIgniter4/pull/2847) ([paulbalandan](https://github.com/paulbalandan)) +- Improve readability in the userguide with a fixed size [\#2846](https://github.com/codeigniter4/CodeIgniter4/pull/2846) ([jreklund](https://github.com/jreklund)) +- Fixed Issue \#2840 on discovery of classes by FileLocator [\#2844](https://github.com/codeigniter4/CodeIgniter4/pull/2844) ([paulbalandan](https://github.com/paulbalandan)) +- add $segment parameter in pager call by Model.php [\#2843](https://github.com/codeigniter4/CodeIgniter4/pull/2843) ([paul45](https://github.com/paul45)) +- Improve flash of unstyled content in userguide [\#2842](https://github.com/codeigniter4/CodeIgniter4/pull/2842) ([jreklund](https://github.com/jreklund)) +- Add English message for "string" validation rule [\#2841](https://github.com/codeigniter4/CodeIgniter4/pull/2841) ([rmilecki](https://github.com/rmilecki)) +- more tests for Common functions [\#2837](https://github.com/codeigniter4/CodeIgniter4/pull/2837) ([samsonasik](https://github.com/samsonasik)) +- Pagination: open page \> pageCount get last page [\#2834](https://github.com/codeigniter4/CodeIgniter4/pull/2834) ([samsonasik](https://github.com/samsonasik)) +- add ability for nested language definition [\#2833](https://github.com/codeigniter4/CodeIgniter4/pull/2833) ([samsonasik](https://github.com/samsonasik)) +- Documentation fixes [\#2827](https://github.com/codeigniter4/CodeIgniter4/pull/2827) ([pjio](https://github.com/pjio)) +- fix. URI path is empty [\#2824](https://github.com/codeigniter4/CodeIgniter4/pull/2824) ([Instrye](https://github.com/Instrye)) +- ignore coverage on exit and die [\#2820](https://github.com/codeigniter4/CodeIgniter4/pull/2820) ([samsonasik](https://github.com/samsonasik)) +- add respondUpdated\(\) method into API\ResponseTrait [\#2816](https://github.com/codeigniter4/CodeIgniter4/pull/2816) ([samsonasik](https://github.com/samsonasik)) +- ignore coverage on !CI\_DEBUG [\#2814](https://github.com/codeigniter4/CodeIgniter4/pull/2814) ([samsonasik](https://github.com/samsonasik)) +- Fix missing InvalidArgumentException in Database\BaseBuilder [\#2813](https://github.com/codeigniter4/CodeIgniter4/pull/2813) ([samsonasik](https://github.com/samsonasik)) +- Ensure $\_SERVER\['SCRIPT\_NAME'\] ends with PHP [\#2810](https://github.com/codeigniter4/CodeIgniter4/pull/2810) ([willnode](https://github.com/willnode)) +- make named constructor in Exception classes consistent: use return instead of throw [\#2809](https://github.com/codeigniter4/CodeIgniter4/pull/2809) ([samsonasik](https://github.com/samsonasik)) +- Check if dataset is empty before Model update. [\#2808](https://github.com/codeigniter4/CodeIgniter4/pull/2808) ([vibbow](https://github.com/vibbow)) +- test Controller::validate\(\) with string rules [\#2807](https://github.com/codeigniter4/CodeIgniter4/pull/2807) ([samsonasik](https://github.com/samsonasik)) +- clean up buffer tweak in FeatureTestCaseTest [\#2805](https://github.com/codeigniter4/CodeIgniter4/pull/2805) ([samsonasik](https://github.com/samsonasik)) +- using realpath\(\) for define $pathsPath in index.php [\#2804](https://github.com/codeigniter4/CodeIgniter4/pull/2804) ([samsonasik](https://github.com/samsonasik)) +- add ext-mbstring to required and update regex that sanitize file name [\#2803](https://github.com/codeigniter4/CodeIgniter4/pull/2803) ([samsonasik](https://github.com/samsonasik)) +- Add resetting QBFrom part [\#2802](https://github.com/codeigniter4/CodeIgniter4/pull/2802) ([michalsn](https://github.com/michalsn)) +- Update Routes.php [\#2801](https://github.com/codeigniter4/CodeIgniter4/pull/2801) ([mostafakhudair](https://github.com/mostafakhudair)) +- add more test for Entity : 100% tested [\#2798](https://github.com/codeigniter4/CodeIgniter4/pull/2798) ([samsonasik](https://github.com/samsonasik)) +- \[ci skip\] Fix download badge total shows [\#2797](https://github.com/codeigniter4/CodeIgniter4/pull/2797) ([samsonasik](https://github.com/samsonasik)) +- test for I18n\Time::toFormattedDateString [\#2796](https://github.com/codeigniter4/CodeIgniter4/pull/2796) ([samsonasik](https://github.com/samsonasik)) +- test Logger::determineFile\(\) for no stack trace [\#2795](https://github.com/codeigniter4/CodeIgniter4/pull/2795) ([samsonasik](https://github.com/samsonasik)) +- test CLI\CLI::strlen\(null\) [\#2794](https://github.com/codeigniter4/CodeIgniter4/pull/2794) ([samsonasik](https://github.com/samsonasik)) +- test for API\ResponseTrait::format\(\) with format is not json or xml [\#2793](https://github.com/codeigniter4/CodeIgniter4/pull/2793) ([samsonasik](https://github.com/samsonasik)) +- test for View\Cell::render\(\) with class has initController\(\) method [\#2792](https://github.com/codeigniter4/CodeIgniter4/pull/2792) ([samsonasik](https://github.com/samsonasik)) +- test Autoloader::initialize\(\) with composer path not found [\#2791](https://github.com/codeigniter4/CodeIgniter4/pull/2791) ([samsonasik](https://github.com/samsonasik)) +- add ability to replace {locale} to request-\>getLocale\(\) in form\_open\('action'\) [\#2790](https://github.com/codeigniter4/CodeIgniter4/pull/2790) ([samsonasik](https://github.com/samsonasik)) +- test for IncomingRequest::getFileMultiple\(\) [\#2789](https://github.com/codeigniter4/CodeIgniter4/pull/2789) ([samsonasik](https://github.com/samsonasik)) +- add MockEmail class [\#2788](https://github.com/codeigniter4/CodeIgniter4/pull/2788) ([samsonasik](https://github.com/samsonasik)) +- test for CodeIgniter\Config\Services::email\(\) [\#2785](https://github.com/codeigniter4/CodeIgniter4/pull/2785) ([samsonasik](https://github.com/samsonasik)) +- make Model::paginate\(\) use default perPage from Config\Pager-\>perPage if $perPage parameter not passed [\#2782](https://github.com/codeigniter4/CodeIgniter4/pull/2782) ([samsonasik](https://github.com/samsonasik)) +- \#2780 - LIMIT. [\#2781](https://github.com/codeigniter4/CodeIgniter4/pull/2781) ([nowackipawel](https://github.com/nowackipawel)) +- \[ci skip\] \_remap method must have return [\#2779](https://github.com/codeigniter4/CodeIgniter4/pull/2779) ([Instrye](https://github.com/Instrye)) +- Rework get\_filenames [\#2778](https://github.com/codeigniter4/CodeIgniter4/pull/2778) ([MGatner](https://github.com/MGatner)) +- Fix \#2776 add ability to paginate\(\) query with group by [\#2777](https://github.com/codeigniter4/CodeIgniter4/pull/2777) ([samsonasik](https://github.com/samsonasik)) +- Update on "Build Your First Application" [\#2775](https://github.com/codeigniter4/CodeIgniter4/pull/2775) ([jreklund](https://github.com/jreklund)) +- Fix ? bind with := bind [\#2773](https://github.com/codeigniter4/CodeIgniter4/pull/2773) ([musmanikram](https://github.com/musmanikram)) +- Fixed some styling in "Installation" chapter \[ci skip\] [\#2772](https://github.com/codeigniter4/CodeIgniter4/pull/2772) ([jreklund](https://github.com/jreklund)) +- Uncommented tests, mistake in my previous PR :\( [\#2767](https://github.com/codeigniter4/CodeIgniter4/pull/2767) ([musmanikram](https://github.com/musmanikram)) +- fix. MYSQLI::DBDebug can't woker [\#2755](https://github.com/codeigniter4/CodeIgniter4/pull/2755) ([Instrye](https://github.com/Instrye)) +- fix. delete\_cookite can't delete alreday set Cookie [\#2709](https://github.com/codeigniter4/CodeIgniter4/pull/2709) ([Instrye](https://github.com/Instrye)) +- Re-write userguide to support Python 3 and future proofing Sphinx [\#2671](https://github.com/codeigniter4/CodeIgniter4/pull/2671) ([jreklund](https://github.com/jreklund)) +- Colored table in CLI [\#2624](https://github.com/codeigniter4/CodeIgniter4/pull/2624) ([enix-app](https://github.com/enix-app)) + +## [4.0.2](https://github.com/codeigniter4/CodeIgniter4/tree/4.0.2) (2020-02-25) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.1...4.0.2) + +**Fixed bugs:** + +- Bug: Your requirements could not be resolved to an installable set of packages. [\#2613](https://github.com/codeigniter4/CodeIgniter4/issues/2613) + +**Merged pull requests:** + +- Removed unused test class that was causing appstarter not to work from CLI. [\#2614](https://github.com/codeigniter4/CodeIgniter4/pull/2614) ([lonnieezell](https://github.com/lonnieezell)) +- \[UG\] Fix all Sphinx warnings [\#2611](https://github.com/codeigniter4/CodeIgniter4/pull/2611) ([LittleJ](https://github.com/LittleJ)) +- \[UG\] Sphinx\_rtd\_theme fixes and improvements [\#2610](https://github.com/codeigniter4/CodeIgniter4/pull/2610) ([LittleJ](https://github.com/LittleJ)) + +## [v4.0.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.1) (2020-02-24) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/4.0.0...v4.0.1) + +**Fixed bugs:** + +- Bug: Difficult architecture of the codeigniter4 project [\#2602](https://github.com/codeigniter4/CodeIgniter4/issues/2602) +- Bug: mentioned rc4 in the changelog file of V4 userguide [\#2599](https://github.com/codeigniter4/CodeIgniter4/issues/2599) + +**Merged pull requests:** + +- \[doc\] Removal of the 'rc' parameter from the installation / update co… [\#2604](https://github.com/codeigniter4/CodeIgniter4/pull/2604) ([jlamim](https://github.com/jlamim)) + +## [4.0.0](https://github.com/codeigniter4/CodeIgniter4/tree/4.0.0) (2020-02-24) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.4...4.0.0) + +**Fixed bugs:** + +- Bug: CI cant display error with API Response Trait [\#2586](https://github.com/codeigniter4/CodeIgniter4/issues/2586) +- Bug: testOrHavingBy\(\) requires a select [\#2584](https://github.com/codeigniter4/CodeIgniter4/issues/2584) +- Bug: Email-\>validateEmail\(\) on wrong email address leads to TypeError [\#2580](https://github.com/codeigniter4/CodeIgniter4/issues/2580) +- Bug: $forge-\>dropColumn not allowing arrays [\#2576](https://github.com/codeigniter4/CodeIgniter4/issues/2576) +- Bug: CI4 set ID to NULL when I try insert a row [\#2557](https://github.com/codeigniter4/CodeIgniter4/issues/2557) +- "Required" rules are not being enforced at all by the model [\#2555](https://github.com/codeigniter4/CodeIgniter4/issues/2555) +- Bug: Debug Bar showing before the head tag [\#2545](https://github.com/codeigniter4/CodeIgniter4/issues/2545) +- Bug: Docs / Library Reference / Validation: missing important information on reset\(\) method [\#2535](https://github.com/codeigniter4/CodeIgniter4/issues/2535) +- Bug: system/Config/BaseService.php getSharedInstance\(\) will not return mocks with non-lowercase key [\#2534](https://github.com/codeigniter4/CodeIgniter4/issues/2534) +- Bug: multi-column WHERE not prefixed [\#2532](https://github.com/codeigniter4/CodeIgniter4/issues/2532) +- Bug: CodeIgniter\Files\File::getSize\(\) - wrong type of result [\#2476](https://github.com/codeigniter4/CodeIgniter4/issues/2476) +- Bug: Move work with $\_GET\['page'\] from Model to Pager [\#2467](https://github.com/codeigniter4/CodeIgniter4/issues/2467) +- Bug: ImageHandler has no reality checks [\#2421](https://github.com/codeigniter4/CodeIgniter4/issues/2421) +- Bug: No default HTTP protocol version is set when creating a new Response [\#2383](https://github.com/codeigniter4/CodeIgniter4/issues/2383) +- Bug: Filter wildcards ignore default methods [\#2455](https://github.com/codeigniter4/CodeIgniter4/issues/2455) +- trailing slash cause redirect to root [\#2445](https://github.com/codeigniter4/CodeIgniter4/issues/2445) +- Bug: Commands cannot accept many paths [\#2148](https://github.com/codeigniter4/CodeIgniter4/issues/2148) + +**Closed issues:** + +- Add an 'alpha\_numeric\_punct' rule to FormatRules [\#2549](https://github.com/codeigniter4/CodeIgniter4/issues/2549) +- Feature: Forge, Adding Foreign key for table already created [\#2543](https://github.com/codeigniter4/CodeIgniter4/issues/2543) +- Error In UserGuide [\#2530](https://github.com/codeigniter4/CodeIgniter4/issues/2530) +- Lack of test on Travis for PHP 7.4 and tests don't run on upcoming PHP versions [\#2293](https://github.com/codeigniter4/CodeIgniter4/issues/2293) +- Feature: model helper [\#2292](https://github.com/codeigniter4/CodeIgniter4/issues/2292) +- Issue with layouts renderer and sections =\> merge sections in one view [\#2491](https://github.com/codeigniter4/CodeIgniter4/issues/2491) +- Update package dependency version for kint-php/kint [\#2373](https://github.com/codeigniter4/CodeIgniter4/issues/2373) + +**Merged pull requests:** + +- Deprecate Devstarter, add `builds` [\#2598](https://github.com/codeigniter4/CodeIgniter4/pull/2598) ([MGatner](https://github.com/MGatner)) +- Fix typo issues causing Sphinx warnings + Update "Welcome page" screenshot [\#2597](https://github.com/codeigniter4/CodeIgniter4/pull/2597) ([LittleJ](https://github.com/LittleJ)) +- Update sphinx\_rtd\_theme from version 0.2.4 to version 0.4.3 [\#2596](https://github.com/codeigniter4/CodeIgniter4/pull/2596) ([LittleJ](https://github.com/LittleJ)) +- Fixed Bug: testOrHavingBy\(\) requires a select | \#2584 [\#2595](https://github.com/codeigniter4/CodeIgniter4/pull/2595) ([jlamim](https://github.com/jlamim)) +- Add Slack to the "Support" section of the documentation [\#2594](https://github.com/codeigniter4/CodeIgniter4/pull/2594) ([LittleJ](https://github.com/LittleJ)) +- Fixed links to the AJAX Requests details page [\#2593](https://github.com/codeigniter4/CodeIgniter4/pull/2593) ([jlamim](https://github.com/jlamim)) +- Add color scheme information in the documentation [\#2592](https://github.com/codeigniter4/CodeIgniter4/pull/2592) ([LittleJ](https://github.com/LittleJ)) +- User Guide authentication recommendations [\#2591](https://github.com/codeigniter4/CodeIgniter4/pull/2591) ([MGatner](https://github.com/MGatner)) +- Add a "Go further" section on the welcome page [\#2590](https://github.com/codeigniter4/CodeIgniter4/pull/2590) ([LittleJ](https://github.com/LittleJ)) +- Add DotEnv::parse\(\) [\#2588](https://github.com/codeigniter4/CodeIgniter4/pull/2588) ([MGatner](https://github.com/MGatner)) +- Better debug routes [\#2587](https://github.com/codeigniter4/CodeIgniter4/pull/2587) ([atishamte](https://github.com/atishamte)) +- Typos change in contributing.md [\#2583](https://github.com/codeigniter4/CodeIgniter4/pull/2583) ([atishamte](https://github.com/atishamte)) +- Fix type error in email validation [\#2582](https://github.com/codeigniter4/CodeIgniter4/pull/2582) ([musmanikram](https://github.com/musmanikram)) +- Fixed Markdown heading [\#2581](https://github.com/codeigniter4/CodeIgniter4/pull/2581) ([ImMaax](https://github.com/ImMaax)) +- Cache FileHandler unlink exception [\#2579](https://github.com/codeigniter4/CodeIgniter4/pull/2579) ([MGatner](https://github.com/MGatner)) +- Fix drop column with array [\#2578](https://github.com/codeigniter4/CodeIgniter4/pull/2578) ([musmanikram](https://github.com/musmanikram)) +- Refactor Tests [\#2577](https://github.com/codeigniter4/CodeIgniter4/pull/2577) ([MGatner](https://github.com/MGatner)) +- Subfolder base\_url\(\) with parameter [\#2574](https://github.com/codeigniter4/CodeIgniter4/pull/2574) ([MGatner](https://github.com/MGatner)) +- Image verification [\#2573](https://github.com/codeigniter4/CodeIgniter4/pull/2573) ([MGatner](https://github.com/MGatner)) +- Use default protocol if unspecified [\#2572](https://github.com/codeigniter4/CodeIgniter4/pull/2572) ([MGatner](https://github.com/MGatner)) +- Retain CLI segments [\#2571](https://github.com/codeigniter4/CodeIgniter4/pull/2571) ([MGatner](https://github.com/MGatner)) +- Model's set method should accept not only string [\#2570](https://github.com/codeigniter4/CodeIgniter4/pull/2570) ([nowackipawel](https://github.com/nowackipawel)) +- Use lowercase service names [\#2569](https://github.com/codeigniter4/CodeIgniter4/pull/2569) ([MGatner](https://github.com/MGatner)) +- Apply User Guide code style to Errors [\#2567](https://github.com/codeigniter4/CodeIgniter4/pull/2567) ([MGatner](https://github.com/MGatner)) +- Move debug toolbar after head tag. Fixes \#2545 [\#2566](https://github.com/codeigniter4/CodeIgniter4/pull/2566) ([MGatner](https://github.com/MGatner)) +- Updates To Kint Loading [\#2565](https://github.com/codeigniter4/CodeIgniter4/pull/2565) ([najdanovicivan](https://github.com/najdanovicivan)) +- Updated loader and composer script to use Kint 3.3 for \#2373 [\#2564](https://github.com/codeigniter4/CodeIgniter4/pull/2564) ([lonnieezell](https://github.com/lonnieezell)) +- Added rule "alpha\_numeric\_punct" [\#2562](https://github.com/codeigniter4/CodeIgniter4/pull/2562) ([dafriend](https://github.com/dafriend)) +- Fix - Add ajax to docs "General " page \[ci skip\] [\#2561](https://github.com/codeigniter4/CodeIgniter4/pull/2561) ([dafriend](https://github.com/dafriend)) +- MySQLi: Incorrect DBDebug flag used for connection charset [\#2558](https://github.com/codeigniter4/CodeIgniter4/pull/2558) ([jreklund](https://github.com/jreklund)) +- Update File.php [\#2552](https://github.com/codeigniter4/CodeIgniter4/pull/2552) ([thanhtaivtt](https://github.com/thanhtaivtt)) +- disable buffer check on "testing" environment [\#2551](https://github.com/codeigniter4/CodeIgniter4/pull/2551) ([samsonasik](https://github.com/samsonasik)) +- Improved view: welcome\_message.php [\#2550](https://github.com/codeigniter4/CodeIgniter4/pull/2550) ([Vizzielli](https://github.com/Vizzielli)) +- Add retry creation server when the port is used [\#2544](https://github.com/codeigniter4/CodeIgniter4/pull/2544) ([thanhtaivtt](https://github.com/thanhtaivtt)) +- New "welcome" page [\#2541](https://github.com/codeigniter4/CodeIgniter4/pull/2541) ([LittleJ](https://github.com/LittleJ)) +- valid\_ip removed $data which was causing exception [\#2540](https://github.com/codeigniter4/CodeIgniter4/pull/2540) ([nowackipawel](https://github.com/nowackipawel)) +- explanation of reset\(\) method in section Working With Validation fixes \#2535 [\#2539](https://github.com/codeigniter4/CodeIgniter4/pull/2539) ([bivanbi](https://github.com/bivanbi)) +- Update TravisCI config for PHP7.4 [\#2537](https://github.com/codeigniter4/CodeIgniter4/pull/2537) ([musmanikram](https://github.com/musmanikram)) +- Fix multi-column WHERE not prefixed with DBPrefix [\#2533](https://github.com/codeigniter4/CodeIgniter4/pull/2533) ([musmanikram](https://github.com/musmanikram)) +- Update images.rst [\#2529](https://github.com/codeigniter4/CodeIgniter4/pull/2529) ([avegacms](https://github.com/avegacms)) +- Added new model helper method. [\#2514](https://github.com/codeigniter4/CodeIgniter4/pull/2514) ([lonnieezell](https://github.com/lonnieezell)) +- Debug bar: Dark/light mode + Complete CSS refactoring [\#2478](https://github.com/codeigniter4/CodeIgniter4/pull/2478) ([LittleJ](https://github.com/LittleJ)) +- WIP Update Validation.php [\#2083](https://github.com/codeigniter4/CodeIgniter4/pull/2083) ([MohKari](https://github.com/MohKari)) + +## [v4.0.0-rc.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.4) (2020-02-07) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.3...v4.0.0-rc.4) + +**Fixed bugs:** + +- Bug: Class 'Locale' not found when I try to use Time::parse\(\) [\#2519](https://github.com/codeigniter4/CodeIgniter4/issues/2519) +- Bug: Wrong Links for pager when having subfolders [\#2505](https://github.com/codeigniter4/CodeIgniter4/issues/2505) +- View Cell render not be call initController [\#2500](https://github.com/codeigniter4/CodeIgniter4/issues/2500) +- Bug: user guide compile failed. [\#2492](https://github.com/codeigniter4/CodeIgniter4/issues/2492) +- Bug: setAutoRoute to false not working [\#2480](https://github.com/codeigniter4/CodeIgniter4/issues/2480) +- Bug: CodeIgniter\Router\Router-\>checkRoutes\(\) strpos\(\) expects parameter 1 to be string, array given [\#2479](https://github.com/codeigniter4/CodeIgniter4/issues/2479) +- Bug: Unable to use ul\(\) helper with out modification [\#2473](https://github.com/codeigniter4/CodeIgniter4/issues/2473) +- Bug: Cannot pass value in the function in ControllerTest [\#2470](https://github.com/codeigniter4/CodeIgniter4/issues/2470) +- Bug: $useTimestamps cause insert\(\) exception [\#2469](https://github.com/codeigniter4/CodeIgniter4/issues/2469) +- Bug: prototype of setBody\($data\); method in CodeIgniter\HTTP\Message should be changed to setBody\(string $data\); [\#2466](https://github.com/codeigniter4/CodeIgniter4/issues/2466) +- Don't update it. It's rubbishBug: [\#2463](https://github.com/codeigniter4/CodeIgniter4/issues/2463) +- Bug: Autoload over Composer [\#2461](https://github.com/codeigniter4/CodeIgniter4/issues/2461) +- Bug: The docs say `composer install` instead of `composer required` [\#2457](https://github.com/codeigniter4/CodeIgniter4/issues/2457) +- Bug: if not CSPEnaled but i have some [\#2456](https://github.com/codeigniter4/CodeIgniter4/issues/2456) +- Bug: IsAJAX\(\) relies on inconsistent headers [\#2454](https://github.com/codeigniter4/CodeIgniter4/issues/2454) +- Bug: [\#2448](https://github.com/codeigniter4/CodeIgniter4/issues/2448) +- Bug: Double use where and etc [\#2444](https://github.com/codeigniter4/CodeIgniter4/issues/2444) +- Bug: Double use esc function with form\_input and etc... [\#2443](https://github.com/codeigniter4/CodeIgniter4/issues/2443) +- Bug: Entity casts do not cast the original data [\#2441](https://github.com/codeigniter4/CodeIgniter4/issues/2441) +- Bug: namespace view returns empty value [\#2440](https://github.com/codeigniter4/CodeIgniter4/issues/2440) +- Bug: php spark Call to undefined function CodeIgniter\Autoloader\get\_filenames\(\) [\#2439](https://github.com/codeigniter4/CodeIgniter4/issues/2439) +- Bug: Curly brace deprecation [\#2430](https://github.com/codeigniter4/CodeIgniter4/issues/2430) +- Bug: Routes and Namespace [\#2423](https://github.com/codeigniter4/CodeIgniter4/issues/2423) +- Bug: Validation not working [\#2418](https://github.com/codeigniter4/CodeIgniter4/issues/2418) +- Bug: Baseservice - getSharedInstance [\#2414](https://github.com/codeigniter4/CodeIgniter4/issues/2414) +- Bug: base\_url\(\) and redirect\(\) not honoring baseURL with paths [\#2409](https://github.com/codeigniter4/CodeIgniter4/issues/2409) +- form\_input double escaping data why ? [\#2405](https://github.com/codeigniter4/CodeIgniter4/issues/2405) +- Bug: initController not called in ResourceController [\#2404](https://github.com/codeigniter4/CodeIgniter4/issues/2404) +- Bug: [\#2397](https://github.com/codeigniter4/CodeIgniter4/issues/2397) +- URL Helper safe\_mailto UTF8Bug: [\#2396](https://github.com/codeigniter4/CodeIgniter4/issues/2396) +- CSRF Filter redirect back not working [\#2395](https://github.com/codeigniter4/CodeIgniter4/issues/2395) +- Bug: 404 error page override with cache [\#2391](https://github.com/codeigniter4/CodeIgniter4/issues/2391) +- Bug: Mixed migration formats don't order [\#2386](https://github.com/codeigniter4/CodeIgniter4/issues/2386) +- Bug: \CodeIgniter\Model::validate\(\) returns TRUE if $data is empty [\#2384](https://github.com/codeigniter4/CodeIgniter4/issues/2384) +- Bug: Usage of `static::methodName` in CodeIgniter\Config\Services prevents Service overriding [\#2376](https://github.com/codeigniter4/CodeIgniter4/issues/2376) +- Bug: Duplicate headers in response [\#2375](https://github.com/codeigniter4/CodeIgniter4/issues/2375) +- Bug: Nothing work with minimal config \(DIRECTORY SEPARATOR\) [\#2370](https://github.com/codeigniter4/CodeIgniter4/issues/2370) +- Bug: current\_url function not working as expected. [\#2365](https://github.com/codeigniter4/CodeIgniter4/issues/2365) +- Bug: localhost development server after edit the content not updated or reloaded [\#2363](https://github.com/codeigniter4/CodeIgniter4/issues/2363) +- Bug: with the parser, nl2br in a foreach duplicates entries. [\#2360](https://github.com/codeigniter4/CodeIgniter4/issues/2360) +- Bug: Prevents the use of global functions with parameters \[Validation\] [\#2357](https://github.com/codeigniter4/CodeIgniter4/issues/2357) +- Bug: lang\('app.name'\) should prefer APPPATH.Language/Validation/en/app.php over installed packages [\#2354](https://github.com/codeigniter4/CodeIgniter4/issues/2354) +- Bug: Inappropriate delimiter used in fillRouteParams [\#2353](https://github.com/codeigniter4/CodeIgniter4/issues/2353) +- Bug: Please there is issue on the time and date guide in codeigniter 4 [\#2351](https://github.com/codeigniter4/CodeIgniter4/issues/2351) +- Bug: Model\(\)-\>find\(null\) should return null value [\#2350](https://github.com/codeigniter4/CodeIgniter4/issues/2350) +- Bug: URL, Redirect and Pagination misbehave [\#2347](https://github.com/codeigniter4/CodeIgniter4/issues/2347) +- Bug: Toolbar ErrorException Division by zero [\#2340](https://github.com/codeigniter4/CodeIgniter4/issues/2340) +- Bug: Cannot pass a string param with a space to a custom parser plugin [\#2318](https://github.com/codeigniter4/CodeIgniter4/issues/2318) +- Bug: Logger Path Duplicated [\#2286](https://github.com/codeigniter4/CodeIgniter4/issues/2286) +- Bug: Email: SMTP Protocol Implementation @ Data Termination [\#2274](https://github.com/codeigniter4/CodeIgniter4/issues/2274) +- Bug: Redirect to route ignores path set in baseurl [\#2119](https://github.com/codeigniter4/CodeIgniter4/issues/2119) + +**Closed issues:** + +- $routes not do the job as well [\#2531](https://github.com/codeigniter4/CodeIgniter4/issues/2531) +- Multiple composer.json handling in codeigniter!! [\#2528](https://github.com/codeigniter4/CodeIgniter4/issues/2528) +- Mention about events in the upgrading doc pages [\#2521](https://github.com/codeigniter4/CodeIgniter4/issues/2521) +- Missing Constant in E-Mail [\#2512](https://github.com/codeigniter4/CodeIgniter4/issues/2512) +- Image Manipulation Class [\#2498](https://github.com/codeigniter4/CodeIgniter4/issues/2498) +- Schema param in the .env file [\#2483](https://github.com/codeigniter4/CodeIgniter4/issues/2483) +- system\Database\MigrationRunner-\>regress\(\) resets instance variable $namespace to null [\#2474](https://github.com/codeigniter4/CodeIgniter4/issues/2474) +- Issue passing data to views [\#2464](https://github.com/codeigniter4/CodeIgniter4/issues/2464) +- currentURL & previousURL doesnt work in parser [\#2460](https://github.com/codeigniter4/CodeIgniter4/issues/2460) +- Double quotes [\#2459](https://github.com/codeigniter4/CodeIgniter4/issues/2459) +- Feature about Localization [\#2419](https://github.com/codeigniter4/CodeIgniter4/issues/2419) +- Documentation Fix Needed [\#2412](https://github.com/codeigniter4/CodeIgniter4/issues/2412) +- No such file or Directory found In Ubuntu 19.10 [\#2394](https://github.com/codeigniter4/CodeIgniter4/issues/2394) +- previous\_url\(\) not loading the base path together [\#2378](https://github.com/codeigniter4/CodeIgniter4/issues/2378) +- Wrong Logo on GitHub page [\#2372](https://github.com/codeigniter4/CodeIgniter4/issues/2372) +- How to use the pagination with view parser? [\#2371](https://github.com/codeigniter4/CodeIgniter4/issues/2371) +- Feature Request: Validation: in\_db\[table.field\] [\#2366](https://github.com/codeigniter4/CodeIgniter4/issues/2366) +- Feature request [\#2361](https://github.com/codeigniter4/CodeIgniter4/issues/2361) +- Feature: AJAX route option [\#2310](https://github.com/codeigniter4/CodeIgniter4/issues/2310) +- Return value of CodeIgniter\Database\BaseConnection::getConnectStart\(\) must be of the type float, null returned [\#2158](https://github.com/codeigniter4/CodeIgniter4/issues/2158) +- Create Security Guideline [\#73](https://github.com/codeigniter4/CodeIgniter4/issues/73) + +**Merged pull requests:** + +- Update manual.rst [\#2527](https://github.com/codeigniter4/CodeIgniter4/pull/2527) ([avegacms](https://github.com/avegacms)) +- Page in the official documentation on ajax requests with iSAJAX\(\) fixes \#2454 [\#2526](https://github.com/codeigniter4/CodeIgniter4/pull/2526) ([jlamim](https://github.com/jlamim)) +- Remove incorrect inline doc type [\#2525](https://github.com/codeigniter4/CodeIgniter4/pull/2525) ([MGatner](https://github.com/MGatner)) +- Restore namespace after regress. Fixes \#2474 [\#2524](https://github.com/codeigniter4/CodeIgniter4/pull/2524) ([MGatner](https://github.com/MGatner)) +- Replace legacy CI3 constant. Fixes \#2512 [\#2523](https://github.com/codeigniter4/CodeIgniter4/pull/2523) ([MGatner](https://github.com/MGatner)) +- Adding Events information in the 'Upgrading from 3.x to 4.x' section [\#2522](https://github.com/codeigniter4/CodeIgniter4/pull/2522) ([jlamim](https://github.com/jlamim)) +- Fix pager URI to work in subfolders. [\#2518](https://github.com/codeigniter4/CodeIgniter4/pull/2518) ([lonnieezell](https://github.com/lonnieezell)) +- HTML Helper - Fix attribute type for lists [\#2516](https://github.com/codeigniter4/CodeIgniter4/pull/2516) ([najdanovicivan](https://github.com/najdanovicivan)) +- Layout Renderer Fix [\#2515](https://github.com/codeigniter4/CodeIgniter4/pull/2515) ([najdanovicivan](https://github.com/najdanovicivan)) +- \[ci skip\] Typo in userguide "Entity Classes - Business Logic" [\#2513](https://github.com/codeigniter4/CodeIgniter4/pull/2513) ([jreklund](https://github.com/jreklund)) +- Database add highlight [\#2511](https://github.com/codeigniter4/CodeIgniter4/pull/2511) ([MashinaMashina](https://github.com/MashinaMashina)) +- Revert Renderer section reset [\#2509](https://github.com/codeigniter4/CodeIgniter4/pull/2509) ([MGatner](https://github.com/MGatner)) +- Update ordering of search locations for better prioritization. Fixes \#2354 [\#2507](https://github.com/codeigniter4/CodeIgniter4/pull/2507) ([lonnieezell](https://github.com/lonnieezell)) +- Proposal: HTTP Response - Fix crash on CSP methods CSP is disabled [\#2506](https://github.com/codeigniter4/CodeIgniter4/pull/2506) ([najdanovicivan](https://github.com/najdanovicivan)) +- BaseConnection - Nullable return type in getConnectStart\(\) [\#2504](https://github.com/codeigniter4/CodeIgniter4/pull/2504) ([najdanovicivan](https://github.com/najdanovicivan)) +- View Renderer - Reset sections after generating the ouput [\#2502](https://github.com/codeigniter4/CodeIgniter4/pull/2502) ([najdanovicivan](https://github.com/najdanovicivan)) +- view\_cell call controller on initController method. [\#2501](https://github.com/codeigniter4/CodeIgniter4/pull/2501) ([byazrail](https://github.com/byazrail)) +- View Parser - Fix ParsePair\(\) with filter [\#2499](https://github.com/codeigniter4/CodeIgniter4/pull/2499) ([najdanovicivan](https://github.com/najdanovicivan)) +- Fix splitQueryPart\(\) [\#2497](https://github.com/codeigniter4/CodeIgniter4/pull/2497) ([MashinaMashina](https://github.com/MashinaMashina)) +- Use site\_url for RedirectResponse. Fixes \#2119 [\#2496](https://github.com/codeigniter4/CodeIgniter4/pull/2496) ([lonnieezell](https://github.com/lonnieezell)) +- \[ci skip\] update toolbar userguide [\#2495](https://github.com/codeigniter4/CodeIgniter4/pull/2495) ([Instrye](https://github.com/Instrye)) +- Debug Toolbar - Fix Debugbar-Time header, Render in \ [\#2494](https://github.com/codeigniter4/CodeIgniter4/pull/2494) ([najdanovicivan](https://github.com/najdanovicivan)) +- fix sphinx version. [\#2493](https://github.com/codeigniter4/CodeIgniter4/pull/2493) ([ytetsuro](https://github.com/ytetsuro)) +- fix. Toolbar init view Error [\#2490](https://github.com/codeigniter4/CodeIgniter4/pull/2490) ([Instrye](https://github.com/Instrye)) +- Fix pager [\#2489](https://github.com/codeigniter4/CodeIgniter4/pull/2489) ([MashinaMashina](https://github.com/MashinaMashina)) +- Update current\_url and previous\_url in the docs for View Parser. Fixes \#2460 [\#2486](https://github.com/codeigniter4/CodeIgniter4/pull/2486) ([lonnieezell](https://github.com/lonnieezell)) +- Typo in user guide "Running via the Command Line" [\#2485](https://github.com/codeigniter4/CodeIgniter4/pull/2485) ([jreklund](https://github.com/jreklund)) +- Services request add URI Core System extend support [\#2482](https://github.com/codeigniter4/CodeIgniter4/pull/2482) ([byazrail](https://github.com/byazrail)) +- Fix \#2479. Priority Redirection. [\#2481](https://github.com/codeigniter4/CodeIgniter4/pull/2481) ([Instrye](https://github.com/Instrye)) +- ControllerTest should work without URI specified. Fixes \#2470 [\#2472](https://github.com/codeigniter4/CodeIgniter4/pull/2472) ([lonnieezell](https://github.com/lonnieezell)) +- Transition from Zend Escaper to Laminas Escaper [\#2471](https://github.com/codeigniter4/CodeIgniter4/pull/2471) ([lonnieezell](https://github.com/lonnieezell)) +- Fix impossible length for migration table id. [\#2462](https://github.com/codeigniter4/CodeIgniter4/pull/2462) ([ytetsuro](https://github.com/ytetsuro)) +- Replace `composer install` by `composer require` [\#2458](https://github.com/codeigniter4/CodeIgniter4/pull/2458) ([SteeveDroz](https://github.com/SteeveDroz)) +- \[ci skip\] Error correction in reference to Query Builder emptyTable m… [\#2452](https://github.com/codeigniter4/CodeIgniter4/pull/2452) ([jlamim](https://github.com/jlamim)) +- CRITICAL when $\_SESSION is null / Argument 2 passed to dot\_array\_search\(\) must be \[\] [\#2450](https://github.com/codeigniter4/CodeIgniter4/pull/2450) ([nowackipawel](https://github.com/nowackipawel)) +- User Guide: Query Builder selectCount - error correction in example [\#2449](https://github.com/codeigniter4/CodeIgniter4/pull/2449) ([jlamim](https://github.com/jlamim)) +- Existing File checks \(Nowackipawel/patch-69\) [\#2447](https://github.com/codeigniter4/CodeIgniter4/pull/2447) ([MGatner](https://github.com/MGatner)) +- DB Insert Ignore \(Tada5hi/database-feature\) [\#2446](https://github.com/codeigniter4/CodeIgniter4/pull/2446) ([MGatner](https://github.com/MGatner)) +- Nice array view in debug toolbar [\#2438](https://github.com/codeigniter4/CodeIgniter4/pull/2438) ([MashinaMashina](https://github.com/MashinaMashina)) +- \[ci skip\] Fix Message method reference [\#2436](https://github.com/codeigniter4/CodeIgniter4/pull/2436) ([MGatner](https://github.com/MGatner)) +- Inserting through a model should respect all validation rules. Fixes \#2384 [\#2433](https://github.com/codeigniter4/CodeIgniter4/pull/2433) ([lonnieezell](https://github.com/lonnieezell)) +- Fix curly brace deprecation in php 7.4 [\#2432](https://github.com/codeigniter4/CodeIgniter4/pull/2432) ([musmanikram](https://github.com/musmanikram)) +- fix. safe\_mailto multi-byte safe [\#2429](https://github.com/codeigniter4/CodeIgniter4/pull/2429) ([Instrye](https://github.com/Instrye)) +- Add $recipients property to Config\Email [\#2427](https://github.com/codeigniter4/CodeIgniter4/pull/2427) ([dafriend](https://github.com/dafriend)) +- Add hex validation rule, test, Guide [\#2426](https://github.com/codeigniter4/CodeIgniter4/pull/2426) ([MGatner](https://github.com/MGatner)) +- fix: Router setDefaultNameSpace can't worker [\#2425](https://github.com/codeigniter4/CodeIgniter4/pull/2425) ([Instrye](https://github.com/Instrye)) +- Don't show duplicate Date headers when running under PHPs server. Fixes \#2375 [\#2422](https://github.com/codeigniter4/CodeIgniter4/pull/2422) ([lonnieezell](https://github.com/lonnieezell)) +- Change current\_url\(\) to use cloned URI [\#2420](https://github.com/codeigniter4/CodeIgniter4/pull/2420) ([MGatner](https://github.com/MGatner)) +- Revise Encryption Service Documentation \[ci skip\] [\#2417](https://github.com/codeigniter4/CodeIgniter4/pull/2417) ([dafriend](https://github.com/dafriend)) +- Add missing closing braces of condition 'hasError\(\)' under Check If… [\#2416](https://github.com/codeigniter4/CodeIgniter4/pull/2416) ([musmanikram](https://github.com/musmanikram)) +- Add 'nullable' to MySQL field data [\#2415](https://github.com/codeigniter4/CodeIgniter4/pull/2415) ([MGatner](https://github.com/MGatner)) +- fix. toolbar file 301 [\#2413](https://github.com/codeigniter4/CodeIgniter4/pull/2413) ([Instrye](https://github.com/Instrye)) +- \#2318 - fix parse params of plugin [\#2411](https://github.com/codeigniter4/CodeIgniter4/pull/2411) ([oleg1540](https://github.com/oleg1540)) +- Looks like a typo. [\#2410](https://github.com/codeigniter4/CodeIgniter4/pull/2410) ([AndiKod](https://github.com/AndiKod)) +- Ensure previous\_url\(\) gets accurate URI. [\#2408](https://github.com/codeigniter4/CodeIgniter4/pull/2408) ([lonnieezell](https://github.com/lonnieezell)) +- Fix url helper functions to work when site hosted in subfolders. [\#2407](https://github.com/codeigniter4/CodeIgniter4/pull/2407) ([lonnieezell](https://github.com/lonnieezell)) +- Fix issue \#2391 CodeIgniter::display404errors\(\) [\#2406](https://github.com/codeigniter4/CodeIgniter4/pull/2406) ([dafriend](https://github.com/dafriend)) +- Removed pointless isset\(\) check [\#2402](https://github.com/codeigniter4/CodeIgniter4/pull/2402) ([dafriend](https://github.com/dafriend)) +- Remove pointless check from conditional [\#2401](https://github.com/codeigniter4/CodeIgniter4/pull/2401) ([dafriend](https://github.com/dafriend)) +- Remove redundant check in conditionals [\#2400](https://github.com/codeigniter4/CodeIgniter4/pull/2400) ([dafriend](https://github.com/dafriend)) +- Revise Controllers Documentation \[ci skip\] [\#2399](https://github.com/codeigniter4/CodeIgniter4/pull/2399) ([dafriend](https://github.com/dafriend)) +- Edit .htaccess [\#2398](https://github.com/codeigniter4/CodeIgniter4/pull/2398) ([MashinaMashina](https://github.com/MashinaMashina)) +- Add validation function `is\_not\_unique` [\#2392](https://github.com/codeigniter4/CodeIgniter4/pull/2392) ([kennylajara](https://github.com/kennylajara)) +- Confer silent status to nested seeders [\#2389](https://github.com/codeigniter4/CodeIgniter4/pull/2389) ([MGatner](https://github.com/MGatner)) +- Fix copypaste command comment [\#2388](https://github.com/codeigniter4/CodeIgniter4/pull/2388) ([MGatner](https://github.com/MGatner)) +- Use only digits for migrations order [\#2387](https://github.com/codeigniter4/CodeIgniter4/pull/2387) ([MGatner](https://github.com/MGatner)) +- quick fix postgresql insert id [\#2382](https://github.com/codeigniter4/CodeIgniter4/pull/2382) ([iam-adty](https://github.com/iam-adty)) +- Fix: Use of CodeIgniter\Config\Services prevents Service overriding [\#2381](https://github.com/codeigniter4/CodeIgniter4/pull/2381) ([dafriend](https://github.com/dafriend)) +- Replace null log file extension check [\#2379](https://github.com/codeigniter4/CodeIgniter4/pull/2379) ([MGatner](https://github.com/MGatner)) +- Docs Rev: Replacing Core Classes \[ci skip\] [\#2377](https://github.com/codeigniter4/CodeIgniter4/pull/2377) ([dafriend](https://github.com/dafriend)) +- Remove LoggerAwareTrait from Email class [\#2369](https://github.com/codeigniter4/CodeIgniter4/pull/2369) ([dafriend](https://github.com/dafriend)) +- Remove log\_message from Email::\_\_construct [\#2368](https://github.com/codeigniter4/CodeIgniter4/pull/2368) ([dafriend](https://github.com/dafriend)) +- Email config doesn't incorporate .env items [\#2364](https://github.com/codeigniter4/CodeIgniter4/pull/2364) ([dafriend](https://github.com/dafriend)) +- Fix SMTP protocol problem [\#2362](https://github.com/codeigniter4/CodeIgniter4/pull/2362) ([jim-parry](https://github.com/jim-parry)) +- Bugfix Model after event data [\#2359](https://github.com/codeigniter4/CodeIgniter4/pull/2359) ([MGatner](https://github.com/MGatner)) +- Fix Logger config [\#2358](https://github.com/codeigniter4/CodeIgniter4/pull/2358) ([jim-parry](https://github.com/jim-parry)) +- Fix typo in comments of Services.php [\#2356](https://github.com/codeigniter4/CodeIgniter4/pull/2356) ([mladoux](https://github.com/mladoux)) +- Fix method name to 'toDateString\(\)' in Date and Times user guide [\#2352](https://github.com/codeigniter4/CodeIgniter4/pull/2352) ([musmanikram](https://github.com/musmanikram)) +- Inccorectly formated JSON response , if body is string [\#2276](https://github.com/codeigniter4/CodeIgniter4/pull/2276) ([nowackipawel](https://github.com/nowackipawel)) + +## [v4.0.0-rc.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.3) (2019-10-19) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.2b...v4.0.0-rc.3) + +**Fixed bugs:** + +- Route can not work: Controller or its method is not found [\#2299](https://github.com/codeigniter4/CodeIgniter4/issues/2299) +- CURLRequest - supplied argument is not a valid File-Handle resource \#Windows x64 [\#2202](https://github.com/codeigniter4/CodeIgniter4/issues/2202) +- FilterExceptions result in blank page [\#2077](https://github.com/codeigniter4/CodeIgniter4/issues/2077) + +**Closed issues:** + +- Codeigniter 4 not redirecting well. is not including the baseUrl well on redirect [\#2342](https://github.com/codeigniter4/CodeIgniter4/issues/2342) +- Session variable value set to 0 [\#2334](https://github.com/codeigniter4/CodeIgniter4/issues/2334) +- Undefined variable: errors SYSTEMPATH/Validation/Validation.php at line 651 [\#2331](https://github.com/codeigniter4/CodeIgniter4/issues/2331) +- Router Regex not working with controllers [\#2330](https://github.com/codeigniter4/CodeIgniter4/issues/2330) +- --host,--port and others are not working [\#2329](https://github.com/codeigniter4/CodeIgniter4/issues/2329) +- Type juggling can be eliminated if these three small changes are made [\#2326](https://github.com/codeigniter4/CodeIgniter4/issues/2326) +- url\_title doesn't handle diacritics [\#2323](https://github.com/codeigniter4/CodeIgniter4/issues/2323) +- View Cell Feature ? [\#2322](https://github.com/codeigniter4/CodeIgniter4/issues/2322) +- autoRoute function issue in case of sub-directory [\#2319](https://github.com/codeigniter4/CodeIgniter4/issues/2319) +- Can't store multidimensional data with Session Library [\#2309](https://github.com/codeigniter4/CodeIgniter4/issues/2309) +- Model\(\)-\>find\(\) return NULL for existing row [\#2306](https://github.com/codeigniter4/CodeIgniter4/issues/2306) +- Requesting Model::getValidationRules\(\) documentation [\#2304](https://github.com/codeigniter4/CodeIgniter4/issues/2304) +- Routes Not working [\#2301](https://github.com/codeigniter4/CodeIgniter4/issues/2301) +- ViewPath cannot be moved only another path added [\#2291](https://github.com/codeigniter4/CodeIgniter4/issues/2291) +- Version not updated? [\#2287](https://github.com/codeigniter4/CodeIgniter4/issues/2287) +- \_remap is not working [\#2277](https://github.com/codeigniter4/CodeIgniter4/issues/2277) +- Debug Toolbar error not found tpl error and fix [\#2275](https://github.com/codeigniter4/CodeIgniter4/issues/2275) +- cURL request returns 404 [\#2250](https://github.com/codeigniter4/CodeIgniter4/issues/2250) +- Problem with renaming deleted\_at column inside model [\#2248](https://github.com/codeigniter4/CodeIgniter4/issues/2248) +- App\Config\Routes loaded twice [\#2203](https://github.com/codeigniter4/CodeIgniter4/issues/2203) +- Feature idea: Model results by key [\#2167](https://github.com/codeigniter4/CodeIgniter4/issues/2167) +- Remove "separator" comment between function declarations? [\#2146](https://github.com/codeigniter4/CodeIgniter4/issues/2146) +- find\(\) is returning one character string instead of boolean [\#2096](https://github.com/codeigniter4/CodeIgniter4/issues/2096) +- Database Groups in Migrations [\#2087](https://github.com/codeigniter4/CodeIgniter4/issues/2087) +- "Cannot call session save handler in a recursive manner" [\#2056](https://github.com/codeigniter4/CodeIgniter4/issues/2056) +- Model afterInsert return originals? [\#2045](https://github.com/codeigniter4/CodeIgniter4/issues/2045) +- debug toolbar renderTimeline couses a non well formed numeric value encountered error [\#2034](https://github.com/codeigniter4/CodeIgniter4/issues/2034) +- Mysql update , affectedRows return bug [\#2003](https://github.com/codeigniter4/CodeIgniter4/issues/2003) +- Add validation on exists database before created [\#1759](https://github.com/codeigniter4/CodeIgniter4/issues/1759) + +**Merged pull requests:** + +- Prep changelog for RC.3 \[ci skip\] [\#2349](https://github.com/codeigniter4/CodeIgniter4/pull/2349) ([jim-parry](https://github.com/jim-parry)) +- CodeIgniter Foundation gets copyright \[ci skip\] [\#2348](https://github.com/codeigniter4/CodeIgniter4/pull/2348) ([jim-parry](https://github.com/jim-parry)) +- Fix FilerHandlerTest.php wierdness [\#2346](https://github.com/codeigniter4/CodeIgniter4/pull/2346) ([dafriend](https://github.com/dafriend)) +- Tests readme polish [\#2345](https://github.com/codeigniter4/CodeIgniter4/pull/2345) ([dafriend](https://github.com/dafriend)) +- Setup vs Set Up [\#2344](https://github.com/codeigniter4/CodeIgniter4/pull/2344) ([dafriend](https://github.com/dafriend)) +- User guide minor fixes. Fix class names and code area. [\#2343](https://github.com/codeigniter4/CodeIgniter4/pull/2343) ([natanfelles](https://github.com/natanfelles)) +- Simplify Validation::getErrors\(\) [\#2341](https://github.com/codeigniter4/CodeIgniter4/pull/2341) ([dafriend](https://github.com/dafriend)) +- Fix Session::get\('key'\) returns null when value is \(int\) 0 [\#2339](https://github.com/codeigniter4/CodeIgniter4/pull/2339) ([dafriend](https://github.com/dafriend)) +- Revert RedirectException change [\#2338](https://github.com/codeigniter4/CodeIgniter4/pull/2338) ([MGatner](https://github.com/MGatner)) +- \[ci skip\] Guide: Minor grammar corrections [\#2337](https://github.com/codeigniter4/CodeIgniter4/pull/2337) ([dafriend](https://github.com/dafriend)) +- Correct cleaning of namespaces in FileLocater for better Windows compatibility. See \#2203 [\#2336](https://github.com/codeigniter4/CodeIgniter4/pull/2336) ([lonnieezell](https://github.com/lonnieezell)) +- \[ci skip\] Guide: RESTful table formatting [\#2333](https://github.com/codeigniter4/CodeIgniter4/pull/2333) ([MGatner](https://github.com/MGatner)) +- Change after methods to use actual data [\#2332](https://github.com/codeigniter4/CodeIgniter4/pull/2332) ([MGatner](https://github.com/MGatner)) +- Update Application Structure [\#2328](https://github.com/codeigniter4/CodeIgniter4/pull/2328) ([kenjis](https://github.com/kenjis)) +- Correct the routing UG page [\#2327](https://github.com/codeigniter4/CodeIgniter4/pull/2327) ([jim-parry](https://github.com/jim-parry)) +- Fix bug in url\_title\(\) function with diacritics [\#2325](https://github.com/codeigniter4/CodeIgniter4/pull/2325) ([michalsn](https://github.com/michalsn)) +- Renderer Toolbar Debug Toggle [\#2324](https://github.com/codeigniter4/CodeIgniter4/pull/2324) ([MGatner](https://github.com/MGatner)) +- \[ci skip\] Update RESTful User Guide [\#2321](https://github.com/codeigniter4/CodeIgniter4/pull/2321) ([MGatner](https://github.com/MGatner)) +- Add getValidationRules\(\) to model UG page [\#2316](https://github.com/codeigniter4/CodeIgniter4/pull/2316) ([jim-parry](https://github.com/jim-parry)) +- Enhance Toolbar::renderTimeline [\#2315](https://github.com/codeigniter4/CodeIgniter4/pull/2315) ([jim-parry](https://github.com/jim-parry)) +- RESTful User Guide cleanup [\#2313](https://github.com/codeigniter4/CodeIgniter4/pull/2313) ([MGatner](https://github.com/MGatner)) +- BaseBuilder variable type fix [\#2312](https://github.com/codeigniter4/CodeIgniter4/pull/2312) ([TysiacSzescset](https://github.com/TysiacSzescset)) +- Convert all language returns to single quote [\#2311](https://github.com/codeigniter4/CodeIgniter4/pull/2311) ([MGatner](https://github.com/MGatner)) +- Bugfix extra autoroute slashes [\#2308](https://github.com/codeigniter4/CodeIgniter4/pull/2308) ([MGatner](https://github.com/MGatner)) +- Resolve session save handler issue [\#2307](https://github.com/codeigniter4/CodeIgniter4/pull/2307) ([jim-parry](https://github.com/jim-parry)) +- Fix curl debug bug [\#2305](https://github.com/codeigniter4/CodeIgniter4/pull/2305) ([michalsn](https://github.com/michalsn)) +- Use DBGroup variable from migration class if defined [\#2303](https://github.com/codeigniter4/CodeIgniter4/pull/2303) ([michalsn](https://github.com/michalsn)) +- Fix MySql \_fromTables\(\) [\#2302](https://github.com/codeigniter4/CodeIgniter4/pull/2302) ([pjsde](https://github.com/pjsde)) +- \[ci skip\] Routes collector for toolbar should not die when a method name is calculated through \_remap [\#2300](https://github.com/codeigniter4/CodeIgniter4/pull/2300) ([lonnieezell](https://github.com/lonnieezell)) +- fix issue on session\_regenerate. [\#2298](https://github.com/codeigniter4/CodeIgniter4/pull/2298) ([pjsde](https://github.com/pjsde)) +- Add counted\(\) to Inflector Helper [\#2296](https://github.com/codeigniter4/CodeIgniter4/pull/2296) ([MGatner](https://github.com/MGatner)) +- Test set\(\) method in Builder class more [\#2295](https://github.com/codeigniter4/CodeIgniter4/pull/2295) ([michalsn](https://github.com/michalsn)) +- Fix Code Modules documentation for psr4 namespace configuration [\#2290](https://github.com/codeigniter4/CodeIgniter4/pull/2290) ([romaven](https://github.com/romaven)) +- Don't restrict model's access to properties in a read-only manner [\#2289](https://github.com/codeigniter4/CodeIgniter4/pull/2289) ([lonnieezell](https://github.com/lonnieezell)) +- Fix line numbering in Debug/Exceptions class [\#2288](https://github.com/codeigniter4/CodeIgniter4/pull/2288) ([michalsn](https://github.com/michalsn)) +- Fix error with Host header for CURLRequest class [\#2285](https://github.com/codeigniter4/CodeIgniter4/pull/2285) ([michalsn](https://github.com/michalsn)) +- Fix getErrors\(\) for validation with redirect [\#2284](https://github.com/codeigniter4/CodeIgniter4/pull/2284) ([michalsn](https://github.com/michalsn)) +- Rename collectors \_\*.tpl.php to \_\*.tpl [\#2283](https://github.com/codeigniter4/CodeIgniter4/pull/2283) ([MGatner](https://github.com/MGatner)) +- Bug in CSRF parameter cleanup [\#2279](https://github.com/codeigniter4/CodeIgniter4/pull/2279) ([michalsn](https://github.com/michalsn)) +- WIP fix store\(\) default value bug [\#2123](https://github.com/codeigniter4/CodeIgniter4/pull/2123) ([s-proj](https://github.com/s-proj)) +- WIP Added validation on exists database before created for MySQLi… [\#2100](https://github.com/codeigniter4/CodeIgniter4/pull/2100) ([oleg1540](https://github.com/oleg1540)) + +## [v4.0.0-rc.2b](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.2b) (2019-09-28) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.2.1...v4.0.0-rc.2b) + +**Merged pull requests:** + +- Fix user guide for Message class [\#2282](https://github.com/codeigniter4/CodeIgniter4/pull/2282) ([michalsn](https://github.com/michalsn)) +- Handle X-CSRF-TOKEN - CSRF [\#2272](https://github.com/codeigniter4/CodeIgniter4/pull/2272) ([nowackipawel](https://github.com/nowackipawel)) +- QUICKFIX Batch Update Where Reset [\#2252](https://github.com/codeigniter4/CodeIgniter4/pull/2252) ([searchy2](https://github.com/searchy2)) + +## [v4.0.0-rc.2.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.2.1) (2019-09-28) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.2...v4.0.0-rc.2.1) + +**Closed issues:** + +- listTables\(\) failing to use correct prefix [\#2210](https://github.com/codeigniter4/CodeIgniter4/issues/2210) +- Query Builder Class documentation [\#2140](https://github.com/codeigniter4/CodeIgniter4/issues/2140) + +## [v4.0.0-rc.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.2) (2019-09-27) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-rc.1...v4.0.0-rc.2) + +**Fixed bugs:** + +- Add magic \_\_isset to classes with \_\_get [\#2219](https://github.com/codeigniter4/CodeIgniter4/issues/2219) +- CIDatabaseTestCase double-deletes tables [\#2206](https://github.com/codeigniter4/CodeIgniter4/issues/2206) +- Locals problems [\#2195](https://github.com/codeigniter4/CodeIgniter4/issues/2195) +- Translations are not loaded from composer repository [\#2120](https://github.com/codeigniter4/CodeIgniter4/issues/2120) + +**Closed issues:** + +- Query grouping not working for HAVING clause [\#2247](https://github.com/codeigniter4/CodeIgniter4/issues/2247) +- $builder-\>like\(\) not support for HAVING clause [\#2242](https://github.com/codeigniter4/CodeIgniter4/issues/2242) +- invalid switch parameter [\#2239](https://github.com/codeigniter4/CodeIgniter4/issues/2239) +- DateTime::createFromFormat\(\) parameter incorrect [\#2238](https://github.com/codeigniter4/CodeIgniter4/issues/2238) +- strlen\(\) expects parameter string, integer given [\#2237](https://github.com/codeigniter4/CodeIgniter4/issues/2237) +- ini\_set\(\) expects parameter 2 to be string, integer given [\#2236](https://github.com/codeigniter4/CodeIgniter4/issues/2236) +- ini\_set\(\) expects parameter 2 to be string, integer given [\#2235](https://github.com/codeigniter4/CodeIgniter4/issues/2235) +- \# ini\_set\(\) expects parameter 2 to be string, integer given [\#2234](https://github.com/codeigniter4/CodeIgniter4/issues/2234) +- Extending The Model [\#2223](https://github.com/codeigniter4/CodeIgniter4/issues/2223) +- BUG curl\_setopt\_array\(\): supplied argument is not a valid file-handle resource [\#2222](https://github.com/codeigniter4/CodeIgniter4/issues/2222) +- How do I dynamically modify the configuration? [\#2214](https://github.com/codeigniter4/CodeIgniter4/issues/2214) +- Document the "whoops" error page [\#2198](https://github.com/codeigniter4/CodeIgniter4/issues/2198) +- Fail to open system/bootstrap.php [\#2193](https://github.com/codeigniter4/CodeIgniter4/issues/2193) +- Function lang\(\) / Type of return value [\#2192](https://github.com/codeigniter4/CodeIgniter4/issues/2192) +- Can we use codeigniter 4 on live server?? [\#2188](https://github.com/codeigniter4/CodeIgniter4/issues/2188) +- Custom query in model CI4 [\#2187](https://github.com/codeigniter4/CodeIgniter4/issues/2187) +- conflict between php zlib.output\_compression and output buffering [\#2182](https://github.com/codeigniter4/CodeIgniter4/issues/2182) +- API Trait documentation fix - failValidationError [\#2176](https://github.com/codeigniter4/CodeIgniter4/issues/2176) +- Validation issue on multiple file upload [\#2175](https://github.com/codeigniter4/CodeIgniter4/issues/2175) +- exif\_read\_data [\#2161](https://github.com/codeigniter4/CodeIgniter4/issues/2161) +- Database count methods [\#2159](https://github.com/codeigniter4/CodeIgniter4/issues/2159) +- Devstarter $salt [\#2156](https://github.com/codeigniter4/CodeIgniter4/issues/2156) +- Migration migrate, rollback and create problems [\#2147](https://github.com/codeigniter4/CodeIgniter4/issues/2147) +- Query Builder getWhere Crash [\#2143](https://github.com/codeigniter4/CodeIgniter4/issues/2143) +- View: $parser-\>render\(\); [\#2086](https://github.com/codeigniter4/CodeIgniter4/issues/2086) +- Return value of lang\(\) must be of the type string, array returned [\#2075](https://github.com/codeigniter4/CodeIgniter4/issues/2075) +- Wrong links for pager [\#2016](https://github.com/codeigniter4/CodeIgniter4/issues/2016) +- base\_url\(\) value dropped between namespaces [\#1942](https://github.com/codeigniter4/CodeIgniter4/issues/1942) +- Unable to use \_remap without default method in controller [\#1928](https://github.com/codeigniter4/CodeIgniter4/issues/1928) +- RESTful resources [\#1765](https://github.com/codeigniter4/CodeIgniter4/issues/1765) + +**Merged pull requests:** + +- Fix changelog \[ci skip\] [\#2273](https://github.com/codeigniter4/CodeIgniter4/pull/2273) ([jim-parry](https://github.com/jim-parry)) +- fix ResourcePresenter::setModel\(\) [\#2271](https://github.com/codeigniter4/CodeIgniter4/pull/2271) ([pjsde](https://github.com/pjsde)) +- groupStart\(\) refactorization [\#2270](https://github.com/codeigniter4/CodeIgniter4/pull/2270) ([michalsn](https://github.com/michalsn)) +- testMode\(\) method for BaseBuilder [\#2269](https://github.com/codeigniter4/CodeIgniter4/pull/2269) ([michalsn](https://github.com/michalsn)) +- Validation session use only if exists [\#2268](https://github.com/codeigniter4/CodeIgniter4/pull/2268) ([jim-parry](https://github.com/jim-parry)) +- Tests setUp and tearDown: void [\#2267](https://github.com/codeigniter4/CodeIgniter4/pull/2267) ([MGatner](https://github.com/MGatner)) +- RC.2 release prep [\#2266](https://github.com/codeigniter4/CodeIgniter4/pull/2266) ([jim-parry](https://github.com/jim-parry)) +- Fix a validation issue on multiple file upload [\#2265](https://github.com/codeigniter4/CodeIgniter4/pull/2265) ([pjsde](https://github.com/pjsde)) +- fix. Parser allow other extension [\#2264](https://github.com/codeigniter4/CodeIgniter4/pull/2264) ([Instrye](https://github.com/Instrye)) +- Fix parameter type in Debug/Exceptions [\#2262](https://github.com/codeigniter4/CodeIgniter4/pull/2262) ([jim-parry](https://github.com/jim-parry)) +- Fix lang\(\) signature [\#2261](https://github.com/codeigniter4/CodeIgniter4/pull/2261) ([jim-parry](https://github.com/jim-parry)) +- Explain the whoops page [\#2260](https://github.com/codeigniter4/CodeIgniter4/pull/2260) ([jim-parry](https://github.com/jim-parry)) +- Add URI & url\_helper tests [\#2259](https://github.com/codeigniter4/CodeIgniter4/pull/2259) ([jim-parry](https://github.com/jim-parry)) +- Several updates to the HAVING clauses [\#2257](https://github.com/codeigniter4/CodeIgniter4/pull/2257) ([michalsn](https://github.com/michalsn)) +- Fix invalid parameters [\#2253](https://github.com/codeigniter4/CodeIgniter4/pull/2253) ([pjsde](https://github.com/pjsde)) +- EXIF not supported for GIF [\#2246](https://github.com/codeigniter4/CodeIgniter4/pull/2246) ([jim-parry](https://github.com/jim-parry)) +- Fix class ref parameter types [\#2245](https://github.com/codeigniter4/CodeIgniter4/pull/2245) ([jim-parry](https://github.com/jim-parry)) +- Fix ini\_set parameter type [\#2241](https://github.com/codeigniter4/CodeIgniter4/pull/2241) ([jim-parry](https://github.com/jim-parry)) +- Handle JSON POSTs in CSRF [\#2240](https://github.com/codeigniter4/CodeIgniter4/pull/2240) ([nowackipawel](https://github.com/nowackipawel)) +- Fixes BaseBuilder getWhere\(\) bug [\#2232](https://github.com/codeigniter4/CodeIgniter4/pull/2232) ([michalsn](https://github.com/michalsn)) +- Add magic \_\_isset to classes with \_\_get [\#2231](https://github.com/codeigniter4/CodeIgniter4/pull/2231) ([MGatner](https://github.com/MGatner)) +- Add escape to SQLite \_listTables\(\) [\#2230](https://github.com/codeigniter4/CodeIgniter4/pull/2230) ([MGatner](https://github.com/MGatner)) +- MySQLi escapeLikeStringDirect\(\) [\#2229](https://github.com/codeigniter4/CodeIgniter4/pull/2229) ([MGatner](https://github.com/MGatner)) +- Exclude `sqlite\_%` from listTables\(\) [\#2228](https://github.com/codeigniter4/CodeIgniter4/pull/2228) ([MGatner](https://github.com/MGatner)) +- fix. CONTRIBUTING.md link [\#2226](https://github.com/codeigniter4/CodeIgniter4/pull/2226) ([Instrye](https://github.com/Instrye)) +- \[ci skip\] Fix malformed table in view\_parser.rst [\#2225](https://github.com/codeigniter4/CodeIgniter4/pull/2225) ([jim-parry](https://github.com/jim-parry)) +- change new \Config\Database\(\) to config\('Database'\) [\#2224](https://github.com/codeigniter4/CodeIgniter4/pull/2224) ([techoner](https://github.com/techoner)) +- Documentation fixes [\#2221](https://github.com/codeigniter4/CodeIgniter4/pull/2221) ([najdanovicivan](https://github.com/najdanovicivan)) +- Typo corrected [\#2218](https://github.com/codeigniter4/CodeIgniter4/pull/2218) ([dangereyes88](https://github.com/dangereyes88)) +- Update uri.rst [\#2216](https://github.com/codeigniter4/CodeIgniter4/pull/2216) ([dangereyes88](https://github.com/dangereyes88)) +- Filter listTables cache response on constrainPrefix [\#2213](https://github.com/codeigniter4/CodeIgniter4/pull/2213) ([MGatner](https://github.com/MGatner)) +- Add listTable\(\) tests [\#2211](https://github.com/codeigniter4/CodeIgniter4/pull/2211) ([MGatner](https://github.com/MGatner)) +- Add trace\(\) [\#2209](https://github.com/codeigniter4/CodeIgniter4/pull/2209) ([MGatner](https://github.com/MGatner)) +- Add $db-\>getPrefix\(\) [\#2208](https://github.com/codeigniter4/CodeIgniter4/pull/2208) ([MGatner](https://github.com/MGatner)) +- Fix empty\(\) bug on DBPrefix [\#2205](https://github.com/codeigniter4/CodeIgniter4/pull/2205) ([MGatner](https://github.com/MGatner)) +- Foreign key columns [\#2201](https://github.com/codeigniter4/CodeIgniter4/pull/2201) ([MGatner](https://github.com/MGatner)) +- Notify Kint of dd alias [\#2200](https://github.com/codeigniter4/CodeIgniter4/pull/2200) ([MGatner](https://github.com/MGatner)) +- Add getForeignKeyData to User Guide [\#2199](https://github.com/codeigniter4/CodeIgniter4/pull/2199) ([MGatner](https://github.com/MGatner)) +- Update Session.php [\#2197](https://github.com/codeigniter4/CodeIgniter4/pull/2197) ([cstechsandesh](https://github.com/cstechsandesh)) +- Migration rollback reverse [\#2191](https://github.com/codeigniter4/CodeIgniter4/pull/2191) ([MGatner](https://github.com/MGatner)) +- \[ci skip\] Fix name of ForeignKeyChecks [\#2190](https://github.com/codeigniter4/CodeIgniter4/pull/2190) ([MGatner](https://github.com/MGatner)) +- missing return [\#2189](https://github.com/codeigniter4/CodeIgniter4/pull/2189) ([titounnes](https://github.com/titounnes)) +- Fix case on "Seeds/" directory [\#2184](https://github.com/codeigniter4/CodeIgniter4/pull/2184) ([MGatner](https://github.com/MGatner)) +- Check `defined` for constants [\#2183](https://github.com/codeigniter4/CodeIgniter4/pull/2183) ([MGatner](https://github.com/MGatner)) +- Remove copy-paste extraneous text [\#2181](https://github.com/codeigniter4/CodeIgniter4/pull/2181) ([MGatner](https://github.com/MGatner)) +- Fix \_fromTables\(\) [\#2174](https://github.com/codeigniter4/CodeIgniter4/pull/2174) ([pjsde](https://github.com/pjsde)) +- Fix for CURL for 'debug' option [\#2168](https://github.com/codeigniter4/CodeIgniter4/pull/2168) ([MGatner](https://github.com/MGatner)) + +## [v4.0.0-rc.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-rc.1) (2019-09-03) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-beta.4...v4.0.0-rc.1) + +**Implemented enhancements:** + +- BaseConfig should support array values with dot syntax [\#454](https://github.com/codeigniter4/CodeIgniter4/issues/454) + +**Closed issues:** + +- \[internal function\]: CodeIgniter\Debug\Exceptions-\>shutdownHandler\(\) [\#2173](https://github.com/codeigniter4/CodeIgniter4/issues/2173) +- Message-\>setHeader allowing duplicates [\#2170](https://github.com/codeigniter4/CodeIgniter4/issues/2170) +- CLI: Exit status [\#2163](https://github.com/codeigniter4/CodeIgniter4/issues/2163) +- QB countAllResults shouldn't use LIMIT settings [\#2152](https://github.com/codeigniter4/CodeIgniter4/issues/2152) +- BaseBuilder::get\(\) resets query even if reset = false [\#2141](https://github.com/codeigniter4/CodeIgniter4/issues/2141) +- Some migrations not running [\#2139](https://github.com/codeigniter4/CodeIgniter4/issues/2139) +- Migrations Refactor Namespaces [\#2138](https://github.com/codeigniter4/CodeIgniter4/issues/2138) +- $primaryKey forcefully 'needs' to be auto\_increment [\#2133](https://github.com/codeigniter4/CodeIgniter4/issues/2133) +- response data not set [\#2124](https://github.com/codeigniter4/CodeIgniter4/issues/2124) +- RESTful behaviour [\#2122](https://github.com/codeigniter4/CodeIgniter4/issues/2122) +- Redis [\#2121](https://github.com/codeigniter4/CodeIgniter4/issues/2121) +- Toolbar download bug [\#2117](https://github.com/codeigniter4/CodeIgniter4/issues/2117) +- Packagist not updated with latest release? [\#2115](https://github.com/codeigniter4/CodeIgniter4/issues/2115) +- Fatal error Installing using composer [\#2114](https://github.com/codeigniter4/CodeIgniter4/issues/2114) +- Allow loading Common.php function overrides [\#2101](https://github.com/codeigniter4/CodeIgniter4/issues/2101) +- Result from database was auto encoded when using Entity [\#2088](https://github.com/codeigniter4/CodeIgniter4/issues/2088) +- Honeypot does not close the form [\#2084](https://github.com/codeigniter4/CodeIgniter4/issues/2084) +- Imagick Image library handler return array instead of boolean [\#2029](https://github.com/codeigniter4/CodeIgniter4/issues/2029) +- Migrations command should use the UTC datetime when creating new migrations [\#2018](https://github.com/codeigniter4/CodeIgniter4/issues/2018) +- FileLocator-\>getNamespaces with parameter [\#1866](https://github.com/codeigniter4/CodeIgniter4/issues/1866) + +**Merged pull requests:** + +- Fix query builder user guide page [\#2180](https://github.com/codeigniter4/CodeIgniter4/pull/2180) ([jim-parry](https://github.com/jim-parry)) +- RC.1 prep [\#2179](https://github.com/codeigniter4/CodeIgniter4/pull/2179) ([jim-parry](https://github.com/jim-parry)) +- Add fallback for missing finfo\_open [\#2178](https://github.com/codeigniter4/CodeIgniter4/pull/2178) ([MGatner](https://github.com/MGatner)) +- Fix missing form close tag [\#2177](https://github.com/codeigniter4/CodeIgniter4/pull/2177) ([jim-parry](https://github.com/jim-parry)) +- Base FeatureTestCase on CIUnitTestCase [\#2172](https://github.com/codeigniter4/CodeIgniter4/pull/2172) ([jim-parry](https://github.com/jim-parry)) +- Setheader dupes [\#2171](https://github.com/codeigniter4/CodeIgniter4/pull/2171) ([MGatner](https://github.com/MGatner)) +- Add $quality usage for Image Library [\#2169](https://github.com/codeigniter4/CodeIgniter4/pull/2169) ([MGatner](https://github.com/MGatner)) +- Cookie error [\#2166](https://github.com/codeigniter4/CodeIgniter4/pull/2166) ([pjsde](https://github.com/pjsde)) +- RESTful help [\#2165](https://github.com/codeigniter4/CodeIgniter4/pull/2165) ([jim-parry](https://github.com/jim-parry)) +- Exit error code on CLI Command failure [\#2164](https://github.com/codeigniter4/CodeIgniter4/pull/2164) ([MGatner](https://github.com/MGatner)) +- User Guide updates for Common.php [\#2162](https://github.com/codeigniter4/CodeIgniter4/pull/2162) ([MGatner](https://github.com/MGatner)) +- Add BaseBuilder SelectCount [\#2160](https://github.com/codeigniter4/CodeIgniter4/pull/2160) ([MGatner](https://github.com/MGatner)) +- Update migrations config [\#2157](https://github.com/codeigniter4/CodeIgniter4/pull/2157) ([jim-parry](https://github.com/jim-parry)) +- Include .gitignore in starters [\#2155](https://github.com/codeigniter4/CodeIgniter4/pull/2155) ([MGatner](https://github.com/MGatner)) +- Fix email & migrations docs; update changelog [\#2154](https://github.com/codeigniter4/CodeIgniter4/pull/2154) ([jim-parry](https://github.com/jim-parry)) +- Bug fix countAllResults with LIMIT [\#2153](https://github.com/codeigniter4/CodeIgniter4/pull/2153) ([tangix](https://github.com/tangix)) +- ImageMagick-\>save\(\) return value [\#2151](https://github.com/codeigniter4/CodeIgniter4/pull/2151) ([MGatner](https://github.com/MGatner)) +- New logic for Image-\>fit\(\) [\#2150](https://github.com/codeigniter4/CodeIgniter4/pull/2150) ([MGatner](https://github.com/MGatner)) +- listNamespaceFiles: Ensure trailing slash [\#2149](https://github.com/codeigniter4/CodeIgniter4/pull/2149) ([MGatner](https://github.com/MGatner)) +- Remove UserModel reference from Home controller [\#2145](https://github.com/codeigniter4/CodeIgniter4/pull/2145) ([andreportaro](https://github.com/andreportaro)) +- Update Redis legacy function [\#2144](https://github.com/codeigniter4/CodeIgniter4/pull/2144) ([MGatner](https://github.com/MGatner)) +- Fixing BuilderBase resetting when getting the SQL [\#2142](https://github.com/codeigniter4/CodeIgniter4/pull/2142) ([tangix](https://github.com/tangix)) +- New Migration Logic [\#2137](https://github.com/codeigniter4/CodeIgniter4/pull/2137) ([MGatner](https://github.com/MGatner)) +- Migrations user guide fixes [\#2136](https://github.com/codeigniter4/CodeIgniter4/pull/2136) ([MGatner](https://github.com/MGatner)) +- Encryption [\#2135](https://github.com/codeigniter4/CodeIgniter4/pull/2135) ([jim-parry](https://github.com/jim-parry)) +- Fix localization writeup [\#2134](https://github.com/codeigniter4/CodeIgniter4/pull/2134) ([jim-parry](https://github.com/jim-parry)) +- Update migration User Guide [\#2132](https://github.com/codeigniter4/CodeIgniter4/pull/2132) ([MGatner](https://github.com/MGatner)) +- Added No Content response to API\ResponseTrait [\#2131](https://github.com/codeigniter4/CodeIgniter4/pull/2131) ([tangix](https://github.com/tangix)) +- Add setFileName\(\) to DownloadResponse [\#2129](https://github.com/codeigniter4/CodeIgniter4/pull/2129) ([MGatner](https://github.com/MGatner)) +- guessExtension fallback to clientExtension [\#2128](https://github.com/codeigniter4/CodeIgniter4/pull/2128) ([MGatner](https://github.com/MGatner)) +- Update limit function since $offset is nullable [\#2127](https://github.com/codeigniter4/CodeIgniter4/pull/2127) ([vibbow](https://github.com/vibbow)) +- Limit storePreviousURL to certain requests [\#2126](https://github.com/codeigniter4/CodeIgniter4/pull/2126) ([MGatner](https://github.com/MGatner)) +- Updated redis session handler to support redis 5.0.x [\#2125](https://github.com/codeigniter4/CodeIgniter4/pull/2125) ([tangix](https://github.com/tangix)) +- Disabled Toolbar on downloads [\#2118](https://github.com/codeigniter4/CodeIgniter4/pull/2118) ([MGatner](https://github.com/MGatner)) +- Add Image-\>convert\(\) [\#2113](https://github.com/codeigniter4/CodeIgniter4/pull/2113) ([MGatner](https://github.com/MGatner)) +- Update `Entity.php` `\_\_isset` method [\#2112](https://github.com/codeigniter4/CodeIgniter4/pull/2112) ([vibbow](https://github.com/vibbow)) +- Added app/Common.php [\#2110](https://github.com/codeigniter4/CodeIgniter4/pull/2110) ([jason-napolitano](https://github.com/jason-napolitano)) +- Fix typo in checking if exists db\_connect\(\) [\#2109](https://github.com/codeigniter4/CodeIgniter4/pull/2109) ([xbotkaj](https://github.com/xbotkaj)) +- Original email port [\#2092](https://github.com/codeigniter4/CodeIgniter4/pull/2092) ([jim-parry](https://github.com/jim-parry)) +- Fix prevent soft delete all without conditions set [\#2090](https://github.com/codeigniter4/CodeIgniter4/pull/2090) ([rino7](https://github.com/rino7)) +- Update BaseConfig.php [\#2082](https://github.com/codeigniter4/CodeIgniter4/pull/2082) ([zl59503020](https://github.com/zl59503020)) +- WIP: Migration updates for more wholistic functionality [\#2065](https://github.com/codeigniter4/CodeIgniter4/pull/2065) ([lonnieezell](https://github.com/lonnieezell)) +- clean base controller code [\#2046](https://github.com/codeigniter4/CodeIgniter4/pull/2046) ([behnampro](https://github.com/behnampro)) +- Fix CSRF hash regeneration [\#2027](https://github.com/codeigniter4/CodeIgniter4/pull/2027) ([Workoverflow](https://github.com/Workoverflow)) +- WIP Verbiage revisions [\#2010](https://github.com/codeigniter4/CodeIgniter4/pull/2010) ([kydojo](https://github.com/kydojo)) +- Subqueries in BaseBuilder [\#2001](https://github.com/codeigniter4/CodeIgniter4/pull/2001) ([iRedds](https://github.com/iRedds)) + +## [v4.0.0-beta.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-beta.4) (2019-07-25) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-beta.3...v4.0.0-beta.4) + +**Fixed bugs:** + +- Controller filters match too loosely. [\#2038](https://github.com/codeigniter4/CodeIgniter4/issues/2038) + +**Closed issues:** + +- File-\>getDestination fails without extension [\#2103](https://github.com/codeigniter4/CodeIgniter4/issues/2103) +- User Guide: UploadedFile Class [\#2102](https://github.com/codeigniter4/CodeIgniter4/issues/2102) +- Worries about postgresql errors [\#2097](https://github.com/codeigniter4/CodeIgniter4/issues/2097) +- README.md - Link to Announcement on Forums is a 404? [\#2094](https://github.com/codeigniter4/CodeIgniter4/issues/2094) +- Entity castAsJson returns an empty array [\#2080](https://github.com/codeigniter4/CodeIgniter4/issues/2080) +- Migrations Sequential field information is required [\#2076](https://github.com/codeigniter4/CodeIgniter4/issues/2076) +- function gussExtension return wrong result, return csv instead of right answer "txt" or "text" [\#2066](https://github.com/codeigniter4/CodeIgniter4/issues/2066) +- Unexpected empty "query" property when returning CodeIgniter\HTTP\URI [\#2062](https://github.com/codeigniter4/CodeIgniter4/issues/2062) +- Multiple rules for file upload always return false [\#2061](https://github.com/codeigniter4/CodeIgniter4/issues/2061) +- The assets of the public\_folder are not loaded [\#2047](https://github.com/codeigniter4/CodeIgniter4/issues/2047) +- Modify Model's deleted field to be a date [\#2041](https://github.com/codeigniter4/CodeIgniter4/issues/2041) +- Filter Config not quite working with Routes? [\#2037](https://github.com/codeigniter4/CodeIgniter4/issues/2037) +- force\_https\(\) doesn't redirect [\#2033](https://github.com/codeigniter4/CodeIgniter4/issues/2033) +- URI segments passed in as method parameters skips segments with value as 0 \(zero\) [\#2032](https://github.com/codeigniter4/CodeIgniter4/issues/2032) +- /System/Debug/Toolbar/Collectors/Routes.php on line 83 [\#2028](https://github.com/codeigniter4/CodeIgniter4/issues/2028) +- php spark not working [\#2025](https://github.com/codeigniter4/CodeIgniter4/issues/2025) +- PR\#2012 caused 404 exception in spark [\#2021](https://github.com/codeigniter4/CodeIgniter4/issues/2021) +- Cache config [\#2017](https://github.com/codeigniter4/CodeIgniter4/issues/2017) +- CodeIgniter\Entity Setter doesn't work [\#2013](https://github.com/codeigniter4/CodeIgniter4/issues/2013) +- validation match\[x\] don't work anymore... if custom setter is used. [\#2006](https://github.com/codeigniter4/CodeIgniter4/issues/2006) +- Paths issue when moving Views outside of app folder [\#1998](https://github.com/codeigniter4/CodeIgniter4/issues/1998) +- View Parser Register Plugins as closures not works! [\#1997](https://github.com/codeigniter4/CodeIgniter4/issues/1997) +- View Parser site\_url not works? [\#1995](https://github.com/codeigniter4/CodeIgniter4/issues/1995) +- CURLRequest not respecting debug flag [\#1994](https://github.com/codeigniter4/CodeIgniter4/issues/1994) +- Entity null values cause database error [\#1992](https://github.com/codeigniter4/CodeIgniter4/issues/1992) +- SQLite driver throws exception when using dropForeignKey [\#1982](https://github.com/codeigniter4/CodeIgniter4/issues/1982) +- Security: DotEnv loads DB password plaintext in $\_SERVER [\#1969](https://github.com/codeigniter4/CodeIgniter4/issues/1969) +- Feature: FK Constraint Enable/Disable [\#1964](https://github.com/codeigniter4/CodeIgniter4/issues/1964) +- redirect\($namedRoute\) missing helpful exception [\#1953](https://github.com/codeigniter4/CodeIgniter4/issues/1953) + +**Merged pull requests:** + +- Update the starters [\#2108](https://github.com/codeigniter4/CodeIgniter4/pull/2108) ([jim-parry](https://github.com/jim-parry)) +- Prep for beta.4 [\#2107](https://github.com/codeigniter4/CodeIgniter4/pull/2107) ([jim-parry](https://github.com/jim-parry)) +- File & UploadFile Fixes [\#2104](https://github.com/codeigniter4/CodeIgniter4/pull/2104) ([MGatner](https://github.com/MGatner)) +- Timezone select [\#2091](https://github.com/codeigniter4/CodeIgniter4/pull/2091) ([MGatner](https://github.com/MGatner)) +- JSON format checking improved [\#2081](https://github.com/codeigniter4/CodeIgniter4/pull/2081) ([nowackipawel](https://github.com/nowackipawel)) +- Update config\(\) to check all namespaces [\#2079](https://github.com/codeigniter4/CodeIgniter4/pull/2079) ([MGatner](https://github.com/MGatner)) +- Throttler can access bucket for bucket life time [\#2074](https://github.com/codeigniter4/CodeIgniter4/pull/2074) ([MohKari](https://github.com/MohKari)) +- Fix autoloader.rst formatting [\#2071](https://github.com/codeigniter4/CodeIgniter4/pull/2071) ([jim-parry](https://github.com/jim-parry)) +- validation rule: then -\> than \(spelling\) [\#2069](https://github.com/codeigniter4/CodeIgniter4/pull/2069) ([nowackipawel](https://github.com/nowackipawel)) +- Bugfix file locator slash error [\#2064](https://github.com/codeigniter4/CodeIgniter4/pull/2064) ([MGatner](https://github.com/MGatner)) +- Ensure query vars are part of request-\>uri. Fixes \#2062 [\#2063](https://github.com/codeigniter4/CodeIgniter4/pull/2063) ([lonnieezell](https://github.com/lonnieezell)) +- Cache Drive Backups [\#2060](https://github.com/codeigniter4/CodeIgniter4/pull/2060) ([MohKari](https://github.com/MohKari)) +- Add multi-path support to `locateFile\(\)` [\#2059](https://github.com/codeigniter4/CodeIgniter4/pull/2059) ([MGatner](https://github.com/MGatner)) +- Add model exceptions for missing/invalid dateFormat [\#2054](https://github.com/codeigniter4/CodeIgniter4/pull/2054) ([MGatner](https://github.com/MGatner)) +- Change Model's deleted flag to a deleted\_at datetime/timestamp. Fixes \#2041 [\#2053](https://github.com/codeigniter4/CodeIgniter4/pull/2053) ([lonnieezell](https://github.com/lonnieezell)) +- Add various tests for \(not\) null [\#2052](https://github.com/codeigniter4/CodeIgniter4/pull/2052) ([MGatner](https://github.com/MGatner)) +- Soft deletes use deleted\_at [\#2051](https://github.com/codeigniter4/CodeIgniter4/pull/2051) ([MGatner](https://github.com/MGatner)) +- Stash insert ID before event trigger [\#2050](https://github.com/codeigniter4/CodeIgniter4/pull/2050) ([MGatner](https://github.com/MGatner)) +- Zero params should be passed through when routing. Fixes \#2032 [\#2043](https://github.com/codeigniter4/CodeIgniter4/pull/2043) ([lonnieezell](https://github.com/lonnieezell)) +- SQLite3 now supports dropping foreign keys. Fixes \#1982 [\#2042](https://github.com/codeigniter4/CodeIgniter4/pull/2042) ([lonnieezell](https://github.com/lonnieezell)) +- Update CURLRequest.php [\#2040](https://github.com/codeigniter4/CodeIgniter4/pull/2040) ([nowackipawel](https://github.com/nowackipawel)) +- Restrict filter matching of uris so they require an exact match. Fixes \#2038 [\#2039](https://github.com/codeigniter4/CodeIgniter4/pull/2039) ([lonnieezell](https://github.com/lonnieezell)) +- Make `force\_https\(\)` send headers before exit [\#2036](https://github.com/codeigniter4/CodeIgniter4/pull/2036) ([MGatner](https://github.com/MGatner)) +- Various typos and Guide corrections [\#2035](https://github.com/codeigniter4/CodeIgniter4/pull/2035) ([MGatner](https://github.com/MGatner)) +- Fallback to server request for default method [\#2031](https://github.com/codeigniter4/CodeIgniter4/pull/2031) ([MGatner](https://github.com/MGatner)) +- Support the new `router` service in Debug Toolbar [\#2030](https://github.com/codeigniter4/CodeIgniter4/pull/2030) ([MGatner](https://github.com/MGatner)) +- Extension Pager::makeLinks \(optional grup name\) [\#2026](https://github.com/codeigniter4/CodeIgniter4/pull/2026) ([nowackipawel](https://github.com/nowackipawel)) +- Refactor the way the router and route collection determine the current HTTP verb. [\#2024](https://github.com/codeigniter4/CodeIgniter4/pull/2024) ([lonnieezell](https://github.com/lonnieezell)) +- SQLite and Mysql driver additional tests and migration runner test fixes [\#2019](https://github.com/codeigniter4/CodeIgniter4/pull/2019) ([lonnieezell](https://github.com/lonnieezell)) +- Direct user to follow the upgrade steps after installation [\#2015](https://github.com/codeigniter4/CodeIgniter4/pull/2015) ([agmckee](https://github.com/agmckee)) +- Added a new Session/ArrayHandler that can be used during testing. [\#2014](https://github.com/codeigniter4/CodeIgniter4/pull/2014) ([lonnieezell](https://github.com/lonnieezell)) +- Use request-\>method for HTTP verb [\#2012](https://github.com/codeigniter4/CodeIgniter4/pull/2012) ([MGatner](https://github.com/MGatner)) +- Set the raw data array without any mutations for the Entity [\#2011](https://github.com/codeigniter4/CodeIgniter4/pull/2011) ([iRedds](https://github.com/iRedds)) +- Add `patch` method to command "routes" [\#2008](https://github.com/codeigniter4/CodeIgniter4/pull/2008) ([MGatner](https://github.com/MGatner)) +- Plugin closures docs update and test [\#2005](https://github.com/codeigniter4/CodeIgniter4/pull/2005) ([lonnieezell](https://github.com/lonnieezell)) +- Allow hasChanged\(\) without parameter [\#2004](https://github.com/codeigniter4/CodeIgniter4/pull/2004) ([MGatner](https://github.com/MGatner)) +- Entity refactor [\#2002](https://github.com/codeigniter4/CodeIgniter4/pull/2002) ([lonnieezell](https://github.com/lonnieezell)) +- use CodeIgniter\Controller; not needed since Home Controller extends … [\#1999](https://github.com/codeigniter4/CodeIgniter4/pull/1999) ([titounnes](https://github.com/titounnes)) +- Attempting to fix CURLRequest debug issue. \#1994 [\#1996](https://github.com/codeigniter4/CodeIgniter4/pull/1996) ([lonnieezell](https://github.com/lonnieezell)) +- argument set\(\) must by type of string - cannot agree [\#1989](https://github.com/codeigniter4/CodeIgniter4/pull/1989) ([nowackipawel](https://github.com/nowackipawel)) +- Prevent reverseRoute from searching closures [\#1959](https://github.com/codeigniter4/CodeIgniter4/pull/1959) ([MGatner](https://github.com/MGatner)) + +## [v4.0.0-beta.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-beta.3) (2019-05-06) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-beta.1...v4.0.0-beta.3) + +**Fixed bugs:** + +- travis-ci build broken [\#1830](https://github.com/codeigniter4/CodeIgniter4/issues/1830) +- \[Re-opened\] 404 File not found when running CodeIgniter on local Apache web server and virtual hosts [\#1400](https://github.com/codeigniter4/CodeIgniter4/issues/1400) +- MySQLi SSL verify [\#1219](https://github.com/codeigniter4/CodeIgniter4/issues/1219) + +**Closed issues:** + +- SQLite driver doesn't drop indexes when dropping a table [\#1983](https://github.com/codeigniter4/CodeIgniter4/issues/1983) +- About mysqli options MYSQLI\_OPT\_INT\_AND\_FLOAT\_NATIVE [\#1979](https://github.com/codeigniter4/CodeIgniter4/issues/1979) +- Toolbar won't accept custom collectors [\#1971](https://github.com/codeigniter4/CodeIgniter4/issues/1971) +- 404 Error in pathinfo mode [\#1965](https://github.com/codeigniter4/CodeIgniter4/issues/1965) +- A controller parameter without default value creates ReflectionException error [\#1948](https://github.com/codeigniter4/CodeIgniter4/issues/1948) +- Wrong datetime on history debug toolbar [\#1944](https://github.com/codeigniter4/CodeIgniter4/issues/1944) +- Appstarter is not working after installation [\#1941](https://github.com/codeigniter4/CodeIgniter4/issues/1941) +- AppStarter Vanilla Installation - No hint that writable directory is not writable [\#1934](https://github.com/codeigniter4/CodeIgniter4/issues/1934) +- Updating appstarter with composer does not take composer.phar into account [\#1932](https://github.com/codeigniter4/CodeIgniter4/issues/1932) +- Problem with extending core class. [\#1922](https://github.com/codeigniter4/CodeIgniter4/issues/1922) +- Debug Toolbar causes error if an array is provided as session value since Beta 2 [\#1919](https://github.com/codeigniter4/CodeIgniter4/issues/1919) +- Wrong controller filter processing because of faulty regular expression generation [\#1907](https://github.com/codeigniter4/CodeIgniter4/issues/1907) +- Toolbar not supporting IE11 \(realXHR.responseURL\) [\#1905](https://github.com/codeigniter4/CodeIgniter4/issues/1905) +- Validation Always Print Error Message [\#1903](https://github.com/codeigniter4/CodeIgniter4/issues/1903) +- Using soft deletes can lead to mysql ambiguous exception [\#1881](https://github.com/codeigniter4/CodeIgniter4/issues/1881) +- Error when running `php spark serve` \(spaces in folder names\) [\#1880](https://github.com/codeigniter4/CodeIgniter4/issues/1880) +- Class 'CodeIgniter\Filters\DebugToolbar' not found [\#1871](https://github.com/codeigniter4/CodeIgniter4/issues/1871) +- Should unmatched cache\(\) return null? [\#1870](https://github.com/codeigniter4/CodeIgniter4/issues/1870) +- Class 'CodeIgniter\Test\CIUnitTestCase' not found when testing in Appstarter project [\#1864](https://github.com/codeigniter4/CodeIgniter4/issues/1864) +- Public methods in system/Controller [\#1849](https://github.com/codeigniter4/CodeIgniter4/issues/1849) +- Controller in folder - method not accessible unless I set a route on Linux - Windows OK [\#1841](https://github.com/codeigniter4/CodeIgniter4/issues/1841) +- Routing to hardcoded ids does not work [\#1838](https://github.com/codeigniter4/CodeIgniter4/issues/1838) +- Form Validation: required\_with and required\_without implementations [\#1837](https://github.com/codeigniter4/CodeIgniter4/issues/1837) +- Events.php on method $callable parameter accepts only callable [\#1835](https://github.com/codeigniter4/CodeIgniter4/issues/1835) +- Controller response property overriding by ControllerResponse inPHPUnit [\#1834](https://github.com/codeigniter4/CodeIgniter4/issues/1834) +- ValidationInterface run method $data attribute should be nullable [\#1833](https://github.com/codeigniter4/CodeIgniter4/issues/1833) +- Fail to render view in a view with layout [\#1826](https://github.com/codeigniter4/CodeIgniter4/issues/1826) +- UploadedFile::move and File::move have different implementation [\#1825](https://github.com/codeigniter4/CodeIgniter4/issues/1825) +- Missing documentation: parser is not able to handle nested loops [\#1821](https://github.com/codeigniter4/CodeIgniter4/issues/1821) +- Is hashId function missing? [\#1801](https://github.com/codeigniter4/CodeIgniter4/issues/1801) +- Parser is not able to handle nested loops [\#1799](https://github.com/codeigniter4/CodeIgniter4/issues/1799) +- Routing rules order \[suspended / probably csrf\] [\#1798](https://github.com/codeigniter4/CodeIgniter4/issues/1798) +- I need to call session\(\) if I want to be able to use old\(\) in the forms. [\#1795](https://github.com/codeigniter4/CodeIgniter4/issues/1795) +- Output getting buffered when running via command line [\#1792](https://github.com/codeigniter4/CodeIgniter4/issues/1792) +- Wrong CodeIgniter::handleRequest method definition [\#1786](https://github.com/codeigniter4/CodeIgniter4/issues/1786) +- File::move is not moving file [\#1785](https://github.com/codeigniter4/CodeIgniter4/issues/1785) +- Question about date helper [\#1783](https://github.com/codeigniter4/CodeIgniter4/issues/1783) +- Intention or bug? File::move does not update path [\#1782](https://github.com/codeigniter4/CodeIgniter4/issues/1782) +- Small typos in documentation section "Taking Advantage of Spl" [\#1781](https://github.com/codeigniter4/CodeIgniter4/issues/1781) +- Documentation mistake: Model::save does not return a boolean only [\#1780](https://github.com/codeigniter4/CodeIgniter4/issues/1780) +- Toolbar::run produces incompatible data for json\_encode [\#1779](https://github.com/codeigniter4/CodeIgniter4/issues/1779) +- History::setFiles may crash when reading empty file [\#1778](https://github.com/codeigniter4/CodeIgniter4/issues/1778) +- Can't set subquery as WHERE condition. [\#1775](https://github.com/codeigniter4/CodeIgniter4/issues/1775) +- Ignoring 'required' validation rule for inserts. [\#1773](https://github.com/codeigniter4/CodeIgniter4/issues/1773) +- save\(\) method trying to insert instead of update [\#1770](https://github.com/codeigniter4/CodeIgniter4/issues/1770) +- Controller Test / Feature Testing output issues [\#1767](https://github.com/codeigniter4/CodeIgniter4/issues/1767) +- MigrationRunner::version should return "current version string on success" [\#1766](https://github.com/codeigniter4/CodeIgniter4/issues/1766) +- DIRECTORY\_SEPARATOR / Different Behavior under Windows [\#1760](https://github.com/codeigniter4/CodeIgniter4/issues/1760) +- HTTP Feature Testing not working [\#1710](https://github.com/codeigniter4/CodeIgniter4/issues/1710) +- alpha4-\>5 requires to have primary key in every model/table [\#1706](https://github.com/codeigniter4/CodeIgniter4/issues/1706) +- route\_to\('name'\); does not work for other subdomains [\#1697](https://github.com/codeigniter4/CodeIgniter4/issues/1697) +- Router issue - overwriting. [\#1692](https://github.com/codeigniter4/CodeIgniter4/issues/1692) +- Using Memcache as Session Handler cause exception during regenerate. [\#1676](https://github.com/codeigniter4/CodeIgniter4/issues/1676) +- Model's without primary keys get pagination counts wrong [\#1597](https://github.com/codeigniter4/CodeIgniter4/issues/1597) +- Unable set ENVIRONMENT with Spark [\#1268](https://github.com/codeigniter4/CodeIgniter4/issues/1268) +- WIP Improve unit tests [\#512](https://github.com/codeigniter4/CodeIgniter4/issues/512) + +**Merged pull requests:** + +- Prep for beta.3 [\#1990](https://github.com/codeigniter4/CodeIgniter4/pull/1990) ([jim-parry](https://github.com/jim-parry)) +- Correct API docblock problems for phpdocs [\#1987](https://github.com/codeigniter4/CodeIgniter4/pull/1987) ([jim-parry](https://github.com/jim-parry)) +- Update docblock version to 4.0.0 [\#1986](https://github.com/codeigniter4/CodeIgniter4/pull/1986) ([jim-parry](https://github.com/jim-parry)) +- Fix filter processing. Fixes \#1907 [\#1985](https://github.com/codeigniter4/CodeIgniter4/pull/1985) ([jim-parry](https://github.com/jim-parry)) +- Add footing to HTML Table [\#1984](https://github.com/codeigniter4/CodeIgniter4/pull/1984) ([jim-parry](https://github.com/jim-parry)) +- Using soft deletes should not return an ambiguous field message when joining tables. Closes \#1881 [\#1981](https://github.com/codeigniter4/CodeIgniter4/pull/1981) ([lonnieezell](https://github.com/lonnieezell)) +- Corrected return value for Session/RedisHandler::read to string, per PHP specs [\#1980](https://github.com/codeigniter4/CodeIgniter4/pull/1980) ([lonnieezell](https://github.com/lonnieezell)) +- Implement HTML Table for CI4 [\#1978](https://github.com/codeigniter4/CodeIgniter4/pull/1978) ([jim-parry](https://github.com/jim-parry)) +- Test/featuretestcase [\#1977](https://github.com/codeigniter4/CodeIgniter4/pull/1977) ([jim-parry](https://github.com/jim-parry)) +- Fix validation rules table format [\#1975](https://github.com/codeigniter4/CodeIgniter4/pull/1975) ([jim-parry](https://github.com/jim-parry)) +- Remove framework classes from the autoloader classmap. [\#1974](https://github.com/codeigniter4/CodeIgniter4/pull/1974) ([lonnieezell](https://github.com/lonnieezell)) +- Defaultfixes [\#1973](https://github.com/codeigniter4/CodeIgniter4/pull/1973) ([lonnieezell](https://github.com/lonnieezell)) +- Toolbar fix for custom collectors [\#1972](https://github.com/codeigniter4/CodeIgniter4/pull/1972) ([MGatner](https://github.com/MGatner)) +- Add back filter arguments [\#1970](https://github.com/codeigniter4/CodeIgniter4/pull/1970) ([MGatner](https://github.com/MGatner)) +- Fixed pathinfo mode 404 error, rebuild array index of uri segments from array\_filter\(\) [\#1968](https://github.com/codeigniter4/CodeIgniter4/pull/1968) ([viosion](https://github.com/viosion)) +- String type primary key should also wrap into an array during db update [\#1963](https://github.com/codeigniter4/CodeIgniter4/pull/1963) ([vibbow](https://github.com/vibbow)) +- WIP - Fix side issue [\#1962](https://github.com/codeigniter4/CodeIgniter4/pull/1962) ([vibbow](https://github.com/vibbow)) +- Fix Debugbar url tail slash issue [\#1961](https://github.com/codeigniter4/CodeIgniter4/pull/1961) ([vibbow](https://github.com/vibbow)) +- New generic string validation rule. [\#1957](https://github.com/codeigniter4/CodeIgniter4/pull/1957) ([lonnieezell](https://github.com/lonnieezell)) +- Use Null Coalesce Operator [\#1956](https://github.com/codeigniter4/CodeIgniter4/pull/1956) ([carusogabriel](https://github.com/carusogabriel)) +- Travis-CI build failed fix [\#1955](https://github.com/codeigniter4/CodeIgniter4/pull/1955) ([atishamte](https://github.com/atishamte)) +- Fix validation table format [\#1954](https://github.com/codeigniter4/CodeIgniter4/pull/1954) ([jim-parry](https://github.com/jim-parry)) +- Add Validations for `equals\(\)` and `not\_equals\(\)` [\#1952](https://github.com/codeigniter4/CodeIgniter4/pull/1952) ([MGatner](https://github.com/MGatner)) +- System typos changes & code cleanup [\#1951](https://github.com/codeigniter4/CodeIgniter4/pull/1951) ([atishamte](https://github.com/atishamte)) +- Fix some side issue [\#1950](https://github.com/codeigniter4/CodeIgniter4/pull/1950) ([vibbow](https://github.com/vibbow)) +- Toobar/Routes correction [\#1949](https://github.com/codeigniter4/CodeIgniter4/pull/1949) ([atishamte](https://github.com/atishamte)) +- Fix BaseConfig didn't load Registrar files properly [\#1947](https://github.com/codeigniter4/CodeIgniter4/pull/1947) ([vibbow](https://github.com/vibbow)) +- Fix datetime extraction from debugbar file [\#1945](https://github.com/codeigniter4/CodeIgniter4/pull/1945) ([soft2u](https://github.com/soft2u)) +- Model, Entity, Exception & Migration test cases [\#1943](https://github.com/codeigniter4/CodeIgniter4/pull/1943) ([atishamte](https://github.com/atishamte)) +- Remove section that prevents hotlinking [\#1939](https://github.com/codeigniter4/CodeIgniter4/pull/1939) ([MGatner](https://github.com/MGatner)) +- Database typos changes [\#1938](https://github.com/codeigniter4/CodeIgniter4/pull/1938) ([atishamte](https://github.com/atishamte)) +- Docs: improve app testing writeup [\#1936](https://github.com/codeigniter4/CodeIgniter4/pull/1936) ([jim-parry](https://github.com/jim-parry)) +- Update phpunit.xml scripts. Fixes \#1932 [\#1935](https://github.com/codeigniter4/CodeIgniter4/pull/1935) ([jim-parry](https://github.com/jim-parry)) +- having \(Is NULL deletion\) [\#1933](https://github.com/codeigniter4/CodeIgniter4/pull/1933) ([nowackipawel](https://github.com/nowackipawel)) +- Toolbar IE11 fix [\#1931](https://github.com/codeigniter4/CodeIgniter4/pull/1931) ([REJack](https://github.com/REJack)) +- Model Changes w.r.t. \#1773 [\#1930](https://github.com/codeigniter4/CodeIgniter4/pull/1930) ([atishamte](https://github.com/atishamte)) +- Entity exception for non existed props. [\#1927](https://github.com/codeigniter4/CodeIgniter4/pull/1927) ([nowackipawel](https://github.com/nowackipawel)) +- Docs: update installation guide [\#1926](https://github.com/codeigniter4/CodeIgniter4/pull/1926) ([jim-parry](https://github.com/jim-parry)) +- removed $\_SERVER\['CI\_ENVIRONMENT'\] [\#1925](https://github.com/codeigniter4/CodeIgniter4/pull/1925) ([truelineinfotech](https://github.com/truelineinfotech)) +- missing return [\#1923](https://github.com/codeigniter4/CodeIgniter4/pull/1923) ([titounnes](https://github.com/titounnes)) +- JSONFormatter [\#1918](https://github.com/codeigniter4/CodeIgniter4/pull/1918) ([nowackipawel](https://github.com/nowackipawel)) +- Database Test Cases [\#1917](https://github.com/codeigniter4/CodeIgniter4/pull/1917) ([atishamte](https://github.com/atishamte)) +- Check if the value is string [\#1916](https://github.com/codeigniter4/CodeIgniter4/pull/1916) ([daif](https://github.com/daif)) +- Fix for POST + JSON \(Content-Length added\) [\#1915](https://github.com/codeigniter4/CodeIgniter4/pull/1915) ([nowackipawel](https://github.com/nowackipawel)) +- Housekeeping - prep for beta.2 [\#1914](https://github.com/codeigniter4/CodeIgniter4/pull/1914) ([jim-parry](https://github.com/jim-parry)) +- More RouteCollection tests for overwriting. Closes \#1692 [\#1913](https://github.com/codeigniter4/CodeIgniter4/pull/1913) ([jim-parry](https://github.com/jim-parry)) +- Additional RouteCollectionTests [\#1912](https://github.com/codeigniter4/CodeIgniter4/pull/1912) ([jim-parry](https://github.com/jim-parry)) +- JSON Cast exception test cases [\#1911](https://github.com/codeigniter4/CodeIgniter4/pull/1911) ([atishamte](https://github.com/atishamte)) +- Added print method to CLI library so you can print multiple times on same line [\#1910](https://github.com/codeigniter4/CodeIgniter4/pull/1910) ([lonnieezell](https://github.com/lonnieezell)) +- Add filter parameters to User Guide [\#1908](https://github.com/codeigniter4/CodeIgniter4/pull/1908) ([MGatner](https://github.com/MGatner)) +- SubQuery related test cases w.r.t \#1775 [\#1906](https://github.com/codeigniter4/CodeIgniter4/pull/1906) ([atishamte](https://github.com/atishamte)) +- BaseBuilder Corrections [\#1902](https://github.com/codeigniter4/CodeIgniter4/pull/1902) ([atishamte](https://github.com/atishamte)) +- Update .htaccess for better security and caching [\#1900](https://github.com/codeigniter4/CodeIgniter4/pull/1900) ([atishamte](https://github.com/atishamte)) +- Database Forge correction [\#1899](https://github.com/codeigniter4/CodeIgniter4/pull/1899) ([atishamte](https://github.com/atishamte)) +- Toolbar fix w.r.t \#1779 [\#1897](https://github.com/codeigniter4/CodeIgniter4/pull/1897) ([atishamte](https://github.com/atishamte)) +- Mysql connection issue with MYSQLI\_CLIENT\_SSL\_DONT\_VERIFY\_SERVER\_CERT \#1219 [\#1896](https://github.com/codeigniter4/CodeIgniter4/pull/1896) ([atishamte](https://github.com/atishamte)) +- Unmatched Cache Library `get\(\)` return null [\#1895](https://github.com/codeigniter4/CodeIgniter4/pull/1895) ([MGatner](https://github.com/MGatner)) +- New method Find Column w.r.t. \#1619 [\#1861](https://github.com/codeigniter4/CodeIgniter4/pull/1861) ([atishamte](https://github.com/atishamte)) + +## [v4.0.0-beta.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-beta.1) (2019-03-01) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0.0-alpha.5...v4.0.0-beta.1) + +**Fixed bugs:** + +- Sequential Migrations don't run to the correct version [\#1666](https://github.com/codeigniter4/CodeIgniter4/issues/1666) + +**Closed issues:** + +- Back references not working in route [\#1761](https://github.com/codeigniter4/CodeIgniter4/issues/1761) +- setDefaultController not working as expected [\#1758](https://github.com/codeigniter4/CodeIgniter4/issues/1758) +- Spark doesn't work when using devstarter [\#1748](https://github.com/codeigniter4/CodeIgniter4/issues/1748) +- required\_with and required\_without validation rules [\#1735](https://github.com/codeigniter4/CodeIgniter4/issues/1735) +- validation rule password never match [\#1728](https://github.com/codeigniter4/CodeIgniter4/issues/1728) +- Entities errors [\#1727](https://github.com/codeigniter4/CodeIgniter4/issues/1727) +- Loading namespace helpers doesn't work as expected CI4 alpha5 [\#1726](https://github.com/codeigniter4/CodeIgniter4/issues/1726) +- spark migrate:latest ErrorException alpha5 [\#1724](https://github.com/codeigniter4/CodeIgniter4/issues/1724) +- redirect\(\)-\>to lost $baseURL config [\#1721](https://github.com/codeigniter4/CodeIgniter4/issues/1721) +- Bug Report: Seeding [\#1720](https://github.com/codeigniter4/CodeIgniter4/issues/1720) +- Spark missing arguments [\#1718](https://github.com/codeigniter4/CodeIgniter4/issues/1718) +- Model required validation rule not working [\#1717](https://github.com/codeigniter4/CodeIgniter4/issues/1717) +- ZendEscaper - duplicate? [\#1716](https://github.com/codeigniter4/CodeIgniter4/issues/1716) +- Why we required form pointed to correct url? [\#1713](https://github.com/codeigniter4/CodeIgniter4/issues/1713) +- Why there is only 1 function in ArrayHelper? Can we introduce more? [\#1711](https://github.com/codeigniter4/CodeIgniter4/issues/1711) +- CodeIgniter\Model::cleanValidationRules\(\) must be of the type array, string given [\#1707](https://github.com/codeigniter4/CodeIgniter4/issues/1707) +- alpha 4-\>5 query param binding [\#1705](https://github.com/codeigniter4/CodeIgniter4/issues/1705) +- failValidationError\($description\) [\#1702](https://github.com/codeigniter4/CodeIgniter4/issues/1702) +- Bug : changing viewsDirectory misses errors folder when exception occures [\#1701](https://github.com/codeigniter4/CodeIgniter4/issues/1701) +- Cannot define complex routes , i.e. date [\#1700](https://github.com/codeigniter4/CodeIgniter4/issues/1700) +- lang bug or not? \(empty translations\) [\#1698](https://github.com/codeigniter4/CodeIgniter4/issues/1698) +- Issue Extend Core Class [\#1653](https://github.com/codeigniter4/CodeIgniter4/issues/1653) +- Turn OFF getMyProperty\(\) method during DB save. [\#1646](https://github.com/codeigniter4/CodeIgniter4/issues/1646) +- Model class crashes when handling complex validation rules [\#1574](https://github.com/codeigniter4/CodeIgniter4/issues/1574) +- Database ForgeTest hiccup [\#1478](https://github.com/codeigniter4/CodeIgniter4/issues/1478) +- SQLLite3 Forge needs better column handling [\#1255](https://github.com/codeigniter4/CodeIgniter4/issues/1255) +- TODO BaseConnection needs better error handling [\#1254](https://github.com/codeigniter4/CodeIgniter4/issues/1254) +- Model Alternative Keys [\#428](https://github.com/codeigniter4/CodeIgniter4/issues/428) + +**Merged pull requests:** + +- Housekeeping for beta.1 [\#1774](https://github.com/codeigniter4/CodeIgniter4/pull/1774) ([jim-parry](https://github.com/jim-parry)) +- Helper changes [\#1768](https://github.com/codeigniter4/CodeIgniter4/pull/1768) ([atishamte](https://github.com/atishamte)) +- Fix routing when no default route has been specified. Fixes \#1758 [\#1764](https://github.com/codeigniter4/CodeIgniter4/pull/1764) ([lonnieezell](https://github.com/lonnieezell)) +- Ensure validation works in Model with errors as part of rules. Fixes \#1574 [\#1763](https://github.com/codeigniter4/CodeIgniter4/pull/1763) ([lonnieezell](https://github.com/lonnieezell)) +- Correct the unneeded double-quote \(typo\) [\#1757](https://github.com/codeigniter4/CodeIgniter4/pull/1757) ([smhnaji](https://github.com/smhnaji)) +- lowercase 'vfsStream' in composer files [\#1755](https://github.com/codeigniter4/CodeIgniter4/pull/1755) ([MGatner](https://github.com/MGatner)) +- Fixed typo preventing link format [\#1752](https://github.com/codeigniter4/CodeIgniter4/pull/1752) ([MGatner](https://github.com/MGatner)) +- Guide: Moving misplaced text under correct heading [\#1751](https://github.com/codeigniter4/CodeIgniter4/pull/1751) ([MGatner](https://github.com/MGatner)) +- Remove reference to Encryption Key in User Guide [\#1750](https://github.com/codeigniter4/CodeIgniter4/pull/1750) ([MGatner](https://github.com/MGatner)) +- Adding environment to .env [\#1749](https://github.com/codeigniter4/CodeIgniter4/pull/1749) ([MGatner](https://github.com/MGatner)) +- Updated composite key tests for SQLite3 support. Fixes \#1478 [\#1745](https://github.com/codeigniter4/CodeIgniter4/pull/1745) ([lonnieezell](https://github.com/lonnieezell)) +- Update entity docs for current framework state. Fixes \#1727 [\#1744](https://github.com/codeigniter4/CodeIgniter4/pull/1744) ([lonnieezell](https://github.com/lonnieezell)) +- Manually sort migrations found instead of relying on the OS. Fixes \#1666 [\#1743](https://github.com/codeigniter4/CodeIgniter4/pull/1743) ([lonnieezell](https://github.com/lonnieezell)) +- Fix required\_without rule bug. [\#1742](https://github.com/codeigniter4/CodeIgniter4/pull/1742) ([bangbangda](https://github.com/bangbangda)) +- Helpers with a specific namespace can be loaded now. Fixes \#1726 [\#1741](https://github.com/codeigniter4/CodeIgniter4/pull/1741) ([lonnieezell](https://github.com/lonnieezell)) +- Refactor test support for app starter [\#1740](https://github.com/codeigniter4/CodeIgniter4/pull/1740) ([jim-parry](https://github.com/jim-parry)) +- Fix typo [\#1739](https://github.com/codeigniter4/CodeIgniter4/pull/1739) ([vibbow](https://github.com/vibbow)) +- Fix required\_with rule bug. Fixes \#1728 [\#1738](https://github.com/codeigniter4/CodeIgniter4/pull/1738) ([bangbangda](https://github.com/bangbangda)) +- Added support for dropTable and modifyTable with SQLite driver [\#1737](https://github.com/codeigniter4/CodeIgniter4/pull/1737) ([lonnieezell](https://github.com/lonnieezell)) +- Accommodate long travis execution times [\#1736](https://github.com/codeigniter4/CodeIgniter4/pull/1736) ([jim-parry](https://github.com/jim-parry)) +- Fix increment and decrement errors with Postgres [\#1733](https://github.com/codeigniter4/CodeIgniter4/pull/1733) ([lonnieezell](https://github.com/lonnieezell)) +- Don't check from CLI in Routes. Fixes \#1724 [\#1732](https://github.com/codeigniter4/CodeIgniter4/pull/1732) ([lonnieezell](https://github.com/lonnieezell)) +- Revert "Ensure isn't checked during RouteCollection calls when called from CLI" [\#1731](https://github.com/codeigniter4/CodeIgniter4/pull/1731) ([lonnieezell](https://github.com/lonnieezell)) +- Ensure isn't checked during RouteCollection calls when called from CLI [\#1730](https://github.com/codeigniter4/CodeIgniter4/pull/1730) ([lonnieezell](https://github.com/lonnieezell)) +- New View Layout functionality for simple template functionality. [\#1729](https://github.com/codeigniter4/CodeIgniter4/pull/1729) ([lonnieezell](https://github.com/lonnieezell)) +- Update Request.php [\#1725](https://github.com/codeigniter4/CodeIgniter4/pull/1725) ([HieuPT7](https://github.com/HieuPT7)) +- Log an error if redis authentication is failed. [\#1723](https://github.com/codeigniter4/CodeIgniter4/pull/1723) ([vibbow](https://github.com/vibbow)) +- Seeder adds default namespace to seeds [\#1722](https://github.com/codeigniter4/CodeIgniter4/pull/1722) ([lonnieezell](https://github.com/lonnieezell)) +- Update Cache RedisHandler to support select database. [\#1719](https://github.com/codeigniter4/CodeIgniter4/pull/1719) ([vibbow](https://github.com/vibbow)) +- minors \(Model.php\) [\#1712](https://github.com/codeigniter4/CodeIgniter4/pull/1712) ([nowackipawel](https://github.com/nowackipawel)) +- Fix/rc [\#1709](https://github.com/codeigniter4/CodeIgniter4/pull/1709) ([jim-parry](https://github.com/jim-parry)) +- UploadFile - language support [\#1708](https://github.com/codeigniter4/CodeIgniter4/pull/1708) ([nowackipawel](https://github.com/nowackipawel)) +- Fix viewsDirectory bug Fixes \#1701 [\#1704](https://github.com/codeigniter4/CodeIgniter4/pull/1704) ([bangbangda](https://github.com/bangbangda)) +- Fix install link in user guide [\#1699](https://github.com/codeigniter4/CodeIgniter4/pull/1699) ([jim-parry](https://github.com/jim-parry)) +- Fix page structure etc [\#1696](https://github.com/codeigniter4/CodeIgniter4/pull/1696) ([jim-parry](https://github.com/jim-parry)) +- Tidy up code blocks in the user guide [\#1695](https://github.com/codeigniter4/CodeIgniter4/pull/1695) ([jim-parry](https://github.com/jim-parry)) + +## [v4.0.0.0-alpha.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0.0-alpha.5) (2019-01-30) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.5...v4.0.0.0-alpha.5) + +**Implemented enhancements:** + +- Cache system should support site-wide prefixes [\#1659](https://github.com/codeigniter4/CodeIgniter4/issues/1659) + +**Fixed bugs:** + +- Problem with Database BaseBuilder binds [\#1226](https://github.com/codeigniter4/CodeIgniter4/issues/1226) + +**Closed issues:** + +- DB query not working with disabled escaping [\#1687](https://github.com/codeigniter4/CodeIgniter4/issues/1687) +- migrate:refresh ArgumentCountError [\#1682](https://github.com/codeigniter4/CodeIgniter4/issues/1682) +- Do I need to clear old session files manually? [\#1681](https://github.com/codeigniter4/CodeIgniter4/issues/1681) +- Pagination / pager-\>links\(\) double encodes links [\#1680](https://github.com/codeigniter4/CodeIgniter4/issues/1680) +- Document Method Spoofing for forms. [\#1668](https://github.com/codeigniter4/CodeIgniter4/issues/1668) +- insertBatch with escape=false still escapes values [\#1667](https://github.com/codeigniter4/CodeIgniter4/issues/1667) +- Filters should not be case sensitive [\#1664](https://github.com/codeigniter4/CodeIgniter4/issues/1664) +- RouteCollection::discoverRoutes incomplete [\#1662](https://github.com/codeigniter4/CodeIgniter4/issues/1662) +- Feature request make is\_unique - more than one field. [\#1655](https://github.com/codeigniter4/CodeIgniter4/issues/1655) +- Toolbar logs tab - not logging [\#1651](https://github.com/codeigniter4/CodeIgniter4/issues/1651) +- DebugToolbar - too much recursion [\#1650](https://github.com/codeigniter4/CodeIgniter4/issues/1650) +- \[documentation\] Typing mistake in transaction example [\#1639](https://github.com/codeigniter4/CodeIgniter4/issues/1639) +- Transaction documentation error and/or bug [\#1638](https://github.com/codeigniter4/CodeIgniter4/issues/1638) +- Bug : pagination broken when using 1 as perPage [\#1628](https://github.com/codeigniter4/CodeIgniter4/issues/1628) +- View data not being passed between each call? [\#1621](https://github.com/codeigniter4/CodeIgniter4/issues/1621) +- Composer Installation downloads app and application folder. [\#1620](https://github.com/codeigniter4/CodeIgniter4/issues/1620) +- countAllResults\(\) should respect soft deletes [\#1617](https://github.com/codeigniter4/CodeIgniter4/issues/1617) +- redirect function don't redirect to base\_url [\#1611](https://github.com/codeigniter4/CodeIgniter4/issues/1611) +- Memory issue - Toolbar collects every query [\#1607](https://github.com/codeigniter4/CodeIgniter4/issues/1607) +- Pls remove string type in parameter $group at Database::forge [\#1605](https://github.com/codeigniter4/CodeIgniter4/issues/1605) +- SQL JOIN : bad aliasing on join with prefixed db tables [\#1599](https://github.com/codeigniter4/CodeIgniter4/issues/1599) +- Model's update method fails when validation rules exist [\#1584](https://github.com/codeigniter4/CodeIgniter4/issues/1584) +- maybe need to modify session garbage collector section. \(FileHandler\) [\#1565](https://github.com/codeigniter4/CodeIgniter4/issues/1565) +- Maybe routes has problem. \[setTranslateURIDashes\] [\#1564](https://github.com/codeigniter4/CodeIgniter4/issues/1564) +- ErrorException Trying to get property 'affected\_rows' of non-object [\#1559](https://github.com/codeigniter4/CodeIgniter4/issues/1559) +- UG - typo in Managing Apps [\#1558](https://github.com/codeigniter4/CodeIgniter4/issues/1558) +- Database migration uses wrong database when initialising migration classes [\#1532](https://github.com/codeigniter4/CodeIgniter4/issues/1532) +- Database migration table not correctly created when a non-default database connection is used [\#1531](https://github.com/codeigniter4/CodeIgniter4/issues/1531) +- MYSQL : orderBy\(\) considers CASE statement as a column [\#1528](https://github.com/codeigniter4/CodeIgniter4/issues/1528) +- getCompiledSelect\(\) return query without binds [\#1526](https://github.com/codeigniter4/CodeIgniter4/issues/1526) +- Commit pre-hook misbehaving [\#1404](https://github.com/codeigniter4/CodeIgniter4/issues/1404) +- Lack of proper instruction in documentation for changing Application and System folder name [\#1366](https://github.com/codeigniter4/CodeIgniter4/issues/1366) +- SubQueries \(tables from outside of the current model\) [\#1175](https://github.com/codeigniter4/CodeIgniter4/issues/1175) +- FileHandler Garbage Collector fails to delete expired session files. [\#942](https://github.com/codeigniter4/CodeIgniter4/issues/942) + +**Merged pull requests:** + +- Update changelog for alpha.5 [\#1694](https://github.com/codeigniter4/CodeIgniter4/pull/1694) ([jim-parry](https://github.com/jim-parry)) +- Docs/tutorial [\#1693](https://github.com/codeigniter4/CodeIgniter4/pull/1693) ([jim-parry](https://github.com/jim-parry)) +- Update the running docs [\#1691](https://github.com/codeigniter4/CodeIgniter4/pull/1691) ([jim-parry](https://github.com/jim-parry)) +- Rework install docs [\#1690](https://github.com/codeigniter4/CodeIgniter4/pull/1690) ([jim-parry](https://github.com/jim-parry)) +- Model Validation Fix [\#1689](https://github.com/codeigniter4/CodeIgniter4/pull/1689) ([lonnieezell](https://github.com/lonnieezell)) +- Add copyright blocks to filters [\#1688](https://github.com/codeigniter4/CodeIgniter4/pull/1688) ([jim-parry](https://github.com/jim-parry)) +- Refactor/filters [\#1686](https://github.com/codeigniter4/CodeIgniter4/pull/1686) ([jim-parry](https://github.com/jim-parry)) +- Fix admin - app starter creation [\#1685](https://github.com/codeigniter4/CodeIgniter4/pull/1685) ([jim-parry](https://github.com/jim-parry)) +- Updating session id cleanup for filehandler. Fixes \#1681 Fixes \#1565 [\#1684](https://github.com/codeigniter4/CodeIgniter4/pull/1684) ([lonnieezell](https://github.com/lonnieezell)) +- Fix migrate:refresh bug Fixes \#1682 [\#1683](https://github.com/codeigniter4/CodeIgniter4/pull/1683) ([bangbangda](https://github.com/bangbangda)) +- save\_path - for memcached \(Session.php\) + sess\_prefix \(..Handler.php\) [\#1679](https://github.com/codeigniter4/CodeIgniter4/pull/1679) ([nowackipawel](https://github.com/nowackipawel)) +- fix route not replacing forward slashes [\#1678](https://github.com/codeigniter4/CodeIgniter4/pull/1678) ([puschie286](https://github.com/puschie286)) +- Implement Don't Escape feature for db engine [\#1677](https://github.com/codeigniter4/CodeIgniter4/pull/1677) ([lonnieezell](https://github.com/lonnieezell)) +- Add missing test group directives [\#1675](https://github.com/codeigniter4/CodeIgniter4/pull/1675) ([jim-parry](https://github.com/jim-parry)) +- Changelog alpha.5 so far [\#1674](https://github.com/codeigniter4/CodeIgniter4/pull/1674) ([jim-parry](https://github.com/jim-parry)) +- Updated download & installation docs [\#1673](https://github.com/codeigniter4/CodeIgniter4/pull/1673) ([jim-parry](https://github.com/jim-parry)) +- Update Autoloader.php [\#1672](https://github.com/codeigniter4/CodeIgniter4/pull/1672) ([zl59503020](https://github.com/zl59503020)) +- Update docs [\#1671](https://github.com/codeigniter4/CodeIgniter4/pull/1671) ([jim-parry](https://github.com/jim-parry)) +- Update PHP dependency to 7.2 [\#1670](https://github.com/codeigniter4/CodeIgniter4/pull/1670) ([jim-parry](https://github.com/jim-parry)) +- Enhance Parser & Plugin testing [\#1669](https://github.com/codeigniter4/CodeIgniter4/pull/1669) ([jim-parry](https://github.com/jim-parry)) +- Composer PSR4 namespaces are now part of the modules auto-discovery [\#1665](https://github.com/codeigniter4/CodeIgniter4/pull/1665) ([lonnieezell](https://github.com/lonnieezell)) +- Fix bind issue that occurred when using whereIn or orWhereIn with a c… [\#1663](https://github.com/codeigniter4/CodeIgniter4/pull/1663) ([lonnieezell](https://github.com/lonnieezell)) +- Migrations Tests and database tweaks [\#1660](https://github.com/codeigniter4/CodeIgniter4/pull/1660) ([lonnieezell](https://github.com/lonnieezell)) +- DBGroup in \_\_get\(\), allows to validate "database" data outside the model. [\#1656](https://github.com/codeigniter4/CodeIgniter4/pull/1656) ([nowackipawel](https://github.com/nowackipawel)) +- Toolbar - Return Logger::$logCache items when collecting [\#1654](https://github.com/codeigniter4/CodeIgniter4/pull/1654) ([natanfelles](https://github.com/natanfelles)) +- remove php 7.3 from "allow\_failures" in travis config [\#1649](https://github.com/codeigniter4/CodeIgniter4/pull/1649) ([samsonasik](https://github.com/samsonasik)) +- Update "managing apps" docs [\#1648](https://github.com/codeigniter4/CodeIgniter4/pull/1648) ([jim-parry](https://github.com/jim-parry)) +- Fix transaction enabling confusing \(docu\) [\#1645](https://github.com/codeigniter4/CodeIgniter4/pull/1645) ([puschie286](https://github.com/puschie286)) +- Remove Email module [\#1643](https://github.com/codeigniter4/CodeIgniter4/pull/1643) ([jim-parry](https://github.com/jim-parry)) +- CSP nonce attribute value in "" [\#1642](https://github.com/codeigniter4/CodeIgniter4/pull/1642) ([nowackipawel](https://github.com/nowackipawel)) +- More unit testing tweaks [\#1641](https://github.com/codeigniter4/CodeIgniter4/pull/1641) ([jim-parry](https://github.com/jim-parry)) +- Update getCompiledX methods in BaseBuilder to return fully compiled q… [\#1640](https://github.com/codeigniter4/CodeIgniter4/pull/1640) ([lonnieezell](https://github.com/lonnieezell)) +- Fix starter README [\#1637](https://github.com/codeigniter4/CodeIgniter4/pull/1637) ([kenjis](https://github.com/kenjis)) +- Refactor Files module [\#1636](https://github.com/codeigniter4/CodeIgniter4/pull/1636) ([jim-parry](https://github.com/jim-parry)) +- Unit testing enhancements [\#1635](https://github.com/codeigniter4/CodeIgniter4/pull/1635) ([jim-parry](https://github.com/jim-parry)) +- Uses csrf\_field and form\_hidden instead of inline-html in form\_open [\#1633](https://github.com/codeigniter4/CodeIgniter4/pull/1633) ([nowackipawel](https://github.com/nowackipawel)) +- DBGroup should be passed to -\>run instead of -\>setRules [\#1632](https://github.com/codeigniter4/CodeIgniter4/pull/1632) ([nowackipawel](https://github.com/nowackipawel)) +- move use statement after License doc at UploadedFile class [\#1631](https://github.com/codeigniter4/CodeIgniter4/pull/1631) ([samsonasik](https://github.com/samsonasik)) +- Update copyright to 2019 [\#1630](https://github.com/codeigniter4/CodeIgniter4/pull/1630) ([jim-parry](https://github.com/jim-parry)) +- "application" to "app" directory doc and comments and welcome\_message clean up [\#1629](https://github.com/codeigniter4/CodeIgniter4/pull/1629) ([samsonasik](https://github.com/samsonasik)) +- clean up Paths::$viewDirectory property [\#1626](https://github.com/codeigniter4/CodeIgniter4/pull/1626) ([samsonasik](https://github.com/samsonasik)) +- fix. After matches is not set empty [\#1625](https://github.com/codeigniter4/CodeIgniter4/pull/1625) ([Instrye](https://github.com/Instrye)) +- Property was not cast if was defined as nullable. [\#1623](https://github.com/codeigniter4/CodeIgniter4/pull/1623) ([nowackipawel](https://github.com/nowackipawel)) +- Nullable support for \_\_set. [\#1622](https://github.com/codeigniter4/CodeIgniter4/pull/1622) ([nowackipawel](https://github.com/nowackipawel)) +- Fix View config merge order [\#1616](https://github.com/codeigniter4/CodeIgniter4/pull/1616) ([jim-parry](https://github.com/jim-parry)) +- Typo in documentation [\#1613](https://github.com/codeigniter4/CodeIgniter4/pull/1613) ([tpw1314](https://github.com/tpw1314)) +- WIP img fix\(?\) - html\_helper [\#1538](https://github.com/codeigniter4/CodeIgniter4/pull/1538) ([nowackipawel](https://github.com/nowackipawel)) + +## [v4.0.0-alpha.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.5) (2018-12-15) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.4...v4.0.0-alpha.5) + +## [v4.0.0-alpha.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.4) (2018-12-15) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.3...v4.0.0-alpha.4) + +**Implemented enhancements:** + +- WIP Vagrant and/or Docker support [\#1452](https://github.com/codeigniter4/CodeIgniter4/issues/1452) + +**Closed issues:** + +- Custom routing rule not match the User Guide [\#1609](https://github.com/codeigniter4/CodeIgniter4/issues/1609) +- Memory leak - binds not being cleared after find\(\) [\#1604](https://github.com/codeigniter4/CodeIgniter4/issues/1604) +- Soft deletes need parentheses around proceeding query? [\#1592](https://github.com/codeigniter4/CodeIgniter4/issues/1592) +- Cannot use Model first\(\) and update\(\) in table without primary key [\#1583](https://github.com/codeigniter4/CodeIgniter4/issues/1583) +- tried to allocate [\#1578](https://github.com/codeigniter4/CodeIgniter4/issues/1578) +- Database Migrations inconsistent info about sequential type [\#1577](https://github.com/codeigniter4/CodeIgniter4/issues/1577) +- Wrong or not precise documentation of magic \_\_get and \_\_set methods in section Handling Business Logic [\#1568](https://github.com/codeigniter4/CodeIgniter4/issues/1568) +- Entity's fill method is not supporting key mapping [\#1567](https://github.com/codeigniter4/CodeIgniter4/issues/1567) +- Ability to add namespace to FileLocator class [\#1552](https://github.com/codeigniter4/CodeIgniter4/issues/1552) +- Should log file contain plain text database username passwords etc? [\#1542](https://github.com/codeigniter4/CodeIgniter4/issues/1542) +- Issues with redirects - had to use exit to make it work and blank page [\#1501](https://github.com/codeigniter4/CodeIgniter4/issues/1501) +- Use of undefined constant BASEPATH [\#1439](https://github.com/codeigniter4/CodeIgniter4/issues/1439) +- MYSQL : BETWEEN operator loses condition value on JOIN in \(:\) used [\#1403](https://github.com/codeigniter4/CodeIgniter4/issues/1403) +- The problem in catching exceptions [\#1274](https://github.com/codeigniter4/CodeIgniter4/issues/1274) +- TODO Language needs improved locating [\#1262](https://github.com/codeigniter4/CodeIgniter4/issues/1262) +- Email attachment [\#1008](https://github.com/codeigniter4/CodeIgniter4/issues/1008) + +**Merged pull requests:** + +- Alpha.4 release prep [\#1612](https://github.com/codeigniter4/CodeIgniter4/pull/1612) ([jim-parry](https://github.com/jim-parry)) +- Test, fix & enhance Language [\#1610](https://github.com/codeigniter4/CodeIgniter4/pull/1610) ([jim-parry](https://github.com/jim-parry)) +- Note about environment configuration in UG [\#1608](https://github.com/codeigniter4/CodeIgniter4/pull/1608) ([jim-parry](https://github.com/jim-parry)) +- release framework script clean up [\#1606](https://github.com/codeigniter4/CodeIgniter4/pull/1606) ([samsonasik](https://github.com/samsonasik)) +- Flesh out I18n testing [\#1603](https://github.com/codeigniter4/CodeIgniter4/pull/1603) ([jim-parry](https://github.com/jim-parry)) +- Model's first and update didn't work primary key-less tables [\#1602](https://github.com/codeigniter4/CodeIgniter4/pull/1602) ([lonnieezell](https://github.com/lonnieezell)) +- clean up \Config\Services in Common.php [\#1601](https://github.com/codeigniter4/CodeIgniter4/pull/1601) ([samsonasik](https://github.com/samsonasik)) +- admin/starter/composer.json clean up [\#1600](https://github.com/codeigniter4/CodeIgniter4/pull/1600) ([samsonasik](https://github.com/samsonasik)) +- use $defaultGroup as default value for database session DBGroup [\#1598](https://github.com/codeigniter4/CodeIgniter4/pull/1598) ([puschie286](https://github.com/puschie286)) +- Retry handle fatal error via pre\_system [\#1595](https://github.com/codeigniter4/CodeIgniter4/pull/1595) ([samsonasik](https://github.com/samsonasik)) +- Fix Toolbar invalid css [\#1594](https://github.com/codeigniter4/CodeIgniter4/pull/1594) ([puschie286](https://github.com/puschie286)) +- Flesh out the Test package testing [\#1593](https://github.com/codeigniter4/CodeIgniter4/pull/1593) ([jim-parry](https://github.com/jim-parry)) +- Fix Toolbar file loading throw exception [\#1589](https://github.com/codeigniter4/CodeIgniter4/pull/1589) ([puschie286](https://github.com/puschie286)) +- Fix site\_url generate invalid url [\#1588](https://github.com/codeigniter4/CodeIgniter4/pull/1588) ([puschie286](https://github.com/puschie286)) +- Add Language fallback [\#1587](https://github.com/codeigniter4/CodeIgniter4/pull/1587) ([natanfelles](https://github.com/natanfelles)) +- Fix model namespace in tutorial [\#1586](https://github.com/codeigniter4/CodeIgniter4/pull/1586) ([jim-parry](https://github.com/jim-parry)) +- Type hint MigrationRunner methods [\#1585](https://github.com/codeigniter4/CodeIgniter4/pull/1585) ([natanfelles](https://github.com/natanfelles)) +- Fix changelog index & common functions UG indent [\#1582](https://github.com/codeigniter4/CodeIgniter4/pull/1582) ([jim-parry](https://github.com/jim-parry)) +- ContentSecurityPolicy testing & enhancement [\#1581](https://github.com/codeigniter4/CodeIgniter4/pull/1581) ([jim-parry](https://github.com/jim-parry)) +- Use Absolute Paths [\#1579](https://github.com/codeigniter4/CodeIgniter4/pull/1579) ([natanfelles](https://github.com/natanfelles)) +- Testing13/http [\#1576](https://github.com/codeigniter4/CodeIgniter4/pull/1576) ([jim-parry](https://github.com/jim-parry)) +- Adds ?integer, ?double, ?string, etc. cast types :\) [\#1575](https://github.com/codeigniter4/CodeIgniter4/pull/1575) ([nowackipawel](https://github.com/nowackipawel)) +- Lessons learned [\#1573](https://github.com/codeigniter4/CodeIgniter4/pull/1573) ([jim-parry](https://github.com/jim-parry)) +- Toolbar updates [\#1571](https://github.com/codeigniter4/CodeIgniter4/pull/1571) ([natanfelles](https://github.com/natanfelles)) +- Test esc\(\) with different encodings and ignore app-only helpers [\#1569](https://github.com/codeigniter4/CodeIgniter4/pull/1569) ([natanfelles](https://github.com/natanfelles)) +- id attribute support added for csrf\_field [\#1563](https://github.com/codeigniter4/CodeIgniter4/pull/1563) ([nowackipawel](https://github.com/nowackipawel)) +- Integrates Autoloader and FileLocator [\#1562](https://github.com/codeigniter4/CodeIgniter4/pull/1562) ([natanfelles](https://github.com/natanfelles)) +- Update Connection.php [\#1561](https://github.com/codeigniter4/CodeIgniter4/pull/1561) ([nowackipawel](https://github.com/nowackipawel)) +- remove \ prefix on use statements [\#1557](https://github.com/codeigniter4/CodeIgniter4/pull/1557) ([samsonasik](https://github.com/samsonasik)) +- using protected intead of public modifier for setUp\(\) function in tests [\#1556](https://github.com/codeigniter4/CodeIgniter4/pull/1556) ([samsonasik](https://github.com/samsonasik)) +- autoload clean up: remove Psr\Log namespace from composer.json [\#1555](https://github.com/codeigniter4/CodeIgniter4/pull/1555) ([samsonasik](https://github.com/samsonasik)) +- remove manual define "system/" directory prefix at ComposerScripts [\#1551](https://github.com/codeigniter4/CodeIgniter4/pull/1551) ([samsonasik](https://github.com/samsonasik)) +- allows to set empty html attr [\#1548](https://github.com/codeigniter4/CodeIgniter4/pull/1548) ([nowackipawel](https://github.com/nowackipawel)) +- Add Vagrantfile [\#1459](https://github.com/codeigniter4/CodeIgniter4/pull/1459) ([natanfelles](https://github.com/natanfelles)) + +## [v4.0.0-alpha.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.3) (2018-11-30) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.2...v4.0.0-alpha.3) + +**Implemented enhancements:** + +- Events should pass it's arguments by reference [\#1298](https://github.com/codeigniter4/CodeIgniter4/issues/1298) +- Feature request. Small but useful. Entity class. [\#1176](https://github.com/codeigniter4/CodeIgniter4/issues/1176) + +**Fixed bugs:** + +- \Config\Database::connect returns unexpected result for custom config values [\#1533](https://github.com/codeigniter4/CodeIgniter4/issues/1533) +- Old\(\) - Seems to have an issue with retrieving array values [\#1492](https://github.com/codeigniter4/CodeIgniter4/issues/1492) +- Language is not merging with modules [\#1433](https://github.com/codeigniter4/CodeIgniter4/issues/1433) +- RedirectResponse does not set cookies [\#1393](https://github.com/codeigniter4/CodeIgniter4/issues/1393) +- ThrottleTest intermittent failure [\#1382](https://github.com/codeigniter4/CodeIgniter4/issues/1382) +- Support replacer functions on validations [\#1363](https://github.com/codeigniter4/CodeIgniter4/issues/1363) +- Filter on route group only attach / [\#1247](https://github.com/codeigniter4/CodeIgniter4/issues/1247) +- TypeError in MemcachedHandler::\_\_construct\(\) [\#1204](https://github.com/codeigniter4/CodeIgniter4/issues/1204) +- Required\_without validation rule [\#1007](https://github.com/codeigniter4/CodeIgniter4/issues/1007) +- Division by zero - fresh install [\#979](https://github.com/codeigniter4/CodeIgniter4/issues/979) + +**Closed issues:** + +- need change file\_exists to is\_file? [\#1543](https://github.com/codeigniter4/CodeIgniter4/issues/1543) +- Docs : some links are broken due to lack of prefix CodeIgniter4 [\#1537](https://github.com/codeigniter4/CodeIgniter4/issues/1537) +- Spelling mistake /wrong function reference in documentation "Handling Business Logic" [\#1535](https://github.com/codeigniter4/CodeIgniter4/issues/1535) +- Sample code in Entity documentation contains useless statement [\#1534](https://github.com/codeigniter4/CodeIgniter4/issues/1534) +- Model events why is there no beforeFind? [\#1527](https://github.com/codeigniter4/CodeIgniter4/issues/1527) +- \[Documentation\] Bug in code example for "Validating $\_POST data" [\#1520](https://github.com/codeigniter4/CodeIgniter4/issues/1520) +- Before filters with sessions break php spark serve [\#1519](https://github.com/codeigniter4/CodeIgniter4/issues/1519) +- Missing documentation for placeholders in validation errors [\#1503](https://github.com/codeigniter4/CodeIgniter4/issues/1503) +- no $baseURL set will be notice "The baseURL value must be set" [\#1476](https://github.com/codeigniter4/CodeIgniter4/issues/1476) +- $field parameter as string at Forge::addField [\#1474](https://github.com/codeigniter4/CodeIgniter4/issues/1474) +- FeatureTestCaseTest still broken [\#1446](https://github.com/codeigniter4/CodeIgniter4/issues/1446) +- Unit test output not captured [\#1435](https://github.com/codeigniter4/CodeIgniter4/issues/1435) +- Response setJSON body \$key set value [\#1522](https://github.com/codeigniter4/CodeIgniter4/pull/1522) ([samsonasik](https://github.com/samsonasik)) +- .gitignore clean up [\#1521](https://github.com/codeigniter4/CodeIgniter4/pull/1521) ([samsonasik](https://github.com/samsonasik)) +- Small typo: changed setCreatedOn to setCreatedAt [\#1518](https://github.com/codeigniter4/CodeIgniter4/pull/1518) ([obozdag](https://github.com/obozdag)) +- move .htaccess from per-directory in writable/{directory} to writable/ [\#1517](https://github.com/codeigniter4/CodeIgniter4/pull/1517) ([samsonasik](https://github.com/samsonasik)) +- More secure redirection [\#1513](https://github.com/codeigniter4/CodeIgniter4/pull/1513) ([jim-parry](https://github.com/jim-parry)) +- remove unused use statements [\#1509](https://github.com/codeigniter4/CodeIgniter4/pull/1509) ([samsonasik](https://github.com/samsonasik)) +- remove duplicate strtolower\(\) call in URI::setScheme\(\) call [\#1508](https://github.com/codeigniter4/CodeIgniter4/pull/1508) ([samsonasik](https://github.com/samsonasik)) +- Fix multi "empty" string separated by "," marked as valid emails [\#1507](https://github.com/codeigniter4/CodeIgniter4/pull/1507) ([samsonasik](https://github.com/samsonasik)) +- Flesh out HTTP/File unit testing [\#1506](https://github.com/codeigniter4/CodeIgniter4/pull/1506) ([jim-parry](https://github.com/jim-parry)) +- Do not exit until all Response is completed [\#1505](https://github.com/codeigniter4/CodeIgniter4/pull/1505) ([natanfelles](https://github.com/natanfelles)) +- Revert RedirectResponse changes [\#1504](https://github.com/codeigniter4/CodeIgniter4/pull/1504) ([jim-parry](https://github.com/jim-parry)) +- Revert to buggy oldInput [\#1502](https://github.com/codeigniter4/CodeIgniter4/pull/1502) ([jim-parry](https://github.com/jim-parry)) +- Ignoring errors suppressed by @ [\#1500](https://github.com/codeigniter4/CodeIgniter4/pull/1500) ([samsonasik](https://github.com/samsonasik)) +- Fix form\_helper's set\_value writeup [\#1499](https://github.com/codeigniter4/CodeIgniter4/pull/1499) ([jim-parry](https://github.com/jim-parry)) +- Add CURLRequest helper methods [\#1498](https://github.com/codeigniter4/CodeIgniter4/pull/1498) ([natanfelles](https://github.com/natanfelles)) +- Remove unused RedirectException and add some PHPDocs [\#1497](https://github.com/codeigniter4/CodeIgniter4/pull/1497) ([natanfelles](https://github.com/natanfelles)) +- Fix Common::old\(\) [\#1496](https://github.com/codeigniter4/CodeIgniter4/pull/1496) ([jim-parry](https://github.com/jim-parry)) +- Add URI segment test [\#1495](https://github.com/codeigniter4/CodeIgniter4/pull/1495) ([natanfelles](https://github.com/natanfelles)) +- Method naming [\#1494](https://github.com/codeigniter4/CodeIgniter4/pull/1494) ([ghost](https://github.com/ghost)) +- Error logging [\#1491](https://github.com/codeigniter4/CodeIgniter4/pull/1491) ([jim-parry](https://github.com/jim-parry)) +- Changelog\(s\) restructure [\#1490](https://github.com/codeigniter4/CodeIgniter4/pull/1490) ([jim-parry](https://github.com/jim-parry)) +- Add CLI::strlen\(\) [\#1489](https://github.com/codeigniter4/CodeIgniter4/pull/1489) ([natanfelles](https://github.com/natanfelles)) +- Load Language strings from other locations [\#1488](https://github.com/codeigniter4/CodeIgniter4/pull/1488) ([natanfelles](https://github.com/natanfelles)) +- Test RedirectResponse problem report [\#1486](https://github.com/codeigniter4/CodeIgniter4/pull/1486) ([jim-parry](https://github.com/jim-parry)) +- missing slash [\#1484](https://github.com/codeigniter4/CodeIgniter4/pull/1484) ([titounnes](https://github.com/titounnes)) +- Small typo in Session\Handlers\BaseHandler.php [\#1483](https://github.com/codeigniter4/CodeIgniter4/pull/1483) ([obozdag](https://github.com/obozdag)) +- doc fix: query binding fix in Seeds documentation [\#1482](https://github.com/codeigniter4/CodeIgniter4/pull/1482) ([samsonasik](https://github.com/samsonasik)) +- RedisHandler test clean up: remove unneeded 2nd parameter in \_\_construct [\#1481](https://github.com/codeigniter4/CodeIgniter4/pull/1481) ([samsonasik](https://github.com/samsonasik)) +- Fix Language Key-File confusion [\#1480](https://github.com/codeigniter4/CodeIgniter4/pull/1480) ([puschie286](https://github.com/puschie286)) +- Yet another time test to fix [\#1479](https://github.com/codeigniter4/CodeIgniter4/pull/1479) ([jim-parry](https://github.com/jim-parry)) +- Add Response send testing [\#1477](https://github.com/codeigniter4/CodeIgniter4/pull/1477) ([jim-parry](https://github.com/jim-parry)) +- Correct phpdocs for Forge::addField\(\) [\#1475](https://github.com/codeigniter4/CodeIgniter4/pull/1475) ([jim-parry](https://github.com/jim-parry)) +- Fuzzify another time test [\#1473](https://github.com/codeigniter4/CodeIgniter4/pull/1473) ([jim-parry](https://github.com/jim-parry)) +- HTTP\Response cookie testing & missing functionality [\#1472](https://github.com/codeigniter4/CodeIgniter4/pull/1472) ([jim-parry](https://github.com/jim-parry)) +- remove unused local variable $result in XMLFormatter::format\(\) [\#1471](https://github.com/codeigniter4/CodeIgniter4/pull/1471) ([samsonasik](https://github.com/samsonasik)) +- Allow create table with array field constraints [\#1470](https://github.com/codeigniter4/CodeIgniter4/pull/1470) ([natanfelles](https://github.com/natanfelles)) +- use static:: instead of self:: for call protected/public functions as well [\#1469](https://github.com/codeigniter4/CodeIgniter4/pull/1469) ([samsonasik](https://github.com/samsonasik)) +- Fix FeatureTestCaseTest output buffer [\#1468](https://github.com/codeigniter4/CodeIgniter4/pull/1468) ([puschie286](https://github.com/puschie286)) +- Provide time testing within tolerance [\#1467](https://github.com/codeigniter4/CodeIgniter4/pull/1467) ([jim-parry](https://github.com/jim-parry)) +- Fix phpdocs for BaseBuilder [\#1466](https://github.com/codeigniter4/CodeIgniter4/pull/1466) ([jim-parry](https://github.com/jim-parry)) +- use static:: instead of self:: for protected and public properties [\#1465](https://github.com/codeigniter4/CodeIgniter4/pull/1465) ([samsonasik](https://github.com/samsonasik)) +- remove unused use statements [\#1464](https://github.com/codeigniter4/CodeIgniter4/pull/1464) ([samsonasik](https://github.com/samsonasik)) +- Fix the remaining bcit-ci references [\#1463](https://github.com/codeigniter4/CodeIgniter4/pull/1463) ([jim-parry](https://github.com/jim-parry)) +- Typo fix: donload -\> download [\#1461](https://github.com/codeigniter4/CodeIgniter4/pull/1461) ([samsonasik](https://github.com/samsonasik)) +- remove unneeded ternary check at HoneyPot::hasContent\(\) [\#1460](https://github.com/codeigniter4/CodeIgniter4/pull/1460) ([samsonasik](https://github.com/samsonasik)) +- WIP use $paths-\>systemDirectory in public/index.php [\#1457](https://github.com/codeigniter4/CodeIgniter4/pull/1457) ([samsonasik](https://github.com/samsonasik)) +- Beef up HTTP URI & Response testing [\#1456](https://github.com/codeigniter4/CodeIgniter4/pull/1456) ([jim-parry](https://github.com/jim-parry)) +- WIP un-ignore application/Database/Migrations directory from .gitignore [\#1455](https://github.com/codeigniter4/CodeIgniter4/pull/1455) ([samsonasik](https://github.com/samsonasik)) +- add missing break; in loop at Email::getEncoding\(\) [\#1454](https://github.com/codeigniter4/CodeIgniter4/pull/1454) ([samsonasik](https://github.com/samsonasik)) +- BugFix if there extension has only one mime type \(string\) [\#1453](https://github.com/codeigniter4/CodeIgniter4/pull/1453) ([nowackipawel](https://github.com/nowackipawel)) +- remove unneeded $session-\>start\(\); check on RedirectResponse::ensureSession\(\) [\#1451](https://github.com/codeigniter4/CodeIgniter4/pull/1451) ([samsonasik](https://github.com/samsonasik)) +- phpcbf: fix all at once [\#1450](https://github.com/codeigniter4/CodeIgniter4/pull/1450) ([natanfelles](https://github.com/natanfelles)) +- Simplify how to get indexData from mysql/mariadb [\#1449](https://github.com/codeigniter4/CodeIgniter4/pull/1449) ([natanfelles](https://github.com/natanfelles)) +- documentation: add missing application structures: Database, Filters, ThirdParty directory [\#1448](https://github.com/codeigniter4/CodeIgniter4/pull/1448) ([samsonasik](https://github.com/samsonasik)) +- add missing break; on loop cards to get card info at CreditCardRules::valid\_cc\_number\(\) [\#1447](https://github.com/codeigniter4/CodeIgniter4/pull/1447) ([samsonasik](https://github.com/samsonasik)) +- using existing is\_cli\(\) function in HTTP\IncomingRequest::isCLI\(\) [\#1445](https://github.com/codeigniter4/CodeIgniter4/pull/1445) ([samsonasik](https://github.com/samsonasik)) +- Dox for reorganized repo admin \(4of4\) [\#1444](https://github.com/codeigniter4/CodeIgniter4/pull/1444) ([jim-parry](https://github.com/jim-parry)) +- Fixes \#1435 : unit test output not captured [\#1443](https://github.com/codeigniter4/CodeIgniter4/pull/1443) ([samsonasik](https://github.com/samsonasik)) +- remove form view in application/View/ and form helper usage in create new items tutorial [\#1442](https://github.com/codeigniter4/CodeIgniter4/pull/1442) ([samsonasik](https://github.com/samsonasik)) +- Access to model's last inserted ID [\#1440](https://github.com/codeigniter4/CodeIgniter4/pull/1440) ([nowackipawel](https://github.com/nowackipawel)) +- Tailor the last few repo org names \(3of4\) [\#1438](https://github.com/codeigniter4/CodeIgniter4/pull/1438) ([jim-parry](https://github.com/jim-parry)) +- Replace repo org name in MOST php docs \(2 of 4\) [\#1437](https://github.com/codeigniter4/CodeIgniter4/pull/1437) ([jim-parry](https://github.com/jim-parry)) +- Change github organization name in docs \(1of4\) [\#1436](https://github.com/codeigniter4/CodeIgniter4/pull/1436) ([jim-parry](https://github.com/jim-parry)) +- Use mb\_strlen to get length of columns [\#1432](https://github.com/codeigniter4/CodeIgniter4/pull/1432) ([natanfelles](https://github.com/natanfelles)) +- can't call run\(\) method with params from commands migrations. [\#1431](https://github.com/codeigniter4/CodeIgniter4/pull/1431) ([bangbangda](https://github.com/bangbangda)) +- performance improvement in Database\BaseResult to use truthy check instead of count\($var\) when possible [\#1426](https://github.com/codeigniter4/CodeIgniter4/pull/1426) ([samsonasik](https://github.com/samsonasik)) +- Ensure FileHandlerTest uses MockFileHandler [\#1425](https://github.com/codeigniter4/CodeIgniter4/pull/1425) ([jim-parry](https://github.com/jim-parry)) +- Fix FileMovingTest leaving cruft [\#1424](https://github.com/codeigniter4/CodeIgniter4/pull/1424) ([jim-parry](https://github.com/jim-parry)) +- Fix Controller use validate bug Fixes \#1419 [\#1423](https://github.com/codeigniter4/CodeIgniter4/pull/1423) ([bangbangda](https://github.com/bangbangda)) +- normalize composer.json [\#1418](https://github.com/codeigniter4/CodeIgniter4/pull/1418) ([samsonasik](https://github.com/samsonasik)) +- add php 7.3 to travis config [\#1394](https://github.com/codeigniter4/CodeIgniter4/pull/1394) ([samsonasik](https://github.com/samsonasik)) +- Add Header Link Pagination [\#622](https://github.com/codeigniter4/CodeIgniter4/pull/622) ([natanfelles](https://github.com/natanfelles)) + +## [v4.0.0-alpha.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.2) (2018-10-26) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.0-alpha.1...v4.0.0-alpha.2) + +**Implemented enhancements:** + +- Filter in the router [\#1315](https://github.com/codeigniter4/CodeIgniter4/issues/1315) +- Making Views path changeable [\#1296](https://github.com/codeigniter4/CodeIgniter4/issues/1296) + +**Fixed bugs:** + +- Error in user guide for session config [\#1330](https://github.com/codeigniter4/CodeIgniter4/issues/1330) +- Route in the News Tutorial Routes are ERROR all over. [\#1240](https://github.com/codeigniter4/CodeIgniter4/issues/1240) +- Time testing in travis-ci wonky [\#1229](https://github.com/codeigniter4/CodeIgniter4/issues/1229) + +**Closed issues:** + +- CLI CommandRunner is trying to instantiate abstract classes [\#1349](https://github.com/codeigniter4/CodeIgniter4/issues/1349) +- redirect\(\)-\> problem [\#1346](https://github.com/codeigniter4/CodeIgniter4/issues/1346) +- Question new form validation rule [\#1332](https://github.com/codeigniter4/CodeIgniter4/issues/1332) +- Download Response Not working [\#1331](https://github.com/codeigniter4/CodeIgniter4/issues/1331) +- Incorrect Error Message ? [\#1328](https://github.com/codeigniter4/CodeIgniter4/issues/1328) +- Wrong variable reference [\#1324](https://github.com/codeigniter4/CodeIgniter4/issues/1324) +- Model Pagination: Problem with total rows [\#1318](https://github.com/codeigniter4/CodeIgniter4/issues/1318) +- Should display an exception when minimum PHP version not met. [\#1307](https://github.com/codeigniter4/CodeIgniter4/issues/1307) +- News Tutorial Error on form submit [\#1301](https://github.com/codeigniter4/CodeIgniter4/issues/1301) +- Small Typo Correction [\#1299](https://github.com/codeigniter4/CodeIgniter4/issues/1299) +- Making config variable global as CI3 [\#1297](https://github.com/codeigniter4/CodeIgniter4/issues/1297) +- Config files aren't discovered automatically when using the config\(\) function. [\#1293](https://github.com/codeigniter4/CodeIgniter4/issues/1293) +- News Tutorial post riute still not working [\#1292](https://github.com/codeigniter4/CodeIgniter4/issues/1292) +- Form Validation [\#1290](https://github.com/codeigniter4/CodeIgniter4/issues/1290) +- News Tutorial Routes [\#1288](https://github.com/codeigniter4/CodeIgniter4/issues/1288) +- I want to separate responsibility of Cast from Entity. [\#1287](https://github.com/codeigniter4/CodeIgniter4/issues/1287) +- error 404 in routing with controller in subdirectories [\#1276](https://github.com/codeigniter4/CodeIgniter4/issues/1276) +- TODO Extending helpers [\#1264](https://github.com/codeigniter4/CodeIgniter4/issues/1264) +- TODO MockResponse needs cookies solution [\#1263](https://github.com/codeigniter4/CodeIgniter4/issues/1263) +- TODO url\_helper needs fixing [\#1260](https://github.com/codeigniter4/CodeIgniter4/issues/1260) +- TODO FileLocator better path checking [\#1252](https://github.com/codeigniter4/CodeIgniter4/issues/1252) +- TODO FileLocator filename sanitizing [\#1251](https://github.com/codeigniter4/CodeIgniter4/issues/1251) +- BUG in form\_hidden with associative array [\#1244](https://github.com/codeigniter4/CodeIgniter4/issues/1244) +- Save entity after selected find results in null data [\#1234](https://github.com/codeigniter4/CodeIgniter4/issues/1234) +- System/Database/Database.php::loadForge returns Connection when using custom DBDriver [\#1225](https://github.com/codeigniter4/CodeIgniter4/issues/1225) + +**Merged pull requests:** + +- Add timing assertion to CIUnitTestCase [\#1361](https://github.com/codeigniter4/CodeIgniter4/pull/1361) ([jim-parry](https://github.com/jim-parry)) +- Testing/commands [\#1356](https://github.com/codeigniter4/CodeIgniter4/pull/1356) ([jim-parry](https://github.com/jim-parry)) +- Handle duplicate HTTP verb and generic rules properly [\#1355](https://github.com/codeigniter4/CodeIgniter4/pull/1355) ([jim-parry](https://github.com/jim-parry)) +- Refresh changelog [\#1352](https://github.com/codeigniter4/CodeIgniter4/pull/1352) ([jim-parry](https://github.com/jim-parry)) +- Checks if class is instantiable and is a command [\#1350](https://github.com/codeigniter4/CodeIgniter4/pull/1350) ([natanfelles](https://github.com/natanfelles)) +- Fix sphinx formatting in sessions [\#1348](https://github.com/codeigniter4/CodeIgniter4/pull/1348) ([jim-parry](https://github.com/jim-parry)) +- Fix sphinx formatting in sessions [\#1347](https://github.com/codeigniter4/CodeIgniter4/pull/1347) ([jim-parry](https://github.com/jim-parry)) +- Toolbar Styles [\#1342](https://github.com/codeigniter4/CodeIgniter4/pull/1342) ([lonnieezell](https://github.com/lonnieezell)) +- Make viewpath configurable in Paths.php. Fixes \#1296 [\#1341](https://github.com/codeigniter4/CodeIgniter4/pull/1341) ([lonnieezell](https://github.com/lonnieezell)) +- Update docs for downloads to reflect the need to return it. Fixes \#1331 [\#1340](https://github.com/codeigniter4/CodeIgniter4/pull/1340) ([lonnieezell](https://github.com/lonnieezell)) +- Fix error where Forge class might not be returned. Fixes \#1225 [\#1339](https://github.com/codeigniter4/CodeIgniter4/pull/1339) ([lonnieezell](https://github.com/lonnieezell)) +- Filter in the router Fixes \#1315 [\#1337](https://github.com/codeigniter4/CodeIgniter4/pull/1337) ([bangbangda](https://github.com/bangbangda)) +- Revert alpha.2 [\#1336](https://github.com/codeigniter4/CodeIgniter4/pull/1336) ([jim-parry](https://github.com/jim-parry)) +- Proposed changelog for alpha.2 [\#1334](https://github.com/codeigniter4/CodeIgniter4/pull/1334) ([jim-parry](https://github.com/jim-parry)) +- Error in user guide for session config. Fixes \#1330 [\#1333](https://github.com/codeigniter4/CodeIgniter4/pull/1333) ([bangbangda](https://github.com/bangbangda)) +- Tweaks [\#1329](https://github.com/codeigniter4/CodeIgniter4/pull/1329) ([lonnieezell](https://github.com/lonnieezell)) +- FIX form\_hidden and form\_open - value escaping as is in form\_input. [\#1327](https://github.com/codeigniter4/CodeIgniter4/pull/1327) ([nowackipawel](https://github.com/nowackipawel)) +- Fix doc error : show\_404\(\) doesn't exist any more [\#1323](https://github.com/codeigniter4/CodeIgniter4/pull/1323) ([bvrignaud](https://github.com/bvrignaud)) +- Added missing xml\_helper UG page [\#1321](https://github.com/codeigniter4/CodeIgniter4/pull/1321) ([jim-parry](https://github.com/jim-parry)) +- Testing/entity [\#1319](https://github.com/codeigniter4/CodeIgniter4/pull/1319) ([jim-parry](https://github.com/jim-parry)) +- Refactor TimeTest [\#1316](https://github.com/codeigniter4/CodeIgniter4/pull/1316) ([jim-parry](https://github.com/jim-parry)) +- Fix & expand Honeypot & its tests [\#1314](https://github.com/codeigniter4/CodeIgniter4/pull/1314) ([jim-parry](https://github.com/jim-parry)) +- Clean exception [\#1313](https://github.com/codeigniter4/CodeIgniter4/pull/1313) ([lonnieezell](https://github.com/lonnieezell)) +- Add headerEmited \(or not\) assertions to CIUnitTestCase [\#1312](https://github.com/codeigniter4/CodeIgniter4/pull/1312) ([jim-parry](https://github.com/jim-parry)) +- Entities store an original stack of values to compare against so we d… [\#1311](https://github.com/codeigniter4/CodeIgniter4/pull/1311) ([lonnieezell](https://github.com/lonnieezell)) +- Testing3/http [\#1306](https://github.com/codeigniter4/CodeIgniter4/pull/1306) ([jim-parry](https://github.com/jim-parry)) +- Change chdir\('public'\) to chdir\($public\) [\#1305](https://github.com/codeigniter4/CodeIgniter4/pull/1305) ([titounnes](https://github.com/titounnes)) +- Refactor script name stripping in parseRequestURI\(\) [\#1304](https://github.com/codeigniter4/CodeIgniter4/pull/1304) ([jim-parry](https://github.com/jim-parry)) +- Testing/http [\#1303](https://github.com/codeigniter4/CodeIgniter4/pull/1303) ([jim-parry](https://github.com/jim-parry)) +- Exception:No Formatter defined for mime type '' [\#1302](https://github.com/codeigniter4/CodeIgniter4/pull/1302) ([bangbangda](https://github.com/bangbangda)) +- Allow redirect with Query Vars from the current request. [\#1300](https://github.com/codeigniter4/CodeIgniter4/pull/1300) ([lonnieezell](https://github.com/lonnieezell)) +- Fix grammar in front controller comment. [\#1295](https://github.com/codeigniter4/CodeIgniter4/pull/1295) ([mdwheele](https://github.com/mdwheele)) +- Updated final tutorial page. Fixes \#1292 [\#1294](https://github.com/codeigniter4/CodeIgniter4/pull/1294) ([lonnieezell](https://github.com/lonnieezell)) +- Allows extending of helpers. Fixes \#1264 [\#1291](https://github.com/codeigniter4/CodeIgniter4/pull/1291) ([lonnieezell](https://github.com/lonnieezell)) +- Cookies [\#1286](https://github.com/codeigniter4/CodeIgniter4/pull/1286) ([lonnieezell](https://github.com/lonnieezell)) +- Ensure current HTTP verb routes are matched prior to any \* matched ro… [\#1285](https://github.com/codeigniter4/CodeIgniter4/pull/1285) ([lonnieezell](https://github.com/lonnieezell)) +- Entities [\#1283](https://github.com/codeigniter4/CodeIgniter4/pull/1283) ([lonnieezell](https://github.com/lonnieezell)) +- system/Test/FeatureTestCase::setupRequest\(\), minor fixes phpdoc block… [\#1282](https://github.com/codeigniter4/CodeIgniter4/pull/1282) ([fmertins](https://github.com/fmertins)) +- Tut [\#1281](https://github.com/codeigniter4/CodeIgniter4/pull/1281) ([lonnieezell](https://github.com/lonnieezell)) +- Add contributing reference to user guide [\#1280](https://github.com/codeigniter4/CodeIgniter4/pull/1280) ([jim-parry](https://github.com/jim-parry)) +- Fix/timing [\#1273](https://github.com/codeigniter4/CodeIgniter4/pull/1273) ([jim-parry](https://github.com/jim-parry)) +- Fix undefined variable "heading" in cli 404 [\#1272](https://github.com/codeigniter4/CodeIgniter4/pull/1272) ([samsonasik](https://github.com/samsonasik)) +- remove inexistent "CodeIgniter\Loader" from AutoloadConfig::classmap [\#1271](https://github.com/codeigniter4/CodeIgniter4/pull/1271) ([samsonasik](https://github.com/samsonasik)) +- Release notes & process [\#1269](https://github.com/codeigniter4/CodeIgniter4/pull/1269) ([jim-parry](https://github.com/jim-parry)) +- Fix \#1244 \(form\_hidden declaration\) [\#1245](https://github.com/codeigniter4/CodeIgniter4/pull/1245) ([bvrignaud](https://github.com/bvrignaud)) +- 【Unsolicited PR】I changed the download method to testable. [\#1239](https://github.com/codeigniter4/CodeIgniter4/pull/1239) ([ytetsuro](https://github.com/ytetsuro)) +- Optional parameter for resetSelect\(\) call in Builder's countAll\(\); [\#1217](https://github.com/codeigniter4/CodeIgniter4/pull/1217) ([nowackipawel](https://github.com/nowackipawel)) +- Fix undefined function xml\_convert at Database\BaseUtils [\#1209](https://github.com/codeigniter4/CodeIgniter4/pull/1209) ([samsonasik](https://github.com/samsonasik)) + +## [v4.0.0-alpha.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.0-alpha.1) (2018-09-29) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/0d92381e74730331626b49e6f259d9073727c4f1...v4.0.0-alpha.1) + +**Implemented enhancements:** + +- Parser plugins should be allowed to have non-paired directives [\#547](https://github.com/codeigniter4/CodeIgniter4/issues/547) +- Modify View Parser to support quoted phrases [\#537](https://github.com/codeigniter4/CodeIgniter4/issues/537) +- Move Formatting functionality from the API namespace to it's own namespace [\#451](https://github.com/codeigniter4/CodeIgniter4/issues/451) +- Add method spoofing for forms. [\#432](https://github.com/codeigniter4/CodeIgniter4/issues/432) +- Add overwrite option to UploadedFile-\>move\(\) [\#275](https://github.com/codeigniter4/CodeIgniter4/issues/275) +- Add Download method to Response object. [\#208](https://github.com/codeigniter4/CodeIgniter4/issues/208) +- Cache/Session fallback to predis/predis composer package if phpredis not available [\#192](https://github.com/codeigniter4/CodeIgniter4/issues/192) +- View: saveData [\#181](https://github.com/codeigniter4/CodeIgniter4/issues/181) +- url\_helper implementation [\#157](https://github.com/codeigniter4/CodeIgniter4/issues/157) +- Add true 'prepare' functionality to Queries [\#131](https://github.com/codeigniter4/CodeIgniter4/issues/131) +- Add :hash placeholder for URI parameters [\#130](https://github.com/codeigniter4/CodeIgniter4/issues/130) +- \[URI\] Provide ability for query vars manipulation [\#119](https://github.com/codeigniter4/CodeIgniter4/issues/119) +- \[FilesCollection\] Allow getFiles with dot syntax [\#112](https://github.com/codeigniter4/CodeIgniter4/issues/112) +- Database to fire events [\#105](https://github.com/codeigniter4/CodeIgniter4/issues/105) +- New Toolbar Collector for Events [\#84](https://github.com/codeigniter4/CodeIgniter4/issues/84) +- Debug Toolbar Enhancements [\#83](https://github.com/codeigniter4/CodeIgniter4/issues/83) +- Add indexing functions to Forge [\#65](https://github.com/codeigniter4/CodeIgniter4/issues/65) +- Refactor DB Backup to stream to file instead of holding in memory [\#64](https://github.com/codeigniter4/CodeIgniter4/issues/64) +- Add support for foreign keys to the Forge [\#63](https://github.com/codeigniter4/CodeIgniter4/issues/63) +- Headers must support multiple headers with same name [\#16](https://github.com/codeigniter4/CodeIgniter4/issues/16) +- Add 'secure' option for Routes [\#10](https://github.com/codeigniter4/CodeIgniter4/issues/10) +- \[Test Helper\] assertLogged [\#9](https://github.com/codeigniter4/CodeIgniter4/issues/9) +- Additional Logger Enhancements [\#8](https://github.com/codeigniter4/CodeIgniter4/issues/8) +- Implement Content Secure Policy [\#6](https://github.com/codeigniter4/CodeIgniter4/issues/6) +- Replace our escaper with Zend Escaper [\#5](https://github.com/codeigniter4/CodeIgniter4/issues/5) +- Redirect Security [\#4](https://github.com/codeigniter4/CodeIgniter4/issues/4) +- Reverse Routing [\#3](https://github.com/codeigniter4/CodeIgniter4/issues/3) + +**Fixed bugs:** + +- Testing output buffer not closed [\#1230](https://github.com/codeigniter4/CodeIgniter4/issues/1230) +- XML formater xmltoarray indexed array incorrect [\#577](https://github.com/codeigniter4/CodeIgniter4/issues/577) +- stringify\_attributes method MUST escape the values [\#282](https://github.com/codeigniter4/CodeIgniter4/issues/282) +- base\_url\(\) function doesn't work properly when it used on page with uri segments [\#240](https://github.com/codeigniter4/CodeIgniter4/issues/240) +- mysqli update bug [\#229](https://github.com/codeigniter4/CodeIgniter4/issues/229) +- database update [\#201](https://github.com/codeigniter4/CodeIgniter4/issues/201) +- Paginating Multiple Results - user Guides [\#196](https://github.com/codeigniter4/CodeIgniter4/issues/196) +- 'Filters' Bug [\#188](https://github.com/codeigniter4/CodeIgniter4/issues/188) +- POST and debugbar [\#172](https://github.com/codeigniter4/CodeIgniter4/issues/172) +- url\_helper functions don't correctly apply $baseURL [\#155](https://github.com/codeigniter4/CodeIgniter4/issues/155) +- Routes ending in '/' redirect oddly [\#147](https://github.com/codeigniter4/CodeIgniter4/issues/147) +- Error when trying access URI with Global Function's name [\#136](https://github.com/codeigniter4/CodeIgniter4/issues/136) +- CLI problem with progress complete's message [\#135](https://github.com/codeigniter4/CodeIgniter4/issues/135) + +**Closed issues:** + +- validation error [\#1214](https://github.com/codeigniter4/CodeIgniter4/issues/1214) +- How to use Controller own constructer? [\#1208](https://github.com/codeigniter4/CodeIgniter4/issues/1208) +- autoload psr4 is not right. [\#1205](https://github.com/codeigniter4/CodeIgniter4/issues/1205) +- multiple rules in validate\(\) for File Upload not working [\#1201](https://github.com/codeigniter4/CodeIgniter4/issues/1201) +- can't use the same model to update, delete and insert record [\#1193](https://github.com/codeigniter4/CodeIgniter4/issues/1193) +- $myModel-\>find\(string "value-of-my-primary-key"\) [\#1188](https://github.com/codeigniter4/CodeIgniter4/issues/1188) +- Undefined variable: \_SESSION in command php spark [\#1183](https://github.com/codeigniter4/CodeIgniter4/issues/1183) +- Typographical error [\#1179](https://github.com/codeigniter4/CodeIgniter4/issues/1179) +- CSP + .kint d\(foo\) [\#1174](https://github.com/codeigniter4/CodeIgniter4/issues/1174) +- DebugBar -\> Server Error 500 [\#1170](https://github.com/codeigniter4/CodeIgniter4/issues/1170) +- NULL in select is escaped \(mysqli\_sql\_exception\) [\#1169](https://github.com/codeigniter4/CodeIgniter4/issues/1169) +- Routing for "cli" actions. [\#1166](https://github.com/codeigniter4/CodeIgniter4/issues/1166) +- Create Auto-discovery system [\#1161](https://github.com/codeigniter4/CodeIgniter4/issues/1161) +- Routing wrong default value [\#1139](https://github.com/codeigniter4/CodeIgniter4/issues/1139) +- $session-\>push gets wrong [\#1136](https://github.com/codeigniter4/CodeIgniter4/issues/1136) +- Toolbar Oldest files delete bug [\#1135](https://github.com/codeigniter4/CodeIgniter4/issues/1135) +- redirect helper : redirectResponse is ignored [\#1127](https://github.com/codeigniter4/CodeIgniter4/issues/1127) +- redirect to route ignore baseurl [\#1126](https://github.com/codeigniter4/CodeIgniter4/issues/1126) +- redirect-\>route wrong docu or default values [\#1125](https://github.com/codeigniter4/CodeIgniter4/issues/1125) +- \#1109 breaks route setup/uri parsing [\#1114](https://github.com/codeigniter4/CodeIgniter4/issues/1114) +- \[TESTS\] Session tests for php 7.2 [\#1106](https://github.com/codeigniter4/CodeIgniter4/issues/1106) +- set\_cookie - not working [\#1103](https://github.com/codeigniter4/CodeIgniter4/issues/1103) +- safe\_mailto - not working [\#1102](https://github.com/codeigniter4/CodeIgniter4/issues/1102) +- CLI tool sorting wrong [\#1099](https://github.com/codeigniter4/CodeIgniter4/issues/1099) +- Issues with redirect [\#1098](https://github.com/codeigniter4/CodeIgniter4/issues/1098) +- Can't use validate with regex\_match\[\] [\#1084](https://github.com/codeigniter4/CodeIgniter4/issues/1084) +- problem with redirect\(\) withInput\(\) when validation [\#1081](https://github.com/codeigniter4/CodeIgniter4/issues/1081) +- Redis Handler Fails [\#1079](https://github.com/codeigniter4/CodeIgniter4/issues/1079) +- about cache path [\#1078](https://github.com/codeigniter4/CodeIgniter4/issues/1078) +- validation error [\#1077](https://github.com/codeigniter4/CodeIgniter4/issues/1077) +- \#Request. Features for REST server. [\#1076](https://github.com/codeigniter4/CodeIgniter4/issues/1076) +- Database Migrations [\#1075](https://github.com/codeigniter4/CodeIgniter4/issues/1075) +- Codeigniter/Model - Select Database Table Fields [\#1072](https://github.com/codeigniter4/CodeIgniter4/issues/1072) +- New Config helper [\#1071](https://github.com/codeigniter4/CodeIgniter4/issues/1071) +- HTTP\ResponseTest Language Problem [\#1069](https://github.com/codeigniter4/CodeIgniter4/issues/1069) +- CLI Error [\#1068](https://github.com/codeigniter4/CodeIgniter4/issues/1068) +- Entity \_options dates [\#1061](https://github.com/codeigniter4/CodeIgniter4/issues/1061) +- class Locale not found when using I18n/Time on xampp localhost [\#1059](https://github.com/codeigniter4/CodeIgniter4/issues/1059) +- Cookie not working [\#1057](https://github.com/codeigniter4/CodeIgniter4/issues/1057) +- Where is class 'MessageFormatter' [\#1054](https://github.com/codeigniter4/CodeIgniter4/issues/1054) +- Is CI 4 ready for production, please? [\#1051](https://github.com/codeigniter4/CodeIgniter4/issues/1051) +- Router 404 [\#1050](https://github.com/codeigniter4/CodeIgniter4/issues/1050) +- Toolbar - memory usage [\#1049](https://github.com/codeigniter4/CodeIgniter4/issues/1049) +- Session saving [\#1045](https://github.com/codeigniter4/CodeIgniter4/issues/1045) +- $Email-\>initialize\($config\) not work! [\#1042](https://github.com/codeigniter4/CodeIgniter4/issues/1042) +- Class '\CodeIgniter\Database\postgre\Connection' not found [\#1038](https://github.com/codeigniter4/CodeIgniter4/issues/1038) +- Tutorial controller Pages and file\_exists\(\) case sensitivity [\#1030](https://github.com/codeigniter4/CodeIgniter4/issues/1030) +- options base\_uri not being based from curlrequest client instantiation [\#1029](https://github.com/codeigniter4/CodeIgniter4/issues/1029) +- Missing method in db result [\#1022](https://github.com/codeigniter4/CodeIgniter4/issues/1022) +- Email Config $fromEmail not work [\#1021](https://github.com/codeigniter4/CodeIgniter4/issues/1021) +- Class 'CodeIgniter\PageNotFoundException' not found [\#1016](https://github.com/codeigniter4/CodeIgniter4/issues/1016) +- Redirect glitch [\#1013](https://github.com/codeigniter4/CodeIgniter4/issues/1013) +- CSRF Error [\#1012](https://github.com/codeigniter4/CodeIgniter4/issues/1012) +- Php serv CLI stopped working [\#1006](https://github.com/codeigniter4/CodeIgniter4/issues/1006) +- Unit testing broken in travis-ci [\#1003](https://github.com/codeigniter4/CodeIgniter4/issues/1003) +- empty php\_errors.log file [\#1001](https://github.com/codeigniter4/CodeIgniter4/issues/1001) +- \[Help\] setVar\('body', $view, 'raw'\) [\#1000](https://github.com/codeigniter4/CodeIgniter4/issues/1000) +- It's blank page and set $baseURL [\#999](https://github.com/codeigniter4/CodeIgniter4/issues/999) +- Unable to use another controller'method in one controller? [\#997](https://github.com/codeigniter4/CodeIgniter4/issues/997) +- CodeIgniter\Session\Handlers\FileHandler Class and writable\session Directory not found while using .env [\#994](https://github.com/codeigniter4/CodeIgniter4/issues/994) +- route\_to\(\) function not work if greater than 3 parameters. [\#992](https://github.com/codeigniter4/CodeIgniter4/issues/992) +- Redirect Back [\#991](https://github.com/codeigniter4/CodeIgniter4/issues/991) +- helper method should accept more than one filename.... [\#987](https://github.com/codeigniter4/CodeIgniter4/issues/987) +- New Feature Request - Sub queries using query builder class [\#985](https://github.com/codeigniter4/CodeIgniter4/issues/985) +- MySQL join / missing value for field which was used in join. [\#983](https://github.com/codeigniter4/CodeIgniter4/issues/983) +- Documentation - Session Library - session\(\)-\>start\(\) [\#982](https://github.com/codeigniter4/CodeIgniter4/issues/982) +- Cache with handler file woking incorrect on windown os [\#978](https://github.com/codeigniter4/CodeIgniter4/issues/978) +- Formatter for: "content-type: application/json" [\#977](https://github.com/codeigniter4/CodeIgniter4/issues/977) +- route\_to function working incorrect [\#975](https://github.com/codeigniter4/CodeIgniter4/issues/975) +- Getters and Setters in the Model [\#974](https://github.com/codeigniter4/CodeIgniter4/issues/974) +- About Replacing Core Classes! [\#973](https://github.com/codeigniter4/CodeIgniter4/issues/973) +- CodeIgniter 4 should use PSR-2 [\#972](https://github.com/codeigniter4/CodeIgniter4/issues/972) +- Model Validations and Insert/Update Batchs [\#967](https://github.com/codeigniter4/CodeIgniter4/issues/967) +- function old\(\) not allow input name type array [\#966](https://github.com/codeigniter4/CodeIgniter4/issues/966) +- test [\#963](https://github.com/codeigniter4/CodeIgniter4/issues/963) +- namespace error in centos7 [\#959](https://github.com/codeigniter4/CodeIgniter4/issues/959) +- Session cannot be saved [\#958](https://github.com/codeigniter4/CodeIgniter4/issues/958) +- Model calling Query Builder replace\(\) [\#957](https://github.com/codeigniter4/CodeIgniter4/issues/957) +- gzuncompress\(\): data error [\#956](https://github.com/codeigniter4/CodeIgniter4/issues/956) +- Additional space in a parameter which is a string \(mysql\) [\#955](https://github.com/codeigniter4/CodeIgniter4/issues/955) +- Pager with search queries [\#950](https://github.com/codeigniter4/CodeIgniter4/issues/950) +- IncomingRequest getUserAgent problem only object [\#948](https://github.com/codeigniter4/CodeIgniter4/issues/948) +- url\_helper current\_url https fix. [\#947](https://github.com/codeigniter4/CodeIgniter4/issues/947) +- Validation "matches" rule is not getting the label from the corresponding field [\#946](https://github.com/codeigniter4/CodeIgniter4/issues/946) +- no luck setting up user\_guide [\#944](https://github.com/codeigniter4/CodeIgniter4/issues/944) +- mysqli\_sql\_exception: No such file or directory [\#943](https://github.com/codeigniter4/CodeIgniter4/issues/943) +- Major Security Flaw - Database Credentials get leaked. [\#935](https://github.com/codeigniter4/CodeIgniter4/issues/935) +- Debug Toolbar is not loaded when CSP is enabled [\#934](https://github.com/codeigniter4/CodeIgniter4/issues/934) +- This is a problem with frequent operations [\#933](https://github.com/codeigniter4/CodeIgniter4/issues/933) +- Redirects not working [\#931](https://github.com/codeigniter4/CodeIgniter4/issues/931) +- Model fillPlaceholders\(\) rule reference [\#930](https://github.com/codeigniter4/CodeIgniter4/issues/930) +- session in not working on database [\#925](https://github.com/codeigniter4/CodeIgniter4/issues/925) +- Twig [\#919](https://github.com/codeigniter4/CodeIgniter4/issues/919) +- setDefaultNamespace Sub directories [\#917](https://github.com/codeigniter4/CodeIgniter4/issues/917) +- Load language issue [\#913](https://github.com/codeigniter4/CodeIgniter4/issues/913) +- Find a bug ,CSRFVerify [\#912](https://github.com/codeigniter4/CodeIgniter4/issues/912) +- validation form\_error\(\) [\#911](https://github.com/codeigniter4/CodeIgniter4/issues/911) +- About Filters matching rules [\#908](https://github.com/codeigniter4/CodeIgniter4/issues/908) +- Prepared Query Update problem [\#904](https://github.com/codeigniter4/CodeIgniter4/issues/904) +- Model::first\(\) ambiguous id error [\#903](https://github.com/codeigniter4/CodeIgniter4/issues/903) +- Add Model beforeDelete property [\#902](https://github.com/codeigniter4/CodeIgniter4/issues/902) +- passing form\_validation\(\) errors with redirect\(\) to any view | and include methods for post only by using \[ HTTP verbs in routes \] and Resource route [\#900](https://github.com/codeigniter4/CodeIgniter4/issues/900) +- Database failover error on postgresql [\#899](https://github.com/codeigniter4/CodeIgniter4/issues/899) +- valid\_email validation error [\#898](https://github.com/codeigniter4/CodeIgniter4/issues/898) +- Multi language \( on same page \) support [\#891](https://github.com/codeigniter4/CodeIgniter4/issues/891) +- About environnement error\_reporting [\#889](https://github.com/codeigniter4/CodeIgniter4/issues/889) +- Subfolders for lang\(\) [\#887](https://github.com/codeigniter4/CodeIgniter4/issues/887) +- Urgent issues [\#875](https://github.com/codeigniter4/CodeIgniter4/issues/875) +- remark [\#871](https://github.com/codeigniter4/CodeIgniter4/issues/871) +- Bug fix mysqli transaction function call [\#870](https://github.com/codeigniter4/CodeIgniter4/issues/870) +- Debugbar SSL request javascript problem [\#867](https://github.com/codeigniter4/CodeIgniter4/issues/867) +- Backslashes being escaped in where conditions. [\#866](https://github.com/codeigniter4/CodeIgniter4/issues/866) +- Model::delete method [\#865](https://github.com/codeigniter4/CodeIgniter4/issues/865) +- about SessionHandlerInterface error [\#864](https://github.com/codeigniter4/CodeIgniter4/issues/864) +- Respond Error Pages by Content-Type [\#863](https://github.com/codeigniter4/CodeIgniter4/issues/863) +- When codeigniter 4 will be released ? [\#860](https://github.com/codeigniter4/CodeIgniter4/issues/860) +- Catch in Controllers/Checks.php [\#859](https://github.com/codeigniter4/CodeIgniter4/issues/859) +- View render function LFI\(local arbitray file include\) issue [\#857](https://github.com/codeigniter4/CodeIgniter4/issues/857) +- Suggestion regarding codeigniter 4 [\#856](https://github.com/codeigniter4/CodeIgniter4/issues/856) +- Problem with using Entity class \(Could not execute App\Entities\::\_\_construct\(\)\) [\#855](https://github.com/codeigniter4/CodeIgniter4/issues/855) +- Config Unexpectedly Being Overwritten by $\_ENV variable [\#853](https://github.com/codeigniter4/CodeIgniter4/issues/853) +- Use PHP 7.0 or 7.1 typehints? [\#847](https://github.com/codeigniter4/CodeIgniter4/issues/847) +- There is something wrong with ViewTest::testRenderScrapsDataByDefault [\#846](https://github.com/codeigniter4/CodeIgniter4/issues/846) +- Session in filter [\#840](https://github.com/codeigniter4/CodeIgniter4/issues/840) +- Moved uploaded files doesn't retain new filename. [\#839](https://github.com/codeigniter4/CodeIgniter4/issues/839) +- Unable to use like in where? [\#838](https://github.com/codeigniter4/CodeIgniter4/issues/838) +- Status of the version [\#837](https://github.com/codeigniter4/CodeIgniter4/issues/837) +- when set cache to file, when get a error [\#836](https://github.com/codeigniter4/CodeIgniter4/issues/836) +- Bug toolbar [\#834](https://github.com/codeigniter4/CodeIgniter4/issues/834) +- Table name can not use the alias [\#831](https://github.com/codeigniter4/CodeIgniter4/issues/831) +- Validation - permit\_empty [\#830](https://github.com/codeigniter4/CodeIgniter4/issues/830) +- Unable to install Codeigniter using composer [\#829](https://github.com/codeigniter4/CodeIgniter4/issues/829) +- Throttler usage [\#827](https://github.com/codeigniter4/CodeIgniter4/issues/827) +- Display Label on Form Validation [\#826](https://github.com/codeigniter4/CodeIgniter4/issues/826) +- Question about Filters [\#824](https://github.com/codeigniter4/CodeIgniter4/issues/824) +- uri-\>getHost\(\) [\#815](https://github.com/codeigniter4/CodeIgniter4/issues/815) +- Error changing databases CodeIgniter\Database\MySQLi\Connection::dbSelect\(\) [\#813](https://github.com/codeigniter4/CodeIgniter4/issues/813) +- Create a skeleton system for codeigniter [\#806](https://github.com/codeigniter4/CodeIgniter4/issues/806) +- Feature - CLI Prompt with Validation [\#800](https://github.com/codeigniter4/CodeIgniter4/issues/800) +- Routing problem /sth-abc-\>App:sth/foo \(ok\) and /sth/abc-\>App:sth/foo \(nok\) [\#799](https://github.com/codeigniter4/CodeIgniter4/issues/799) +- Update with delete value [\#796](https://github.com/codeigniter4/CodeIgniter4/issues/796) +- redirect\(\)-\>to\(\) is changing value of the base64 parameter [\#790](https://github.com/codeigniter4/CodeIgniter4/issues/790) +- View Parser escaping data even if passed 'raw' as context in setData\(\) [\#788](https://github.com/codeigniter4/CodeIgniter4/issues/788) +- Cant instantiate Parser Directly. [\#787](https://github.com/codeigniter4/CodeIgniter4/issues/787) +- about redis error [\#783](https://github.com/codeigniter4/CodeIgniter4/issues/783) +- Validation issue and routing issue [\#782](https://github.com/codeigniter4/CodeIgniter4/issues/782) +- Query binding stopped working [\#781](https://github.com/codeigniter4/CodeIgniter4/issues/781) +- Entity exception in line 270 and 143 \(current version\). [\#780](https://github.com/codeigniter4/CodeIgniter4/issues/780) +- about Error Handling [\#778](https://github.com/codeigniter4/CodeIgniter4/issues/778) +- $this-\>CI-\>request-\>getIPAddress\(\) protected [\#776](https://github.com/codeigniter4/CodeIgniter4/issues/776) +- How do you access the $this bound data of the controller in the view? Ci3 is OK, ci4 doesn't seem to work [\#775](https://github.com/codeigniter4/CodeIgniter4/issues/775) +- I wish I could call the $this of the controller in the business model. What's the solution? [\#774](https://github.com/codeigniter4/CodeIgniter4/issues/774) +- Ignore\_value should be the value of ID, how do I get it? [\#772](https://github.com/codeigniter4/CodeIgniter4/issues/772) +- Can you increase the function of unique values in the test database? [\#771](https://github.com/codeigniter4/CodeIgniter4/issues/771) +- about where\(null\) [\#770](https://github.com/codeigniter4/CodeIgniter4/issues/770) +- redirect\(\)-\>to\(\) problem with output buffering [\#769](https://github.com/codeigniter4/CodeIgniter4/issues/769) +- Class 'App\Models\NewsModel' not found [\#766](https://github.com/codeigniter4/CodeIgniter4/issues/766) +- Undefined property: Config\App::$errorViewPath [\#765](https://github.com/codeigniter4/CodeIgniter4/issues/765) +- Zend/Escaper too big ,its not necessary [\#764](https://github.com/codeigniter4/CodeIgniter4/issues/764) +- .htaccess on php7.0.12 nts not work. [\#763](https://github.com/codeigniter4/CodeIgniter4/issues/763) +- DebugBar Routes Collector throws ErrorException when optional parameter is not passed to controller [\#762](https://github.com/codeigniter4/CodeIgniter4/issues/762) +- Debug View change page structure [\#761](https://github.com/codeigniter4/CodeIgniter4/issues/761) +- Debug Tool: Show view filepath [\#758](https://github.com/codeigniter4/CodeIgniter4/issues/758) +- PHPCBF - CodeIgniter4-Standard [\#757](https://github.com/codeigniter4/CodeIgniter4/issues/757) +- about errorViewPath [\#745](https://github.com/codeigniter4/CodeIgniter4/issues/745) +- Combine Composer with Install [\#744](https://github.com/codeigniter4/CodeIgniter4/issues/744) +- I have multiple applications. How can I share helpers and Class Map in multiple applications? [\#742](https://github.com/codeigniter4/CodeIgniter4/issues/742) +- I have multiple applications. How can I share helpers and Class Map in multiple applications? [\#741](https://github.com/codeigniter4/CodeIgniter4/issues/741) +- When can the fourth version be released, too much [\#738](https://github.com/codeigniter4/CodeIgniter4/issues/738) +- about Sub-directories [\#737](https://github.com/codeigniter4/CodeIgniter4/issues/737) +- About 【Use Config\Services as CodeIgniter\Services】 [\#735](https://github.com/codeigniter4/CodeIgniter4/issues/735) +- Mysqli Driver doesn't throw sql level exceptions / error [\#734](https://github.com/codeigniter4/CodeIgniter4/issues/734) +- LogicException \#403 - CSRF [\#733](https://github.com/codeigniter4/CodeIgniter4/issues/733) +- User guide for ci4 is not responsive on mobile.\(screen width less then 769px\) [\#730](https://github.com/codeigniter4/CodeIgniter4/issues/730) +- Composer intall, as described in documentation, points to different github repository\(!\) [\#726](https://github.com/codeigniter4/CodeIgniter4/issues/726) +- Fatal error when trying to run using PHP built in server [\#725](https://github.com/codeigniter4/CodeIgniter4/issues/725) +- Passing array as argument in Parser Plugin not working [\#724](https://github.com/codeigniter4/CodeIgniter4/issues/724) +- $request-\>uri-\>getSegment\(\); return error [\#722](https://github.com/codeigniter4/CodeIgniter4/issues/722) +- Double quotation in getFieldData\(\); [\#721](https://github.com/codeigniter4/CodeIgniter4/issues/721) +- Should Model class implement the magic setter/getter methods for protected properties? [\#720](https://github.com/codeigniter4/CodeIgniter4/issues/720) +- Forge class trying to create UNSIGNED integer fields in Postgresql [\#719](https://github.com/codeigniter4/CodeIgniter4/issues/719) +- Tests Failing [\#717](https://github.com/codeigniter4/CodeIgniter4/issues/717) +- Form validation not working when Passing Empty array \[ \] [\#715](https://github.com/codeigniter4/CodeIgniter4/issues/715) +- Redirects to wrong incomplete URL [\#710](https://github.com/codeigniter4/CodeIgniter4/issues/710) +- pg\_escape\_literal\(\) expects parameter 1 to be resource, boolean given [\#709](https://github.com/codeigniter4/CodeIgniter4/issues/709) +- Separate DebugToolbar from CI\_DEBUG [\#707](https://github.com/codeigniter4/CodeIgniter4/issues/707) +- common.php [\#706](https://github.com/codeigniter4/CodeIgniter4/issues/706) +- Parser::parsePair fails if template contains a $ sign [\#705](https://github.com/codeigniter4/CodeIgniter4/issues/705) +- view parser throws InvalidArgumentException [\#704](https://github.com/codeigniter4/CodeIgniter4/issues/704) +- Auto redirect to root folder [\#703](https://github.com/codeigniter4/CodeIgniter4/issues/703) +- ErrorException \#1 htmlspecialchars\(\) expects parameter 1 to be string [\#702](https://github.com/codeigniter4/CodeIgniter4/issues/702) +- Support for Labels in Validation [\#696](https://github.com/codeigniter4/CodeIgniter4/issues/696) +- Why is system/bootstrap.php lowercase? [\#695](https://github.com/codeigniter4/CodeIgniter4/issues/695) +- To much escaping in where [\#691](https://github.com/codeigniter4/CodeIgniter4/issues/691) +- session in DB not working [\#690](https://github.com/codeigniter4/CodeIgniter4/issues/690) +- imagecopyresampled\(\): supplied resource is not a valid Image resource [\#689](https://github.com/codeigniter4/CodeIgniter4/issues/689) +- Postgres json column search error [\#680](https://github.com/codeigniter4/CodeIgniter4/issues/680) +- multiple File upload issue [\#679](https://github.com/codeigniter4/CodeIgniter4/issues/679) +- Postgres Json type error on save [\#677](https://github.com/codeigniter4/CodeIgniter4/issues/677) +- show strange chracters in kint.php inside system/ThirdParty folder. [\#676](https://github.com/codeigniter4/CodeIgniter4/issues/676) +- Need Parser plugin for validation errors. [\#675](https://github.com/codeigniter4/CodeIgniter4/issues/675) +- Fatal error: Cannot use Config\Services as Services [\#674](https://github.com/codeigniter4/CodeIgniter4/issues/674) +- Routing issue - bad args in function call. [\#672](https://github.com/codeigniter4/CodeIgniter4/issues/672) +- Entity Concerns [\#662](https://github.com/codeigniter4/CodeIgniter4/issues/662) +- Undefined method CLIRequest::isSecure\(\) [\#656](https://github.com/codeigniter4/CodeIgniter4/issues/656) +- PREG\_QUOTE and SQL String Binding [\#655](https://github.com/codeigniter4/CodeIgniter4/issues/655) +- Discussions on Pagination hasNext\(\) and hasPrevious\(\) [\#651](https://github.com/codeigniter4/CodeIgniter4/issues/651) +- Feature requested- Auth and Template Engine [\#647](https://github.com/codeigniter4/CodeIgniter4/issues/647) +- route\_to doesn't work for post routes [\#642](https://github.com/codeigniter4/CodeIgniter4/issues/642) +- CURL response header parsing \(100-continue\) [\#638](https://github.com/codeigniter4/CodeIgniter4/issues/638) +- Q: Support of array notations is not longer supported? [\#627](https://github.com/codeigniter4/CodeIgniter4/issues/627) +- Formvalidation not skip validation rules if field not required and field data empty [\#614](https://github.com/codeigniter4/CodeIgniter4/issues/614) +- Route naming and Groups [\#612](https://github.com/codeigniter4/CodeIgniter4/issues/612) +- Image resizing issue [\#610](https://github.com/codeigniter4/CodeIgniter4/issues/610) +- Too much escaping negative int \(int \< 0\) [\#606](https://github.com/codeigniter4/CodeIgniter4/issues/606) +- Problem with Sth3Model loading from Sth2Model when Sth3Model is in subdiretory \(Subdir1\) [\#605](https://github.com/codeigniter4/CodeIgniter4/issues/605) +- I'm not sure but captcha seems currently unavailable. Would it be included in ci4? [\#604](https://github.com/codeigniter4/CodeIgniter4/issues/604) +- Issue with UploadFile \[tempName == name/originalName\] ? [\#602](https://github.com/codeigniter4/CodeIgniter4/issues/602) +- Command Line Seeding Error [\#601](https://github.com/codeigniter4/CodeIgniter4/issues/601) +- form\_open\_multipart\('', \[\], $hidden\) [\#598](https://github.com/codeigniter4/CodeIgniter4/issues/598) +- Minor issue with docs - form helper \(there is no html\_escape function\) - shoud be esc [\#595](https://github.com/codeigniter4/CodeIgniter4/issues/595) +- $session-\>destroy\(\) and $session-\>stop\(\) do not work? [\#592](https://github.com/codeigniter4/CodeIgniter4/issues/592) +- file upload issue [\#591](https://github.com/codeigniter4/CodeIgniter4/issues/591) +- \IncomingRequest method getFiles\(\) - Should really return only arrays? [\#590](https://github.com/codeigniter4/CodeIgniter4/issues/590) +- Third argument '$param' missing in validation getErrorMessage [\#589](https://github.com/codeigniter4/CodeIgniter4/issues/589) +- Error on Postgre insertID [\#587](https://github.com/codeigniter4/CodeIgniter4/issues/587) +- \InputRequest $request-\>getFiles\(\) doesn't return FileCollection instance [\#586](https://github.com/codeigniter4/CodeIgniter4/issues/586) +- Documentation out of date? discoverLocal\(\) [\#581](https://github.com/codeigniter4/CodeIgniter4/issues/581) +- Storing data [\#580](https://github.com/codeigniter4/CodeIgniter4/issues/580) +- Propose Moving Model::classToArray\(\) method to Entity class as public method [\#579](https://github.com/codeigniter4/CodeIgniter4/issues/579) +- camelize helper function incorrect [\#576](https://github.com/codeigniter4/CodeIgniter4/issues/576) +- helper function uses include instead of include\_once [\#575](https://github.com/codeigniter4/CodeIgniter4/issues/575) +- system/Validation/Views/list.php fix [\#574](https://github.com/codeigniter4/CodeIgniter4/issues/574) +- \[FEATURE\] Model Class: updateWhere\(\) [\#572](https://github.com/codeigniter4/CodeIgniter4/issues/572) +- Model Order [\#571](https://github.com/codeigniter4/CodeIgniter4/issues/571) +- Minor thing with View file not found exception... [\#570](https://github.com/codeigniter4/CodeIgniter4/issues/570) +- Error: redirect\(\) - ltrim\(\) expects parameter 1 to be string, object given [\#568](https://github.com/codeigniter4/CodeIgniter4/issues/568) +- Tutorial is Wrong [\#562](https://github.com/codeigniter4/CodeIgniter4/issues/562) +- Model Hooks/Events Suggestion [\#557](https://github.com/codeigniter4/CodeIgniter4/issues/557) +- Initialize on Model \_\_construct [\#556](https://github.com/codeigniter4/CodeIgniter4/issues/556) +- Event trigger "pre\_system" not found [\#555](https://github.com/codeigniter4/CodeIgniter4/issues/555) +- Validation Issues [\#550](https://github.com/codeigniter4/CodeIgniter4/issues/550) +- Model Entity suggestion [\#549](https://github.com/codeigniter4/CodeIgniter4/issues/549) +- CodeIgniter\Files\File\(\) and FileNotFoundException issue with creating new files [\#548](https://github.com/codeigniter4/CodeIgniter4/issues/548) +- override core files [\#546](https://github.com/codeigniter4/CodeIgniter4/issues/546) +- CodeIgniter\Format\JSONFormatter needs a look. [\#544](https://github.com/codeigniter4/CodeIgniter4/issues/544) +- getenv non covered system method [\#543](https://github.com/codeigniter4/CodeIgniter4/issues/543) +- Couldn't manage without index.php [\#541](https://github.com/codeigniter4/CodeIgniter4/issues/541) +- Debug bar: Uncaught ErrorException: str\_repeat\(\): Second argument has to be greater than or equal to 0 [\#538](https://github.com/codeigniter4/CodeIgniter4/issues/538) +- Create localization filters for View Parser to aid in formatting numbers [\#536](https://github.com/codeigniter4/CodeIgniter4/issues/536) +- Create lang plugin for the View Parser [\#535](https://github.com/codeigniter4/CodeIgniter4/issues/535) +- Minor: Debug bar showing htmlentities \(\\) [\#531](https://github.com/codeigniter4/CodeIgniter4/issues/531) +- Autoloading not functional with custom namespaces outside application dir [\#529](https://github.com/codeigniter4/CodeIgniter4/issues/529) +- where i extends Controller, and rewrite \_\_construct, get error [\#527](https://github.com/codeigniter4/CodeIgniter4/issues/527) +- Debug/ImageException.php failed to open stream: No such file or directory [\#525](https://github.com/codeigniter4/CodeIgniter4/issues/525) +- Superglobals reset [\#524](https://github.com/codeigniter4/CodeIgniter4/issues/524) +- Bring ENV definition back out to the index file. [\#519](https://github.com/codeigniter4/CodeIgniter4/issues/519) +- Exception issue [\#517](https://github.com/codeigniter4/CodeIgniter4/issues/517) +- Port Zip library from CI3 [\#505](https://github.com/codeigniter4/CodeIgniter4/issues/505) +- Port Encryption library from CI3 [\#504](https://github.com/codeigniter4/CodeIgniter4/issues/504) +- Port SQLite database driver from CI3 [\#502](https://github.com/codeigniter4/CodeIgniter4/issues/502) +- A template engine suggession in this phase [\#500](https://github.com/codeigniter4/CodeIgniter4/issues/500) +- \_\_set in \App\Entities\Sample doesnt works [\#499](https://github.com/codeigniter4/CodeIgniter4/issues/499) +- Router rule: \(:alphanum\) doesn't work [\#498](https://github.com/codeigniter4/CodeIgniter4/issues/498) +- Undefined index: host [\#497](https://github.com/codeigniter4/CodeIgniter4/issues/497) +- Unreasonable default parameter [\#482](https://github.com/codeigniter4/CodeIgniter4/issues/482) +- Model issues [\#479](https://github.com/codeigniter4/CodeIgniter4/issues/479) +- Route Blocking [\#474](https://github.com/codeigniter4/CodeIgniter4/issues/474) +- Class 'CodeIgniter\Hooks\Hooks' not found [\#473](https://github.com/codeigniter4/CodeIgniter4/issues/473) +- Work with entities and validation bug [\#472](https://github.com/codeigniter4/CodeIgniter4/issues/472) +- route bug or not? [\#470](https://github.com/codeigniter4/CodeIgniter4/issues/470) +- serve.php option --host no effect on line 37 [\#469](https://github.com/codeigniter4/CodeIgniter4/issues/469) +- File class should stand on its own [\#468](https://github.com/codeigniter4/CodeIgniter4/issues/468) +- Database can't save connect instances because var "$group" default is NULL [\#466](https://github.com/codeigniter4/CodeIgniter4/issues/466) +- redirect\(\) issue [\#465](https://github.com/codeigniter4/CodeIgniter4/issues/465) +- Join not working in Query Builder [\#464](https://github.com/codeigniter4/CodeIgniter4/issues/464) +- If the controller's method's parameter has a default value , the program will show 500 error [\#461](https://github.com/codeigniter4/CodeIgniter4/issues/461) +- Wrong ROOTPATH on console [\#460](https://github.com/codeigniter4/CodeIgniter4/issues/460) +- Rename "public" Folder Not Working [\#453](https://github.com/codeigniter4/CodeIgniter4/issues/453) +- Environment File [\#452](https://github.com/codeigniter4/CodeIgniter4/issues/452) +- Currency Localisation Not Working [\#448](https://github.com/codeigniter4/CodeIgniter4/issues/448) +- session getFlashdata\(\) is not removing the flash data [\#446](https://github.com/codeigniter4/CodeIgniter4/issues/446) +- CURLRequest: HTTPHEADER and POSTFIELDS [\#445](https://github.com/codeigniter4/CodeIgniter4/issues/445) +- Language Line Prepend Recommendation [\#443](https://github.com/codeigniter4/CodeIgniter4/issues/443) +- Debug Toolbar Array Post Data [\#442](https://github.com/codeigniter4/CodeIgniter4/issues/442) +- Migration File & Class Name Issues [\#437](https://github.com/codeigniter4/CodeIgniter4/issues/437) +- Validation Rule Constants [\#436](https://github.com/codeigniter4/CodeIgniter4/issues/436) +- CURLRequest: Only variables should be passed by reference [\#434](https://github.com/codeigniter4/CodeIgniter4/issues/434) +- CLI new commands [\#433](https://github.com/codeigniter4/CodeIgniter4/issues/433) +- issue in phpunit [\#429](https://github.com/codeigniter4/CodeIgniter4/issues/429) +- Model Class To Array Does Not Use Get Magic Method Or Allowed Fields Property [\#427](https://github.com/codeigniter4/CodeIgniter4/issues/427) +- Response 404 not same satus get it [\#425](https://github.com/codeigniter4/CodeIgniter4/issues/425) +- Response Trait Fail Server Error Method [\#424](https://github.com/codeigniter4/CodeIgniter4/issues/424) +- Validation Required Rule Only Accepts Arrays or Strings [\#423](https://github.com/codeigniter4/CodeIgniter4/issues/423) +- CLI with parameters is not working [\#422](https://github.com/codeigniter4/CodeIgniter4/issues/422) +- API Response Trait :: failValidationError\(\) Status Code 422 [\#420](https://github.com/codeigniter4/CodeIgniter4/issues/420) +- Validation Get & Set Rule Group [\#419](https://github.com/codeigniter4/CodeIgniter4/issues/419) +- One-Off Validation for a Single Value [\#418](https://github.com/codeigniter4/CodeIgniter4/issues/418) +- Language File Array Support [\#414](https://github.com/codeigniter4/CodeIgniter4/issues/414) +- Pagination: How to trans string param when using $pager-\>links\(\)? [\#413](https://github.com/codeigniter4/CodeIgniter4/issues/413) +- Validation Exceptions [\#412](https://github.com/codeigniter4/CodeIgniter4/issues/412) +- Incorrect .htaccess [\#410](https://github.com/codeigniter4/CodeIgniter4/issues/410) +- Call to a member function getResult\(\) on boolean [\#409](https://github.com/codeigniter4/CodeIgniter4/issues/409) +- Writable directory question [\#407](https://github.com/codeigniter4/CodeIgniter4/issues/407) +- Paginate Class [\#406](https://github.com/codeigniter4/CodeIgniter4/issues/406) +- Base URL Not Working With HTTPS [\#396](https://github.com/codeigniter4/CodeIgniter4/issues/396) +- How to current page detailed information on router or etc? [\#393](https://github.com/codeigniter4/CodeIgniter4/issues/393) +- timer long time getElapsedTime problem [\#390](https://github.com/codeigniter4/CodeIgniter4/issues/390) +- Problem with system bootstrap file needs to be fixed! [\#389](https://github.com/codeigniter4/CodeIgniter4/issues/389) +- redirect reverseRoute problem [\#387](https://github.com/codeigniter4/CodeIgniter4/issues/387) +- Hooks::on not working \(it was working some time ago\) [\#383](https://github.com/codeigniter4/CodeIgniter4/issues/383) +- disable coveralls auto comment on pull request [\#382](https://github.com/codeigniter4/CodeIgniter4/issues/382) +- phpunit don't work \(fatal error\) on PHP 7.0.0 [\#373](https://github.com/codeigniter4/CodeIgniter4/issues/373) +- session id validation when using php7.1 [\#371](https://github.com/codeigniter4/CodeIgniter4/issues/371) +- travis test coverage report service [\#370](https://github.com/codeigniter4/CodeIgniter4/issues/370) +- The efficiency of 'insertBatch' function [\#368](https://github.com/codeigniter4/CodeIgniter4/issues/368) +- Run phpunit with --coverage-text/html got Error: Undefined variable: matchIP [\#363](https://github.com/codeigniter4/CodeIgniter4/issues/363) +- Extending Core Classes [\#358](https://github.com/codeigniter4/CodeIgniter4/issues/358) +- Validation field name issue? [\#356](https://github.com/codeigniter4/CodeIgniter4/issues/356) +- Old Cache-Control header tag remove? [\#355](https://github.com/codeigniter4/CodeIgniter4/issues/355) +- $this-\>request-\>getPost\(\) // Subkey not included data [\#353](https://github.com/codeigniter4/CodeIgniter4/issues/353) +- Routes static parameter problem not found page [\#352](https://github.com/codeigniter4/CodeIgniter4/issues/352) +- bug-form\_open\_multipart [\#345](https://github.com/codeigniter4/CodeIgniter4/issues/345) +- Recently ci4 everything goes well? I look ci the space named [\#336](https://github.com/codeigniter4/CodeIgniter4/issues/336) +- Request setBody not working [\#332](https://github.com/codeigniter4/CodeIgniter4/issues/332) +- HTTP/URI, CreateURIString yield unexpected result \(http:/// instead of http://\) [\#331](https://github.com/codeigniter4/CodeIgniter4/issues/331) +- session class: inconsistency on get\(\) between document and code [\#330](https://github.com/codeigniter4/CodeIgniter4/issues/330) +- Model.php Return Type Object causes Error when Saving. \(Fixed with this modification\) [\#329](https://github.com/codeigniter4/CodeIgniter4/issues/329) +- a bug about set\(\) [\#325](https://github.com/codeigniter4/CodeIgniter4/issues/325) +- `google map` in Mysqli Could be a Mistake [\#324](https://github.com/codeigniter4/CodeIgniter4/issues/324) +- Modules Controllers sub-directory problem [\#322](https://github.com/codeigniter4/CodeIgniter4/issues/322) +- Post action after Toolbar -\> Vars -\> Headers Content-Type 2 lines [\#321](https://github.com/codeigniter4/CodeIgniter4/issues/321) +- Database builder-\>table\(\) connection reference problem [\#320](https://github.com/codeigniter4/CodeIgniter4/issues/320) +- RedisHandler, Session regenerate id problem [\#318](https://github.com/codeigniter4/CodeIgniter4/issues/318) +- view\_cell optional variables array\_key\_exists null fix. [\#317](https://github.com/codeigniter4/CodeIgniter4/issues/317) +- sessionDriver Database not available [\#315](https://github.com/codeigniter4/CodeIgniter4/issues/315) +- Add support for Content-MD5 headers [\#314](https://github.com/codeigniter4/CodeIgniter4/issues/314) +- Scalar type declaration 'string' must be unqualified [\#312](https://github.com/codeigniter4/CodeIgniter4/issues/312) +- Now ci4 product development can be used? Or how long before they can be released? Very much looking forward [\#311](https://github.com/codeigniter4/CodeIgniter4/issues/311) +- Sessions in Debug Bar Could be a Mistake [\#310](https://github.com/codeigniter4/CodeIgniter4/issues/310) +- Should namespaces be used or fully written at method heads [\#309](https://github.com/codeigniter4/CodeIgniter4/issues/309) +- More filtering issues with Query [\#306](https://github.com/codeigniter4/CodeIgniter4/issues/306) +- Too much escaping in DB? [\#302](https://github.com/codeigniter4/CodeIgniter4/issues/302) +- Error Cell caching [\#297](https://github.com/codeigniter4/CodeIgniter4/issues/297) +- A suggestion for debug exceptions layout [\#295](https://github.com/codeigniter4/CodeIgniter4/issues/295) +- getSharedInstance doesn't work on costum services [\#294](https://github.com/codeigniter4/CodeIgniter4/issues/294) +- Separated services default with trait [\#292](https://github.com/codeigniter4/CodeIgniter4/issues/292) +- HTML-Purifier [\#291](https://github.com/codeigniter4/CodeIgniter4/issues/291) +- Implement native ORM [\#289](https://github.com/codeigniter4/CodeIgniter4/issues/289) +- Igniter 4 services [\#287](https://github.com/codeigniter4/CodeIgniter4/issues/287) +- Error Documentation - Tutorial [\#286](https://github.com/codeigniter4/CodeIgniter4/issues/286) +- Request is doing the filter\_var even if value is not in the $\_REQUEST array [\#285](https://github.com/codeigniter4/CodeIgniter4/issues/285) +- controller routing [\#284](https://github.com/codeigniter4/CodeIgniter4/issues/284) +- URI Routing issues [\#278](https://github.com/codeigniter4/CodeIgniter4/issues/278) +- Email Library [\#276](https://github.com/codeigniter4/CodeIgniter4/issues/276) +- sql error [\#273](https://github.com/codeigniter4/CodeIgniter4/issues/273) +- DB Needs Transactions implemented. [\#268](https://github.com/codeigniter4/CodeIgniter4/issues/268) +- Error Call to undefined method CodeIgniter\Database\MySQLi\Connection::close\(\) [\#267](https://github.com/codeigniter4/CodeIgniter4/issues/267) +- Application/ThirdParty folder necessity [\#265](https://github.com/codeigniter4/CodeIgniter4/issues/265) +- ErrorException when Connecting to Multiple Databases [\#255](https://github.com/codeigniter4/CodeIgniter4/issues/255) +- Toolbar.php :: ErrorException: Array to string conversion [\#254](https://github.com/codeigniter4/CodeIgniter4/issues/254) +- The Hooks feature does not work [\#248](https://github.com/codeigniter4/CodeIgniter4/issues/248) +- $baseUrl problem with Router [\#238](https://github.com/codeigniter4/CodeIgniter4/issues/238) +- File upload bugs [\#236](https://github.com/codeigniter4/CodeIgniter4/issues/236) +- standardize comments [\#234](https://github.com/codeigniter4/CodeIgniter4/issues/234) +- 3.1.0 Email Library Corrupting PDF Attachments [\#220](https://github.com/codeigniter4/CodeIgniter4/issues/220) +- Error DotEnv.php on line 121 [\#216](https://github.com/codeigniter4/CodeIgniter4/issues/216) +- Typography Helper [\#214](https://github.com/codeigniter4/CodeIgniter4/issues/214) +- Security Helper [\#213](https://github.com/codeigniter4/CodeIgniter4/issues/213) +- Number Helper [\#212](https://github.com/codeigniter4/CodeIgniter4/issues/212) +- Text Helper [\#211](https://github.com/codeigniter4/CodeIgniter4/issues/211) +- Inflector Helper [\#210](https://github.com/codeigniter4/CodeIgniter4/issues/210) +- HTML Helper [\#209](https://github.com/codeigniter4/CodeIgniter4/issues/209) +- FileSystem "Helper" [\#207](https://github.com/codeigniter4/CodeIgniter4/issues/207) +- Date Helper [\#206](https://github.com/codeigniter4/CodeIgniter4/issues/206) +- Cookie Helper [\#205](https://github.com/codeigniter4/CodeIgniter4/issues/205) +- Missing {memory\_usage} ? [\#197](https://github.com/codeigniter4/CodeIgniter4/issues/197) +- Exception or disable toolbar for specific URI [\#195](https://github.com/codeigniter4/CodeIgniter4/issues/195) +- Config Settings Usability [\#186](https://github.com/codeigniter4/CodeIgniter4/issues/186) +- Binders [\#185](https://github.com/codeigniter4/CodeIgniter4/issues/185) +- Mess Detector rules [\#184](https://github.com/codeigniter4/CodeIgniter4/issues/184) +- Coding Standards Fixer rules [\#183](https://github.com/codeigniter4/CodeIgniter4/issues/183) +- Code Sniffer Rules [\#182](https://github.com/codeigniter4/CodeIgniter4/issues/182) +- Placing view template outside of the 'Views' dir when using view\(\) [\#180](https://github.com/codeigniter4/CodeIgniter4/issues/180) +- Controller return output instead of echo view [\#179](https://github.com/codeigniter4/CodeIgniter4/issues/179) +- Honeypot Filter [\#176](https://github.com/codeigniter4/CodeIgniter4/issues/176) +- Form Helper [\#174](https://github.com/codeigniter4/CodeIgniter4/issues/174) +- ILIKE-based portion of the query for PostgreSQL [\#173](https://github.com/codeigniter4/CodeIgniter4/issues/173) +- “&get\_instance\(\)” in the Ci3 how to use it? [\#166](https://github.com/codeigniter4/CodeIgniter4/issues/166) +- system/Database/BaseConnection.php Change in getFieldNames\(\) method [\#164](https://github.com/codeigniter4/CodeIgniter4/issues/164) +- Error in session FileHandler and BaseHandler [\#152](https://github.com/codeigniter4/CodeIgniter4/issues/152) +- No listFields\(\) method in Postgre connection [\#151](https://github.com/codeigniter4/CodeIgniter4/issues/151) +- Controller Filters [\#150](https://github.com/codeigniter4/CodeIgniter4/issues/150) +- insert bug [\#149](https://github.com/codeigniter4/CodeIgniter4/issues/149) +- Router striping real dirpath from the urls. [\#148](https://github.com/codeigniter4/CodeIgniter4/issues/148) +- Problem throw error for default controller [\#146](https://github.com/codeigniter4/CodeIgniter4/issues/146) +- Routing issues. [\#145](https://github.com/codeigniter4/CodeIgniter4/issues/145) +- Pagination Library [\#142](https://github.com/codeigniter4/CodeIgniter4/issues/142) +- \[i18n\] Localization In Core [\#141](https://github.com/codeigniter4/CodeIgniter4/issues/141) +- Language [\#140](https://github.com/codeigniter4/CodeIgniter4/issues/140) +- Parser [\#139](https://github.com/codeigniter4/CodeIgniter4/issues/139) +- Application directory is missing the Helpers folder [\#133](https://github.com/codeigniter4/CodeIgniter4/issues/133) +- HTTP/Request.php Error [\#132](https://github.com/codeigniter4/CodeIgniter4/issues/132) +- Public properties issue? [\#124](https://github.com/codeigniter4/CodeIgniter4/issues/124) +- ci4 support websocket it? [\#121](https://github.com/codeigniter4/CodeIgniter4/issues/121) +- View Cells [\#116](https://github.com/codeigniter4/CodeIgniter4/issues/116) +- Cache Engine [\#115](https://github.com/codeigniter4/CodeIgniter4/issues/115) +- Image Class [\#114](https://github.com/codeigniter4/CodeIgniter4/issues/114) +- Uploader Class [\#113](https://github.com/codeigniter4/CodeIgniter4/issues/113) +- API Response Trait [\#86](https://github.com/codeigniter4/CodeIgniter4/issues/86) +- phpDocumentor bug [\#85](https://github.com/codeigniter4/CodeIgniter4/issues/85) +- Reserved method name [\#76](https://github.com/codeigniter4/CodeIgniter4/issues/76) +- Provide Throttler Filter [\#75](https://github.com/codeigniter4/CodeIgniter4/issues/75) +- Ensure docs are updated for current code. [\#72](https://github.com/codeigniter4/CodeIgniter4/issues/72) +- Writing Testing Docs [\#71](https://github.com/codeigniter4/CodeIgniter4/issues/71) +- Update Tutorial [\#70](https://github.com/codeigniter4/CodeIgniter4/issues/70) +- Review Contribution Guidelines [\#69](https://github.com/codeigniter4/CodeIgniter4/issues/69) +- Database Connection setDatabase\(\) and getVersion\(\) methods [\#68](https://github.com/codeigniter4/CodeIgniter4/issues/68) +- BaseConfig getEnvValue type juggling [\#67](https://github.com/codeigniter4/CodeIgniter4/issues/67) +- Migrations should track history per db group. [\#66](https://github.com/codeigniter4/CodeIgniter4/issues/66) +- Allow passing custom config values to Config\Database::connect\(\) [\#62](https://github.com/codeigniter4/CodeIgniter4/issues/62) +- SessionInterface: unset\(\) vs remove\(\) [\#60](https://github.com/codeigniter4/CodeIgniter4/issues/60) +- Remove Query Builder caching [\#59](https://github.com/codeigniter4/CodeIgniter4/issues/59) +- Why is `$getShared = false` the default? [\#55](https://github.com/codeigniter4/CodeIgniter4/issues/55) +- Why IncomingRequest has setCookie\(\)? [\#52](https://github.com/codeigniter4/CodeIgniter4/issues/52) +- AutoRoute and method arguments [\#45](https://github.com/codeigniter4/CodeIgniter4/issues/45) +- Rename the loader class [\#39](https://github.com/codeigniter4/CodeIgniter4/issues/39) +- exit\(\) prevents from testing [\#31](https://github.com/codeigniter4/CodeIgniter4/issues/31) +- getHeaders [\#27](https://github.com/codeigniter4/CodeIgniter4/issues/27) +- SYSDIR [\#25](https://github.com/codeigniter4/CodeIgniter4/issues/25) +- Coding style checker [\#21](https://github.com/codeigniter4/CodeIgniter4/issues/21) +- Test folder structure [\#20](https://github.com/codeigniter4/CodeIgniter4/issues/20) +- Namespace for test case classes [\#17](https://github.com/codeigniter4/CodeIgniter4/issues/17) +- Missing protocolVersion in response header [\#15](https://github.com/codeigniter4/CodeIgniter4/issues/15) +- Problem with Code Coverage Reporting [\#13](https://github.com/codeigniter4/CodeIgniter4/issues/13) +- Class 'Config\App' not found [\#12](https://github.com/codeigniter4/CodeIgniter4/issues/12) +- Can't get Code Coverage [\#7](https://github.com/codeigniter4/CodeIgniter4/issues/7) +- APPPATH, SYSPATH and similar constants. Rename? [\#2](https://github.com/codeigniter4/CodeIgniter4/issues/2) + +**Merged pull requests:** + +- Adjusting the release build scripts [\#1266](https://github.com/codeigniter4/CodeIgniter4/pull/1266) ([jim-parry](https://github.com/jim-parry)) +- WIP Fix docs re PHP server [\#1265](https://github.com/codeigniter4/CodeIgniter4/pull/1265) ([jim-parry](https://github.com/jim-parry)) +- Release prep part 1 [\#1248](https://github.com/codeigniter4/CodeIgniter4/pull/1248) ([jim-parry](https://github.com/jim-parry)) +- Tweaking the release builder [\#1246](https://github.com/codeigniter4/CodeIgniter4/pull/1246) ([jim-parry](https://github.com/jim-parry)) +- Move Response & APIResponseTrait to outgoing section of UG [\#1243](https://github.com/codeigniter4/CodeIgniter4/pull/1243) ([jim-parry](https://github.com/jim-parry)) +- workaround for buffer problem [\#1242](https://github.com/codeigniter4/CodeIgniter4/pull/1242) ([puschie286](https://github.com/puschie286)) +- Docs/restructure [\#1241](https://github.com/codeigniter4/CodeIgniter4/pull/1241) ([jim-parry](https://github.com/jim-parry)) +- doc fix: replace validation\_errors\(\) function with \Config\Services::validation\(\)-\>listErrors\(\) [\#1238](https://github.com/codeigniter4/CodeIgniter4/pull/1238) ([samsonasik](https://github.com/samsonasik)) +- doc fix: remove unneeded call helper\(url\) as already bootstrapped by default [\#1237](https://github.com/codeigniter4/CodeIgniter4/pull/1237) ([samsonasik](https://github.com/samsonasik)) +- Fix gh-pages deployment [\#1236](https://github.com/codeigniter4/CodeIgniter4/pull/1236) ([jim-parry](https://github.com/jim-parry)) +- Doc/fixes [\#1235](https://github.com/codeigniter4/CodeIgniter4/pull/1235) ([jim-parry](https://github.com/jim-parry)) +- typo in unset [\#1233](https://github.com/codeigniter4/CodeIgniter4/pull/1233) ([titounnes](https://github.com/titounnes)) +- Release build script [\#1231](https://github.com/codeigniter4/CodeIgniter4/pull/1231) ([jim-parry](https://github.com/jim-parry)) +- Fix user guide errors [\#1228](https://github.com/codeigniter4/CodeIgniter4/pull/1228) ([jim-parry](https://github.com/jim-parry)) +- Admin script for user guide build & deploy [\#1227](https://github.com/codeigniter4/CodeIgniter4/pull/1227) ([jim-parry](https://github.com/jim-parry)) +- use short array syntax [\#1223](https://github.com/codeigniter4/CodeIgniter4/pull/1223) ([samsonasik](https://github.com/samsonasik)) +- doc fix: FormatterInterface namespace [\#1222](https://github.com/codeigniter4/CodeIgniter4/pull/1222) ([samsonasik](https://github.com/samsonasik)) +- Improved division logic of validation rules. [\#1220](https://github.com/codeigniter4/CodeIgniter4/pull/1220) ([ytetsuro](https://github.com/ytetsuro)) +- Docs/contributing [\#1218](https://github.com/codeigniter4/CodeIgniter4/pull/1218) ([jim-parry](https://github.com/jim-parry)) +- Niggly fixes [\#1216](https://github.com/codeigniter4/CodeIgniter4/pull/1216) ([jim-parry](https://github.com/jim-parry)) +- Autodiscovery [\#1215](https://github.com/codeigniter4/CodeIgniter4/pull/1215) ([lonnieezell](https://github.com/lonnieezell)) +- Fix warnings in welcome\_message.php [\#1211](https://github.com/codeigniter4/CodeIgniter4/pull/1211) ([puschie286](https://github.com/puschie286)) +- Correct helper tests namespace [\#1207](https://github.com/codeigniter4/CodeIgniter4/pull/1207) ([jim-parry](https://github.com/jim-parry)) +- Validation Class - corresponding about the escaped separator. [\#1203](https://github.com/codeigniter4/CodeIgniter4/pull/1203) ([ytetsuro](https://github.com/ytetsuro)) +- Fixes FileRules::max\_size\(\) to use file-\>getSize\(\) instead of number\_formatted size [\#1199](https://github.com/codeigniter4/CodeIgniter4/pull/1199) ([samsonasik](https://github.com/samsonasik)) +- use Validation-\>getErrors\(\) call instead of Valdation::errors to handle errors that came from session [\#1197](https://github.com/codeigniter4/CodeIgniter4/pull/1197) ([samsonasik](https://github.com/samsonasik)) +- allows to get table and primary key name out of the model [\#1196](https://github.com/codeigniter4/CodeIgniter4/pull/1196) ([nowackipawel](https://github.com/nowackipawel)) +- pagination - optional page number [\#1195](https://github.com/codeigniter4/CodeIgniter4/pull/1195) ([nowackipawel](https://github.com/nowackipawel)) +- add writable/session directory and set default App::sessionSavePath to it [\#1194](https://github.com/codeigniter4/CodeIgniter4/pull/1194) ([samsonasik](https://github.com/samsonasik)) +- Fix travis error build on Router and RouteCollection [\#1192](https://github.com/codeigniter4/CodeIgniter4/pull/1192) ([samsonasik](https://github.com/samsonasik)) +- add bool type hint for getShared parameter [\#1191](https://github.com/codeigniter4/CodeIgniter4/pull/1191) ([samsonasik](https://github.com/samsonasik)) +- Catch Email Exceptions [\#1190](https://github.com/codeigniter4/CodeIgniter4/pull/1190) ([puschie286](https://github.com/puschie286)) +- $myModel-\>find\(string "value-of-my-primary-key"\) \#1188 [\#1189](https://github.com/codeigniter4/CodeIgniter4/pull/1189) ([nowackipawel](https://github.com/nowackipawel)) +- Adds valid\_json which is using json\_last\_error\(\) === JSON\_ERROR\_NONE [\#1187](https://github.com/codeigniter4/CodeIgniter4/pull/1187) ([nowackipawel](https://github.com/nowackipawel)) +- remove start\(\) call on session\(\) function call at Validation [\#1185](https://github.com/codeigniter4/CodeIgniter4/pull/1185) ([samsonasik](https://github.com/samsonasik)) +- remove unused salt in Model [\#1184](https://github.com/codeigniter4/CodeIgniter4/pull/1184) ([samsonasik](https://github.com/samsonasik)) +- log file:failed to delete buffer. No buffer to delete. [\#1182](https://github.com/codeigniter4/CodeIgniter4/pull/1182) ([bangbangda](https://github.com/bangbangda)) +- use string type hint in $file parameter in DotEnv::\_\_construct\(\) [\#1181](https://github.com/codeigniter4/CodeIgniter4/pull/1181) ([samsonasik](https://github.com/samsonasik)) +- Adjust log level to match RFC 5424 [\#1178](https://github.com/codeigniter4/CodeIgniter4/pull/1178) ([sugenganthos](https://github.com/sugenganthos)) +- Update Response.php [\#1173](https://github.com/codeigniter4/CodeIgniter4/pull/1173) ([sugenganthos](https://github.com/sugenganthos)) +- comparison fix at CIDatabaseTestCase [\#1172](https://github.com/codeigniter4/CodeIgniter4/pull/1172) ([samsonasik](https://github.com/samsonasik)) +- remove if \($template==forums/categories\) check in View/Parser [\#1171](https://github.com/codeigniter4/CodeIgniter4/pull/1171) ([samsonasik](https://github.com/samsonasik)) +- show PageNotFoundException message [\#1168](https://github.com/codeigniter4/CodeIgniter4/pull/1168) ([puschie286](https://github.com/puschie286)) +- make name parameter in HTTP\Header mandatory [\#1164](https://github.com/codeigniter4/CodeIgniter4/pull/1164) ([samsonasik](https://github.com/samsonasik)) +- Fixed insufficient validation of parameters related to pager. [\#1162](https://github.com/codeigniter4/CodeIgniter4/pull/1162) ([ytetsuro](https://github.com/ytetsuro)) +- remove unneeded helper\('url'\) call in plugins and form helper as already called in bootstrap file [\#1160](https://github.com/codeigniter4/CodeIgniter4/pull/1160) ([samsonasik](https://github.com/samsonasik)) +- reduce str\_replace in View/Parser::render\(\) [\#1159](https://github.com/codeigniter4/CodeIgniter4/pull/1159) ([samsonasik](https://github.com/samsonasik)) +- add missing string parameter type hint in Autoload methods [\#1158](https://github.com/codeigniter4/CodeIgniter4/pull/1158) ([samsonasik](https://github.com/samsonasik)) +- remove unneeded isset\($options\[hostname\]\) when next check is !empty\($options\[hostname\]\) [\#1157](https://github.com/codeigniter4/CodeIgniter4/pull/1157) ([samsonasik](https://github.com/samsonasik)) +- remove commented and never used methods in View\Parser class [\#1156](https://github.com/codeigniter4/CodeIgniter4/pull/1156) ([samsonasik](https://github.com/samsonasik)) +- Fixes \#1135 : Toolbar oldest file delete and show maximum in the list as App::toolbarMaxHistory [\#1155](https://github.com/codeigniter4/CodeIgniter4/pull/1155) ([samsonasik](https://github.com/samsonasik)) +- add ext-intl to require at composer.json [\#1153](https://github.com/codeigniter4/CodeIgniter4/pull/1153) ([samsonasik](https://github.com/samsonasik)) +- remove unneeded \(string\) cast as type hinted [\#1152](https://github.com/codeigniter4/CodeIgniter4/pull/1152) ([samsonasik](https://github.com/samsonasik)) +- implements session-\>push\(\) [\#1151](https://github.com/codeigniter4/CodeIgniter4/pull/1151) ([samsonasik](https://github.com/samsonasik)) +- SplFileInfo type case [\#1150](https://github.com/codeigniter4/CodeIgniter4/pull/1150) ([samsonasik](https://github.com/samsonasik)) +- Update views.rst [\#1149](https://github.com/codeigniter4/CodeIgniter4/pull/1149) ([sugenganthos](https://github.com/sugenganthos)) +- remove unneeded @todo for Time::setMonth\(\) to check max [\#1148](https://github.com/codeigniter4/CodeIgniter4/pull/1148) ([samsonasik](https://github.com/samsonasik)) +- fix RedirectResponse::route and added test [\#1147](https://github.com/codeigniter4/CodeIgniter4/pull/1147) ([puschie286](https://github.com/puschie286)) +- Fix uri detection with no index in uri [\#1146](https://github.com/codeigniter4/CodeIgniter4/pull/1146) ([puschie286](https://github.com/puschie286)) +- remove unneeded isset\($\_SESSION\) when next check is !empty\($\_SESSION\) [\#1145](https://github.com/codeigniter4/CodeIgniter4/pull/1145) ([samsonasik](https://github.com/samsonasik)) +- Add a namespace to the Pages.php [\#1143](https://github.com/codeigniter4/CodeIgniter4/pull/1143) ([fdjkgh580](https://github.com/fdjkgh580)) +- Add a namespace to the controller. [\#1142](https://github.com/codeigniter4/CodeIgniter4/pull/1142) ([fdjkgh580](https://github.com/fdjkgh580)) +- remove unused use statements [\#1141](https://github.com/codeigniter4/CodeIgniter4/pull/1141) ([samsonasik](https://github.com/samsonasik)) +- implements @todo max day in current month at Time::setDay\(\) [\#1140](https://github.com/codeigniter4/CodeIgniter4/pull/1140) ([samsonasik](https://github.com/samsonasik)) +- remove unneeded \(int\) casting as use int type hint or certainly an int [\#1138](https://github.com/codeigniter4/CodeIgniter4/pull/1138) ([samsonasik](https://github.com/samsonasik)) +- Update html\_helper.php [\#1133](https://github.com/codeigniter4/CodeIgniter4/pull/1133) ([WaldemarStanislawski](https://github.com/WaldemarStanislawski)) +- update to latest php-coveralls [\#1131](https://github.com/codeigniter4/CodeIgniter4/pull/1131) ([samsonasik](https://github.com/samsonasik)) +- Update View.php [\#1130](https://github.com/codeigniter4/CodeIgniter4/pull/1130) ([sugenganthos](https://github.com/sugenganthos)) +- Fix debugbar loading while csp is enabled [\#1129](https://github.com/codeigniter4/CodeIgniter4/pull/1129) ([puschie286](https://github.com/puschie286)) +- Run session tests in separate processes - fix for \#1106 [\#1128](https://github.com/codeigniter4/CodeIgniter4/pull/1128) ([andreif23](https://github.com/andreif23)) +- Feature/sqlite [\#793](https://github.com/codeigniter4/CodeIgniter4/pull/793) ([lonnieezell](https://github.com/lonnieezell)) diff --git a/changelogs/CHANGELOG_4.1.md b/changelogs/CHANGELOG_4.1.md new file mode 100644 index 000000000000..12b13d20cb63 --- /dev/null +++ b/changelogs/CHANGELOG_4.1.md @@ -0,0 +1,565 @@ +# Changelog 4.1 + +## [v4.1.9](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.9) (2022-02-25) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.8...v4.1.9) + +**SECURITY** + +* *Remote CLI Command Execution Vulnerability* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-xjp4-6w75-qrj7) for more information. +* *Cross-Site Request Forgery (CSRF) Protection Bypass Vulnerability* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-4v37-24gm-h554) for more information. + +## [v4.1.8](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.8) (2022-01-24) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.7...v4.1.8) + +**SECURITY** + +* *XSS Vulnerability* in the `API\ResponseTrait` was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-7528-7jg5-6g62) for more information. + +## [v4.1.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.7) (2022-01-09) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.6...v4.1.7) + +**Breaking Changes** + +* fix: replace deprecated FILTER_SANITIZE_STRING by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5555 + +**Fixed Bugs** + +* 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 + +## [v4.1.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.6) (2022-01-03) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.5...v4.1.6) + +**SECURITY** + +* *Deserialization of Untrusted Data* found in the ``old()`` function was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-w6jr-wj64-mc9x) for more information. + +**Breaking Changes** + +* fix: Incorrect type `BaseBuilder::$tableName` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5378 +* fix: Validation cannot handle array item by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5405 + +**Fixed Bugs** + +* fix: FileLocator cannot find files in sub-namespaces of the same vendor by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5292 +* fix: add a workaround for upgraded users who do not update Config\Exceptions by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5314 +* Fix db escape negative integers by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5277 +* Fix: remove incorrect processing of CLI params by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5274 +* fix: table alias is prefixed when LIKE clause by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5361 +* fix: `dot_array_search()` unexpected array structure causes Type Error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5372 +* fix: UploadedFile::move() may return incorrect value by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5302 +* fix: BaseModel::insert() may not pass all the values from Entity by @katie1348 in https://github.com/codeigniter4/CodeIgniter4/pull/4980 +* fix: `IncomingRequest::getJsonVar()` may cause TypeError by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5392 +* chore: fix example test code for appstarter and module by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5421 +* fix: Model::save() may call unneeded countAllResults() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5439 +* fix: errors when MariaDB/MySQL has `ANSI_QUOTES` enabled by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5425 +* fix: Security class sends cookies immediately by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5429 +* fix: `is_cli()` returns `true` when `$_SERVER['HTTP_USER_AGENT']` is missing by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5393 +* fix: `MySQLi\Connection::_foreignKeyData()` may return duplicated rows by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5416 +* fix: `number_to_currency()` error on PHP 8.1 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5454 +* fix: VENDORPATH definition by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5453 +* fix: Throttler does not show correct token time by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5470 +* fix: directory_mirror() throws an error if destination directory exists by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5493 +* fix: KINT visual error when activating CSP by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5501 + +**New Features** + +* feat: add filter to check invalid chars in user input by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5227 + +**Enhancements** + +* Add support for PHP 8.1 by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/4883 +* Toolbar - Make it possible to turn off var data collection by @najdanovicivan in https://github.com/codeigniter4/CodeIgniter4/pull/5295 +* feat: add CSRF token randomization by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5283 +* Display file:line and trace information to database queries in debug toolbar by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/5334 +* feat: add SecureHeaders filter by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5419 +* Feature: BaseBuilder instance as subquery. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5488 + +**Refactoring** + +* Do not inappropriately register bind when the value is a closure by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5247 +* refactor: replace $request->uri with $request->getUri() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5346 +* Determine if binds are simple or named by looking at the $binds array by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5138 +* Remove unneeded cast to array by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5379 +* Additional fix for deprecated `null` usage by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5388 +* refactor: dot_array_search() regex by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5443 +* refactor: Time::getDst() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5415 +* The View class. Optimizing duplicate code. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5455 +* refactor: fix `ThrottleTest::testFlooding` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5463 +* refactor: update deprecated method in DatetimeCast by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5474 +* Remove semicolons from SQL statements. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5513 + +**New Contributors** + +* @katie1348 made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/4980 + +## [v4.1.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.5) (2021-11-08) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.4...v4.1.5) + +**Fixed bugs:** + +* Fix entity name generation when bundled in model by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5055 +* Fix `Model::__call` throwing `BadMethodCallException` on empty results by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5139 +* Fixed an issue where the dropForeginKey method would execute an empty query when the dropConstraintStr property was empty. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5173 +* Update 'updated_at' when enabled in replace() by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/4684 +* Fix query binding with two colons in query by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5117 +* Fixed the problem that _createTable does not take into account that it returns true. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5133 +* Fixed a problem with not run escape for identities in like when `insensitiveSearch` is true. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5170 +* Fixed an issue where an unnecessary prefix was given when the random number was a column. by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5179 +* Always escape identifiers in the set(), setUpdateBatch(), and insertBatch() by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5132 +* Error when value is an object - validating api data by @daycry in https://github.com/codeigniter4/CodeIgniter4/pull/5142 +* Fix color not updated in several places of the precompiled CSS by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5155 +* Fix debugbar styles printing by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5100 +* Fix highlighting in database debug toolbar by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5129 +* Fix debug toolbar db connection count by @danielTiringer in https://github.com/codeigniter4/CodeIgniter4/pull/5172 +* Fix CSRF filter does not work when set it to only post by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5194 +* Add CSRF Protection for PUT/PATCH/DELETE by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5228 +* Fix GC issue when session lifetime is set to 0 by @lf-uraku-yuki in https://github.com/codeigniter4/CodeIgniter4/pull/4744 +* Fix wrong helper path resolution by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5246 +* Fix: remove CURLRequest headers sharing from $_SERVER by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5249 +* Fix Localization not working/being ignored for 404 page by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5267 +* fix: module filters are not discovered when using route filters by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5280 +* IncomingRequest - Trim trailing slash by @najdanovicivan in https://github.com/codeigniter4/CodeIgniter4/pull/4974 +* Previous Responses by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/5034 +* (Paging) Ensure page validity by @puschie286 in https://github.com/codeigniter4/CodeIgniter4/pull/5125 +* Fix variable variable `$$id` in RedisHandler by @Terrorboy in https://github.com/codeigniter4/CodeIgniter4/pull/5062 +* Fixes and enhancements to Exceptions by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5052 + +**Implemented enhancements:** + +* feat: `_` can be used as separators in environment variable names by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5156 +* Multiple filters for a route and classname filter by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5128 +* Feature - Mark duplicate queries by @danielTiringer in https://github.com/codeigniter4/CodeIgniter4/pull/5185 +* [Debug] Add formatted query string to timeline. by @sfadschm in https://github.com/codeigniter4/CodeIgniter4/pull/5196 +* [Debug] Improve keyword highlighting and escaping of query strings. by @sfadschm in https://github.com/codeigniter4/CodeIgniter4/pull/5200 +* Add `dropKey` method to `Forge` by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5171 +* Reduce memory usage of insertBatch(), updateBatch() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5202 +* Add Session based CSRF Protection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5201 +* feat: add valid_url_strict rule by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5268 + +**Merged pull requests:** + +* Merge branch '4.2' by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5060 +* Update to latest laminas-escaper 2.9.0 by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5065 +* Remove unintended dead code in pre-commit by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5116 +* Adjust orange color in debug toolbar by @vlakoff in https://github.com/codeigniter4/CodeIgniter4/pull/5136 +* Extract method to get prefix for DB access function by @ytetsuro in https://github.com/codeigniter4/CodeIgniter4/pull/5178 +* Improve `model()` auto-completion by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5186 +* Rename toolbar loader to be a regular JS file by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5224 +* [HTTP] Update Http Status Description based on latest iana.org by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/5235 +* Remove CSRF properties by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5231 +* Remove static variables for PHP 8.1 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5262 +* Replace usage of `FILTER_SANITIZE_STRING` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5263 +* Simplify logic of `number_to_roman` function by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5270 +* Fix compatibility of `PgSql\Result` on closing the result instance by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5279 +* Fix compatibility of Postgres result for PHP 8.1 by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5278 + +**New Contributors** + +* @Terrorboy made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/5062 +* @vlakoff made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/5136 +* @Felipebros made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/5152 +* @daycry made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/5142 +* @danielTiringer made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/5172 + +## [v4.1.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.4) (2021-09-06) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.3...v4.1.4) + +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](https://github.com/CodeIgniter/coding-standard) (based on PSR-12). + +**What's Changed** + +* Use php-cs-fixer as coding style tool by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/4770 +* Remove unused local variables by @jeromegamez in https://github.com/codeigniter4/CodeIgniter4/pull/4783 +* Use static lambda if a binding to `$this` is not required. by @jeromegamez in https://github.com/codeigniter4/CodeIgniter4/pull/4784 +* Use/Fix `preg_quote()` delimiters by @jeromegamez in https://github.com/codeigniter4/CodeIgniter4/pull/4789 +* Don't override `$path` parameter by @jeromegamez in https://github.com/codeigniter4/CodeIgniter4/pull/4787 +* Don't override `$value` parameter by @jeromegamez in https://github.com/codeigniter4/CodeIgniter4/pull/4788 +* Add brackets to clarify intent and avoid unwanted side-effects by @jeromegamez in https://github.com/codeigniter4/CodeIgniter4/pull/4791 +* Remove removed `safe_mode` ini Option by @jeromegamez in https://github.com/codeigniter4/CodeIgniter4/pull/4795 +* It will fix undefined index cid error when sending emails with embedded images by @mmfarhan in https://github.com/codeigniter4/CodeIgniter4/pull/4798 +* Revert Model coalesce by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/4819 +* Master language constructs shall be used instead of aliases. by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/4847 +* [Commands] Remove unused $minPHPVersion property at Serve command by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/4852 +* Update to latest laminas-escaper ^2.8 by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/4878 +* Remove 'memory_usage' from 'displayPerformanceMetrics()' comment by @Mauricevb in https://github.com/codeigniter4/CodeIgniter4/pull/4939 +* Remove useless code separator comments by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/4946 +* Optimize Filters by @mostafakhudair in https://github.com/codeigniter4/CodeIgniter4/pull/4965 +* Fix properly the phpstan error in 0.12.93 by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/4970 +* Manual cleanup of docblocks and comments by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/4964 +* Make Cookie compatible with ArrayAccess by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5004 +* Replace deprecated FILTER_SANITIZE_STRING by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5005 +* Make CookieStore compatible with IteratorAggregate::getIterator by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5010 +* Make the session handlers all compatible with SessionHandlerInterface by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5012 +* Make CITestStreamFilter compatible with php_user_filter by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5014 +* Make Time compatible with DateTime by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5022 +* Add `ReturnTypeWillChange` attribute to Entity by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5028 +* Replace unused Entity private method by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5029 +* Make File compatible with SplFileInfo by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5040 +* Update documentation code samples by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5039 +* PHP Copy-Paste Detector by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/5031 +* Fix key casting in form_dropdown helper. by @sfadschm in https://github.com/codeigniter4/CodeIgniter4/pull/5035 +* Switch to official coding standard by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5038 + +**New Contributors** + +* @mmfarhan made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/4798 +* @Mauricevb made their first contribution in https://github.com/codeigniter4/CodeIgniter4/pull/4939 + +## [v4.1.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.3) (2021-06-06) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.2...v4.1.3) + +**Fixed bugs:** + +- Bug: Error using SQLITE3 strftime in CodeIgniter 4.1.2 [\#4760](https://github.com/codeigniter4/CodeIgniter4/issues/4760) +- Bug: Caching something through cron, is not accessible in the web application [\#4751](https://github.com/codeigniter4/CodeIgniter4/issues/4751) +- Bug: SQLite Drop Column [\#4746](https://github.com/codeigniter4/CodeIgniter4/issues/4746) +- Bug: CURL Class - BaseURI options notworking [\#4713](https://github.com/codeigniter4/CodeIgniter4/issues/4713) +- Bug: autorouting [\#4711](https://github.com/codeigniter4/CodeIgniter4/issues/4711) +- Bug: curlrequest not using baseURI on localhost [\#4707](https://github.com/codeigniter4/CodeIgniter4/issues/4707) +- Bug: cli not working with cron [\#4699](https://github.com/codeigniter4/CodeIgniter4/issues/4699) + +**Closed issues:** + +- Bug: Class 'Locale' not found [\#4775](https://github.com/codeigniter4/CodeIgniter4/issues/4775) +- Bug: deprecated notice on CodeIgniter\HTTP\RequestInterface::getMethod\(\) [\#4717](https://github.com/codeigniter4/CodeIgniter4/issues/4717) +- Allow to join models between primary keys and foreign keys [\#4714](https://github.com/codeigniter4/CodeIgniter4/issues/4714) +- DateTime::\_\_construct\(\): Failed to parse time string \(\) at position 0 \(�\): Unexpected character [\#4708](https://github.com/codeigniter4/CodeIgniter4/issues/4708) +- Bug: Query Builder breaks with SQL function LENGTH\(\) and column name "row" [\#4687](https://github.com/codeigniter4/CodeIgniter4/issues/4687) + +**Merged pull requests:** + +- Expand Query named binds recognition [\#4769](https://github.com/codeigniter4/CodeIgniter4/pull/4769) ([paulbalandan](https://github.com/paulbalandan)) +- \[Rector\] Remove @var from class constant [\#4766](https://github.com/codeigniter4/CodeIgniter4/pull/4766) ([samsonasik](https://github.com/samsonasik)) +- Set WarningsReturnAsErrors = 0 before connection [\#4762](https://github.com/codeigniter4/CodeIgniter4/pull/4762) ([obelisk-services](https://github.com/obelisk-services)) +- \[Rector\] Apply Rector: VarConstantCommentRector [\#4759](https://github.com/codeigniter4/CodeIgniter4/pull/4759) ([samsonasik](https://github.com/samsonasik)) +- \[Autoloader\] include\_once is not needed on Autoloader::loadClass\(\) with no namespace [\#4756](https://github.com/codeigniter4/CodeIgniter4/pull/4756) ([samsonasik](https://github.com/samsonasik)) +- Fix imagemagick build [\#4755](https://github.com/codeigniter4/CodeIgniter4/pull/4755) ([michalsn](https://github.com/michalsn)) +- \[Rector\] Apply Rector: MoveVariableDeclarationNearReferenceRector [\#4752](https://github.com/codeigniter4/CodeIgniter4/pull/4752) ([samsonasik](https://github.com/samsonasik)) +- SQLite3 "nullable" [\#4749](https://github.com/codeigniter4/CodeIgniter4/pull/4749) ([MGatner](https://github.com/MGatner)) +- Remove $response variable at ControllerResponse::\_\_construct\(\) as never defined [\#4747](https://github.com/codeigniter4/CodeIgniter4/pull/4747) ([samsonasik](https://github.com/samsonasik)) +- Use variable for Config/Paths config to reduce repetitive definition [\#4745](https://github.com/codeigniter4/CodeIgniter4/pull/4745) ([samsonasik](https://github.com/samsonasik)) +- \[Rector\] Apply Rector : ListToArrayDestructRector [\#4743](https://github.com/codeigniter4/CodeIgniter4/pull/4743) ([samsonasik](https://github.com/samsonasik)) +- Add default TTL [\#4742](https://github.com/codeigniter4/CodeIgniter4/pull/4742) ([MGatner](https://github.com/MGatner)) +- update return sample of `dot array\_search\(\)` [\#4740](https://github.com/codeigniter4/CodeIgniter4/pull/4740) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Additional check for `$argv` variable when detecting CLI [\#4739](https://github.com/codeigniter4/CodeIgniter4/pull/4739) ([paulbalandan](https://github.com/paulbalandan)) +- Ensure variable declarations [\#4737](https://github.com/codeigniter4/CodeIgniter4/pull/4737) ([jeromegamez](https://github.com/jeromegamez)) +- Fix setting of value in Cookie's flag attributes [\#4736](https://github.com/codeigniter4/CodeIgniter4/pull/4736) ([paulbalandan](https://github.com/paulbalandan)) +- Add missing imports [\#4735](https://github.com/codeigniter4/CodeIgniter4/pull/4735) ([jeromegamez](https://github.com/jeromegamez)) +- Add environment spark command [\#4734](https://github.com/codeigniter4/CodeIgniter4/pull/4734) ([paulbalandan](https://github.com/paulbalandan)) +- Remove explicit condition that is always true [\#4731](https://github.com/codeigniter4/CodeIgniter4/pull/4731) ([jeromegamez](https://github.com/jeromegamez)) +- Deduplicate code [\#4730](https://github.com/codeigniter4/CodeIgniter4/pull/4730) ([jeromegamez](https://github.com/jeromegamez)) +- Replace `isset\(\)` with the `??` null coalesce operator [\#4729](https://github.com/codeigniter4/CodeIgniter4/pull/4729) ([jeromegamez](https://github.com/jeromegamez)) +- Remove unused imports [\#4728](https://github.com/codeigniter4/CodeIgniter4/pull/4728) ([jeromegamez](https://github.com/jeromegamez)) +- Fix truncated SCRIPT\_NAME [\#4726](https://github.com/codeigniter4/CodeIgniter4/pull/4726) ([MGatner](https://github.com/MGatner)) +- Expand CLI detection [\#4725](https://github.com/codeigniter4/CodeIgniter4/pull/4725) ([paulbalandan](https://github.com/paulbalandan)) +- \[Rector\] Add custom Rector Rule: RemoveErrorSuppressInTryCatchStmtsRector rector rule [\#4724](https://github.com/codeigniter4/CodeIgniter4/pull/4724) ([samsonasik](https://github.com/samsonasik)) +- Test with MySQL 8 [\#4721](https://github.com/codeigniter4/CodeIgniter4/pull/4721) ([jeromegamez](https://github.com/jeromegamez)) +- Replace URI string casts [\#4716](https://github.com/codeigniter4/CodeIgniter4/pull/4716) ([MGatner](https://github.com/MGatner)) +- Format URI directly [\#4715](https://github.com/codeigniter4/CodeIgniter4/pull/4715) ([MGatner](https://github.com/MGatner)) +- Additional File functions [\#4712](https://github.com/codeigniter4/CodeIgniter4/pull/4712) ([MGatner](https://github.com/MGatner)) +- Remove unused private rowOffset property in Database/SQLSRV/Result.php [\#4709](https://github.com/codeigniter4/CodeIgniter4/pull/4709) ([samsonasik](https://github.com/samsonasik)) +- Check for configured instead of hard-coded database in DbUtilsTest [\#4705](https://github.com/codeigniter4/CodeIgniter4/pull/4705) ([jeromegamez](https://github.com/jeromegamez)) +- Revert UG margins [\#4704](https://github.com/codeigniter4/CodeIgniter4/pull/4704) ([MGatner](https://github.com/MGatner)) +- Create .git/hooks directory if not already present [\#4703](https://github.com/codeigniter4/CodeIgniter4/pull/4703) ([jeromegamez](https://github.com/jeromegamez)) +- Annotate specifically designed slow tests with custom limits [\#4698](https://github.com/codeigniter4/CodeIgniter4/pull/4698) ([paulbalandan](https://github.com/paulbalandan)) +- Cache robustness [\#4697](https://github.com/codeigniter4/CodeIgniter4/pull/4697) ([MGatner](https://github.com/MGatner)) + +## [v4.1.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.2) (2021-05-18) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.1...v4.1.2) + +**Implemented enhancements:** + +- New HTTP classes, ``Cookie`` and ``CookieStore``, for abstracting web cookies. +- New ``assertRedirectTo()`` assertion available for HTTP tests. +- New logger handler, ``ErrorlogHandler``, that writes to ``error_log()``. +- Entity. Added custom type casting functionality. +- New option in routing. The ``priority`` option lower the priority of specific route processing. +- The ``Autoloader`` class can now load files which do not contain PHP classes. The list of `non-class` files will be listed in the ``$files`` property of ``Config\Autoload`` class. + +**Deprecations:** + +- Deprecated ``Codeigniter\View\View::$currentSection`` property. +- Language strings and exceptions on invalid cookie samesite are deprecated for the ``CookieException``'s own exception message. +- Deprecated `CodeIgniter\Entity` in favor of `CodeIgniter\Entity\Entity` +- Deprecated cookie-related properties of ``Response`` in order to use the ``Cookie`` class. +- Deprecated cookie-related properties of ``Security`` in order to use the ``Cookie`` class. +- Deprecated cookie-related properties of ``Session`` in order to use the ``Cookie`` class. +- Deprecated ``Security::isExpired()`` to use the ``Cookie``'s internal expires status. +- Deprecated ``CIDatabaseTestCase`` to use the ``DatabaseTestTrait`` instead. +- Deprecated ``FeatureTestCase`` to use the ``FeatureTestTrait`` instead. +- Deprecated ``ControllerTester`` to use the ``ControllerTestTrait`` instead. +- Consolidated and deprecated ``ControllerResponse`` and ``FeatureResponse`` in favor of ``TestResponse``. +- Deprecated ``Time::instance()``, use ``Time::createFromInstance()`` instead (now accepts ``DateTimeInterface``). +- Deprecated ``IncomingRequest::removeRelativeDirectory()``, use ``URI::removeDotSegments()`` instead +- Deprecated ``\API\ResponseTrait::failValidationError`` to use ``\API\ResponseTrait::failValidationErrors`` instead + +**Fixed bugs:** + +- Bug: NULL Fields definition not working for MSQLi Forge in Migration due property $\_null and $null names difference [\#4693](https://github.com/codeigniter4/CodeIgniter4/issues/4693) +- Bug: Missing Cookie Config [\#4619](https://github.com/codeigniter4/CodeIgniter4/issues/4619) +- Bug: [\#4610](https://github.com/codeigniter4/CodeIgniter4/issues/4610) +- Bug: Customized Validation language does not take effect [\#4597](https://github.com/codeigniter4/CodeIgniter4/issues/4597) +- Bug: colon issue in query binding [\#4595](https://github.com/codeigniter4/CodeIgniter4/issues/4595) +- Bug: set\_checkbox\(\) default value not working [\#4582](https://github.com/codeigniter4/CodeIgniter4/issues/4582) +- Bug: Request & Response objects stored multiple times [\#4580](https://github.com/codeigniter4/CodeIgniter4/issues/4580) +- Bug: Class information on output is missing during migrate:rollback command [\#4579](https://github.com/codeigniter4/CodeIgniter4/issues/4579) +- Bug: Cookie path replaced with system's PATH env variable [\#4559](https://github.com/codeigniter4/CodeIgniter4/issues/4559) +- Bug: Validation::withRequest\(\) method does not receive data. [\#4552](https://github.com/codeigniter4/CodeIgniter4/issues/4552) +- `esc` and `nl2br` combo gives nasty HTML error output [\#4533](https://github.com/codeigniter4/CodeIgniter4/issues/4533) +- Bug: typo error when creating a model using php spark make:model [\#4525](https://github.com/codeigniter4/CodeIgniter4/issues/4525) +- Bug: if\_exist not working with ".\*" notation [\#4521](https://github.com/codeigniter4/CodeIgniter4/issues/4521) +- Bug: Query::matchSimpleBinds index problem only toolbar. [\#4518](https://github.com/codeigniter4/CodeIgniter4/issues/4518) +- Bug: Unable to use debugger toolbar on a live server [\#4516](https://github.com/codeigniter4/CodeIgniter4/issues/4516) +- Missing config options and config options repetition [\#4504](https://github.com/codeigniter4/CodeIgniter4/issues/4504) +- Bug: db:create command should create database even database not exists yet, and defined in .env [\#4498](https://github.com/codeigniter4/CodeIgniter4/issues/4498) +- Bug: Differences in file names created with CLI command [\#4495](https://github.com/codeigniter4/CodeIgniter4/issues/4495) +- Bug: Session removeTempdata\(\) method not accepting arrays. [\#4490](https://github.com/codeigniter4/CodeIgniter4/issues/4490) +- Bug: Session remove\(\) method not removing tempdata sessions. [\#4489](https://github.com/codeigniter4/CodeIgniter4/issues/4489) +- Bug: Session getFlashdata\(\) not support for dot notation. [\#4488](https://github.com/codeigniter4/CodeIgniter4/issues/4488) +- Bug: New Service replacement fails at service provider precedence on core factory implementations [\#4483](https://github.com/codeigniter4/CodeIgniter4/issues/4483) +- Bug: Filter is not work ! [\#4482](https://github.com/codeigniter4/CodeIgniter4/issues/4482) +- Bug: PHPStorm anlysis fault [\#4474](https://github.com/codeigniter4/CodeIgniter4/issues/4474) +- Bug: apache mod\_userdir causes weird URL segment duplication [\#4471](https://github.com/codeigniter4/CodeIgniter4/issues/4471) +- Postgre Forge doesn't use schema in creating tables [\#4469](https://github.com/codeigniter4/CodeIgniter4/issues/4469) +- Bug: UG QueryBuilder::from\(\) wrong SQL example. [\#4464](https://github.com/codeigniter4/CodeIgniter4/issues/4464) +- Bug: results on getX\(\) not equal [\#4452](https://github.com/codeigniter4/CodeIgniter4/issues/4452) +- Bug: Queries with LOWER\( throwing errors [\#4443](https://github.com/codeigniter4/CodeIgniter4/issues/4443) +- Bug: RouteCollection::getHTTPVerb\(\) can return null [\#4435](https://github.com/codeigniter4/CodeIgniter4/issues/4435) +- Bug: can't run `spark migrate` on CI server [\#4428](https://github.com/codeigniter4/CodeIgniter4/issues/4428) +- Bug: URI Routing Placeholders [\#4421](https://github.com/codeigniter4/CodeIgniter4/issues/4421) +- Bug: Third Flags needs default [\#4411](https://github.com/codeigniter4/CodeIgniter4/issues/4411) +- Bug: another Flags needs default [\#4410](https://github.com/codeigniter4/CodeIgniter4/issues/4410) +- Bug: Flags needs default value [\#4409](https://github.com/codeigniter4/CodeIgniter4/issues/4409) +- Bug: log\_message passed object [\#4407](https://github.com/codeigniter4/CodeIgniter4/issues/4407) +- Bug: Model creation error [\#4393](https://github.com/codeigniter4/CodeIgniter4/issues/4393) +- Bug: If the file name contains "app", "php spark make: migration" will not create it successfully. [\#4383](https://github.com/codeigniter4/CodeIgniter4/issues/4383) +- Bug: IncomingRequest.php getVar\(\) [\#4381](https://github.com/codeigniter4/CodeIgniter4/issues/4381) +- Bug: Minimum PHP Version Discrepancy [\#4361](https://github.com/codeigniter4/CodeIgniter4/issues/4361) +- Bug: insertBatch generates an incorrect SQL query if the fields differ only in number at the end [\#4345](https://github.com/codeigniter4/CodeIgniter4/issues/4345) +- Bug: Database/Live tests fail [\#4336](https://github.com/codeigniter4/CodeIgniter4/issues/4336) +- Bug: red line on model by setPrefix & prefixTable [\#4329](https://github.com/codeigniter4/CodeIgniter4/issues/4329) +- Bug: $model-\>errors\(\) produce output when no error [\#4323](https://github.com/codeigniter4/CodeIgniter4/issues/4323) +- Bug: Can't Rewrite System Validation Messages [\#4318](https://github.com/codeigniter4/CodeIgniter4/issues/4318) +- Bug: "useSoftDelete" for model files generated by `phpspark` [\#4316](https://github.com/codeigniter4/CodeIgniter4/issues/4316) +- Bug: require the unused namespace [\#4309](https://github.com/codeigniter4/CodeIgniter4/issues/4309) +- Bug: FeatureTest cannot assert Status\(404\) [\#4306](https://github.com/codeigniter4/CodeIgniter4/issues/4306) +- Bug: BaseBuilder-\>\_insert [\#4302](https://github.com/codeigniter4/CodeIgniter4/issues/4302) +- Bug: previous\_url\(\) contains current URL after reloading a page. [\#4299](https://github.com/codeigniter4/CodeIgniter4/issues/4299) +- Bug: Cannot add route to controller in filename with dash/hyphen [\#4294](https://github.com/codeigniter4/CodeIgniter4/issues/4294) +- Bug: FeatureTest dies when throws RedirectException/cached page [\#4288](https://github.com/codeigniter4/CodeIgniter4/issues/4288) +- Bug: /test.php show home page [\#4263](https://github.com/codeigniter4/CodeIgniter4/issues/4263) +- Bug: Fabricator::fake\(\) function is breaking when it returns an array [\#4261](https://github.com/codeigniter4/CodeIgniter4/issues/4261) +- Bug: Session issue with CI Environment set to Testing \(CI4\) [\#4248](https://github.com/codeigniter4/CodeIgniter4/issues/4248) +- Bug: Wrong HTML code in output of "form\_input" helper function [\#4235](https://github.com/codeigniter4/CodeIgniter4/issues/4235) +- make:scaffold input information is missing [\#4230](https://github.com/codeigniter4/CodeIgniter4/issues/4230) +- Bug: CodeIgniter 4.1.1 - csrf token is always regenerated [\#4224](https://github.com/codeigniter4/CodeIgniter4/issues/4224) +- Bug: getFileMultiple expects an "0" index but string is given [\#4221](https://github.com/codeigniter4/CodeIgniter4/issues/4221) +- Bug: cannot resolve Services::xxx\(\) [\#4220](https://github.com/codeigniter4/CodeIgniter4/issues/4220) +- Bug: tfoot\_open / tfoot\_close have no default when using custom table template [\#4219](https://github.com/codeigniter4/CodeIgniter4/issues/4219) +- Bug: Spark PHP version [\#4213](https://github.com/codeigniter4/CodeIgniter4/issues/4213) +- Bug: Soft deletes and model validation when unique [\#4162](https://github.com/codeigniter4/CodeIgniter4/issues/4162) +- Bug: Debug Toolbar - Memory Leak - Allocation Exception [\#4137](https://github.com/codeigniter4/CodeIgniter4/issues/4137) +- current\_url\(\) global method returning URLs without the index.php part. [\#4116](https://github.com/codeigniter4/CodeIgniter4/issues/4116) +- Bug: appstarter HealthTest::testBaseUrlHasBeenSet fails [\#3977](https://github.com/codeigniter4/CodeIgniter4/issues/3977) +- Bug: Time::createFromTimestamp\(\) uses default timezone, not UTC for timestamp [\#3951](https://github.com/codeigniter4/CodeIgniter4/issues/3951) +- Bug: Unexpected filter behavior [\#3874](https://github.com/codeigniter4/CodeIgniter4/issues/3874) +- Bug: Double initializing of class [\#3855](https://github.com/codeigniter4/CodeIgniter4/issues/3855) +- Bug: Registrars take priority over .env [\#3845](https://github.com/codeigniter4/CodeIgniter4/issues/3845) +- Bug: SQLite3 NOT NULL prevents inserts [\#3599](https://github.com/codeigniter4/CodeIgniter4/issues/3599) +- Bug: Model doesn't reset errors in FeatureTestCase [\#3578](https://github.com/codeigniter4/CodeIgniter4/issues/3578) +- Bug: Problem in "/system/Database/Query.php" function "compileBinds\(\)" [\#3566](https://github.com/codeigniter4/CodeIgniter4/issues/3566) +- Bug: Exceptions cause risky Feature Tests [\#3114](https://github.com/codeigniter4/CodeIgniter4/issues/3114) +- Bug: current\_url\(\) loses subdomain [\#3004](https://github.com/codeigniter4/CodeIgniter4/issues/3004) + +**Closed issues:** + +- mysqli\_sql\_exception \#2002 [\#4640](https://github.com/codeigniter4/CodeIgniter4/issues/4640) +- intl - Name missing exception [\#4636](https://github.com/codeigniter4/CodeIgniter4/issues/4636) +- HUGE BUG: update\(\) function updates all records if id is empty [\#4617](https://github.com/codeigniter4/CodeIgniter4/issues/4617) +- Bug: Validation rule "matches" doesn't work [\#4615](https://github.com/codeigniter4/CodeIgniter4/issues/4615) +- Bug: chmod 777 writable/cache fixed codeignitor install for me [\#4598](https://github.com/codeigniter4/CodeIgniter4/issues/4598) +- Model-\>where method does not exist [\#4583](https://github.com/codeigniter4/CodeIgniter4/issues/4583) +- Transactions between two databases [\#4578](https://github.com/codeigniter4/CodeIgniter4/issues/4578) +- Bug: Mysql connection issue with MYSQLI\_CLIENT\_SSL\_DONT\_VERIFY\_SERVER\_CERT [\#4558](https://github.com/codeigniter4/CodeIgniter4/issues/4558) +- Release cycle [\#4526](https://github.com/codeigniter4/CodeIgniter4/issues/4526) +- Call to a member function setContentType\(\) on null - Responsetrait [\#4524](https://github.com/codeigniter4/CodeIgniter4/issues/4524) +- Bug: mock single\_service [\#4515](https://github.com/codeigniter4/CodeIgniter4/issues/4515) +- Bug: failed to open stream [\#4514](https://github.com/codeigniter4/CodeIgniter4/issues/4514) +- Array Validation Fails [\#4510](https://github.com/codeigniter4/CodeIgniter4/issues/4510) +- Bug: return $this-\>failValidationError\($validation-\>getErrors\(\)\) Has Invalid Signature [\#4506](https://github.com/codeigniter4/CodeIgniter4/issues/4506) +- Bug: Ok The Model ERRORS came right back with the newest build again! [\#4491](https://github.com/codeigniter4/CodeIgniter4/issues/4491) +- Bug: Composer install loads require-dev when I require another package [\#4477](https://github.com/codeigniter4/CodeIgniter4/issues/4477) +- Logger [\#4460](https://github.com/codeigniter4/CodeIgniter4/issues/4460) +- Bug: Improve creation of scaffolds with the CLI [\#4441](https://github.com/codeigniter4/CodeIgniter4/issues/4441) +- Request: Feature Test Optimization [\#4438](https://github.com/codeigniter4/CodeIgniter4/issues/4438) +- request.getVar not populated with GET parameters | unexpected behavior [\#4418](https://github.com/codeigniter4/CodeIgniter4/issues/4418) +- Running via CLI - Only Default Controller works [\#4415](https://github.com/codeigniter4/CodeIgniter4/issues/4415) +- Parser content typehint \[strict\_types=1\] [\#4412](https://github.com/codeigniter4/CodeIgniter4/issues/4412) +- Toolbar::setFiles\(\) requires int \[strict\_types=1\] [\#4408](https://github.com/codeigniter4/CodeIgniter4/issues/4408) +- FeatureTest currently supports file testing? [\#4405](https://github.com/codeigniter4/CodeIgniter4/issues/4405) +- Bug: set404Override now working in group rotes [\#4400](https://github.com/codeigniter4/CodeIgniter4/issues/4400) +- Dynamic URL [\#4394](https://github.com/codeigniter4/CodeIgniter4/issues/4394) +- ErrorException preg\_replace\_callback\(\): Unknown modifier '{' SYSTEMPATH/View/Parser.php at line 584 [\#4367](https://github.com/codeigniter4/CodeIgniter4/issues/4367) +- Feature: In HTTP Feature Testing, delivering in body in application/json format [\#4362](https://github.com/codeigniter4/CodeIgniter4/issues/4362) +- \[Dev\] Database Live Tests should depart from using deprecated CIDatabaseTestCase [\#4351](https://github.com/codeigniter4/CodeIgniter4/issues/4351) +- Bug: Migration in module \(different namespace\) do not find migrations [\#4348](https://github.com/codeigniter4/CodeIgniter4/issues/4348) +- Bug: getVar does not look at $\_SESSION as documentation suggests [\#4284](https://github.com/codeigniter4/CodeIgniter4/issues/4284) +- QBSelect, QBFrom, other properties cannot be accessed, modified from the model. [\#4255](https://github.com/codeigniter4/CodeIgniter4/issues/4255) +- Dev: Restrictions on trait "ResponseTrait" [\#4238](https://github.com/codeigniter4/CodeIgniter4/issues/4238) +- ResponseTrait trait \> Can the description support array? [\#4237](https://github.com/codeigniter4/CodeIgniter4/issues/4237) +- Feature: add old data in afterUpdate model event [\#4234](https://github.com/codeigniter4/CodeIgniter4/issues/4234) +- Dev: [\#4233](https://github.com/codeigniter4/CodeIgniter4/issues/4233) +- Cache unable to write to /var/www/html/ci4test/writable/cache/ [\#4227](https://github.com/codeigniter4/CodeIgniter4/issues/4227) +- Documentation: multiple databases setup in the ENV also need to be setup in the database config file [\#4218](https://github.com/codeigniter4/CodeIgniter4/issues/4218) +- Documentation: $this-\>request-\>setLocale\(\) is missing in documentation [\#4091](https://github.com/codeigniter4/CodeIgniter4/issues/4091) +- vars in .env sometimes returns null [\#3992](https://github.com/codeigniter4/CodeIgniter4/issues/3992) +- parseRequestURI dose not override globals\['server'\] ? [\#3976](https://github.com/codeigniter4/CodeIgniter4/issues/3976) +- Feature: Spark header Suppression [\#3918](https://github.com/codeigniter4/CodeIgniter4/issues/3918) +- Feature: AJAX filters don't work [\#2314](https://github.com/codeigniter4/CodeIgniter4/issues/2314) +- Request: Bulk route filters with parameters [\#2078](https://github.com/codeigniter4/CodeIgniter4/issues/2078) +- Need a global way to set config values dynamically [\#1661](https://github.com/codeigniter4/CodeIgniter4/issues/1661) +- Feature Request : support the db config instead of .env while the core is initialzed [\#1618](https://github.com/codeigniter4/CodeIgniter4/issues/1618) +- TODO Database BaseConnection needs better connections [\#1253](https://github.com/codeigniter4/CodeIgniter4/issues/1253) + +**Merged pull requests:** + +- Fix nullable type not showing in SQL string [\#4696](https://github.com/codeigniter4/CodeIgniter4/pull/4696) ([paulbalandan](https://github.com/paulbalandan)) +- Add reference to cache repo [\#4694](https://github.com/codeigniter4/CodeIgniter4/pull/4694) ([MGatner](https://github.com/MGatner)) +- Allow CI Environments [\#4692](https://github.com/codeigniter4/CodeIgniter4/pull/4692) ([MGatner](https://github.com/MGatner)) +- Add URI cast [\#4691](https://github.com/codeigniter4/CodeIgniter4/pull/4691) ([MGatner](https://github.com/MGatner)) +- MockCache::getCacheInfo\(\) [\#4689](https://github.com/codeigniter4/CodeIgniter4/pull/4689) ([MGatner](https://github.com/MGatner)) +- Remove Psr\Cache [\#4688](https://github.com/codeigniter4/CodeIgniter4/pull/4688) ([MGatner](https://github.com/MGatner)) +- Spacing issues [\#4686](https://github.com/codeigniter4/CodeIgniter4/pull/4686) ([MGatner](https://github.com/MGatner)) +- \[Rector\] Update rector 0.11.2 and phpstan 0.12.86 [\#4685](https://github.com/codeigniter4/CodeIgniter4/pull/4685) ([samsonasik](https://github.com/samsonasik)) +- Optimize CommandRunner and Commands [\#4683](https://github.com/codeigniter4/CodeIgniter4/pull/4683) ([paulbalandan](https://github.com/paulbalandan)) +- Revert Actions minor version [\#4682](https://github.com/codeigniter4/CodeIgniter4/pull/4682) ([MGatner](https://github.com/MGatner)) +- Revert Actions minor versioning [\#4681](https://github.com/codeigniter4/CodeIgniter4/pull/4681) ([MGatner](https://github.com/MGatner)) +- Bump shivammathur/setup-php from 2 to 2.11.0 [\#4679](https://github.com/codeigniter4/CodeIgniter4/pull/4679) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump actions/checkout from 2 to 2.3.4 [\#4678](https://github.com/codeigniter4/CodeIgniter4/pull/4678) ([dependabot[bot]](https://github.com/apps/dependabot)) +- \[Rector\] Update rector to 0.10.22, remove symplify/composer-json-manipulator [\#4677](https://github.com/codeigniter4/CodeIgniter4/pull/4677) ([samsonasik](https://github.com/samsonasik)) +- URL Functions [\#4675](https://github.com/codeigniter4/CodeIgniter4/pull/4675) ([MGatner](https://github.com/MGatner)) +- Remove unused imports [\#4674](https://github.com/codeigniter4/CodeIgniter4/pull/4674) ([paulbalandan](https://github.com/paulbalandan)) +- Split URL Helper tests [\#4672](https://github.com/codeigniter4/CodeIgniter4/pull/4672) ([MGatner](https://github.com/MGatner)) +- \[Rector\] Apply Rector: RemoveUnusedPrivatePropertyRector [\#4671](https://github.com/codeigniter4/CodeIgniter4/pull/4671) ([samsonasik](https://github.com/samsonasik)) +- \[UG\] update line number for "managing apps" [\#4670](https://github.com/codeigniter4/CodeIgniter4/pull/4670) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Add setLocale to UG [\#4669](https://github.com/codeigniter4/CodeIgniter4/pull/4669) ([MGatner](https://github.com/MGatner)) +- UTC Time from timestamp [\#4668](https://github.com/codeigniter4/CodeIgniter4/pull/4668) ([MGatner](https://github.com/MGatner)) +- PSR: Cache [\#4667](https://github.com/codeigniter4/CodeIgniter4/pull/4667) ([MGatner](https://github.com/MGatner)) +- Limit cache filenames [\#4666](https://github.com/codeigniter4/CodeIgniter4/pull/4666) ([MGatner](https://github.com/MGatner)) +- Use descriptive failure message for `assertLogged` [\#4665](https://github.com/codeigniter4/CodeIgniter4/pull/4665) ([paulbalandan](https://github.com/paulbalandan)) +- \[Rector\] Use $containerConfigurator-\>import\(\) instead of "sets" Option [\#4664](https://github.com/codeigniter4/CodeIgniter4/pull/4664) ([samsonasik](https://github.com/samsonasik)) +- Update rector/rector requirement from 0.10.19 to 0.10.21 [\#4663](https://github.com/codeigniter4/CodeIgniter4/pull/4663) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Spark header suppression [\#4661](https://github.com/codeigniter4/CodeIgniter4/pull/4661) ([MGatner](https://github.com/MGatner)) +- Registrar and .env priority [\#4659](https://github.com/codeigniter4/CodeIgniter4/pull/4659) ([MGatner](https://github.com/MGatner)) +- Reset Single Service [\#4657](https://github.com/codeigniter4/CodeIgniter4/pull/4657) ([MGatner](https://github.com/MGatner)) +- Unify migration message format for `migrate` and `migrate:rollback` [\#4656](https://github.com/codeigniter4/CodeIgniter4/pull/4656) ([paulbalandan](https://github.com/paulbalandan)) +- \[Scripts\] Make sure bash script still works in Windows [\#4655](https://github.com/codeigniter4/CodeIgniter4/pull/4655) ([paulbalandan](https://github.com/paulbalandan)) +- change instance\(\) to createFromInstance\(\) [\#4654](https://github.com/codeigniter4/CodeIgniter4/pull/4654) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Add branch alias for develop branch [\#4652](https://github.com/codeigniter4/CodeIgniter4/pull/4652) ([paulbalandan](https://github.com/paulbalandan)) +- Refactor URI detection [\#4651](https://github.com/codeigniter4/CodeIgniter4/pull/4651) ([MGatner](https://github.com/MGatner)) +- \[Scipts\] Ensure admin/setup.sh exists before run bash admin/setup.sh [\#4650](https://github.com/codeigniter4/CodeIgniter4/pull/4650) ([samsonasik](https://github.com/samsonasik)) +- Update rector/rector requirement from 0.10.17 to 0.10.19 [\#4649](https://github.com/codeigniter4/CodeIgniter4/pull/4649) ([dependabot[bot]](https://github.com/apps/dependabot)) +- \[ci skip\] Remove ajax filter reference from docs. Fixes \#2314 [\#4648](https://github.com/codeigniter4/CodeIgniter4/pull/4648) ([lonnieezell](https://github.com/lonnieezell)) +- Internal URI handling [\#4646](https://github.com/codeigniter4/CodeIgniter4/pull/4646) ([MGatner](https://github.com/MGatner)) +- URI::removeDotSegments\(\) [\#4644](https://github.com/codeigniter4/CodeIgniter4/pull/4644) ([MGatner](https://github.com/MGatner)) +- \[Rector\] Pin "nikic/php-parser": "4.10.4" [\#4642](https://github.com/codeigniter4/CodeIgniter4/pull/4642) ([samsonasik](https://github.com/samsonasik)) +- Update to psr/log v1.1.4 [\#4641](https://github.com/codeigniter4/CodeIgniter4/pull/4641) ([paulbalandan](https://github.com/paulbalandan)) +- Update rector/rector requirement from 0.10.15 to 0.10.17 [\#4639](https://github.com/codeigniter4/CodeIgniter4/pull/4639) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Update MockCache [\#4638](https://github.com/codeigniter4/CodeIgniter4/pull/4638) ([MGatner](https://github.com/MGatner)) +- Cache Key Validation [\#4637](https://github.com/codeigniter4/CodeIgniter4/pull/4637) ([MGatner](https://github.com/MGatner)) +- \[Cache\] Allow covariant returns and optimize code [\#4635](https://github.com/codeigniter4/CodeIgniter4/pull/4635) ([paulbalandan](https://github.com/paulbalandan)) +- Refactor ComposerScripts [\#4634](https://github.com/codeigniter4/CodeIgniter4/pull/4634) ([paulbalandan](https://github.com/paulbalandan)) +- Expand Time for interface [\#4633](https://github.com/codeigniter4/CodeIgniter4/pull/4633) ([MGatner](https://github.com/MGatner)) +- Patch Log code [\#4631](https://github.com/codeigniter4/CodeIgniter4/pull/4631) ([MGatner](https://github.com/MGatner)) +- Cache Returns Types [\#4630](https://github.com/codeigniter4/CodeIgniter4/pull/4630) ([MGatner](https://github.com/MGatner)) +- Cache getMetadata\(\) Format [\#4629](https://github.com/codeigniter4/CodeIgniter4/pull/4629) ([MGatner](https://github.com/MGatner)) +- fix\(cache\): add check for redis empty results in deleteMatching [\#4628](https://github.com/codeigniter4/CodeIgniter4/pull/4628) ([yassinedoghri](https://github.com/yassinedoghri)) +- Update rector/rector requirement from 0.10.12 to 0.10.15 [\#4627](https://github.com/codeigniter4/CodeIgniter4/pull/4627) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Add fallback for Config\Cookie [\#4625](https://github.com/codeigniter4/CodeIgniter4/pull/4625) ([paulbalandan](https://github.com/paulbalandan)) +- \[Test\] Use @codeCoverageIgnore for deprecated class/method [\#4623](https://github.com/codeigniter4/CodeIgniter4/pull/4623) ([samsonasik](https://github.com/samsonasik)) +- Fix: Nested sections rendering [\#4622](https://github.com/codeigniter4/CodeIgniter4/pull/4622) ([iRedds](https://github.com/iRedds)) +- Update rector/rector requirement from 0.10.11 to 0.10.12 [\#4621](https://github.com/codeigniter4/CodeIgniter4/pull/4621) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Update phpstan/phpstan requirement from 0.12.84 to 0.12.85 [\#4620](https://github.com/codeigniter4/CodeIgniter4/pull/4620) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump actions/github-script from v4.0.1 to v4.0.2 [\#4614](https://github.com/codeigniter4/CodeIgniter4/pull/4614) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Update rector/rector requirement from 0.10.9 to 0.10.11 [\#4613](https://github.com/codeigniter4/CodeIgniter4/pull/4613) ([dependabot[bot]](https://github.com/apps/dependabot)) +- \[Rector\] Refactor UnderscoreToCamelCaseVariableNameRector with latest Rector compatible code [\#4612](https://github.com/codeigniter4/CodeIgniter4/pull/4612) ([samsonasik](https://github.com/samsonasik)) +- \[Feat\]\[Autoloader\] Allow autoloading non-class files [\#4611](https://github.com/codeigniter4/CodeIgniter4/pull/4611) ([paulbalandan](https://github.com/paulbalandan)) +- failValidationError can take an array of errors [\#4609](https://github.com/codeigniter4/CodeIgniter4/pull/4609) ([caswell-wc](https://github.com/caswell-wc)) +- assertJsonFragment fails gracefully with invalid json [\#4608](https://github.com/codeigniter4/CodeIgniter4/pull/4608) ([caswell-wc](https://github.com/caswell-wc)) +- Non-persistent fake [\#4607](https://github.com/codeigniter4/CodeIgniter4/pull/4607) ([caswell-wc](https://github.com/caswell-wc)) +- Fix validation of array data [\#4606](https://github.com/codeigniter4/CodeIgniter4/pull/4606) ([paulbalandan](https://github.com/paulbalandan)) +- Use realpath\(\) to fix app prioritization of validation messages [\#4605](https://github.com/codeigniter4/CodeIgniter4/pull/4605) ([paulbalandan](https://github.com/paulbalandan)) +- Optimizations for Autoloader [\#4604](https://github.com/codeigniter4/CodeIgniter4/pull/4604) ([paulbalandan](https://github.com/paulbalandan)) +- format style [\#4603](https://github.com/codeigniter4/CodeIgniter4/pull/4603) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- whitespaces [\#4602](https://github.com/codeigniter4/CodeIgniter4/pull/4602) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- \[Rector\] Apply Full PHP 7.3 Rector Set List \(Skip JsonThrowOnErrorRector & StringifyStrNeedlesRector\) [\#4601](https://github.com/codeigniter4/CodeIgniter4/pull/4601) ([samsonasik](https://github.com/samsonasik)) +- Bump actions/github-script from v3 to v4.0.1 [\#4599](https://github.com/codeigniter4/CodeIgniter4/pull/4599) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Simplify Cookie Class [\#4596](https://github.com/codeigniter4/CodeIgniter4/pull/4596) ([mostafakhudair](https://github.com/mostafakhudair)) +- Fix service methods …$params type [\#4594](https://github.com/codeigniter4/CodeIgniter4/pull/4594) ([najdanovicivan](https://github.com/najdanovicivan)) +- Fix new service replacement service provider precedence on core factory implementations [\#4593](https://github.com/codeigniter4/CodeIgniter4/pull/4593) ([element-code](https://github.com/element-code)) +- Update rector/rector requirement from 0.10.6 to 0.10.9 [\#4592](https://github.com/codeigniter4/CodeIgniter4/pull/4592) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Debug/Toolbar - Memory issue fix [\#4590](https://github.com/codeigniter4/CodeIgniter4/pull/4590) ([najdanovicivan](https://github.com/najdanovicivan)) +- BaseModel - Add public getIdValue\(\) method [\#4589](https://github.com/codeigniter4/CodeIgniter4/pull/4589) ([najdanovicivan](https://github.com/najdanovicivan)) +- Feature: Escaping array dot notation [\#4588](https://github.com/codeigniter4/CodeIgniter4/pull/4588) ([iRedds](https://github.com/iRedds)) +- Update phpstan/phpstan requirement from 0.12.83 to 0.12.84 [\#4587](https://github.com/codeigniter4/CodeIgniter4/pull/4587) ([dependabot[bot]](https://github.com/apps/dependabot)) +- extra \ in tabs area [\#4586](https://github.com/codeigniter4/CodeIgniter4/pull/4586) ([jbrahy](https://github.com/jbrahy)) +- user\_guide update: remove whitespaces, convert tabs to spaces & adjust the over-extended lines [\#4585](https://github.com/codeigniter4/CodeIgniter4/pull/4585) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Fix: BaseModel. Removed duplicate code. [\#4581](https://github.com/codeigniter4/CodeIgniter4/pull/4581) ([iRedds](https://github.com/iRedds)) +- Change Entity Namespace [\#4577](https://github.com/codeigniter4/CodeIgniter4/pull/4577) ([mostafakhudair](https://github.com/mostafakhudair)) +- \[Rector\] Remove RemoveDefaultArgumentValueRector [\#4576](https://github.com/codeigniter4/CodeIgniter4/pull/4576) ([samsonasik](https://github.com/samsonasik)) +- \[Rector\] Add Comment for reason RemoveDefaultArgumentValueRector copied to utils/Rector [\#4575](https://github.com/codeigniter4/CodeIgniter4/pull/4575) ([samsonasik](https://github.com/samsonasik)) +- \[Rector\] Copy RemoveDefaultArgumentValueRector to utils/Rector [\#4574](https://github.com/codeigniter4/CodeIgniter4/pull/4574) ([samsonasik](https://github.com/samsonasik)) +- Fix: Validation::withRequest\(\) with Content-Type: multipart/form-data [\#4571](https://github.com/codeigniter4/CodeIgniter4/pull/4571) ([iRedds](https://github.com/iRedds)) +- \[Rector\] Update Rector 0.10.6, re-enable auto imports [\#4569](https://github.com/codeigniter4/CodeIgniter4/pull/4569) ([samsonasik](https://github.com/samsonasik)) +- feat\(cache\): add deleteMatching method to remove multiple cache items [\#4567](https://github.com/codeigniter4/CodeIgniter4/pull/4567) ([yassinedoghri](https://github.com/yassinedoghri)) +- Mysql connection issue with MYSQLI\_CLIENT\_SSL\_DONT\_VERIFY\_SERVER\_CERT [\#4566](https://github.com/codeigniter4/CodeIgniter4/pull/4566) ([fedeburo](https://github.com/fedeburo)) +- Bump actions/cache from v2.1.4 to v2.1.5 [\#4564](https://github.com/codeigniter4/CodeIgniter4/pull/4564) ([dependabot[bot]](https://github.com/apps/dependabot)) +- \[Rector\] Apply Rector: UnnecessaryTernaryExpressionRector [\#4563](https://github.com/codeigniter4/CodeIgniter4/pull/4563) ([samsonasik](https://github.com/samsonasik)) +- Check intl extension loaded on check min PHP version [\#4562](https://github.com/codeigniter4/CodeIgniter4/pull/4562) ([samsonasik](https://github.com/samsonasik)) +- Prefix calls to getenv\(\) during config resolution [\#4561](https://github.com/codeigniter4/CodeIgniter4/pull/4561) ([paulbalandan](https://github.com/paulbalandan)) +- \[Rector\] Apply Rector: ChangeArrayPushToArrayAssignRector [\#4560](https://github.com/codeigniter4/CodeIgniter4/pull/4560) ([samsonasik](https://github.com/samsonasik)) +- \[Rector\] Enable check tests/system/Models [\#4557](https://github.com/codeigniter4/CodeIgniter4/pull/4557) ([samsonasik](https://github.com/samsonasik)) +- Debugging SQL Server in Actions [\#4554](https://github.com/codeigniter4/CodeIgniter4/pull/4554) ([paulbalandan](https://github.com/paulbalandan)) +- Support for masking sensitive debug data [\#4550](https://github.com/codeigniter4/CodeIgniter4/pull/4550) ([pixobit](https://github.com/pixobit)) +- Use message directly if intl is not available [\#4549](https://github.com/codeigniter4/CodeIgniter4/pull/4549) ([paulbalandan](https://github.com/paulbalandan)) +- Add compatibility for strict types [\#4548](https://github.com/codeigniter4/CodeIgniter4/pull/4548) ([paulbalandan](https://github.com/paulbalandan)) +- Removes deprecated settings in env file [\#4547](https://github.com/codeigniter4/CodeIgniter4/pull/4547) ([paulbalandan](https://github.com/paulbalandan)) +- Fix wrong argument passed in doc [\#4546](https://github.com/codeigniter4/CodeIgniter4/pull/4546) ([paulbalandan](https://github.com/paulbalandan)) +- Fix dot notation for if\_exist [\#4545](https://github.com/codeigniter4/CodeIgniter4/pull/4545) ([paulbalandan](https://github.com/paulbalandan)) +- Relocate cookie exception [\#4544](https://github.com/codeigniter4/CodeIgniter4/pull/4544) ([mostafakhudair](https://github.com/mostafakhudair)) +- \[Rector\] Apply RemoveDefaultArgumentValueRector [\#4543](https://github.com/codeigniter4/CodeIgniter4/pull/4543) ([samsonasik](https://github.com/samsonasik)) +- Fix html formatting for exceptions and errors [\#4542](https://github.com/codeigniter4/CodeIgniter4/pull/4542) ([musmanikram](https://github.com/musmanikram)) +- Create Config::Cookie Class [\#4508](https://github.com/codeigniter4/CodeIgniter4/pull/4508) ([mostafakhudair](https://github.com/mostafakhudair)) + +## [v4.1.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.0) (2021-02-01) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.0...v4.1.1) + +**Fixed bugs:** + +- Fixed an issue where **.gitattributes** was preventing framework downloads + +## [v4.1.0](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.0) (2021-01-31) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.5...v4.1.0) + +**Implemented enhancements:** + +- Rector 0.9 fixes [\#4196](https://github.com/codeigniter4/CodeIgniter4/issues/#4196) +- Cannot declare class Config\App error on running PHPUnit [\#4114](https://github.com/codeigniter4/CodeIgniter4/issues/4114) +- Backfill non-optional parameters (https://github.com/codeigniter4/CodeIgniter4/pull/3938) +- Change deprecated assertFileNotExists (https://github.com/codeigniter4/CodeIgniter4/pull/3862) + +For the changelog of v4.0, see [CHANGELOG_4.0.md](./CHANGELOG_4.0.md). diff --git a/changelogs/CHANGELOG_4.2.md b/changelogs/CHANGELOG_4.2.md new file mode 100644 index 000000000000..f0b230bc09c6 --- /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 + +For the changelog of v4.1, see [CHANGELOG_4.1.md](./CHANGELOG_4.1.md). diff --git a/changelogs/CHANGELOG_4.3.md b/changelogs/CHANGELOG_4.3.md new file mode 100644 index 000000000000..9c1345dbbd97 --- /dev/null +++ b/changelogs/CHANGELOG_4.3.md @@ -0,0 +1,326 @@ +# Changelog 4.3 + +## [v4.3.8](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.8) (2023-08-25) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.7...v4.3.8) + +### Fixed Bugs + +* fix: [Pager] knocks down variables for View by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7758 +* fix: Model::insertBatch() causes error to non auto increment table by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7759 +* fix: [Model] updateBatch() may generate invalid SQL statement by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7787 +* fix: Model inserts cast $primaryKey value when using Entity by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7806 +* fix: instances of Validation rules are incremented each time `run()` is executed by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7815 +* fix: filter except empty by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7823 +* fix: `set_checkbox()` checks unchecked checkbox by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7818 + +### Refactoring + +* Normalize data provider names by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/7656 +* refactor: remove Model::$tempPrimaryKeyValue by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7760 +* Remove unused cast on RedisHandler by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/7786 + +## [v4.3.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.7) (2023-07-30) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.6...v4.3.7) + +### Breaking Changes + +* fix: FeatureTestTrait may change $params values passed to call(), and a few bug fixes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7602 +* fix: auto routing legacy and $route->add() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7690 +* fix: [Model] setValidationRule() cannot use with ruleGroup by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7691 + +### Fixed Bugs + +* docs: fix incorrect description on RedirectException (1) by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7629 +* docs: fix incorrect description on RedirectException (2) by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7633 +* fix: skip http proxy added header by @jozefrebjak in https://github.com/codeigniter4/CodeIgniter4/pull/7622 +* fix: number_to_roman() param type by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7640 +* fix: [Auto Routing Improved] feature testing may use incorrect param count by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7597 +* fix: `url_to()` error message by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7652 +* fix: [ViewCells] caching by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7663 +* fix: [ViewCells] when there are cells with the same short name, only the first cell is loaded by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7686 +* Fix number comparison in number_to_amount() in number_helper.php by @sba in https://github.com/codeigniter4/CodeIgniter4/pull/7701 +* fix: wrong Config classname to config() in Toolbar by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7735 +* fix: $sensitiveDataInTrace does not work by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7725 + +### Enhancements + +* Remove PHPStan from pre-commit hook by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/7618 + +### Refactoring + +* refactor: remove unused property in Encryption\Handlers\BaseHandler by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7579 +* refactor: use ::class to config() param by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7611 +* refactor: remove unused non-empty array in RequestTrait by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7620 +* refactor: [Cache] simplify code of `FileHandler::getItem()` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/7667 +* refactor: replace `config(Paths::class)` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7702 + +## [v4.3.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.6) (2023-06-18) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.5...v4.3.6) + +### Breaking Changes + +* fix: [Validation] DBGroup is ignored when checking the value of a placeholder by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7549 +* fix: [Auto Routing Improved] feature testing may not find controller/method by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7543 + +### Fixed Bugs + +* fix: feature test with validation by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7548 +* fix: [Postgre] Semicolon in the connection parameters break the DSN string by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7552 +* fix: [QueryBuilder] incorrect SQL without space before "ON DUPLICATE KEY UPDATE" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7564 +* fix: wrong classname in exception message in Cell by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7569 +* fix: `imagecreatefrompng()` gd-png: libpng warning by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/7570 + +### Refactoring + +* refactor: remove unneeded code in IncomingRequest by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7525 +* refactor: View by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7534 +* refactor: [Entity] fix incorrect return value by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7542 +* refactor: Database::initDriver() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7553 +* refactor: remove Factories::models() by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/7566 +* refactor: Validation::processRules() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7565 +* refactor: [Auto Routing Improved] ensure $httpVerb is lower case by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7575 + +## [v4.3.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.5) (2023-05-21) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.4...v4.3.5) + +### SECURITY + +* *Remote Code Execution Vulnerability in Validation Placeholders* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-m6m8-6gq8-c9fj) for more information. +* fix: Session::stop() does not destroy session by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7503 + +### Fixed Bugs + +* docs: remove incorrect @property in ResponseTrait by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7495 +* fix: validation error when a closure is used in combination with permit_empty or if_exist rules by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7492 +* fix: standardize behavior of `make:cell` and `Cells` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/7481 +* fix: PostgreSQL getVersion() logic by @marekmosna in https://github.com/codeigniter4/CodeIgniter4/pull/7488 +* fix: PostgreSQL getVersion() output by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7509 + +### Enhancements + +* feat: user guide dark mode by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7463 + +### Refactoring + +* refactor: Entity variable by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7499 + +## [v4.3.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.4) (2023-04-27) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.3...v4.3.4) + +### Breaking Changes + +* fix: redirect status code by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7445 +* fix: [SQLite3][Postgres][SQLSRV][OCI8] Forge::modifyColumn() changes NULL constraint incorrectly by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7371 + +### Fixed Bugs + +* fix: view cell cannot locate the auto-generated view file by @sammyskills in https://github.com/codeigniter4/CodeIgniter4/pull/7392 +* fix: CURLRequest - clear response headers between requests by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/7398 +* fix: [Auto Routing Improved] spark routes shows invalid routes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7419 +* fix: remove $insertID in make:model template by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7443 +* fix: add missing 'make:cell' in app/Config/Generators.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7458 + +### Refactoring + +* refactor: Security::getPostedToken() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7377 + +## [v4.3.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.3) (2023-03-26) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.3.2...v4.3.3) + +### Fixed Bugs + +* docs: fix $systemDirectory path in existing project. by @jozefrebjak in https://github.com/codeigniter4/CodeIgniter4/pull/7289 +* docs: fix message.rst and improve content_negotiation.rst by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7280 +* fix: Encryption CI3 compatibility by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7273 +* fix: [QueryBuilder] RawSql causes error when using like() and countAllResults() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7277 +* fix: handling of null bytes in `Exceptions::renderBacktrace()` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/7306 +* fix: incorrect metadata querying of Redis cache by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/7307 +* fix: [Email] add missing TLS 1.3 support by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7317 +* docs: add warning to random_string() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7333 +* fix: random_string() numeric by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7336 +* docs: add note for addColumn() and NULL by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7342 +* fix: respondNoContent() returns Kint script in development mode by @anggadarkprince in https://github.com/codeigniter4/CodeIgniter4/pull/7347 +* fix: use first exception in exceptionHandler() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7341 +* fix: random_string() alpha alnum nozero by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7344 +* fix: migrate:rollback -b negative number by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7350 +* fix: site_url() does not support protocol-relative links by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7353 +* docs: add uri_string() BC in v4.3.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7356 +* fix: Cache FileHandler error when there is a folder in cache dir by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7361 + +### Refactoring + +* refactor: consistent header name case by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7299 + +## [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 +* fix: remove parameter $relative in `uri_string()` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7135 + +### 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: [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) + +### Fixed Bugs + +* fix: Email config in the .env doesn't appear as expected by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7078 +* fix: TypeError in Validation is_unique/is_not_unique by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7085 +* fix: revert method name resetQuery() changed accidentally by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7083 +* fix: handling float in Validation Strcit Rules (greater_than, greater_than_equal_to, less_than, less_than_equal_to) by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7098 +* docs: add missing instruction for Config/Exceptions in PHP 8.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7100 +* fix: Call to undefined method Composer\InstalledVersions::getAllRawData() error by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/7107 + +### Refactoring + +* [Rector] Enable AddDefaultValueForUndefinedVariableRector by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/7088 + +## [v4.3.0](https://github.com/codeigniter4/CodeIgniter4/tree/v4.3.0) (2023-01-10) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.12...v4.3.0) + +### Breaking Changes + +* fix: throws DatabaseException in DB connections by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6163 +* config: DB Error always throws Exception CI_DBUG by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6183 +* Config Property Types by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6214 +* refactor: loading app/Config/routes.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6293 +* fix: exceptionHandler may return invalid HTTP status code by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6228 +* feat: add Form helpers for Validation Errors by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6384 +* fix: ValidationInterface by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6253 +* fix: types in database classes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6527 +* fix: ResponseInterface (1) by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6556 +* Improve BaseConnection::getForeignKeyData() and Forge::addForeignKey() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6468 +* Refactor BaseBuilder *Batch() Methods by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6536 +* refactor: remove `type="text/javascript"` in `' - . '' - . '' + . '' + . '' . $kintScript . PHP_EOL; - if (strpos($response->getBody(), '') !== false) { - $response->setBody(preg_replace('//', '' . $script, $response->getBody(), 1)); + if (str_contains((string) $response->getBody(), '')) { + $response->setBody( + preg_replace( + '//', + '' . $script, + $response->getBody(), + 1, + ), + ); return; } @@ -409,13 +455,13 @@ public function prepare(?RequestInterface $request = null, ?ResponseInterface $r * * @codeCoverageIgnore */ - public function respond() + public function respond(): void { if (ENVIRONMENT === 'testing') { return; } - $request = Services::request(); + $request = service('request'); // If the request contains '?debugbar then we're // simply returning the loading script @@ -423,9 +469,9 @@ public function respond() header('Content-Type: application/javascript'); ob_start(); - include $this->config->viewsPath . 'toolbarloader.js.php'; + include $this->config->viewsPath . 'toolbarloader.js'; $output = ob_get_clean(); - $output = substr($output, 8, -10); // trim the script tags + $output = str_replace('{url}', rtrim(site_url(), '/'), $output); echo $output; exit; @@ -433,7 +479,7 @@ public function respond() // Otherwise, if it includes ?debugbar_time, then // we should return the entire debugbar. - if ($request->getGet('debugbar_time')) { + if ($request->getGet('debugbar_time') !== null) { helper('security'); // Negotiate the content-type to format the output @@ -464,11 +510,11 @@ protected function format(string $data, string $format = 'html'): string { $data = json_decode($data, true); - if ($this->config->maxHistory !== 0) { + if (preg_match('/\d+\.\d{6}/s', (string) service('request')->getGet('debugbar_time'), $debugbarTime)) { $history = new History(); $history->setFiles( - (int) Services::request()->getGet('debugbar_time'), - $this->config->maxHistory + $debugbarTime[0], + $this->config->maxHistory, ); $data['collectors'][] = $history->getAsArray(); @@ -480,7 +526,7 @@ protected function format(string $data, string $format = 'html'): string case 'html': $data['styles'] = []; extract($data); - $parser = Services::parser($this->config->viewsPath, null, false); + $parser = service('parser', $this->config->viewsPath, null, false); ob_start(); include $this->config->viewsPath . 'toolbar.tpl.php'; $output = ob_get_clean(); @@ -499,4 +545,76 @@ protected function format(string $data, string $format = 'html'): string return $output; } + + /** + * Checks if the native PHP headers indicate a non-HTML response + * or if headers are already sent. + */ + protected function hasNativeHeaderConflict(): bool + { + // If headers are sent, we can't inject HTML. + if (headers_sent()) { + return true; + } + + // Native Header Inspection + foreach (headers_list() as $header) { + $lowerHeader = strtolower($header); + + $isNonHtmlContent = str_starts_with($lowerHeader, 'content-type:') && ! str_contains($lowerHeader, 'text/html'); + $isAttachment = str_starts_with($lowerHeader, 'content-disposition:') && str_contains($lowerHeader, 'attachment'); + + if ($isNonHtmlContent || $isAttachment) { + return true; + } + } + + return false; + } + + /** + * Determine if the toolbar should be disabled based on the request headers. + * + * This method allows checking both the presence of headers and their expected values. + * Useful for AJAX, HTMX, Unpoly, Turbo, etc., where partial HTML responses are expected. + * + * @return bool True if any header condition matches; false otherwise. + */ + private function shouldDisableToolbar(IncomingRequest $request): bool + { + // Fallback for older installations where the config option is missing (e.g. after upgrading from a previous version). + $headers = $this->config->disableOnHeaders ?? ['X-Requested-With' => 'xmlhttprequest']; // @phpstan-ignore nullCoalesce.property + + foreach ($headers as $headerName => $expectedValue) { + if (! $request->hasHeader($headerName)) { + continue; // header not present, skip + } + + // If expectedValue is null, only presence is enough + if ($expectedValue === null) { + return true; + } + + $headerValue = strtolower($request->getHeaderLine($headerName)); + + if ($headerValue === strtolower($expectedValue)) { + return true; + } + } + + return false; + } + + /** + * Reset all collectors for worker mode. + * Calls reset() on collectors that support it. + */ + public function reset(): void + { + foreach ($this->collectors as $collector) { + if (method_exists($collector, 'reset')) { + $collector->reset(); + } + } + } } diff --git a/system/Debug/Toolbar/Collectors/BaseCollector.php b/system/Debug/Toolbar/Collectors/BaseCollector.php index 5d8d39ff169f..81cc631f98e7 100644 --- a/system/Debug/Toolbar/Collectors/BaseCollector.php +++ b/system/Debug/Toolbar/Collectors/BaseCollector.php @@ -1,5 +1,7 @@ 'baz' * ], * ]; + * + * @return array|null */ public function getVarData() { @@ -174,17 +176,19 @@ public function display() } /** - * Clean Path - * * This makes nicer looking paths for the error output. + * + * @deprecated Use the dedicated `clean_path()` function. */ public function cleanPath(string $file): string { - return Exceptions::cleanPath($file); + return clean_path($file); } /** * Gets the "badge" value for the button. + * + * @return int|null */ public function getBadgeValue() { diff --git a/system/Debug/Toolbar/Collectors/Config.php b/system/Debug/Toolbar/Collectors/Config.php index faf7a19b7391..80673f979f6c 100644 --- a/system/Debug/Toolbar/Collectors/Config.php +++ b/system/Debug/Toolbar/Collectors/Config.php @@ -1,5 +1,7 @@ ENVIRONMENT, 'baseURL' => $config->baseURL, 'timezone' => app_timezone(), - 'locale' => Services::request()->getLocale(), + 'locale' => service('request')->getLocale(), 'cspEnabled' => $config->CSPEnabled, ]; } diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php index 3686dd1dea76..fdf961d2f974 100644 --- a/system/Debug/Toolbar/Collectors/Database.php +++ b/system/Debug/Toolbar/Collectors/Database.php @@ -1,5 +1,7 @@ maxQueries ?: 100; if (count(static::$queries) < $max) { - static::$queries[] = $query; + $queryString = $query->getQuery(); + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + if (! is_cli()) { + // when called in the browser, the first two trace arrays + // are from the DB event trigger, which are unneeded + $backtrace = array_slice($backtrace, 2); + } + + static::$queries[] = [ + 'query' => $query, + 'string' => $queryString, + 'duplicate' => in_array($queryString, array_column(static::$queries, 'string'), true), + 'trace' => $backtrace, + ]; } } @@ -110,8 +133,9 @@ protected function formatTimelineData(): array $data[] = [ 'name' => 'Query', 'component' => 'Database', - 'start' => $query->getStartTime(true), - 'duration' => $query->getDuration(), + 'start' => $query['query']->getStartTime(true), + 'duration' => $query['query']->getDuration(), + 'query' => $query['query']->debugToolbarDisplay(), ]; } @@ -123,14 +147,54 @@ protected function formatTimelineData(): array */ public function display(): array { - $data['queries'] = array_map(static function (Query $query) { + return ['queries' => array_map(static function (array $query): array { + $isDuplicate = $query['duplicate'] === true; + + $firstNonSystemLine = ''; + + foreach ($query['trace'] as $index => &$line) { + // simplify file and line + if (isset($line['file'])) { + $line['file'] = clean_path($line['file']) . ':' . $line['line']; + unset($line['line']); + } else { + $line['file'] = '[internal function]'; + } + + // find the first trace line that does not originate from `system/` + if ($firstNonSystemLine === '' && ! str_contains($line['file'], 'SYSTEMPATH')) { + $firstNonSystemLine = $line['file']; + } + + // simplify function call + if (isset($line['class'])) { + $line['function'] = $line['class'] . $line['type'] . $line['function']; + unset($line['class'], $line['type']); + } + + if (strrpos($line['function'], '{closure}') === false) { + $line['function'] .= '()'; + } + + $line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function']; + + // add index numbering padded with nonbreaking space + $indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT); + $indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded); + + $line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4); + } + return [ - 'duration' => ((float) $query->getDuration(5) * 1000) . ' ms', - 'sql' => $query->debugToolbarDisplay(), + 'hover' => $isDuplicate ? 'This query was called more than once.' : '', + 'class' => $isDuplicate ? 'duplicate' : '', + 'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms', + 'sql' => $query['query']->debugToolbarDisplay(), + 'trace' => $query['trace'], + 'trace-file' => $firstNonSystemLine, + 'qid' => md5($query['query'] . Time::now()->format('0.u00 U')), ]; - }, static::$queries); - - return $data; + }, static::$queries)]; } /** @@ -151,14 +215,17 @@ public function getTitleDetails(): string $this->getConnections(); $queryCount = count(static::$queries); + $uniqueCount = count(array_filter(static::$queries, static fn ($query): bool => $query['duplicate'] === false)); $connectionCount = count($this->connections); return sprintf( - '(%d Quer%s across %d Connection%s)', + '(%d total Quer%s, %d %s unique across %d Connection%s)', $queryCount, $queryCount > 1 ? 'ies' : 'y', + $uniqueCount, + $uniqueCount > 1 ? 'of them' : '', $connectionCount, - $connectionCount > 1 ? 's' : '' + $connectionCount > 1 ? 's' : '', ); } @@ -167,7 +234,7 @@ public function getTitleDetails(): string */ public function isEmpty(): bool { - return empty(static::$queries); + return static::$queries === []; } /** @@ -183,8 +250,17 @@ public function icon(): string /** * Gets the connections from the database config */ - private function getConnections() + private function getConnections(): void { $this->connections = \Config\Database::getConnections(); } + + /** + * Reset collector state for worker mode. + * Clears collected queries between requests. + */ + public function reset(): void + { + static::$queries = []; + } } diff --git a/system/Debug/Toolbar/Collectors/Events.php b/system/Debug/Toolbar/Collectors/Events.php index b8c59f4566bb..173c71a32733 100644 --- a/system/Debug/Toolbar/Collectors/Events.php +++ b/system/Debug/Toolbar/Collectors/Events.php @@ -1,5 +1,7 @@ viewer = Services::renderer(); - } - /** * Child classes should implement this to return the timeline data * formatted for correct usage. @@ -74,12 +58,12 @@ protected function formatTimelineData(): array { $data = []; - $rows = $this->viewer->getPerformanceData(); // @phpstan-ignore-line + $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/Files.php b/system/Debug/Toolbar/Collectors/Files.php index d6aefd2c3fe9..a573388f960e 100644 --- a/system/Debug/Toolbar/Collectors/Files.php +++ b/system/Debug/Toolbar/Collectors/Files.php @@ -1,5 +1,7 @@ cleanPath($file); + $path = clean_path($file); - if (strpos($path, 'SYSTEMPATH') !== false) { + if (str_contains($path, 'SYSTEMPATH')) { $coreFiles[] = [ - 'name' => basename($file), 'path' => $path, + 'name' => basename($file), ]; } else { $userFiles[] = [ - 'name' => basename($file), 'path' => $path, + 'name' => basename($file), ]; } } diff --git a/system/Debug/Toolbar/Collectors/History.php b/system/Debug/Toolbar/Collectors/History.php index 3c0f4268f65e..583b350cb4b6 100644 --- a/system/Debug/Toolbar/Collectors/History.php +++ b/system/Debug/Toolbar/Collectors/History.php @@ -1,5 +1,7 @@ $time, - 'datetime' => date('Y-m-d H:i:s', $time), + 'datetime' => DateTime::createFromFormat('U.u', $time)->format('Y-m-d H:i:s.u'), 'active' => $time === $current, 'status' => $contents->vars->response->statusCode, 'method' => $contents->method, @@ -122,7 +130,7 @@ public function getBadgeValue(): int */ public function isEmpty(): bool { - return empty($this->files); + return $this->files === []; } /** diff --git a/system/Debug/Toolbar/Collectors/Logs.php b/system/Debug/Toolbar/Collectors/Logs.php index 42d7307ab9d1..92562e81e629 100644 --- a/system/Debug/Toolbar/Collectors/Logs.php +++ b/system/Debug/Toolbar/Collectors/Logs.php @@ -1,5 +1,7 @@ */ - protected $data; + protected $data = []; /** - * Returns the data of this collector to be formatted in the toolbar + * Returns the data of this collector to be formatted in the toolbar. + * + * @return array{logs: list} */ public function display(): array { @@ -66,7 +68,7 @@ public function isEmpty(): bool { $this->collectLogs(); - return empty($this->data); + return $this->data === []; } /** @@ -81,13 +83,23 @@ public function icon(): string /** * Ensures the data has been collected. + * + * @return list */ protected function collectLogs() { - if (! empty($this->data)) { + if ($this->data !== []) { return $this->data; } - return $this->data = Services::logger(true)->logCache ?? []; + $logger = service('logger'); + + if (! property_exists($logger, 'logCache')) { + return $this->data; + } + + $this->data = $logger->logCache; + + return $this->data; } } diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php index 060d8b995872..f8005065c57e 100644 --- a/system/Debug/Toolbar/Collectors/Routes.php +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -1,5 +1,7 @@ + * }>, + * routes: list + * } + * * @throws ReflectionException */ public function display(): array { - $rawRoutes = Services::routes(true); - $router = Services::router(null, null, true); - - // Matched Route - $route = $router->getMatchedRoute(); + $rawRoutes = service('routes', true); + $router = service('router', null, null, true); // Get our parameters // Closure routes @@ -65,10 +83,18 @@ public function display(): array } else { try { $method = new ReflectionMethod($router->controllerName(), $router->methodName()); - } catch (ReflectionException $e) { - // If we're here, the method doesn't exist - // and is likely calculated in _remap. - $method = new ReflectionMethod($router->controllerName(), '_remap'); + } catch (ReflectionException) { + try { + // If we're here, the method doesn't exist + // and is likely calculated in _remap. + $method = new ReflectionMethod($router->controllerName(), '_remap'); + } catch (ReflectionException) { + // If we're here, page cache is returned. The router is not executed. + return [ + 'matchedRoute' => [], + 'routes' => [], + ]; + } } } @@ -78,9 +104,13 @@ public function display(): array foreach ($rawParams as $key => $param) { $params[] = [ - 'name' => $param->getName(), + 'name' => '$' . $param->getName() . ' = ', 'value' => $router->params()[$key] ?? - ('<empty> | default: ' . var_export($param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, true)), + ' | default: ' + . var_export( + $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, + true, + ), ]; } @@ -96,32 +126,18 @@ public function display(): array ]; // Defined Routes - $routes = []; - $methods = [ - 'get', - 'head', - 'post', - 'patch', - 'put', - 'delete', - 'options', - 'trace', - 'connect', - 'cli', - ]; - - foreach ($methods as $method) { - $raw = $rawRoutes->getRoutes($method); - - foreach ($raw as $route => $handler) { - // filter for strings, as callbacks aren't displayable - if (is_string($handler)) { - $routes[] = [ - 'method' => strtoupper($method), - 'route' => $route, - 'handler' => $handler, - ]; - } + $routes = []; + + $definedRouteCollector = new DefinedRouteCollector($rawRoutes); + + foreach ($definedRouteCollector->collect() as $route) { + // filter for strings, as callbacks aren't displayable + if ($route['handler'] !== '(Closure)') { + $routes[] = [ + 'method' => strtoupper($route['method']), + 'route' => $route['route'], + 'handler' => $route['handler'], + ]; } } @@ -136,7 +152,7 @@ public function display(): array */ public function getBadgeValue(): int { - $rawRoutes = Services::routes(true); + $rawRoutes = service('routes', true); return count($rawRoutes->getRoutes()); } diff --git a/system/Debug/Toolbar/Collectors/Timers.php b/system/Debug/Toolbar/Collectors/Timers.php index cfce9e617594..d450cc5c306a 100644 --- a/system/Debug/Toolbar/Collectors/Timers.php +++ b/system/Debug/Toolbar/Collectors/Timers.php @@ -1,5 +1,7 @@ getTimers(6); foreach ($rows as $name => $info) { diff --git a/system/Debug/Toolbar/Collectors/Views.php b/system/Debug/Toolbar/Collectors/Views.php index 75fea1d6cc6d..a63e172c3b8a 100644 --- a/system/Debug/Toolbar/Collectors/Views.php +++ b/system/Debug/Toolbar/Collectors/Views.php @@ -1,5 +1,7 @@ viewer = Services::renderer(); + $this->viewer ??= service('renderer'); } /** @@ -87,9 +85,11 @@ public function __construct() */ protected function formatTimelineData(): array { + $this->initViewer(); + $data = []; - $rows = $this->viewer->getPerformanceData(); // @phpstan-ignore-line + $rows = $this->viewer->getPerformanceData(); foreach ($rows as $info) { $data[] = [ @@ -121,8 +121,9 @@ protected function formatTimelineData(): array */ public function getVarData(): array { + $this->initViewer(); + return [ - // @phpstan-ignore-next-line 'View Data' => $this->viewer->getData(), ]; } @@ -132,7 +133,9 @@ public function getVarData(): array */ public function getBadgeValue(): int { - return count($this->viewer->getPerformanceData()); // @phpstan-ignore-line + $this->initViewer(); + + return count($this->viewer->getPerformanceData()); } /** diff --git a/system/Debug/Toolbar/Views/_config.tpl b/system/Debug/Toolbar/Views/_config.tpl index 3934cf62ebee..e3235ec35b3f 100644 --- a/system/Debug/Toolbar/Views/_config.tpl +++ b/system/Debug/Toolbar/Views/_config.tpl @@ -1,48 +1,48 @@

- Read the CodeIgniter docs... + Read the CodeIgniter docs...

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeIgniter Version:{ ciVersion }
PHP Version:{ phpVersion }
PHP SAPI:{ phpSAPI }
Environment:{ environment }
Base URL: - { if $baseURL == '' } -
- The $baseURL should always be set manually to prevent possible URL personification from external parties. -
- { else } - { baseURL } - { endif } -
Timezone:{ timezone }
Locale:{ locale }
Content Security Policy Enabled:{ if $cspEnabled } Yes { else } No { endif }
CodeIgniter Version:{ ciVersion }
PHP Version:{ phpVersion }
PHP SAPI:{ phpSAPI }
Environment:{ environment }
Base URL: + { if $baseURL == '' } +
+ The $baseURL should always be set manually to prevent possible URL personification from external parties. +
+ { else } + { baseURL } + { endif } +
Timezone:{ timezone }
Locale:{ locale }
Content Security Policy Enabled:{ if $cspEnabled } Yes { else } No { endif }
diff --git a/system/Debug/Toolbar/Views/_database.tpl b/system/Debug/Toolbar/Views/_database.tpl index b5cf1a43a7d7..054dd36231c3 100644 --- a/system/Debug/Toolbar/Views/_database.tpl +++ b/system/Debug/Toolbar/Views/_database.tpl @@ -7,9 +7,19 @@ {queries} - + {duration} {! sql !} + {trace-file} + + + + + {trace} + {index}{file}
+ {function}

+ {/trace} + {/queries} diff --git a/system/Debug/Toolbar/Views/_history.tpl b/system/Debug/Toolbar/Views/_history.tpl index 9db00ecc4679..7f22f560f936 100644 --- a/system/Debug/Toolbar/Views/_history.tpl +++ b/system/Debug/Toolbar/Views/_history.tpl @@ -1,22 +1,22 @@ - - - - - - - - - + + + + + + + + + {files} - - + diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css index 7382f4d7e60a..69583597eed7 100644 --- a/system/Debug/Toolbar/Views/toolbar.css +++ b/system/Debug/Toolbar/Views/toolbar.css @@ -13,19 +13,28 @@ z-index: 10000; height: 36px; width: 36px; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; clear: both; - text-align: center; } - #debug-icon a svg { - margin: 8px; - max-width: 20px; - max-height: 20px; } - #debug-icon.fixed-top { - bottom: auto; - top: 0; } - #debug-icon .debug-bar-ndisplay { - display: none; } + text-align: center; + cursor: pointer; +} +#debug-icon a svg { + margin: 8px; + max-width: 20px; + max-height: 20px; +} +#debug-icon.fixed-top { + bottom: auto; + top: 0; +} +#debug-icon .debug-bar-ndisplay { + display: none; +} + +.debug-bar-vars { + cursor: pointer; +} #debug-bar { bottom: 0; @@ -37,213 +46,270 @@ line-height: 36px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; - font-weight: 400; } - #debug-bar h1 { - bottom: 0; - display: inline-block; - font-size: 14px; - font-weight: normal; - margin: 0 16px 0 0; - padding: 0; - position: absolute; - right: 30px; - text-align: left; - top: 0; } - #debug-bar h1 svg { - width: 16px; - margin-right: 5px; } - #debug-bar h2 { - font-size: 16px; - margin: 0; - padding: 5px 0 10px 0; } - #debug-bar h2 span { - font-size: 13px; } - #debug-bar h3 { - font-size: 12px; - font-weight: 200; - margin: 0 0 0 10px; - padding: 0; - text-transform: uppercase; } - #debug-bar p { - font-size: 12px; - margin: 0 0 0 15px; - padding: 0; } - #debug-bar a { - text-decoration: none; } - #debug-bar a:hover { - text-decoration: underline; } - #debug-bar button { - border: 1px solid; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - cursor: pointer; - line-height: 15px; } - #debug-bar button:hover { - text-decoration: underline; } - #debug-bar table { - border-collapse: collapse; - font-size: 14px; - line-height: normal; - margin: 5px 10px 15px 10px; - width: calc(100% - 10px); } - #debug-bar table strong { - font-weight: 500; } - #debug-bar table th { - display: table-cell; - font-weight: 600; - padding-bottom: 0.7em; - text-align: left; } - #debug-bar table tr { - border: none; } - #debug-bar table td { - border: none; - display: table-cell; - margin: 0; - text-align: left; } - #debug-bar table td:first-child { - max-width: 20%; } - #debug-bar table td:first-child.narrow { - width: 7em; } - #debug-bar td[data-debugbar-route] form { - display: none; } - #debug-bar td[data-debugbar-route]:hover form { - display: block; } - #debug-bar td[data-debugbar-route]:hover > div { - display: none; } - #debug-bar td[data-debugbar-route] input[type=text] { - padding: 2px; } - #debug-bar .toolbar { - display: flex; - overflow: hidden; - overflow-y: auto; - padding: 0 12px 0 12px; - white-space: nowrap; - z-index: 10000; } - #debug-bar.fixed-top { - bottom: auto; - top: 0; } - #debug-bar.fixed-top .tab { - bottom: auto; - top: 36px; } - #debug-bar #toolbar-position a, - #debug-bar #toolbar-theme a { - padding: 0 6px; - display: inline-flex; - vertical-align: top; } - #debug-bar #toolbar-position a:hover, - #debug-bar #toolbar-theme a:hover { - text-decoration: none; } - #debug-bar #debug-bar-link { - bottom: 0; - display: inline-block; - font-size: 16px; - line-height: 36px; - padding: 6px; - position: absolute; - right: 10px; - top: 0; - width: 24px; } - #debug-bar .ci-label { - display: inline-flex; - font-size: 14px; } - #debug-bar .ci-label:hover { - cursor: pointer; } - #debug-bar .ci-label a { - color: inherit; - display: flex; - letter-spacing: normal; - padding: 0 10px; - text-decoration: none; - align-items: center; } - #debug-bar .ci-label img { - margin: 6px 3px 6px 0; - width: 16px !important; } - #debug-bar .ci-label .badge { - border-radius: 12px; - -moz-border-radius: 12px; - -webkit-border-radius: 12px; - display: inline-block; - font-size: 75%; - font-weight: bold; - line-height: 12px; - margin-left: 5px; - padding: 2px 5px; - text-align: center; - vertical-align: baseline; - white-space: nowrap; } - #debug-bar .tab { - bottom: 35px; - display: none; - left: 0; - max-height: 62%; - overflow: hidden; - overflow-y: auto; - padding: 1em 2em; - position: fixed; - right: 0; - z-index: 9999; } - #debug-bar .timeline { - margin-left: 0; - width: 100%; } - #debug-bar .timeline th { - border-left: 1px solid; - font-size: 12px; - font-weight: 200; - padding: 5px 5px 10px 5px; - position: relative; - text-align: left; } - #debug-bar .timeline th:first-child { - border-left: 0; } - #debug-bar .timeline td { - border-left: 1px solid; - padding: 5px; - position: relative; } - #debug-bar .timeline td:first-child { - border-left: 0; - max-width: none; } - #debug-bar .timeline td.child-container { - padding: 0px; } - #debug-bar .timeline td.child-container .timeline { - margin: 0px; } - #debug-bar .timeline td.child-container .timeline td:first-child:not(.child-container) { - padding-left: calc(5px + 10px * var(--level)); } - #debug-bar .timeline .timer { - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - display: inline-block; - padding: 5px; - position: absolute; - top: 30%; } - #debug-bar .timeline .timeline-parent { - cursor: pointer; } - #debug-bar .timeline .timeline-parent td:first-child nav { - background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMCAxNTAiPjxwYXRoIGQ9Ik02IDdoMThsLTkgMTV6bTAgMzBoMThsLTkgMTV6bTAgNDVoMThsLTktMTV6bTAgMzBoMThsLTktMTV6bTAgMTJsMTggMThtLTE4IDBsMTgtMTgiIGZpbGw9IiM1NTUiLz48cGF0aCBkPSJNNiAxMjZsMTggMThtLTE4IDBsMTgtMTgiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSIjNTU1Ii8+PC9zdmc+") no-repeat scroll 0 0/15px 75px transparent; - background-position: 0 25%; - display: inline-block; - height: 15px; - width: 15px; - margin-right: 3px; - vertical-align: middle; } - #debug-bar .timeline .timeline-parent-open { - background-color: #DFDFDF; } - #debug-bar .timeline .timeline-parent-open td:first-child nav { - background-position: 0 75%; } - #debug-bar .timeline .child-row:hover { - background: transparent; } - #debug-bar .route-params, - #debug-bar .route-params-item { - vertical-align: top; } - #debug-bar .route-params td:first-child, - #debug-bar .route-params-item td:first-child { - font-style: italic; - padding-left: 1em; - text-align: right; } + font-weight: 400; +} +#debug-bar h1 { + display: flex; + font-weight: normal; + margin: 0 0 0 auto; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; +} +#debug-bar h1 svg { + width: 16px; + margin-right: 5px; +} +#debug-bar h2 { + font-weight: bold; + font-size: 16px; + margin: 0; + padding: 5px 0 10px 0; +} +#debug-bar h2 span { + font-size: 13px; +} +#debug-bar h3 { + font-size: 12px; + font-weight: 200; + margin: 0 0 0 10px; + padding: 0; + text-transform: uppercase; +} +#debug-bar p { + font-size: 12px; + margin: 0 0 0 15px; + padding: 0; +} +#debug-bar a { + text-decoration: none; +} +#debug-bar a:hover { + text-decoration: underline; +} +#debug-bar button { + border: 1px solid; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + cursor: pointer; + line-height: 15px; +} +#debug-bar button:hover { + text-decoration: underline; +} +#debug-bar table { + border-collapse: collapse; + font-size: 14px; + line-height: normal; + margin: 5px 10px 15px 10px; + width: calc(100% - 10px); +} +#debug-bar table strong { + font-weight: 500; +} +#debug-bar table th { + display: table-cell; + font-weight: 600; + padding-bottom: 0.7em; + text-align: left; +} +#debug-bar table tr { + border: none; +} +#debug-bar table td { + border: none; + display: table-cell; + margin: 0; + text-align: left; +} +#debug-bar table td:first-child { + max-width: 20%; +} +#debug-bar table td:first-child.narrow { + width: 7em; +} +#debug-bar td[data-debugbar-route] form { + display: none; +} +#debug-bar td[data-debugbar-route]:hover form { + display: block; +} +#debug-bar td[data-debugbar-route]:hover > div { + display: none; +} +#debug-bar td[data-debugbar-route] input[type=text] { + padding: 2px; +} +#debug-bar .toolbar { + display: flex; + overflow: hidden; + overflow-y: auto; + padding: 0 12px 0 12px; + white-space: nowrap; + z-index: 10000; +} +#debug-bar .toolbar .rotate { + animation: toolbar-rotate 9s linear infinite; +} +@keyframes toolbar-rotate { + to { + transform: rotate(360deg); + } +} +#debug-bar.fixed-top { + bottom: auto; + top: 0; +} +#debug-bar.fixed-top .tab { + bottom: auto; + top: 36px; +} +#debug-bar #toolbar-position, +#debug-bar #toolbar-theme { + padding: 0 6px; + display: inline-flex; + vertical-align: top; + cursor: pointer; +} +#debug-bar #toolbar-position:hover, +#debug-bar #toolbar-theme:hover { + text-decoration: none; +} +#debug-bar #debug-bar-link { + display: flex; + padding: 6px; + cursor: pointer; +} +#debug-bar .ci-label { + display: inline-flex; + font-size: 14px; +} +#debug-bar .ci-label:hover { + cursor: pointer; +} +#debug-bar .ci-label a { + color: inherit; + display: flex; + letter-spacing: normal; + padding: 0 10px; + text-decoration: none; + align-items: center; +} +#debug-bar .ci-label img { + margin: 6px 3px 6px 0; + width: 16px !important; +} +#debug-bar .ci-label .badge { + border-radius: 12px; + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + display: inline-block; + font-size: 75%; + font-weight: bold; + line-height: 12px; + margin-left: 5px; + padding: 2px 5px; + text-align: center; + vertical-align: baseline; + white-space: nowrap; +} +#debug-bar .tab { + height: fit-content; + text-align: left; + bottom: 35px; + display: none; + left: 0; + max-height: 62%; + overflow: hidden; + overflow-y: auto; + padding: 1em 2em; + position: fixed; + right: 0; + z-index: 9999; +} +#debug-bar .timeline { + position: static; + display: table; + margin-left: 0; + width: 100%; +} +#debug-bar .timeline th { + border-left: 1px solid; + font-size: 12px; + font-weight: 200; + padding: 5px 5px 10px 5px; + position: relative; + text-align: left; +} +#debug-bar .timeline th:first-child { + border-left: 0; +} +#debug-bar .timeline td { + border-left: 1px solid; + padding: 5px; + position: relative; +} +#debug-bar .timeline td:first-child { + border-left: 0; + max-width: none; +} +#debug-bar .timeline td.child-container { + padding: 0px; +} +#debug-bar .timeline td.child-container .timeline { + margin: 0px; +} +#debug-bar .timeline td.child-container .timeline td:first-child:not(.child-container) { + padding-left: calc(5px + 10px * var(--level)); +} +#debug-bar .timeline .timer { + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + display: inline-block; + padding: 5px; + position: absolute; + top: 30%; +} +#debug-bar .timeline .timeline-parent { + cursor: pointer; +} +#debug-bar .timeline .timeline-parent td:first-child nav { + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMCAxNTAiPjxwYXRoIGQ9Ik02IDdoMThsLTkgMTV6bTAgMzBoMThsLTkgMTV6bTAgNDVoMThsLTktMTV6bTAgMzBoMThsLTktMTV6bTAgMTJsMTggMThtLTE4IDBsMTgtMTgiIGZpbGw9IiM1NTUiLz48cGF0aCBkPSJNNiAxMjZsMTggMThtLTE4IDBsMTgtMTgiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSIjNTU1Ii8+PC9zdmc+") no-repeat scroll 0 0/15px 75px transparent; + background-position: 0 25%; + display: inline-block; + height: 15px; + width: 15px; + margin-right: 3px; + vertical-align: middle; +} +#debug-bar .timeline .timeline-parent-open { + background-color: #DFDFDF; +} +#debug-bar .timeline .timeline-parent-open td:first-child nav { + background-position: 0 75%; +} +#debug-bar .timeline .child-row:hover { + background: transparent; +} +#debug-bar .route-params, +#debug-bar .route-params-item { + vertical-align: top; +} +#debug-bar .route-params td:first-child, +#debug-bar .route-params-item td:first-child { + font-style: italic; + padding-left: 1em; + text-align: right; +} +#debug-bar > .debug-bar-dblock { + display: block; +} .debug-view.show-view { border: 1px solid; - margin: 4px; } + margin: 4px; +} .debug-view-path { font-family: monospace; @@ -251,30 +317,190 @@ letter-spacing: normal; min-height: 16px; padding: 2px; - text-align: left; } + text-align: left; +} .show-view .debug-view-path { - display: block !important; } + display: block !important; +} @media screen and (max-width: 1024px) { #debug-bar .ci-label img { - margin: unset; } + margin: unset; + } .hide-sm { - display: none !important; } } - + display: none !important; + } +} +@media screen and (max-width: 768px) { + #debug-bar table { + display: block; + overflow-x: auto; + font-size: 12px; + margin: 5px 5px 10px 5px; + } + #debug-bar table td, + #debug-bar table th { + padding: 4px 6px; + } + #debug-bar .timeline { + display: block; + white-space: nowrap; + font-size: 12px; + } + #debug-bar .toolbar { + overflow-x: auto; + } +} #debug-icon { background-color: #FFFFFF; box-shadow: 0 0 4px #DFDFDF; -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } - #debug-icon a:active, - #debug-icon a:link, - #debug-icon a:visited { - color: #DD8615; } + -webkit-box-shadow: 0 0 4px #DFDFDF; +} + +#debug-icon a:active, +#debug-icon a:link, +#debug-icon a:visited { + color: #DD8615; +} #debug-bar { background-color: #FFFFFF; - color: #434343; } + color: #434343; +} + +#debug-bar h1, +#debug-bar h2, +#debug-bar h3, +#debug-bar p, +#debug-bar a, +#debug-bar button, +#debug-bar table, +#debug-bar thead, +#debug-bar tr, +#debug-bar td, +#debug-bar button, +#debug-bar .toolbar { + background-color: transparent; + color: #434343; +} + +#debug-bar button { + background-color: #FFFFFF; +} + +#debug-bar table strong { + color: #DD8615; +} + +#debug-bar table tbody tr:hover { + background-color: #DFDFDF; +} + +#debug-bar table tbody tr.current { + background-color: #FDC894; +} + +#debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; +} + +#debug-bar .toolbar { + background-color: #FFFFFF; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; +} + +#debug-bar .toolbar img { + filter: brightness(0) invert(0.4); +} + +#debug-bar.fixed-top .toolbar { + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; +} + +#debug-bar.fixed-top .tab { + box-shadow: 0 1px 4px #DFDFDF; + -moz-box-shadow: 0 1px 4px #DFDFDF; + -webkit-box-shadow: 0 1px 4px #DFDFDF; +} + +#debug-bar .muted { + color: #434343; +} + +#debug-bar .muted td { + color: #DFDFDF; +} + +#debug-bar .muted:hover td { + color: #434343; +} + +#debug-bar #toolbar-position, +#debug-bar #toolbar-theme { + filter: brightness(0) invert(0.6); +} + +#debug-bar .ci-label.active { + background-color: #DFDFDF; +} + +#debug-bar .ci-label:hover { + background-color: #DFDFDF; +} + +#debug-bar .ci-label .badge { + background-color: #DD4814; + color: #FFFFFF; +} + +#debug-bar .tab { + background-color: #FFFFFF; + box-shadow: 0 -1px 4px #DFDFDF; + -moz-box-shadow: 0 -1px 4px #DFDFDF; + -webkit-box-shadow: 0 -1px 4px #DFDFDF; +} + +#debug-bar .timeline th, +#debug-bar .timeline td { + border-color: #DFDFDF; +} + +#debug-bar .timeline .timer { + background-color: #DD8615; +} + +.debug-view.show-view { + border-color: #DD8615; +} + +.debug-view-path { + background-color: #FDC894; + color: #434343; +} + +@media (prefers-color-scheme: dark) { + #debug-icon { + background-color: #252525; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; + } + #debug-icon a:active, + #debug-icon a:link, + #debug-icon a:visited { + color: #DD8615; + } + #debug-bar { + background-color: #252525; + color: #DFDFDF; + } #debug-bar h1, #debug-bar h2, #debug-bar h3, @@ -288,360 +514,395 @@ #debug-bar button, #debug-bar .toolbar { background-color: transparent; - color: #434343; } + color: #DFDFDF; + } #debug-bar button { - background-color: #FFFFFF; } + background-color: #252525; + } #debug-bar table strong { - color: #DD8615; } + color: #DD8615; + } #debug-bar table tbody tr:hover { - background-color: #DFDFDF; } + background-color: #434343; + } #debug-bar table tbody tr.current { - background-color: #FDC894; } - #debug-bar table tbody tr.current:hover td { - background-color: #DD4814; - color: #FFFFFF; } + background-color: #FDC894; + } + #debug-bar table tbody tr.current td { + color: #252525; + } + #debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; + } #debug-bar .toolbar { - background-color: #FFFFFF; - box-shadow: 0 0 4px #DFDFDF; - -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } - #debug-bar .toolbar img { - filter: brightness(0) invert(0.4); } + background-color: #434343; + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; + } + #debug-bar .toolbar img { + filter: brightness(0) invert(1); + } #debug-bar.fixed-top .toolbar { - box-shadow: 0 0 4px #DFDFDF; - -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; + } #debug-bar.fixed-top .tab { - box-shadow: 0 1px 4px #DFDFDF; - -moz-box-shadow: 0 1px 4px #DFDFDF; - -webkit-box-shadow: 0 1px 4px #DFDFDF; } + box-shadow: 0 1px 4px #434343; + -moz-box-shadow: 0 1px 4px #434343; + -webkit-box-shadow: 0 1px 4px #434343; + } #debug-bar .muted { - color: #434343; } - #debug-bar .muted td { - color: #DFDFDF; } - #debug-bar .muted:hover td { - color: #434343; } + color: #DFDFDF; + } + #debug-bar .muted td { + color: #434343; + } + #debug-bar .muted:hover td { + color: #DFDFDF; + } #debug-bar #toolbar-position, #debug-bar #toolbar-theme { - filter: brightness(0) invert(0.6); } + filter: brightness(0) invert(0.6); + } #debug-bar .ci-label.active { - background-color: #DFDFDF; } + background-color: #252525; + } #debug-bar .ci-label:hover { - background-color: #DFDFDF; } + background-color: #252525; + } #debug-bar .ci-label .badge { - background-color: #5BC0DE; - color: #FFFFFF; } + background-color: #DD4814; + color: #FFFFFF; + } #debug-bar .tab { - background-color: #FFFFFF; - box-shadow: 0 -1px 4px #DFDFDF; - -moz-box-shadow: 0 -1px 4px #DFDFDF; - -webkit-box-shadow: 0 -1px 4px #DFDFDF; } + background-color: #252525; + box-shadow: 0 -1px 4px #434343; + -moz-box-shadow: 0 -1px 4px #434343; + -webkit-box-shadow: 0 -1px 4px #434343; + } #debug-bar .timeline th, #debug-bar .timeline td { - border-color: #DFDFDF; } + border-color: #434343; + } #debug-bar .timeline .timer { - background-color: #DD8615; } - -.debug-view.show-view { - border-color: #DD8615; } - -.debug-view-path { - background-color: #FDC894; - color: #434343; } - -@media (prefers-color-scheme: dark) { - #debug-icon { - background-color: #252525; - box-shadow: 0 0 4px #DFDFDF; - -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } - #debug-icon a:active, - #debug-icon a:link, - #debug-icon a:visited { - color: #DD8615; } - #debug-bar { - background-color: #252525; - color: #DFDFDF; } - #debug-bar h1, - #debug-bar h2, - #debug-bar h3, - #debug-bar p, - #debug-bar a, - #debug-bar button, - #debug-bar table, - #debug-bar thead, - #debug-bar tr, - #debug-bar td, - #debug-bar button, - #debug-bar .toolbar { - background-color: transparent; - color: #DFDFDF; } - #debug-bar button { - background-color: #252525; } - #debug-bar table strong { - color: #DD8615; } - #debug-bar table tbody tr:hover { - background-color: #434343; } - #debug-bar table tbody tr.current { - background-color: #FDC894; } - #debug-bar table tbody tr.current td { - color: #252525; } - #debug-bar table tbody tr.current:hover td { - background-color: #DD4814; - color: #FFFFFF; } - #debug-bar .toolbar { - background-color: #434343; - box-shadow: 0 0 4px #434343; - -moz-box-shadow: 0 0 4px #434343; - -webkit-box-shadow: 0 0 4px #434343; } - #debug-bar .toolbar img { - filter: brightness(0) invert(1); } - #debug-bar.fixed-top .toolbar { - box-shadow: 0 0 4px #434343; - -moz-box-shadow: 0 0 4px #434343; - -webkit-box-shadow: 0 0 4px #434343; } - #debug-bar.fixed-top .tab { - box-shadow: 0 1px 4px #434343; - -moz-box-shadow: 0 1px 4px #434343; - -webkit-box-shadow: 0 1px 4px #434343; } - #debug-bar .muted { - color: #DFDFDF; } - #debug-bar .muted td { - color: #434343; } - #debug-bar .muted:hover td { - color: #DFDFDF; } - #debug-bar #toolbar-position, - #debug-bar #toolbar-theme { - filter: brightness(0) invert(0.6); } - #debug-bar .ci-label.active { - background-color: #252525; } - #debug-bar .ci-label:hover { - background-color: #252525; } - #debug-bar .ci-label .badge { - background-color: #5BC0DE; - color: #DFDFDF; } - #debug-bar .tab { - background-color: #252525; - box-shadow: 0 -1px 4px #434343; - -moz-box-shadow: 0 -1px 4px #434343; - -webkit-box-shadow: 0 -1px 4px #434343; } - #debug-bar .timeline th, - #debug-bar .timeline td { - border-color: #434343; } - #debug-bar .timeline .timer { - background-color: #DD8615; } + background-color: #DD8615; + } .debug-view.show-view { - border-color: #DD8615; } + border-color: #DD8615; + } .debug-view-path { background-color: #FDC894; - color: #434343; } } - + color: #434343; + } +} #toolbarContainer.dark #debug-icon { background-color: #252525; box-shadow: 0 0 4px #DFDFDF; -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } - #toolbarContainer.dark #debug-icon a:active, - #toolbarContainer.dark #debug-icon a:link, - #toolbarContainer.dark #debug-icon a:visited { - color: #DD8615; } - + -webkit-box-shadow: 0 0 4px #DFDFDF; +} +#toolbarContainer.dark #debug-icon a:active, +#toolbarContainer.dark #debug-icon a:link, +#toolbarContainer.dark #debug-icon a:visited { + color: #DD8615; +} #toolbarContainer.dark #debug-bar { background-color: #252525; - color: #DFDFDF; } - #toolbarContainer.dark #debug-bar h1, - #toolbarContainer.dark #debug-bar h2, - #toolbarContainer.dark #debug-bar h3, - #toolbarContainer.dark #debug-bar p, - #toolbarContainer.dark #debug-bar a, - #toolbarContainer.dark #debug-bar button, - #toolbarContainer.dark #debug-bar table, - #toolbarContainer.dark #debug-bar thead, - #toolbarContainer.dark #debug-bar tr, - #toolbarContainer.dark #debug-bar td, - #toolbarContainer.dark #debug-bar button, - #toolbarContainer.dark #debug-bar .toolbar { - background-color: transparent; - color: #DFDFDF; } - #toolbarContainer.dark #debug-bar button { - background-color: #252525; } - #toolbarContainer.dark #debug-bar table strong { - color: #DD8615; } - #toolbarContainer.dark #debug-bar table tbody tr:hover { - background-color: #434343; } - #toolbarContainer.dark #debug-bar table tbody tr.current { - background-color: #FDC894; } - #toolbarContainer.dark #debug-bar table tbody tr.current td { - color: #252525; } - #toolbarContainer.dark #debug-bar table tbody tr.current:hover td { - background-color: #DD4814; - color: #FFFFFF; } - #toolbarContainer.dark #debug-bar .toolbar { - background-color: #434343; - box-shadow: 0 0 4px #434343; - -moz-box-shadow: 0 0 4px #434343; - -webkit-box-shadow: 0 0 4px #434343; } - #toolbarContainer.dark #debug-bar .toolbar img { - filter: brightness(0) invert(1); } - #toolbarContainer.dark #debug-bar.fixed-top .toolbar { - box-shadow: 0 0 4px #434343; - -moz-box-shadow: 0 0 4px #434343; - -webkit-box-shadow: 0 0 4px #434343; } - #toolbarContainer.dark #debug-bar.fixed-top .tab { - box-shadow: 0 1px 4px #434343; - -moz-box-shadow: 0 1px 4px #434343; - -webkit-box-shadow: 0 1px 4px #434343; } - #toolbarContainer.dark #debug-bar .muted { - color: #DFDFDF; } - #toolbarContainer.dark #debug-bar .muted td { - color: #434343; } - #toolbarContainer.dark #debug-bar .muted:hover td { - color: #DFDFDF; } - #toolbarContainer.dark #debug-bar #toolbar-position, - #toolbarContainer.dark #debug-bar #toolbar-theme { - filter: brightness(0) invert(0.6); } - #toolbarContainer.dark #debug-bar .ci-label.active { - background-color: #252525; } - #toolbarContainer.dark #debug-bar .ci-label:hover { - background-color: #252525; } - #toolbarContainer.dark #debug-bar .ci-label .badge { - background-color: #5BC0DE; - color: #DFDFDF; } - #toolbarContainer.dark #debug-bar .tab { - background-color: #252525; - box-shadow: 0 -1px 4px #434343; - -moz-box-shadow: 0 -1px 4px #434343; - -webkit-box-shadow: 0 -1px 4px #434343; } - #toolbarContainer.dark #debug-bar .timeline th, - #toolbarContainer.dark #debug-bar .timeline td { - border-color: #434343; } - #toolbarContainer.dark #debug-bar .timeline .timer { - background-color: #DD8615; } - + color: #DFDFDF; +} +#toolbarContainer.dark #debug-bar h1, +#toolbarContainer.dark #debug-bar h2, +#toolbarContainer.dark #debug-bar h3, +#toolbarContainer.dark #debug-bar p, +#toolbarContainer.dark #debug-bar a, +#toolbarContainer.dark #debug-bar button, +#toolbarContainer.dark #debug-bar table, +#toolbarContainer.dark #debug-bar thead, +#toolbarContainer.dark #debug-bar tr, +#toolbarContainer.dark #debug-bar td, +#toolbarContainer.dark #debug-bar button, +#toolbarContainer.dark #debug-bar .toolbar { + background-color: transparent; + color: #DFDFDF; +} +#toolbarContainer.dark #debug-bar button { + background-color: #252525; +} +#toolbarContainer.dark #debug-bar table strong { + color: #DD8615; +} +#toolbarContainer.dark #debug-bar table tbody tr:hover { + background-color: #434343; +} +#toolbarContainer.dark #debug-bar table tbody tr.current { + background-color: #FDC894; +} +#toolbarContainer.dark #debug-bar table tbody tr.current td { + color: #252525; +} +#toolbarContainer.dark #debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; +} +#toolbarContainer.dark #debug-bar .toolbar { + background-color: #434343; + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; +} +#toolbarContainer.dark #debug-bar .toolbar img { + filter: brightness(0) invert(1); +} +#toolbarContainer.dark #debug-bar.fixed-top .toolbar { + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; +} +#toolbarContainer.dark #debug-bar.fixed-top .tab { + box-shadow: 0 1px 4px #434343; + -moz-box-shadow: 0 1px 4px #434343; + -webkit-box-shadow: 0 1px 4px #434343; +} +#toolbarContainer.dark #debug-bar .muted { + color: #DFDFDF; +} +#toolbarContainer.dark #debug-bar .muted td { + color: #434343; +} +#toolbarContainer.dark #debug-bar .muted:hover td { + color: #DFDFDF; +} +#toolbarContainer.dark #debug-bar #toolbar-position, +#toolbarContainer.dark #debug-bar #toolbar-theme { + filter: brightness(0) invert(0.6); +} +#toolbarContainer.dark #debug-bar .ci-label.active { + background-color: #252525; +} +#toolbarContainer.dark #debug-bar .ci-label:hover { + background-color: #252525; +} +#toolbarContainer.dark #debug-bar .ci-label .badge { + background-color: #DD4814; + color: #FFFFFF; +} +#toolbarContainer.dark #debug-bar .tab { + background-color: #252525; + box-shadow: 0 -1px 4px #434343; + -moz-box-shadow: 0 -1px 4px #434343; + -webkit-box-shadow: 0 -1px 4px #434343; +} +#toolbarContainer.dark #debug-bar .timeline th, +#toolbarContainer.dark #debug-bar .timeline td { + border-color: #434343; +} +#toolbarContainer.dark #debug-bar .timeline .timer { + background-color: #DD8615; +} #toolbarContainer.dark .debug-view.show-view { - border-color: #DD8615; } - + border-color: #DD8615; +} #toolbarContainer.dark .debug-view-path { background-color: #FDC894; - color: #434343; } - + color: #434343; +} #toolbarContainer.dark td[data-debugbar-route] input[type=text] { background: #000; - color: #fff; } + color: #fff; +} #toolbarContainer.light #debug-icon { background-color: #FFFFFF; box-shadow: 0 0 4px #DFDFDF; -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } - #toolbarContainer.light #debug-icon a:active, - #toolbarContainer.light #debug-icon a:link, - #toolbarContainer.light #debug-icon a:visited { - color: #DD8615; } - + -webkit-box-shadow: 0 0 4px #DFDFDF; +} +#toolbarContainer.light #debug-icon a:active, +#toolbarContainer.light #debug-icon a:link, +#toolbarContainer.light #debug-icon a:visited { + color: #DD8615; +} #toolbarContainer.light #debug-bar { background-color: #FFFFFF; - color: #434343; } - #toolbarContainer.light #debug-bar h1, - #toolbarContainer.light #debug-bar h2, - #toolbarContainer.light #debug-bar h3, - #toolbarContainer.light #debug-bar p, - #toolbarContainer.light #debug-bar a, - #toolbarContainer.light #debug-bar button, - #toolbarContainer.light #debug-bar table, - #toolbarContainer.light #debug-bar thead, - #toolbarContainer.light #debug-bar tr, - #toolbarContainer.light #debug-bar td, - #toolbarContainer.light #debug-bar button, - #toolbarContainer.light #debug-bar .toolbar { - background-color: transparent; - color: #434343; } - #toolbarContainer.light #debug-bar button { - background-color: #FFFFFF; } - #toolbarContainer.light #debug-bar table strong { - color: #DD8615; } - #toolbarContainer.light #debug-bar table tbody tr:hover { - background-color: #DFDFDF; } - #toolbarContainer.light #debug-bar table tbody tr.current { - background-color: #FDC894; } - #toolbarContainer.light #debug-bar table tbody tr.current:hover td { - background-color: #DD4814; - color: #FFFFFF; } - #toolbarContainer.light #debug-bar .toolbar { - background-color: #FFFFFF; - box-shadow: 0 0 4px #DFDFDF; - -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } - #toolbarContainer.light #debug-bar .toolbar img { - filter: brightness(0) invert(0.4); } - #toolbarContainer.light #debug-bar.fixed-top .toolbar { - box-shadow: 0 0 4px #DFDFDF; - -moz-box-shadow: 0 0 4px #DFDFDF; - -webkit-box-shadow: 0 0 4px #DFDFDF; } - #toolbarContainer.light #debug-bar.fixed-top .tab { - box-shadow: 0 1px 4px #DFDFDF; - -moz-box-shadow: 0 1px 4px #DFDFDF; - -webkit-box-shadow: 0 1px 4px #DFDFDF; } - #toolbarContainer.light #debug-bar .muted { - color: #434343; } - #toolbarContainer.light #debug-bar .muted td { - color: #DFDFDF; } - #toolbarContainer.light #debug-bar .muted:hover td { - color: #434343; } - #toolbarContainer.light #debug-bar #toolbar-position, - #toolbarContainer.light #debug-bar #toolbar-theme { - filter: brightness(0) invert(0.6); } - #toolbarContainer.light #debug-bar .ci-label.active { - background-color: #DFDFDF; } - #toolbarContainer.light #debug-bar .ci-label:hover { - background-color: #DFDFDF; } - #toolbarContainer.light #debug-bar .ci-label .badge { - background-color: #5BC0DE; - color: #FFFFFF; } - #toolbarContainer.light #debug-bar .tab { - background-color: #FFFFFF; - box-shadow: 0 -1px 4px #DFDFDF; - -moz-box-shadow: 0 -1px 4px #DFDFDF; - -webkit-box-shadow: 0 -1px 4px #DFDFDF; } - #toolbarContainer.light #debug-bar .timeline th, - #toolbarContainer.light #debug-bar .timeline td { - border-color: #DFDFDF; } - #toolbarContainer.light #debug-bar .timeline .timer { - background-color: #DD8615; } - + color: #434343; +} +#toolbarContainer.light #debug-bar h1, +#toolbarContainer.light #debug-bar h2, +#toolbarContainer.light #debug-bar h3, +#toolbarContainer.light #debug-bar p, +#toolbarContainer.light #debug-bar a, +#toolbarContainer.light #debug-bar button, +#toolbarContainer.light #debug-bar table, +#toolbarContainer.light #debug-bar thead, +#toolbarContainer.light #debug-bar tr, +#toolbarContainer.light #debug-bar td, +#toolbarContainer.light #debug-bar button, +#toolbarContainer.light #debug-bar .toolbar { + background-color: transparent; + color: #434343; +} +#toolbarContainer.light #debug-bar button { + background-color: #FFFFFF; +} +#toolbarContainer.light #debug-bar table strong { + color: #DD8615; +} +#toolbarContainer.light #debug-bar table tbody tr:hover { + background-color: #DFDFDF; +} +#toolbarContainer.light #debug-bar table tbody tr.current { + background-color: #FDC894; +} +#toolbarContainer.light #debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; +} +#toolbarContainer.light #debug-bar .toolbar { + background-color: #FFFFFF; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; +} +#toolbarContainer.light #debug-bar .toolbar img { + filter: brightness(0) invert(0.4); +} +#toolbarContainer.light #debug-bar.fixed-top .toolbar { + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; +} +#toolbarContainer.light #debug-bar.fixed-top .tab { + box-shadow: 0 1px 4px #DFDFDF; + -moz-box-shadow: 0 1px 4px #DFDFDF; + -webkit-box-shadow: 0 1px 4px #DFDFDF; +} +#toolbarContainer.light #debug-bar .muted { + color: #434343; +} +#toolbarContainer.light #debug-bar .muted td { + color: #DFDFDF; +} +#toolbarContainer.light #debug-bar .muted:hover td { + color: #434343; +} +#toolbarContainer.light #debug-bar #toolbar-position, +#toolbarContainer.light #debug-bar #toolbar-theme { + filter: brightness(0) invert(0.6); +} +#toolbarContainer.light #debug-bar .ci-label.active { + background-color: #DFDFDF; +} +#toolbarContainer.light #debug-bar .ci-label:hover { + background-color: #DFDFDF; +} +#toolbarContainer.light #debug-bar .ci-label .badge { + background-color: #DD4814; + color: #FFFFFF; +} +#toolbarContainer.light #debug-bar .tab { + background-color: #FFFFFF; + box-shadow: 0 -1px 4px #DFDFDF; + -moz-box-shadow: 0 -1px 4px #DFDFDF; + -webkit-box-shadow: 0 -1px 4px #DFDFDF; +} +#toolbarContainer.light #debug-bar .timeline th, +#toolbarContainer.light #debug-bar .timeline td { + border-color: #DFDFDF; +} +#toolbarContainer.light #debug-bar .timeline .timer { + background-color: #DD8615; +} #toolbarContainer.light .debug-view.show-view { - border-color: #DD8615; } - + border-color: #DD8615; +} #toolbarContainer.light .debug-view-path { background-color: #FDC894; - color: #434343; } + color: #434343; +} .debug-bar-width30 { - width: 30%; } + width: 30%; +} .debug-bar-width10 { - width: 10%; } + width: 10%; +} .debug-bar-width70p { - width: 70px; } + width: 70px; +} -.debug-bar-width140p { - width: 140px; } +.debug-bar-width190p { + width: 190px; +} .debug-bar-width20e { - width: 20em; } + width: 20em; +} .debug-bar-width6r { - width: 6rem; } + width: 6rem; +} .debug-bar-ndisplay { - display: none; } + display: none; +} .debug-bar-alignRight { - text-align: right; } + text-align: right; +} .debug-bar-alignLeft { - text-align: left; } + text-align: left; +} .debug-bar-noverflow { - overflow: hidden; } + overflow: hidden; +} + +.debug-bar-dtableRow { + display: table-row; +} + +.debug-bar-dinlineBlock { + display: inline-block; +} + +.debug-bar-pointer { + cursor: pointer; +} + +.debug-bar-mleft4 { + margin-left: 4px; +} + +.debug-bar-level-0 { + --level: 0; +} + +.debug-bar-level-1 { + --level: 1; +} + +.debug-bar-level-2 { + --level: 2; +} + +.debug-bar-level-3 { + --level: 3; +} + +.debug-bar-level-4 { + --level: 4; +} + +.debug-bar-level-5 { + --level: 5; +} + +.debug-bar-level-6 { + --level: 6; +} diff --git a/system/Debug/Toolbar/Views/toolbar.js b/system/Debug/Toolbar/Views/toolbar.js index 690535f2de0d..46504e80ca25 100644 --- a/system/Debug/Toolbar/Views/toolbar.js +++ b/system/Debug/Toolbar/Views/toolbar.js @@ -3,663 +3,830 @@ */ var ciDebugBar = { - - toolbarContainer : null, - toolbar : null, - icon : null, - - init : function () { - this.toolbarContainer = document.getElementById('toolbarContainer'); - this.toolbar = document.getElementById('debug-bar'); - this.icon = document.getElementById('debug-icon'); - - ciDebugBar.createListeners(); - ciDebugBar.setToolbarState(); - ciDebugBar.setToolbarPosition(); - ciDebugBar.setToolbarTheme(); - ciDebugBar.toggleViewsHints(); - ciDebugBar.routerLink(); - - document.getElementById('debug-bar-link').addEventListener('click', ciDebugBar.toggleToolbar, true); - document.getElementById('debug-icon-link').addEventListener('click', ciDebugBar.toggleToolbar, true); - - // Allows to highlight the row of the current history request - var btn = document.querySelector('button[data-time="' + localStorage.getItem('debugbar-time') + '"]'); - ciDebugBar.addClass(btn.parentNode.parentNode, 'current'); - - historyLoad = document.getElementsByClassName('ci-history-load'); - - for (var i = 0; i < historyLoad.length; i++) - { - historyLoad[i].addEventListener('click', function () { - loadDoc(this.getAttribute('data-time')); - }, true); - } - - // Display the active Tab on page load - var tab = ciDebugBar.readCookie('debug-bar-tab'); - if (document.getElementById(tab)) - { - var el = document.getElementById(tab); - el.style.display = 'block'; - ciDebugBar.addClass(el, 'active'); - tab = document.querySelector('[data-tab=' + tab + ']'); - if (tab) - { - ciDebugBar.addClass(tab.parentNode, 'active'); - } - } - }, - - createListeners : function () { - var buttons = [].slice.call(document.querySelectorAll('#debug-bar .ci-label a')); - - for (var i = 0; i < buttons.length; i++) - { - buttons[i].addEventListener('click', ciDebugBar.showTab, true); - } - }, - - showTab: function () { - // Get the target tab, if any - var tab = document.getElementById(this.getAttribute('data-tab')); - - // If the label have not a tab stops here - if (! tab) - { - return; - } - - // Remove debug-bar-tab cookie - ciDebugBar.createCookie('debug-bar-tab', '', -1); - - // Check our current state. - var state = tab.style.display; - - // Hide all tabs - var tabs = document.querySelectorAll('#debug-bar .tab'); - - for (var i = 0; i < tabs.length; i++) - { - tabs[i].style.display = 'none'; - } - - // Mark all labels as inactive - var labels = document.querySelectorAll('#debug-bar .ci-label'); - - for (var i = 0; i < labels.length; i++) - { - ciDebugBar.removeClass(labels[i], 'active'); - } - - // Show/hide the selected tab - if (state != 'block') - { - tab.style.display = 'block'; - ciDebugBar.addClass(this.parentNode, 'active'); - // Create debug-bar-tab cookie to persistent state - ciDebugBar.createCookie('debug-bar-tab', this.getAttribute('data-tab'), 365); - } - }, - - addClass : function (el, className) { - if (el.classList) - { - el.classList.add(className); - } - else - { - el.className += ' ' + className; - } - }, - - removeClass : function (el, className) { - if (el.classList) - { - el.classList.remove(className); - } - else - { - el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); - } - }, - - /** - * Toggle display of a data table - * - * @param obj - */ - toggleDataTable : function (obj) { - if (typeof obj == 'string') - { - obj = document.getElementById(obj + '_table'); - } - - if (obj) - { - obj.style.display = obj.style.display == 'none' ? 'block' : 'none'; - } - }, - - /** - * Toggle display of timeline child elements - * - * @param obj - */ - toggleChildRows : function (obj) { - if (typeof obj == 'string') - { - par = document.getElementById(obj + '_parent') - obj = document.getElementById(obj + '_children'); - } - - if (par && obj) - { - obj.style.display = obj.style.display == 'none' ? '' : 'none'; - par.classList.toggle('timeline-parent-open'); - } - }, - - - //-------------------------------------------------------------------- - - /** - * Toggle tool bar from full to icon and icon to full - */ - toggleToolbar : function () { - var open = ciDebugBar.toolbar.style.display != 'none'; - - ciDebugBar.icon.style.display = open == true ? 'inline-block' : 'none'; - ciDebugBar.toolbar.style.display = open == false ? 'inline-block' : 'none'; - - // Remember it for other page loads on this site - ciDebugBar.createCookie('debug-bar-state', '', -1); - ciDebugBar.createCookie('debug-bar-state', open == true ? 'minimized' : 'open' , 365); - }, - - /** - * Sets the initial state of the toolbar (open or minimized) when - * the page is first loaded to allow it to remember the state between refreshes. - */ - setToolbarState: function () { - var open = ciDebugBar.readCookie('debug-bar-state'); - - ciDebugBar.icon.style.display = open != 'open' ? 'inline-block' : 'none'; - ciDebugBar.toolbar.style.display = open == 'open' ? 'inline-block' : 'none'; - }, - - toggleViewsHints: function () { - // Avoid toggle hints on history requests that are not the initial - if (localStorage.getItem('debugbar-time') != localStorage.getItem('debugbar-time-new')) - { - var a = document.querySelector('a[data-tab="ci-views"]'); - a.href = '#'; - return; - } - - var nodeList = []; // [ Element, NewElement( 1 )/OldElement( 0 ) ] - var sortedComments = []; - var comments = []; - - var getComments = function () { - var nodes = []; - var result = []; - var xpathResults = document.evaluate( "//comment()[starts-with(., ' DEBUG-VIEW')]", document, null, XPathResult.ANY_TYPE, null); - var nextNode = xpathResults.iterateNext(); - while ( nextNode ) - { - nodes.push( nextNode ); - nextNode = xpathResults.iterateNext(); - } - - // sort comment by opening and closing tags - for (var i = 0; i < nodes.length; ++i) - { - // get file path + name to use as key - var path = nodes[i].nodeValue.substring( 18, nodes[i].nodeValue.length - 1 ); - - if ( nodes[i].nodeValue[12] === 'S' ) // simple check for start comment - { - // create new entry - result[path] = [ nodes[i], null ]; - } - else if (result[path]) - { - // add to existing entry - result[path][1] = nodes[i]; - } - } - - return result; - }; - - // find node that has TargetNode as parentNode - var getParentNode = function ( node, targetNode ) { - if ( node.parentNode === null ) - { - return null; - } - - if ( node.parentNode !== targetNode ) - { - return getParentNode( node.parentNode, targetNode ); - } - - return node; - }; - - // define invalid & outer ( also invalid ) elements - const INVALID_ELEMENTS = [ 'NOSCRIPT', 'SCRIPT', 'STYLE' ]; - const OUTER_ELEMENTS = [ 'HTML', 'BODY', 'HEAD' ]; - - var getValidElementInner = function ( node, reverse ) { - // handle invalid tags - if ( OUTER_ELEMENTS.indexOf( node.nodeName ) !== -1 ) - { - for (var i = 0; i < document.body.children.length; ++i) - { - var index = reverse ? document.body.children.length - ( i + 1 ) : i; - var element = document.body.children[index]; - - // skip invalid tags - if ( INVALID_ELEMENTS.indexOf( element.nodeName ) !== -1 ) - { - continue; - } - - return [ element, reverse ]; - } - - return null; - } - - // get to next valid element - while ( node !== null && INVALID_ELEMENTS.indexOf( node.nodeName ) !== -1 ) - { - node = reverse ? node.previousElementSibling : node.nextElementSibling; - } - - // return non array if we couldnt find something - if ( node === null ) - { - return null; - } - - return [ node, reverse ]; - }; - - // get next valid element ( to be safe to add divs ) - // @return [ element, skip element ] or null if we couldnt find a valid place - var getValidElement = function ( nodeElement ) { - if (nodeElement) - { - if ( nodeElement.nextElementSibling !== null ) - { - return getValidElementInner( nodeElement.nextElementSibling, false ) - || getValidElementInner( nodeElement.previousElementSibling, true ); - } - if ( nodeElement.previousElementSibling !== null ) - { - return getValidElementInner( nodeElement.previousElementSibling, true ); - } - } - - // something went wrong! -> element is not in DOM - return null; - }; - - function showHints() - { - // Had AJAX? Reset view blocks - sortedComments = getComments(); - - for (var key in sortedComments) - { - var startElement = getValidElement( sortedComments[key][0] ); - var endElement = getValidElement( sortedComments[key][1] ); - - // skip if we couldnt get a valid element - if ( startElement === null || endElement === null ) - { - continue; - } - - // find element which has same parent as startelement - var jointParent = getParentNode( endElement[0], startElement[0].parentNode ); - if ( jointParent === null ) - { - // find element which has same parent as endelement - jointParent = getParentNode( startElement[0], endElement[0].parentNode ); - if ( jointParent === null ) - { - // both tries failed - continue; - } - else - { - startElement[0] = jointParent; - } - } - else - { - endElement[0] = jointParent; - } - - var debugDiv = document.createElement( 'div' ); // holder - var debugPath = document.createElement( 'div' ); // path - var childArray = startElement[0].parentNode.childNodes; // target child array - var parent = startElement[0].parentNode; - var start, end; - - // setup container - debugDiv.classList.add( 'debug-view' ); - debugDiv.classList.add( 'show-view' ); - debugPath.classList.add( 'debug-view-path' ); - debugPath.innerText = key; - debugDiv.appendChild( debugPath ); - - // calc distance between them - // start - for (var i = 0; i < childArray.length; ++i) - { - // check for comment ( start & end ) -> if its before valid start element - if ( childArray[i] === sortedComments[key][1] || - childArray[i] === sortedComments[key][0] || - childArray[i] === startElement[0] ) - { - start = i; - if ( childArray[i] === sortedComments[key][0] ) - { - start++; // increase to skip the start comment - } - break; - } - } - // adjust if we want to skip the start element - if ( startElement[1] ) - { - start++; - } - - // end - for (var i = start; i < childArray.length; ++i) - { - if ( childArray[i] === endElement[0] ) - { - end = i; - // dont break to check for end comment after end valid element - } - else if ( childArray[i] === sortedComments[key][1] ) - { - // if we found the end comment, we can break - end = i; - break; - } - } - - // move elements - var number = end - start; - if ( endElement[1] ) - { - number++; - } - for (var i = 0; i < number; ++i) - { - if ( INVALID_ELEMENTS.indexOf( childArray[start] ) !== -1 ) - { - // skip invalid childs that can cause problems if moved - start++; - continue; - } - debugDiv.appendChild( childArray[start] ); - } - - // add container to DOM - nodeList.push( parent.insertBefore( debugDiv, childArray[start] ) ); - } - - ciDebugBar.createCookie('debug-view', 'show', 365); - ciDebugBar.addClass(btn, 'active'); - } - - function hideHints() - { - for (var i = 0; i < nodeList.length; ++i) - { - var index; - - // find index - for (var j = 0; j < nodeList[i].parentNode.childNodes.length; ++j) - { - if ( nodeList[i].parentNode.childNodes[j] === nodeList[i] ) - { - index = j; - break; - } - } - - // move child back - while ( nodeList[i].childNodes.length !== 1 ) - { - nodeList[i].parentNode.insertBefore( nodeList[i].childNodes[1], nodeList[i].parentNode.childNodes[index].nextSibling ); - index++; - } - - nodeList[i].parentNode.removeChild( nodeList[i] ); - } - nodeList.length = 0; - - ciDebugBar.createCookie('debug-view', '', -1); - ciDebugBar.removeClass(btn, 'active'); - } - - var btn = document.querySelector('[data-tab=ci-views]'); - - // If the Views Collector is inactive stops here - if (! btn) - { - return; - } - - btn.parentNode.onclick = function () { - if (ciDebugBar.readCookie('debug-view')) - { - hideHints(); - } - else - { - showHints(); - } - }; - - // Determine Hints state on page load - if (ciDebugBar.readCookie('debug-view')) - { - showHints(); - } - }, - - setToolbarPosition: function () { - var btnPosition = document.getElementById('toolbar-position'); - - if (ciDebugBar.readCookie('debug-bar-position') === 'top') - { - ciDebugBar.addClass(ciDebugBar.icon, 'fixed-top'); - ciDebugBar.addClass(ciDebugBar.toolbar, 'fixed-top'); - } - - btnPosition.addEventListener('click', function () { - var position = ciDebugBar.readCookie('debug-bar-position'); - - ciDebugBar.createCookie('debug-bar-position', '', -1); - - if (!position || position === 'bottom') - { - ciDebugBar.createCookie('debug-bar-position', 'top', 365); - ciDebugBar.addClass(ciDebugBar.icon, 'fixed-top'); - ciDebugBar.addClass(ciDebugBar.toolbar, 'fixed-top'); - } - else - { - ciDebugBar.createCookie('debug-bar-position', 'bottom', 365); - ciDebugBar.removeClass(ciDebugBar.icon, 'fixed-top'); - ciDebugBar.removeClass(ciDebugBar.toolbar, 'fixed-top'); - } - }, true); - }, - - setToolbarTheme: function () { - var btnTheme = document.getElementById('toolbar-theme'); - var isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; - var isLightMode = window.matchMedia("(prefers-color-scheme: light)").matches; - - // If a cookie is set with a value, we force the color scheme - if (ciDebugBar.readCookie('debug-bar-theme') === 'dark') - { - ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'light'); - ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'dark'); - } - else if (ciDebugBar.readCookie('debug-bar-theme') === 'light') - { - ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark'); - ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light'); - } - - btnTheme.addEventListener('click', function () { - var theme = ciDebugBar.readCookie('debug-bar-theme'); - - if (!theme && window.matchMedia("(prefers-color-scheme: dark)").matches) - { - // If there is no cookie, and "prefers-color-scheme" is set to "dark" - // It means that the user wants to switch to light mode - ciDebugBar.createCookie('debug-bar-theme', 'light', 365); - ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark'); - ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light'); - } - else - { - if (theme === 'dark') - { - ciDebugBar.createCookie('debug-bar-theme', 'light', 365); - ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark'); - ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light'); - } - else - { - // In any other cases: if there is no cookie, or the cookie is set to - // "light", or the "prefers-color-scheme" is "light"... - ciDebugBar.createCookie('debug-bar-theme', 'dark', 365); - ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'light'); - ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'dark'); - } - } - }, true); - }, - - /** - * Helper to create a cookie. - * - * @param name - * @param value - * @param days - */ - createCookie : function (name,value,days) { - if (days) - { - var date = new Date(); - - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - - var expires = "; expires=" + date.toGMTString(); - } - else - { - var expires = ""; - } - - document.cookie = name + "=" + value + expires + "; path=/; samesite=Lax"; - }, - - readCookie : function (name) { - var nameEQ = name + "="; - var ca = document.cookie.split(';'); - - for (var i = 0; i < ca.length; i++) - { - var c = ca[i]; - while (c.charAt(0) == ' ') - { - c = c.substring(1,c.length); - } - if (c.indexOf(nameEQ) == 0) - { - return c.substring(nameEQ.length,c.length); - } - } - return null; - }, - - trimSlash: function (text) { - return text.replace(/^\/|\/$/g, ''); - }, - - routerLink: function () { - var row, _location; - var rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"]'); - var patt = /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/; - - for (var i = 0; i < rowGet.length; i++) - { - row = rowGet[i]; - if (!/\/\(.+?\)/.test(rowGet[i].innerText)) - { - row.style = 'cursor: pointer;'; - row.setAttribute('title', location.origin + '/' + ciDebugBar.trimSlash(row.innerText)); - row.addEventListener('click', function (ev) { - _location = location.origin + '/' + ciDebugBar.trimSlash(ev.target.innerText); - var redirectWindow = window.open(_location, '_blank'); - redirectWindow.location; - }); - } - else - { - row.innerHTML = '
' + row.innerText + '
' - + '' - + row.innerText.replace(patt, '') - + '' - + ''; - } - } - - rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"] form'); - for (var i = 0; i < rowGet.length; i++) - { - row = rowGet[i]; - - row.addEventListener('submit', function (event) { - event.preventDefault() - var inputArray = [], t = 0; - var input = event.target.querySelectorAll('input[type=text]'); - var tpl = event.target.getAttribute('data-debugbar-route-tpl'); - - for (var n = 0; n < input.length; n++) - { - if (input[n].value.length > 0) - { - inputArray.push(input[n].value); - } - } - - if (inputArray.length > 0) - { - _location = location.origin + '/' + tpl.replace(/\?/g, function () { - return inputArray[t++] - }); - - var redirectWindow = window.open(_location, '_blank'); - redirectWindow.location; - } - }) - } - } + toolbarContainer: null, + toolbar: null, + icon: null, + + init: function () { + this.toolbarContainer = document.getElementById("toolbarContainer"); + this.toolbar = document.getElementById("debug-bar"); + this.icon = document.getElementById("debug-icon"); + + ciDebugBar.createListeners(); + ciDebugBar.setToolbarState(); + ciDebugBar.setToolbarPosition(); + ciDebugBar.setToolbarTheme(); + ciDebugBar.toggleViewsHints(); + ciDebugBar.routerLink(); + ciDebugBar.setHotReloadState(); + + document + .getElementById("debug-bar-link") + .addEventListener("click", ciDebugBar.toggleToolbar, true); + document + .getElementById("debug-icon-link") + .addEventListener("click", ciDebugBar.toggleToolbar, true); + + historyLoad = this.toolbar.getElementsByClassName("ci-history-load"); + + if (historyLoad.length) { + // Allows highlighting the row of the current history request + var btn = this.toolbar.querySelector( + 'button[data-time="' + localStorage.getItem("debugbar-time-new") + '"]' + ); + ciDebugBar.addClass(btn.parentNode.parentNode, "current"); + + + for (var i = 0; i < historyLoad.length; i++) { + historyLoad[i].addEventListener( + "click", + function () { + loadDoc(this.getAttribute("data-time")); + }, + true + ); + } + } + + // Display the active Tab on page load + var tab = ciDebugBar.readCookie("debug-bar-tab"); + if (document.getElementById(tab)) { + var el = document.getElementById(tab); + ciDebugBar.switchClass(el, "debug-bar-ndisplay", "debug-bar-dblock"); + ciDebugBar.addClass(el, "active"); + tab = document.querySelector("[data-tab=" + tab + "]"); + if (tab) { + ciDebugBar.addClass(tab.parentNode, "active"); + } + } + }, + + createListeners: function () { + var buttons = [].slice.call( + this.toolbar.querySelectorAll(".ci-label a") + ); + + for (var i = 0; i < buttons.length; i++) { + buttons[i].addEventListener("click", ciDebugBar.showTab, true); + } + + // Hook up generic toggle via data attributes `data-toggle="foo"` + var links = this.toolbar.querySelectorAll("[data-toggle]"); + for (var i = 0; i < links.length; i++) { + let toggleData = links[i].getAttribute("data-toggle"); + if (toggleData === "datatable") { + + let datatable = links[i].getAttribute("data-table"); + links[i].addEventListener("click", function() { + ciDebugBar.toggleDataTable(datatable) + }, true); + + } else if (toggleData === "childrows") { + + let child = links[i].getAttribute("data-child"); + links[i].addEventListener("click", function() { + ciDebugBar.toggleChildRows(child) + }, true); + + } else { + links[i].addEventListener("click", ciDebugBar.toggleRows, true); + } + } + }, + + showTab: function () { + // Get the target tab, if any + var tab = document.getElementById(this.getAttribute("data-tab")); + + // If the label have not a tab stops here + if (! tab) { + return; + } + + // Remove debug-bar-tab cookie + ciDebugBar.createCookie("debug-bar-tab", "", -1); + + // Check our current state. + var state = tab.classList.contains("debug-bar-dblock"); + + // Hide all tabs + var tabs = document.querySelectorAll("#debug-bar .tab"); + + for (var i = 0; i < tabs.length; i++) { + ciDebugBar.switchClass(tabs[i], "debug-bar-dblock", "debug-bar-ndisplay"); + } + + // Mark all labels as inactive + var labels = document.querySelectorAll("#debug-bar .ci-label"); + + for (var i = 0; i < labels.length; i++) { + ciDebugBar.removeClass(labels[i], "active"); + } + + // Show/hide the selected tab + if (! state) { + ciDebugBar.switchClass(tab, "debug-bar-ndisplay", "debug-bar-dblock"); + ciDebugBar.addClass(this.parentNode, "active"); + // Create debug-bar-tab cookie to persistent state + ciDebugBar.createCookie( + "debug-bar-tab", + this.getAttribute("data-tab"), + 365 + ); + } + }, + + addClass: function (el, className) { + if (el.classList) { + el.classList.add(className); + } else { + el.className += " " + className; + } + }, + + removeClass: function (el, className) { + if (el.classList) { + el.classList.remove(className); + } else { + el.className = el.className.replace( + new RegExp( + "(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", + "gi" + ), + " " + ); + } + }, + + switchClass : function(el, classFrom, classTo) { + ciDebugBar.removeClass(el, classFrom); + ciDebugBar.addClass(el, classTo); + }, + + /** + * Toggle display of another object based on + * the data-toggle value of this object + * + * @param event + */ + toggleRows: function (event) { + if (event.target) { + let row = event.target.closest("tr"); + let target = document.getElementById( + row.getAttribute("data-toggle") + ); + + if (target.classList.contains("debug-bar-ndisplay")) { + ciDebugBar.switchClass(target, "debug-bar-ndisplay", "debug-bar-dtableRow"); + } else { + ciDebugBar.switchClass(target, "debug-bar-dtableRow", "debug-bar-ndisplay"); + } + } + }, + + /** + * Toggle display of a data table + * + * @param obj + */ + toggleDataTable: function (obj) { + if (typeof obj == "string") { + obj = document.getElementById(obj + "_table"); + } + + if (obj) { + if (obj.classList.contains("debug-bar-ndisplay")) { + ciDebugBar.switchClass(obj, "debug-bar-ndisplay", "debug-bar-dblock"); + } else { + ciDebugBar.switchClass(obj, "debug-bar-dblock", "debug-bar-ndisplay"); + } + } + }, + + /** + * Toggle display of timeline child elements + * + * @param obj + */ + toggleChildRows: function (obj) { + if (typeof obj == "string") { + par = document.getElementById(obj + "_parent"); + obj = document.getElementById(obj + "_children"); + } + + if (par && obj) { + + if (obj.classList.contains("debug-bar-ndisplay")) { + ciDebugBar.removeClass(obj, "debug-bar-ndisplay"); + } else { + ciDebugBar.addClass(obj, "debug-bar-ndisplay"); + } + + par.classList.toggle("timeline-parent-open"); + } + }, + + //-------------------------------------------------------------------- + + /** + * Toggle tool bar from full to icon and icon to full + */ + toggleToolbar: function () { + var open = ! ciDebugBar.toolbar.classList.contains("debug-bar-ndisplay"); + + if (open) { + ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + } else { + ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + } + + // Remember it for other page loads on this site + ciDebugBar.createCookie("debug-bar-state", "", -1); + ciDebugBar.createCookie( + "debug-bar-state", + open == true ? "minimized" : "open", + 365 + ); + }, + + /** + * Sets the initial state of the toolbar (open or minimized) when + * the page is first loaded to allow it to remember the state between refreshes. + */ + setToolbarState: function () { + var open = ciDebugBar.readCookie("debug-bar-state"); + + if (open != "open") { + ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + } else { + ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + } + }, + + toggleViewsHints: function () { + // Avoid toggle hints on history requests that are not the initial + if ( + localStorage.getItem("debugbar-time") != + localStorage.getItem("debugbar-time-new") + ) { + var a = document.querySelector('a[data-tab="ci-views"]'); + a.href = "#"; + return; + } + + var nodeList = []; // [ Element, NewElement( 1 )/OldElement( 0 ) ] + var sortedComments = []; + var comments = []; + + var getComments = function () { + var nodes = []; + var result = []; + var xpathResults = document.evaluate( + "//comment()[starts-with(., ' DEBUG-VIEW')]", + document, + null, + XPathResult.ANY_TYPE, + null + ); + var nextNode = xpathResults.iterateNext(); + while (nextNode) { + nodes.push(nextNode); + nextNode = xpathResults.iterateNext(); + } + + // sort comment by opening and closing tags + for (var i = 0; i < nodes.length; ++i) { + // get file path + name to use as key + var path = nodes[i].nodeValue.substring( + 18, + nodes[i].nodeValue.length - 1 + ); + + if (nodes[i].nodeValue[12] === "S") { + // simple check for start comment + // create new entry + result[path] = [nodes[i], null]; + } else if (result[path]) { + // add to existing entry + result[path][1] = nodes[i]; + } + } + + return result; + }; + + // find node that has TargetNode as parentNode + var getParentNode = function (node, targetNode) { + if (node.parentNode === null) { + return null; + } + + if (node.parentNode !== targetNode) { + return getParentNode(node.parentNode, targetNode); + } + + return node; + }; + + // define invalid & outer ( also invalid ) elements + const INVALID_ELEMENTS = ["NOSCRIPT", "SCRIPT", "STYLE"]; + const OUTER_ELEMENTS = ["HTML", "BODY", "HEAD"]; + + var getValidElementInner = function (node, reverse) { + // handle null node + if (node === null) { + return null; + } + + // handle invalid tags + if (OUTER_ELEMENTS.indexOf(node.nodeName) !== -1) { + for (var i = 0; i < document.body.children.length; ++i) { + var index = reverse + ? document.body.children.length - (i + 1) + : i; + var element = document.body.children[index]; + + // skip invalid tags + if (INVALID_ELEMENTS.indexOf(element.nodeName) !== -1) { + continue; + } + + return [element, reverse]; + } + + return null; + } + + // get to next valid element + while ( + node !== null && + INVALID_ELEMENTS.indexOf(node.nodeName) !== -1 + ) { + node = reverse + ? node.previousElementSibling + : node.nextElementSibling; + } + + // return non array if we couldnt find something + if (node === null) { + return null; + } + + return [node, reverse]; + }; + + // get next valid element ( to be safe to add divs ) + // @return [ element, skip element ] or null if we couldnt find a valid place + var getValidElement = function (nodeElement) { + if (nodeElement) { + if (nodeElement.nextElementSibling !== null) { + return ( + getValidElementInner( + nodeElement.nextElementSibling, + false + ) || + getValidElementInner( + nodeElement.previousElementSibling, + true + ) + ); + } + if (nodeElement.previousElementSibling !== null) { + return getValidElementInner( + nodeElement.previousElementSibling, + true + ); + } + } + + // something went wrong! -> element is not in DOM + return null; + }; + + function showHints() { + // Had AJAX? Reset view blocks + sortedComments = getComments(); + + for (var key in sortedComments) { + var startElement = getValidElement(sortedComments[key][0]); + var endElement = getValidElement(sortedComments[key][1]); + + // skip if we couldnt get a valid element + if (startElement === null || endElement === null) { + continue; + } + + // find element which has same parent as startelement + var jointParent = getParentNode( + endElement[0], + startElement[0].parentNode + ); + if (jointParent === null) { + // find element which has same parent as endelement + jointParent = getParentNode( + startElement[0], + endElement[0].parentNode + ); + if (jointParent === null) { + // both tries failed + continue; + } else { + startElement[0] = jointParent; + } + } else { + endElement[0] = jointParent; + } + + var debugDiv = document.createElement("div"); // holder + var debugPath = document.createElement("div"); // path + var childArray = startElement[0].parentNode.childNodes; // target child array + var parent = startElement[0].parentNode; + let start, end; + + // setup container + debugDiv.classList.add("debug-view"); + debugDiv.classList.add("show-view"); + debugPath.classList.add("debug-view-path"); + debugPath.innerText = key; + debugDiv.appendChild(debugPath); + + // calc distance between them + // start + for (var i = 0; i < childArray.length; ++i) { + // check for comment ( start & end ) -> if its before valid start element + if ( + childArray[i] === sortedComments[key][1] || + childArray[i] === sortedComments[key][0] || + childArray[i] === startElement[0] + ) { + start = i; + if (childArray[i] === sortedComments[key][0]) { + start++; // increase to skip the start comment + } + break; + } + } + // adjust if we want to skip the start element + if (startElement[1]) { + start++; + } + + // end + for (var i = start; i < childArray.length; ++i) { + if (childArray[i] === endElement[0]) { + end = i; + // dont break to check for end comment after end valid element + } else if (childArray[i] === sortedComments[key][1]) { + // if we found the end comment, we can break + end = i; + break; + } + } + + // move elements + var number = end - start; + if (endElement[1]) { + number++; + } + for (var i = 0; i < number; ++i) { + if (INVALID_ELEMENTS.indexOf(childArray[start]) !== -1) { + // skip invalid childs that can cause problems if moved + start++; + continue; + } + debugDiv.appendChild(childArray[start]); + } + + // add container to DOM + nodeList.push(parent.insertBefore(debugDiv, childArray[start])); + } + + ciDebugBar.createCookie("debug-view", "show", 365); + ciDebugBar.addClass(btn, "active"); + } + + function hideHints() { + for (var i = 0; i < nodeList.length; ++i) { + var index; + + // find index + for ( + var j = 0; + j < nodeList[i].parentNode.childNodes.length; + ++j + ) { + if (nodeList[i].parentNode.childNodes[j] === nodeList[i]) { + index = j; + break; + } + } + + // move child back + while (nodeList[i].childNodes.length !== 1) { + nodeList[i].parentNode.insertBefore( + nodeList[i].childNodes[1], + nodeList[i].parentNode.childNodes[index].nextSibling + ); + index++; + } + + nodeList[i].parentNode.removeChild(nodeList[i]); + } + nodeList.length = 0; + + ciDebugBar.createCookie("debug-view", "", -1); + ciDebugBar.removeClass(btn, "active"); + } + + var btn = document.querySelector("[data-tab=ci-views]"); + + // If the Views Collector is inactive stops here + if (! btn) { + return; + } + + btn.parentNode.onclick = function () { + if (ciDebugBar.readCookie("debug-view")) { + hideHints(); + } else { + showHints(); + } + }; + + // Determine Hints state on page load + if (ciDebugBar.readCookie("debug-view")) { + showHints(); + } + }, + + setToolbarPosition: function () { + var btnPosition = this.toolbar.querySelector("#toolbar-position"); + + if (ciDebugBar.readCookie("debug-bar-position") === "top") { + ciDebugBar.addClass(ciDebugBar.icon, "fixed-top"); + ciDebugBar.addClass(ciDebugBar.toolbar, "fixed-top"); + } + + btnPosition.addEventListener( + "click", + function () { + var position = ciDebugBar.readCookie("debug-bar-position"); + + ciDebugBar.createCookie("debug-bar-position", "", -1); + + if (! position || position === "bottom") { + ciDebugBar.createCookie("debug-bar-position", "top", 365); + ciDebugBar.addClass(ciDebugBar.icon, "fixed-top"); + ciDebugBar.addClass(ciDebugBar.toolbar, "fixed-top"); + } else { + ciDebugBar.createCookie( + "debug-bar-position", + "bottom", + 365 + ); + ciDebugBar.removeClass(ciDebugBar.icon, "fixed-top"); + ciDebugBar.removeClass(ciDebugBar.toolbar, "fixed-top"); + } + }, + true + ); + }, + + setToolbarTheme: function () { + var btnTheme = this.toolbar.querySelector("#toolbar-theme"); + var isDarkMode = window.matchMedia( + "(prefers-color-scheme: dark)" + ).matches; + var isLightMode = window.matchMedia( + "(prefers-color-scheme: light)" + ).matches; + + // If a cookie is set with a value, we force the color scheme + if (ciDebugBar.readCookie("debug-bar-theme") === "dark") { + ciDebugBar.removeClass(ciDebugBar.toolbarContainer, "light"); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, "dark"); + } else if (ciDebugBar.readCookie("debug-bar-theme") === "light") { + ciDebugBar.removeClass(ciDebugBar.toolbarContainer, "dark"); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, "light"); + } + + btnTheme.addEventListener( + "click", + function () { + var theme = ciDebugBar.readCookie("debug-bar-theme"); + + if ( + ! theme && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + // If there is no cookie, and "prefers-color-scheme" is set to "dark" + // It means that the user wants to switch to light mode + ciDebugBar.createCookie("debug-bar-theme", "light", 365); + ciDebugBar.removeClass(ciDebugBar.toolbarContainer, "dark"); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, "light"); + } else { + if (theme === "dark") { + ciDebugBar.createCookie( + "debug-bar-theme", + "light", + 365 + ); + ciDebugBar.removeClass( + ciDebugBar.toolbarContainer, + "dark" + ); + ciDebugBar.addClass( + ciDebugBar.toolbarContainer, + "light" + ); + } else { + // In any other cases: if there is no cookie, or the cookie is set to + // "light", or the "prefers-color-scheme" is "light"... + ciDebugBar.createCookie("debug-bar-theme", "dark", 365); + ciDebugBar.removeClass( + ciDebugBar.toolbarContainer, + "light" + ); + ciDebugBar.addClass( + ciDebugBar.toolbarContainer, + "dark" + ); + } + } + }, + true + ); + }, + + setHotReloadState: function () { + var btn = document.getElementById("debug-hot-reload").parentNode; + var btnImg = btn.getElementsByTagName("img")[0]; + var eventSource; + + // If the Hot Reload Collector is inactive stops here + if (! btn) { + return; + } + + btn.onclick = function () { + if (ciDebugBar.readCookie("debug-hot-reload")) { + ciDebugBar.createCookie("debug-hot-reload", "", -1); + ciDebugBar.removeClass(btn, "active"); + ciDebugBar.removeClass(btnImg, "rotate"); + + // Close the EventSource connection if it exists + if (typeof eventSource !== "undefined") { + eventSource.close(); + eventSource = void 0; // Undefine the variable + } + } else { + ciDebugBar.createCookie("debug-hot-reload", "show", 365); + ciDebugBar.addClass(btn, "active"); + ciDebugBar.addClass(btnImg, "rotate"); + + eventSource = ciDebugBar.hotReloadConnect(); + } + }; + + // Determine Hot Reload state on page load + if (ciDebugBar.readCookie("debug-hot-reload")) { + ciDebugBar.addClass(btn, "active"); + ciDebugBar.addClass(btnImg, "rotate"); + eventSource = ciDebugBar.hotReloadConnect(); + } + }, + + hotReloadConnect: function () { + const eventSource = new EventSource(ciSiteURL + "/__hot-reload"); + + eventSource.addEventListener("reload", function (e) { + console.log("reload", e); + window.location.reload(); + }); + + eventSource.onerror = (err) => { + console.error("EventSource failed:", err); + }; + + return eventSource; + }, + + /** + * Helper to create a cookie. + * + * @param name + * @param value + * @param days + */ + createCookie: function (name, value, days) { + if (days) { + var date = new Date(); + + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + + var expires = "; expires=" + date.toGMTString(); + } else { + var expires = ""; + } + + document.cookie = + name + "=" + value + expires + "; path=/; samesite=Lax"; + }, + + readCookie: function (name) { + var nameEQ = name + "="; + var ca = document.cookie.split(";"); + + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == " ") { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) == 0) { + return c.substring(nameEQ.length, c.length); + } + } + return null; + }, + + trimSlash: function (text) { + return text.replace(/^\/|\/$/g, ""); + }, + + routerLink: function () { + var row, _location; + var rowGet = this.toolbar.querySelectorAll( + 'td[data-debugbar-route="GET"]' + ); + var patt = /\(.+?\)/g; + + for (var i = 0; i < rowGet.length; i++) { + row = rowGet[i]; + if (!/\/\(.+?\)/.test(rowGet[i].innerText)) { + ciDebugBar.addClass(row, "debug-bar-pointer"); + row.setAttribute( + "title", + location.origin + "/" + ciDebugBar.trimSlash(row.innerText) + ); + row.addEventListener("click", function (ev) { + _location = + location.origin + + "/" + + ciDebugBar.trimSlash(ev.target.innerText); + var redirectWindow = window.open(_location, "_blank"); + redirectWindow.location; + }); + } else { + row.innerHTML = + "
" + + row.innerText + + "
" + + '' + + row.innerText.replace(patt, function (match) { + return ''; + }) + + '' + + ""; + } + } + + rowGet = this.toolbar.querySelectorAll( + 'td[data-debugbar-route="GET"] form' + ); + for (var i = 0; i < rowGet.length; i++) { + row = rowGet[i]; + + row.addEventListener("submit", function (event) { + event.preventDefault(); + var inputArray = [], + t = 0; + var input = event.target.querySelectorAll("input[type=text]"); + var tpl = event.target.getAttribute("data-debugbar-route-tpl"); + + for (var n = 0; n < input.length; n++) { + if (input[n].value.length > 0) { + inputArray.push(input[n].value); + } + } + + if (inputArray.length > 0) { + _location = + location.origin + + "/" + + tpl.replace(/\?/g, function () { + return inputArray[t++]; + }); + + var redirectWindow = window.open(_location, "_blank"); + redirectWindow.location; + } + }); + } + }, }; diff --git a/system/Debug/Toolbar/Views/toolbar.tpl.php b/system/Debug/Toolbar/Views/toolbar.tpl.php index 65cf2a4c3edf..b86baba23336 100644 --- a/system/Debug/Toolbar/Views/toolbar.tpl.php +++ b/system/Debug/Toolbar/Views/toolbar.tpl.php @@ -1,270 +1,280 @@ - - -
-
- - 🔅 - - - - ms   MB - - +
+ + 🔅 + + + + + + + + + ms   MB + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - Vars - - + + + + Vars + + -

- - - - - - -

+

+ + + + + + +

- - - - -
+ + + + +
- -
-
ActionDatetimeStatusMethodURLContent-TypeIs AJAX?
ActionDatetimeStatusMethodURLContent-TypeIs AJAX?
- + + {datetime}{datetime} {status} {method} {url}
- - - - - - - - - - - - renderTimeline($collectors, $startTime, $segmentCount, $segmentDuration, $styles) ?> - -
NAMECOMPONENTDURATION ms
- + +
+ + + + + + + + + + + + + renderTimeline($collectors, $startTime, $segmentCount, $segmentDuration, $styles) ?> + +
NAMECOMPONENTDURATION ms
+
- - - - -
-

+ + + + +
+

- setData($c['display'])->render("_{$c['titleSafe']}.tpl") ?> -
- - - + setData($c['display'])->render("_{$c['titleSafe']}.tpl") ?> +
+ + + - -
+ +
- - - $items) : ?> + + + $items) : ?> - -

-
+ +

+
- + - - - $value) : ?> - - - - - - -
+ + + $value) : ?> + + + + + + +
- -

No data to display.

- - - + +

No data to display.

+ + + - - -

Session User Data

-
+ + +

Session User Data

+
- - - - - $value) : ?> - - - - - - -
- -

No data to display.

- - -

Session doesn't seem to be active.

- + + + + + $value) : ?> + + + + + + +
+ +

No data to display.

+ + +

Session doesn't seem to be active.

+ -

Request ( )

+

Request ( )

- - -

$_GET

-
+ + +

$_GET

+
- - - $value) : ?> - - - - - - -
- + + + $value) : ?> + + + + + + +
+ - - -

$_POST

-
+ + +

$_POST

+
- - - $value) : ?> - - - - - - -
- + + + $value) : ?> + + + + + + +
+ - - -

Headers

-
+ + +

Headers

+
- - - $value) : ?> - - - - - - -
- + + + $value) : ?> + + + + + + +
+ - - -

Cookies

-
+ + +

Cookies

+
- - - $value) : ?> - - - - - - - - + + + $value) : ?> + + + + + + + + -

Response - ( ) -

+

Response + ( ) +

- - -

Headers

-
+ + +

Headers

+
- - - $value) : ?> - - - - - - -
- -
+ + + $value) : ?> + + + + + + +
+ +
- -
-

System Configuration

+ +
+

System Configuration

- setData($config)->render('_config.tpl') ?> -
+ setData($config)->render('_config.tpl') ?> +
-', start); + dynamicStyle.innerHTML = responseText.substr(start, end - start); + responseText = responseText.substr(end + 8); + + // get the first script after the first style, copy contents to dynamic_script, then remove here + start = responseText.indexOf('>', responseText.indexOf('', start); + dynamicScript.innerHTML = responseText.substr(start, end - start); + responseText = responseText.substr(end + 9); + + // check for last style block, append contents to dynamic_style, then remove here + start = responseText.indexOf('>', responseText.indexOf('', start); + dynamicStyle.innerHTML += responseText.substr(start, end - start); + responseText = responseText.substr(0, start - 8); + + toolbar.innerHTML = responseText; + + if (typeof ciDebugBar === 'object') { + ciDebugBar.init(); + } + } else if (this.readyState === 4 && this.status === 404) { + console.log('CodeIgniter DebugBar: File "WRITEPATH/debugbar/debugbar_' + time + '" not found.'); + } + }; + + xhttp.open("GET", url + "?debugbar_time=" + time, true); + xhttp.send(); +} + +window.oldXHR = window.ActiveXObject + ? new ActiveXObject('Microsoft.XMLHTTP') + : window.XMLHttpRequest; + +function newXHR() { + const realXHR = new window.oldXHR(); + + realXHR.addEventListener("readystatechange", function() { + // Only success responses and URLs that do not contains "debugbar_time" are tracked + if (realXHR.readyState === 4 && realXHR.status.toString()[0] === '2' && realXHR.responseURL.indexOf('debugbar_time') === -1) { + if (realXHR.getAllResponseHeaders().indexOf("Debugbar-Time") >= 0) { + let debugbarTime = realXHR.getResponseHeader('Debugbar-Time'); + + if (debugbarTime) { + let h2 = document.querySelector('#ci-history > h2'); + + if (h2) { + h2.innerHTML = 'History You have new debug data. '; + document.querySelector('a[data-tab="ci-history"] > span > .badge').className += ' active'; + document.getElementById('ci-history-update').addEventListener('click', function () { + loadDoc(debugbarTime); + }, false) + } + } + } + } + }, false); + return realXHR; +} + +window.XMLHttpRequest = newXHR; diff --git a/system/Debug/Toolbar/Views/toolbarloader.js.php b/system/Debug/Toolbar/Views/toolbarloader.js.php deleted file mode 100644 index add85b52acda..000000000000 --- a/system/Debug/Toolbar/Views/toolbarloader.js.php +++ /dev/null @@ -1,92 +0,0 @@ - - diff --git a/system/Debug/Toolbar/Views/toolbarstandalone.js b/system/Debug/Toolbar/Views/toolbarstandalone.js new file mode 100644 index 000000000000..f4a71d5463a7 --- /dev/null +++ b/system/Debug/Toolbar/Views/toolbarstandalone.js @@ -0,0 +1,69 @@ +/* + * Bootstrap for standalone Debug Toolbar pages (?debugbar_time=...). + */ + +if (! document.getElementById('debugbar_loader')) { + if (typeof loadDoc !== 'function') { + window.loadDoc = function (time) { + if (isNaN(time)) { + return; + } + + window.location.href = ciSiteURL + '?debugbar_time=' + time; + }; + } + + (function () { + function ensureToolbarContainer(icon, toolbar) { + let toolbarContainer = document.getElementById('toolbarContainer'); + + if (toolbarContainer) { + return; + } + + toolbarContainer = document.createElement('div'); + toolbarContainer.setAttribute('id', 'toolbarContainer'); + + if (icon) { + toolbarContainer.appendChild(icon); + } + + if (toolbar) { + toolbarContainer.appendChild(toolbar); + } + + document.body.appendChild(toolbarContainer); + } + + function initStandaloneToolbar() { + if (typeof ciDebugBar !== 'object') { + return; + } + + const icon = document.getElementById('debug-icon'); + const toolbar = document.getElementById('debug-bar'); + + if (! toolbar || ! icon) { + return; + } + + const currentTime = new URLSearchParams(window.location.search).get('debugbar_time'); + + if (currentTime && ! isNaN(currentTime)) { + if (! localStorage.getItem('debugbar-time')) { + localStorage.setItem('debugbar-time', currentTime); + } + localStorage.setItem('debugbar-time-new', currentTime); + } + + ensureToolbarContainer(icon, toolbar); + ciDebugBar.init(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initStandaloneToolbar, false); + } else { + initStandaloneToolbar(); + } + })(); +} diff --git a/system/Email/Email.php b/system/Email/Email.php index 966c08e6f830..4b3e94dfb38b 100644 --- a/system/Email/Email.php +++ b/system/Email/Email.php @@ -1,5 +1,7 @@ */ public $priority = 3; @@ -173,9 +182,9 @@ class Email * * @see http://www.ietf.org/rfc/rfc822.txt * - * @var string "\r\n" or "\n" + * @var "\r\n"|"n" */ - public $newline = "\n"; + public $newline = "\r\n"; /** * CRLF character sequence @@ -188,9 +197,9 @@ class Email * * @see http://www.ietf.org/rfc/rfc822.txt * - * @var string + * @var "\n"|"\r\n" */ - public $CRLF = "\n"; + public $CRLF = "\r\n"; /** * Whether to use Delivery Status Notification. @@ -254,14 +263,14 @@ class Email /** * SMTP Connection socket placeholder * - * @var resource|null + * @var false|resource|null */ protected $SMTPConnect; /** * Mail encoding * - * @var string '8bit' or '7bit' + * @var '7bit'|'8bit' */ protected $encoding = '8bit'; @@ -272,6 +281,11 @@ class Email */ protected $SMTPAuth = false; + /** + * Which SMTP authentication method to use: login, plain + */ + protected string $SMTPAuthMethod = 'login'; + /** * Whether to send a Reply-To header * @@ -288,6 +302,13 @@ class Email */ protected $debugMessage = []; + /** + * Raw debug messages + * + * @var list + */ + private array $debugMessageRaw = []; + /** * Recipients * @@ -328,7 +349,7 @@ class Email * * @see Email::$protocol * - * @var array + * @var list */ protected $protocols = [ 'mail', @@ -340,7 +361,7 @@ class Email * Character sets valid for 7-bit encoding, * excluding language suffix. * - * @var array + * @var list */ protected $baseCharsets = [ 'us-ascii', @@ -354,7 +375,7 @@ class Email * * @see Email::$encoding * - * @var array + * @var list */ protected $bitDepths = [ '7bit', @@ -366,7 +387,7 @@ class Email * * Actual values to send with the X-Priority header * - * @var array + * @var array */ protected $priorities = [ 1 => '1 (Highest)', @@ -379,27 +400,28 @@ class Email /** * mbstring.func_overload flag * - * @var bool + * @var bool|null */ protected static $func_overload; /** - * @param array|null $config + * @param array|\Config\Email|null $config */ public function __construct($config = null) { $this->initialize($config); + if (! isset(static::$func_overload)) { - static::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + static::$func_overload = extension_loaded('mbstring') && ini_get('mbstring.func_overload'); } } /** * Initialize preferences * - * @param array|\Config\Email $config + * @param array|\Config\Email|null $config * - * @return Email + * @return $this */ public function initialize($config) { @@ -430,24 +452,25 @@ public function initialize($config) /** * @param bool $clearAttachments * - * @return Email + * @return $this */ public function clear($clearAttachments = false) { - $this->subject = ''; - $this->body = ''; - $this->finalBody = ''; - $this->headerStr = ''; - $this->replyToFlag = false; - $this->recipients = []; - $this->CCArray = []; - $this->BCCArray = []; - $this->headers = []; - $this->debugMessage = []; + $this->subject = ''; + $this->body = ''; + $this->finalBody = ''; + $this->headerStr = ''; + $this->replyToFlag = false; + $this->recipients = []; + $this->CCArray = []; + $this->BCCArray = []; + $this->headers = []; + $this->debugMessage = []; + $this->debugMessageRaw = []; $this->setHeader('Date', $this->setDate()); - if ($clearAttachments !== false) { + if ($clearAttachments) { $this->attachments = []; } @@ -457,20 +480,20 @@ public function clear($clearAttachments = false) /** * @param string $from * @param string $name - * @param string|null $returnPath Return-Path + * @param string|null $returnPath * - * @return Email + * @return $this */ public function setFrom($from, $name = '', $returnPath = null) { - if (preg_match('/\<(.*)\>/', $from, $match)) { + if (preg_match('/\<(.*)\>/', $from, $match) === 1) { $from = $match[1]; } if ($this->validate) { $this->validateEmail($this->stringToArray($from)); - if ($returnPath) { + if ($returnPath !== null) { $this->validateEmail($this->stringToArray($returnPath)); } } @@ -480,7 +503,7 @@ public function setFrom($from, $name = '', $returnPath = null) if ($name !== '') { // only use Q encoding if there are characters that would require it - if (! preg_match('/[\200-\377]/', $name)) { + if (preg_match('/[\200-\377]/', $name) !== 1) { $name = '"' . addcslashes($name, "\0..\37\177'\"\\") . '"'; } else { $name = $this->prepQEncoding($name); @@ -488,9 +511,8 @@ public function setFrom($from, $name = '', $returnPath = null) } $this->setHeader('From', $name . ' <' . $from . '>'); - if (! isset($returnPath)) { - $returnPath = $from; - } + $returnPath ??= $from; + $this->setHeader('Return-Path', '<' . $returnPath . '>'); $this->tmpArchive['returnPath'] = $returnPath; @@ -501,11 +523,11 @@ public function setFrom($from, $name = '', $returnPath = null) * @param string $replyto * @param string $name * - * @return Email + * @return $this */ public function setReplyTo($replyto, $name = '') { - if (preg_match('/\<(.*)\>/', $replyto, $match)) { + if (preg_match('/\<(.*)\>/', $replyto, $match) === 1) { $replyto = $match[1]; } @@ -517,7 +539,7 @@ public function setReplyTo($replyto, $name = '') $this->tmpArchive['replyName'] = $name; // only use Q encoding if there are characters that would require it - if (! preg_match('/[\200-\377]/', $name)) { + if (preg_match('/[\200-\377]/', $name) !== 1) { $name = '"' . addcslashes($name, "\0..\37\177'\"\\") . '"'; } else { $name = $this->prepQEncoding($name); @@ -534,7 +556,7 @@ public function setReplyTo($replyto, $name = '') /** * @param array|string $to * - * @return Email + * @return $this */ public function setTo($to) { @@ -557,7 +579,7 @@ public function setTo($to) /** * @param string $cc * - * @return Email + * @return $this */ public function setCC($cc) { @@ -582,7 +604,7 @@ public function setCC($cc) * @param string $bcc * @param string $limit * - * @return Email + * @return $this */ public function setBCC($bcc, $limit = '') { @@ -610,7 +632,7 @@ public function setBCC($bcc, $limit = '') /** * @param string $subject * - * @return Email + * @return $this */ public function setSubject($subject) { @@ -625,7 +647,7 @@ public function setSubject($subject) /** * @param string $body * - * @return Email + * @return $this */ public function setMessage($body) { @@ -645,7 +667,7 @@ public function setMessage($body) public function attach($file, $disposition = '', $newname = null, $mime = '') { if ($mime === '') { - if (strpos($file, '://') === false && ! is_file($file)) { + if (! str_contains($file, '://') && ! is_file($file)) { $this->setErrorMessage(lang('Email.attachmentMissing', [$file])); return false; @@ -692,6 +714,7 @@ public function attach($file, $disposition = '', $newname = null, $mime = '') public function setAttachmentCID($filename) { foreach ($this->attachments as $i => $attachment) { + // For file path. if ($attachment['name'][0] === $filename) { $this->attachments[$i]['multipart'] = 'related'; @@ -699,6 +722,15 @@ public function setAttachmentCID($filename) return $this->attachments[$i]['cid']; } + + // For buffer string. + if ($attachment['name'][1] === $filename) { + $this->attachments[$i]['multipart'] = 'related'; + + $this->attachments[$i]['cid'] = uniqid(basename($attachment['name'][1]) . '@', true); + + return $this->attachments[$i]['cid']; + } } return false; @@ -708,7 +740,7 @@ public function setAttachmentCID($filename) * @param string $header * @param string $value * - * @return Email + * @return $this */ public function setHeader($header, $value) { @@ -718,14 +750,16 @@ public function setHeader($header, $value) } /** - * @param string $email + * @param list|string $email * - * @return array + * @return list */ protected function stringToArray($email) { if (! is_array($email)) { - return (strpos($email, ',') !== false) ? preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY) : (array) trim($email); + return str_contains($email, ',') + ? preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY) + : (array) trim($email); } return $email; @@ -734,7 +768,7 @@ protected function stringToArray($email) /** * @param string $str * - * @return Email + * @return $this */ public function setAltMessage($str) { @@ -746,11 +780,11 @@ public function setAltMessage($str) /** * @param string $type * - * @return Email + * @return $this */ public function setMailType($type = 'text') { - $this->mailType = ($type === 'html') ? 'html' : 'text'; + $this->mailType = $type === 'html' ? 'html' : 'text'; return $this; } @@ -758,7 +792,7 @@ public function setMailType($type = 'text') /** * @param bool $wordWrap * - * @return Email + * @return $this */ public function setWordWrap($wordWrap = true) { @@ -770,7 +804,7 @@ public function setWordWrap($wordWrap = true) /** * @param string $protocol * - * @return Email + * @return $this */ public function setProtocol($protocol = 'mail') { @@ -782,7 +816,7 @@ public function setProtocol($protocol = 'mail') /** * @param int $n * - * @return Email + * @return $this */ public function setPriority($n = 3) { @@ -794,7 +828,7 @@ public function setPriority($n = 3) /** * @param string $newline * - * @return Email + * @return $this */ public function setNewline($newline = "\n") { @@ -806,11 +840,11 @@ public function setNewline($newline = "\n") /** * @param string $CRLF * - * @return Email + * @return $this */ public function setCRLF($CRLF = "\n") { - $this->CRLF = ($CRLF !== "\n" && $CRLF !== "\r\n" && $CRLF !== "\r") ? "\n" : $CRLF; + $this->CRLF = in_array($CRLF, ["\n", "\r\n", "\r"], true) ? $CRLF : "\n"; return $this; } @@ -849,7 +883,7 @@ protected function getEncoding() } foreach ($this->baseCharsets as $charset) { - if (strpos($this->charset, $charset) === 0) { + if (str_starts_with($this->charset, $charset)) { $this->encoding = '7bit'; break; @@ -865,10 +899,10 @@ protected function getEncoding() protected function getContentType() { if ($this->mailType === 'html') { - return empty($this->attachments) ? 'html' : 'html-attach'; + return $this->attachments === [] ? 'html' : 'html-attach'; } - if ($this->mailType === 'text' && ! empty($this->attachments)) { + if ($this->mailType === 'text' && $this->attachments !== []) { return 'plain-attach'; } @@ -884,7 +918,7 @@ protected function setDate() { $timezone = date('Z'); $operator = ($timezone[0] === '-') ? '-' : '+'; - $timezone = abs($timezone); + $timezone = abs((int) $timezone); $timezone = floor($timezone / 3600) * 100 + ($timezone % 3600) / 60; return sprintf('%s %s%04d', date('D, j M Y H:i:s'), $operator, $timezone); @@ -970,8 +1004,8 @@ public function cleanEmail($email) */ protected function getAltMessage() { - if (! empty($this->altMessage)) { - return ($this->wordWrap) ? $this->wordWrap($this->altMessage, 76) : $this->altMessage; + if ($this->altMessage !== '') { + return $this->wordWrap ? $this->wordWrap($this->altMessage, 76) : $this->altMessage; } $body = preg_match('/\(.*)\<\/body\>/si', $this->body, $match) ? $match[1] : $this->body; @@ -983,7 +1017,7 @@ protected function getAltMessage() $body = preg_replace('| +|', ' ', $body); - return ($this->wordWrap) ? $this->wordWrap($body, 76) : $body; + return $this->wordWrap ? $this->wordWrap($body, 76) : $body; } /** @@ -994,11 +1028,13 @@ protected function getAltMessage() */ public function wordWrap($str, $charlim = null) { - if (empty($charlim)) { - $charlim = empty($this->wrapChars) ? 76 : $this->wrapChars; + $charlim ??= 0; + + if ($charlim === 0) { + $charlim = $this->wrapChars === 0 ? 76 : $this->wrapChars; } - if (strpos($str, "\r") !== false) { + if (str_contains($str, "\r")) { $str = str_replace(["\r\n", "\r"], "\n", $str); } @@ -1006,7 +1042,7 @@ public function wordWrap($str, $charlim = null) $unwrap = []; - if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches)) { + if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches) >= 1) { for ($i = 0, $c = count($matches[0]); $i < $c; $i++) { $unwrap[] = $matches[1][$i]; $str = str_replace($matches[0][$i], '{{unwrapped' . $i . '}}', $str); @@ -1046,10 +1082,8 @@ public function wordWrap($str, $charlim = null) $output .= $line . $this->newline; } - if ($unwrap) { - foreach ($unwrap as $key => $val) { - $output = str_replace('{{unwrapped' . $key . '}}', $val, $output); - } + foreach ($unwrap as $key => $val) { + $output = str_replace('{{unwrapped' . $key . '}}', $val, $output); } return $output; @@ -1057,6 +1091,8 @@ public function wordWrap($str, $charlim = null) /** * Build final headers + * + * @return void */ protected function buildHeaders() { @@ -1070,6 +1106,8 @@ protected function buildHeaders() /** * Write Headers as a string + * + * @return void */ protected function writeHeaders() { @@ -1096,6 +1134,8 @@ protected function writeHeaders() /** * Build Final Body and attachments + * + * @return void */ protected function buildMessage() { @@ -1204,7 +1244,9 @@ protected function buildMessage() $this->headerStr .= $hdr; } - static::strlen($body) && $body .= $this->newline . $this->newline; + if (static::strlen($body) > 0) { + $body .= $this->newline . $this->newline; + } $body .= $this->getMimeMessage() . $this->newline . $this->newline . '--' . $lastBoundary . $this->newline @@ -1219,13 +1261,13 @@ protected function buildMessage() . $this->prepQuotedPrintable($this->body) . $this->newline . $this->newline . '--' . $altBoundary . '--' . $this->newline . $this->newline; - if (! empty($relBoundary)) { + if (isset($relBoundary)) { $body .= $this->newline . $this->newline; $this->appendAttachments($body, $relBoundary, 'related'); } // multipart/mixed attachments - if (! empty($atcBoundary)) { + if (isset($atcBoundary)) { $body .= $this->newline . $this->newline; $this->appendAttachments($body, $atcBoundary, 'mixed'); } @@ -1237,13 +1279,13 @@ protected function buildMessage() } /** - * @param mixed $type + * @param string $type * * @return bool */ protected function attachmentsHaveMultipart($type) { - foreach ($this->attachments as &$attachment) { + foreach ($this->attachments as $attachment) { if ($attachment['multipart'] === $type) { return true; } @@ -1256,6 +1298,8 @@ protected function attachmentsHaveMultipart($type) * @param string $body Message body to append to * @param string $boundary Multipart boundary * @param string|null $multipart When provided, only attachments of this type will be processed + * + * @return void */ protected function appendAttachments(&$body, $boundary, $multipart = null) { @@ -1269,14 +1313,14 @@ protected function appendAttachments(&$body, $boundary, $multipart = null) . 'Content-Type: ' . $attachment['type'] . '; name="' . $name . '"' . $this->newline . 'Content-Disposition: ' . $attachment['disposition'] . ';' . $this->newline . 'Content-Transfer-Encoding: base64' . $this->newline - . (empty($attachment['cid']) ? '' : 'Content-ID: <' . $attachment['cid'] . '>' . $this->newline) + . (isset($attachment['cid']) && $attachment['cid'] !== '' ? 'Content-ID: <' . $attachment['cid'] . '>' . $this->newline : '') . $this->newline . $attachment['content'] . $this->newline; } // $name won't be set if no attachments were appended, // and therefore a boundary wouldn't be necessary - if (! empty($name)) { + if (isset($name)) { $body .= '--' . $boundary . '--'; } } @@ -1291,89 +1335,6 @@ protected function appendAttachments(&$body, $boundary, $multipart = null) */ protected function prepQuotedPrintable($str) { - // ASCII code numbers for "safe" characters that can always be - // used literally, without encoding, as described in RFC 2049. - // http://www.ietf.org/rfc/rfc2049.txt - static $asciiSafeChars = [ - // ' ( ) + , - . / : = ? - 39, - 40, - 41, - 43, - 44, - 45, - 46, - 47, - 58, - 61, - 63, - // numbers - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - // upper-case letters - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - // lower-case letters - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - ]; - // We are intentionally wrapping so mail servers will encode characters // properly and MUAs will behave, so {unwrap} must go! $str = str_replace(['{unwrap}', '{/unwrap}'], '', $str); @@ -1390,50 +1351,59 @@ protected function prepQuotedPrintable($str) $str = preg_replace(['| +|', '/\x00+/'], [' ', ''], $str); // Standardize newlines - if (strpos($str, "\r") !== false) { + if (str_contains($str, "\r")) { $str = str_replace(["\r\n", "\r"], "\n", $str); } - $escape = '='; + static $asciiSafeChars; + if ($asciiSafeChars === null) { + $safeChars = [39, 40, 41, 43, 44, 45, 46, 47, 58, 61, 63]; + $safeChars = array_merge($safeChars, range(48, 57), range(65, 90), range(97, 122)); + $asciiSafeChars = array_fill_keys($safeChars, true); + } + $output = ''; foreach (explode("\n", $str) as $line) { - $length = static::strlen($line); - $temp = ''; + $length = static::strlen($line); + $temp = ''; + $tempLen = 0; // Loop through each character in the line to add soft-wrap // characters at the end of a line " =\r\n" and add the newly // processed line(s) to the output (see comment on $crlf class property) for ($i = 0; $i < $length; $i++) { - // Grab the next character - $char = $line[$i]; - $ascii = ord($char); + $char = $line[$i]; + $ascii = ord($char); + $charLen = 1; // Convert spaces and tabs but only if it's the end of the line if ($ascii === 32 || $ascii === 9) { if ($i === ($length - 1)) { - $char = $escape . sprintf('%02s', dechex($ascii)); + $char = sprintf('=%02X', $ascii); + $charLen = 3; } } // DO NOT move this below the $ascii_safe_chars line! // // = (equals) signs are allowed by RFC2049, but must be encoded // as they are the encoding delimiter! - elseif ($ascii === 61) { - $char = $escape . strtoupper(sprintf('%02s', dechex($ascii))); // =3D - } elseif (! in_array($ascii, $asciiSafeChars, true)) { - $char = $escape . strtoupper(sprintf('%02s', dechex($ascii))); + elseif ($ascii === 61 || ! isset($asciiSafeChars[$ascii])) { + $char = sprintf('=%02X', $ascii); + $charLen = 3; } // If we're at the character limit, add the line to the output, // reset our temp variable, and keep on chuggin' - if ((static::strlen($temp) + static::strlen($char)) >= 76) { - $output .= $temp . $escape . $this->CRLF; - $temp = ''; + if (($tempLen + $charLen) >= 76) { + $output .= $temp . '=' . $this->CRLF; + $temp = ''; + $tempLen = 0; } // Add the character to our temporary line $temp .= $char; + $tempLen += $charLen; } // Add our completed line to the output @@ -1532,7 +1502,11 @@ public function send($autoClear = true) $this->setReplyTo($this->headers['From']); } - if (empty($this->recipients) && ! isset($this->headers['To']) && empty($this->BCCArray) && ! isset($this->headers['Bcc']) && ! isset($this->headers['Cc'])) { + if ( + empty($this->recipients) && ! isset($this->headers['To']) + && empty($this->BCCArray) && ! isset($this->headers['Bcc']) + && ! isset($this->headers['Cc']) + ) { $this->setErrorMessage(lang('Email.noRecipients')); return false; @@ -1568,6 +1542,8 @@ public function send($autoClear = true) /** * Batch Bcc Send. Sends groups of BCCs in batches + * + * @return void */ public function batchBCCSend() { @@ -1612,29 +1588,30 @@ public function batchBCCSend() /** * Unwrap special elements + * + * @return void */ protected function unwrapSpecials() { $this->finalBody = preg_replace_callback( '/\{unwrap\}(.*?)\{\/unwrap\}/si', - [ - $this, - 'removeNLCallback', - ], - $this->finalBody + $this->removeNLCallback(...), + $this->finalBody, ); } /** * Strip line-breaks via callback * - * @param string $matches + * @used-by unwrapSpecials() + * + * @param list $matches * * @return string */ protected function removeNLCallback($matches) { - if (strpos($matches[1], "\r") !== false || strpos($matches[1], "\n") !== false) { + if (str_contains($matches[1], "\r") || str_contains($matches[1], "\n")) { $matches[1] = str_replace(["\r\n", "\r", "\n"], '', $matches[1]); } @@ -1649,18 +1626,24 @@ protected function removeNLCallback($matches) protected function spoolEmail() { $this->unwrapSpecials(); - $protocol = $this->getProtocol(); - $method = 'sendWith' . ucfirst($protocol); + $protocol = $this->getProtocol(); + $upperFirstProtocol = ucfirst($protocol); + $method = 'sendWith' . $upperFirstProtocol; try { $success = $this->{$method}(); } catch (ErrorException $e) { $success = false; - log_message('error', 'Email: ' . $method . ' throwed ' . $e->getMessage()); + log_message('error', 'Email: ' . $method . ' threw ' . $e); } if (! $success) { - $this->setErrorMessage(lang('Email.sendFailure' . ($protocol === 'mail' ? 'PHPMail' : ucfirst($protocol)))); + $message = lang('Email.sendFailure' . ($protocol === 'mail' ? 'PHPMail' : $upperFirstProtocol)); + + log_message('error', 'Email: ' . $message); + log_message('error', $this->printDebuggerRaw()); + + $this->setErrorMessage($message); return false; } @@ -1810,7 +1793,7 @@ protected function sendWithSmtp() $this->setErrorMessage($reply); $this->SMTPEnd(); - if (strpos($reply, '250') !== 0) { + if (! str_starts_with($reply, '250')) { $this->setErrorMessage(lang('Email.SMTPError', [$reply])); return false; @@ -1821,6 +1804,8 @@ protected function sendWithSmtp() /** * Shortcut to send RSET or QUIT depending on keep-alive + * + * @return void */ protected function SMTPEnd() { @@ -1832,15 +1817,19 @@ protected function SMTPEnd() */ protected function SMTPConnect() { - if (is_resource($this->SMTPConnect)) { + if ($this->isSMTPConnected()) { return true; } $ssl = ''; + // Connection to port 465 should use implicit TLS (without STARTTLS) + // as per RFC 8314. if ($this->SMTPPort === 465) { $ssl = 'tls://'; - } elseif ($this->SMTPCrypto === 'ssl') { + } + // But if $SMTPCrypto is set to `ssl`, SSL can be used. + if ($this->SMTPCrypto === 'ssl') { $ssl = 'ssl://'; } @@ -1849,10 +1838,10 @@ protected function SMTPConnect() $this->SMTPPort, $errno, $errstr, - $this->SMTPTimeout + $this->SMTPTimeout, ); - if (! is_resource($this->SMTPConnect)) { + if (! $this->isSMTPConnected()) { $this->setErrorMessage(lang('Email.SMTPError', [$errno . ' ' . $errstr])); return false; @@ -1867,7 +1856,10 @@ protected function SMTPConnect() $crypto = stream_socket_enable_crypto( $this->SMTPConnect, true, - STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT + | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT + | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT, ); if ($crypto !== true) { @@ -1939,7 +1931,8 @@ protected function sendCommand($cmd, $data = '') $reply = $this->getSMTPData(); - $this->debugMessage[] = '
' . $cmd . ': ' . $reply . '
'; + $this->debugMessage[] = '
' . $cmd . ': ' . $reply . '
'; + $this->debugMessageRaw[] = $cmd . ': ' . $reply; if ($resp === null || ((int) static::substr($reply, 0, 3) !== $resp)) { $this->setErrorMessage(lang('Email.SMTPError', [$reply])); @@ -1963,45 +1956,72 @@ protected function SMTPAuthenticate() return true; } - if ($this->SMTPUser === '' && $this->SMTPPass === '') { + // If no username or password is set + if ($this->SMTPUser === '' || $this->SMTPPass === '') { $this->setErrorMessage(lang('Email.noSMTPAuth')); return false; } - $this->sendData('AUTH LOGIN'); + // normalize in case user entered capital words LOGIN/PLAIN + $this->SMTPAuthMethod = strtolower($this->SMTPAuthMethod); + + // Validate supported authentication methods + if (! in_array($this->SMTPAuthMethod, ['login', 'plain'], true)) { + $this->setErrorMessage(lang('Email.invalidSMTPAuthMethod', [$this->SMTPAuthMethod])); + + return false; + } + + $upperAuthMethod = strtoupper($this->SMTPAuthMethod); + // send initial 'AUTH' command + $this->sendData('AUTH ' . $upperAuthMethod); $reply = $this->getSMTPData(); - if (strpos($reply, '503') === 0) { // Already authenticated + if (str_starts_with($reply, '503')) { // Already authenticated return true; } - if (strpos($reply, '334') !== 0) { - $this->setErrorMessage(lang('Email.failedSMTPLogin', [$reply])); + // if 'AUTH' command is unsuported by the server + if (! str_starts_with($reply, '334')) { + $this->setErrorMessage(lang('Email.failureSMTPAuthMethod', [$upperAuthMethod])); return false; } - $this->sendData(base64_encode($this->SMTPUser)); - $reply = $this->getSMTPData(); + switch ($this->SMTPAuthMethod) { + case 'login': + $this->sendData(base64_encode($this->SMTPUser)); + $reply = $this->getSMTPData(); - if (strpos($reply, '334') !== 0) { - $this->setErrorMessage(lang('Email.SMTPAuthUsername', [$reply])); + if (! str_starts_with($reply, '334')) { + $this->setErrorMessage(lang('Email.SMTPAuthUsername', [$reply])); - return false; + return false; + } + + $this->sendData(base64_encode($this->SMTPPass)); + break; + + case 'plain': + // send credentials as the single second command + $authString = "\0" . $this->SMTPUser . "\0" . $this->SMTPPass; + + $this->sendData(base64_encode($authString)); + break; } - $this->sendData(base64_encode($this->SMTPPass)); $reply = $this->getSMTPData(); + if (! str_starts_with($reply, '235')) { // Authentication failed + $errorMessage = $this->SMTPAuthMethod === 'plain' ? 'Email.SMTPAuthCredentials' : 'Email.SMTPAuthPassword'; - if (strpos($reply, '235') !== 0) { - $this->setErrorMessage(lang('Email.SMTPAuthPassword', [$reply])); + $this->setErrorMessage(lang($errorMessage, [$reply])); return false; } if ($this->SMTPKeepAlive) { - $this->SMTPAuth = false; + $this->SMTPAuth = false; // Prevent re-authentication for keep-alive sessions } return true; @@ -2026,8 +2046,8 @@ protected function sendData($data) // See https://bugs.php.net/bug.php?id=39598 and http://php.net/manual/en/function.fwrite.php#96951 if ($result === 0) { if ($timestamp === 0) { - $timestamp = time(); - } elseif ($timestamp < (time() - $this->SMTPTimeout)) { + $timestamp = Time::now()->getTimestamp(); + } elseif ($timestamp < (Time::now()->getTimestamp() - $this->SMTPTimeout)) { $result = false; break; @@ -2080,20 +2100,29 @@ protected function getSMTPData() */ protected function getHostname() { - if (isset($_SERVER['SERVER_NAME'])) { - return $_SERVER['SERVER_NAME']; + $superglobals = service('superglobals'); + + $serverName = $superglobals->server('SERVER_NAME'); + if (! in_array($serverName, [null, ''], true)) { + return $serverName; + } + + $serverAddr = $superglobals->server('SERVER_ADDR'); + if (! in_array($serverAddr, [null, ''], true)) { + return '[' . $serverAddr . ']'; } - if (isset($_SERVER['SERVER_ADDR'])) { - return '[' . $_SERVER['SERVER_ADDR'] . ']'; + $hostname = gethostname(); + if ($hostname !== false) { + return $hostname; } return '[127.0.0.1]'; } /** - * @param array $include List of raw data chunks to include in the output - * Valid options are: 'headers', 'subject', 'body' + * @param array|string $include List of raw data chunks to include in the output + * Valid options are: 'headers', 'subject', 'body' * * @return string */ @@ -2121,12 +2150,23 @@ public function printDebugger($include = ['headers', 'subject', 'body']) return $msg . ($rawData === '' ? '' : '
' . $rawData . '
'); } + /** + * Returns raw debug messages + */ + private function printDebuggerRaw(): string + { + return implode("\n", $this->debugMessageRaw); + } + /** * @param string $msg + * + * @return void */ protected function setErrorMessage($msg) { - $this->debugMessage[] = $msg . '
'; + $this->debugMessage[] = $msg . '
'; + $this->debugMessageRaw[] = $msg; } /** @@ -2140,13 +2180,19 @@ protected function mimeTypes($ext = '') { $mime = Mimes::guessTypeFromExtension(strtolower($ext)); - return ! empty($mime) ? $mime : 'application/x-unknown-content-type'; + return empty($mime) ? 'application/x-unknown-content-type' : $mime; } public function __destruct() { - if (is_resource($this->SMTPConnect)) { - $this->sendCommand('quit'); + if ($this->isSMTPConnected()) { + try { + $this->sendCommand('quit'); + } catch (ErrorException $e) { + $protocol = $this->getProtocol(); + $method = 'sendWith' . ucfirst($protocol); + log_message('error', 'Email: ' . $method . ' threw ' . $e); + } } } @@ -2196,4 +2242,16 @@ protected function setArchiveValues(): array return $this->archive; } + + /** + * Checks if there is an active SMTP connection. + * + * @return bool True if SMTP connection is established and open, false otherwise + */ + protected function isSMTPConnected(): bool + { + return $this->SMTPConnect !== null + && $this->SMTPConnect !== false + && get_debug_type($this->SMTPConnect) !== 'resource (closed)'; + } } diff --git a/system/Encryption/EncrypterInterface.php b/system/Encryption/EncrypterInterface.php index 32dc8644926d..9c1b0d36d1be 100644 --- a/system/Encryption/EncrypterInterface.php +++ b/system/Encryption/EncrypterInterface.php @@ -1,5 +1,7 @@ $drivers + * @property-read string $key + * + * @see \CodeIgniter\Encryption\EncryptionTest */ class Encryption { @@ -71,7 +80,7 @@ class Encryption /** * Handlers that are to be installed * - * @var array + * @var array */ protected $handlers = []; @@ -80,11 +89,11 @@ class Encryption */ public function __construct(?EncryptionConfig $config = null) { - $config = $config ?? new EncryptionConfig(); + $config ??= new EncryptionConfig(); $this->key = $config->key; $this->driver = $config->driver; - $this->digest = $config->digest ?? 'SHA512'; + $this->digest = $config->digest; $this->handlers = [ 'OpenSSL' => extension_loaded('openssl'), @@ -100,16 +109,16 @@ public function __construct(?EncryptionConfig $config = null) /** * Initialize or re-initialize an encrypter * - * @throws EncryptionException - * * @return EncrypterInterface + * + * @throws EncryptionException */ public function initialize(?EncryptionConfig $config = null) { - if ($config) { + if ($config instanceof EncryptionConfig) { $this->key = $config->key; $this->driver = $config->driver; - $this->digest = $config->digest ?? 'SHA512'; + $this->digest = $config->digest; } if (empty($this->driver)) { @@ -129,6 +138,10 @@ public function initialize(?EncryptionConfig $config = null) $handlerName = 'CodeIgniter\\Encryption\\Handlers\\' . $this->driver . 'Handler'; $this->encrypter = new $handlerName($config); + if (($config->previousKeys ?? []) !== []) { + $this->encrypter = new KeyRotationDecorator($this->encrypter, $config->previousKeys); + } + return $this->encrypter; } @@ -149,7 +162,7 @@ public static function createKey($length = 32) * * @param string $key Property name * - * @return mixed + * @return array|string|null */ public function __get($key) { diff --git a/system/Encryption/Exceptions/EncryptionException.php b/system/Encryption/Exceptions/EncryptionException.php index d226fd5dd08b..9a0935d7d91d 100644 --- a/system/Encryption/Exceptions/EncryptionException.php +++ b/system/Encryption/Exceptions/EncryptionException.php @@ -1,5 +1,7 @@ $value) { @@ -61,7 +55,7 @@ protected static function substr($str, $start, $length = null) * * @param string $key Property name * - * @return mixed + * @return array|bool|int|string|null */ public function __get($key) { diff --git a/system/Encryption/Handlers/OpenSSLHandler.php b/system/Encryption/Handlers/OpenSSLHandler.php index dac36dc71dc7..8e1be06d60ed 100644 --- a/system/Encryption/Handlers/OpenSSLHandler.php +++ b/system/Encryption/Handlers/OpenSSLHandler.php @@ -1,5 +1,7 @@ digest size] + */ + protected array $digestSize = [ + 'SHA224' => 28, + 'SHA256' => 32, + 'SHA384' => 48, + 'SHA512' => 64, + ]; + /** * Cipher to use * @@ -39,35 +56,59 @@ class OpenSSLHandler extends BaseHandler */ protected $key = ''; + /** + * Whether the cipher-text should be raw. If set to false, then it will be base64 encoded. + */ + protected bool $rawData = true; + + /** + * Encryption key info. + * This setting is only used by OpenSSLHandler. + * + * Set to 'encryption' for CI3 Encryption compatibility. + */ + public string $encryptKeyInfo = ''; + + /** + * Authentication key info. + * This setting is only used by OpenSSLHandler. + * + * Set to 'authentication' for CI3 Encryption compatibility. + */ + public string $authKeyInfo = ''; + /** * {@inheritDoc} */ - public function encrypt($data, $params = null) + public function encrypt(#[SensitiveParameter] $data, #[SensitiveParameter] $params = null) { // Allow key override - if ($params) { - $this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params; - } + $key = $params !== null + ? (is_array($params) && isset($params['key']) ? $params['key'] : $params) + : $this->key; - if (empty($this->key)) { + if (empty($key)) { throw EncryptionException::forNeedsStarterKey(); } // derive a secret key - $secret = \hash_hkdf($this->digest, $this->key); + $encryptKey = \hash_hkdf($this->digest, $key, 0, $this->encryptKeyInfo); // basic encryption $iv = ($ivSize = \openssl_cipher_iv_length($this->cipher)) ? \openssl_random_pseudo_bytes($ivSize) : null; - $data = \openssl_encrypt($data, $this->cipher, $secret, OPENSSL_RAW_DATA, $iv); + $data = \openssl_encrypt($data, $this->cipher, $encryptKey, OPENSSL_RAW_DATA, $iv); if ($data === false) { throw EncryptionException::forEncryptionFailed(); } - $result = $iv . $data; + $result = $this->rawData ? $iv . $data : base64_encode($iv . $data); - $hmacKey = \hash_hmac($this->digest, $result, $secret, true); + // derive a secret key + $authKey = \hash_hkdf($this->digest, $key, 0, $this->authKeyInfo); + + $hmacKey = \hash_hmac($this->digest, $result, $authKey, $this->rawData); return $hmacKey . $result; } @@ -75,29 +116,34 @@ public function encrypt($data, $params = null) /** * {@inheritDoc} */ - public function decrypt($data, $params = null) + public function decrypt($data, #[SensitiveParameter] $params = null) { // Allow key override - if ($params) { - $this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params; - } + $key = $params !== null + ? (is_array($params) && isset($params['key']) ? $params['key'] : $params) + : $this->key; - if (empty($this->key)) { + if (empty($key)) { throw EncryptionException::forNeedsStarterKey(); } // derive a secret key - $secret = \hash_hkdf($this->digest, $this->key); + $authKey = \hash_hkdf($this->digest, $key, 0, $this->authKeyInfo); + + $hmacLength = $this->rawData + ? $this->digestSize[$this->digest] + : $this->digestSize[$this->digest] * 2; - $hmacLength = self::substr($this->digest, 3) / 8; - $hmacKey = self::substr($data, 0, $hmacLength); - $data = self::substr($data, $hmacLength); - $hmacCalc = \hash_hmac($this->digest, $data, $secret, true); + $hmacKey = self::substr($data, 0, $hmacLength); + $data = self::substr($data, $hmacLength); + $hmacCalc = \hash_hmac($this->digest, $data, $authKey, $this->rawData); if (! hash_equals($hmacKey, $hmacCalc)) { throw EncryptionException::forAuthenticationFailed(); } + $data = $this->rawData ? $data : base64_decode($data, true); + if ($ivSize = \openssl_cipher_iv_length($this->cipher)) { $iv = self::substr($data, 0, $ivSize); $data = self::substr($data, $ivSize); @@ -105,6 +151,15 @@ public function decrypt($data, $params = null) $iv = null; } - return \openssl_decrypt($data, $this->cipher, $secret, OPENSSL_RAW_DATA, $iv); + // derive a secret key + $encryptKey = \hash_hkdf($this->digest, $key, 0, $this->encryptKeyInfo); + + $result = \openssl_decrypt($data, $this->cipher, $encryptKey, OPENSSL_RAW_DATA, $iv); + + if ($result === false) { + throw EncryptionException::forAuthenticationFailed(); + } + + return $result; } } diff --git a/system/Encryption/Handlers/SodiumHandler.php b/system/Encryption/Handlers/SodiumHandler.php index 9dc4cdbdec44..55bf14893251 100644 --- a/system/Encryption/Handlers/SodiumHandler.php +++ b/system/Encryption/Handlers/SodiumHandler.php @@ -1,5 +1,7 @@ parseParams($params); + $key = $this->key; + $blockSize = $this->blockSize; + + if ($params !== null) { + if (is_array($params)) { + $key = $params['key'] ?? $key; + $blockSize = $params['blockSize'] ?? $blockSize; + } else { + $key = $params; + } + } - if (empty($this->key)) { + if (empty($key) || strlen((string) $key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { throw EncryptionException::forNeedsStarterKey(); } - // create a nonce for this operation - $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes - - // add padding before we encrypt the data - if ($this->blockSize <= 0) { + if ($blockSize <= 0) { throw EncryptionException::forEncryptionFailed(); } - $data = sodium_pad($data, $this->blockSize); + $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); + $data = sodium_pad($data, $blockSize); - // encrypt message and combine with nonce - $ciphertext = $nonce . sodium_crypto_secretbox($data, $nonce, $this->key); + $ciphertext = $nonce . sodium_crypto_secretbox($data, $nonce, $key); - // cleanup buffers sodium_memzero($data); - sodium_memzero($this->key); + sodium_memzero($key); return $ciphertext; } @@ -68,41 +78,45 @@ public function encrypt($data, $params = null) /** * {@inheritDoc} */ - public function decrypt($data, $params = null) + public function decrypt($data, #[SensitiveParameter] $params = null) { - $this->parseParams($params); + $key = $this->key; + $blockSize = $this->blockSize; + + if ($params !== null) { + if (is_array($params)) { + $key = $params['key'] ?? $key; + $blockSize = $params['blockSize'] ?? $blockSize; + } else { + $key = $params; + } + } - if (empty($this->key)) { + if (empty($key) || strlen((string) $key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { throw EncryptionException::forNeedsStarterKey(); } if (mb_strlen($data, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) { - // message was truncated throw EncryptionException::forAuthenticationFailed(); } - // Extract info from encrypted data $nonce = self::substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); $ciphertext = self::substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); - // decrypt data - $data = sodium_crypto_secretbox_open($ciphertext, $nonce, $this->key); + $data = sodium_crypto_secretbox_open($ciphertext, $nonce, $key); - if ($data === false) { - // message was tampered in transit - throw EncryptionException::forAuthenticationFailed(); // @codeCoverageIgnore + if ($data === false || $blockSize <= 0) { + throw EncryptionException::forAuthenticationFailed(); } - // remove extra padding during encryption - if ($this->blockSize <= 0) { + try { + $data = sodium_unpad($data, $blockSize); + } catch (SodiumException) { throw EncryptionException::forAuthenticationFailed(); } - $data = sodium_unpad($data, $this->blockSize); - - // cleanup buffers sodium_memzero($ciphertext); - sodium_memzero($this->key); + sodium_memzero($key); return $data; } @@ -112,7 +126,11 @@ public function decrypt($data, $params = null) * * @param array|string|null $params * + * @return void + * * @throws EncryptionException If key is empty + * + * @deprecated 4.7.0 No longer used. */ protected function parseParams($params) { diff --git a/system/Encryption/KeyRotationDecorator.php b/system/Encryption/KeyRotationDecorator.php new file mode 100644 index 000000000000..7202bcc06764 --- /dev/null +++ b/system/Encryption/KeyRotationDecorator.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Encryption; + +use CodeIgniter\Encryption\Exceptions\EncryptionException; +use SensitiveParameter; + +/** + * Key Rotation Decorator + * + * Wraps any EncrypterInterface implementation to provide automatic + * fallback to previous encryption keys during decryption. This enables + * seamless key rotation without requiring re-encryption of existing data. + */ +class KeyRotationDecorator implements EncrypterInterface +{ + /** + * @param EncrypterInterface $innerHandler The wrapped encryption handler + * @param list $previousKeys Array of previous encryption keys + */ + public function __construct( + private readonly EncrypterInterface $innerHandler, + private readonly array $previousKeys, + ) { + } + + /** + * {@inheritDoc} + * + * Encryption always uses the inner handler's current key. + */ + public function encrypt(#[SensitiveParameter] $data, #[SensitiveParameter] $params = null) + { + return $this->innerHandler->encrypt($data, $params); + } + + /** + * {@inheritDoc} + * + * Attempts decryption with current key first. If that fails and no + * explicit key was provided in $params, tries each previous key. + * + * @throws EncryptionException + */ + public function decrypt($data, #[SensitiveParameter] $params = null) + { + try { + return $this->innerHandler->decrypt($data, $params); + } catch (EncryptionException $e) { + // Don't try previous keys if an explicit key was provided + if (is_string($params) || (is_array($params) && isset($params['key']))) { + throw $e; + } + + if ($this->previousKeys === []) { + throw $e; + } + + foreach ($this->previousKeys as $previousKey) { + try { + $previousParams = is_array($params) + ? array_merge($params, ['key' => $previousKey]) + : $previousKey; + + return $this->innerHandler->decrypt($data, $previousParams); + } catch (EncryptionException) { + continue; + } + } + + throw $e; + } + } + + /** + * Delegate property access to the inner handler. + * + * @return array|bool|int|string|null + */ + public function __get(string $key) + { + if (method_exists($this->innerHandler, '__get')) { + return $this->innerHandler->__get($key); + } + + return null; + } + + /** + * Delegate property existence check to inner handler. + */ + public function __isset(string $key): bool + { + if (method_exists($this->innerHandler, '__isset')) { + return $this->innerHandler->__isset($key); + } + + return false; + } +} diff --git a/system/Entity.php b/system/Entity.php deleted file mode 100644 index 94aa7241ffaf..000000000000 --- a/system/Entity.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace CodeIgniter; - -use CodeIgniter\Entity\Entity as CoreEntity; - -/** - * Entity encapsulation, for use with CodeIgniter\Model - * - * @deprecated use CodeIgniter\Entity\Entity class instead - */ -class Entity extends CoreEntity -{ -} diff --git a/system/Entity/Cast/ArrayCast.php b/system/Entity/Cast/ArrayCast.php index 315f2e582fec..6860b41416b6 100644 --- a/system/Entity/Cast/ArrayCast.php +++ b/system/Entity/Cast/ArrayCast.php @@ -1,5 +1,7 @@ [Entity] --- (2) --> [Database] + * [App Code] <-- (4) --- [Entity] <-- (3) --- [Database] */ interface CastInterface { /** - * Get + * Takes a raw value from Entity, returns its value for PHP. * - * @param mixed $value Data - * @param array $params Additional param + * @param mixed $value Data + * @param array $params Additional param * * @return mixed */ public static function get($value, array $params = []); /** - * Set + * Takes a PHP value, returns its raw value for Entity. * - * @param mixed $value Data - * @param array $params Additional param + * @param mixed $value Data + * @param array $params Additional param * * @return mixed */ diff --git a/system/Entity/Cast/DatetimeCast.php b/system/Entity/Cast/DatetimeCast.php index 4655b4e374e3..e49df121987e 100644 --- a/system/Entity/Cast/DatetimeCast.php +++ b/system/Entity/Cast/DatetimeCast.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Entity\Cast; + +use BackedEnum; +use CodeIgniter\Entity\Exceptions\CastException; +use ReflectionEnum; +use UnitEnum; + +class EnumCast extends BaseCast +{ + public static function get($value, array $params = []): BackedEnum|UnitEnum + { + $enumClass = $params[0] ?? null; + + if ($enumClass === null) { + throw CastException::forMissingEnumClass(); + } + + if (! enum_exists($enumClass)) { + throw CastException::forNotEnum($enumClass); + } + + $reflection = new ReflectionEnum($enumClass); + + // Backed enum - validate and cast the value to proper type + if ($reflection->isBacked()) { + $backingType = $reflection->getBackingType(); + + // Cast to proper type (int or string) + if ($backingType->getName() === 'int') { + $value = (int) $value; + } elseif ($backingType->getName() === 'string') { + $value = (string) $value; + } + + $enum = $enumClass::tryFrom($value); + + if ($enum === null) { + throw CastException::forInvalidEnumValue($enumClass, $value); + } + + return $enum; + } + + // Unit enum - match by name + foreach ($enumClass::cases() as $case) { + if ($case->name === $value) { + return $case; + } + } + + throw CastException::forInvalidEnumCaseName($enumClass, $value); + } + + public static function set($value, array $params = []): int|string + { + // Get the expected enum class + $enumClass = $params[0] ?? null; + + if ($enumClass === null) { + throw CastException::forMissingEnumClass(); + } + + if (! enum_exists($enumClass)) { + throw CastException::forNotEnum($enumClass); + } + + // If it's already an enum object, validate and extract its value + if (is_object($value) && enum_exists($value::class)) { + // Validate that the enum is of the expected type + if (! $value instanceof $enumClass) { + throw CastException::forInvalidEnumType($enumClass, $value::class); + } + + $reflection = new ReflectionEnum($value::class); + + // Backed enum - return the properly typed backing value + if ($reflection->isBacked()) { + /** @var BackedEnum $value */ + return $value->value; + } + + // Unit enum - return the case name + /** @var UnitEnum $value */ + return $value->name; + } + + $reflection = new ReflectionEnum($enumClass); + + // Validate backed enum values + if ($reflection->isBacked()) { + $backingType = $reflection->getBackingType(); + + // Cast to proper type (int or string) + if ($backingType->getName() === 'int') { + $value = (int) $value; + } elseif ($backingType->getName() === 'string') { + $value = (string) $value; + } + + if ($enumClass::tryFrom($value) === null) { + throw CastException::forInvalidEnumValue($enumClass, $value); + } + + return $value; + } + + // Validate unit enum case names - must be a string + $value = (string) $value; + + foreach ($enumClass::cases() as $case) { + if ($case->name === $value) { + return $value; + } + } + + throw CastException::forInvalidEnumCaseName($enumClass, $value); + } +} diff --git a/system/Entity/Cast/FloatCast.php b/system/Entity/Cast/FloatCast.php index 78f87570f32c..1a767c0953f0 100644 --- a/system/Entity/Cast/FloatCast.php +++ b/system/Entity/Cast/FloatCast.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Entity\Cast; + +/** + * DB column: int (0/1) <--> Class property: bool + */ +final class IntBoolCast extends BaseCast +{ + /** + * @param int $value + */ + public static function get($value, array $params = []): bool + { + return (bool) $value; + } + + /** + * @param bool|int|string $value + */ + public static function set($value, array $params = []): int + { + return (int) $value; + } +} diff --git a/system/Entity/Cast/IntegerCast.php b/system/Entity/Cast/IntegerCast.php index b9357d7ed49b..84bf09211c0f 100644 --- a/system/Entity/Cast/IntegerCast.php +++ b/system/Entity/Cast/IntegerCast.php @@ -1,5 +1,7 @@ 'db_name' + * 'class_property_name' => 'db_column_name' * ]; + * + * @var array */ protected $datamap = []; + /** + * The date fields. + * + * @var list + */ protected $dates = [ 'created_at', 'updated_at', @@ -55,31 +71,35 @@ class Entity implements JsonSerializable /** * Array of field names and the type of value to cast them as when * they are accessed. + * + * @var array */ protected $casts = []; /** - * Custom convert handlers + * Custom convert handlers. * * @var array */ protected $castHandlers = []; /** - * Default convert handlers + * Default convert handlers. * * @var array */ - private $defaultCastHandlers = [ + private array $defaultCastHandlers = [ 'array' => ArrayCast::class, 'bool' => BooleanCast::class, 'boolean' => BooleanCast::class, 'csv' => CSVCast::class, 'datetime' => DatetimeCast::class, 'double' => FloatCast::class, + 'enum' => EnumCast::class, 'float' => FloatCast::class, 'int' => IntegerCast::class, 'integer' => IntegerCast::class, + 'int-bool' => IntBoolCast::class, 'json' => JsonCast::class, 'object' => ObjectCast::class, 'string' => StringCast::class, @@ -90,7 +110,7 @@ class Entity implements JsonSerializable /** * Holds the current values of all class vars. * - * @var array + * @var array */ protected $attributes = []; @@ -99,22 +119,34 @@ class Entity implements JsonSerializable * what's actually been changed and not accidentally write * nulls where we shouldn't. * - * @var array + * @var array */ protected $original = []; /** - * Holds info whenever properties have to be casted - * - * @var bool + * The data caster. + */ + protected ?DataCaster $dataCaster = null; + + /** + * Holds info whenever properties have to be casted. + */ + private bool $_cast = true; + + /** + * Indicates whether all attributes are scalars (for optimization). */ - private $_cast = true; + private bool $_onlyScalars = true; /** * Allows filling in Entity parameters during construction. + * + * @param array $data */ public function __construct(?array $data = null) { + $this->dataCaster = $this->dataCaster(); + $this->syncOriginal(); $this->fill($data); @@ -125,7 +157,7 @@ public function __construct(?array $data = null) * properties, using any `setCamelCasedProperty()` methods * that may or may not exist. * - * @param array $data + * @param array $data * * @return $this */ @@ -147,21 +179,22 @@ public function fill(?array $data = null) * of this entity as an array. All values are accessed through the * __get() magic method so will have any casts, etc applied to them. * - * @param bool $onlyChanged If true, only return values that have changed since object creation - * @param bool $cast If true, properties will be casted. - * @param bool $recursive If true, inner entities will be casted as array as well. + * @param bool $onlyChanged If true, only return values that have changed since object creation. + * @param bool $cast If true, properties will be cast. + * @param bool $recursive If true, inner entities will be cast as array as well. + * + * @return array */ public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recursive = false): array { - $this->_cast = $cast; + $originalCast = $this->_cast; + $this->_cast = $cast; - $keys = array_filter(array_keys($this->attributes), static function ($key) { - return strpos($key, '_') !== 0; - }); + $keys = array_filter(array_keys($this->attributes), static fn ($key): bool => ! str_starts_with($key, '_')); if (is_array($this->datamap)) { $keys = array_unique( - array_merge(array_diff($keys, $this->datamap), array_keys($this->datamap)) + [...array_diff($keys, $this->datamap), ...array_keys($this->datamap)], ); } @@ -184,7 +217,7 @@ public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recu } } - $this->_cast = true; + $this->_cast = $originalCast; return $return; } @@ -192,42 +225,116 @@ public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recu /** * Returns the raw values of the current attributes. * - * @param bool $onlyChanged If true, only return values that have changed since object creation - * @param bool $recursive If true, inner entities will be casted as array as well. + * @param bool $onlyChanged If true, only return values that have changed since object creation. + * @param bool $recursive If true, inner entities will be cast as array as well. + * + * @return array */ public function toRawArray(bool $onlyChanged = false, bool $recursive = false): array { - $return = []; + $convert = static function ($value) use (&$convert, $recursive) { + if (! $recursive) { + return $value; + } - if (! $onlyChanged) { - if ($recursive) { - return array_map(static function ($value) use ($onlyChanged, $recursive) { - if ($value instanceof self) { - $value = $value->toRawArray($onlyChanged, $recursive); - } elseif (is_callable([$value, 'toRawArray'])) { - $value = $value->toRawArray(); - } + if ($value instanceof self) { + // Always output full array for nested entities + return $value->toRawArray(false, true); + } + + if (is_array($value)) { + $result = []; - return $value; - }, $this->attributes); + foreach ($value as $k => $v) { + $result[$k] = $convert($v); + } + + return $result; } - return $this->attributes; + if (is_object($value) && is_callable([$value, 'toRawArray'])) { + return $value->toRawArray(); + } + + return $value; + }; + + // When returning everything + if (! $onlyChanged) { + return $recursive + ? array_map($convert, $this->attributes) + : $this->attributes; } + // When filtering by changed values only + $return = []; + foreach ($this->attributes as $key => $value) { + // Special handling for arrays of entities in recursive mode + // Skip hasChanged() and do per-entity comparison directly + if ($recursive && is_array($value) && $this->containsOnlyEntities($value)) { + $originalValue = $this->original[$key] ?? null; + + if (! is_string($originalValue)) { + // No original or invalid format, export all entities + $converted = []; + + foreach ($value as $idx => $item) { + $converted[$idx] = $item->toRawArray(false, true); + } + $return[$key] = $converted; + + continue; + } + + // Decode original array structure for per-entity comparison + $originalArray = json_decode($originalValue, true); + $converted = []; + + foreach ($value as $idx => $item) { + // Compare current entity against its original state + $currentNormalized = $this->normalizeValue($item); + $originalNormalized = $originalArray[$idx] ?? null; + + // Only include if changed, new, or can't determine + if ($originalNormalized === null || $currentNormalized !== $originalNormalized) { + $converted[$idx] = $item->toRawArray(false, true); + } + } + + // Only include this property if at least one entity changed + if ($converted !== []) { + $return[$key] = $converted; + } + + continue; + } + + // For all other cases, use hasChanged() if (! $this->hasChanged($key)) { continue; } if ($recursive) { - if ($value instanceof self) { - $value = $value->toRawArray($onlyChanged, $recursive); - } elseif (is_callable([$value, 'toRawArray'])) { - $value = $value->toRawArray(); + // Special handling for arrays (mixed or not all entities) + if (is_array($value)) { + $converted = []; + + foreach ($value as $idx => $item) { + $converted[$idx] = $item instanceof self ? $item->toRawArray(false, true) : $convert($item); + } + $return[$key] = $converted; + + continue; } + + // default recursive conversion + $return[$key] = $convert($value); + + continue; } + // non-recursive changed value $return[$key] = $value; } @@ -237,11 +344,24 @@ public function toRawArray(bool $onlyChanged = false, bool $recursive = false): /** * Ensures our "original" values match the current values. * + * Objects and arrays are normalized and JSON-encoded for reliable change detection, + * while scalars are stored as-is for performance. + * * @return $this */ public function syncOriginal() { - $this->original = $this->attributes; + $this->original = []; + $this->_onlyScalars = true; + + foreach ($this->attributes as $key => $value) { + if (is_object($value) || is_array($value)) { + $this->original[$key] = json_encode($this->normalizeValue($value), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $this->_onlyScalars = false; + } else { + $this->original[$key] = $value; + } + } return $this; } @@ -250,35 +370,141 @@ public function syncOriginal() * Checks a property to see if it has changed since the entity * was created. Or, without a parameter, checks if any * properties have changed. - * - * @param string $key */ public function hasChanged(?string $key = null): bool { // If no parameter was given then check all attributes if ($key === null) { - return $this->original !== $this->attributes; + if ($this->_onlyScalars) { + return $this->original !== $this->attributes; + } + + foreach (array_keys($this->attributes) as $attributeKey) { + if ($this->hasChanged($attributeKey)) { + return true; + } + } + + return false; } + $dbColumn = $this->mapProperty($key); + // Key doesn't exist in either - if (! array_key_exists($key, $this->original) && ! array_key_exists($key, $this->attributes)) { + if (! array_key_exists($dbColumn, $this->original) && ! array_key_exists($dbColumn, $this->attributes)) { return false; } // It's a new element - if (! array_key_exists($key, $this->original) && array_key_exists($key, $this->attributes)) { + if (! array_key_exists($dbColumn, $this->original) && array_key_exists($dbColumn, $this->attributes)) { return true; } - return $this->original[$key] !== $this->attributes[$key]; + // It was removed + if (array_key_exists($dbColumn, $this->original) && ! array_key_exists($dbColumn, $this->attributes)) { + return true; + } + + $originalValue = $this->original[$dbColumn]; + $currentValue = $this->attributes[$dbColumn]; + + // If original is a string, it was JSON-encoded (object or array) + if (is_string($originalValue) && (is_object($currentValue) || is_array($currentValue))) { + return $originalValue !== json_encode($this->normalizeValue($currentValue), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } + + // For scalars, use direct comparison + return $originalValue !== $currentValue; } /** - * Set raw data array without any mutations + * Checks if an array contains only Entity instances. + * This allows optimization for per-entity change tracking. + * + * @param array $data + */ + private function containsOnlyEntities(array $data): bool + { + if ($data === []) { + return false; + } + + foreach ($data as $item) { + if (! $item instanceof self) { + return false; + } + } + + return true; + } + + /** + * Recursively normalize a value for comparison. + * Converts objects and arrays to a JSON-encodable format. + */ + private function normalizeValue(mixed $data): mixed + { + if (is_array($data)) { + $normalized = []; + + foreach ($data as $key => $value) { + $normalized[$key] = $this->normalizeValue($value); + } + + return $normalized; + } + + if (is_object($data)) { + // Check for Entity instance (use raw values, recursive) + if ($data instanceof self) { + $objectData = $data->toRawArray(false, true); + } elseif ($data instanceof UnitEnum) { + return [ + '__class' => $data::class, + '__enum' => $data instanceof BackedEnum ? $data->value : $data->name, + ]; + } elseif ($data instanceof JsonSerializable) { + $objectData = $data->jsonSerialize(); + } elseif (method_exists($data, 'toArray')) { + $objectData = $data->toArray(); + } elseif ($data instanceof Traversable) { + $objectData = iterator_to_array($data); + } elseif ($data instanceof DateTimeInterface) { + return [ + '__class' => $data::class, + '__datetime' => $data->format(DATE_RFC3339_EXTENDED), + ]; + } else { + $objectData = get_object_vars($data); + + // Fallback for value objects with __toString() + // when properties are not accessible + if ($objectData === [] && method_exists($data, '__toString')) { + return [ + '__class' => $data::class, + '__string' => (string) $data, + ]; + } + } + + return [ + '__class' => $data::class, + '__data' => $this->normalizeValue($objectData), + ]; + } + + // Return scalars and null as-is + return $data; + } + + /** + * Set raw data array without any mutations. + * + * @param array $data * * @return $this */ - public function setAttributes(array $data) + public function injectRawData(array $data) { $this->attributes = $data; @@ -288,18 +514,18 @@ public function setAttributes(array $data) } /** - * Checks the datamap to see if this column name is being mapped, - * and returns the mapped name, if any, or the original name. + * Checks the datamap to see if this property name is being mapped, + * and returns the DB column name, if any, or the original property name. * - * @return mixed|string + * @return string Database column name. */ protected function mapProperty(string $key) { - if (empty($this->datamap)) { + if ($this->datamap === []) { return $key; } - if (! empty($this->datamap[$key])) { + if (array_key_exists($key, $this->datamap) && $this->datamap[$key] !== '') { return $this->datamap[$key]; } @@ -307,14 +533,14 @@ protected function mapProperty(string $key) } /** - * Converts the given string|timestamp|DateTime|Time instance + * Converts the given string|timestamp|DateTimeInterface instance * into the "CodeIgniter\I18n\Time" object. * - * @param mixed $value + * @param DateTimeInterface|float|int|string $value * - * @throws Exception + * @return Time * - * @return mixed|Time + * @throws Exception */ protected function mutateDate($value) { @@ -323,86 +549,65 @@ protected function mutateDate($value) /** * Provides the ability to cast an item as a specific data type. - * Add ? at the beginning of $type (i.e. ?string) to get NULL - * instead of casting $value if $value === null - * - * @param mixed $value Attribute value - * @param string $attribute Attribute name - * @param string $method Allowed to "get" and "set" + * Add ? at the beginning of the type (i.e. ?string) to get `null` + * instead of casting $value when $value is null. * - * @throws CastException + * @param bool|float|int|string|null $value Attribute value + * @param string $attribute Attribute name + * @param string $method Allowed to "get" and "set" * * @return mixed + * + * @throws CastException */ protected function castAs($value, string $attribute, string $method = 'get') { - if (empty($this->casts[$attribute])) { - return $value; - } - - $type = $this->casts[$attribute]; - - $isNullable = false; - - if (strpos($type, '?') === 0) { - $isNullable = true; - - if ($value === null) { - return null; - } - - $type = substr($type, 1); - } - - // In order not to create a separate handler for the - // json-array type, we transform the required one. - $type = $type === 'json-array' ? 'json[array]' : $type; - - if (! in_array($method, ['get', 'set'], true)) { - throw CastException::forInvalidMethod($method); - } - - $params = []; - - // Attempt to retrieve additional parameters if specified - // type[param, param2,param3] - if (preg_match('/^(.+)\[(.+)\]$/', $type, $matches)) { - $type = $matches[1]; - $params = array_map('trim', explode(',', $matches[2])); - } - - if ($isNullable) { - $params[] = 'nullable'; + if ($this->dataCaster() instanceof DataCaster) { + return $this->dataCaster + // @TODO if $casts is readonly, we don't need the setTypes() method. + ->setTypes($this->casts) + ->castAs($value, $attribute, $method); } - $type = trim($type, '[]'); + return $value; + } - $handlers = array_merge($this->defaultCastHandlers, $this->castHandlers); + /** + * Returns a DataCaster instance when casts are defined. + * If no casts are configured, no DataCaster is created and null is returned. + */ + protected function dataCaster(): ?DataCaster + { + if ($this->casts === []) { + $this->dataCaster = null; - if (empty($handlers[$type])) { - return $value; + return null; } - if (! is_subclass_of($handlers[$type], CastInterface::class)) { - throw CastException::forInvalidInterface($handlers[$type]); + if (! $this->dataCaster instanceof DataCaster) { + $this->dataCaster = new DataCaster( + array_merge($this->defaultCastHandlers, $this->castHandlers), + null, + null, + false, + ); } - return $handlers[$type]::$method($value, $params); + return $this->dataCaster; } /** - * Support for json_encode() + * Support for json_encode(). * - * @return array + * @return array */ - #[ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } /** - * Change the value of the private $_cast property + * Change the value of the private $_cast property. * * @return bool|Entity */ @@ -426,41 +631,47 @@ public function cast(?bool $cast = null) * $this->my_property = $p; * $this->setMyProperty() = $p; * - * @param mixed|null $value + * @param mixed $value * - * @throws Exception + * @return void * - * @return $this + * @throws Exception */ public function __set(string $key, $value = null) { - $key = $this->mapProperty($key); + $dbColumn = $this->mapProperty($key); // Check if the field should be mutated into a date - if (in_array($key, $this->dates, true)) { + if (in_array($dbColumn, $this->dates, true)) { $value = $this->mutateDate($value); } - $value = $this->castAs($value, $key, 'set'); + $value = $this->castAs($value, $dbColumn, 'set'); - // if a set* method exists for this key, use that method to + // if a setter method exists for this key, use that method to // insert this value. should be outside $isNullable check, // so maybe wants to do sth with null value automatically - $method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); + $method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $dbColumn))); + + // If a "`_set` + $key" method exists, it is a setter. + if (method_exists($this, '_' . $method)) { + $this->{'_' . $method}($value); + return; + } + + // If a "`set` + $key" method exists, it is also a setter. if (method_exists($this, $method)) { $this->{$method}($value); - return $this; + return; } // Otherwise, just the value. This allows for creation of new // class properties that are undefined, though they cannot be // saved. Useful for grabbing values through joins, assigning // relationships, etc. - $this->attributes[$key] = $value; - - return $this; + $this->attributes[$dbColumn] = $value; } /** @@ -471,38 +682,42 @@ public function __set(string $key, $value = null) * $p = $this->my_property * $p = $this->getMyProperty() * - * @throws Exception - * * @return mixed + * + * @throws Exception */ public function __get(string $key) { - $key = $this->mapProperty($key); + $dbColumn = $this->mapProperty($key); $result = null; // Convert to CamelCase for the method - $method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); + $method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $dbColumn))); - // if a set* method exists for this key, + // if a getter method exists for this key, // use that method to insert this value. - if (method_exists($this, $method)) { + if (method_exists($this, '_' . $method)) { + // If a "`_get` + $key" method exists, it is a getter. + $result = $this->{'_' . $method}(); + } elseif (method_exists($this, $method)) { + // If a "`get` + $key" method exists, it is also a getter. $result = $this->{$method}(); } // Otherwise return the protected property // if it exists. - elseif (array_key_exists($key, $this->attributes)) { - $result = $this->attributes[$key]; + elseif (array_key_exists($dbColumn, $this->attributes)) { + $result = $this->attributes[$dbColumn]; } // Do we need to mutate this into a date? - if (in_array($key, $this->dates, true)) { + if (in_array($dbColumn, $this->dates, true)) { $result = $this->mutateDate($result); } // Or cast it as something? elseif ($this->_cast) { - $result = $this->castAs($result, $key); + $result = $this->castAs($result, $dbColumn); } return $result; @@ -514,15 +729,19 @@ public function __get(string $key) */ public function __isset(string $key): bool { - $key = $this->mapProperty($key); + if ($this->isMappedDbColumn($key)) { + return false; + } + + $dbColumn = $this->mapProperty($key); - $method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); + $method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $dbColumn))); if (method_exists($this, $method)) { return true; } - return isset($this->attributes[$key]); + return isset($this->attributes[$dbColumn]); } /** @@ -530,6 +749,37 @@ public function __isset(string $key): bool */ public function __unset(string $key): void { - unset($this->attributes[$key]); + if ($this->isMappedDbColumn($key)) { + return; + } + + $dbColumn = $this->mapProperty($key); + + unset($this->attributes[$dbColumn]); + } + + /** + * Whether this key is mapped db column name? + */ + protected function isMappedDbColumn(string $key): bool + { + $dbColumn = $this->mapProperty($key); + + // The $key is a property name which has mapped db column name + if ($key !== $dbColumn) { + return false; + } + + return $this->hasMappedProperty($key); + } + + /** + * Whether this key has mapped property? + */ + protected function hasMappedProperty(string $key): bool + { + $property = array_search($key, $this->datamap, true); + + return $property !== false; } } diff --git a/system/Entity/Exceptions/CastException.php b/system/Entity/Exceptions/CastException.php index 2cda4d72238d..033f1ced478e 100644 --- a/system/Entity/Exceptions/CastException.php +++ b/system/Entity/Exceptions/CastException.php @@ -1,5 +1,7 @@ new static(lang('Cast.jsonErrorDepth')), + JSON_ERROR_STATE_MISMATCH => new static(lang('Cast.jsonErrorStateMismatch')), + JSON_ERROR_CTRL_CHAR => new static(lang('Cast.jsonErrorCtrlChar')), + JSON_ERROR_SYNTAX => new static(lang('Cast.jsonErrorSyntax')), + JSON_ERROR_UTF8 => new static(lang('Cast.jsonErrorUtf8')), + default => new static(lang('Cast.jsonErrorUnknown')), + }; } /** @@ -75,4 +72,54 @@ public static function forInvalidTimestamp() { return new static(lang('Cast.invalidTimestamp')); } + + /** + * Thrown when the enum class is not specified in cast parameters. + * + * @return static + */ + public static function forMissingEnumClass() + { + return new static(lang('Cast.enumMissingClass')); + } + + /** + * Thrown when the specified class is not an enum. + * + * @return static + */ + public static function forNotEnum(string $class) + { + return new static(lang('Cast.enumNotEnum', [$class])); + } + + /** + * Thrown when an invalid value is provided for an enum. + * + * @return static + */ + public static function forInvalidEnumValue(string $enumClass, mixed $value) + { + return new static(lang('Cast.enumInvalidValue', [$enumClass, $value])); + } + + /** + * Thrown when an invalid case name is provided for a unit enum. + * + * @return static + */ + public static function forInvalidEnumCaseName(string $enumClass, string $caseName) + { + return new static(lang('Cast.enumInvalidCaseName', [$caseName, $enumClass])); + } + + /** + * Thrown when an enum instance of wrong type is provided. + * + * @return static + */ + public static function forInvalidEnumType(string $expectedClass, string $actualClass) + { + return new static(lang('Cast.enumInvalidType', [$actualClass, $expectedClass])); + } } diff --git a/system/Events/Events.php b/system/Events/Events.php index ac08107459ca..3cad30995f94 100644 --- a/system/Events/Events.php +++ b/system/Events/Events.php @@ -1,5 +1,7 @@ , 2: list}> */ protected static $listeners = []; @@ -50,19 +53,21 @@ class Events * Stores information about the events * for display in the debug toolbar. * - * @var array> + * @var list */ protected static $performanceLog = []; /** * A list of found files. * - * @var string[] + * @var list */ protected static $files = []; /** * Ensures that we have a events file ready. + * + * @return void */ public static function initialize() { @@ -71,24 +76,20 @@ public static function initialize() return; } - /** @var Modules $config */ - $config = config('Modules'); + $config = new Modules(); $events = APPPATH . 'Config' . DIRECTORY_SEPARATOR . 'Events.php'; $files = []; if ($config->shouldDiscover('events')) { - $files = Services::locator()->search('Config/Events.php'); + $files = service('locator')->search('Config/Events.php'); } - $files = array_filter(array_map(static function (string $file) { - if (is_file($file)) { - return realpath($file) ?: $file; - } - - return false; // @codeCoverageIgnore - }, $files)); + $files = array_filter(array_map( + realpath(...), + $files, + )); - static::$files = array_unique(array_merge($files, [$events])); + static::$files = array_values(array_unique(array_merge($files, [$events]))); foreach (static::$files as $file) { include $file; @@ -106,11 +107,13 @@ public static function initialize() * Events::on('create', [$myInstance, 'myMethod']); // Method on an existing instance * Events::on('create', function() {}); // Closure * - * @param string $eventName - * @param callable $callback - * @param int $priority + * @param string $eventName + * @param callable(mixed): mixed $callback + * @param int $priority + * + * @return void */ - public static function on($eventName, $callback, $priority = EVENT_PRIORITY_NORMAL) + public static function on($eventName, $callback, $priority = self::PRIORITY_NORMAL) { if (! isset(static::$listeners[$eventName])) { static::$listeners[$eventName] = [ @@ -132,7 +135,7 @@ public static function on($eventName, $callback, $priority = EVENT_PRIORITY_NORM * b) a method returns false, at which point execution of subscribers stops. * * @param string $eventName - * @param mixed $arguments + * @param mixed ...$arguments */ public static function trigger($eventName, ...$arguments): bool { @@ -152,7 +155,7 @@ public static function trigger($eventName, ...$arguments): bool static::$performanceLog[] = [ 'start' => $start, 'end' => microtime(true), - 'event' => strtolower($eventName), + 'event' => $eventName, ]; } @@ -169,6 +172,8 @@ public static function trigger($eventName, ...$arguments): bool * sorted by priority. * * @param string $eventName + * + * @return list */ public static function listeners($eventName): array { @@ -194,7 +199,8 @@ public static function listeners($eventName): array * If the listener couldn't be found, returns FALSE, else TRUE if * it was removed. * - * @param string $eventName + * @param string $eventName + * @param callable(mixed): mixed $listener */ public static function removeListener($eventName, callable $listener): bool { @@ -206,7 +212,7 @@ public static function removeListener($eventName, callable $listener): bool if ($check === $listener) { unset( static::$listeners[$eventName][1][$index], - static::$listeners[$eventName][2][$index] + static::$listeners[$eventName][2][$index], ); return true; @@ -223,6 +229,8 @@ public static function removeListener($eventName, callable $listener): bool * removed, otherwise all listeners for all events are removed. * * @param string|null $eventName + * + * @return void */ public static function removeAllListeners($eventName = null) { @@ -235,6 +243,10 @@ public static function removeAllListeners($eventName = null) /** * Sets the path to the file that routes are read from. + * + * @param list $files + * + * @return void */ public static function setFiles(array $files) { @@ -244,9 +256,9 @@ public static function setFiles(array $files) /** * Returns the files that were found/loaded during this request. * - * @return string[] + * @return list */ - public function getFiles() + public static function getFiles() { return static::$files; } @@ -255,6 +267,8 @@ public function getFiles() * Turns simulation on or off. When on, events will not be triggered, * simply logged. Useful during testing when you don't actually want * the tests to run. + * + * @return void */ public static function simulate(bool $choice = true) { @@ -264,10 +278,29 @@ public static function simulate(bool $choice = true) /** * Getter for the performance log records. * - * @return array> + * @return list */ public static function getPerformanceLogs() { return static::$performanceLog; } + + /** + * Cleanup performance log and request-specific listeners for worker mode. + * + * Called at the END of each request to clean up state. + * + * @param list $resetEventListeners Additional event names to reset. + */ + public static function cleanupForWorkerMode(array $resetEventListeners = []): void + { + if (CI_DEBUG) { + static::$performanceLog = []; + static::removeAllListeners('DBQuery'); + } + + foreach ($resetEventListeners as $event) { + static::removeAllListeners($event); + } + } } diff --git a/system/Exceptions/AlertError.php b/system/Exceptions/AlertError.php deleted file mode 100644 index 274bb73c562f..000000000000 --- a/system/Exceptions/AlertError.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace CodeIgniter\Exceptions; - -use Error; - -/** - * Error: Action must be taken immediately (system/db down, etc) - */ -class AlertError extends Error -{ -} diff --git a/system/Exceptions/BadFunctionCallException.php b/system/Exceptions/BadFunctionCallException.php new file mode 100644 index 000000000000..84d9798dc0c4 --- /dev/null +++ b/system/Exceptions/BadFunctionCallException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Exceptions; + +/** + * Exception thrown if a function is called in the wrong way, or the function + * does not exist. + */ +class BadFunctionCallException extends \BadFunctionCallException implements ExceptionInterface +{ +} diff --git a/system/Exceptions/BadMethodCallException.php b/system/Exceptions/BadMethodCallException.php new file mode 100644 index 000000000000..977e8ae721e2 --- /dev/null +++ b/system/Exceptions/BadMethodCallException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Exceptions; + +/** + * Exception thrown if a method is called in the wrong way, or the method + * does not exist. + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/system/Exceptions/CastException.php b/system/Exceptions/CastException.php deleted file mode 100644 index 5e41631536c7..000000000000 --- a/system/Exceptions/CastException.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace CodeIgniter\Exceptions; - -/** - * Cast Exceptions. - * - * @deprecated use CodeIgniter\Entity\Exceptions\CastException instead. - * - * @codeCoverageIgnore - */ -class CastException extends CriticalError -{ - use DebugTraceableTrait; - - /** - * Error code - * - * @var int - */ - protected $code = 3; - - public static function forInvalidJsonFormatException(int $error) - { - switch ($error) { - case JSON_ERROR_DEPTH: - return new static(lang('Cast.jsonErrorDepth')); - - case JSON_ERROR_STATE_MISMATCH: - return new static(lang('Cast.jsonErrorStateMismatch')); - - case JSON_ERROR_CTRL_CHAR: - return new static(lang('Cast.jsonErrorCtrlChar')); - - case JSON_ERROR_SYNTAX: - return new static(lang('Cast.jsonErrorSyntax')); - - case JSON_ERROR_UTF8: - return new static(lang('Cast.jsonErrorUtf8')); - - default: - return new static(lang('Cast.jsonErrorUnknown')); - } - } -} diff --git a/system/Exceptions/ConfigException.php b/system/Exceptions/ConfigException.php index 41ae4959e594..b30818124901 100644 --- a/system/Exceptions/ConfigException.php +++ b/system/Exceptions/ConfigException.php @@ -1,5 +1,7 @@ - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace CodeIgniter\Exceptions; - -use Error; - -/** - * Error: system is unusable - */ -class EmergencyError extends Error -{ -} diff --git a/system/Exceptions/ExceptionInterface.php b/system/Exceptions/ExceptionInterface.php index be8ac692663a..27b9c2faf51b 100644 --- a/system/Exceptions/ExceptionInterface.php +++ b/system/Exceptions/ExceptionInterface.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Exceptions; + +/** + * Interface for Exceptions that has exception code as HTTP status code. + */ +interface HTTPExceptionInterface extends ExceptionInterface +{ +} diff --git a/system/Exceptions/HasExitCodeInterface.php b/system/Exceptions/HasExitCodeInterface.php new file mode 100644 index 000000000000..48d32655714c --- /dev/null +++ b/system/Exceptions/HasExitCodeInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Exceptions; + +/** + * Interface for Exceptions that has exception code as exit code. + */ +interface HasExitCodeInterface extends ExceptionInterface +{ + /** + * Returns exit status code. + */ + public function getExitCode(): int; +} diff --git a/system/Exceptions/InvalidArgumentException.php b/system/Exceptions/InvalidArgumentException.php new file mode 100644 index 000000000000..4790249e3f78 --- /dev/null +++ b/system/Exceptions/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Exceptions; + +/** + * Exception thrown if an argument is not of the expected type. + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/system/Exceptions/LogicException.php b/system/Exceptions/LogicException.php new file mode 100644 index 000000000000..2fe46792efcc --- /dev/null +++ b/system/Exceptions/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Exceptions; + +/** + * Exception that represents error in the program logic. + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/system/Exceptions/ModelException.php b/system/Exceptions/ModelException.php index 7ef6f7a808b9..a92e388b4d9a 100644 --- a/system/Exceptions/ModelException.php +++ b/system/Exceptions/ModelException.php @@ -1,5 +1,7 @@ request->setLocale()` in the controller afterwards, + * the Language locale will not be changed. + */ + private static function lang(string $line, array $args = []): string + { + $lang = service('language', null, false); + + return $lang->getLine($line, $args); } } diff --git a/system/Exceptions/RuntimeException.php b/system/Exceptions/RuntimeException.php new file mode 100644 index 000000000000..c35d55ba9e45 --- /dev/null +++ b/system/Exceptions/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Exceptions; + +/** + * Exception thrown if an error which can only be found on runtime occurs. + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/system/Exceptions/TestException.php b/system/Exceptions/TestException.php index a1c4c51cf814..4487ae1e1815 100644 --- a/system/Exceptions/TestException.php +++ b/system/Exceptions/TestException.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Files\Exceptions; + +/** + * Provides a domain-level interface for broad capture + * of all Files-related exceptions. + * + * catch (\CodeIgniter\Files\Exceptions\ExceptionInterface) { ... } + */ +interface ExceptionInterface extends \CodeIgniter\Exceptions\ExceptionInterface +{ +} diff --git a/system/Files/Exceptions/FileException.php b/system/Files/Exceptions/FileException.php index 03af1bc57b08..3202978c3af6 100644 --- a/system/Files/Exceptions/FileException.php +++ b/system/Files/Exceptions/FileException.php @@ -1,5 +1,7 @@ size ?? ($this->size = parent::getSize()); } + /** + * Retrieve the file size by unit, calculated in IEC standards with 1024 as base value. + * + * @param positive-int $precision + */ + public function getSizeByBinaryUnit(FileSizeUnit $unit = FileSizeUnit::B, int $precision = 3): int|string + { + return $this->getSizeByUnitInternal(1024, $unit, $precision); + } + + /** + * Retrieve the file size by unit, calculated in metric standards with 1000 as base value. + * + * @param positive-int $precision + */ + public function getSizeByMetricUnit(FileSizeUnit $unit = FileSizeUnit::B, int $precision = 3): int|string + { + return $this->getSizeByUnitInternal(1000, $unit, $precision); + } + /** * Retrieve the file size by unit. * + * @deprecated 4.6.0 Use getSizeByBinaryUnit() or getSizeByMetricUnit() instead + * * @return false|int|string */ public function getSizeByUnit(string $unit = 'b') { - switch (strtolower($unit)) { - case 'kb': - return number_format($this->getSize() / 1024, 3); - - case 'mb': - return number_format(($this->getSize() / 1024) / 1024, 3); - - default: - return $this->getSize(); - } + return match (strtolower($unit)) { + 'kb' => $this->getSizeByBinaryUnit(FileSizeUnit::KB), + 'mb' => $this->getSizeByBinaryUnit(FileSizeUnit::MB), + default => $this->getSize(), + }; } /** @@ -89,7 +110,12 @@ public function getSizeByUnit(string $unit = 'b') */ public function guessExtension(): ?string { - return Mimes::guessExtensionFromType($this->getMimeType()); + // naively get the path extension using pathinfo + $pathinfo = pathinfo($this->getRealPath() ?: $this->__toString()) + ['extension' => '']; + + $proposedExtension = $pathinfo['extension']; + + return Mimes::guessExtensionFromType($this->getMimeType(), $proposedExtension); } /** @@ -105,11 +131,9 @@ public function getMimeType(): string return $this->originalMimeType ?? 'application/octet-stream'; // @codeCoverageIgnore } - $finfo = finfo_open(FILEINFO_MIME_TYPE); - $mimeType = finfo_file($finfo, $this->getRealPath() ?: $this->__toString()); - finfo_close($finfo); + $finfo = finfo_open(FILEINFO_MIME_TYPE); - return $mimeType; + return finfo_file($finfo, $this->getRealPath() ?: $this->__toString()); } /** @@ -121,7 +145,7 @@ public function getRandomName(): string $extension = $this->getExtension(); $extension = empty($extension) ? '' : '.' . $extension; - return time() . '_' . bin2hex(random_bytes(10)) . $extension; + return Time::now()->getTimestamp() . '_' . bin2hex(random_bytes(10)) . $extension; } /** @@ -131,8 +155,8 @@ public function getRandomName(): string */ public function move(string $targetPath, ?string $name = null, bool $overwrite = false) { - $targetPath = rtrim($targetPath, '/') . '/'; - $name = $name ?? $this->getBaseName(); + $targetPath = rtrim($targetPath, '/') . '/'; + $name ??= $this->getBasename(); $destination = $overwrite ? $targetPath . $name : $this->getDestination($targetPath . $name); $oldName = $this->getRealPath() ?: $this->__toString(); @@ -165,7 +189,7 @@ public function getDestination(string $destination, string $delimiter = '_', int $info = pathinfo($destination); $extension = isset($info['extension']) ? '.' . $info['extension'] : ''; - if (strpos($info['filename'], $delimiter) !== false) { + if (str_contains($info['filename'], $delimiter)) { $parts = explode($delimiter, $info['filename']); if (is_numeric(end($parts))) { @@ -183,4 +207,17 @@ public function getDestination(string $destination, string $delimiter = '_', int return $destination; } + + private function getSizeByUnitInternal(int $fileSizeBase, FileSizeUnit $unit, int $precision): int|string + { + $exponent = $unit->value; + $divider = $fileSizeBase ** $exponent; + $size = $this->getSize() / $divider; + + if ($unit !== FileSizeUnit::B) { + $size = number_format($size, $precision); + } + + return $size; + } } diff --git a/system/Files/FileCollection.php b/system/Files/FileCollection.php index 0b1714425ba9..b293b4d3c08d 100644 --- a/system/Files/FileCollection.php +++ b/system/Files/FileCollection.php @@ -1,5 +1,7 @@ + * @see \CodeIgniter\Files\FileCollectionTest */ class FileCollection implements Countable, IteratorAggregate { /** * The current list of file paths. * - * @var string[] + * @var list */ protected $files = []; - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- // Support Methods - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Resolves a full path and verifies it is an actual directory. @@ -72,26 +77,24 @@ final protected static function resolveFile(string $file): string /** * Removes files that are not part of the given directory (recursive). * - * @param string[] $files + * @param list $files * - * @return string[] + * @return list */ final protected static function filterFiles(array $files, string $directory): array { $directory = self::resolveDirectory($directory); - return array_filter($files, static function (string $value) use ($directory): bool { - return strpos($value, $directory) === 0; - }); + return array_filter($files, static fn (string $value): bool => str_starts_with($value, $directory)); } /** * Returns any files whose `basename` matches the given pattern. * - * @param string[] $files - * @param string $pattern Regex or pseudo-regex string + * @param list $files + * @param string $pattern Regex or pseudo-regex string * - * @return string[] + * @return list */ final protected static function matchFiles(array $files, string $pattern): array { @@ -100,24 +103,22 @@ final protected static function matchFiles(array $files, string $pattern): array $pattern = str_replace( ['#', '.', '*', '?'], ['\#', '\.', '.*', '.'], - $pattern + $pattern, ); - $pattern = "#{$pattern}#"; + $pattern = "#\\A{$pattern}\\z#"; } - return array_filter($files, static function ($value) use ($pattern) { - return (bool) preg_match($pattern, basename($value)); - }); + return array_filter($files, static fn ($value): bool => (bool) preg_match($pattern, basename($value))); } - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- // Class Core - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Loads the Filesystem helper and adds any initial files. * - * @param string[] $files + * @param list $files */ public function __construct(array $files = []) { @@ -137,7 +138,7 @@ protected function define(): void /** * Optimizes and returns the current file list. * - * @return string[] + * @return list */ public function get(): array { @@ -151,7 +152,7 @@ public function get(): array * Sets the file list directly, files are still subject to verification. * This works as a "reset" method with []. * - * @param string[] $files The new file list to use + * @param list $files The new file list to use * * @return $this */ @@ -165,7 +166,7 @@ public function set(array $files) /** * Adds an array/single file or directory to the list. * - * @param string|string[] $paths + * @param list|string $paths * * @return $this */ @@ -181,8 +182,10 @@ public function add($paths, bool $recursive = true) try { // Test for a directory self::resolveDirectory($path); - } catch (FileException $e) { - return $this->addFile($path); + } catch (FileException) { + $this->addFile($path); + + continue; } $this->addDirectory($path, $recursive); @@ -191,14 +194,14 @@ public function add($paths, bool $recursive = true) return $this; } - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- // File Handling - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Verifies and adds files to the list. * - * @param string[] $files + * @param list $files * * @return $this */ @@ -226,7 +229,7 @@ public function addFile(string $file) /** * Removes files from the list. * - * @param string[] $files + * @param list $files * * @return $this */ @@ -247,15 +250,15 @@ public function removeFile(string $file) return $this->removeFiles([$file]); } - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- // Directory Handling - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Verifies and adds files from each * directory to the list. * - * @param string[] $directories + * @param list $directories * * @return $this */ @@ -289,9 +292,9 @@ public function addDirectory(string $directory, bool $recursive = false) return $this; } - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- // Filtering - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Removes any files from the list that match the supplied pattern @@ -337,9 +340,47 @@ public function retainPattern(string $pattern, ?string $scope = null) return $this->removeFiles(array_diff($files, self::matchFiles($files, $pattern))); } - //-------------------------------------------------------------------- + /** + * Keeps only the files from the list that match multiple patterns + * (within the optional scope). + * + * @param list $patterns Array of regex or pseudo-regex strings + * @param string|null $scope A directory to limit the scope + * + * @return $this + */ + public function retainMultiplePatterns(array $patterns, ?string $scope = null) + { + if ($patterns === []) { + return $this; + } + + if (count($patterns) === 1 && $patterns[0] === '') { + return $this; + } + + // Start with all files or those in scope + $files = $scope === null ? $this->files : self::filterFiles($this->files, $scope); + + // Add files to retain to array + $filesToRetain = []; + + foreach ($patterns as $pattern) { + if ($pattern === '') { + continue; + } + + // Matches the pattern within the scoped files + $filesToRetain = array_merge($filesToRetain, self::matchFiles($files, $pattern)); + } + + // Remove the inverse of files to retain + return $this->removeFiles(array_diff($files, $filesToRetain)); + } + + // -------------------------------------------------------------------- // Interface Methods - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Returns the current number of files in the collection. @@ -354,9 +395,9 @@ public function count(): int * Yields as an Iterator for the current files. * Fulfills IteratorAggregate. * - * @throws FileNotFoundException - * * @return Generator + * + * @throws FileNotFoundException */ public function getIterator(): Generator { diff --git a/system/Files/FileSizeUnit.php b/system/Files/FileSizeUnit.php new file mode 100644 index 000000000000..84c2ba44ca13 --- /dev/null +++ b/system/Files/FileSizeUnit.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Files; + +use CodeIgniter\Exceptions\InvalidArgumentException; + +enum FileSizeUnit: int +{ + case B = 0; + case KB = 1; + case MB = 2; + case GB = 3; + case TB = 4; + + /** + * Allows the creation of a FileSizeUnit from Strings like "kb" or "mb" + * + * @throws InvalidArgumentException + */ + public static function fromString(string $unit): self + { + return match (strtolower($unit)) { + 'b' => self::B, + 'kb' => self::KB, + 'mb' => self::MB, + 'gb' => self::GB, + 'tb' => self::TB, + default => throw new InvalidArgumentException("Invalid unit: {$unit}"), + }; + } +} diff --git a/system/Filters/CSRF.php b/system/Filters/CSRF.php index 9d17b5e6e241..ea9a9939f2de 100644 --- a/system/Filters/CSRF.php +++ b/system/Filters/CSRF.php @@ -1,5 +1,7 @@ |null $arguments * - * @throws SecurityException + * @return RedirectResponse|null * - * @return mixed + * @throws SecurityException */ public function before(RequestInterface $request, $arguments = null) { - if ($request->isCLI()) { - return; + if (! $request instanceof IncomingRequest) { + return null; } - $security = Services::security(); + /** @var Security $security */ + $security = service('security'); try { $security->verify($request); @@ -58,16 +57,17 @@ public function before(RequestInterface $request, $arguments = null) throw $e; } + + return null; } /** * We don't have anything to do here. * - * @param array|null $arguments - * - * @return mixed + * @param list|null $arguments */ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { + return null; } } diff --git a/system/Filters/Cors.php b/system/Filters/Cors.php new file mode 100644 index 000000000000..c070d72925e5 --- /dev/null +++ b/system/Filters/Cors.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Filters; + +use CodeIgniter\HTTP\Cors as CorsService; +use CodeIgniter\HTTP\IncomingRequest; +use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\ResponseInterface; + +/** + * @see \CodeIgniter\Filters\CorsTest + */ +class Cors implements FilterInterface +{ + private ?CorsService $cors = null; + + /** + * @testTag $config is used for testing purposes only. + * + * @param array{ + * allowedOrigins?: list, + * allowedOriginsPatterns?: list, + * supportsCredentials?: bool, + * allowedHeaders?: list, + * exposedHeaders?: list, + * allowedMethods?: list, + * maxAge?: int, + * } $config + */ + public function __construct(array $config = []) + { + if ($config !== []) { + $this->cors = new CorsService($config); + } + } + + /** + * @param list|null $arguments + * + * @return ResponseInterface|null + */ + public function before(RequestInterface $request, $arguments = null) + { + if (! $request instanceof IncomingRequest) { + return null; + } + + $this->createCorsService($arguments); + + /** @var ResponseInterface $response */ + $response = service('response'); + + if ($this->cors->isPreflightRequest($request)) { + $response = $this->cors->handlePreflightRequest($request, $response); + + // Always adds `Vary: Access-Control-Request-Method` header for cacheability. + // If there is an intermediate cache server such as a CDN, if a plain + // OPTIONS request is sent, it may be cached. But valid preflight requests + // have this header, so it will be cached separately. + $response->appendHeader('Vary', 'Access-Control-Request-Method'); + + return $response; + } + + if ($request->is('OPTIONS')) { + // Always adds `Vary: Access-Control-Request-Method` header for cacheability. + // If there is an intermediate cache server such as a CDN, if a plain + // OPTIONS request is sent, it may be cached. But valid preflight requests + // have this header, so it will be cached separately. + $response->appendHeader('Vary', 'Access-Control-Request-Method'); + } + + $this->cors->addResponseHeaders($request, $response); + + return null; + } + + /** + * @param list|null $arguments + */ + private function createCorsService(?array $arguments): void + { + $this->cors ??= ($arguments === null) ? CorsService::factory() + : CorsService::factory($arguments[0]); + } + + /** + * @param list|null $arguments + */ + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + if (! $request instanceof IncomingRequest) { + return null; + } + + $this->createCorsService($arguments); + + if ($this->cors->hasResponseHeaders($request, $response)) { + return null; + } + + // Always adds `Vary: Access-Control-Request-Method` header for cacheability. + // If there is an intermediate cache server such as a CDN, if a plain + // OPTIONS request is sent, it may be cached. But valid preflight requests + // have this header, so it will be cached separately. + if ($request->is('OPTIONS')) { + $response->appendHeader('Vary', 'Access-Control-Request-Method'); + } + + return $this->cors->addResponseHeaders($request, $response); + } +} diff --git a/system/Filters/DebugToolbar.php b/system/Filters/DebugToolbar.php index ae4c09c8bf9c..7199b85f9d62 100644 --- a/system/Filters/DebugToolbar.php +++ b/system/Filters/DebugToolbar.php @@ -1,5 +1,7 @@ |null $arguments */ public function before(RequestInterface $request, $arguments = null) { + return null; } /** * If the debug flag is set (CI_DEBUG) then collect performance * and debug information and display it in a toolbar. * - * @param array|null $arguments + * @param list|null $arguments */ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { - Services::toolbar()->prepare($request, $response); + service('toolbar')->prepare($request, $response); + + return null; } } diff --git a/system/Filters/Exceptions/FilterException.php b/system/Filters/Exceptions/FilterException.php index 04bb36bdc2c5..3a4d914e3cc3 100644 --- a/system/Filters/Exceptions/FilterException.php +++ b/system/Filters/Exceptions/FilterException.php @@ -1,5 +1,7 @@ |null $arguments * - * @return mixed + * @return RequestInterface|ResponseInterface|string|null */ public function before(RequestInterface $request, $arguments = null); @@ -41,9 +43,9 @@ public function before(RequestInterface $request, $arguments = null); * to stop execution of other after filters, short of * throwing an Exception or Error. * - * @param null $arguments + * @param list|null $arguments * - * @return mixed + * @return ResponseInterface|null */ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null); } diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index c81dadf3330f..bf5419eab560 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -1,5 +1,7 @@ [ + * 'alias', + * 'alias:arg1', + * 'alias:arg1,arg2', + * ], + * 'after' => [ + * 'alias', + * 'alias:arg1', + * 'alias:arg1,arg2', + * ], + * ] * - * @var array + * @var array{ + * before: list, + * after: list + * } */ protected $filters = [ 'before' => [], @@ -71,27 +93,52 @@ class Filters ]; /** - * The collection of filters' class names that will - * be used to execute in each position. + * The collection of filter classnames and their arguments to execute for + * the current request (URI path). * - * @var array + * This does not include "Required Filters". + * + * [ + * 'before' => [ + * [classname, arguments], + * ], + * 'after' => [ + * [classname, arguments], + * ], + * ] + * + * @var array{ + * before: list}>, + * after: list}> + * } */ protected $filtersClass = [ 'before' => [], 'after' => [], ]; + /** + * List of filter class instances. + * + * @var array [classname => instance] + */ + protected array $filterClassInstances = []; + /** * Any arguments to be passed to filters. * - * @var array + * @var array|null> [name => params] + * + * @deprecated 4.6.0 No longer used. */ protected $arguments = []; /** * Any arguments to be passed to filtersClass. * - * @var array + * @var array|null> [classname => arguments] + * + * @deprecated 4.6.0 No longer used. */ protected $argumentsClass = []; @@ -106,20 +153,26 @@ public function __construct($config, RequestInterface $request, ResponseInterfac $this->request = &$request; $this->setResponse($response); - $this->modules = $modules ?? config('Modules'); + $this->modules = $modules instanceof Modules ? $modules : new Modules(); + + if ($this->modules->shouldDiscover('filters')) { + $this->discoverFilters(); + } } /** * If discoverFilters is enabled in Config then system will try to - * auto-discover custom filters files in Namespaces and allow access to - * the config object via the variable $customfilters as with the routes file + * auto-discover custom filters files in namespaces and allow access to + * the config object via the variable $filters as with the routes file. * - * Sample : + * Sample: * $filters->aliases['custom-auth'] = \Acme\Blob\Filters\BlobAuth::class; + * + * @deprecated 4.4.2 Use Registrar instead. */ - private function discoverFilters() + private function discoverFilters(): void { - $locator = Services::locator(); + $locator = service('locator'); // for access by custom filters $filters = $this->config; @@ -127,10 +180,11 @@ private function discoverFilters() $files = $locator->search('Config/Filters.php'); foreach ($files as $file) { + // The $file may not be a class file. $className = $locator->getClassname($file); // Don't include our main Filter config again... - if ($className === 'Config\\Filters') { + if ($className === FiltersConfig::class || $className === BaseFiltersConfig::class) { continue; } @@ -139,67 +193,245 @@ private function discoverFilters() } /** - * Set the response explicity. + * Set the response explicitly. + * + * @return void */ public function setResponse(ResponseInterface $response) { - $this->response = &$response; + $this->response = $response; } /** - * Runs through all of the filters for the specified - * uri and position. + * Runs through all the filters (except "Required Filters") for the specified + * URI and position. * - * @throws FilterException + * @param string $uri URI path relative to baseURL + * @param 'after'|'before' $position + * + * @return RequestInterface|ResponseInterface|string|null * - * @return mixed|RequestInterface|ResponseInterface + * @throws FilterException */ public function run(string $uri, string $position = 'before') { $this->initialize(strtolower($uri)); - foreach ($this->filtersClass[$position] as $className) { - $class = new $className(); + if ($position === 'before') { + return $this->runBefore($this->filtersClass[$position]); + } - if (! $class instanceof FilterInterface) { - throw FilterException::forIncorrectInterface(get_class($class)); - } + // After + return $this->runAfter($this->filtersClass[$position]); + } - if ($position === 'before') { - $result = $class->before($this->request, $this->argumentsClass[$className] ?? null); + /** + * @param list}> $filterClassList [[classname, arguments], ...] + * + * @return RequestInterface|ResponseInterface|string + */ + private function runBefore(array $filterClassList) + { + foreach ($filterClassList as $filterClassInfo) { + $className = $filterClassInfo[0]; + $arguments = ($filterClassInfo[1] === []) ? null : $filterClassInfo[1]; - if ($result instanceof RequestInterface) { - $this->request = $result; + $instance = $this->createFilter($className); - continue; - } + $result = $instance->before($this->request, $arguments); - // If the response object was sent back, - // then send it and quit. - if ($result instanceof ResponseInterface) { - // short circuit - bypass any other filters - return $result; - } - // Ignore an empty result - if (empty($result)) { - continue; - } + if ($result instanceof RequestInterface) { + $this->request = $result; + + continue; + } + // If the response object was sent back, + // then send it and quit. + if ($result instanceof ResponseInterface) { + // short circuit - bypass any other filters return $result; } - if ($position === 'after') { - $result = $class->after($this->request, $this->response, $this->argumentsClass[$className] ?? null); + // Ignore an empty result + if (empty($result)) { + continue; + } + + return $result; + } + + return $this->request; + } + + /** + * @param list}> $filterClassList [[classname, arguments], ...] + */ + private function runAfter(array $filterClassList): ResponseInterface + { + foreach ($filterClassList as $filterClassInfo) { + $className = $filterClassInfo[0]; + $arguments = ($filterClassInfo[1] === []) ? null : $filterClassInfo[1]; - if ($result instanceof ResponseInterface) { - $this->response = $result; + $instance = $this->createFilter($className); - continue; + $result = $instance->after($this->request, $this->response, $arguments); + + if ($result instanceof ResponseInterface) { + $this->response = $result; + + continue; + } + } + + return $this->response; + } + + /** + * @param class-string $className + */ + private function createFilter(string $className): FilterInterface + { + if (isset($this->filterClassInstances[$className])) { + return $this->filterClassInstances[$className]; + } + + $instance = new $className(); + + if (! $instance instanceof FilterInterface) { + throw FilterException::forIncorrectInterface($instance::class); + } + + $this->filterClassInstances[$className] = $instance; + + return $instance; + } + + /** + * Returns the "Required Filters" class list. + * + * @param 'after'|'before' $position + * + * @return list}> [[classname, arguments], ...] + */ + public function getRequiredClasses(string $position): array + { + [$filters, $aliases] = $this->getRequiredFilters($position); + + if ($filters === []) { + return []; + } + + $filterClassList = []; + + foreach ($filters as $alias) { + if (is_array($aliases[$alias])) { + foreach ($this->config->aliases[$alias] as $class) { + $filterClassList[] = [$class, []]; } + } else { + $filterClassList[] = [$aliases[$alias], []]; + } + } + + return $filterClassList; + } + + /** + * Runs "Required Filters" for the specified position. + * + * @param 'after'|'before' $position + * + * @return RequestInterface|ResponseInterface|string|null + * + * @throws FilterException + * + * @internal + */ + public function runRequired(string $position = 'before') + { + $filterClassList = $this->getRequiredClasses($position); + + if ($filterClassList === []) { + return $position === 'before' ? $this->request : $this->response; + } + + if ($position === 'before') { + return $this->runBefore($filterClassList); + } + + // After + return $this->runAfter($filterClassList); + } + + /** + * Returns "Required Filters" for the specified position. + * + * @param 'after'|'before' $position + * + * @internal + */ + public function getRequiredFilters(string $position = 'before'): array + { + // For backward compatibility. For users who do not update Config\Filters. + if (! isset($this->config->required[$position])) { + $baseConfig = config(BaseFiltersConfig::class); + $filters = $baseConfig->required[$position]; + $aliases = $baseConfig->aliases; + } else { + $filters = $this->config->required[$position]; + $aliases = $this->config->aliases; + } + + if ($filters === []) { + return [[], $aliases]; + } + + if ($position === 'after') { + if (in_array('toolbar', $this->filters['after'], true)) { + // It was already run in globals filters. So remove it. + $filters = $this->setToolbarToLast($filters, true); + } else { + // Set the toolbar filter to the last position to be executed + $filters = $this->setToolbarToLast($filters); + } + } + + foreach ($filters as $alias) { + if (! array_key_exists($alias, $aliases)) { + throw FilterException::forNoAlias($alias); } } - return $position === 'before' ? $this->request : $this->response; + return [$filters, $aliases]; + } + + /** + * Set the toolbar filter to the last position to be executed. + * + * @param list $filters `after` filter array + * @param bool $remove if true, remove `toolbar` filter + */ + private function setToolbarToLast(array $filters, bool $remove = false): array + { + $afters = []; + $found = false; + + foreach ($filters as $alias) { + if ($alias === 'toolbar') { + $found = true; + + continue; + } + + $afters[] = $alias; + } + + if ($found && ! $remove) { + $afters[] = 'toolbar'; + } + + return $afters; } /** @@ -211,13 +443,18 @@ public function run(string $uri, string $position = 'before') * The resulting $this->filters is an array of only filters * that should be applied to this request. * - * We go ahead an process the entire tree because we'll need to + * We go ahead and process the entire tree because we'll need to * run through both a before and after and don't want to double * process the rows. * - * @param string $uri + * @param string|null $uri URI path relative to baseURL (all lowercase) + * + * @TODO We don't need to accept null as $uri. * * @return Filters + * + * @testTag Only for test code. The run() calls this, so you don't need to + * call this in your app. */ public function initialize(?string $uri = null) { @@ -225,22 +462,27 @@ public function initialize(?string $uri = null) return $this; } - if ($this->modules->shouldDiscover('filters')) { - $this->discoverFilters(); + // Decode URL-encoded string + $uri = urldecode($uri ?? ''); + + $oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false; // @phpstan-ignore nullCoalesce.property + if ($oldFilterOrder) { + $this->processGlobals($uri); + $this->processMethods(); + $this->processFilters($uri); + } else { + $this->processFilters($uri); + $this->processMethods(); + $this->processGlobals($uri); } - $this->processGlobals($uri); - $this->processMethods(); - $this->processFilters($uri); - // Set the toolbar filter to the last position to be executed - if (in_array('toolbar', $this->filters['after'], true) - && ($count = count($this->filters['after'])) > 1 - && $this->filters['after'][$count - 1] !== 'toolbar' - ) { - array_splice($this->filters['after'], array_search('toolbar', $this->filters['after'], true), 1); - $this->filters['after'][] = 'toolbar'; - } + $this->filters['after'] = $this->setToolbarToLast($this->filters['after']); + + // Since some filters like rate limiters rely on being executed once a request, + // we filter em here. + $this->filters['before'] = array_unique($this->filters['before']); + $this->filters['after'] = array_unique($this->filters['after']); $this->processAliasesToClass('before'); $this->processAliasesToClass('after'); @@ -271,6 +513,12 @@ public function reset(): self /** * Returns the processed filters array. + * This does not include "Required Filters". + * + * @return array{ + * before: list, + * after: list + * } */ public function getFilters(): array { @@ -279,6 +527,12 @@ public function getFilters(): array /** * Returns the filtersClass array. + * This does not include "Required Filters". + * + * @return array{ + * before: list}>, + * after: list}> + * } */ public function getFiltersClass(): array { @@ -290,23 +544,25 @@ public function getFiltersClass(): array * MUST be called prior to initialize(); * Intended for use within routes files. * + * @param 'after'|'before' $position + * * @return $this */ - public function addFilter(string $class, ?string $alias = null, string $when = 'before', string $section = 'globals') + public function addFilter(string $class, ?string $alias = null, string $position = 'before', string $section = 'globals') { - $alias = $alias ?? md5($class); + $alias ??= md5($class); if (! isset($this->config->{$section})) { $this->config->{$section} = []; } - if (! isset($this->config->{$section}[$when])) { - $this->config->{$section}[$when] = []; + if (! isset($this->config->{$section}[$position])) { + $this->config->{$section}[$position] = []; } $this->config->aliases[$alias] = $class; - $this->config->{$section}[$when][] = $alias; + $this->config->{$section}[$position][] = $alias; return $this; } @@ -318,56 +574,70 @@ public function addFilter(string $class, ?string $alias = null, string $when = ' * after the filter name, followed by a comma-separated list of arguments that * are passed to the filter when executed. * - * @return Filters - * - * @deprecated Use enableFilters(). This method will be private. + * @param string $filter filter_name or filter_name:arguments like 'role:admin,manager' + * or filter classname. + * @param 'after'|'before' $position */ - public function enableFilter(string $name, string $when = 'before') + private function enableFilter(string $filter, string $position = 'before'): void { - // Get parameters and clean name - if (strpos($name, ':') !== false) { - [$name, $params] = explode(':', $name); - - $params = explode(',', $params); - array_walk($params, static function (&$item) { - $item = trim($item); - }); - - $this->arguments[$name] = $params; + // Normalize the arguments. + [$alias, $arguments] = $this->getCleanName($filter); + $filter = ($arguments === []) ? $alias : $alias . ':' . implode(',', $arguments); + + if (class_exists($alias)) { + $this->config->aliases[$alias] = $alias; + } elseif (! array_key_exists($alias, $this->config->aliases)) { + throw FilterException::forNoAlias($alias); } - if (class_exists($name)) { - $this->config->aliases[$name] = $name; - } elseif (! array_key_exists($name, $this->config->aliases)) { - throw FilterException::forNoAlias($name); + if (! isset($this->filters[$position][$filter])) { + $this->filters[$position][] = $filter; } - $classNames = (array) $this->config->aliases[$name]; + // Since some filters like rate limiters rely on being executed once a request, + // we filter em here. + $this->filters[$position] = array_unique($this->filters[$position]); + } - foreach ($classNames as $className) { - $this->argumentsClass[$className] = $this->arguments[$name] ?? null; - } + /** + * Get clean name and arguments + * + * @param string $filter filter_name or filter_name:arguments like 'role:admin,manager' + * + * @return array{0: string, 1: list} [name, arguments] + */ + private function getCleanName(string $filter): array + { + $arguments = []; - if (! isset($this->filters[$when][$name])) { - $this->filters[$when][] = $name; - $this->filtersClass[$when] = array_merge($this->filtersClass[$when], $classNames); + if (! str_contains($filter, ':')) { + return [$filter, $arguments]; } - return $this; + [$alias, $arguments] = explode(':', $filter); + + $arguments = explode(',', $arguments); + array_walk($arguments, static function (&$item): void { + $item = trim($item); + }); + + return [$alias, $arguments]; } /** - * Ensures that specific filters is on and enabled for the current request. + * Ensures that specific filters are on and enabled for the current request. * * Filters can have "arguments". This is done by placing a colon immediately * after the filter name, followed by a comma-separated list of arguments that * are passed to the filter when executed. * + * @param list $filters filter_name or filter_name:arguments like 'role:admin,manager' + * * @return Filters */ - public function enableFilters(array $names, string $when = 'before') + public function enableFilters(array $filters, string $when = 'before') { - foreach ($names as $filter) { + foreach ($filters as $filter) { $this->enableFilter($filter, $when); } @@ -377,35 +647,34 @@ public function enableFilters(array $names, string $when = 'before') /** * Returns the arguments for a specified key, or all. * - * @return mixed + * @return array|string + * + * @deprecated 4.6.0 Already does not work. */ public function getArguments(?string $key = null) { - return $key === null ? $this->arguments : $this->arguments[$key]; + return ((string) $key === '') ? $this->arguments : $this->arguments[$key]; } - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- // Processors - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Add any applicable (not excluded) global filter settings to the mix. * - * @param string $uri + * @param string|null $uri URI path relative to baseURL (all lowercase) + * + * @return void */ protected function processGlobals(?string $uri = null) { - if (! isset($this->config->globals) || ! is_array($this->config->globals)) { - return; - } - - $uri = strtolower(trim($uri, '/ ')); + $uri = strtolower(trim($uri ?? '', '/ ')); // Add any global filters, unless they are excluded for this URI - $sets = [ - 'before', - 'after', - ]; + $sets = ['before', 'after']; + + $filters = []; foreach ($sets as $set) { if (isset($this->config->globals[$set])) { @@ -417,7 +686,7 @@ protected function processGlobals(?string $uri = null) if (isset($rules['except'])) { // grab the exclusion rules $check = $rules['except']; - if ($this->pathApplies($uri, $check)) { + if ($this->checkExcept($uri, $check)) { $keep = false; } } @@ -426,103 +695,173 @@ protected function processGlobals(?string $uri = null) } if ($keep) { - $this->filters[$set][] = $alias; + $filters[$set][] = $alias; } } } } + + if (isset($filters['before'])) { + $oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false; // @phpstan-ignore nullCoalesce.property + if ($oldFilterOrder) { + $this->filters['before'] = array_merge($this->filters['before'], $filters['before']); + } else { + $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + } + } + + if (isset($filters['after'])) { + $this->filters['after'] = array_merge($this->filters['after'], $filters['after']); + } } /** * Add any method-specific filters to the mix. + * + * @return void */ protected function processMethods() { - if (! isset($this->config->methods) || ! is_array($this->config->methods)) { - return; - } + $method = $this->request->getMethod(); - // Request method won't be set for CLI-based requests - $method = strtolower($_SERVER['REQUEST_METHOD'] ?? 'cli'); + $found = false; if (array_key_exists($method, $this->config->methods)) { - $this->filters['before'] = array_merge($this->filters['before'], $this->config->methods[$method]); + $found = true; + } + // Checks lowercase HTTP method for backward compatibility. + // @deprecated 4.5.0 + // @TODO remove this in the future. + elseif (array_key_exists(strtolower($method), $this->config->methods)) { + @trigger_error( + 'Setting lowercase HTTP method key "' . strtolower($method) . '" is deprecated.' + . ' Use uppercase HTTP method like "' . strtoupper($method) . '".', + E_USER_DEPRECATED, + ); + + $found = true; + $method = strtolower($method); + } + + if ($found) { + $oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false; // @phpstan-ignore nullCoalesce.property + if ($oldFilterOrder) { + $this->filters['before'] = array_merge($this->filters['before'], $this->config->methods[$method]); + } else { + $this->filters['before'] = array_merge($this->config->methods[$method], $this->filters['before']); + } } } /** * Add any applicable configured filters to the mix. * - * @param string $uri + * @param string|null $uri URI path relative to baseURL (all lowercase) + * + * @return void */ protected function processFilters(?string $uri = null) { - if (! isset($this->config->filters) || ! $this->config->filters) { + if ($this->config->filters === []) { return; } $uri = strtolower(trim($uri, '/ ')); // Add any filters that apply to this URI - foreach ($this->config->filters as $alias => $settings) { + $filters = []; + + foreach ($this->config->filters as $filter => $settings) { + // Normalize the arguments. + [$alias, $arguments] = $this->getCleanName($filter); + $filter = ($arguments === []) ? $alias : $alias . ':' . implode(',', $arguments); + // Look for inclusion rules if (isset($settings['before'])) { $path = $settings['before']; + if ($this->pathApplies($uri, $path)) { - $this->filters['before'][] = $alias; + $filters['before'][] = $filter; } } if (isset($settings['after'])) { $path = $settings['after']; + if ($this->pathApplies($uri, $path)) { - $this->filters['after'][] = $alias; + $filters['after'][] = $filter; } } } + + $oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false; // @phpstan-ignore nullCoalesce.property + + if (isset($filters['before'])) { + if ($oldFilterOrder) { + $this->filters['before'] = array_merge($this->filters['before'], $filters['before']); + } else { + $this->filters['before'] = array_merge($filters['before'], $this->filters['before']); + } + } + + if (isset($filters['after'])) { + if (! $oldFilterOrder) { + $filters['after'] = array_reverse($filters['after']); + } + + $this->filters['after'] = array_merge($this->filters['after'], $filters['after']); + } } /** * Maps filter aliases to the equivalent filter classes * + * @param 'after'|'before' $position + * + * @return void + * * @throws FilterException */ protected function processAliasesToClass(string $position) { - foreach ($this->filters[$position] as $alias => $rules) { - if (is_numeric($alias) && is_string($rules)) { - $alias = $rules; - } + $filterClassList = []; + + foreach ($this->filters[$position] as $filter) { + // Get arguments and clean alias + [$alias, $arguments] = $this->getCleanName($filter); if (! array_key_exists($alias, $this->config->aliases)) { throw FilterException::forNoAlias($alias); } if (is_array($this->config->aliases[$alias])) { - $this->filtersClass[$position] = array_merge($this->filtersClass[$position], $this->config->aliases[$alias]); + foreach ($this->config->aliases[$alias] as $class) { + $filterClassList[] = [$class, $arguments]; + } } else { - $this->filtersClass[$position][] = $this->config->aliases[$alias]; + $filterClassList[] = [$this->config->aliases[$alias], $arguments]; } } - // when using enableFilter() we already write the class name in ->filtersClass as well as the - // alias in ->filters. This leads to duplicates when using route filters. - // Since some filters like rate limiters rely on being executed once a request we filter em here. - $this->filtersClass[$position] = array_unique($this->filtersClass[$position]); + if ($position === 'before') { + $this->filtersClass[$position] = array_merge($filterClassList, $this->filtersClass[$position]); + } else { + $this->filtersClass[$position] = array_merge($this->filtersClass[$position], $filterClassList); + } } /** * Check paths for match for URI * - * @param string $uri URI to test against - * @param mixed $paths The path patterns to test + * @param string $uri URI to test against + * @param array|string $paths The path patterns to test * * @return bool True if any of the paths apply to the URI */ private function pathApplies(string $uri, $paths) { // empty path matches all - if (empty($paths)) { + if ($paths === '' || $paths === []) { return true; } @@ -531,14 +870,49 @@ private function pathApplies(string $uri, $paths) $paths = [$paths]; } - // treat each paths as pseudo-regex + return $this->checkPseudoRegex($uri, $paths); + } + + /** + * Check except paths + * + * @param string $uri URI path relative to baseURL (all lowercase) + * @param array|string $paths The except path patterns + * + * @return bool True if the URI matches except paths. + */ + private function checkExcept(string $uri, $paths): bool + { + // empty array does not match anything + if ($paths === []) { + return false; + } + + // make sure the paths are iterable + if (is_string($paths)) { + $paths = [$paths]; + } + + return $this->checkPseudoRegex($uri, $paths); + } + + /** + * Check the URI path as pseudo-regex + * + * @param string $uri URI path relative to baseURL (all lowercase, URL-decoded) + * @param array $paths The except path patterns + */ + private function checkPseudoRegex(string $uri, array $paths): bool + { + // treat each path as pseudo-regex foreach ($paths as $path) { // need to escape path separators $path = str_replace('/', '\/', trim($path, '/ ')); // need to make pseudo wildcard real $path = strtolower(str_replace('*', '.*', $path)); + // Does this rule apply here? - if (preg_match('#^' . $path . '$#', $uri, $match) === 1) { + if (preg_match('#\A' . $path . '\z#u', $uri, $match) === 1) { return true; } } diff --git a/system/Filters/ForceHTTPS.php b/system/Filters/ForceHTTPS.php new file mode 100644 index 000000000000..aa9cb3716d08 --- /dev/null +++ b/system/Filters/ForceHTTPS.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Filters; + +use CodeIgniter\HTTP\Exceptions\RedirectException; +use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\ResponseInterface; +use Config\App; + +/** + * Force HTTPS filter + */ +class ForceHTTPS implements FilterInterface +{ + /** + * Force Secure Site Access? If the config value 'forceGlobalSecureRequests' + * is true, will enforce that all requests to this site are made through + * HTTPS. Will redirect the user to the current page with HTTPS, as well + * as set the HTTP Strict Transport Security (HSTS) header for those browsers + * that support it. + * + * @param array|null $arguments + * + * @return ResponseInterface|null + */ + public function before(RequestInterface $request, $arguments = null) + { + $config = config(App::class); + + if ($config->forceGlobalSecureRequests !== true) { + return null; + } + + $response = service('response'); + + try { + force_https(YEAR, $request, $response); + } catch (RedirectException $e) { + return $e->getResponse(); + } + + return null; + } + + /** + * We don't have anything to do here. + * + * @param array|null $arguments + */ + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + return null; + } +} diff --git a/system/Filters/Honeypot.php b/system/Filters/Honeypot.php index 539513e7cb8a..4b24691ef905 100644 --- a/system/Filters/Honeypot.php +++ b/system/Filters/Honeypot.php @@ -1,5 +1,7 @@ |null $arguments * * @throws HoneypotException */ public function before(RequestInterface $request, $arguments = null) { - if (Services::honeypot()->hasContent($request)) { + if (! $request instanceof IncomingRequest) { + return null; + } + + if (service('honeypot')->hasContent($request)) { throw HoneypotException::isBot(); } + + return null; } /** * Attach a honeypot to the current response. * - * @param array|null $arguments + * @param list|null $arguments */ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { - Services::honeypot()->attachHoneypot($response); + service('honeypot')->attachHoneypot($response); + + return null; } } diff --git a/system/Filters/InvalidChars.php b/system/Filters/InvalidChars.php new file mode 100644 index 000000000000..85e71c5d4f59 --- /dev/null +++ b/system/Filters/InvalidChars.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Filters; + +use CodeIgniter\HTTP\IncomingRequest; +use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\Security\Exceptions\SecurityException; + +/** + * InvalidChars filter. + * + * Check if user input data ($_GET, $_POST, $_COOKIE, php://input) do not contain + * invalid characters: + * - invalid UTF-8 characters + * - control characters except line break and tab code + * + * @see \CodeIgniter\Filters\InvalidCharsTest + */ +class InvalidChars implements FilterInterface +{ + /** + * Data source + * + * @var string + */ + protected $source; + + /** + * Regular expressions for valid control codes + * + * @var string + */ + protected $controlCodeRegex = '/\A[\r\n\t[:^cntrl:]]*\z/u'; + + /** + * Check invalid characters. + * + * @param list|null $arguments + */ + public function before(RequestInterface $request, $arguments = null) + { + if (! $request instanceof IncomingRequest) { + return null; + } + + $data = [ + 'get' => $request->getGet(), + 'post' => $request->getPost(), + 'cookie' => $request->getCookie(), + 'rawInput' => $request->getRawInput(), + ]; + + foreach ($data as $source => $values) { + $this->source = $source; + $this->checkEncoding($values); + $this->checkControl($values); + } + + return null; + } + + /** + * We don't have anything to do here. + * + * @param list|null $arguments + */ + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + return null; + } + + /** + * Check the character encoding is valid UTF-8. + * + * @param array|string $value + * + * @return array|string + * + * @throws SecurityException + */ + protected function checkEncoding($value) + { + if (is_array($value)) { + foreach ($value as $key => $item) { + if (is_string($key)) { + $this->checkEncoding($key); + } + $this->checkEncoding($item); + } + + return $value; + } + + if (mb_check_encoding($value, 'UTF-8')) { + return $value; + } + + throw SecurityException::forInvalidUTF8Chars($this->source, $value); + } + + /** + * Check for the presence of control characters except line breaks and tabs. + * + * @param array|string $value + * + * @return array|string + */ + protected function checkControl($value) + { + if (is_array($value)) { + foreach ($value as $key => $item) { + if (is_string($key)) { + $this->checkControl($key); + } + $this->checkControl($item); + } + + return $value; + } + + if (preg_match($this->controlCodeRegex, $value) === 1) { + return $value; + } + + throw SecurityException::forInvalidControlChars($this->source, $value); + } +} diff --git a/system/Filters/PageCache.php b/system/Filters/PageCache.php new file mode 100644 index 000000000000..ff4bd097f84c --- /dev/null +++ b/system/Filters/PageCache.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Filters; + +use CodeIgniter\Cache\ResponseCache; +use CodeIgniter\HTTP\CLIRequest; +use CodeIgniter\HTTP\DownloadResponse; +use CodeIgniter\HTTP\IncomingRequest; +use CodeIgniter\HTTP\RedirectResponse; +use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\ResponseInterface; +use Config\Cache; + +/** + * Page Cache filter + */ +class PageCache implements FilterInterface +{ + private readonly ResponseCache $pageCache; + + /** + * @var list + */ + private readonly array $cacheStatusCodes; + + public function __construct(?Cache $config = null) + { + $config ??= config('Cache'); + + $this->pageCache = service('responsecache'); + $this->cacheStatusCodes = $config->cacheStatusCodes ?? []; // @phpstan-ignore nullCoalesce.property + } + + /** + * Checks page cache and return if found. + * + * @param array|null $arguments + * + * @return ResponseInterface|null + */ + public function before(RequestInterface $request, $arguments = null) + { + assert($request instanceof CLIRequest || $request instanceof IncomingRequest); + + $response = service('response'); + + return $this->pageCache->get($request, $response); + } + + /** + * Cache the page. + * + * @param array|null $arguments + */ + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + assert($request instanceof CLIRequest || $request instanceof IncomingRequest); + + if ( + ! $response instanceof DownloadResponse + && ! $response instanceof RedirectResponse + && ($this->cacheStatusCodes === [] || in_array($response->getStatusCode(), $this->cacheStatusCodes, true)) + ) { + // Cache it without the performance metrics replaced + // so that we can have live speed updates along the way. + // Must be run after filters to preserve the Response headers. + $this->pageCache->make($request, $response); + + return $response; + } + + return null; + } +} diff --git a/system/Filters/PerformanceMetrics.php b/system/Filters/PerformanceMetrics.php new file mode 100644 index 000000000000..97582ad8dc05 --- /dev/null +++ b/system/Filters/PerformanceMetrics.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Filters; + +use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\ResponseInterface; + +/** + * Performance Metrics filter + */ +class PerformanceMetrics implements FilterInterface +{ + /** + * We don't need to do anything here. + * + * @param array|null $arguments + */ + public function before(RequestInterface $request, $arguments = null) + { + return null; + } + + /** + * Replaces the performance metrics. + * + * @param array|null $arguments + */ + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + $body = $response->getBody(); + + if ($body !== null) { + $benchmark = service('timer'); + + $output = str_replace( + [ + '{elapsed_time}', + '{memory_usage}', + ], + [ + (string) $benchmark->getElapsedTime('total_execution'), + number_format(memory_get_peak_usage() / 1024 / 1024, 3), + ], + $body, + ); + + $response->setBody($output); + + return $response; + } + + return null; + } +} diff --git a/system/Filters/SecureHeaders.php b/system/Filters/SecureHeaders.php new file mode 100644 index 000000000000..42e5842384db --- /dev/null +++ b/system/Filters/SecureHeaders.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Filters; + +use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\ResponseInterface; + +/** + * Add Common Security Headers + * + * @see \CodeIgniter\Filters\SecureHeadersTest + */ +class SecureHeaders implements FilterInterface +{ + /** + * @var array + */ + protected $headers = [ + // https://owasp.org/www-project-secure-headers/#x-frame-options + 'X-Frame-Options' => 'SAMEORIGIN', + + // https://owasp.org/www-project-secure-headers/#x-content-type-options + 'X-Content-Type-Options' => 'nosniff', + + // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/jj542450(v=vs.85)#the-noopen-directive + 'X-Download-Options' => 'noopen', + + // https://owasp.org/www-project-secure-headers/#x-permitted-cross-domain-policies + 'X-Permitted-Cross-Domain-Policies' => 'none', + + // https://owasp.org/www-project-secure-headers/#referrer-policy + 'Referrer-Policy' => 'same-origin', + + // https://owasp.org/www-project-secure-headers/#x-xss-protection + // If you do not need to support legacy browsers, it is recommended that you use + // Content-Security-Policy without allowing unsafe-inline scripts instead. + // 'X-XSS-Protection' => '1; mode=block', + ]; + + /** + * We don't have anything to do here. + * + * @param list|null $arguments + */ + public function before(RequestInterface $request, $arguments = null) + { + return null; + } + + /** + * Add security headers. + * + * @param list|null $arguments + */ + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + foreach ($this->headers as $header => $value) { + $response->setHeader($header, $value); + } + + return $response; + } +} diff --git a/system/Format/Exceptions/FormatException.php b/system/Format/Exceptions/FormatException.php index 5fef64eae2a1..cde333b1292c 100644 --- a/system/Format/Exceptions/FormatException.php +++ b/system/Format/Exceptions/FormatException.php @@ -1,5 +1,7 @@ config = $config; } /** diff --git a/system/Format/FormatterInterface.php b/system/Format/FormatterInterface.php index 04ba3e8973e9..0c2492b90a77 100644 --- a/system/Format/FormatterInterface.php +++ b/system/Format/FormatterInterface.php @@ -1,5 +1,7 @@ |object|string $data * - * @return mixed + * @return false|non-empty-string */ public function format($data); } diff --git a/system/Format/JSONFormatter.php b/system/Format/JSONFormatter.php index b9b3daaebb25..ac237fcc7af3 100644 --- a/system/Format/JSONFormatter.php +++ b/system/Format/JSONFormatter.php @@ -1,5 +1,7 @@ |object|string $data * - * @return bool|string (JSON string | false) + * @return false|non-empty-string */ public function format($data) { $config = new Format(); $options = $config->formatterOptions['application/json'] ?? JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; - $options = $options | JSON_PARTIAL_OUTPUT_ON_ERROR; + $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; - $options = ENVIRONMENT === 'production' ? $options : $options | JSON_PRETTY_PRINT; + if (ENVIRONMENT !== 'production') { + $options |= JSON_PRETTY_PRINT; + } - $result = json_encode($data, $options, 512); + $result = json_encode($data, $options, $config->jsonEncodeDepth); if (! in_array(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_RECURSION], true)) { throw FormatException::forInvalidJSON(json_last_error_msg()); diff --git a/system/Format/XMLFormatter.php b/system/Format/XMLFormatter.php index b32e148a7da9..efef70cec2cb 100644 --- a/system/Format/XMLFormatter.php +++ b/system/Format/XMLFormatter.php @@ -1,5 +1,7 @@ |object|string $data * - * @return bool|string (XML string | false) + * @return false|non-empty-string */ public function format($data) { @@ -34,10 +38,7 @@ public function format($data) // SimpleXML is installed but default // but best to check, and then provide a fallback. if (! extension_loaded('simplexml')) { - // never thrown in travis-ci - // @codeCoverageIgnoreStart - throw FormatException::forMissingExtension(); - // @codeCoverageIgnoreEnd + throw FormatException::forMissingExtension(); // @codeCoverageIgnore } $options = $config->formatterOptions['application/xml'] ?? 0; @@ -55,7 +56,10 @@ public function format($data) * * @see http://www.codexworld.com/convert-array-to-xml-in-php/ * - * @param SimpleXMLElement $output + * @param array $data + * @param SimpleXMLElement $output + * + * @return void */ protected function arrayToXML(array $data, &$output) { @@ -90,6 +94,8 @@ protected function normalizeXMLTag($key) '\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}'; $validName = $startChar . '\\.\\d\\x{B7}\\x{300}-\\x{36F}\\x{203F}-\\x{2040}'; + $key = (string) $key; + $key = trim($key); $key = preg_replace("/[^{$validName}-]+/u", '', $key); $key = preg_replace("/^[^{$startChar}]+/u", 'item$0', $key); diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php index b8db857fce54..67c6ad4c1c7c 100644 --- a/system/HTTP/CLIRequest.php +++ b/system/HTTP/CLIRequest.php @@ -1,5 +1,7 @@ parseCommand(); + + // Set SiteURI for this request + $this->uri = new SiteURI($config, $this->getPath()); } /** @@ -82,9 +95,7 @@ public function __construct(App $config) */ public function getPath(): string { - $path = implode('/', $this->segments); - - return empty($path) ? '' : $path; + return implode('/', $this->segments); } /** @@ -96,6 +107,14 @@ public function getOptions(): array return $this->options; } + /** + * Returns an array of all CLI arguments (segments and options). + */ + public function getArgs(): array + { + return $this->args; + } + /** * Returns the path segments. */ @@ -128,7 +147,7 @@ public function getOption(string $key) */ public function getOptionString(bool $useLongOpts = false): string { - if (empty($this->options)) { + if ($this->options === []) { return ''; } @@ -141,11 +160,13 @@ public function getOptionString(bool $useLongOpts = false): string $out .= "-{$name} "; } - // If there's a space, we need to group - // so it will pass correctly. - if (mb_strpos($value, ' ') !== false) { + if ($value === null) { + continue; + } + + if (str_contains($value, ' ')) { $out .= '"' . $value . '" '; - } elseif ($value !== null) { + } else { $out .= "{$value} "; } } @@ -159,10 +180,12 @@ public function getOptionString(bool $useLongOpts = false): string * * NOTE: I tried to use getopt but had it fail occasionally to find * any options, where argv has always had our back. + * + * @return void */ protected function parseCommand() { - $args = $this->getServer('argv'); + $args = $this->getServer('argv') ?? []; array_shift($args); // Scrap index.php $optionValue = false; @@ -172,21 +195,23 @@ protected function parseCommand() if ($optionValue) { $optionValue = false; } else { - $this->segments[] = filter_var($arg, FILTER_SANITIZE_STRING); + $this->segments[] = $arg; + $this->args[] = $arg; } continue; } - $arg = filter_var(ltrim($arg, '-'), FILTER_SANITIZE_STRING); + $arg = ltrim($arg, '-'); $value = null; if (isset($args[$i + 1]) && mb_strpos($args[$i + 1], '-') !== 0) { - $value = filter_var($args[$i + 1], FILTER_SANITIZE_STRING); + $value = $args[$i + 1]; $optionValue = true; } $this->options[$arg] = $value; + $this->args[$arg] = $value; } } @@ -195,6 +220,103 @@ protected function parseCommand() */ public function isCLI(): bool { - return is_cli(); + return true; + } + + /** + * Fetch an item from GET data. + * + * @param array|string|null $index Index for item to fetch from $_GET. + * @param int|null $filter A filter name to apply. + * @param array|int|null $flags + * + * @return array|null + */ + public function getGet($index = null, $filter = null, $flags = null) + { + return $this->returnNullOrEmptyArray($index); + } + + /** + * Fetch an item from POST. + * + * @param array|string|null $index Index for item to fetch from $_POST. + * @param int|null $filter A filter name to apply + * @param array|int|null $flags + * + * @return array|null + */ + public function getPost($index = null, $filter = null, $flags = null) + { + return $this->returnNullOrEmptyArray($index); + } + + /** + * Fetch an item from POST data with fallback to GET. + * + * @param array|string|null $index Index for item to fetch from $_POST or $_GET + * @param int|null $filter A filter name to apply + * @param array|int|null $flags + * + * @return array|null + */ + public function getPostGet($index = null, $filter = null, $flags = null) + { + return $this->returnNullOrEmptyArray($index); + } + + /** + * Fetch an item from GET data with fallback to POST. + * + * @param array|string|null $index Index for item to be fetched from $_GET or $_POST + * @param int|null $filter A filter name to apply + * @param array|int|null $flags + * + * @return array|null + */ + public function getGetPost($index = null, $filter = null, $flags = null) + { + return $this->returnNullOrEmptyArray($index); + } + + /** + * This is a place holder for calls from cookie_helper get_cookie(). + * + * @param array|string|null $index Index for item to be fetched from $_COOKIE + * @param int|null $filter A filter name to be applied + * @param mixed $flags + * + * @return array|null + */ + public function getCookie($index = null, $filter = null, $flags = null) + { + return $this->returnNullOrEmptyArray($index); + } + + /** + * @param array|string|null $index + * + * @return array|null + */ + private function returnNullOrEmptyArray($index) + { + return ($index === null || is_array($index)) ? [] : null; + } + + /** + * Gets the current locale, with a fallback to the default + * locale if none is set. + */ + public function getLocale(): string + { + return Locale::getDefault(); + } + + /** + * Checks this request type. + */ + public function is(string $type): bool + { + return false; } } diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 3be8214d42b4..76ded55c20a6 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -1,5 +1,7 @@ 0.0, 'connect_timeout' => 150, 'debug' => false, @@ -72,6 +90,24 @@ class CURLRequest extends Request */ protected $delay = 0.0; + /** + * The default options from the constructor. Applied to all requests. + */ + private readonly array $defaultOptions; + + /** + * Whether share options between requests or not. + * + * If true, all the options won't be reset between requests. + * It may cause an error request with unnecessary headers. + */ + private readonly bool $shareOptions; + + /** + * The share connection instance. + */ + protected ?CurlShareHandle $shareConnection = null; + /** * Takes an array of options to set the following possible class properties: * @@ -79,50 +115,95 @@ class CURLRequest extends Request * - timeout * - any other request options to use as defaults. * - * @param ResponseInterface $response + * @param array $options */ public function __construct(App $config, URI $uri, ?ResponseInterface $response = null, array $options = []) { if (! function_exists('curl_version')) { - // we won't see this during travis-CI - // @codeCoverageIgnoreStart - throw HTTPException::forMissingCurl(); - // @codeCoverageIgnoreEnd + throw HTTPException::forMissingCurl(); // @codeCoverageIgnore } - parent::__construct($config); + parent::__construct(Method::GET, $uri); + + $this->responseOrig = $response ?? new Response($config); + // Remove the default Content-Type header. + $this->responseOrig->removeHeader('Content-Type'); + + $this->baseURI = $uri->useRawQueryString(); + $this->defaultOptions = $options; - $this->response = $response; - $this->baseURI = $uri->useRawQueryString(); + $this->shareOptions = config(ConfigCURLRequest::class)->shareOptions; + $this->config = $this->defaultConfig; $this->parseOptions($options); + + // Share Connection + $optShareConnection = config(ConfigCURLRequest::class)->shareConnectionOptions ?? [ // @phpstan-ignore nullCoalesce.property + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_DNS, + ]; + + if ($optShareConnection !== []) { + $this->shareConnection = curl_share_init(); + + foreach (array_unique($optShareConnection) as $opt) { + curl_share_setopt($this->shareConnection, CURLSHOPT_SHARE, $opt); + } + } } /** * Sends an HTTP request to the specified $url. If this is a relative * URL, it will be merged with $this->baseURI to form a complete URL. * - * @param string $method + * @param string $method HTTP method */ public function request($method, string $url, array $options = []): ResponseInterface { + $this->response = clone $this->responseOrig; + $this->parseOptions($options); $url = $this->prepareURL($url); - $method = filter_var($method, FILTER_SANITIZE_STRING); + $method = esc(strip_tags($method)); $this->send($method, $url); + if ($this->shareOptions === false) { + $this->resetOptions(); + } + return $this->response; } + /** + * Reset all options to default. + * + * @return void + */ + protected function resetOptions() + { + // Reset headers + $this->headers = []; + $this->headerMap = []; + + // Reset body + $this->body = null; + + // Reset configs + $this->config = $this->defaultConfig; + + // Set the default options for next request + $this->parseOptions($this->defaultOptions); + } + /** * Convenience method for sending a GET request. */ public function get(string $url, array $options = []): ResponseInterface { - return $this->request('get', $url, $options); + return $this->request(Method::GET, $url, $options); } /** @@ -130,7 +211,7 @@ public function get(string $url, array $options = []): ResponseInterface */ public function delete(string $url, array $options = []): ResponseInterface { - return $this->request('delete', $url, $options); + return $this->request('DELETE', $url, $options); } /** @@ -138,7 +219,7 @@ public function delete(string $url, array $options = []): ResponseInterface */ public function head(string $url, array $options = []): ResponseInterface { - return $this->request('head', $url, $options); + return $this->request('HEAD', $url, $options); } /** @@ -146,7 +227,7 @@ public function head(string $url, array $options = []): ResponseInterface */ public function options(string $url, array $options = []): ResponseInterface { - return $this->request('options', $url, $options); + return $this->request('OPTIONS', $url, $options); } /** @@ -154,7 +235,7 @@ public function options(string $url, array $options = []): ResponseInterface */ public function patch(string $url, array $options = []): ResponseInterface { - return $this->request('patch', $url, $options); + return $this->request('PATCH', $url, $options); } /** @@ -162,7 +243,7 @@ public function patch(string $url, array $options = []): ResponseInterface */ public function post(string $url, array $options = []): ResponseInterface { - return $this->request('post', $url, $options); + return $this->request(Method::POST, $url, $options); } /** @@ -170,7 +251,7 @@ public function post(string $url, array $options = []): ResponseInterface */ public function put(string $url, array $options = []): ResponseInterface { - return $this->request('put', $url, $options); + return $this->request(Method::PUT, $url, $options); } /** @@ -180,13 +261,9 @@ public function put(string $url, array $options = []): ResponseInterface * * @return $this */ - public function setAuth(string $username, string $password, string $type = 'basic') + public function setAuth(string $username, #[SensitiveParameter] string $password, string $type = 'basic') { - $this->config['auth'] = [ - $username, - $password, - $type, - ]; + $this->config['auth'] = [$username, $password, $type]; return $this; } @@ -226,6 +303,8 @@ public function setJSON($data) /** * Sets the correct settings based on the options array * passed in. + * + * @return void */ protected function parseOptions(array $options) { @@ -249,6 +328,11 @@ protected function parseOptions(array $options) unset($options['delay']); } + if (array_key_exists('body', $options)) { + $this->setBody($options['body']); + unset($options['body']); + } + foreach ($options as $key => $value) { $this->config[$key] = $value; } @@ -261,25 +345,20 @@ protected function parseOptions(array $options) protected function prepareURL(string $url): string { // If it's a full URI, then we have nothing to do here... - if (strpos($url, '://') !== false) { + if (str_contains($url, '://')) { return $url; } $uri = $this->baseURI->resolveRelativeURI($url); // Create the string instead of casting to prevent baseURL muddling - return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); - } - - /** - * Get the request method. Overrides the Request class' method - * since users expect a different answer here. - * - * @param bool|false $upper Whether to return in upper or lower case. - */ - public function getMethod(bool $upper = false): string - { - return ($upper) ? strtoupper($this->method) : strtolower($this->method); + return URI::createURIString( + $uri->getScheme(), + $uri->getAuthority(), + $uri->getPath(), + $uri->getQuery(), + $uri->getFragment(), + ); } /** @@ -302,8 +381,12 @@ public function send(string $method, string $url) $curlOptions[CURLOPT_URL] = $url; $curlOptions[CURLOPT_RETURNTRANSFER] = true; - $curlOptions[CURLOPT_HEADER] = true; - $curlOptions[CURLOPT_FRESH_CONNECT] = true; + + if ($this->shareConnection instanceof CurlShareHandle) { + $curlOptions[CURLOPT_SHARE] = $this->shareConnection; + } + + $curlOptions[CURLOPT_HEADER] = true; // Disable @file uploads in post data. $curlOptions[CURLOPT_SAFE_UPLOAD] = true; @@ -313,7 +396,7 @@ public function send(string $method, string $url) // Do we need to delay this request? if ($this->delay > 0) { - sleep($this->delay); // @phpstan-ignore-line + usleep((int) $this->delay * 1_000_000); } $output = $this->sendRequest($curlOptions); @@ -321,14 +404,8 @@ public function send(string $method, string $url) // Set the string we want to break our response from $breakString = "\r\n\r\n"; - if (strpos($output, 'HTTP/1.1 100 Continue') === 0) { - $output = substr($output, strpos($output, $breakString) + 4); - } - - // If request and response have Digest - if (isset($this->config['auth'][2]) && $this->config['auth'][2] === 'digest' && strpos($output, 'WWW-Authenticate: Digest') !== false) { - $output = substr($output, strpos($output, $breakString) + 4); - } + // Remove all intermediate responses + $output = $this->removeIntermediateResponses($output, $breakString); // Split out our headers and body $break = strpos($output, $breakString); @@ -350,27 +427,17 @@ public function send(string $method, string $url) } /** - * Takes all headers current part of this request and adds them - * to the cURL request. + * Adds $this->headers to the cURL request. */ protected function applyRequestHeaders(array $curlOptions = []): array { if (empty($this->headers)) { - $this->populateHeaders(); - // Otherwise, it will corrupt the request - $this->removeHeader('Host'); - $this->removeHeader('Accept-Encoding'); - } - - $headers = $this->headers(); - - if (empty($headers)) { return $curlOptions; } $set = []; - foreach (array_keys($headers) as $name) { + foreach (array_keys($this->headers) as $name) { $set[] = $name . ': ' . $this->getHeaderLine($name); } @@ -384,19 +451,17 @@ protected function applyRequestHeaders(array $curlOptions = []): array */ protected function applyMethod(string $method, array $curlOptions): array { - $method = strtoupper($method); - $this->method = $method; $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; - $size = strlen($this->body); + $size = strlen($this->body ?? ''); // Have content? if ($size > 0) { return $this->applyBody($curlOptions); } - if ($method === 'PUT' || $method === 'POST') { + if ($method === Method::PUT || $method === Method::POST) { // See http://tools.ietf.org/html/rfc7230#section-3.3.2 if ($this->header('content-length') === null && ! isset($this->config['multipart'])) { $this->setHeader('Content-Length', '0'); @@ -423,24 +488,30 @@ protected function applyBody(array $curlOptions = []): array /** * Parses the header retrieved from the cURL response into * our Response object. + * + * @return void */ protected function setResponseHeaders(array $headers = []) { foreach ($headers as $header) { if (($pos = strpos($header, ':')) !== false) { - $title = substr($header, 0, $pos); - $value = substr($header, $pos + 1); + $title = trim(substr($header, 0, $pos)); + $value = trim(substr($header, $pos + 1)); - $this->response->setHeader($title, $value); - } elseif (strpos($header, 'HTTP') === 0) { - preg_match('#^HTTP\/([12](?:\.[01])?) (\d+) (.+)#', $header, $matches); + if ($this->response instanceof Response) { + $this->response->addHeader($title, $value); + } else { + $this->response->setHeader($title, $value); + } + } elseif (str_starts_with($header, 'HTTP')) { + preg_match('#^HTTP\/([12](?:\.[01])?) (\d+)(?: (.+))?#', $header, $matches); if (isset($matches[1])) { $this->response->setProtocolVersion($matches[1]); } if (isset($matches[2])) { - $this->response->setStatusCode($matches[2], $matches[3] ?? null); + $this->response->setStatusCode((int) $matches[2], $matches[3] ?? ''); } } } @@ -449,27 +520,56 @@ protected function setResponseHeaders(array $headers = []) /** * Set CURL options * - * @throws InvalidArgumentException - * * @return array + * + * @throws InvalidArgumentException */ protected function setCURLOptions(array $curlOptions = [], array $config = []) { - // Auth Headers - if (! empty($config['auth'])) { - $curlOptions[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1]; + $curlOptions = $this->applyAuthOptions($curlOptions, $config); + $curlOptions = $this->applySslOptions($curlOptions, $config); + $curlOptions = $this->applyProxyOptions($curlOptions, $config); + $curlOptions = $this->applyDebugOptions($curlOptions, $config); + $curlOptions = $this->applyRedirectOptions($curlOptions, $config); + $curlOptions = $this->applyConnectionOptions($curlOptions, $config); + $curlOptions = $this->applyBodyOptions($curlOptions, $config); + $curlOptions = $this->applyResponseOptions($curlOptions, $config); + $curlOptions = $this->applyProtocolOptions($curlOptions, $config); + + return $this->applyClientOptions($curlOptions, $config); + } - if (! empty($config['auth'][2]) && strtolower($config['auth'][2]) === 'digest') { - $curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; - } else { - $curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; - } + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyAuthOptions(array $curlOptions, array $config): array + { + // Auth Headers + if (isset($config['auth']) && is_array($config['auth']) && count($config['auth']) >= 2) { + $curlOptions[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1]; + $curlOptions[CURLOPT_HTTPAUTH] = (isset($config['auth'][2]) && $config['auth'][2] !== '' && strtolower($config['auth'][2]) === 'digest') + ? CURLAUTH_DIGEST + : CURLAUTH_BASIC; } + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applySslOptions(array $curlOptions, array $config): array + { // Certificate - if (! empty($config['cert'])) { - $cert = $config['cert']; + $cert = $config['cert'] ?? null; + if ((bool) $cert) { if (is_array($cert)) { $curlOptions[CURLOPT_SSLCERTPASSWD] = $cert[1]; $cert = $cert[0]; @@ -485,37 +585,66 @@ protected function setCURLOptions(array $curlOptions = [], array $config = []) // SSL Verification if (isset($config['verify'])) { if (is_string($config['verify'])) { - $file = realpath($config['ssl_key']) ?: $config['ssl_key']; + $file = realpath($config['verify']) ?: $config['verify']; if (! is_file($file)) { - throw HTTPException::forInvalidSSLKey($config['ssl_key']); + throw HTTPException::forInvalidSSLKey($config['verify']); } $curlOptions[CURLOPT_CAINFO] = $file; - $curlOptions[CURLOPT_SSL_VERIFYPEER] = 1; + $curlOptions[CURLOPT_SSL_VERIFYPEER] = true; + $curlOptions[CURLOPT_SSL_VERIFYHOST] = 2; } elseif (is_bool($config['verify'])) { $curlOptions[CURLOPT_SSL_VERIFYPEER] = $config['verify']; + $curlOptions[CURLOPT_SSL_VERIFYHOST] = $config['verify'] ? 2 : 0; } } + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyProxyOptions(array $curlOptions, array $config): array + { + // Proxy + if (isset($config['proxy'])) { + $curlOptions[CURLOPT_HTTPPROXYTUNNEL] = true; + $curlOptions[CURLOPT_PROXY] = $config['proxy']; + } + + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyDebugOptions(array $curlOptions, array $config): array + { // Debug - if ($config['debug']) { + if ((bool) ($config['debug'] ?? false)) { $curlOptions[CURLOPT_VERBOSE] = 1; $curlOptions[CURLOPT_STDERR] = is_string($config['debug']) ? fopen($config['debug'], 'a+b') : fopen('php://stderr', 'wb'); } - // Decode Content - if (! empty($config['decode_content'])) { - $accept = $this->getHeaderLine('Accept-Encoding'); - - if ($accept) { - $curlOptions[CURLOPT_ENCODING] = $accept; - } else { - $curlOptions[CURLOPT_ENCODING] = ''; - $curlOptions[CURLOPT_HTTPHEADER] = 'Accept-Encoding'; - } - } + return $curlOptions; + } + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyRedirectOptions(array $curlOptions, array $config): array + { // Allow Redirects if (array_key_exists('allow_redirects', $config)) { $settings = $this->redirectDefaults; @@ -544,14 +673,55 @@ protected function setCURLOptions(array $curlOptions = [], array $config = []) } } + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyConnectionOptions(array $curlOptions, array $config): array + { + // DNS Cache Timeout + if (isset($config['dns_cache_timeout']) && is_numeric($config['dns_cache_timeout']) && $config['dns_cache_timeout'] >= -1) { + $curlOptions[CURLOPT_DNS_CACHE_TIMEOUT] = (int) $config['dns_cache_timeout']; + } + + // Fresh Connect (default true) + $curlOptions[CURLOPT_FRESH_CONNECT] = isset($config['fresh_connect']) && is_bool($config['fresh_connect']) + ? $config['fresh_connect'] + : true; + // Timeout - $curlOptions[CURLOPT_TIMEOUT_MS] = (float) $config['timeout'] * 1000; + $curlOptions[CURLOPT_TIMEOUT_MS] = (float) ($config['timeout'] ?? 0) * 1000; // Connection Timeout - $curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = (float) $config['connect_timeout'] * 1000; + $curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = (float) ($config['connect_timeout'] ?? 150) * 1000; + + // Resolve IP + if (array_key_exists('force_ip_resolve', $config)) { + $curlOptions[CURLOPT_IPRESOLVE] = match ($config['force_ip_resolve']) { + 'v4' => CURL_IPRESOLVE_V4, + 'v6' => CURL_IPRESOLVE_V6, + default => CURL_IPRESOLVE_WHATEVER, + }; + } + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyBodyOptions(array $curlOptions, array $config): array + { // Post Data - application/x-www-form-urlencoded - if (! empty($config['form_params']) && is_array($config['form_params'])) { + if (isset($config['form_params']) && is_array($config['form_params']) && $config['form_params'] !== []) { $postFields = http_build_query($config['form_params']); $curlOptions[CURLOPT_POSTFIELDS] = $postFields; @@ -562,14 +732,11 @@ protected function setCURLOptions(array $curlOptions = [], array $config = []) } // Post Data - multipart/form-data - if (! empty($config['multipart']) && is_array($config['multipart'])) { + if (isset($config['multipart']) && is_array($config['multipart']) && $config['multipart'] !== []) { // setting the POSTFIELDS option automatically sets multipart $curlOptions[CURLOPT_POSTFIELDS] = $config['multipart']; } - // HTTP Errors - $curlOptions[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true; - // JSON if (isset($config['json'])) { // Will be set as the body in `applyBody()` @@ -579,15 +746,76 @@ protected function setCURLOptions(array $curlOptions = [], array $config = []) $this->setHeader('Content-Length', (string) strlen($json)); } - // version - if (! empty($config['version'])) { - if ($config['version'] === 1.0) { - $curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; - } elseif ($config['version'] === 1.1) { - $curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyResponseOptions(array $curlOptions, array $config): array + { + // Decode Content + if ((bool) ($config['decode_content'] ?? false)) { + $accept = $this->getHeaderLine('Accept-Encoding'); + + if ($accept !== '') { + $curlOptions[CURLOPT_ENCODING] = $accept; + } else { + $curlOptions[CURLOPT_ENCODING] = ''; + $curlOptions[CURLOPT_HTTPHEADER] = 'Accept-Encoding'; } } + // HTTP Errors + $curlOptions[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true; + + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyProtocolOptions(array $curlOptions, array $config): array + { + if (! isset($config['version']) || (bool) $config['version'] === false) { + return $curlOptions; + } + + $version = sprintf('%.1F', (float) $config['version']); + + if ($version === '3.0' && ! defined('CURL_HTTP_VERSION_3')) { + define('CURL_HTTP_VERSION_3', 30); + } + + $curlVersion = match ($version) { + '1.0' => CURL_HTTP_VERSION_1_0, + '1.1' => CURL_HTTP_VERSION_1_1, + '2.0' => CURL_HTTP_VERSION_2_0, + '3.0' => CURL_HTTP_VERSION_3, + default => null, + }; + + if ($curlVersion !== null) { + $curlOptions[CURLOPT_HTTP_VERSION] = $curlVersion; + } + + return $curlOptions; + } + + /** + * @param array $curlOptions + * @param array $config + * + * @return array + */ + private function applyClientOptions(array $curlOptions, array $config): array + { // Cookie if (isset($config['cookie'])) { $curlOptions[CURLOPT_COOKIEJAR] = $config['cookie']; @@ -606,6 +834,8 @@ protected function setCURLOptions(array $curlOptions = [], array $config = []) * Does the actual work of initializing cURL, setting the options, * and grabbing the output. * + * @param array $curlOptions + * * @codeCoverageIgnore */ protected function sendRequest(array $curlOptions = []): string @@ -621,8 +851,74 @@ protected function sendRequest(array $curlOptions = []): string throw HTTPException::forCurlError((string) curl_errno($ch), curl_error($ch)); } - curl_close($ch); + return $output; + } + + private function removeIntermediateResponses(string $output, string $breakString): string + { + while (true) { + // Check if we should remove the current response + if ($this->shouldRemoveCurrentResponse($output, $breakString)) { + $breakStringPos = strpos($output, $breakString); + if ($breakStringPos !== false) { + $output = substr($output, $breakStringPos + 4); + + continue; + } + } + + // No more intermediate responses to remove + break; + } return $output; } + + /** + * Check if the current response (at the beginning of output) should be removed. + */ + private function shouldRemoveCurrentResponse(string $output, string $breakString): bool + { + // HTTP/x.x 1xx responses (Continue, Processing, etc.) + if (preg_match('/^HTTP\/\d+(?:\.\d+)?\s+1\d\d\s/', $output)) { + return true; + } + + // HTTP/x.x 200 Connection established (proxy responses) + if (preg_match('/^HTTP\/\d+(?:\.\d+)?\s+200\s+Connection\s+established/i', $output)) { + return true; + } + + // HTTP/x.x 3xx responses (redirects) - only if redirects are allowed + $allowRedirects = isset($this->config['allow_redirects']) && $this->config['allow_redirects'] !== false; + if ($allowRedirects && preg_match('/^HTTP\/\d+(?:\.\d+)?\s+3\d\d\s/', $output)) { + // Check if there's a Location header + $breakStringPos = strpos($output, $breakString); + if ($breakStringPos !== false) { + $headerSection = substr($output, 0, $breakStringPos); + $headers = explode("\n", $headerSection); + + foreach ($headers as $header) { + if (str_starts_with(strtolower($header), 'location:')) { + return true; // Found location header, this is a redirect to remove + } + } + } + } + + // Digest auth challenges - only remove if there's another response after + if (isset($this->config['auth'][2]) && $this->config['auth'][2] === 'digest') { + $breakStringPos = strpos($output, $breakString); + if ($breakStringPos !== false) { + $headerSection = substr($output, 0, $breakStringPos); + if (str_contains($headerSection, 'WWW-Authenticate: Digest')) { + $nextBreakPos = strpos($output, $breakString, $breakStringPos + 4); + + return $nextBreakPos !== false; // Only remove if there's another response + } + } + } + + return false; + } } diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 3b18fc8bf1df..acea6b2b5ad6 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -1,5 +1,7 @@ 'baseURI', + 'child-src' => 'childSrc', + 'connect-src' => 'connectSrc', + 'default-src' => 'defaultSrc', + 'font-src' => 'fontSrc', + 'form-action' => 'formAction', + 'frame-ancestors' => 'frameAncestors', + 'frame-src' => 'frameSrc', + 'img-src' => 'imageSrc', + 'media-src' => 'mediaSrc', + 'object-src' => 'objectSrc', + 'plugin-types' => 'pluginTypes', + 'script-src' => 'scriptSrc', + 'style-src' => 'styleSrc', + 'sandbox' => 'sandbox', + 'manifest-src' => 'manifestSrc', + 'script-src-elem' => 'scriptSrcElem', + 'script-src-attr' => 'scriptSrcAttr', + 'style-src-elem' => 'styleSrcElem', + 'style-src-attr' => 'styleSrcAttr', + 'worker-src' => 'workerSrc', + ]; + + /** + * Map of CSP directives to this class's properties. + * + * @var array + */ + protected array $directives = [ + ...self::DIRECTIVES_ALLOWING_SOURCE_LISTS, + 'report-uri' => 'reportURI', + 'report-to' => 'reportTo', + ]; + /** - * Used for security enforcement + * The `base-uri` directive restricts the URLs that can be used to specify the document base URL. * - * @var array|string + * @var array|string|null */ protected $baseURI = []; /** - * Used for security enforcement + * The `child-src` directive governs the creation of nested browsing contexts as well + * as Worker execution contexts. * - * @var array|string + * @var array|string */ protected $childSrc = []; /** - * Used for security enforcement + * The `connect-src` directive restricts which URLs the protected resource can load using script interfaces. * - * @var array + * @var array|string */ protected $connectSrc = []; /** - * Used for security enforcement + * The `default-src` directive sets a default source list for a number of directives. * - * @var array|string + * @var array|string|null */ protected $defaultSrc = []; /** - * Used for security enforcement + * The `font-src` directive restricts from where the protected resource can load fonts. * - * @var array|string + * @var array|string */ protected $fontSrc = []; /** - * Used for security enforcement + * The `form-action` directive restricts which URLs can be used as the action of HTML form elements. * - * @var array|string + * @var array|string */ protected $formAction = []; /** - * Used for security enforcement + * The `frame-ancestors` directive indicates whether the user agent should allow embedding + * the resource using a `frame`, `iframe`, `object`, `embed` or `applet` element, + * or equivalent functionality in non-HTML resources. * - * @var array|string + * @var array|string */ protected $frameAncestors = []; /** - * Used for security enforcement + * The `frame-src` directive restricts the URLs which may be loaded into child navigables. * - * @var array|string + * @var array|string */ protected $frameSrc = []; /** - * Used for security enforcement + * The `img-src` directive restricts from where the protected resource can load images. * - * @var array|string + * @var array|string */ protected $imageSrc = []; /** - * Used for security enforcement + * The `media-src` directive restricts from where the protected resource can load video, + * audio, and associated text tracks. * - * @var array|string + * @var array|string */ protected $mediaSrc = []; /** - * Used for security enforcement + * The `object-src` directive restricts from where the protected resource can load plugins. * - * @var array|string + * @var array|string */ protected $objectSrc = []; /** - * Used for security enforcement + * The `plugin-types` directive restricts the set of plugins that can be invoked by the + * protected resource by limiting the types of resources that can be embedded. * - * @var array|string + * @var array|string */ protected $pluginTypes = []; /** - * Used for security enforcement + * The `script-src` directive restricts which scripts the protected resource can execute. * - * @var string + * @var array|string */ - protected $reportURI; + protected $scriptSrc = []; /** - * Used for security enforcement + * The `style-src` directive restricts which styles the user may applies to the protected resource. * - * @var array|string + * @var array|string */ - protected $sandbox = []; + protected $styleSrc = []; /** - * Used for security enforcement + * The `sandbox` directive specifies an HTML sandbox policy that the user agent applies to the protected resource. * - * @var array|string + * @var array|string */ - protected $scriptSrc = []; + protected $sandbox = []; /** - * Used for security enforcement + * The `report-uri` directive specifies a URL to which the user agent sends reports about policy violation. * - * @var array|string + * @var string|null */ - protected $styleSrc = []; + protected $reportURI; + + /** + * The `report-to` directive specifies a named group in a Reporting API + * endpoint to which the user agent sends reports about policy violation. + */ + protected ?string $reportTo = null; + + // -------------------------------------------------------------- + // CSP Level 3 Directives + // -------------------------------------------------------------- /** - * Used for security enforcement + * The `manifest-src` directive restricts the URLs from which application manifests may be loaded. * - * @var array|string + * @var array|string */ protected $manifestSrc = []; /** - * Used for security enforcement + * The `script-src-elem` directive applies to all script requests and script blocks. + * + * @var array|string + */ + protected array|string $scriptSrcElem = []; + + /** + * The `script-src-attr` directive applies to event handlers and, if present, + * it will override the `script-src` directive for relevant checks. + * + * @var array|string + */ + protected array|string $scriptSrcAttr = []; + + /** + * The `style-src-elem` directive governs the behaviour of styles except + * for styles defined in inline attributes. + * + * @var array|string + */ + protected array|string $styleSrcElem = []; + + /** + * The `style-src-attr` directive governs the behaviour of style attributes. + * + * @var array|string + */ + protected array|string $styleSrcAttr = []; + + /** + * The `worker-src` directive restricts the URLs which may be loaded as a `Worker`, + * `SharedWorker`, or `ServiceWorker`. + * + * @var array|string + */ + protected array|string $workerSrc = []; + + /** + * Instructs user agents to rewrite URL schemes by changing HTTP to HTTPS. * * @var bool */ protected $upgradeInsecureRequests = false; /** - * Used for security enforcement + * Set to `true` to make all directives report-only instead of enforced. * * @var bool */ protected $reportOnly = false; /** - * Used for security enforcement + * Set of valid keyword-sources. * - * @var array + * @see https://www.w3.org/TR/CSP3/#source-expression + * + * @var list */ protected $validSources = [ + // CSP2 keywords 'self', 'none', 'unsafe-inline', 'unsafe-eval', + // CSP3 keywords + 'strict-dynamic', + 'unsafe-hashes', + 'report-sample', + 'unsafe-allow-redirects', + 'wasm-unsafe-eval', + 'trusted-types-eval', + 'report-sha256', + 'report-sha384', + 'report-sha512', ]; /** - * Used for security enforcement + * Set of nonces generated. + * + * @var list * - * @var array + * @deprecated 4.7.0 Never used. */ protected $nonces = []; /** - * An array of header info since we have - * to build ourself before passing to Response. + * Nonce for style tags. + * + * @var string|null + */ + protected $styleNonce; + + /** + * Nonce for script tags. + * + * @var string|null + */ + protected $scriptNonce; + + /** + * Nonce placeholder for style tags. + * + * @var string + */ + protected $styleNonceTag = '{csp-style-nonce}'; + + /** + * Nonce placeholder for script tags. + * + * @var string + */ + protected $scriptNonceTag = '{csp-script-nonce}'; + + /** + * Replace nonce tags automatically? * - * @var array + * @var bool + */ + protected $autoNonce = true; + + /** + * An array of header info since we have to build + * ourselves before passing to a Response object. + * + * @var array */ protected $tempHeaders = []; /** - * An array of header info to build - * that should only be reported. + * An array of header info to build that should only be reported. * - * @var array + * @var array */ protected $reportOnlyHeaders = []; /** - * Constructor. + * Whether Content Security Policy is being enforced. + * + * @var bool + */ + protected $CSPEnabled = false; + + /** + * Map of reporting endpoints to their URLs. * + * @var array + */ + private array $reportingEndpoints = []; + + /** * Stores our default values from the Config file. */ public function __construct(ContentSecurityPolicyConfig $config) { + $this->CSPEnabled = config(App::class)->CSPEnabled; + foreach (get_object_vars($config) as $setting => $value) { - if (property_exists($this, $setting)) { - $this->{$setting} = $value; + if (! property_exists($this, $setting)) { + continue; } + + if ( + in_array($setting, self::DIRECTIVES_ALLOWING_SOURCE_LISTS, true) + && is_array($value) + && array_is_list($value) + ) { + // Config sets these directives as `list|string` + // but we need them as `array` internally. + $this->{$setting} = array_combine($value, array_fill(0, count($value), $this->reportOnly)); + + continue; + } + + $this->{$setting} = $value; + } + + if (! is_array($this->styleSrc)) { + $this->styleSrc = [$this->styleSrc => $this->reportOnly]; + } + + if (! is_array($this->scriptSrc)) { + $this->scriptSrc = [$this->scriptSrc => $this->reportOnly]; } } + /** + * Whether Content Security Policy is being enforced. + */ + public function enabled(): bool + { + return $this->CSPEnabled; + } + + /** + * Get the nonce for the style tag. + */ + public function getStyleNonce(): string + { + if ($this->styleNonce === null) { + $this->styleNonce = base64_encode(random_bytes(12)); + $this->addStyleSrc('nonce-' . $this->styleNonce); + + if ($this->styleSrcElem !== []) { + $this->addStyleSrcElem('nonce-' . $this->styleNonce); + } + } + + return $this->styleNonce; + } + + /** + * Get the nonce for the script tag. + */ + public function getScriptNonce(): string + { + if ($this->scriptNonce === null) { + $this->scriptNonce = base64_encode(random_bytes(12)); + $this->addScriptSrc('nonce-' . $this->scriptNonce); + + if ($this->scriptSrcElem !== []) { + $this->addScriptSrcElem('nonce-' . $this->scriptNonce); + } + } + + return $this->scriptNonce; + } + /** * Compiles and sets the appropriate headers in the request. * * Should be called just prior to sending the response to the user agent. + * + * @return void */ - public function finalize(ResponseInterface &$response) + public function finalize(ResponseInterface $response) { $this->generateNonces($response); + $this->buildHeaders($response); } @@ -234,13 +457,13 @@ public function reportOnly(bool $value = true) } /** - * Adds a new base_uri value. Can be either a URI class or a simple string. + * Adds a new value to the `base-uri` directive. * - * base_uri restricts the URLs that can appear in a page’s element. + * `base-uri` restricts the URLs that can appear in a page's element. * * @see http://www.w3.org/TR/CSP/#directive-base-uri * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -252,16 +475,15 @@ public function addBaseURI($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `child-src` directive. * - * child-src lists the URLs for workers and embedded frame contents. + * `child-src` lists the URLs for workers and embedded frame contents. * For example: child-src https://youtube.com would enable embedding * videos from YouTube but not from other origins. * * @see http://www.w3.org/TR/CSP/#directive-child-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -273,15 +495,14 @@ public function addChildSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `connect-src` directive. * - * connect-src limits the origins to which you can connect + * `connect-src` limits the origins to which you can connect * (via XHR, WebSockets, and EventSource). * * @see http://www.w3.org/TR/CSP/#directive-connect-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -293,15 +514,14 @@ public function addConnectSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `default-src` directive. * - * default_src is the URI that is used for many of the settings when + * `default-src` is the URI that is used for many of the settings when * no other source has been set. * * @see http://www.w3.org/TR/CSP/#directive-default-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -313,14 +533,13 @@ public function setDefaultSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `font-src` directive. * - * font-src specifies the origins that can serve web fonts. + * `font-src` specifies the origins that can serve web fonts. * * @see http://www.w3.org/TR/CSP/#directive-font-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -332,12 +551,11 @@ public function addFontSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `form-action` directive. * * @see http://www.w3.org/TR/CSP/#directive-form-action * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -349,12 +567,11 @@ public function addFormAction($uri, ?bool $explicitReporting = null) } /** - * Adds a new resource that should allow embedding the resource using - * ,